Skip to content

consensus: update state to prevote nil when proposal block does not match locked block.#6986

Merged
williambanfield merged 65 commits intowb/proposer-based-timestampsfrom
wb/issue-6850
Oct 23, 2021
Merged

consensus: update state to prevote nil when proposal block does not match locked block.#6986
williambanfield merged 65 commits intowb/proposer-based-timestampsfrom
wb/issue-6850

Conversation

@williambanfield
Copy link
Contributor

@williambanfield williambanfield commented Sep 23, 2021

This change updates state.go to modify the conditions under which a validator will issue prevote for a proposed block. This change attempts to bring the prevote logic in this go implementation more inline with the algorithm as described in the 2018 tendermint arXiv paper.

The change adds a condition under which this code will issue a prevote. The code will now prevote if +2/3 prevotes were seen for a block in a previous round and that block is proposed in current round. This matches the condition on line 28 of the arXiv paper. Prevoting in this condition must not update the locked block. The locked block should only be updated if +2/3 prevotes were seen within the same round as the proposal. TestState_PrevotePOLFromPreviousRound was added to check the correctness of this change.

This change also removes a condition under which the code will issue a prevote. Previously, the code issued for the validator's locked block regardless of what the proposal was for. The code will now only issue a prevote for its locked block if it matches the currently proposed block. TestStateLock_PrevoteNilWhenLockedAndMissProposal was added to check the correctness of this change.

Other tests were modified to ensure we correctly issue prevotes for nil or the locked block as a result of this change.

closes: #6850

@ValarDragon
Copy link
Contributor

Left some comments on the docs as I tried to understand the new change / check correspondence. Plz don't block merge of this on me / these comments. Just thought I'd review try to understand more of the consensus code while its being touched!

williambanfield and others added 7 commits October 14, 2021 08:03
Co-authored-by: Dev Ojha <ValarDragon@users.noreply.github.com>
Co-authored-by: Dev Ojha <ValarDragon@users.noreply.github.com>
Co-authored-by: Dev Ojha <ValarDragon@users.noreply.github.com>
*/
blockID, ok := cs.Votes.Prevotes(cs.Proposal.POLRound).TwoThirdsMajority()
if ok && cs.ProposalBlock.HashesTo(blockID.Hash) && cs.Proposal.POLRound >= 0 && cs.Proposal.POLRound < cs.Round {
if cs.LockedRound <= cs.Proposal.POLRound {
Copy link

Choose a reason for hiding this comment

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

Why >= here? The log message states "round later than the locked round", so > will be more consistent with it.

Copy link

Choose a reason for hiding this comment

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

Well, it is true that the ArXiv algorithm uses this condition: lockedRound_p ≤ v_r.

Copy link

Choose a reason for hiding this comment

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

The code is implicitly assuming the following: if Proposal.POLRound == LockedRound AND Prevotes(Proposal.POLRound).TwoThirdsMajority() == id(ProposalBlock) THEN ProposalBlock == LockedBlock.

Which is very likely to be true. But since hashes are performed lots of times in this code, maybe is better to double check this in the following if.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The code is implicitly assuming the following: if Proposal.POLRound == LockedRound AND Prevotes(Proposal.POLRound).TwoThirdsMajority() == id(ProposalBlock) THEN ProposalBlock == LockedBlock.

I'm not totally sure we make that assumption. We have 2/3 prevotes from some round round_p. Given that we received 2/3 prevotes for the block in some round round_p, and round_p >= locked_round then we are safe to prevote that block, whether it matches our locked block or not.

Copy link

Choose a reason for hiding this comment

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

If round_p == locked_round, then the block that received 2/3+ votes in round_p must be our locked block, mustn't it? I am assuming that <1/3 of the voting power is Byzantine... but I'm not sure if we should assume this.

Copy link
Contributor Author

@williambanfield williambanfield Oct 15, 2021

Choose a reason for hiding this comment

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

Definitely, there is no reason for it not to match our locked block. I'm more saying that from the perspective of the correctness of the algorithm, seeing 2/3 prevotes in a round >= to our locked round is enough for us to prevote the block. It absolutely should match the locked block, and from the current implementation I'm not sure why it ever would not (happy to be corrected here), but I think that keeping it in this notation allows us to match the arXiv paper. I think keeping it in the same notation as the arXiv paper is preferable so that we can more easily know we're making safe changes to it as we go forward.

Copy link

Choose a reason for hiding this comment

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

'v_r' is a round greater than our current locked round.

Agreed. But in this case I would make the comment consistent: v_r is not lesser/smaller than...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good point, fixed.

Copy link

@cason cason left a comment

Choose a reason for hiding this comment

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

Reviewed the implementation, not the tests yet.

Copy link

@cason cason left a comment

Choose a reason for hiding this comment

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

Tests reviewed, minor comments.

@williambanfield
Copy link
Contributor Author

williambanfield commented Oct 15, 2021

Thanks @cason for the review! Looks like I missed a few comments while refactoring. Very appreciated.

@williambanfield
Copy link
Contributor Author

Another situation in which POLRound > LockedRound is when POLRound = ValidRound > LockedRound. This happens when we receive a block and 2/3+ Prevotes for that block after the PrevoteStep of that round. This causes Valid{Block,BlockParts,Round} to be updated while Locked{} are not, as there is a contract that Locked{} should match the previous issued Prevote.

I see, so we would have missed 2/3 prevotes during the actual time that would have been actionable, but still retained them for a future POL check. Thanks for the info.

@williambanfield williambanfield added S:automerge Automatically merge PR when requirements pass and removed S:automerge Automatically merge PR when requirements pass labels Oct 15, 2021
@williambanfield williambanfield merged commit fe2ed68 into wb/proposer-based-timestamps Oct 23, 2021
@williambanfield williambanfield deleted the wb/issue-6850 branch October 23, 2021 14:12
@zeu5
Copy link

zeu5 commented Nov 12, 2021

We ran a few test scenarios around this change.

  1. We test if a replica unlocks on seeing 2f+1 nil votes to vote on a new proposal.
  2. We test if a replica continues to vote on a locked block.
  3. We test if a replica which has locked onto a value, is able to relock onto a new proposal when it receives 2f+1 votes for the new proposal.

The outcome was that the first test case fails and the other two pass. This was the expected behaviour.
We also ran other test cases that are not related to this change.

  1. We test if we can skip rounds by not delivering enough votes and changing votes.
  2. We test if we can skip rounds by not delivering the BlockPart messages.

As expected, the other two test cases were successful as well.

The test cases are described in more detail here - https://github.com/ds-test-framework/tendermint-test/blob/master/docs/report/Tendermint.md. Apologies for posting it late but please let me know if you have any comments or feedback.

@josef-widder
Copy link
Contributor

Thanks a lot @zeu5!!
@williambanfield, @cmwaters: What do you think? Should we track these outcomes of the tests somehow within the repo? I am not sure a discussion in a closed PR is very visible in the future...

williambanfield added a commit that referenced this pull request Nov 24, 2021
…atch locked block. (#6986)

* add failing test

* tweak comments in failing test

* failing test comment

* initial attempt at removing prevote locked block logic

* comment out broken function

* undo reset on prevotes

* fixing TestProposeValidBlock test

* update test for completed POL update

* comment updates

* further unlock testing

* update comments

* Update internal/consensus/state.go

* spacing nit

* comment cleanup

* nil check in addVote

* update unlock description

* update precommit on relock comment

* add ensure new timeout back

* rename IsZero to IsNil and replace uses of block len check with helper

* add testing.T to new assertions

* begin removing unlock condition

* fix TestStateProposerSelection2 to precommit for nil correctly

* remove erroneous sleep

* update TestStatePOL comment

* update relock test to be more clear

* add _ into test names

* rename slashing

* udpate no relock function to be cleaner

* do not relock on old proposal test cleanup

* con state name update

* remove all references to unlock

* update test comments to include new

* add relock test

* add ensureRelock to common_test

* remove all event unlock

* remove unlock checks

* no lint add space

* lint ++

* add test for nil prevote on different proposal

* fix prevote nil condition

* fix defaultDoPrevote

* state_test.go fixes to accomodate prevoting for nil

* add failing test for POL from previous round case

* update prevote logic to prevote POL from previous round

* state.go comment fixes

* update validatePrevotes to correctly look for nil

* update new test name and comment

* update POLFromPreviousRound test

* fixes post merge

* fix spacing

* make the linter happy

* change prevote log message

* update prevote nil debug line

* update enterPrevote comment

* lint

* Update internal/consensus/state.go

Co-authored-by: Dev Ojha <ValarDragon@users.noreply.github.com>

* Update internal/consensus/state.go

Co-authored-by: Dev Ojha <ValarDragon@users.noreply.github.com>

* add english description of alg rules

* Update internal/consensus/state.go

Co-authored-by: Dev Ojha <ValarDragon@users.noreply.github.com>

* comment fixes from review

* fix comment

* fix comment

Co-authored-by: Dev Ojha <ValarDragon@users.noreply.github.com>
williambanfield added a commit that referenced this pull request Jan 15, 2022
…atch locked block. (#6986)

* add failing test

* tweak comments in failing test

* failing test comment

* initial attempt at removing prevote locked block logic

* comment out broken function

* undo reset on prevotes

* fixing TestProposeValidBlock test

* update test for completed POL update

* comment updates

* further unlock testing

* update comments

* Update internal/consensus/state.go

* spacing nit

* comment cleanup

* nil check in addVote

* update unlock description

* update precommit on relock comment

* add ensure new timeout back

* rename IsZero to IsNil and replace uses of block len check with helper

* add testing.T to new assertions

* begin removing unlock condition

* fix TestStateProposerSelection2 to precommit for nil correctly

* remove erroneous sleep

* update TestStatePOL comment

* update relock test to be more clear

* add _ into test names

* rename slashing

* udpate no relock function to be cleaner

* do not relock on old proposal test cleanup

* con state name update

* remove all references to unlock

* update test comments to include new

* add relock test

* add ensureRelock to common_test

* remove all event unlock

* remove unlock checks

* no lint add space

* lint ++

* add test for nil prevote on different proposal

* fix prevote nil condition

* fix defaultDoPrevote

* state_test.go fixes to accomodate prevoting for nil

* add failing test for POL from previous round case

* update prevote logic to prevote POL from previous round

* state.go comment fixes

* update validatePrevotes to correctly look for nil

* update new test name and comment

* update POLFromPreviousRound test

* fixes post merge

* fix spacing

* make the linter happy

* change prevote log message

* update prevote nil debug line

* update enterPrevote comment

* lint

* Update internal/consensus/state.go

Co-authored-by: Dev Ojha <ValarDragon@users.noreply.github.com>

* Update internal/consensus/state.go

Co-authored-by: Dev Ojha <ValarDragon@users.noreply.github.com>

* add english description of alg rules

* Update internal/consensus/state.go

Co-authored-by: Dev Ojha <ValarDragon@users.noreply.github.com>

* comment fixes from review

* fix comment

* fix comment

Co-authored-by: Dev Ojha <ValarDragon@users.noreply.github.com>
@williambanfield williambanfield restored the wb/issue-6850 branch September 9, 2022 16:12
firelizzard18 pushed a commit to AccumulateNetwork/tendermint that referenced this pull request Feb 1, 2024
…not match locked block (tendermint#1203)

* consensus: update state to prevote nil when proposal block does not match locked block. (tendermint#6986)

* add failing test

* tweak comments in failing test

* failing test comment

* initial attempt at removing prevote locked block logic

* comment out broken function

* undo reset on prevotes

* fixing TestProposeValidBlock test

* update test for completed POL update

* comment updates

* further unlock testing

* update comments

* Update internal/consensus/state.go

* spacing nit

* comment cleanup

* nil check in addVote

* update unlock description

* update precommit on relock comment

* add ensure new timeout back

* rename IsZero to IsNil and replace uses of block len check with helper

* add testing.T to new assertions

* begin removing unlock condition

* fix TestStateProposerSelection2 to precommit for nil correctly

* remove erroneous sleep

* update TestStatePOL comment

* update relock test to be more clear

* add _ into test names

* rename slashing

* udpate no relock function to be cleaner

* do not relock on old proposal test cleanup

* con state name update

* remove all references to unlock

* update test comments to include new

* add relock test

* add ensureRelock to common_test

* remove all event unlock

* remove unlock checks

* no lint add space

* lint ++

* add test for nil prevote on different proposal

* fix prevote nil condition

* fix defaultDoPrevote

* state_test.go fixes to accomodate prevoting for nil

* add failing test for POL from previous round case

* update prevote logic to prevote POL from previous round

* state.go comment fixes

* update validatePrevotes to correctly look for nil

* update new test name and comment

* update POLFromPreviousRound test

* fixes post merge

* fix spacing

* make the linter happy

* change prevote log message

* update prevote nil debug line

* update enterPrevote comment

* lint

* Update internal/consensus/state.go

Co-authored-by: Dev Ojha <ValarDragon@users.noreply.github.com>

* Update internal/consensus/state.go

Co-authored-by: Dev Ojha <ValarDragon@users.noreply.github.com>

* add english description of alg rules

* Update internal/consensus/state.go

Co-authored-by: Dev Ojha <ValarDragon@users.noreply.github.com>

* comment fixes from review

* fix comment

* fix comment

Co-authored-by: Dev Ojha <ValarDragon@users.noreply.github.com>

* Fix UTs

* Addressed comments

* Add changelog

* Update consensus/state.go

Co-authored-by: Hernán Vanzetto <15466498+hvanz@users.noreply.github.com>

---------

Co-authored-by: William Banfield <4561443+williambanfield@users.noreply.github.com>
Co-authored-by: Dev Ojha <ValarDragon@users.noreply.github.com>
Co-authored-by: Hernán Vanzetto <15466498+hvanz@users.noreply.github.com>
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.

6 participants