diff --git a/src/main/java/com/limechain/network/protocol/grandpa/messages/consensus/GrandpaConsensusMessage.java b/src/main/java/com/limechain/network/protocol/grandpa/messages/consensus/GrandpaConsensusMessage.java new file mode 100644 index 000000000..a29e436ab --- /dev/null +++ b/src/main/java/com/limechain/network/protocol/grandpa/messages/consensus/GrandpaConsensusMessage.java @@ -0,0 +1,16 @@ +package com.limechain.network.protocol.grandpa.messages.consensus; + +import com.limechain.chain.lightsyncstate.Authority; +import lombok.Data; + +import java.math.BigInteger; +import java.util.List; + +@Data +public class GrandpaConsensusMessage { + private BigInteger delayStartBlockNumber; + private List authorities; + private BigInteger disabledAuthority; + private long delay; + private GrandpaConsensusMessageFormat format; +} diff --git a/src/main/java/com/limechain/network/protocol/grandpa/messages/consensus/GrandpaConsensusMessageFormat.java b/src/main/java/com/limechain/network/protocol/grandpa/messages/consensus/GrandpaConsensusMessageFormat.java new file mode 100644 index 000000000..849316da2 --- /dev/null +++ b/src/main/java/com/limechain/network/protocol/grandpa/messages/consensus/GrandpaConsensusMessageFormat.java @@ -0,0 +1,23 @@ +package com.limechain.network.protocol.grandpa.messages.consensus; + +import lombok.Getter; + +@Getter +public enum GrandpaConsensusMessageFormat { + GRANDPA_SCHEDULED_CHANGE(1), GRANDPA_FORCED_CHANGE(2), GRANDPA_ON_DISABLED(3), GRANDPA_PAUSE(4), GRANDPA_RESUME(5); + + private final int format; + + GrandpaConsensusMessageFormat(int format) { + this.format = format; + } + + public static GrandpaConsensusMessageFormat fromFormat(byte format) { + for (GrandpaConsensusMessageFormat messageFormat : values()) { + if (messageFormat.getFormat() == format) { + return messageFormat; + } + } + throw new IllegalArgumentException("Unknown grandpa consensus message format: " + format); + } +} diff --git a/src/main/java/com/limechain/network/protocol/grandpa/messages/consensus/GrandpaConsensusMessageReader.java b/src/main/java/com/limechain/network/protocol/grandpa/messages/consensus/GrandpaConsensusMessageReader.java new file mode 100644 index 000000000..cfaa48cfb --- /dev/null +++ b/src/main/java/com/limechain/network/protocol/grandpa/messages/consensus/GrandpaConsensusMessageReader.java @@ -0,0 +1,40 @@ +package com.limechain.network.protocol.grandpa.messages.consensus; + +import com.limechain.chain.lightsyncstate.Authority; +import com.limechain.chain.lightsyncstate.scale.AuthorityReader; +import io.emeraldpay.polkaj.scale.ScaleCodecReader; +import io.emeraldpay.polkaj.scale.ScaleReader; +import io.emeraldpay.polkaj.scale.reader.ListReader; +import io.emeraldpay.polkaj.scale.reader.UInt64Reader; + +import java.math.BigInteger; +import java.util.List; + +public class GrandpaConsensusMessageReader implements ScaleReader { + + @Override + public GrandpaConsensusMessage read(ScaleCodecReader reader) { + GrandpaConsensusMessage grandpaConsensusMessage = new GrandpaConsensusMessage(); + GrandpaConsensusMessageFormat format = GrandpaConsensusMessageFormat.fromFormat(reader.readByte()); + grandpaConsensusMessage.setFormat(format); + switch (format) { + case GRANDPA_SCHEDULED_CHANGE -> { + List authorities = reader.read(new ListReader<>(new AuthorityReader())); + long delay = reader.readUint32(); + grandpaConsensusMessage.setAuthorities(authorities); + grandpaConsensusMessage.setDelay(delay); + } + case GRANDPA_FORCED_CHANGE -> { + long delayStartBlockNumber = reader.readUint32(); + List authorities = reader.read(new ListReader<>(new AuthorityReader())); + long delay = reader.readUint32(); + grandpaConsensusMessage.setDelayStartBlockNumber(BigInteger.valueOf(delayStartBlockNumber)); + grandpaConsensusMessage.setAuthorities(authorities); + grandpaConsensusMessage.setDelay(delay); + } + case GRANDPA_ON_DISABLED -> grandpaConsensusMessage.setDisabledAuthority(new UInt64Reader().read(reader)); + case GRANDPA_PAUSE, GRANDPA_RESUME -> grandpaConsensusMessage.setDelay(reader.readUint32()); + } + return grandpaConsensusMessage; + } +} diff --git a/src/main/java/com/limechain/network/protocol/warp/DigestHelper.java b/src/main/java/com/limechain/network/protocol/warp/DigestHelper.java index d3c689866..ff9a5e908 100644 --- a/src/main/java/com/limechain/network/protocol/warp/DigestHelper.java +++ b/src/main/java/com/limechain/network/protocol/warp/DigestHelper.java @@ -4,6 +4,8 @@ import com.limechain.babe.consensus.scale.BabeConsensusMessageReader; import com.limechain.babe.predigest.BabePreDigest; import com.limechain.babe.predigest.scale.PreDigestReader; +import com.limechain.network.protocol.grandpa.messages.consensus.GrandpaConsensusMessage; +import com.limechain.network.protocol.grandpa.messages.consensus.GrandpaConsensusMessageReader; import com.limechain.network.protocol.warp.dto.BlockHeader; import com.limechain.network.protocol.warp.dto.ConsensusEngine; import com.limechain.network.protocol.warp.dto.DigestType; @@ -32,6 +34,15 @@ public static Optional getBabeConsensusMessage(HeaderDiges .map(message -> ScaleUtils.Decode.decode(message, new BabeConsensusMessageReader())); } + public static Optional getGrandpaConsensusMessage(HeaderDigest[] headerDigests) { + return Arrays.stream(headerDigests) + .filter(headerDigest -> DigestType.CONSENSUS_MESSAGE.equals(headerDigest.getType()) && + ConsensusEngine.GRANDPA.equals(headerDigest.getId())) + .findFirst() + .map(HeaderDigest::getMessage) + .map(message -> ScaleUtils.Decode.decode(message, new GrandpaConsensusMessageReader())); + } + public static Optional getBabePreRuntimeDigest(HeaderDigest[] headerDigests) { return Arrays.stream(headerDigests) .filter(headerDigest -> DigestType.PRE_RUNTIME.equals(headerDigest.getType()) && diff --git a/src/test/java/com/limechain/network/protocol/grandpa/messages/consensus/GrandpaConsensusMessageReaderTest.java b/src/test/java/com/limechain/network/protocol/grandpa/messages/consensus/GrandpaConsensusMessageReaderTest.java new file mode 100644 index 000000000..6e85ba825 --- /dev/null +++ b/src/test/java/com/limechain/network/protocol/grandpa/messages/consensus/GrandpaConsensusMessageReaderTest.java @@ -0,0 +1,71 @@ +package com.limechain.network.protocol.grandpa.messages.consensus; + +import com.limechain.utils.StringUtils; +import io.emeraldpay.polkaj.scale.ScaleCodecReader; +import org.junit.jupiter.api.Test; + +import java.math.BigInteger; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +class GrandpaConsensusMessageReaderTest { + + private final GrandpaConsensusMessageReader reader = new GrandpaConsensusMessageReader(); + + @Test + void testScheduledChangeInput() { + String hexWithPrefix = "0x0104010101010101010101010101010101010101010101010101010101010101010101020000000000000003000000"; + byte[] input = StringUtils.hexToBytes(hexWithPrefix); + GrandpaConsensusMessage message = reader.read(new ScaleCodecReader(input)); + assertNotNull(message); + assertEquals(GrandpaConsensusMessageFormat.GRANDPA_SCHEDULED_CHANGE, message.getFormat()); + assertNotNull(message.getAuthorities()); + assertEquals(1, message.getAuthorities().size()); + assertEquals(768L, message.getDelay()); + } + + @Test + void testForcedChangeInput() { + String hexWithPrefix = "0x020300000004010101010101010101010101010101010101010101010101010101010101010101020000000000000003000000"; + byte[] input = StringUtils.hexToBytes(hexWithPrefix); + GrandpaConsensusMessage message = reader.read(new ScaleCodecReader(input)); + assertNotNull(message); + assertEquals(GrandpaConsensusMessageFormat.GRANDPA_FORCED_CHANGE, message.getFormat()); + assertEquals(BigInteger.valueOf(3), message.getDelayStartBlockNumber()); + assertNotNull(message.getAuthorities()); + + assertEquals(1, message.getAuthorities().size()); + assertEquals(768L, message.getDelay()); + } + + @Test + void testOnDisabledInput() { + String hexWithPrefix = "0x0315cd5b0700000000"; + byte[] input = StringUtils.hexToBytes(hexWithPrefix); + GrandpaConsensusMessage message = reader.read(new ScaleCodecReader(input)); + assertNotNull(message); + assertEquals(GrandpaConsensusMessageFormat.GRANDPA_ON_DISABLED, message.getFormat()); + assertEquals(BigInteger.valueOf(123456789L), message.getDisabledAuthority()); + } + + @Test + void testPauseInput() { + String hexWithPrefix = "0x0414000000"; + byte[] input = StringUtils.hexToBytes(hexWithPrefix); + GrandpaConsensusMessage message = reader.read(new ScaleCodecReader(input)); + assertNotNull(message); + assertEquals(GrandpaConsensusMessageFormat.GRANDPA_PAUSE, message.getFormat()); + assertEquals(20L, message.getDelay()); + } + + @Test + void testResumeInput() { + String hexWithPrefix = "0x0519000000"; + byte[] input = StringUtils.hexToBytes(hexWithPrefix); + GrandpaConsensusMessage message = reader.read(new ScaleCodecReader(input)); + assertNotNull(message); + assertEquals(GrandpaConsensusMessageFormat.GRANDPA_RESUME, message.getFormat()); + assertEquals(25L, message.getDelay()); + } +} diff --git a/src/test/java/com/limechain/network/protocol/warp/DigestHelperTest.java b/src/test/java/com/limechain/network/protocol/warp/DigestHelperTest.java index a658c2b4b..0d2e6d7e0 100644 --- a/src/test/java/com/limechain/network/protocol/warp/DigestHelperTest.java +++ b/src/test/java/com/limechain/network/protocol/warp/DigestHelperTest.java @@ -2,6 +2,7 @@ import com.limechain.babe.consensus.BabeConsensusMessageFormat; import com.limechain.babe.predigest.PreDigestType; +import com.limechain.network.protocol.grandpa.messages.consensus.GrandpaConsensusMessageFormat; import com.limechain.network.protocol.warp.dto.BlockHeader; import com.limechain.network.protocol.warp.dto.ConsensusEngine; import com.limechain.network.protocol.warp.dto.DigestType; @@ -52,6 +53,34 @@ void getBabeConsensusMessageWithoutSuchDigestInHeadersTest() { assertTrue(optResult.isEmpty()); } + @Test + void getGrandpaConsensusMessageTest() { + HeaderDigest consensusDigest = new HeaderDigest(); + consensusDigest.setId(ConsensusEngine.GRANDPA); + consensusDigest.setType(DigestType.CONSENSUS_MESSAGE); + + // Create a consensus message with type GRANDPA_ON_DISABLED(3) and value equal to 0 + var message = new byte[33]; + message[0] = 3; + consensusDigest.setMessage(message); + + HeaderDigest[] headerDigests = new HeaderDigest[] {consensusDigest}; + var optResult = DigestHelper.getGrandpaConsensusMessage(headerDigests); + + assertTrue(optResult.isPresent()); + + var result = optResult.get(); + assertEquals(GrandpaConsensusMessageFormat.GRANDPA_ON_DISABLED, result.getFormat()); + assertEquals(BigInteger.ZERO, result.getDisabledAuthority()); + assertNull(result.getAuthorities()); + } + + @Test + void getGrandpaConsensusMessageWithoutSuchDigestInHeadersTest() { + var optResult = DigestHelper.getGrandpaConsensusMessage(new HeaderDigest[0]); + assertTrue(optResult.isEmpty()); + } + @Test void getBabePreRuntimeDigestForPrimarySlotTest() { HeaderDigest consensusDigest = new HeaderDigest();