Skip to content

Commit

Permalink
Merge pull request #681 from LimeChain/680-Store-grandpa-data
Browse files Browse the repository at this point in the history
680: Persist and Fetch GRANDPA Data with Refactoring from Previous Class
  • Loading branch information
hMitov authored Jan 13, 2025
2 parents 0ea68e6 + 269aa41 commit f80bd9d
Show file tree
Hide file tree
Showing 13 changed files with 211 additions and 58 deletions.
98 changes: 92 additions & 6 deletions src/main/java/com/limechain/grandpa/state/RoundState.java
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
package com.limechain.grandpa.state;

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.storage.DBConstants;
import com.limechain.storage.KVRepository;
import com.limechain.storage.StateUtil;
import io.libp2p.core.crypto.PubKey;
import jakarta.annotation.PostConstruct;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import org.springframework.stereotype.Component;

import java.math.BigInteger;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
Expand All @@ -20,12 +27,13 @@
*/
@Getter
@Setter //TODO: remove it when initialize() method is implemented
@Component
@RequiredArgsConstructor
public class RoundState {

private static final BigInteger THRESHOLD_DENOMINATOR = BigInteger.valueOf(3);
private final KVRepository<String, Object> repository;

private List<Authority> voters;
private List<Authority> authorities;
private BigInteger setId;
private BigInteger roundNumber;

Expand All @@ -35,6 +43,12 @@ public class RoundState {
private Map<PubKey, SignedVote> pvEquivocations = new ConcurrentHashMap<>();
private Map<PubKey, SignedVote> pcEquivocations = new ConcurrentHashMap<>();


@PostConstruct
public void initialize() {
loadPersistedState();
}

/**
* The threshold is determined as the total weight of authorities
* subtracted by the weight of potentially faulty authorities (one-third of the total weight minus one).
Expand All @@ -48,13 +62,85 @@ public BigInteger getThreshold() {
}

private BigInteger getAuthoritiesTotalWeight() {
return voters.stream()
return authorities.stream()
.map(Authority::getWeight)
.reduce(BigInteger.ZERO, BigInteger::add);
}

public BigInteger derivePrimary() {
var votersCount = BigInteger.valueOf(voters.size());
return roundNumber.remainder(votersCount);
var authoritiesCount = BigInteger.valueOf(authorities.size());
return roundNumber.remainder(authoritiesCount);
}

public void saveGrandpaAuthorities() {
repository.save(StateUtil.generateAuthorityKey(DBConstants.AUTHORITY_SET, setId), authorities);
}

public Authority[] fetchGrandpaAuthorities() {
return repository.find(StateUtil.generateAuthorityKey(DBConstants.AUTHORITY_SET, setId), new Authority[0]);
}

public void saveAuthoritySetId() {
repository.save(DBConstants.SET_ID, setId);
}

public BigInteger fetchAuthoritiesSetId() {
return repository.find(DBConstants.SET_ID, BigInteger.ONE);
}

public void saveLatestRound() {
repository.save(DBConstants.LATEST_ROUND, roundNumber);
}

public BigInteger fetchLatestRound() {
return repository.find(DBConstants.LATEST_ROUND, BigInteger.ONE);
}

public void savePrevotes() {
repository.save(StateUtil.generatePrevotesKey(DBConstants.GRANDPA_PREVOTES, roundNumber, setId), prevotes);
}

public Map<PubKey, Vote> fetchPrevotes() {
return repository.find(StateUtil.generatePrevotesKey(DBConstants.GRANDPA_PREVOTES, roundNumber, setId),
Collections.emptyMap());
}

public void savePrecommits() {
repository.save(StateUtil.generatePrecommitsKey(DBConstants.GRANDPA_PRECOMMITS, roundNumber, setId), precommits);
}

public Map<PubKey, Vote> fetchPrecommits() {
return repository.find(StateUtil.generatePrecommitsKey(DBConstants.GRANDPA_PRECOMMITS, roundNumber, setId),
Collections.emptyMap());
}

private void loadPersistedState() {
this.authorities = Arrays.asList(fetchGrandpaAuthorities());
this.setId = fetchAuthoritiesSetId();
this.roundNumber = fetchLatestRound();
this.precommits = fetchPrecommits();
this.prevotes = fetchPrevotes();
}

public void persistState() {
saveGrandpaAuthorities();
saveAuthoritySetId();
saveLatestRound();
savePrecommits();
savePrevotes();
}

public BigInteger incrementSetId() {
this.setId = setId.add(BigInteger.ONE);
return setId;
}

public void resetRound() {
this.roundNumber = BigInteger.ONE;
}

public void setLightSyncState(LightSyncState initState) {
this.setId = initState.getGrandpaAuthoritySet().getSetId();
this.authorities = Arrays.asList(initState.getGrandpaAuthoritySet().getCurrentAuthorities());
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.limechain.network.protocol.message;

import com.limechain.grandpa.state.RoundState;
import com.limechain.network.protocol.blockannounce.messages.BlockAnnounceMessage;
import com.limechain.network.protocol.grandpa.messages.neighbour.NeighbourMessage;
import com.limechain.network.protocol.warp.dto.BlockHeader;
Expand All @@ -13,11 +14,12 @@ public class ProtocolMessageBuilder {

public NeighbourMessage buildNeighbourMessage() {
SyncState syncState = AppBean.getBean(SyncState.class);
RoundState roundState = AppBean.getBean(RoundState.class);

return new NeighbourMessage(
NEIGHBOUR_MESSAGE_VERSION,
syncState.getLatestRound(),
syncState.getSetId(),
roundState.getRoundNumber(),
roundState.getSetId(),
syncState.getLastFinalizedBlockNumber()
);
}
Expand Down
13 changes: 10 additions & 3 deletions src/main/java/com/limechain/rpc/config/CommonConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import com.limechain.config.HostConfig;
import com.limechain.config.SystemInfo;
import com.limechain.constants.GenesisBlockHash;
import com.limechain.grandpa.state.RoundState;
import com.limechain.network.Network;
import com.limechain.network.PeerMessageCoordinator;
import com.limechain.network.PeerRequester;
Expand Down Expand Up @@ -86,19 +87,25 @@ public Network network(ChainService chainService, HostConfig hostConfig, KVRepos
return new Network(chainService, hostConfig, repository, cliArgs, genesisBlockHash);
}

@Bean
public RoundState roundState(KVRepository<String, Object> repository) {
return new RoundState(repository);
}

@Bean
public WarpSyncState warpSyncState(SyncState syncState,
RoundState roundState,
KVRepository<String, Object> repository,
RuntimeBuilder runtimeBuilder,
PeerRequester requester,
PeerMessageCoordinator messageCoordinator) {
return new WarpSyncState(syncState, repository, runtimeBuilder, requester, messageCoordinator);
return new WarpSyncState(syncState, repository, runtimeBuilder, requester, messageCoordinator, roundState);
}

@Bean
public WarpSyncMachine warpSyncMachine(Network network, ChainService chainService, SyncState syncState,
WarpSyncState warpSyncState) {
return new WarpSyncMachine(network, chainService, syncState, warpSyncState);
WarpSyncState warpSyncState, RoundState roundState) {
return new WarpSyncMachine(network, chainService, syncState, warpSyncState, roundState);
}

@Bean
Expand Down
15 changes: 9 additions & 6 deletions src/main/java/com/limechain/storage/DBConstants.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class DBConstants {
/**
* Key for storing the privateKey for nabu
* */
* Key for storing the privateKey for nabu
*/
public static final String PEER_ID = "nodePeerId";
/**
* Key under which the genesis chain spec is stored
Expand All @@ -32,9 +32,12 @@ public class DBConstants {
// SyncState keys
public static final String LAST_FINALIZED_BLOCK_NUMBER = "ss::lastFinalizedBlockNumber";
public static final String LAST_FINALIZED_BLOCK_HASH = "ss::lastFinalizedBlockHash";
public static final String AUTHORITY_SET = "ss::authoritySet";
public static final String LATEST_ROUND = "ss::latestRound";
public static final String STATE_ROOT = "ss::stateRoot";
public static final String SET_ID = "ss::setId";
// SyncState keys

// GrandpaState keys
public static final String AUTHORITY_SET = "gs::authoritySet";
public static final String LATEST_ROUND = "gs::latestRound";
public static final String SET_ID = "gs::setId";
public static final String GRANDPA_PREVOTES = "gs:grandpaPrevotes";
public static final String GRANDPA_PRECOMMITS = "gs:grandpaPrecommits";
}
6 changes: 6 additions & 0 deletions src/main/java/com/limechain/storage/DBRepository.java
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,12 @@ public synchronized Optional<Object> find(String key) {
return Optional.ofNullable(value);
}

@Override
@SuppressWarnings("unchecked")
public <T> T find(String key, T defaultValue) {
return (T) find(key).orElse(defaultValue);
}

@Override
public synchronized List<byte[]> findKeysByPrefix(String prefixSeek, int limit) {
return findByPrefix(prefixSeek, (long) limit)
Expand Down
11 changes: 11 additions & 0 deletions src/main/java/com/limechain/storage/KVRepository.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,17 @@ public interface KVRepository<K, V> {
*/
Optional<V> find(K key);


/**
* Generic method to fetch a value from the repository with a default fallback if no result is being found.
*
* @param key The key to fetch from the repository.
* @param defaultValue The default value to return if the key is not found.
* @param <T> The type of the value being fetched.
* @return The fetched value or the default if not present.
*/
<T> T find(String key, T defaultValue);

/**
* Deletes a key-value pair from the DB
*
Expand Down
40 changes: 40 additions & 0 deletions src/main/java/com/limechain/storage/StateUtil.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.limechain.storage;

import lombok.experimental.UtilityClass;

import java.math.BigInteger;

@UtilityClass
public class StateUtil {

/**
* Prepares a concatenated key by appending all suffixes to the base key.
* This method builds a key string by appending the provided suffixes sequentially
* to the base key using a {StringBuilder}.
*
* @param key the base(main) part of the key.
* @param suffixes additional parts to be appended to the key.
* @return the concatenated key as a single {String}.
*/
private String prepareKey(String key, String... suffixes) {
StringBuilder sb = new StringBuilder(key);
for (String suffix : suffixes) {
sb.append(suffix);
}

return sb.toString();
}

public String generateAuthorityKey(String authorityKey, BigInteger setId) {
return prepareKey(authorityKey, setId.toString());
}

public String generatePrevotesKey(String grandpaPrevotes, BigInteger roundNumber, BigInteger setId) {
return prepareKey(grandpaPrevotes, roundNumber.toString(), setId.toString());
}

public String generatePrecommitsKey(String precommitsKey, BigInteger roundNumber, BigInteger setId) {
return prepareKey(precommitsKey, roundNumber.toString(), setId.toString());
}

}
28 changes: 3 additions & 25 deletions src/main/java/com/limechain/storage/block/SyncState.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,7 @@ public class SyncState {
private final BigInteger startingBlock;
private final Hash256 genesisBlockHash;
private Hash256 lastFinalizedBlockHash;
@Setter
private Authority[] authoritySet;
private BigInteger latestRound;
private Hash256 stateRoot;
private BigInteger setId;

public SyncState(GenesisBlockHash genesisBlockHashCalculator, KVRepository<String, Object> repository) {
this.genesisBlockHashCalculator = genesisBlockHashCalculator;
Expand All @@ -43,25 +39,18 @@ public SyncState(GenesisBlockHash genesisBlockHashCalculator, KVRepository<Strin
}

private void loadPersistedState() {
this.lastFinalizedBlockNumber =
(BigInteger) repository.find(DBConstants.LAST_FINALIZED_BLOCK_NUMBER).orElse(BigInteger.ZERO);
this.lastFinalizedBlockNumber = repository.find(DBConstants.LAST_FINALIZED_BLOCK_NUMBER, BigInteger.ZERO);
this.lastFinalizedBlockHash = new Hash256(
(byte[]) repository.find(DBConstants.LAST_FINALIZED_BLOCK_HASH).orElse(genesisBlockHash.getBytes()));
this.authoritySet = (Authority[]) repository.find(DBConstants.AUTHORITY_SET).orElse(new Authority[0]);
this.latestRound = (BigInteger) repository.find(DBConstants.LATEST_ROUND).orElse(BigInteger.ONE);
byte[] stateRootBytes = (byte[]) repository.find(DBConstants.STATE_ROOT).orElse(null);
repository.find(DBConstants.LAST_FINALIZED_BLOCK_HASH, genesisBlockHash.getBytes()));
byte[] stateRootBytes = repository.find(DBConstants.STATE_ROOT, null);
this.stateRoot = stateRootBytes != null ? new Hash256(stateRootBytes) : genesisBlockHashCalculator
.getGenesisBlockHeader().getStateRoot();
this.setId = (BigInteger) repository.find(DBConstants.SET_ID).orElse(BigInteger.ZERO);
}

public void persistState() {
repository.save(DBConstants.LAST_FINALIZED_BLOCK_NUMBER, lastFinalizedBlockNumber);
repository.save(DBConstants.LAST_FINALIZED_BLOCK_HASH, lastFinalizedBlockHash.getBytes());
repository.save(DBConstants.AUTHORITY_SET, authoritySet);
repository.save(DBConstants.LATEST_ROUND, latestRound);
repository.save(DBConstants.STATE_ROOT, stateRoot.getBytes());
repository.save(DBConstants.SET_ID, setId);
}

public void finalizeHeader(BlockHeader header) {
Expand Down Expand Up @@ -98,18 +87,7 @@ private static boolean updateBlockState(CommitMessage commitMessage, Block block
return true;
}

public BigInteger incrementSetId() {
this.setId = this.setId.add(BigInteger.ONE);
return setId;
}

public void resetRound() {
this.latestRound = BigInteger.ONE;
}

public void setLightSyncState(LightSyncState initState) {
this.setId = initState.getGrandpaAuthoritySet().getSetId();
setAuthoritySet(initState.getGrandpaAuthoritySet().getCurrentAuthorities());
finalizeHeader(initState.getFinalizedBlockHeader());
}

Expand Down
7 changes: 4 additions & 3 deletions src/main/java/com/limechain/sync/JustificationVerifier.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.limechain.sync;

import com.limechain.chain.lightsyncstate.Authority;
import com.limechain.grandpa.state.RoundState;
import com.limechain.network.protocol.warp.dto.Precommit;
import com.limechain.rpc.server.AppBean;
import com.limechain.runtime.hostapi.dto.Key;
Expand All @@ -27,9 +28,9 @@
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class JustificationVerifier {
public static boolean verify(Precommit[] precommits, BigInteger round) {
SyncState syncState = AppBean.getBean(SyncState.class);
Authority[] authorities = syncState.getAuthoritySet();
BigInteger authoritiesSetId = syncState.getSetId();
RoundState roundState = AppBean.getBean(RoundState.class);
Authority[] authorities = roundState.getAuthorities().toArray(new Authority[0]);
BigInteger authoritiesSetId = roundState.getSetId();

// Implementation from: https://github.com/smol-dot/smoldot
// lib/src/finality/justification/verify.rs
Expand Down
Loading

0 comments on commit f80bd9d

Please sign in to comment.