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

Implemented Best PreVote Candidate algorithm #679

Open
wants to merge 1 commit into
base: dev
Choose a base branch
from

Conversation

osrib
Copy link
Collaborator

@osrib osrib commented Jan 9, 2025

Implemented Best PreVote Candidate algorithm.
https://spec.polkadot.network/sect-finality#algo-best-prevote-candidate

Checklist:

  • I have read the contributing guidelines.
  • My PR title matches the Conventional Commits spec.
  • My change requires a change to the documentation.
  • I have updated the documentation accordingly.
  • I have added tests to cover my changes.
  • All new and existing tests passed.

Copy link

sonarqubecloud bot commented Jan 9, 2025

@nistanimirov nistanimirov linked an issue Jan 9, 2025 that may be closed by this pull request
@@ -34,6 +35,8 @@ public class RoundState {
private Map<PubKey, Vote> prevotes = new ConcurrentHashMap<>();
private Map<PubKey, SignedVote> pvEquivocations = new ConcurrentHashMap<>();
private Map<PubKey, SignedVote> pcEquivocations = new ConcurrentHashMap<>();
//TODO: Figure out if we need to store vote messages in round state
private VoteMessage voteMessage;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This field serves the purpose of a primary proposal message from the network or from ourselves if we are the primary authority/voter. However it currently gets updated on every handled vote message in the GrandpaEngine. The correct flow of this would be as follows:

GrandpaEngine receives a vote message -> if the message of of type PREVOTE or PRIMARY_PROPOSAL it gets added to the appropriate collection inside of RoundState, same goes for PRECOMMIT.

Regarding the TODO. The state currently holds data that belongs to different aspects of grandpa:

  • Data related to the set: voters, setId and roundNumber
  • Data related to the round: votes and equivocation votes

Imo we should separate this. We can rename this class to GrandpaSetState and keep the data related to sets. Create an object that holds round data eg GrandpaRound that holds the data related to rounds. The state would hold the current and previous rounds as those are the only ones we need per spec. In this way we can apply a more structured update of sets and rounds:

GrandpaSetState will update its voters, setId and set roundNumber to 0 every time we receive an appropriate consensus message from the runtime (similar to BABE).
The current and previous GrandpaRound fields will be updated every time we finalize a round in the grandpa play round algo. This way we'd have an appropriate place to store the votes, equivocation votes and the primary proposal.

@@ -111,6 +113,27 @@ public Vote getGrandpaGhost() {
return selectBlockWithMostVotes(blocks);
}

/**
* Determines what block is our pre-voted block for the current round
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment explains the pseudo code of the algo from the spec, but does not reflect the code in the method.

*/
public Vote getBestPreVoteCandidate() {
Vote currentVote = getGrandpaGhost();
VoteMessage voteMessage = roundState.getVoteMessage();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The getVoteMessage() would almost never return a correct value here. Check my comment under RoundState

VoteMessage voteMessage = roundState.getVoteMessage();
SignedMessage signedMessage = voteMessage.getMessage();

if (signedMessage != null && signedMessage.getBlockNumber().compareTo(currentVote.getBlockNumber()) > 0) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't correctly check if we have a primary proposal vote for the current round. What's happening now is we invoke GHOST and get the current pre-vote block with most votes (B r pv in spec) and a wrong value for primary proposed block (B in spec).
signedMessage.getBlockNumber().compareTo(currentVote.getBlockNumber()) > 0
This would translate to pseudo code as following (considering we have correct B r pv and B prim):
B > B r pv.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To fully align with the specification, you should also add the result of Best_Final_Candidate(r - 1) in the comparison.
Screenshot 2025-01-09 at 17 43 20
I added collection for holding previous result of BEST_FINAL_CANDIDATE to the RoundState in order to have access to the previous rounds data. This collection should probably also be removed form the RoundState and added to the GrandpaSetState.

   //TODO: Refactor if this map is accessed/modified concurrently
   private Map<BigInteger, Vote> bestFinalCandidateArchive = new HashMap<>();
    ...
    public void addBestFinalCandidateToArchive(BigInteger roundNumber, Vote vote) {
        this.bestFinalCandidateArchive.put(roundNumber, vote);
    }

    public Vote getBestFinalCandidateForRound(BigInteger roundNumber) {
        return this.bestFinalCandidateArchive.get(roundNumber);
    }

@Zurcusa
Copy link
Collaborator

Zurcusa commented Jan 9, 2025

The comments that I left may be outside the scope of this PR. What i consider most important for this PR is the following:

  • Separate set data from round data in the RoundState
  • Create the GrandpaRound objects and place them in the state.
  • Change the logic of the Best-PreVote-Candidate method so that it conforms to the spec. For this to happen the primary proposal vote inside of the current GrandpaRound object should be used.

What should be done outside this PR:

  • Proper vote message handling (based on round type)
  • Add logic regarding updating sets, rounds etc.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Best PreVote Candidate
5 participants