Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

680: Persist and Fetch GRANDPA Data with Refactoring from Previous Class #681

Merged
merged 7 commits into from
Jan 13, 2025
Merged
100 changes: 93 additions & 7 deletions src/main/java/com/limechain/grandpa/state/RoundState.java
Original file line number Diff line number Diff line change
@@ -1,31 +1,41 @@
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 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;

import static com.limechain.storage.StateUtil.*;
hMitov marked this conversation as resolved.
Show resolved Hide resolved

/**
* Represents the state information for the current round and authorities that are needed
* for block finalization with GRANDPA.
* Note: Intended for use only when the host is configured as an Authoring Node.
*/
@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 +45,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 +64,83 @@ public BigInteger getThreshold() {
}

private BigInteger getAuthoritiesTotalWeight() {
return voters.stream()
.map(Authority::getWeight)
.reduce(BigInteger.ZERO, BigInteger::add);
return authorities.stream().map(Authority::getWeight).reduce(BigInteger.ZERO, BigInteger::add);
hMitov marked this conversation as resolved.
Show resolved Hide resolved
}

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(generateAuthorityKey(DBConstants.AUTHORITY_SET, setId), authorities);
}

public Authority[] fetchGrandpaVoters() {
hMitov marked this conversation as resolved.
Show resolved Hide resolved
return repository.find(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 fetchLatestRoundNumber() {
hMitov marked this conversation as resolved.
Show resolved Hide resolved
return repository.find(DBConstants.LATEST_ROUND, BigInteger.ONE);
}

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

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

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

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

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

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

public BigInteger incrementAuthoritiesSetId() {
Zurcusa marked this conversation as resolved.
Show resolved Hide resolved
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
16 changes: 10 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 @@ -29,12 +29,16 @@ public class DBConstants {
*/
public static final String HIGHEST_ROUND_AND_SET_ID_KEY = "hrs";


hMitov marked this conversation as resolved.
Show resolved Hide resolved
// 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.NoArgsConstructor;

import java.math.BigInteger;

@NoArgsConstructor
hMitov marked this conversation as resolved.
Show resolved Hide resolved
public final 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 static String prepareKey(String key, String... suffixes) {
StringBuilder sb = new StringBuilder(key);
for (String suffix : suffixes) {
sb.append(suffix);
}

return sb.toString();
}

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

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

public static 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]);
hMitov marked this conversation as resolved.
Show resolved Hide resolved
BigInteger authoritiesSetId = roundState.getSetId();

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