Skip to content

Commit

Permalink
687 populate grandpa and babe states from runtime (#691)
Browse files Browse the repository at this point in the history
# Description

feat: Add ServiceConsensusState interface with populateFromRuntime()
method

- Introduced `ServiceConsensusState` interface to define the
initializeFromRuntime() method for RoundState and EpochState as
requiring runtime data.
- Implemented populateFromRuntime()
- Integrated these state initializations within FullSyncMachine to
ensure proper synchronization during node startup.
Fixes #687

---------

Co-authored-by: Hristiyan Mitov <[email protected]>
  • Loading branch information
hMitov and Hristiyan Mitov authored Jan 16, 2025
1 parent df4f627 commit d93be7a
Show file tree
Hide file tree
Showing 10 changed files with 84 additions and 43 deletions.
14 changes: 14 additions & 0 deletions src/main/java/com/limechain/ServiceConsensusState.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.limechain;

import com.limechain.runtime.Runtime;

/**
* This interface defines states that are initialized by runtime data and updated
* via consensus messages (e.g. BABE, GRANDPA, BEEFY).
*
* <p>Implementing classes initialize state from runtime and update it with consensus messages.</p>
*/
public interface ServiceConsensusState {

void populateDataFromRuntime(Runtime runtime);
}
27 changes: 12 additions & 15 deletions src/main/java/com/limechain/babe/state/EpochState.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
package com.limechain.babe.state;

import com.limechain.babe.api.BabeApiConfiguration;
import com.limechain.ServiceConsensusState;
import com.limechain.babe.consensus.BabeConsensusMessage;
import com.limechain.runtime.Runtime;
import com.limechain.state.AbstractState;
import com.limechain.storage.KVRepository;
import lombok.Getter;
Expand All @@ -19,7 +20,7 @@
@Getter
@Component
@RequiredArgsConstructor
public class EpochState extends AbstractState {
public class EpochState extends AbstractState implements ServiceConsensusState {

private final KVRepository<String, Object> repository;

Expand All @@ -35,10 +36,15 @@ public class EpochState extends AbstractState {
private EpochDescriptor nextEpochDescriptor;

@Override
public void initialize() {
initialized = true;
//TODO: Find a way to initiate a runtime instance during node startup to populate from genesis.
//Epoch data gets populated via a runtime call at the end of the syncing process.
public void populateDataFromRuntime(Runtime runtime) {
var babeApiConfiguration = runtime.getBabeApiConfiguration();
this.slotDuration = babeApiConfiguration.getSlotDuration();
this.epochLength = babeApiConfiguration.getEpochLength();
this.currentEpochData = new EpochData(
babeApiConfiguration.getAuthorities(), babeApiConfiguration.getRandomness());
this.currentEpochDescriptor = new EpochDescriptor(
babeApiConfiguration.getConstant(), babeApiConfiguration.getAllowedSlots());
setGenesisSlotNumber(runtime.getGenesisSlotNumber());
}

@Override
Expand All @@ -51,15 +57,6 @@ public void persistState() {
//TODO: Add methods to store epoch state data.
}

public void populateDataFromRuntime(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());
}

public void updateNextEpochConfig(BabeConsensusMessage message) {
switch (message.getFormat()) {
case NEXT_EPOCH_DATA -> this.nextEpochData = message.getNextEpochData();
Expand Down
4 changes: 3 additions & 1 deletion src/main/java/com/limechain/client/LightClient.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.limechain.client;

import com.limechain.grandpa.state.RoundState;
import com.limechain.network.NetworkService;
import com.limechain.rpc.server.AppBean;
import com.limechain.storage.block.state.BlockState;
Expand All @@ -21,6 +22,7 @@ public LightClient() {
Objects.requireNonNull(AppBean.getBean(SyncService.class))),
List.of(
Objects.requireNonNull(AppBean.getBean(BlockState.class)),
Objects.requireNonNull(AppBean.getBean(SyncState.class))));
Objects.requireNonNull(AppBean.getBean(SyncState.class)),
Objects.requireNonNull(AppBean.getBean(RoundState.class))));
}
}
11 changes: 7 additions & 4 deletions src/main/java/com/limechain/grandpa/state/RoundState.java
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package com.limechain.grandpa.state;

import com.limechain.ServiceConsensusState;
import com.limechain.chain.lightsyncstate.Authority;
import com.limechain.chain.lightsyncstate.LightSyncState;
import com.limechain.network.protocol.grandpa.messages.catchup.res.SignedVote;
import com.limechain.network.protocol.grandpa.messages.commit.Vote;
import com.limechain.network.protocol.grandpa.messages.consensus.GrandpaConsensusMessage;
import com.limechain.runtime.Runtime;
import com.limechain.state.AbstractState;
import com.limechain.storage.DBConstants;
import com.limechain.storage.KVRepository;
Expand Down Expand Up @@ -38,7 +40,7 @@
@Setter //TODO: remove it when initialize() method is implemented
@Component
@RequiredArgsConstructor
public class RoundState extends AbstractState {
public class RoundState extends AbstractState implements ServiceConsensusState {

private static final BigInteger THRESHOLD_DENOMINATOR = BigInteger.valueOf(3);
private final KVRepository<String, Object> repository;
Expand All @@ -56,9 +58,10 @@ public class RoundState extends AbstractState {
private Map<PubKey, SignedVote> pcEquivocations = new ConcurrentHashMap<>();

@Override
public void initialize() {
initialized = true;
//TODO: Find a way to initiate a runtime instance during node startup to populate from genesis.
public void populateDataFromRuntime(Runtime runtime) {
this.authorities = runtime.getGrandpaApiGrandpaAuthorities();
this.setId = BigInteger.ZERO;
this.roundNumber = BigInteger.ZERO;
}

@Override
Expand Down
4 changes: 4 additions & 0 deletions src/main/java/com/limechain/runtime/Runtime.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.limechain.babe.api.BabeApiConfiguration;
import com.limechain.babe.api.BlockEquivocationProof;
import com.limechain.babe.api.OpaqueKeyOwnershipProof;
import com.limechain.chain.lightsyncstate.Authority;
import com.limechain.network.protocol.warp.dto.Block;
import com.limechain.network.protocol.warp.dto.BlockHeader;
import com.limechain.rpc.methods.author.dto.DecodedKey;
Expand Down Expand Up @@ -58,4 +59,7 @@ public interface Runtime {
void persistsChanges();

void close();

List<Authority> getGrandpaApiGrandpaAuthorities();

}
9 changes: 8 additions & 1 deletion src/main/java/com/limechain/runtime/RuntimeBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ public Runtime copyRuntime(Runtime original) {
* @param trieAccessor provides access to the trie storage for a given block
* @return a ready to execute `Runtime` instance
*/
public Runtime buildRuntime(byte[] code, @Nullable TrieAccessor trieAccessor) {
private Runtime buildRuntime(byte[] code, @Nullable TrieAccessor trieAccessor) {
var localStorage = new OffchainStore(db, StorageKind.LOCAL);
var persistentStorage = new OffchainStore(db, StorageKind.PERSISTENT);
// TODO:
Expand All @@ -88,4 +88,11 @@ public Runtime buildRuntime(byte[] code, @Nullable TrieAccessor trieAccessor) {

return RuntimeFactory.buildRuntime(code, cfg);
}

public Runtime buildRuntimeFromState(TrieAccessor trieAccessor) {
return trieAccessor
.findStorageValue(Nibbles.fromBytes(":code".getBytes()))
.map(wasm -> buildRuntime(wasm, trieAccessor))
.orElseThrow(() -> new RuntimeException("Runtime code not found in the trie"));
}
}
1 change: 1 addition & 0 deletions src/main/java/com/limechain/runtime/RuntimeEndpoint.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ public enum RuntimeEndpoint {
SESSION_KEYS_GENERATE_SESSION_KEYS("SessionKeys_generate_session_keys"),
SESSION_KEYS_DECODE_SESSION_KEYS("SessionKeys_decode_session_keys"),
TRANSACTION_QUEUE_VALIDATE_TRANSACTION("TaggedTransactionQueue_validate_transaction"),
GRANDPA_API_GRANDPA_AUTHORITIES("GrandpaApi_grandpa_authorities"),
;

private final String name;
Expand Down
15 changes: 14 additions & 1 deletion src/main/java/com/limechain/runtime/RuntimeImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
import com.limechain.babe.api.scale.BabeApiConfigurationReader;
import com.limechain.babe.api.scale.BlockEquivocationProofWriter;
import com.limechain.babe.api.scale.OpaqueKeyOwnershipProofReader;
import com.limechain.chain.lightsyncstate.Authority;
import com.limechain.chain.lightsyncstate.scale.AuthorityReader;
import com.limechain.exception.scale.ScaleEncodingException;
import com.limechain.network.protocol.blockannounce.scale.BlockHeaderScaleWriter;
import com.limechain.network.protocol.transaction.scale.TransactionReader;
Expand All @@ -20,7 +22,11 @@
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.transaction.dto.*;
import com.limechain.transaction.dto.ApplyExtrinsicResult;
import com.limechain.transaction.dto.Extrinsic;
import com.limechain.transaction.dto.ExtrinsicArray;
import com.limechain.transaction.dto.TransactionValidationRequest;
import com.limechain.transaction.dto.TransactionValidationResponse;
import com.limechain.trie.structure.nibble.Nibbles;
import com.limechain.utils.ByteArrayUtils;
import com.limechain.utils.LittleEndianUtils;
Expand All @@ -32,6 +38,7 @@
import com.limechain.utils.scale.writers.TransactionValidationWriter;
import io.emeraldpay.polkaj.scale.ScaleCodecReader;
import io.emeraldpay.polkaj.scale.ScaleCodecWriter;
import io.emeraldpay.polkaj.scale.reader.ListReader;
import io.emeraldpay.polkaj.scale.writer.UInt64Writer;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
Expand Down Expand Up @@ -268,5 +275,11 @@ private byte[] callInner(RuntimeEndpoint function, RuntimePointerSize parameterP
private Optional<byte[]> findStorageValue(Nibbles key) {
return this.context.trieAccessor.findStorageValue(key);
}

@Override
public List<Authority> getGrandpaApiGrandpaAuthorities() {
return ScaleUtils.Decode.decode(call(RuntimeEndpoint.GRANDPA_API_GRANDPA_AUTHORITIES), new ListReader<>(new AuthorityReader()));
}

}

35 changes: 15 additions & 20 deletions src/main/java/com/limechain/sync/fullsync/FullSyncMachine.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@
import com.limechain.babe.coordinator.SlotCoordinator;
import com.limechain.babe.state.EpochState;
import com.limechain.config.HostConfig;
import com.limechain.constants.GenesisBlockHash;
import com.limechain.exception.storage.BlockNodeNotFoundException;
import com.limechain.exception.sync.BlockExecutionException;
import com.limechain.grandpa.state.RoundState;
import com.limechain.network.NetworkService;
import com.limechain.network.PeerMessageCoordinator;
import com.limechain.network.PeerRequester;
Expand All @@ -33,7 +35,6 @@
import com.limechain.trie.TrieStructureFactory;
import com.limechain.trie.structure.TrieStructure;
import com.limechain.trie.structure.database.NodeData;
import com.limechain.trie.structure.nibble.Nibbles;
import com.limechain.utils.scale.ScaleUtils;
import com.limechain.utils.scale.readers.PairReader;
import io.emeraldpay.polkaj.scale.reader.ListReader;
Expand Down Expand Up @@ -67,6 +68,8 @@ public class FullSyncMachine {
private final TrieStorage trieStorage = AppBean.getBean(TrieStorage.class);
private final RuntimeBuilder runtimeBuilder = AppBean.getBean(RuntimeBuilder.class);
private final EpochState epochState = AppBean.getBean(EpochState.class);
private final RoundState roundState = AppBean.getBean(RoundState.class);
private final GenesisBlockHash genesisBlockHash = AppBean.getBean(GenesisBlockHash.class);
private final SlotCoordinator slotCoordinator = AppBean.getBean(SlotCoordinator.class);
private Runtime runtime = null;

Expand Down Expand Up @@ -99,7 +102,7 @@ public void start() {
loadStateAtBlockFromPeer(lastFinalizedBlockHash);
}

runtime = buildRuntimeFromState(trieAccessor);
runtime = runtimeBuilder.buildRuntimeFromState(trieAccessor);
StateVersion runtimeStateVersion = runtime.getCachedVersion().getStateVersion();
trieAccessor.setCurrentStateVersion(runtimeStateVersion);

Expand All @@ -111,6 +114,12 @@ public void start() {

blockState.storeRuntime(lastFinalizedBlockHash, runtime);

Hash256 genesisStateRoot = genesisBlockHash.getGenesisBlockHeader().getStateRoot();
if (stateRoot.equals(genesisStateRoot)) {
epochState.populateDataFromRuntime(runtime);
roundState.populateDataFromRuntime(runtime);
}

int startNumber = syncState.getLastFinalizedBlockNumber()
.add(BigInteger.ONE)
.intValueExact();
Expand All @@ -130,22 +139,15 @@ public void start() {
}

private void finishFullSync() {
initializeStates();
slotCoordinator.start(List.of(
AppBean.getBean(BabeService.class)
));

AbstractState.setSyncMode(SyncMode.HEAD);
messageCoordinator.handshakeBootNodes();
messageCoordinator.handshakePeers();
}

private void initializeStates() {
epochState.populateDataFromRuntime(runtime.getBabeApiConfiguration());
epochState.setGenesisSlotNumber(runtime.getGenesisSlotNumber());

slotCoordinator.start(List.of(
AppBean.getBean(BabeService.class)
));
}

private TrieStructure<NodeData> loadStateAtBlockFromPeer(Hash256 lastFinalizedBlockHash) {
log.info("Loading state at block from peer");
Map<ByteString, ByteString> kvps = makeStateRequest(lastFinalizedBlockHash);
Expand Down Expand Up @@ -233,7 +235,7 @@ private void executeBlocks(List<Block> receivedBlockDatas, TrieAccessor trieAcce

if (blockUpdatedRuntime) {
log.info("Runtime updated, updating the runtime code");
runtime = buildRuntimeFromState(trieAccessor);
runtime = runtimeBuilder.buildRuntimeFromState(trieAccessor);
trieAccessor.setCurrentStateVersion(runtime.getCachedVersion().getStateVersion());
blockState.storeRuntime(blockHeader.getHash(), runtime);
}
Expand All @@ -247,13 +249,6 @@ private void executeBlocks(List<Block> receivedBlockDatas, TrieAccessor trieAcce
}
}

private Runtime buildRuntimeFromState(TrieAccessor trieAccessor) {
return trieAccessor
.findStorageValue(Nibbles.fromBytes(":code".getBytes()))
.map(wasm -> runtimeBuilder.buildRuntime(wasm, trieAccessor))
.orElseThrow(() -> new RuntimeException("Runtime code not found in the trie"));
}

private boolean checkInherents(Block block) {
// Call BlockBuilder_check_inherents to check the inherents of the block
InherentData inherents = new InherentData(System.currentTimeMillis());
Expand Down
7 changes: 6 additions & 1 deletion src/test/java/com/limechain/babe/state/EpochStateTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@


import com.limechain.babe.api.BabeApiConfiguration;
import com.limechain.runtime.Runtime;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
Expand All @@ -23,11 +24,15 @@ public class EpochStateTest {
@Mock
private BabeApiConfiguration babeApiConfiguration;

@Mock
private Runtime runtime;

@Test
public void testGetCurrentSlotNumber() {
BigInteger slotDuration = BigInteger.valueOf(6000);
when(runtime.getBabeApiConfiguration()).thenReturn(babeApiConfiguration);
when(babeApiConfiguration.getSlotDuration()).thenReturn(slotDuration);
epochState.populateDataFromRuntime(babeApiConfiguration);
epochState.populateDataFromRuntime(runtime);

Instant now = Instant.now();
long expectedSlotNumber = now.toEpochMilli() / slotDuration.longValue();
Expand Down

0 comments on commit d93be7a

Please sign in to comment.