-
Notifications
You must be signed in to change notification settings - Fork 1
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
Conversation
@@ -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; |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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(); |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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
.
There was a problem hiding this comment.
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.
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);
}
The comments that I left may be outside the scope of this PR. What i consider most important for this PR is the following:
What should be done outside this PR:
|
6c8ecea
to
b9cfa61
Compare
public class RoundCache { | ||
|
||
private static final int CACHE_SET_CAPACITY = 4; | ||
private final Map<BigInteger, List<GrandpaRound>> roundMap = new LinkedHashMap<>( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Have we talked about making RoundCache thread-safe, or there is no need for that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From the comment above the class I have a feeling a thread save structure would be needed.
Also, I made a branch with refactored GrandpaService methods, where I use RoundCache -> 398-refactoring-grandpa-methods. You don't need to refactor them; I can merge them to your branch when you are ready. |
|
||
private Map<Hash256, SignedVote> preVotes = new ConcurrentHashMap<>(); | ||
private Map<Hash256, SignedVote> preCommits = new ConcurrentHashMap<>(); | ||
private Map<Hash256, SignedVote> primaryProposals = new ConcurrentHashMap<>(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The primary proposal can be only one per round. I suggest we either treat it as a prevote as it is counted the same way other prevotes are or we have a single SignedVote
field.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Come to think of it I'd include it in the prevote collection so that we don't have to refactor methods that rely on counting prevotes.
|
||
private static final int CACHE_SET_CAPACITY = 4; | ||
private final Map<BigInteger, List<GrandpaRound>> roundMap = new LinkedHashMap<>( | ||
CACHE_SET_CAPACITY, 0.75f, true) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do the 2nd and 3rd parameters do here?
public class RoundCache { | ||
|
||
private static final int CACHE_SET_CAPACITY = 4; | ||
private final Map<BigInteger, List<GrandpaRound>> roundMap = new LinkedHashMap<>( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From the comment above the class I have a feeling a thread save structure would be needed.
There are some new sonar issues. Please have a look. |
b9cfa61
to
23d716d
Compare
Quality Gate passedIssues Measures |
Implemented Best PreVote Candidate algorithm.
https://spec.polkadot.network/sect-finality#algo-best-prevote-candidate
Checklist: