From 12675ecd92c4f3566f48d42147321c533f963640 Mon Sep 17 00:00:00 2001 From: Zarko Milosevic Date: Thu, 4 Oct 2018 15:37:13 +0200 Subject: [PATCH] consensus: Wait timeout precommit before starting new round (#2493) * Disable transitioning to new round upon 2/3+ of Precommit nils Pull in ensureVote test function from https://github.com/tendermint/tendermint/pull/2132 * Add several ensureX test methods to wrap channel read with timeout * Revert panic in tests --- CHANGELOG_PENDING.md | 2 + config/config.go | 6 +- consensus/common_test.go | 88 +++++++++++-- consensus/reactor.go | 2 +- consensus/state.go | 21 +-- consensus/state_test.go | 237 ++++++++++++++++++---------------- lite/dynamic_verifier_test.go | 1 + p2p/conn/connection.go | 2 +- p2p/peer.go | 2 +- p2p/test_util.go | 2 +- 10 files changed, 224 insertions(+), 139 deletions(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index a9538dd10..2a0b58f60 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -40,6 +40,8 @@ IMPROVEMENTS: BUG FIXES: - [autofile] \#2428 Group.RotateFile need call Flush() before rename (@goolAdapter) - [node] \#2434 Make node respond to signal interrupts while sleeping for genesis time +- [consensus] [\#1690](https://github.com/tendermint/tendermint/issues/1690) wait for +timeoutPrecommit before starting next round - [evidence] \#2515 fix db iter leak (@goolAdapter) - [common/bit_array] Fixed a bug in the `Or` function - [common/bit_array] Fixed a bug in the `Sub` function (@bradyjoestar) diff --git a/config/config.go b/config/config.go index 8f3d6d180..1f9ff3e13 100644 --- a/config/config.go +++ b/config/config.go @@ -475,9 +475,9 @@ type MempoolConfig struct { // DefaultMempoolConfig returns a default configuration for the Tendermint mempool func DefaultMempoolConfig() *MempoolConfig { return &MempoolConfig{ - Recheck: true, - Broadcast: true, - WalPath: "", + Recheck: true, + Broadcast: true, + WalPath: "", // Each signature verification takes .5ms, size reduced until we implement // ABCI Recheck Size: 5000, diff --git a/consensus/common_test.go b/consensus/common_test.go index d7e661481..2a5cc8e79 100644 --- a/consensus/common_test.go +++ b/consensus/common_test.go @@ -7,6 +7,7 @@ import ( "io/ioutil" "os" "path" + "reflect" "sort" "sync" "testing" @@ -306,23 +307,94 @@ func randConsensusState(nValidators int) (*ConsensusState, []*validatorStub) { //------------------------------------------------------------------------------- +func ensureNoNewEvent(ch <-chan interface{}, timeout time.Duration, + errorMessage string) { + select { + case <-time.After(timeout): + break + case <-ch: + panic(errorMessage) + } +} + func ensureNoNewStep(stepCh <-chan interface{}) { - timer := time.NewTimer(ensureTimeout) + ensureNoNewEvent(stepCh, ensureTimeout, "We should be stuck waiting, "+ + "not moving to the next step") +} + +func ensureNoNewTimeout(stepCh <-chan interface{}, timeout int64) { + timeoutDuration := time.Duration(timeout*5) * time.Nanosecond + ensureNoNewEvent(stepCh, timeoutDuration, "We should be stuck waiting, "+ + "not moving to the next step") +} + +func ensureNewEvent(ch <-chan interface{}, timeout time.Duration, errorMessage string) { select { - case <-timer.C: + case <-time.After(timeout): + panic(errorMessage) + case <-ch: break - case <-stepCh: - panic("We should be stuck waiting, not moving to the next step") } } func ensureNewStep(stepCh <-chan interface{}) { - timer := time.NewTimer(ensureTimeout) + ensureNewEvent(stepCh, ensureTimeout, + "Timeout expired while waiting for NewStep event") +} + +func ensureNewRound(roundCh <-chan interface{}) { + ensureNewEvent(roundCh, ensureTimeout, + "Timeout expired while waiting for NewRound event") +} + +func ensureNewTimeout(timeoutCh <-chan interface{}, timeout int64) { + timeoutDuration := time.Duration(timeout*5) * time.Nanosecond + ensureNewEvent(timeoutCh, timeoutDuration, + "Timeout expired while waiting for NewTimeout event") +} + +func ensureNewProposal(proposalCh <-chan interface{}) { + ensureNewEvent(proposalCh, ensureTimeout, + "Timeout expired while waiting for NewProposal event") +} + +func ensureNewBlock(blockCh <-chan interface{}) { + ensureNewEvent(blockCh, ensureTimeout, + "Timeout expired while waiting for NewBlock event") +} + +func ensureNewVote(voteCh <-chan interface{}) { + ensureNewEvent(voteCh, ensureTimeout, + "Timeout expired while waiting for NewVote event") +} + +func ensureNewUnlock(unlockCh <-chan interface{}) { + ensureNewEvent(unlockCh, ensureTimeout, + "Timeout expired while waiting for NewUnlock event") +} + +func ensureVote(voteCh chan interface{}, height int64, round int, + voteType byte) { select { - case <-timer.C: - panic("We shouldnt be stuck waiting") - case <-stepCh: + case <-time.After(ensureTimeout): break + case v := <-voteCh: + edv, ok := v.(types.EventDataVote) + if !ok { + panic(fmt.Sprintf("expected a *types.Vote, "+ + "got %v. wrong subscription channel?", + reflect.TypeOf(v))) + } + vote := edv.Vote + if vote.Height != height { + panic(fmt.Sprintf("expected height %v, got %v", height, vote.Height)) + } + if vote.Round != round { + panic(fmt.Sprintf("expected round %v, got %v", round, vote.Round)) + } + if vote.Type != voteType { + panic(fmt.Sprintf("expected type %v, got %v", voteType, vote.Type)) + } } } diff --git a/consensus/reactor.go b/consensus/reactor.go index 16e2e7e2e..376b8eda9 100644 --- a/consensus/reactor.go +++ b/consensus/reactor.go @@ -55,7 +55,7 @@ func NewConsensusReactor(consensusState *ConsensusState, fastSync bool, options conR := &ConsensusReactor{ conS: consensusState, fastSync: fastSync, - metrics: NopMetrics(), + metrics: NopMetrics(), } conR.updateFastSyncingMetric() conR.BaseReactor = *p2p.NewBaseReactor("ConsensusReactor", conR) diff --git a/consensus/state.go b/consensus/state.go index 35bbca0f6..0100a1504 100644 --- a/consensus/state.go +++ b/consensus/state.go @@ -1642,21 +1642,14 @@ func (cs *ConsensusState) addVote(vote *types.Vote, peerID p2p.ID) (added bool, precommits := cs.Votes.Precommits(vote.Round) cs.Logger.Info("Added to precommit", "vote", vote, "precommits", precommits.StringShort()) blockID, ok := precommits.TwoThirdsMajority() - if ok { - if len(blockID.Hash) == 0 { - cs.enterNewRound(height, vote.Round+1) - } else { - cs.enterNewRound(height, vote.Round) - cs.enterPrecommit(height, vote.Round) - cs.enterCommit(height, vote.Round) - - if cs.config.SkipTimeoutCommit && precommits.HasAll() { - // if we have all the votes now, - // go straight to new round (skip timeout commit) - // cs.scheduleTimeout(time.Duration(0), cs.Height, 0, cstypes.RoundStepNewHeight) - cs.enterNewRound(cs.Height, 0) - } + if ok && len(blockID.Hash) != 0 { + // Executed as TwoThirdsMajority could be from a higher round + cs.enterNewRound(height, vote.Round) + cs.enterPrecommit(height, vote.Round) + cs.enterCommit(height, vote.Round) + if cs.config.SkipTimeoutCommit && precommits.HasAll() { + cs.enterNewRound(cs.Height, 0) } } else if cs.Round <= vote.Round && precommits.HasTwoThirdsAny() { cs.enterNewRound(height, vote.Round) diff --git a/consensus/state_test.go b/consensus/state_test.go index 4c34d9d2f..831f77f4a 100644 --- a/consensus/state_test.go +++ b/consensus/state_test.go @@ -67,23 +67,23 @@ func TestStateProposerSelection0(t *testing.T) { startTestRound(cs1, height, round) - // wait for new round so proposer is set - <-newRoundCh + // Wait for new round so proposer is set. + ensureNewRound(newRoundCh) - // lets commit a block and ensure proposer for the next height is correct + // Commit a block and ensure proposer for the next height is correct. prop := cs1.GetRoundState().Validators.GetProposer() if !bytes.Equal(prop.Address, cs1.privValidator.GetAddress()) { t.Fatalf("expected proposer to be validator %d. Got %X", 0, prop.Address) } - // wait for complete proposal - <-proposalCh + // Wait for complete proposal. + ensureNewProposal(proposalCh) rs := cs1.GetRoundState() signAddVotes(cs1, types.VoteTypePrecommit, rs.ProposalBlock.Hash(), rs.ProposalBlockParts.Header(), vss[1:]...) - // wait for new round so next validator is set - <-newRoundCh + // Wait for new round so next validator is set. + ensureNewRound(newRoundCh) prop = cs1.GetRoundState().Validators.GetProposer() if !bytes.Equal(prop.Address, vss[1].GetAddress()) { @@ -102,7 +102,7 @@ func TestStateProposerSelection2(t *testing.T) { incrementRound(vss[1:]...) startTestRound(cs1, cs1.Height, 2) - <-newRoundCh // wait for the new round + ensureNewRound(newRoundCh) // wait for the new round // everyone just votes nil. we get a new proposer each round for i := 0; i < len(vss); i++ { @@ -114,8 +114,7 @@ func TestStateProposerSelection2(t *testing.T) { rs := cs1.GetRoundState() signAddVotes(cs1, types.VoteTypePrecommit, nil, rs.ProposalBlockParts.Header(), vss[1:]...) - <-newRoundCh // wait for the new round event each round - + ensureNewRound(newRoundCh) // wait for the new round event each round incrementRound(vss[1:]...) } @@ -133,13 +132,7 @@ func TestStateEnterProposeNoPrivValidator(t *testing.T) { startTestRound(cs, height, round) // if we're not a validator, EnterPropose should timeout - ticker := time.NewTicker(ensureProposeTimeout(cs.config.TimeoutPropose)) - select { - case <-timeoutCh: - case <-ticker.C: - panic("Expected EnterPropose to timeout") - - } + ensureNewTimeout(timeoutCh, cs.config.TimeoutPropose.Nanoseconds()) if cs.GetRoundState().Proposal != nil { t.Error("Expected to make no proposal, since no privValidator") @@ -159,7 +152,7 @@ func TestStateEnterProposeYesPrivValidator(t *testing.T) { cs.enterNewRound(height, round) cs.startRoutines(3) - <-proposalCh + ensureNewProposal(proposalCh) // Check that Proposal, ProposalBlock, ProposalBlockParts are set. rs := cs.GetRoundState() @@ -174,13 +167,7 @@ func TestStateEnterProposeYesPrivValidator(t *testing.T) { } // if we're a validator, enterPropose should not timeout - ticker := time.NewTicker(ensureProposeTimeout(cs.config.TimeoutPropose)) - select { - case <-timeoutCh: - panic("Expected EnterPropose not to timeout") - case <-ticker.C: - - } + ensureNoNewTimeout(timeoutCh, cs.config.TimeoutPropose.Nanoseconds()) } func TestStateBadProposal(t *testing.T) { @@ -221,19 +208,19 @@ func TestStateBadProposal(t *testing.T) { startTestRound(cs1, height, round) // wait for proposal - <-proposalCh + ensureNewProposal(proposalCh) // wait for prevote - <-voteCh + ensureNewVote(voteCh) validatePrevote(t, cs1, round, vss[0], nil) // add bad prevote from vs2 and wait for it signAddVotes(cs1, types.VoteTypePrevote, propBlock.Hash(), propBlock.MakePartSet(partSize).Header(), vs2) - <-voteCh + ensureNewVote(voteCh) // wait for precommit - <-voteCh + ensureNewVote(voteCh) validatePrecommit(t, cs1, round, 0, vss[0], nil, nil) signAddVotes(cs1, types.VoteTypePrecommit, propBlock.Hash(), propBlock.MakePartSet(partSize).Header(), vs2) @@ -261,19 +248,19 @@ func TestStateFullRound1(t *testing.T) { startTestRound(cs, height, round) - <-newRoundCh + ensureNewRound(newRoundCh) // grab proposal re := <-propCh propBlockHash := re.(types.EventDataRoundState).RoundState.(*cstypes.RoundState).ProposalBlock.Hash() - <-voteCh // wait for prevote + ensureNewVote(voteCh) // wait for prevote validatePrevote(t, cs, round, vss[0], propBlockHash) - <-voteCh // wait for precommit + ensureNewVote(voteCh) // wait for precommit // we're going to roll right into new height - <-newRoundCh + ensureNewRound(newRoundCh) validateLastPrecommit(t, cs, vss[0], propBlockHash) } @@ -288,8 +275,8 @@ func TestStateFullRoundNil(t *testing.T) { cs.enterPrevote(height, round) cs.startRoutines(4) - <-voteCh // prevote - <-voteCh // precommit + ensureNewVote(voteCh) // prevote + ensureNewVote(voteCh) // precommit // should prevote and precommit nil validatePrevoteAndPrecommit(t, cs, round, 0, vss[0], nil, nil) @@ -308,7 +295,7 @@ func TestStateFullRound2(t *testing.T) { // start round and wait for propose and prevote startTestRound(cs1, height, round) - <-voteCh // prevote + ensureNewVote(voteCh) // prevote // we should be stuck in limbo waiting for more prevotes rs := cs1.GetRoundState() @@ -316,9 +303,9 @@ func TestStateFullRound2(t *testing.T) { // prevote arrives from vs2: signAddVotes(cs1, types.VoteTypePrevote, propBlockHash, propPartsHeader, vs2) - <-voteCh + ensureNewVote(voteCh) - <-voteCh //precommit + ensureNewVote(voteCh) //precommit // the proposed block should now be locked and our precommit added validatePrecommit(t, cs1, 0, 0, vss[0], propBlockHash, propBlockHash) @@ -327,10 +314,10 @@ func TestStateFullRound2(t *testing.T) { // precommit arrives from vs2: signAddVotes(cs1, types.VoteTypePrecommit, propBlockHash, propPartsHeader, vs2) - <-voteCh + ensureNewVote(voteCh) // wait to finish commit, propose in next height - <-newBlockCh + ensureNewBlock(newBlockCh) } //------------------------------------------------------------------------------------------ @@ -363,14 +350,14 @@ func TestStateLockNoPOL(t *testing.T) { rs := re.(types.EventDataRoundState).RoundState.(*cstypes.RoundState) theBlockHash := rs.ProposalBlock.Hash() - <-voteCh // prevote + ensureNewVote(voteCh) // prevote // we should now be stuck in limbo forever, waiting for more prevotes // prevote arrives from vs2: signAddVotes(cs1, types.VoteTypePrevote, cs1.ProposalBlock.Hash(), cs1.ProposalBlockParts.Header(), vs2) - <-voteCh // prevote + ensureNewVote(voteCh) // prevote - <-voteCh // precommit + ensureNewVote(voteCh) // precommit // the proposed block should now be locked and our precommit added validatePrecommit(t, cs1, 0, 0, vss[0], theBlockHash, theBlockHash) @@ -382,15 +369,15 @@ func TestStateLockNoPOL(t *testing.T) { copy(hash, theBlockHash) hash[0] = byte((hash[0] + 1) % 255) signAddVotes(cs1, types.VoteTypePrecommit, hash, rs.ProposalBlock.MakePartSet(partSize).Header(), vs2) - <-voteCh // precommit + ensureNewVote(voteCh) // precommit // (note we're entering precommit for a second time this round) // but with invalid args. then we enterPrecommitWait, and the timeout to new round - <-timeoutWaitCh + ensureNewTimeout(timeoutWaitCh, cs1.config.TimeoutPrecommit.Nanoseconds()) /// - <-newRoundCh + ensureNewRound(newRoundCh) t.Log("#### ONTO ROUND 1") /* Round2 (cs1, B) // B B2 @@ -407,20 +394,20 @@ func TestStateLockNoPOL(t *testing.T) { } // wait to finish prevote - <-voteCh + ensureNewVote(voteCh) // we should have prevoted our locked block validatePrevote(t, cs1, 1, vss[0], rs.LockedBlock.Hash()) // add a conflicting prevote from the other validator signAddVotes(cs1, types.VoteTypePrevote, hash, rs.LockedBlock.MakePartSet(partSize).Header(), vs2) - <-voteCh + ensureNewVote(voteCh) // now we're going to enter prevote again, but with invalid args // and then prevote wait, which should timeout. then wait for precommit - <-timeoutWaitCh + ensureNewTimeout(timeoutWaitCh, cs1.config.TimeoutPrevote.Nanoseconds()) - <-voteCh // precommit + ensureNewVote(voteCh) // precommit // the proposed block should still be locked and our precommit added // we should precommit nil and be locked on the proposal @@ -429,13 +416,13 @@ func TestStateLockNoPOL(t *testing.T) { // add conflicting precommit from vs2 // NOTE: in practice we should never get to a point where there are precommits for different blocks at the same round signAddVotes(cs1, types.VoteTypePrecommit, hash, rs.LockedBlock.MakePartSet(partSize).Header(), vs2) - <-voteCh + ensureNewVote(voteCh) // (note we're entering precommit for a second time this round, but with invalid args // then we enterPrecommitWait and timeout into NewRound - <-timeoutWaitCh + ensureNewTimeout(timeoutWaitCh, cs1.config.TimeoutPrecommit.Nanoseconds()) - <-newRoundCh + ensureNewRound(newRoundCh) t.Log("#### ONTO ROUND 2") /* Round3 (vs2, _) // B, B2 @@ -451,22 +438,22 @@ func TestStateLockNoPOL(t *testing.T) { panic(fmt.Sprintf("Expected proposal block to be locked block. Got %v, Expected %v", rs.ProposalBlock, rs.LockedBlock)) } - <-voteCh // prevote + ensureNewVote(voteCh) // prevote validatePrevote(t, cs1, 2, vss[0], rs.LockedBlock.Hash()) signAddVotes(cs1, types.VoteTypePrevote, hash, rs.ProposalBlock.MakePartSet(partSize).Header(), vs2) - <-voteCh + ensureNewVote(voteCh) - <-timeoutWaitCh // prevote wait - <-voteCh // precommit + ensureNewTimeout(timeoutWaitCh, cs1.config.TimeoutPrevote.Nanoseconds()) + ensureNewVote(voteCh) // precommit validatePrecommit(t, cs1, 2, 0, vss[0], nil, theBlockHash) // precommit nil but be locked on proposal signAddVotes(cs1, types.VoteTypePrecommit, hash, rs.ProposalBlock.MakePartSet(partSize).Header(), vs2) // NOTE: conflicting precommits at same height - <-voteCh + ensureNewVote(voteCh) - <-timeoutWaitCh + ensureNewTimeout(timeoutWaitCh, cs1.config.TimeoutPrecommit.Nanoseconds()) // before we time out into new round, set next proposal block prop, propBlock := decideProposal(cs1, vs2, vs2.Height, vs2.Round+1) @@ -476,7 +463,7 @@ func TestStateLockNoPOL(t *testing.T) { incrementRound(vs2) - <-newRoundCh + ensureNewRound(newRoundCh) t.Log("#### ONTO ROUND 3") /* Round4 (vs2, C) // B C // B C @@ -488,22 +475,22 @@ func TestStateLockNoPOL(t *testing.T) { t.Fatal(err) } - <-proposalCh - <-voteCh // prevote + ensureNewProposal(proposalCh) + ensureNewVote(voteCh) // prevote // prevote for locked block (not proposal) validatePrevote(t, cs1, 0, vss[0], cs1.LockedBlock.Hash()) signAddVotes(cs1, types.VoteTypePrevote, propBlock.Hash(), propBlock.MakePartSet(partSize).Header(), vs2) - <-voteCh + ensureNewVote(voteCh) - <-timeoutWaitCh - <-voteCh + ensureNewTimeout(timeoutWaitCh, cs1.config.TimeoutPrevote.Nanoseconds()) + ensureNewVote(voteCh) validatePrecommit(t, cs1, 2, 0, vss[0], nil, theBlockHash) // precommit nil but locked on proposal signAddVotes(cs1, types.VoteTypePrecommit, propBlock.Hash(), propBlock.MakePartSet(partSize).Header(), vs2) // NOTE: conflicting precommits at same height - <-voteCh + ensureNewVote(voteCh) } // 4 vals, one precommits, other 3 polka at next round, so we unlock and precomit the polka @@ -531,18 +518,18 @@ func TestStateLockPOLRelock(t *testing.T) { // start round and wait for propose and prevote startTestRound(cs1, cs1.Height, 0) - <-newRoundCh + ensureNewRound(newRoundCh) re := <-proposalCh rs := re.(types.EventDataRoundState).RoundState.(*cstypes.RoundState) theBlockHash := rs.ProposalBlock.Hash() - <-voteCh // prevote + ensureNewVote(voteCh) // prevote signAddVotes(cs1, types.VoteTypePrevote, cs1.ProposalBlock.Hash(), cs1.ProposalBlockParts.Header(), vs2, vs3, vs4) // prevotes discardFromChan(voteCh, 3) - <-voteCh // our precommit + ensureNewVote(voteCh) // our precommit // the proposed block should now be locked and our precommit added validatePrecommit(t, cs1, 0, 0, vss[0], theBlockHash, theBlockHash) @@ -560,14 +547,14 @@ func TestStateLockPOLRelock(t *testing.T) { incrementRound(vs2, vs3, vs4) // timeout to new round - <-timeoutWaitCh + ensureNewTimeout(timeoutWaitCh, cs1.config.TimeoutPrecommit.Nanoseconds()) //XXX: this isnt guaranteed to get there before the timeoutPropose ... if err := cs1.SetProposalAndBlock(prop, propBlock, propBlockParts, "some peer"); err != nil { t.Fatal(err) } - <-newRoundCh + ensureNewRound(newRoundCh) t.Log("### ONTO ROUND 1") /* @@ -585,7 +572,7 @@ func TestStateLockPOLRelock(t *testing.T) { } // go to prevote, prevote for locked block (not proposal), move on - <-voteCh + ensureNewVote(voteCh) validatePrevote(t, cs1, 0, vss[0], theBlockHash) // now lets add prevotes from everyone else for the new block @@ -625,6 +612,8 @@ func TestStateLockPOLRelock(t *testing.T) { func TestStateLockPOLUnlock(t *testing.T) { cs1, vss := randConsensusState(4) vs2, vs3, vs4 := vss[1], vss[2], vss[3] + h := cs1.GetRoundState().Height + r := cs1.GetRoundState().Round partSize := types.BlockPartSizeBytes @@ -644,20 +633,20 @@ func TestStateLockPOLUnlock(t *testing.T) { */ // start round and wait for propose and prevote - startTestRound(cs1, cs1.Height, 0) - <-newRoundCh + startTestRound(cs1, h, r) + ensureNewRound(newRoundCh) re := <-proposalCh rs := re.(types.EventDataRoundState).RoundState.(*cstypes.RoundState) theBlockHash := rs.ProposalBlock.Hash() - <-voteCh // prevote + ensureVote(voteCh, h, r, types.VoteTypePrevote) signAddVotes(cs1, types.VoteTypePrevote, cs1.ProposalBlock.Hash(), cs1.ProposalBlockParts.Header(), vs2, vs3, vs4) - <-voteCh //precommit + ensureVote(voteCh, h, r, types.VoteTypePrecommit) // the proposed block should now be locked and our precommit added - validatePrecommit(t, cs1, 0, 0, vss[0], theBlockHash, theBlockHash) + validatePrecommit(t, cs1, r, 0, vss[0], theBlockHash, theBlockHash) rs = cs1.GetRoundState() @@ -681,7 +670,7 @@ func TestStateLockPOLUnlock(t *testing.T) { t.Fatal(err) } - <-newRoundCh + ensureNewRound(newRoundCh) t.Log("#### ONTO ROUND 1") /* Round2 (vs2, C) // B nil nil nil // nil nil nil _ @@ -698,21 +687,21 @@ func TestStateLockPOLUnlock(t *testing.T) { } // go to prevote, prevote for locked block (not proposal) - <-voteCh + ensureVote(voteCh, h, r+1, types.VoteTypePrevote) validatePrevote(t, cs1, 0, vss[0], lockedBlockHash) // now lets add prevotes from everyone else for nil (a polka!) signAddVotes(cs1, types.VoteTypePrevote, nil, types.PartSetHeader{}, vs2, vs3, vs4) // the polka makes us unlock and precommit nil - <-unlockCh - <-voteCh // precommit + ensureNewUnlock(unlockCh) + ensureVote(voteCh, h, r+1, types.VoteTypePrecommit) // we should have unlocked and committed nil // NOTE: since we don't relock on nil, the lock round is 0 - validatePrecommit(t, cs1, 1, 0, vss[0], nil, nil) + validatePrecommit(t, cs1, r+1, 0, vss[0], nil, nil) signAddVotes(cs1, types.VoteTypePrecommit, nil, types.PartSetHeader{}, vs2, vs3) - <-newRoundCh + ensureNewRound(newRoundCh) } // 4 vals @@ -722,6 +711,8 @@ func TestStateLockPOLUnlock(t *testing.T) { func TestStateLockPOLSafety1(t *testing.T) { cs1, vss := randConsensusState(4) vs2, vs3, vs4 := vss[1], vss[2], vss[3] + h := cs1.GetRoundState().Height + r := cs1.GetRoundState().Round partSize := types.BlockPartSizeBytes @@ -733,12 +724,12 @@ func TestStateLockPOLSafety1(t *testing.T) { // start round and wait for propose and prevote startTestRound(cs1, cs1.Height, 0) - <-newRoundCh + ensureNewRound(newRoundCh) re := <-proposalCh rs := re.(types.EventDataRoundState).RoundState.(*cstypes.RoundState) propBlock := rs.ProposalBlock - <-voteCh // prevote + ensureVote(voteCh, h, r, types.VoteTypePrevote) validatePrevote(t, cs1, 0, vss[0], propBlock.Hash()) @@ -747,6 +738,8 @@ func TestStateLockPOLSafety1(t *testing.T) { // before we time out into new round, set next proposer // and next proposal block + + //TODO: Should we remove this? /* _, v1 := cs1.Validators.GetByAddress(vss[0].Address) v1.VotingPower = 1 @@ -759,6 +752,11 @@ func TestStateLockPOLSafety1(t *testing.T) { // we do see them precommit nil signAddVotes(cs1, types.VoteTypePrecommit, nil, types.PartSetHeader{}, vs2, vs3, vs4) + ensureVote(voteCh, h, r, types.VoteTypePrecommit) + + ensureNewRound(newRoundCh) + t.Log("### ONTO ROUND 1") + prop, propBlock := decideProposal(cs1, vs2, vs2.Height, vs2.Round+1) propBlockHash := propBlock.Hash() propBlockParts := propBlock.MakePartSet(partSize) @@ -769,9 +767,6 @@ func TestStateLockPOLSafety1(t *testing.T) { if err := cs1.SetProposalAndBlock(prop, propBlock, propBlockParts, "some peer"); err != nil { t.Fatal(err) } - - <-newRoundCh - t.Log("### ONTO ROUND 1") /*Round2 // we timeout and prevote our lock // a polka happened but we didn't see it! @@ -792,24 +787,24 @@ func TestStateLockPOLSafety1(t *testing.T) { } t.Logf("new prop hash %v", fmt.Sprintf("%X", propBlockHash)) // go to prevote, prevote for proposal block - <-voteCh + ensureVote(voteCh, h, r+1, types.VoteTypePrevote) validatePrevote(t, cs1, 1, vss[0], propBlockHash) // now we see the others prevote for it, so we should lock on it signAddVotes(cs1, types.VoteTypePrevote, propBlockHash, propBlockParts.Header(), vs2, vs3, vs4) - <-voteCh // precommit + ensureVote(voteCh, h, r+1, types.VoteTypePrecommit) // we should have precommitted validatePrecommit(t, cs1, 1, 1, vss[0], propBlockHash, propBlockHash) signAddVotes(cs1, types.VoteTypePrecommit, nil, types.PartSetHeader{}, vs2, vs3) - <-timeoutWaitCh + ensureNewTimeout(timeoutWaitCh, cs1.config.TimeoutPrecommit.Nanoseconds()) incrementRound(vs2, vs3, vs4) - <-newRoundCh + ensureNewRound(newRoundCh) t.Log("### ONTO ROUND 2") /*Round3 @@ -817,10 +812,10 @@ func TestStateLockPOLSafety1(t *testing.T) { */ // timeout of propose - <-timeoutProposeCh + ensureNewTimeout(timeoutProposeCh, cs1.config.TimeoutPropose.Nanoseconds()) // finish prevote - <-voteCh + ensureVote(voteCh, h, r+2, types.VoteTypePrevote) // we should prevote what we're locked on validatePrevote(t, cs1, 2, vss[0], propBlockHash) @@ -845,6 +840,8 @@ func TestStateLockPOLSafety1(t *testing.T) { func TestStateLockPOLSafety2(t *testing.T) { cs1, vss := randConsensusState(4) vs2, vs3, vs4 := vss[1], vss[2], vss[3] + h := cs1.GetRoundState().Height + r := cs1.GetRoundState().Round partSize := types.BlockPartSizeBytes @@ -876,20 +873,19 @@ func TestStateLockPOLSafety2(t *testing.T) { t.Log("### ONTO Round 1") // jump in at round 1 - height := cs1.Height - startTestRound(cs1, height, 1) - <-newRoundCh + startTestRound(cs1, h, r+1) + ensureNewRound(newRoundCh) if err := cs1.SetProposalAndBlock(prop1, propBlock1, propBlockParts1, "some peer"); err != nil { t.Fatal(err) } - <-proposalCh + ensureNewProposal(proposalCh) - <-voteCh // prevote + ensureVote(voteCh, h, r+1, types.VoteTypePrevote) signAddVotes(cs1, types.VoteTypePrevote, propBlockHash1, propBlockParts1.Header(), vs2, vs3, vs4) - <-voteCh // precommit + ensureVote(voteCh, h, r+1, types.VoteTypePrecommit) // the proposed block should now be locked and our precommit added validatePrecommit(t, cs1, 1, 1, vss[0], propBlockHash1, propBlockHash1) @@ -900,10 +896,10 @@ func TestStateLockPOLSafety2(t *testing.T) { incrementRound(vs2, vs3, vs4) // timeout of precommit wait to new round - <-timeoutWaitCh + ensureNewTimeout(timeoutWaitCh, cs1.config.TimeoutPrecommit.Nanoseconds()) // in round 2 we see the polkad block from round 0 - newProp := types.NewProposal(height, 2, propBlockParts0.Header(), 0, propBlockID1) + newProp := types.NewProposal(h, 2, propBlockParts0.Header(), 0, propBlockID1) if err := vs3.SignProposal(config.ChainID(), newProp); err != nil { t.Fatal(err) } @@ -914,7 +910,7 @@ func TestStateLockPOLSafety2(t *testing.T) { // Add the pol votes addVotes(cs1, prevotes...) - <-newRoundCh + ensureNewRound(newRoundCh) t.Log("### ONTO Round 2") /*Round2 // now we see the polka from round 1, but we shouldnt unlock @@ -936,6 +932,26 @@ func TestStateLockPOLSafety2(t *testing.T) { } +// 4 vals, 3 Nil Precommits at P0 +// What we want: +// P0 waits for timeoutPrecommit before starting next round +func TestWaitingTimeoutOnNilPolka(t *testing.T) { + cs1, vss := randConsensusState(4) + vs2, vs3, vs4 := vss[1], vss[2], vss[3] + + timeoutWaitCh := subscribe(cs1.eventBus, types.EventQueryTimeoutWait) + newRoundCh := subscribe(cs1.eventBus, types.EventQueryNewRound) + + // start round + startTestRound(cs1, cs1.Height, 0) + ensureNewRound(newRoundCh) + + signAddVotes(cs1, types.VoteTypePrecommit, nil, types.PartSetHeader{}, vs2, vs3, vs4) + + ensureNewTimeout(timeoutWaitCh, cs1.config.TimeoutPrecommit.Nanoseconds()) + ensureNewRound(newRoundCh) +} + //------------------------------------------------------------------------------------------ // SlashingSuite // TODO: Slashing @@ -1024,7 +1040,8 @@ func TestStateSlashingPrecommits(t *testing.T) { func TestStateHalt1(t *testing.T) { cs1, vss := randConsensusState(4) vs2, vs3, vs4 := vss[1], vss[2], vss[3] - + h := cs1.GetRoundState().Height + r := cs1.GetRoundState().Round partSize := types.BlockPartSizeBytes proposalCh := subscribe(cs1.eventBus, types.EventQueryCompleteProposal) @@ -1035,16 +1052,16 @@ func TestStateHalt1(t *testing.T) { // start round and wait for propose and prevote startTestRound(cs1, cs1.Height, 0) - <-newRoundCh + ensureNewRound(newRoundCh) re := <-proposalCh rs := re.(types.EventDataRoundState).RoundState.(*cstypes.RoundState) propBlock := rs.ProposalBlock propBlockParts := propBlock.MakePartSet(partSize) - <-voteCh // prevote + ensureVote(voteCh, h, r, types.VoteTypePrevote) signAddVotes(cs1, types.VoteTypePrevote, propBlock.Hash(), propBlockParts.Header(), vs3, vs4) - <-voteCh // precommit + ensureVote(voteCh, h, r, types.VoteTypePrecommit) // the proposed block should now be locked and our precommit added validatePrecommit(t, cs1, 0, 0, vss[0], propBlock.Hash(), propBlock.Hash()) @@ -1058,7 +1075,7 @@ func TestStateHalt1(t *testing.T) { incrementRound(vs2, vs3, vs4) // timeout to new round - <-timeoutWaitCh + ensureNewTimeout(timeoutWaitCh, cs1.config.TimeoutPrecommit.Nanoseconds()) re = <-newRoundCh rs = re.(types.EventDataRoundState).RoundState.(*cstypes.RoundState) @@ -1069,14 +1086,14 @@ func TestStateHalt1(t *testing.T) { */ // go to prevote, prevote for locked block - <-voteCh // prevote + ensureVote(voteCh, h, r+1, types.VoteTypePrevote) validatePrevote(t, cs1, 0, vss[0], rs.LockedBlock.Hash()) // now we receive the precommit from the previous round addVotes(cs1, precommit4) // receiving that precommit should take us straight to commit - <-newBlockCh + ensureNewBlock(newBlockCh) re = <-newRoundCh rs = re.(types.EventDataRoundState).RoundState.(*cstypes.RoundState) diff --git a/lite/dynamic_verifier_test.go b/lite/dynamic_verifier_test.go index 401c14871..9ff8ed81f 100644 --- a/lite/dynamic_verifier_test.go +++ b/lite/dynamic_verifier_test.go @@ -4,6 +4,7 @@ import ( "fmt" "sync" "testing" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/p2p/conn/connection.go b/p2p/conn/connection.go index 2eb210e3c..0e33adab9 100644 --- a/p2p/conn/connection.go +++ b/p2p/conn/connection.go @@ -756,7 +756,7 @@ func (ch *Channel) recvPacketMsg(packet PacketMsg) ([]byte, error) { func (ch *Channel) updateStats() { // Exponential decay of stats. // TODO: optimize. - atomic.StoreInt64(&ch.recentlySent, int64(float64(atomic.LoadInt64(&ch.recentlySent)) * 0.8)) + atomic.StoreInt64(&ch.recentlySent, int64(float64(atomic.LoadInt64(&ch.recentlySent))*0.8)) } //---------------------------------------- diff --git a/p2p/peer.go b/p2p/peer.go index 064f91817..ba22695e7 100644 --- a/p2p/peer.go +++ b/p2p/peer.go @@ -102,7 +102,7 @@ type peer struct { // User data Data *cmn.CMap - metrics *Metrics + metrics *Metrics metricsTicker *time.Ticker } diff --git a/p2p/test_util.go b/p2p/test_util.go index 3d48aaac4..e35e0989f 100644 --- a/p2p/test_util.go +++ b/p2p/test_util.go @@ -28,7 +28,7 @@ func CreateRandomPeer(outbound bool) *peer { ID: netAddr.ID, ListenAddr: netAddr.DialString(), }, - mconn: &conn.MConnection{}, + mconn: &conn.MConnection{}, metrics: NopMetrics(), } p.SetLogger(log.TestingLogger().With("peer", addr))