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
Introduce EventValidBlock for informing peers about wanted block #2652
Conversation
66b0938
to
a06929b
Compare
Codecov Report
@@ Coverage Diff @@
## develop #2652 +/- ##
==========================================
+ Coverage 61.61% 61.8% +0.19%
==========================================
Files 207 207
Lines 16942 16952 +10
==========================================
+ Hits 10439 10478 +39
+ Misses 5640 5615 -25
+ Partials 863 859 -4
|
3111d10
to
a4cde65
Compare
a4cde65
to
72637c5
Compare
72637c5
to
8ec9a55
Compare
…-enable-valid-block-property
consensus/reactor.go
Outdated
@@ -392,13 +397,18 @@ func (conR *ConsensusReactor) broadcastProposalHeartbeatMessage(hb *types.Heartb | |||
} | |||
|
|||
func (conR *ConsensusReactor) broadcastNewRoundStepMessages(rs *cstypes.RoundState) { |
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.
broadcastNewRoundStepMessage
"Valid block we don't know about. Set ProposalBlock=nil", | ||
"proposal", cs.ProposalBlock.Hash(), "blockId", blockID.Hash) | ||
// We're getting the wrong block. | ||
cs.ProposalBlock = nil |
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 about cs.ValidRound
and cs.ValidBlockParts
? Shouldn't we reset these too?
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.
We update cs.ValidRound
, cs.ValidBlockParts
and cs.ValidBlock
only when we have the corresponding block. Therefore, at this point we can only "tell" gossip layer that we need some block, and then update ValidX variables when block arrives (if it arrives on time).
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.
Great work, but still have a few questions and some minor nits.
consensus/reactor.go
Outdated
@@ -964,7 +963,8 @@ func (ps *PeerState) SetHasProposal(proposal *types.Proposal) { | |||
if ps.PRS.Height != proposal.Height || ps.PRS.Round != proposal.Round { | |||
return | |||
} | |||
if ps.PRS.Proposal { | |||
// ps.PRS.ProposalBlockParts is set due to NewValidBlockMessage | |||
if ps.PRS.Proposal || ps.PRS.ProposalBlockParts != nil { |
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.
Is this really necessary?
I believe the situation this captures is when they don't have a proposal, but they sent us NewValidBlockMessage. For this to run, then either they'd have to send us a proposal (which I think would be Byzantine) or we'd have to have tried to send it to them (should that ever happen?).
I think it does make sense to have the check here, just hard to follow the circumstance where !PRS.Proposal && PRS.ProposalBlockParts != nil
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 key part here is ps.PRS.ProposalBlockParts != nil
. In this case we should't update PRS.ProposalBlockPartsHeader as it is already set (either by previous Proposal message or NewValidBlockMessage). But we might still want to set PRS.Proposal to true in case it is not already set. So maybe we should split this into 2 conditions. Regarding your last point, negation is !PRS.Proposal && PRS.ProposalBlockParts == nil
and this corresponds to the case that we are receiving Proposal for the first time and we haven't received NewValidBlockMessage yet.
consensus/reactor.go
Outdated
ps.mtx.Lock() | ||
defer ps.mtx.Unlock() | ||
|
||
if ps.PRS.Height != msg.Height { | ||
if ps.PRS.Height != msg.Height && ps.PRS.Round != msg.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.
What if there was a commit in Round 2, but this peer never received the block from Round 2, and is already in Round 3. Then it sees the commit from round 2, and sends us NewValidBlockMessage with Round==2?
Do we need special logic for when the NewValidBlockMessage is specifically for a commit, since the round may be different then?
Hard to write a test for this right now :(
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.
Excellent point, I missed this. We probably need to treat commit in a special way. Maybe we can just add flag to NewValidBlockMessage to say if it is commit or not. And yes, we don't have tests for this, but it will come soon :)
@@ -1420,11 +1415,6 @@ func (cs *ConsensusState) defaultSetProposal(proposal *types.Proposal) error { | |||
return nil | |||
} | |||
|
|||
// We don't care about the proposal if we're already in cstypes.RoundStepCommit. |
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 line was pointed out as the reason #2567 wasn't a bug (ie. see #2567 (comment)).
Now, instead of checking the commit step, we check if the ProposalBlockParts is already set, so we don't overwrite it. Is that right?
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.
I was aware of #2567 and was convinced that we still have the same guarantee with the new condition. I will make one more check.
@@ -1616,16 +1611,26 @@ func (cs *ConsensusState) addVote(vote *types.Vote, peerID p2p.ID) (added bool, | |||
|
|||
// Update Valid* if we can. | |||
// NOTE: our proposal block may be nil or not what received a polka.. | |||
// TODO: we may want to still update the ValidBlock and obtain it via gossipping |
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.
Why don't we consider vote.Round < cs.Round anymore?
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.
cs.ProposalBlock corresponds to the cs.Round. The spec also only cares about updating validBlock for the current round.
ensurePrecommit(voteCh, height, round) | ||
// we should have precommitted | ||
validatePrecommit(t, cs1, round, -1, vss[0], nil, nil) | ||
|
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.
We should add checks here to show that ValidBlock and ValidBlockParts are currently empty
consensus/state_test.go
Outdated
t.Fatal(err) | ||
} | ||
|
||
time.Sleep(20 * time.Millisecond) |
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.
Oh this isn't good. Can't we wait on the CompleteProposal event here instead?
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.
Good point. The only point at which we emit CompleteProposal is in enterPrevote (
Line 999 in 6643c5d
cs.eventBus.PublishEventCompleteProposal(cs.RoundStateEvent()) |
consensus/state_test.go
Outdated
startTestRound(cs1, height, round) | ||
ensureNewRound(newRoundCh, height, round) | ||
|
||
// vs2, vs3 and vs4 send prevote for propBlock |
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.
send precommit
assert.True(t, rs.Step == cstypes.RoundStepCommit) | ||
assert.True(t, rs.ProposalBlock == nil) | ||
assert.True(t, rs.ProposalBlockParts.Header().Equals(propBlockParts.Header())) | ||
} |
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.
Awesome.
Can we add another test for the situation where we've already gone to the next round, but then see the commit for the previous round, ie. what was described in #2567 ?
Also, out of curiosity, what happens if we propose a block and then see +2/3 precommit for a different block in that round? That's supposed to imply +2/3 are Byzantine, right?
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.
In the current version of the code this can happen as we are prevoting for the locked value, so it can be normal case. I prefer more having prevote to reflect reaction to proposal (or absence of proposal) like done in spec; in that case seeing +2/3 precommit for a different block means we have +2/3 Byzantine as correct can only precommit for a proposed value in case proposer is correct, or 2) private key of current proposer is stolen :)
If we release v0.26.0 before merging this, we should consider trying to do this in a backwards compatible manner - ie. keep the CommitStepMessage, and only send the NewValidBlock message to peers on the right P2PVersion. We'll have to make the peer's P2PVersion available somehow to the reactor. |
- Add test for the case of +2/3 Precommit from the previous round
Fix #2583. Note that tests that check consensus reactor part will be addressed in the follow up PR.