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

399 Attempt to finalize at round #703

Merged
merged 17 commits into from
Jan 23, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 70 additions & 23 deletions src/main/java/com/limechain/grandpa/GrandpaService.java
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,29 @@ public GrandpaService(GrandpaSetState grandpaSetState,
this.peerMessageCoordinator = peerMessageCoordinator;
}

private void attemptToFinalizeAt(GrandpaRound grandpaRound) {
BigInteger lastFinalizedBlockNumber = blockState.getHighestFinalizedNumber();

Vote bestFinalCandidate = grandpaRound.getBestFinalCandidate();

var bestFinalCandidateVotesCount = BigInteger.valueOf(
getObservedVotesForBlock(grandpaRound, bestFinalCandidate.getBlockHash(), Subround.PRECOMMIT)
);

var threshold = grandpaSetState.getThreshold();

if (bestFinalCandidate.getBlockNumber().compareTo(lastFinalizedBlockNumber) >= 0 &&
bestFinalCandidateVotesCount.compareTo(threshold) >= 0) {

BlockHeader header = blockState.getHeader(bestFinalCandidate.getBlockHash());
blockState.setFinalizedHash(header, grandpaRound.getRoundNumber(), grandpaSetState.getSetId());

if (!grandpaRound.isCommitMessageInArchive(bestFinalCandidate)) {
broadcastCommitMessage(grandpaRound);
}
}
}

/**
* Determines if the specified round can be finalized.
* 1) Checks for a valid preVote candidate and ensures it's completable.
Expand All @@ -48,24 +71,20 @@ public GrandpaService(GrandpaSetState grandpaSetState,
*/
private boolean isFinalizable(GrandpaRound grandpaRound) {

Vote preVoteCandidate = getGrandpaGhost(grandpaRound);
Vote preVoteCandidate = findGrandpaGhost(grandpaRound);
if (preVoteCandidate == null) {
return false;
}

grandpaRound.setPreVotedBlock(preVoteCandidate);

if (!isCompletable(grandpaRound)) {
return false;
}

Vote bestFinalCandidate = getBestFinalCandidate(grandpaRound);
Vote bestFinalCandidate = findBestFinalCandidate(grandpaRound);
if (bestFinalCandidate == null) {
return false;
}

grandpaRound.setBestFinalCandidate(bestFinalCandidate);

var prevGrandpaRound = grandpaRound.getPrevious();
if (prevGrandpaRound == null) {
return false;
Expand Down Expand Up @@ -94,10 +113,10 @@ private boolean isCompletable(GrandpaRound grandpaRound) {
.sum();

long equivocationsCount = grandpaRound.getPcEquivocationsCount();
long totalVoters = grandpaSetState.getAuthorities().size();
long threshold = (2 * totalVoters) / 3;
var totalVotesIncludingEquivocations = BigInteger.valueOf(votesCount + equivocationsCount);
var threshold = grandpaSetState.getThreshold();

if (votesCount + equivocationsCount <= threshold) {
if (totalVotesIncludingEquivocations.compareTo(threshold) < 0) {
return false;
}

Expand All @@ -115,7 +134,11 @@ private boolean isCompletable(GrandpaRound grandpaRound) {
Subround.PRECOMMIT
);

if (votesCount - equivocationsCount - observedVotesForDescendantBlock <= threshold) {
var validVotesForThresholdCheck = BigInteger.valueOf(
votesCount - equivocationsCount - observedVotesForDescendantBlock
);

if (validVotesForThresholdCheck.compareTo(threshold) < 0) {
return false;
}
}
Expand All @@ -131,16 +154,20 @@ private boolean isCompletable(GrandpaRound grandpaRound) {
*
* @return the best final candidate block
*/
public Vote getBestFinalCandidate(GrandpaRound grandpaRound) {
private Vote findBestFinalCandidate(GrandpaRound grandpaRound) {

Vote preVoteCandidate = getGrandpaGhost(grandpaRound);
Vote preVoteCandidate = findGrandpaGhost(grandpaRound);

if (grandpaRound.getRoundNumber().equals(BigInteger.ZERO)) {
return preVoteCandidate;
}

var threshold = grandpaSetState.getThreshold();
Map<Hash256, BigInteger> possibleSelectedBlocks = getPossibleSelectedBlocks(grandpaRound, threshold, Subround.PRECOMMIT);
Map<Hash256, BigInteger> possibleSelectedBlocks = getPossibleSelectedBlocks(
grandpaRound,
threshold,
Subround.PRECOMMIT
);

if (possibleSelectedBlocks.isEmpty()) {
return preVoteCandidate;
Expand Down Expand Up @@ -178,6 +205,8 @@ public Vote getBestFinalCandidate(GrandpaRound grandpaRound) {
}
}

grandpaRound.setBestFinalCandidate(bestFinalCandidate);

return bestFinalCandidate;
}

Expand All @@ -188,7 +217,7 @@ public Vote getBestFinalCandidate(GrandpaRound grandpaRound) {
*
* @return GRANDPA GHOST block as a vote
*/
public Vote getGrandpaGhost(GrandpaRound grandpaRound) {
private Vote findGrandpaGhost(GrandpaRound grandpaRound) {
var threshold = grandpaSetState.getThreshold();

if (grandpaRound.getRoundNumber().equals(BigInteger.ZERO)) {
Expand All @@ -201,7 +230,10 @@ public Vote getGrandpaGhost(GrandpaRound grandpaRound) {
throw new GhostExecutionException("GHOST not found");
}

return selectBlockWithMostVotes(blocks);
Vote grandpaGhost = selectBlockWithMostVotes(blocks);
grandpaRound.setPreVotedBlock(grandpaGhost);

return grandpaGhost;
}

/**
Expand All @@ -213,12 +245,13 @@ public Vote getGrandpaGhost(GrandpaRound grandpaRound) {
*
* @return the best pre-voted block
*/
public Vote getBestPreVoteCandidate(GrandpaRound grandpaRound) {
private Vote findBestPreVoteCandidate(GrandpaRound grandpaRound) {

Vote previousBestFinalCandidate = grandpaRound.getPrevious() != null
? grandpaRound.getPrevious().getBestFinalCandidate()
: new Vote(null, BigInteger.ZERO);
Vote currentVote = getGrandpaGhost(grandpaRound);

Vote currentVote = findGrandpaGhost(grandpaRound);
SignedVote primaryVote = grandpaRound.getPrimaryVote();

if (primaryVote != null) {
Expand All @@ -232,7 +265,6 @@ public Vote getBestPreVoteCandidate(GrandpaRound grandpaRound) {
return currentVote;
}


/**
* Selects the block with the most votes from the provided map of blocks.
* If multiple blocks have the same number of votes, it returns the one with the highest block number.
Expand Down Expand Up @@ -265,7 +297,10 @@ private Vote selectBlockWithMostVotes(Map<Hash256, BigInteger> blocks) {
* @param subround stage of the GRANDPA process, such as PREVOTE, PRECOMMIT or PRIMARY_PROPOSAL.
* @return blocks that exceed the required vote threshold
*/
private Map<Hash256, BigInteger> getPossibleSelectedBlocks(GrandpaRound grandpaRound, BigInteger threshold, Subround subround) {
private Map<Hash256, BigInteger> getPossibleSelectedBlocks(GrandpaRound grandpaRound,
BigInteger threshold,
Subround subround) {

var votes = getDirectVotes(grandpaRound, subround);
var blocks = new HashMap<Hash256, BigInteger>();

Expand All @@ -284,7 +319,13 @@ private Map<Hash256, BigInteger> getPossibleSelectedBlocks(GrandpaRound grandpaR
List<Vote> allVotes = getVotes(grandpaRound, subround);
for (Vote vote : votes.keySet()) {
blocks = new HashMap<>(
getPossibleSelectedAncestors(grandpaRound, allVotes, vote.getBlockHash(), blocks, subround, threshold)
getPossibleSelectedAncestors(grandpaRound,
allVotes,
vote.getBlockHash(),
blocks,
subround,
threshold
)
);
}

Expand Down Expand Up @@ -335,7 +376,13 @@ private Map<Hash256, BigInteger> getPossibleSelectedAncestors(GrandpaRound grand

} else {
// Recursively process ancestors
selected = getPossibleSelectedAncestors(grandpaRound, votes, ancestorBlockHash, selected, subround, threshold);
selected = getPossibleSelectedAncestors(grandpaRound,
votes,
ancestorBlockHash,
selected,
subround,
threshold
);
}
}

Expand Down Expand Up @@ -437,8 +484,8 @@ private Vote getLastFinalizedBlockAsVote() {
* 1. As the primary validator, broadcasting a commit message for the best candidate block of the previous round.
* 2. During attempt-to-finalize, broadcasting a commit message for the best candidate block of the current round.
*/
public void broadcastCommitMessage(GrandpaRound grandpaRound) {
Vote bestCandidate = getBestFinalCandidate(grandpaRound);
private void broadcastCommitMessage(GrandpaRound grandpaRound) {
Vote bestCandidate = findBestFinalCandidate(grandpaRound);
PreCommit[] precommits = transformToCompactJustificationFormat(grandpaRound.getPreCommits());

CommitMessage commitMessage = new CommitMessage();
Expand Down
25 changes: 25 additions & 0 deletions src/main/java/com/limechain/grandpa/state/GrandpaRound.java
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
package com.limechain.grandpa.state;

import com.limechain.exception.grandpa.GrandpaGenericException;
import com.limechain.network.protocol.grandpa.messages.catchup.res.SignedVote;
import com.limechain.network.protocol.grandpa.messages.commit.CommitMessage;
import com.limechain.network.protocol.grandpa.messages.commit.Vote;
import io.emeraldpay.polkaj.types.Hash256;
import lombok.Getter;
import lombok.Setter;

import java.io.Serializable;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
Expand All @@ -29,6 +33,18 @@ public class GrandpaRound implements Serializable {
private Map<Hash256, Set<SignedVote>> pvEquivocations = new ConcurrentHashMap<>();
private Map<Hash256, Set<SignedVote>> pcEquivocations = new ConcurrentHashMap<>();

private List<CommitMessage> commitMessagesArchive = new ArrayList<>();

public Vote getPreVotedBlock() {
if (preVotedBlock == null) throw new GrandpaGenericException("Pre-voted block has not been set");
return preVotedBlock;
}

public Vote getBestFinalCandidate() {
if (bestFinalCandidate == null) throw new GrandpaGenericException("Best final candidate has not been set");
return bestFinalCandidate;
}

public long getPvEquivocationsCount() {
return this.pvEquivocations.values().stream()
.mapToLong(Set::size)
Expand All @@ -40,4 +56,13 @@ public long getPcEquivocationsCount() {
.mapToInt(Set::size)
.sum();
}

public void addCommitMessageToArchive(CommitMessage message) {
commitMessagesArchive.add(message);
}

public boolean isCommitMessageInArchive(Vote vote) {
return commitMessagesArchive.stream()
.anyMatch(cm -> cm.getVote().equals(vote));
}
}
19 changes: 14 additions & 5 deletions src/main/java/com/limechain/grandpa/state/GrandpaSetState.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
import com.limechain.storage.DBConstants;
import com.limechain.storage.KVRepository;
import com.limechain.storage.StateUtil;
import com.limechain.storage.crypto.KeyStore;
import com.limechain.storage.crypto.KeyType;
import com.limechain.sync.warpsync.dto.AuthoritySetChange;
import com.limechain.sync.warpsync.dto.ForcedAuthoritySetChange;
import com.limechain.sync.warpsync.dto.ScheduledAuthoritySetChange;
Expand Down Expand Up @@ -42,14 +44,17 @@
public class GrandpaSetState {

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

private List<Authority> authorities;
private BigInteger disabledAuthority;
private BigInteger setId;

private final PriorityQueue<AuthoritySetChange> authoritySetChanges = new PriorityQueue<>(AuthoritySetChange.getComparator());
private final RoundCache roundCache;
private final KeyStore keyStore;
private final KVRepository<String, Object> repository;

private final PriorityQueue<AuthoritySetChange> authoritySetChanges =
new PriorityQueue<>(AuthoritySetChange.getComparator());

public void initialize() {
loadPersistedState();
Expand Down Expand Up @@ -191,7 +196,6 @@ public void handleGrandpaConsensusMessage(GrandpaConsensusMessage consensusMessa
currentBlockNumber
));
case GRANDPA_ON_DISABLED -> disabledAuthority = consensusMessage.getDisabledAuthority();
//TODO: Implement later
case GRANDPA_PAUSE -> log.log(Level.SEVERE, "'PAUSE' grandpa message not implemented");
case GRANDPA_RESUME -> log.log(Level.SEVERE, "'RESUME' grandpa message not implemented");
}
Expand Down Expand Up @@ -227,4 +231,9 @@ public void handleVoteMessage(VoteMessage voteMessage) {
default -> throw new GrandpaGenericException("Unknown subround: " + subround);
}
}
}

public boolean participatesAsVoter() {
return authorities.stream()
.anyMatch(a -> keyStore.getKeyPair(KeyType.GRANDPA, a.getPublicKey()).isPresent());
}
}
7 changes: 5 additions & 2 deletions src/main/java/com/limechain/rpc/config/CommonConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import com.limechain.storage.KVRepository;
import com.limechain.storage.block.BlockHandler;
import com.limechain.storage.block.SyncState;
import com.limechain.storage.crypto.KeyStore;
import com.limechain.storage.trie.TrieStorage;
import com.limechain.sync.fullsync.FullSyncMachine;
import com.limechain.sync.warpsync.WarpSyncMachine;
Expand Down Expand Up @@ -89,8 +90,10 @@ public Network network(ChainService chainService, HostConfig hostConfig, KVRepos
}

@Bean
public GrandpaSetState grandpaSetState(KVRepository<String, Object> repository, RoundCache roundCache) {
return new GrandpaSetState(repository, roundCache);
public GrandpaSetState grandpaSetState(RoundCache roundCache,
KeyStore keyStore,
KVRepository<String, Object> repository) {
return new GrandpaSetState(roundCache, keyStore, repository);
}

@Bean
Expand Down
6 changes: 5 additions & 1 deletion src/main/java/com/limechain/sync/warpsync/WarpSyncState.java
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,11 @@ public synchronized void syncCommit(CommitMessage commitMessage, PeerId peerId)
return;
}

if (warpSyncFinished) {
grandpaSetState.getRoundCache()
.getRound(commitMessage.getSetId(), commitMessage.getRoundNumber())
.addCommitMessageToArchive(commitMessage);

if (warpSyncFinished && !grandpaSetState.participatesAsVoter()) {
updateState(commitMessage);
}
}
Expand Down
Loading
Loading