diff --git a/src/main/java/com/limechain/babe/state/EpochState.java b/src/main/java/com/limechain/babe/state/EpochState.java index 49ab23757..23cadecb3 100644 --- a/src/main/java/com/limechain/babe/state/EpochState.java +++ b/src/main/java/com/limechain/babe/state/EpochState.java @@ -18,23 +18,29 @@ @Getter @Component public class EpochState { + + private boolean isInitialized = false; + + private long disabledAuthority; private BigInteger slotDuration; private BigInteger epochLength; + private BigInteger genesisSlotNumber; + private EpochData currentEpochData; private EpochDescriptor currentEpochDescriptor; + private EpochData nextEpochData; private EpochDescriptor nextEpochDescriptor; - private long disabledAuthority; - private BigInteger genesisSlotNumber; public void initialize(BabeApiConfiguration babeApiConfiguration) { this.slotDuration = babeApiConfiguration.getSlotDuration(); this.epochLength = babeApiConfiguration.getEpochLength(); this.currentEpochData = new EpochData(babeApiConfiguration.getAuthorities(), babeApiConfiguration.getRandomness()); this.currentEpochDescriptor = new EpochDescriptor(babeApiConfiguration.getConstant(), babeApiConfiguration.getAllowedSlots()); + this.isInitialized = true; } - //TODO: This will be fixed in https://github.com/LimeChain/Fruzhin/issues/593 + //TODO: Call this form the BlockImporter class when it it created! public void updateNextEpochBlockConfig(byte[] message) { BabeConsensusMessage babeConsensusMessage = ScaleUtils.Decode.decode(message, new BabeConsensusMessageReader()); switch (babeConsensusMessage.getFormat()) { @@ -44,6 +50,16 @@ public void updateNextEpochBlockConfig(byte[] message) { } } + public void setGenesisSlotNumber(BigInteger retrievedGenesisSlotNumber) { + if (retrievedGenesisSlotNumber != null) { + this.genesisSlotNumber = retrievedGenesisSlotNumber; + } else { + // If retrieved genesis slot number is null, then there are no executed + // blocks on the chain and current slot number should be the genesis + this.genesisSlotNumber = getCurrentSlotNumber(); + } + } + public BigInteger getCurrentSlotNumber() { return BigInteger.valueOf(Instant.now().toEpochMilli()).divide(slotDuration); } diff --git a/src/main/java/com/limechain/network/protocol/blockannounce/BlockAnnounceEngine.java b/src/main/java/com/limechain/network/protocol/blockannounce/BlockAnnounceEngine.java index bf9adb814..ee4a2e26d 100644 --- a/src/main/java/com/limechain/network/protocol/blockannounce/BlockAnnounceEngine.java +++ b/src/main/java/com/limechain/network/protocol/blockannounce/BlockAnnounceEngine.java @@ -91,8 +91,6 @@ private void handleBlockAnnounce(byte[] msg, PeerId peerId) { if (BlockState.getInstance().isInitialized()) { BlockState.getInstance().addBlockToBlockTree(announce.getHeader()); } - //TODO: This will be fixed in https://github.com/LimeChain/Fruzhin/issues/593 -// digestHelper.handleHeaderDigests(announce.getHeader().getDigest()); } public void writeHandshakeToStream(Stream stream, PeerId peerId) { diff --git a/src/main/java/com/limechain/rpc/config/CommonConfig.java b/src/main/java/com/limechain/rpc/config/CommonConfig.java index c629d3c77..3838bfd83 100644 --- a/src/main/java/com/limechain/rpc/config/CommonConfig.java +++ b/src/main/java/com/limechain/rpc/config/CommonConfig.java @@ -95,8 +95,8 @@ public WarpSyncMachine warpSyncMachine(Network network, ChainService chainServic } @Bean - public FullSyncMachine fullSyncMachine(Network network, SyncState syncState) { - return new FullSyncMachine(network, syncState); + public FullSyncMachine fullSyncMachine(HostConfig hostConfig, Network network, SyncState syncState) { + return new FullSyncMachine(hostConfig, network, syncState); } @Bean diff --git a/src/main/java/com/limechain/runtime/Runtime.java b/src/main/java/com/limechain/runtime/Runtime.java index 6b096c414..8b5186f9d 100644 --- a/src/main/java/com/limechain/runtime/Runtime.java +++ b/src/main/java/com/limechain/runtime/Runtime.java @@ -10,6 +10,8 @@ import com.limechain.runtime.version.scale.RuntimeVersionReader; import com.limechain.sync.fullsync.inherents.InherentData; import com.limechain.sync.fullsync.inherents.scale.InherentDataWriter; +import com.limechain.trie.structure.nibble.Nibbles; +import com.limechain.utils.LittleEndianUtils; import com.limechain.utils.scale.ScaleUtils; import lombok.AccessLevel; import lombok.AllArgsConstructor; @@ -20,6 +22,8 @@ import org.wasmer.Instance; import org.wasmer.Module; +import java.math.BigInteger; +import java.util.Optional; import java.util.logging.Level; @Log @@ -65,6 +69,15 @@ private byte[] callInner(RuntimeEndpoint function, RuntimePointerSize parameterP return context.getSharedMemory().readData(responsePtrSize); } + private Optional findStorageValue(Nibbles key) { + return this.context.trieAccessor.findStorageValue(key); + } + + public BigInteger getGenesisSlotNumber() { + var optGenesisSlotBytes = this.findStorageValue(RuntimeStorageKey.GENESIS_SLOT.getNibbles()); + return optGenesisSlotBytes.map(LittleEndianUtils::fromLittleEndianByteArray).orElse(null); + } + /** * @return the {@link BabeApiConfiguration}. */ diff --git a/src/main/java/com/limechain/runtime/RuntimeStorageKey.java b/src/main/java/com/limechain/runtime/RuntimeStorageKey.java new file mode 100644 index 000000000..d103640dd --- /dev/null +++ b/src/main/java/com/limechain/runtime/RuntimeStorageKey.java @@ -0,0 +1,19 @@ +package com.limechain.runtime; + +import com.limechain.trie.structure.nibble.Nibbles; +import lombok.AllArgsConstructor; +import lombok.Getter; + +// All available keys can be found here: https://www.shawntabrizi.com/substrate-known-keys/ +// Keys are encoded using 'xxHash' algorithm, and we use directly the encoded value +@Getter +@AllArgsConstructor +public enum RuntimeStorageKey { + GENESIS_SLOT("0x1cb6f36e027abb2091cfb5110ab5087f678711d15ebbceba5cd0cea158e6675a"); + + private final String encodedKey; + + public Nibbles getNibbles() { + return Nibbles.fromHexString(this.getEncodedKey()); + } +} diff --git a/src/main/java/com/limechain/sync/fullsync/FullSyncMachine.java b/src/main/java/com/limechain/sync/fullsync/FullSyncMachine.java index b3654c995..70f9b5062 100644 --- a/src/main/java/com/limechain/sync/fullsync/FullSyncMachine.java +++ b/src/main/java/com/limechain/sync/fullsync/FullSyncMachine.java @@ -1,9 +1,11 @@ package com.limechain.sync.fullsync; import com.google.protobuf.ByteString; +import com.limechain.config.HostConfig; import com.limechain.exception.storage.BlockNodeNotFoundException; import com.limechain.exception.sync.BlockExecutionException; import com.limechain.network.Network; +import com.limechain.network.protocol.blockannounce.NodeRole; import com.limechain.network.protocol.sync.BlockRequestDto; import com.limechain.network.protocol.sync.pb.SyncMessage; import com.limechain.network.protocol.warp.dto.Block; @@ -15,7 +17,6 @@ import com.limechain.network.protocol.warp.scale.reader.BlockHeaderReader; import com.limechain.rpc.server.AppBean; import com.limechain.runtime.Runtime; -import com.limechain.babe.api.BabeApiConfiguration; import com.limechain.babe.state.EpochState; import com.limechain.runtime.builder.RuntimeBuilder; import com.limechain.runtime.version.StateVersion; @@ -51,6 +52,8 @@ @Getter @Log public class FullSyncMachine { + + private final HostConfig hostConfig; private final Network networkService; private final SyncState syncState; private final BlockState blockState = BlockState.getInstance(); @@ -59,7 +62,8 @@ public class FullSyncMachine { private final EpochState epochState = AppBean.getBean(EpochState.class); private Runtime runtime = null; - public FullSyncMachine(final Network networkService, final SyncState syncState) { + public FullSyncMachine(HostConfig hostConfig, Network networkService, SyncState syncState) { + this.hostConfig = hostConfig; this.networkService = networkService; this.syncState = syncState; } @@ -81,8 +85,6 @@ public void start() { runtime = buildRuntimeFromState(trieAccessor); StateVersion runtimeStateVersion = runtime.getVersion().getStateVersion(); - BabeApiConfiguration babeApiConfiguration = runtime.callBabeApiConfiguration(); - epochState.initialize(babeApiConfiguration); trieAccessor.setCurrentStateVersion(runtimeStateVersion); byte[] calculatedMerkleRoot = trieAccessor.getMerkleRoot(runtimeStateVersion); @@ -108,9 +110,18 @@ public void start() { receivedBlocks = requestBlocks(startNumber, blocksToFetch); } + if (NodeRole.AUTHORING.equals(hostConfig.getNodeRole())) { + initializeEpochState(); + } + blockState.setFullSyncFinished(true); } + private void initializeEpochState() { + epochState.initialize(runtime.callBabeApiConfiguration()); + epochState.setGenesisSlotNumber(runtime.getGenesisSlotNumber()); + } + private TrieStructure loadStateAtBlockFromPeer(Hash256 lastFinalizedBlockHash) { log.info("Loading state at block from peer"); Map kvps = makeStateRequest(lastFinalizedBlockHash); 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 948055eec..a658c2b4b 100644 --- a/src/test/java/com/limechain/network/protocol/warp/DigestHelperTest.java +++ b/src/test/java/com/limechain/network/protocol/warp/DigestHelperTest.java @@ -1,6 +1,7 @@ package com.limechain.network.protocol.warp; -import com.limechain.babe.state.EpochState; +import com.limechain.babe.consensus.BabeConsensusMessageFormat; +import com.limechain.babe.predigest.PreDigestType; import com.limechain.network.protocol.warp.dto.BlockHeader; import com.limechain.network.protocol.warp.dto.ConsensusEngine; import com.limechain.network.protocol.warp.dto.DigestType; @@ -10,49 +11,108 @@ import io.emeraldpay.polkaj.schnorrkel.SchnorrkelException; import io.emeraldpay.polkaj.types.Hash256; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; import java.math.BigInteger; import java.security.SecureRandom; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; -@ExtendWith(MockitoExtension.class) -public class DigestHelperTest { - - @InjectMocks - private DigestHelper digestHelper; - - @Mock - private EpochState epochState; - - //TODO: This will be fixed in https://github.com/LimeChain/Fruzhin/issues/593 -// @Test -// void handleHeaderDigests_WithConsensusAndPreRuntimeDigests_ShouldProcessBothCorrectly() { -// byte[] consensusMessage = new byte[]{1, 2, 3}; -// byte[] preRuntimeMessage = StringUtils.hexToBytes("0x03b7010000286f301100000000424dc7bc71c9b0f3a15b6aba389471fff57e154dbf3dc046f47513a38375ce4cfe421df446b2b7a0f5b1b2e69c810a6ba36787c0301e6636df3e45688116e005363dfe9922b3b7cc7b80e2fdab8b0b98dcfb4b96cc470fb35f753debda40aa0e"); -// -// HeaderDigest consensusDigest = mock(HeaderDigest.class); -// HeaderDigest preRuntimeDigest = mock(HeaderDigest.class); -// -// when(consensusDigest.getType()).thenReturn(DigestType.CONSENSUS_MESSAGE); -// when(consensusDigest.getId()).thenReturn(ConsensusEngine.BABE); -// when(consensusDigest.getMessage()).thenReturn(consensusMessage); -// -// when(preRuntimeDigest.getType()).thenReturn(DigestType.PRE_RUNTIME); -// when(preRuntimeDigest.getId()).thenReturn(ConsensusEngine.BABE); -// when(preRuntimeDigest.getMessage()).thenReturn(preRuntimeMessage); -// -// HeaderDigest[] headerDigests = {consensusDigest, preRuntimeDigest}; -// -// digestHelper.handleHeaderDigests(headerDigests); -// -// verify(epochState).updateNextEpochBlockConfig(consensusMessage); -// } +class DigestHelperTest { + + @Test + void getBabeConsensusMessageTest() { + HeaderDigest consensusDigest = new HeaderDigest(); + consensusDigest.setId(ConsensusEngine.BABE); + consensusDigest.setType(DigestType.CONSENSUS_MESSAGE); + + // Create a consensus message with type DISABLED_AUTHORITY(2) and value equal to 0 + var message = new byte[33]; + message[0] = 2; + consensusDigest.setMessage(message); + + HeaderDigest[] headerDigests = new HeaderDigest[] {consensusDigest}; + var optResult = DigestHelper.getBabeConsensusMessage(headerDigests); + + assertTrue(optResult.isPresent()); + + var result = optResult.get(); + assertEquals(BabeConsensusMessageFormat.DISABLED_AUTHORITY, result.getFormat()); + assertEquals(0, result.getDisabledAuthority()); + assertNull(result.getNextEpochData()); + assertNull(result.getNextEpochDescriptor()); + } + + @Test + void getBabeConsensusMessageWithoutSuchDigestInHeadersTest() { + var optResult = DigestHelper.getBabeConsensusMessage(new HeaderDigest[0]); + assertTrue(optResult.isEmpty()); + } + + @Test + void getBabePreRuntimeDigestForPrimarySlotTest() { + HeaderDigest consensusDigest = new HeaderDigest(); + consensusDigest.setId(ConsensusEngine.BABE); + consensusDigest.setType(DigestType.PRE_RUNTIME); + + // Create a PreRuntimeDigest with: + // Type -> BABE_PRIMARY(1) + // Authority index -> 0 + // Slot number -> 0 + // VRF_OUTPUT -> byte array with 32 zeros + // VRF_PROOF -> byte array with 64 zeros + var message = new byte[193]; + message[0] = 1; + consensusDigest.setMessage(message); + + HeaderDigest[] headerDigests = new HeaderDigest[] {consensusDigest}; + var optResult = DigestHelper.getBabePreRuntimeDigest(headerDigests); + + assertTrue(optResult.isPresent()); + + var result = optResult.get(); + assertEquals(PreDigestType.BABE_PRIMARY, result.getType()); + assertEquals(0, result.getAuthorityIndex()); + assertEquals(BigInteger.ZERO, result.getSlotNumber()); + assertArrayEquals(new byte[32], result.getVrfOutput()); + assertArrayEquals(new byte[64], result.getVrfProof()); + } + + @Test + void getBabePreRuntimeDigestForSecondaryPlainSlotTest() { + HeaderDigest consensusDigest = new HeaderDigest(); + consensusDigest.setId(ConsensusEngine.BABE); + consensusDigest.setType(DigestType.PRE_RUNTIME); + + // Create a PreRuntimeDigest with: + // Type -> BABE_SECONDARY_PLAIN(2) + // Authority index -> 0 + // Slot number -> 0 + var message = new byte[97]; + message[0] = 2; + consensusDigest.setMessage(message); + + HeaderDigest[] headerDigests = new HeaderDigest[] {consensusDigest}; + var optResult = DigestHelper.getBabePreRuntimeDigest(headerDigests); + + assertTrue(optResult.isPresent()); + + var result = optResult.get(); + assertEquals(PreDigestType.BABE_SECONDARY_PLAIN, result.getType()); + assertEquals(0, result.getAuthorityIndex()); + assertEquals(BigInteger.ZERO, result.getSlotNumber()); + assertNull(result.getVrfOutput()); + assertNull(result.getVrfProof()); + } + + @Test + void getBabePreRuntimeDigestWithoutSuchDigestInHeadersTest() { + var optResult = DigestHelper.getBabePreRuntimeDigest(new HeaderDigest[0]); + assertTrue(optResult.isEmpty()); + } @Test void testBuildSealHeaderDigest() throws SchnorrkelException { @@ -72,7 +132,7 @@ void testBuildSealHeaderDigest() throws SchnorrkelException { Schnorrkel.KeyPair keyPair = Schnorrkel.getInstance().generateKeyPair(new SecureRandom()); - HeaderDigest sealHeaderDigest = digestHelper.buildSealHeaderDigest(blockHeader, keyPair); + HeaderDigest sealHeaderDigest = DigestHelper.buildSealHeaderDigest(blockHeader, keyPair); assertNotNull(sealHeaderDigest); assertEquals(DigestType.SEAL, sealHeaderDigest.getType());