From 6d2cb8ba8e7093ea40aa55cad9f5242d82967eed Mon Sep 17 00:00:00 2001 From: William Banfield <4561443+williambanfield@users.noreply.github.com> Date: Fri, 24 Sep 2021 11:19:57 -0400 Subject: [PATCH] consensus: remove logic to unlock block on 2/3 prevote for nil (#6954) --- internal/consensus/common_test.go | 20 +- internal/consensus/state.go | 72 +--- internal/consensus/state_test.go | 556 ++++++++++++++++++---------- internal/eventbus/event_bus.go | 4 - internal/eventbus/event_bus_test.go | 3 - internal/test/factory/block.go | 2 +- types/block.go | 6 +- types/canonical.go | 2 +- types/events.go | 2 - types/vote.go | 4 +- types/vote_set_test.go | 20 +- 11 files changed, 409 insertions(+), 282 deletions(-) diff --git a/internal/consensus/common_test.go b/internal/consensus/common_test.go index 6630a9e24..aaa250b5d 100644 --- a/internal/consensus/common_test.go +++ b/internal/consensus/common_test.go @@ -547,14 +547,6 @@ func ensureNoNewRoundStep(t *testing.T, stepCh <-chan tmpubsub.Message) { "We should be stuck waiting, not receiving NewRoundStep event") } -func ensureNoNewUnlock(t *testing.T, unlockCh <-chan tmpubsub.Message) { - t.Helper() - ensureNoNewEvent(t, - unlockCh, - ensureTimeout, - "We should be stuck waiting, not receiving Unlock event") -} - func ensureNoNewTimeout(t *testing.T, stepCh <-chan tmpubsub.Message, timeout int64) { t.Helper() timeoutDuration := time.Duration(timeout*10) * time.Nanosecond @@ -653,10 +645,16 @@ func ensureNewBlockHeader(t *testing.T, blockCh <-chan tmpubsub.Message, height } } -func ensureNewUnlock(t *testing.T, unlockCh <-chan tmpubsub.Message, height int64, round int32) { +func ensureLock(t *testing.T, lockCh <-chan tmpubsub.Message, height int64, round int32) { t.Helper() - ensureNewEvent(t, unlockCh, height, round, ensureTimeout, - "Timeout expired while waiting for NewUnlock event") + ensureNewEvent(t, lockCh, height, round, ensureTimeout, + "Timeout expired while waiting for LockValue event") +} + +func ensureRelock(t *testing.T, relockCh <-chan tmpubsub.Message, height int64, round int32) { + t.Helper() + ensureNewEvent(t, relockCh, height, round, ensureTimeout, + "Timeout expired while waiting for RelockValue event") } func ensureProposal(t *testing.T, proposalCh <-chan tmpubsub.Message, height int64, round int32, propID types.BlockID) { diff --git a/internal/consensus/state.go b/internal/consensus/state.go index 49559f15d..00596e36c 100644 --- a/internal/consensus/state.go +++ b/internal/consensus/state.go @@ -1413,7 +1413,6 @@ func (cs *State) enterPrevoteWait(ctx context.Context, height int64, round int32 // Enter: `timeoutPrecommit` after any +2/3 precommits. // Enter: +2/3 precomits for block or nil. // Lock & precommit the ProposalBlock if we have enough prevotes for it (a POL in this round) -// else, unlock an existing lock and precommit nil if +2/3 of prevotes were nil, // else, precommit nil otherwise. func (cs *State) enterPrecommit(ctx context.Context, height int64, round int32) { logger := cs.logger.With("height", height, "round", round) @@ -1460,21 +1459,9 @@ func (cs *State) enterPrecommit(ctx context.Context, height int64, round int32) panic(fmt.Sprintf("this POLRound should be %v but got %v", round, polRound)) } - // +2/3 prevoted nil. Unlock and precommit nil. - if len(blockID.Hash) == 0 { - if cs.LockedBlock == nil { - logger.Debug("precommit step; +2/3 prevoted for nil") - } else { - logger.Debug("precommit step; +2/3 prevoted for nil; unlocking") - cs.LockedRound = -1 - cs.LockedBlock = nil - cs.LockedBlockParts = nil - - if err := cs.eventBus.PublishEventUnlock(ctx, cs.RoundStateEvent()); err != nil { - logger.Error("failed publishing event unlock", "err", err) - } - } - + // +2/3 prevoted nil. Precommit nil. + if blockID.IsNil() { + logger.Debug("precommit step; +2/3 prevoted for nil") cs.signAddVote(ctx, tmproto.PrecommitType, nil, types.PartSetHeader{}) return } @@ -1494,7 +1481,9 @@ func (cs *State) enterPrecommit(ctx context.Context, height int64, round int32) return } - // If +2/3 prevoted for proposal block, stage and precommit it + // If greater than 2/3 of the voting power on the network prevoted for + // the proposed block, update our locked block to this block and issue a + // precommit vote for it. if cs.ProposalBlock.HashesTo(blockID.Hash) { logger.Debug("precommit step; +2/3 prevoted proposal block; locking", "hash", blockID.Hash) @@ -1516,23 +1505,14 @@ func (cs *State) enterPrecommit(ctx context.Context, height int64, round int32) } // There was a polka in this round for a block we don't have. - // Fetch that block, unlock, and precommit nil. - // The +2/3 prevotes for this round is the POL for our unlock. + // Fetch that block, and precommit nil. logger.Debug("precommit step; +2/3 prevotes for a block we do not have; voting nil", "block_id", blockID) - cs.LockedRound = -1 - cs.LockedBlock = nil - cs.LockedBlockParts = nil - if !cs.ProposalBlockParts.HasHeader(blockID.PartSetHeader) { cs.ProposalBlock = nil cs.ProposalBlockParts = types.NewPartSetFromHeader(blockID.PartSetHeader) } - if err := cs.eventBus.PublishEventUnlock(ctx, cs.RoundStateEvent()); err != nil { - logger.Error("failed publishing event unlock", "err", err) - } - cs.signAddVote(ctx, tmproto.PrecommitType, nil, types.PartSetHeader{}) } @@ -1640,7 +1620,7 @@ func (cs *State) tryFinalizeCommit(ctx context.Context, height int64) { } blockID, ok := cs.Votes.Precommits(cs.CommitRound).TwoThirdsMajority() - if !ok || len(blockID.Hash) == 0 { + if !ok || blockID.IsNil() { logger.Error("failed attempt to finalize commit; there was no +2/3 majority or +2/3 was for nil") return } @@ -1967,7 +1947,7 @@ func (cs *State) addProposalBlockPart( // Update Valid* if we can. prevotes := cs.Votes.Prevotes(cs.Round) blockID, hasTwoThirds := prevotes.TwoThirdsMajority() - if hasTwoThirds && !blockID.IsZero() && (cs.ValidRound < cs.Round) { + if hasTwoThirds && !blockID.IsNil() && (cs.ValidRound < cs.Round) { if cs.ProposalBlock.HashesTo(blockID.Hash) { cs.logger.Debug( "updating valid block to new proposal block", @@ -2120,33 +2100,13 @@ func (cs *State) addVote( prevotes := cs.Votes.Prevotes(vote.Round) cs.logger.Debug("added vote to prevote", "vote", vote, "prevotes", prevotes.StringShort()) - // If +2/3 prevotes for a block or nil for *any* round: - if blockID, ok := prevotes.TwoThirdsMajority(); ok { - // There was a polka! - // If we're locked but this is a recent polka, unlock. - // If it matches our ProposalBlock, update the ValidBlock - - // Unlock if `cs.LockedRound < vote.Round <= cs.Round` - // NOTE: If vote.Round > cs.Round, we'll deal with it when we get to vote.Round - if (cs.LockedBlock != nil) && - (cs.LockedRound < vote.Round) && - (vote.Round <= cs.Round) && - !cs.LockedBlock.HashesTo(blockID.Hash) { - - cs.logger.Debug("unlocking because of POL", "locked_round", cs.LockedRound, "pol_round", vote.Round) - - cs.LockedRound = -1 - cs.LockedBlock = nil - cs.LockedBlockParts = nil - - if err := cs.eventBus.PublishEventUnlock(ctx, cs.RoundStateEvent()); err != nil { - return added, err - } - } + // Check to see if >2/3 of the voting power on the network voted for any non-nil block. + if blockID, ok := prevotes.TwoThirdsMajority(); ok && !blockID.IsNil() { + // Greater than 2/3 of the voting power on the network voted for some + // non-nil block // Update Valid* if we can. - // NOTE: our proposal block may be nil or not what received a polka.. - if len(blockID.Hash) != 0 && (cs.ValidRound < vote.Round) && (vote.Round == cs.Round) { + if cs.ValidRound < vote.Round && vote.Round == cs.Round { if cs.ProposalBlock.HashesTo(blockID.Hash) { cs.logger.Debug("updating valid block because of POL", "valid_round", cs.ValidRound, "pol_round", vote.Round) cs.ValidRound = vote.Round @@ -2182,7 +2142,7 @@ func (cs *State) addVote( case cs.Round == vote.Round && cstypes.RoundStepPrevote <= cs.Step: // current round blockID, ok := prevotes.TwoThirdsMajority() - if ok && (cs.isProposalComplete() || len(blockID.Hash) == 0) { + if ok && (cs.isProposalComplete() || blockID.IsNil()) { cs.enterPrecommit(ctx, height, vote.Round) } else if prevotes.HasTwoThirdsAny() { cs.enterPrevoteWait(ctx, height, vote.Round) @@ -2210,7 +2170,7 @@ func (cs *State) addVote( cs.enterNewRound(ctx, height, vote.Round) cs.enterPrecommit(ctx, height, vote.Round) - if len(blockID.Hash) != 0 { + if !blockID.IsNil() { cs.enterCommit(ctx, height, vote.Round) if cs.config.SkipTimeoutCommit && precommits.HasAll() { cs.enterNewRound(ctx, cs.Height, 0) diff --git a/internal/consensus/state_test.go b/internal/consensus/state_test.go index 797f5b197..e1a072553 100644 --- a/internal/consensus/state_test.go +++ b/internal/consensus/state_test.go @@ -35,16 +35,22 @@ x * TestFullRound1 - 1 val, full successful round x * TestFullRoundNil - 1 val, full round of nil x * TestFullRound2 - 2 vals, both required for full round LockSuite -x * TestLockNoPOL - 2 vals, 4 rounds. one val locked, precommits nil every round except first. -x * TestLockPOLRelock - 4 vals, one precommits, other 3 polka at next round, so we unlock and precomit the polka -x * TestLockPOLUnlock - 4 vals, one precommits, other 3 polka nil at next round, so we unlock and precomit nil -x * TestLockPOLSafety1 - 4 vals. We shouldn't change lock based on polka at earlier round -x * TestLockPOLSafety2 - 4 vals. After unlocking, we shouldn't relock based on polka at earlier round +x * TestStateLock_NoPOL - 2 vals, 4 rounds. one val locked, precommits nil every round except first. +x * TestStateLock_POLUpdateLock - 4 vals, one precommits, +other 3 polka at next round, so we unlock and precomit the polka +x * TestStateLock_POLRelock - 4 vals, polka in round 1 and polka in round 2. +Ensure validator updates locked round. +x_*_TestStateLock_POLDoesNotUnlock 4 vals, one precommits, other 3 polka nil at +next round, so we precommit nil but maintain lock +x * TestStateLock_MissingProposalWhenPOLSeenDoesNotUpdateLock - 4 vals, 1 misses proposal but sees POL. +x * TestStateLock_MissingProposalWhenPOLSeenDoesNotUnlock - 4 vals, 1 misses proposal but sees POL. +x * TestStateLock_POLSafety1 - 4 vals. We shouldn't change lock based on polka at earlier round +x * TestStateLock_POLSafety2 - 4 vals. After unlocking, we shouldn't relock based on polka at earlier round * TestNetworkLock - once +1/3 precommits, network should be locked * TestNetworkLockPOL - once +1/3 precommits, the block with more recent polka is committed SlashingSuite -x * TestSlashingPrevotes - a validator prevoting twice in a round gets slashed -x * TestSlashingPrecommits - a validator precomitting twice in a round gets slashed +x * TestStateSlashing_Prevotes - a validator prevoting twice in a round gets slashed +x * TestStateSlashing_Precommits - a validator precomitting twice in a round gets slashed CatchupSuite * TestCatchup - if we might be behind and we've seen any 2/3 prevotes, round skip to new round, precommit, or prevote HaltSuite @@ -139,8 +145,7 @@ func TestStateProposerSelection2(t *testing.T) { int(i+2)%len(vss), prop.Address) - rs := cs1.GetRoundState() - signAddVotes(ctx, t, config, cs1, tmproto.PrecommitType, nil, rs.ProposalBlockParts.Header(), vss[1:]...) + signAddVotes(ctx, t, config, cs1, tmproto.PrecommitType, nil, types.PartSetHeader{}, vss[1:]...) ensureNewRound(t, newRoundCh, height, i+round+1) // wait for the new round event each round incrementRound(vss[1:]...) } @@ -462,7 +467,7 @@ func TestStateFullRound2(t *testing.T) { // two validators, 4 rounds. // two vals take turns proposing. val1 locks on first one, precommits nil on everything else -func TestStateLockNoPOL(t *testing.T) { +func TestStateLock_NoPOL(t *testing.T) { config := configSetup(t) ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -668,11 +673,12 @@ func TestStateLockNoPOL(t *testing.T) { ensurePrecommit(t, voteCh, height, round) } -// 4 vals in two rounds, -// in round one: v1 precommits, other 3 only prevote so the block isn't committed -// in round two: v1 prevotes the same block that the node is locked on -// the others prevote a new block hence v1 changes lock and precommits the new block with the others -func TestStateLockPOLRelock(t *testing.T) { +// TestStateLock_POLUpdateLock tests that a validator maintains updates its locked +// block if the following conditions are met within a round: +// 1. The validator received a valid proposal for the block +// 2. The validator received prevotes representing greater than 2/3 of the voting +// power on the network for the block. +func TestStateLock_POLUpdateLock(t *testing.T) { config := configSetup(t) logger := log.TestingLogger() @@ -691,16 +697,18 @@ func TestStateLockPOLRelock(t *testing.T) { require.NoError(t, err) addr := pv1.Address() voteCh := subscribeToVoter(ctx, t, cs1, addr) + lockCh := subscribe(ctx, t, cs1.eventBus, types.EventQueryLock) newRoundCh := subscribe(ctx, t, cs1.eventBus, types.EventQueryNewRound) - newBlockCh := subscribe(ctx, t, cs1.eventBus, types.EventQueryNewBlockHeader) - - // everything done from perspective of cs1 /* - Round1 (cs1, B) // B B B B// B nil B nil + Round 0: + cs1 creates a proposal for block B. + Send a prevote for B from each of the validators to cs1. + Send a precommit for nil from all of the validators to cs1. - eg. vs2 and vs4 didn't see the 2/3 prevotes + This ensures that cs1 will lock on B in this round but not precommit it. */ + t.Log("### Starting Round 0") // start round and wait for propose and prevote startTestRound(ctx, cs1, height, round) @@ -711,99 +719,211 @@ func TestStateLockPOLRelock(t *testing.T) { theBlockHash := rs.ProposalBlock.Hash() theBlockParts := rs.ProposalBlockParts.Header() - ensurePrevote(t, voteCh, height, round) // prevote + ensurePrevote(t, voteCh, height, round) signAddVotes(ctx, t, config, cs1, tmproto.PrevoteType, theBlockHash, theBlockParts, vs2, vs3, vs4) - ensurePrecommit(t, voteCh, height, round) // our precommit - // the proposed block should now be locked and our precommit added + // check that the validator generates a Lock event. + ensureLock(t, lockCh, height, round) + + // the proposed block should now be locked and our precommit added. + ensurePrecommit(t, voteCh, height, round) validatePrecommit(ctx, t, cs1, round, round, vss[0], theBlockHash, theBlockHash) - // add precommits from the rest + // add precommits from the rest of the validators. signAddVotes(ctx, t, config, cs1, tmproto.PrecommitType, nil, types.PartSetHeader{}, vs2, vs3, vs4) - // before we timeout to the new round set the new proposal - cs2 := newState(ctx, t, logger, cs1.state, vs2, kvstore.NewApplication()) - - prop, propBlock := decideProposal(ctx, t, cs2, vs2, vs2.Height, vs2.Round+1) - if prop == nil || propBlock == nil { - t.Fatal("Failed to create proposal block with vs2") - } - propBlockParts, err := propBlock.MakePartSet(partSize) - require.NoError(t, err) - - propBlockHash := propBlock.Hash() - require.NotEqual(t, propBlockHash, theBlockHash) - - incrementRound(vs2, vs3, vs4) - - // timeout to new round + // timeout to new round. ensureNewTimeout(t, timeoutWaitCh, height, round, cs1.config.Precommit(round).Nanoseconds()) - round++ // moving to the next round - //XXX: this isnt guaranteed to get there before the timeoutPropose ... - if err := cs1.SetProposalAndBlock(ctx, prop, propBlock, propBlockParts, "some peer"); err != nil { + /* + Round 1: + Create a block, D and send a proposal for it to cs1 + Send a prevote for D from each of the validators to cs1. + Send a precommit for nil from all of the validtors to cs1. + + Check that cs1 is now locked on the new block, D and no longer on the old block. + */ + t.Log("### Starting Round 1") + incrementRound(vs2, vs3, vs4) + round++ + + // Generate a new proposal block. + cs2 := newState(ctx, t, logger, cs1.state, vs2, kvstore.NewApplication()) + require.NoError(t, err) + propR1, propBlockR1 := decideProposal(ctx, t, cs2, vs2, vs2.Height, vs2.Round) + propBlockR1Parts, err := propBlockR1.MakePartSet(partSize) + require.NoError(t, err) + propBlockR1Hash := propBlockR1.Hash() + require.NotEqual(t, propBlockR1Hash, theBlockHash) + if err := cs1.SetProposalAndBlock(ctx, propR1, propBlockR1, propBlockR1Parts, "some peer"); err != nil { t.Fatal(err) } ensureNewRound(t, newRoundCh, height, round) - t.Log("### ONTO ROUND 1") - /* - Round2 (vs2, C) // B C C C // C C C _) - - cs1 changes lock! - */ - - // now we're on a new round and not the proposer - // but we should receive the proposal + // ensure that the validator receives the proposal. ensureNewProposal(t, proposalCh, height, round) - // go to prevote, node should prevote for locked block (not the new proposal) - this is relocking + // Prevote our locked block. + // TODO: Ensure we prevote for the proposal if it is valid and from a round greater than + // the valid round: https://github.com/tendermint/tendermint/issues/6850. ensurePrevote(t, voteCh, height, round) validatePrevote(ctx, t, cs1, round, vss[0], theBlockHash) - // now lets add prevotes from everyone else for the new block - signAddVotes(ctx, t, config, cs1, tmproto.PrevoteType, propBlockHash, propBlockParts.Header(), vs2, vs3, vs4) + // Add prevotes from the remainder of the validators for the new locked block. + signAddVotes(ctx, t, config, cs1, tmproto.PrevoteType, propBlockR1Hash, propBlockR1Parts.Header(), vs2, vs3, vs4) + + // Check that we lock on a new block. + ensureLock(t, lockCh, height, round) ensurePrecommit(t, voteCh, height, round) - // we should have unlocked and locked on the new block, sending a precommit for this new block - validatePrecommit(ctx, t, cs1, round, round, vss[0], propBlockHash, propBlockHash) - // more prevote creating a majority on the new block and this is then committed - signAddVotes(ctx, t, config, cs1, tmproto.PrecommitType, propBlockHash, propBlockParts.Header(), vs2, vs3) - ensureNewBlockHeader(t, newBlockCh, height, propBlockHash) - - ensureNewRound(t, newRoundCh, height+1, 0) + // We should now be locked on the new block and prevote it since we saw a sufficient amount + // prevote for the block. + validatePrecommit(ctx, t, cs1, round, round, vss[0], propBlockR1Hash, propBlockR1Hash) } -// 4 vals, one precommits, other 3 polka at next round, so we unlock and precomit the polka -func TestStateLockPOLUnlock(t *testing.T) { - config := configSetup(t) +// TestStateLock_POLRelock tests that a validator updates its locked round if +// it receives votes representing over 2/3 of the voting power on the network +// for a block that it is already locked in. +func TestStateLock_POLRelock(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() + config := configSetup(t) cs1, vss := randState(ctx, t, config, log.TestingLogger(), 4) vs2, vs3, vs4 := vss[1], vss[2], vss[3] height, round := cs1.Height, cs1.Round - partSize := types.BlockPartSizeBytes - - proposalCh := subscribe(ctx, t, cs1.eventBus, types.EventQueryCompleteProposal) timeoutWaitCh := subscribe(ctx, t, cs1.eventBus, types.EventQueryTimeoutWait) - newRoundCh := subscribe(ctx, t, cs1.eventBus, types.EventQueryNewRound) - unlockCh := subscribe(ctx, t, cs1.eventBus, types.EventQueryUnlock) + proposalCh := subscribe(ctx, t, cs1.eventBus, types.EventQueryCompleteProposal) pv1, err := cs1.privValidator.GetPubKey(ctx) require.NoError(t, err) addr := pv1.Address() voteCh := subscribeToVoter(ctx, t, cs1, addr) - - // everything done from perspective of cs1 + lockCh := subscribe(ctx, t, cs1.eventBus, types.EventQueryLock) + relockCh := subscribe(ctx, t, cs1.eventBus, types.EventQueryRelock) + newRoundCh := subscribe(ctx, t, cs1.eventBus, types.EventQueryNewRound) /* - Round1 (cs1, B) // B B B B // B nil B nil - eg. didn't see the 2/3 prevotes + Round 0: + cs1 creates a proposal for block B. + Send a prevote for B from each of the validators to cs1. + Send a precommit for nil from all of the validators to cs1. + This ensures that cs1 will lock on B in this round but not precommit it. */ + t.Log("### Starting Round 0") + + startTestRound(ctx, cs1, height, round) + + ensureNewRound(t, newRoundCh, height, round) + ensureNewProposal(t, proposalCh, height, round) + rs := cs1.GetRoundState() + theBlock := rs.ProposalBlock + theBlockHash := rs.ProposalBlock.Hash() + theBlockParts := rs.ProposalBlockParts + + ensurePrevote(t, voteCh, height, round) + + signAddVotes(ctx, t, config, cs1, tmproto.PrevoteType, theBlockHash, theBlockParts.Header(), vs2, vs3, vs4) + + // check that the validator generates a Lock event. + ensureLock(t, lockCh, height, round) + + // the proposed block should now be locked and our precommit added. + ensurePrecommit(t, voteCh, height, round) + validatePrecommit(ctx, t, cs1, round, round, vss[0], theBlockHash, theBlockHash) + + // add precommits from the rest of the validators. + signAddVotes(ctx, t, config, cs1, tmproto.PrecommitType, nil, types.PartSetHeader{}, vs2, vs3, vs4) + + // timeout to new round. + ensureNewTimeout(t, timeoutWaitCh, height, round, cs1.config.Precommit(round).Nanoseconds()) + + /* + Round 1: + Create a proposal for block B, the same block from round 1. + Send a prevote for B from each of the validators to cs1. + Send a precommit for nil from all of the validtors to cs1. + + Check that cs1 updates its 'locked round' value to the current round. + */ + t.Log("### Starting Round 1") + incrementRound(vs2, vs3, vs4) + round++ + propBlockID := types.BlockID{Hash: theBlockHash, PartSetHeader: theBlockParts.Header()} + propR1 := types.NewProposal(height, round, cs1.ValidRound, propBlockID) + p := propR1.ToProto() + if err := vs2.SignProposal(context.Background(), cs1.state.ChainID, p); err != nil { + t.Fatalf("error signing proposal: %s", err) + } + propR1.Signature = p.Signature + if err := cs1.SetProposalAndBlock(ctx, propR1, theBlock, theBlockParts, ""); err != nil { + t.Fatal(err) + } + + ensureNewRound(t, newRoundCh, height, round) + + // ensure that the validator receives the proposal. + ensureNewProposal(t, proposalCh, height, round) + + // Prevote our locked block. + // TODO: Ensure we prevote for the proposal if it is valid and from a round greater than + // the valid round: https://github.com/tendermint/tendermint/issues/6850. + ensurePrevote(t, voteCh, height, round) + validatePrevote(ctx, t, cs1, round, vss[0], theBlockHash) + + // Add prevotes from the remainder of the validators for the locked block. + signAddVotes(ctx, t, config, cs1, tmproto.PrevoteType, theBlockHash, theBlockParts.Header(), vs2, vs3, vs4) + + // Check that we relock. + ensureRelock(t, relockCh, height, round) + + ensurePrecommit(t, voteCh, height, round) + + // We should now be locked on the same block but with an updated locked round. + validatePrecommit(ctx, t, cs1, round, round, vss[0], theBlockHash, theBlockHash) +} + +// TestStateLock_POLDoesNotUnlock tests that a validator maintains its locked block +// despite receiving +2/3 nil prevotes and nil precommits from other validators. +// Tendermint used to 'unlock' its locked block when greater than 2/3 prevotes +// for a nil block were seen. This behavior has been removed and this test ensures +// that it has been completely removed. +func TestStateLock_POLDoesNotUnlock(t *testing.T) { + config := configSetup(t) + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + /* + All of the assertions in this test occur on the `cs1` validator. + The test sends signed votes from the other validators to cs1 and + cs1's state is then examined to verify that it now matches the expected + state. + */ + + cs1, vss := randState(ctx, t, config, log.TestingLogger(), 4) + vs2, vs3, vs4 := vss[1], vss[2], vss[3] + height, round := cs1.Height, cs1.Round + + proposalCh := subscribe(ctx, t, cs1.eventBus, types.EventQueryCompleteProposal) + timeoutWaitCh := subscribe(ctx, t, cs1.eventBus, types.EventQueryTimeoutWait) + newRoundCh := subscribe(ctx, t, cs1.eventBus, types.EventQueryNewRound) + lockCh := subscribe(ctx, t, cs1.eventBus, types.EventQueryLock) + pv1, err := cs1.privValidator.GetPubKey(context.Background()) + require.NoError(t, err) + addr := pv1.Address() + voteCh := subscribeToVoter(ctx, t, cs1, addr) + + /* + Round 0: + Create a block, B + Send a prevote for B from each of the validators to `cs1`. + Send a precommit for B from one of the validtors to `cs1`. + + This ensures that cs1 will lock on B in this round. + */ + t.Log("#### ONTO ROUND 0") // start round and wait for propose and prevote startTestRound(ctx, cs1, height, round) @@ -819,63 +939,98 @@ func TestStateLockPOLUnlock(t *testing.T) { signAddVotes(ctx, t, config, cs1, tmproto.PrevoteType, theBlockHash, theBlockParts, vs2, vs3, vs4) + // the validator should have locked a block in this round. + ensureLock(t, lockCh, height, round) + ensurePrecommit(t, voteCh, height, round) - // the proposed block should now be locked and our precommit added + // the proposed block should now be locked and our should be for this locked block. + validatePrecommit(ctx, t, cs1, round, round, vss[0], theBlockHash, theBlockHash) - // add precommits from the rest + // Add precommits from the other validators. + // We only issue 1/2 Precommits for the block in this round. + // This ensures that the validator being tested does not commit the block. + // We do not want the validator to commit the block because we want the test + // test to proceeds to the next consensus round. signAddVotes(ctx, t, config, cs1, tmproto.PrecommitType, nil, types.PartSetHeader{}, vs2, vs4) signAddVotes(ctx, t, config, cs1, tmproto.PrecommitType, theBlockHash, theBlockParts, vs3) - // before we time out into new round, set next proposal block - prop, propBlock := decideProposal(ctx, t, cs1, vs2, vs2.Height, vs2.Round+1) - propBlockParts, err := propBlock.MakePartSet(partSize) - require.NoError(t, err) - // timeout to new round ensureNewTimeout(t, timeoutWaitCh, height, round, cs1.config.Precommit(round).Nanoseconds()) - rs = cs1.GetRoundState() - lockedBlockHash := rs.LockedBlock.Hash() - incrementRound(vs2, vs3, vs4) - round++ // moving to the next round - - ensureNewRound(t, newRoundCh, height, round) - t.Log("#### ONTO ROUND 1") /* - Round2 (vs2, C) // B nil nil nil // nil nil nil _ - cs1 unlocks! + Round 1: + Send a prevote for nil from >2/3 of the validators to `cs1`. + Check that cs1 maintains its lock on B but precommits nil. + Send a precommit for nil from >2/3 of the validators to `cs1`. */ - //XXX: this isnt guaranteed to get there before the timeoutPropose ... - if err := cs1.SetProposalAndBlock(ctx, prop, propBlock, propBlockParts, "some peer"); err != nil { + t.Log("#### ONTO ROUND 1") + round++ + incrementRound(vs2, vs3, vs4) + prop, propBlock := decideProposal(ctx, t, cs1, vs2, vs2.Height, vs2.Round) + propBlockParts, err := propBlock.MakePartSet(types.BlockPartSizeBytes) + require.NoError(t, err) + if err := cs1.SetProposalAndBlock(ctx, prop, propBlock, propBlockParts, ""); err != nil { t.Fatal(err) } + ensureNewRound(t, newRoundCh, height, round) + ensureNewProposal(t, proposalCh, height, round) - // go to prevote, prevote for locked block (not proposal) + // prevote for the locked block. We do not currently prevote for the + // proposal. + // TODO: do not prevote the locked block if it does not match the proposal. + // (https://github.com/tendermint/tendermint/issues/6850) ensurePrevote(t, voteCh, height, round) - validatePrevote(ctx, t, cs1, round, vss[0], lockedBlockHash) - // now lets add prevotes from everyone else for nil (a polka!) + validatePrevote(ctx, t, cs1, round, vss[0], theBlockHash) + // add >2/3 prevotes for nil from all other validators signAddVotes(ctx, t, config, cs1, tmproto.PrevoteType, nil, types.PartSetHeader{}, vs2, vs3, vs4) - // the polka makes us unlock and precommit nil - ensureNewUnlock(t, unlockCh, height, round) ensurePrecommit(t, voteCh, height, round) - // we should have unlocked and committed nil - // NOTE: since we don't relock on nil, the lock round is -1 - validatePrecommit(ctx, t, cs1, round, -1, vss[0], nil, nil) + // verify that we haven't update our locked block since the first round + validatePrecommit(ctx, t, cs1, round, 0, vss[0], nil, theBlockHash) + + signAddVotes(ctx, t, config, cs1, tmproto.PrecommitType, nil, types.PartSetHeader{}, vs2, vs3, vs4) + ensureNewTimeout(t, timeoutWaitCh, height, round, cs1.config.Precommit(round).Nanoseconds()) + + /* + Round 2: + The validator cs1 saw >2/3 precommits for nil in the previous round. + Send the validator >2/3 prevotes for nil and ensure that it did not + unlock its block at the end of the previous round. + */ + t.Log("#### ONTO ROUND 2") + round++ + incrementRound(vs2, vs3, vs4) + prop, propBlock = decideProposal(ctx, t, cs1, vs3, vs3.Height, vs3.Round) + propBlockParts, err = propBlock.MakePartSet(types.BlockPartSizeBytes) + require.NoError(t, err) + if err := cs1.SetProposalAndBlock(ctx, prop, propBlock, propBlockParts, ""); err != nil { + t.Fatal(err) + } + + ensureNewRound(t, newRoundCh, height, round) + + ensureNewProposal(t, proposalCh, height, round) + + ensurePrevote(t, voteCh, height, round) + validatePrevote(ctx, t, cs1, round, vss[0], theBlockHash) + + signAddVotes(ctx, t, config, cs1, tmproto.PrevoteType, nil, types.PartSetHeader{}, vs2, vs3, vs4) + + ensurePrecommit(t, voteCh, height, round) + + // verify that we haven't update our locked block since the first round + validatePrecommit(ctx, t, cs1, round, 0, vss[0], nil, theBlockHash) - signAddVotes(ctx, t, config, cs1, tmproto.PrecommitType, nil, types.PartSetHeader{}, vs2, vs3) - ensureNewRound(t, newRoundCh, height, round+1) } -// 4 vals, v1 locks on proposed block in the first round but the other validators only prevote -// In the second round, v1 misses the proposal but sees a majority prevote an unknown block so -// v1 should unlock and precommit nil. In the third round another block is proposed, all vals -// prevote and now v1 can lock onto the third block and precommit that -func TestStateLockPOLUnlockOnUnknownBlock(t *testing.T) { +// TestStateLock_MissingProposalWhenPOLSeenDoesNotUnlock tests that observing +// a two thirds majority for a block does not cause a validator to upate its lock on the +// new block if a proposal was not seen for that block. +func TestStateLock_MissingProposalWhenPOLSeenDoesNotUpdateLock(t *testing.T) { config := configSetup(t) logger := log.TestingLogger() ctx, cancel := context.WithCancel(context.Background()) @@ -895,13 +1050,15 @@ func TestStateLockPOLUnlockOnUnknownBlock(t *testing.T) { addr := pv1.Address() voteCh := subscribeToVoter(ctx, t, cs1, addr) newRoundCh := subscribe(ctx, t, cs1.eventBus, types.EventQueryNewRound) - // everything done from perspective of cs1 - /* - Round0 (cs1, A) // A A A A// A nil nil nil - */ + Round 0: + cs1 creates a proposal for block B. + Send a prevote for B from each of the validators to cs1. + Send a precommit for nil from all of the validators to cs1. - // start round and wait for propose and prevote + This ensures that cs1 will lock on B in this round but not precommit it. + */ + t.Log("### Starting Round 0") startTestRound(ctx, cs1, height, round) ensureNewRound(t, newRoundCh, height, round) @@ -921,9 +1078,22 @@ func TestStateLockPOLUnlockOnUnknownBlock(t *testing.T) { // add precommits from the rest signAddVotes(ctx, t, config, cs1, tmproto.PrecommitType, nil, types.PartSetHeader{}, vs2, vs3, vs4) - // before we timeout to the new round set the new proposal + // timeout to new round + ensureNewTimeout(t, timeoutWaitCh, height, round, cs1.config.Precommit(round).Nanoseconds()) + + /* + Round 1: + Create a new block, D but do not send it to cs1. + Send a prevote for D from each of the validators to cs1. + + Check that cs1 does not update its locked block to this missed block D. + */ + t.Log("### Starting Round 1") + incrementRound(vs2, vs3, vs4) + round++ cs2 := newState(ctx, t, logger, cs1.state, vs2, kvstore.NewApplication()) - prop, propBlock := decideProposal(ctx, t, cs2, vs2, vs2.Height, vs2.Round+1) + require.NoError(t, err) + prop, propBlock := decideProposal(ctx, t, cs2, vs2, vs2.Height, vs2.Round) if prop == nil || propBlock == nil { t.Fatal("Failed to create proposal block with vs2") } @@ -933,21 +1103,7 @@ func TestStateLockPOLUnlockOnUnknownBlock(t *testing.T) { secondBlockHash := propBlock.Hash() require.NotEqual(t, secondBlockHash, firstBlockHash) - incrementRound(vs2, vs3, vs4) - - // timeout to new round - ensureNewTimeout(t, timeoutWaitCh, height, round, cs1.config.Precommit(round).Nanoseconds()) - - round++ // moving to the next round - ensureNewRound(t, newRoundCh, height, round) - t.Log("### ONTO ROUND 1") - - /* - Round1 (vs2, B) // A B B B // nil nil nil nil) - */ - - // now we're on a new round but v1 misses the proposal // go to prevote, node should prevote for locked block (not the new proposal) - this is relocking ensurePrevote(t, voteCh, height, round) @@ -957,61 +1113,89 @@ func TestStateLockPOLUnlockOnUnknownBlock(t *testing.T) { signAddVotes(ctx, t, config, cs1, tmproto.PrevoteType, secondBlockHash, secondBlockParts.Header(), vs2, vs3, vs4) ensurePrecommit(t, voteCh, height, round) - // we should have unlocked and locked on the new block, sending a precommit for this new block + validatePrecommit(ctx, t, cs1, round, 0, vss[0], nil, firstBlockHash) +} + +// TestStateLock_DoesNotLockOnOldProposal tests that observing +// a two thirds majority for a block does not cause a validator to lock on the +// block if a proposal was not seen for that block in the current round, but +// was seen in a previous round. +func TestStateLock_DoesNotLockOnOldProposal(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + config := configSetup(t) + + cs1, vss := randState(ctx, t, config, log.TestingLogger(), 4) + vs2, vs3, vs4 := vss[1], vss[2], vss[3] + height, round := cs1.Height, cs1.Round + + timeoutWaitCh := subscribe(ctx, t, cs1.eventBus, types.EventQueryTimeoutWait) + proposalCh := subscribe(ctx, t, cs1.eventBus, types.EventQueryCompleteProposal) + pv1, err := cs1.privValidator.GetPubKey(context.Background()) + require.NoError(t, err) + addr := pv1.Address() + voteCh := subscribeToVoter(ctx, t, cs1, addr) + newRoundCh := subscribe(ctx, t, cs1.eventBus, types.EventQueryNewRound) + /* + Round 0: + cs1 creates a proposal for block B. + Send a prevote for nil from each of the validators to cs1. + Send a precommit for nil from all of the validators to cs1. + + This ensures that cs1 will not lock on B. + */ + t.Log("### Starting Round 0") + startTestRound(ctx, cs1, height, round) + + ensureNewRound(t, newRoundCh, height, round) + ensureNewProposal(t, proposalCh, height, round) + rs := cs1.GetRoundState() + firstBlockHash := rs.ProposalBlock.Hash() + firstBlockParts := rs.ProposalBlockParts.Header() + + ensurePrevote(t, voteCh, height, round) + + signAddVotes(ctx, t, config, cs1, tmproto.PrevoteType, nil, types.PartSetHeader{}, vs2, vs3, vs4) + + // The proposed block should not have been locked. + ensurePrecommit(t, voteCh, height, round) validatePrecommit(ctx, t, cs1, round, -1, vss[0], nil, nil) - if err := cs1.SetProposalAndBlock(ctx, prop, propBlock, secondBlockParts, "some peer"); err != nil { - t.Fatal(err) - } - - // more prevote creating a majority on the new block and this is then committed signAddVotes(ctx, t, config, cs1, tmproto.PrecommitType, nil, types.PartSetHeader{}, vs2, vs3, vs4) - // before we timeout to the new round set the new proposal - cs3 := newState(ctx, t, logger, cs1.state, vs3, kvstore.NewApplication()) - require.NoError(t, err) - prop, propBlock = decideProposal(ctx, t, cs3, vs3, vs3.Height, vs3.Round+1) - if prop == nil || propBlock == nil { - t.Fatal("Failed to create proposal block with vs2") - } - thirdPropBlockParts, err := propBlock.MakePartSet(partSize) - require.NoError(t, err) - thirdPropBlockHash := propBlock.Hash() - require.NotEqual(t, secondBlockHash, thirdPropBlockHash) - incrementRound(vs2, vs3, vs4) // timeout to new round ensureNewTimeout(t, timeoutWaitCh, height, round, cs1.config.Precommit(round).Nanoseconds()) - round++ // moving to the next round - ensureNewRound(t, newRoundCh, height, round) - t.Log("### ONTO ROUND 2") - /* - Round2 (vs3, C) // C C C C // C nil nil nil) + Round 1: + No proposal new proposal is created. + Send a prevote for B, the block from round 0, from each of the validators to cs1. + Send a precommit for nil from all of the validators to cs1. + cs1 saw a POL for the block it saw in round 0. We ensure that it does not + lock on this block, since it did not see a proposal for it in this round. */ - - if err := cs1.SetProposalAndBlock(ctx, prop, propBlock, thirdPropBlockParts, "some peer"); err != nil { - t.Fatal(err) - } + t.Log("### Starting Round 1") + round++ + ensureNewRound(t, newRoundCh, height, round) ensurePrevote(t, voteCh, height, round) - // we are no longer locked to the first block so we should be able to prevote - validatePrevote(ctx, t, cs1, round, vss[0], thirdPropBlockHash) + validatePrevote(ctx, t, cs1, round, vss[0], nil) // All validators prevote for the old block. - signAddVotes(ctx, t, config, cs1, tmproto.PrevoteType, thirdPropBlockHash, thirdPropBlockParts.Header(), vs2, vs3, vs4) + // All validators prevote for the old block. + signAddVotes(ctx, t, config, cs1, tmproto.PrevoteType, firstBlockHash, firstBlockParts, vs2, vs3, vs4) + // Make sure that cs1 did not lock on the block since it did not receive a proposal for it. ensurePrecommit(t, voteCh, height, round) - // we have a majority, now vs1 can change lock to the third block - validatePrecommit(ctx, t, cs1, round, round, vss[0], thirdPropBlockHash, thirdPropBlockHash) + validatePrecommit(ctx, t, cs1, round, -1, vss[0], nil, nil) } // 4 vals // a polka at round 1 but we miss it // then a polka at round 2 that we lock on // then we see the polka from round 1 but shouldn't unlock -func TestStateLockPOLSafety1(t *testing.T) { +func TestStateLock_POLSafety1(t *testing.T) { config := configSetup(t) ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -1139,7 +1323,7 @@ func TestStateLockPOLSafety1(t *testing.T) { // What we want: // dont see P0, lock on P1 at R1, dont unlock using P0 at R2 -func TestStateLockPOLSafety2(t *testing.T) { +func TestStateLock_POLSafety2(t *testing.T) { config := configSetup(t) ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -1153,7 +1337,6 @@ func TestStateLockPOLSafety2(t *testing.T) { proposalCh := subscribe(ctx, t, cs1.eventBus, types.EventQueryCompleteProposal) timeoutWaitCh := subscribe(ctx, t, cs1.eventBus, types.EventQueryTimeoutWait) newRoundCh := subscribe(ctx, t, cs1.eventBus, types.EventQueryNewRound) - unlockCh := subscribe(ctx, t, cs1.eventBus, types.EventQueryUnlock) pv1, err := cs1.privValidator.GetPubKey(ctx) require.NoError(t, err) addr := pv1.Address() @@ -1231,23 +1414,22 @@ func TestStateLockPOLSafety2(t *testing.T) { */ ensureNewProposal(t, proposalCh, height, round) - ensureNoNewUnlock(t, unlockCh) ensurePrevote(t, voteCh, height, round) validatePrevote(ctx, t, cs1, round, vss[0], propBlockHash1) } // 4 vals. -// polka P0 at R0 for B0. We lock B0 on P0 at R0. P0 unlocks value at R1. +// polka P0 at R0 for B0. We lock B0 on P0 at R0. // What we want: // P0 proposes B0 at R3. func TestProposeValidBlock(t *testing.T) { - config := configSetup(t) + cfg := configSetup(t) ctx, cancel := context.WithCancel(context.Background()) defer cancel() - cs1, vss := randState(ctx, t, config, log.TestingLogger(), 4) + cs1, vss := randState(ctx, t, cfg, log.TestingLogger(), 4) vs2, vs3, vs4 := vss[1], vss[2], vss[3] height, round := cs1.Height, cs1.Round @@ -1257,7 +1439,6 @@ func TestProposeValidBlock(t *testing.T) { timeoutWaitCh := subscribe(ctx, t, cs1.eventBus, types.EventQueryTimeoutWait) timeoutProposeCh := subscribe(ctx, t, cs1.eventBus, types.EventQueryTimeoutPropose) newRoundCh := subscribe(ctx, t, cs1.eventBus, types.EventQueryNewRound) - unlockCh := subscribe(ctx, t, cs1.eventBus, types.EventQueryUnlock) pv1, err := cs1.privValidator.GetPubKey(ctx) require.NoError(t, err) addr := pv1.Address() @@ -1278,15 +1459,16 @@ func TestProposeValidBlock(t *testing.T) { // the others sign a polka bps, err := propBlock.MakePartSet(partSize) require.NoError(t, err) - signAddVotes(ctx, t, config, cs1, tmproto.PrevoteType, + signAddVotes(ctx, t, cfg, cs1, tmproto.PrevoteType, propBlockHash, bps.Header(), vs2, vs3, vs4) ensurePrecommit(t, voteCh, height, round) - // we should have precommitted + // we should have precommitted the proposed block in this round. + validatePrecommit(ctx, t, cs1, round, round, vss[0], propBlockHash, propBlockHash) - signAddVotes(ctx, t, config, cs1, tmproto.PrecommitType, nil, types.PartSetHeader{}, vs2, vs3, vs4) + signAddVotes(ctx, t, cfg, cs1, tmproto.PrecommitType, nil, types.PartSetHeader{}, vs2, vs3, vs4) ensureNewTimeout(t, timeoutWaitCh, height, round, cs1.config.Precommit(round).Nanoseconds()) @@ -1294,8 +1476,7 @@ func TestProposeValidBlock(t *testing.T) { round++ // moving to the next round ensureNewRound(t, newRoundCh, height, round) - - t.Log("### ONTO ROUND 2") + t.Log("### ONTO ROUND 1") // timeout of propose ensureNewTimeout(t, timeoutProposeCh, height, round, cs1.config.Propose(round).Nanoseconds()) @@ -1303,20 +1484,19 @@ func TestProposeValidBlock(t *testing.T) { ensurePrevote(t, voteCh, height, round) validatePrevote(ctx, t, cs1, round, vss[0], propBlockHash) - signAddVotes(ctx, t, config, cs1, tmproto.PrevoteType, nil, types.PartSetHeader{}, vs2, vs3, vs4) - - ensureNewUnlock(t, unlockCh, height, round) + signAddVotes(ctx, t, cfg, cs1, tmproto.PrevoteType, nil, types.PartSetHeader{}, vs2, vs3, vs4) ensurePrecommit(t, voteCh, height, round) - // we should have precommitted - validatePrecommit(ctx, t, cs1, round, -1, vss[0], nil, nil) + // we should have precommitted nil during this round because we received + // >2/3 precommits for nil from the other validators. + validatePrecommit(ctx, t, cs1, round, 0, vss[0], nil, propBlockHash) incrementRound(vs2, vs3, vs4) incrementRound(vs2, vs3, vs4) - signAddVotes(ctx, t, config, cs1, tmproto.PrecommitType, nil, types.PartSetHeader{}, vs2, vs3, vs4) + signAddVotes(ctx, t, cfg, cs1, tmproto.PrecommitType, nil, types.PartSetHeader{}, vs2, vs3, vs4) - round += 2 // moving to the next round + round += 2 // increment by multiple rounds ensureNewRound(t, newRoundCh, height, round) t.Log("### ONTO ROUND 3") @@ -1327,8 +1507,6 @@ func TestProposeValidBlock(t *testing.T) { ensureNewRound(t, newRoundCh, height, round) - t.Log("### ONTO ROUND 4") - ensureNewProposal(t, proposalCh, height, round) rs = cs1.GetRoundState() @@ -1341,11 +1519,11 @@ func TestProposeValidBlock(t *testing.T) { // What we want: // P0 miss to lock B but set valid block to B after receiving delayed prevote. func TestSetValidBlockOnDelayedPrevote(t *testing.T) { - config := configSetup(t) + cfg := configSetup(t) ctx, cancel := context.WithCancel(context.Background()) defer cancel() - cs1, vss := randState(ctx, t, config, log.TestingLogger(), 4) + cs1, vss := randState(ctx, t, cfg, log.TestingLogger(), 4) vs2, vs3, vs4 := vss[1], vss[2], vss[3] height, round := cs1.Height, cs1.Round @@ -1375,10 +1553,10 @@ func TestSetValidBlockOnDelayedPrevote(t *testing.T) { validatePrevote(ctx, t, cs1, round, vss[0], propBlockHash) // vs2 send prevote for propBlock - signAddVotes(ctx, t, config, cs1, tmproto.PrevoteType, propBlockHash, propBlockParts.Header(), vs2) + signAddVotes(ctx, t, cfg, cs1, tmproto.PrevoteType, propBlockHash, propBlockParts.Header(), vs2) // vs3 send prevote nil - signAddVotes(ctx, t, config, cs1, tmproto.PrevoteType, nil, types.PartSetHeader{}, vs3) + signAddVotes(ctx, t, cfg, cs1, tmproto.PrevoteType, nil, types.PartSetHeader{}, vs3) ensureNewTimeout(t, timeoutWaitCh, height, round, cs1.config.Prevote(round).Nanoseconds()) @@ -1393,7 +1571,7 @@ func TestSetValidBlockOnDelayedPrevote(t *testing.T) { assert.True(t, rs.ValidRound == -1) // vs2 send (delayed) prevote for propBlock - signAddVotes(ctx, t, config, cs1, tmproto.PrevoteType, propBlockHash, propBlockParts.Header(), vs4) + signAddVotes(ctx, t, cfg, cs1, tmproto.PrevoteType, propBlockHash, propBlockParts.Header(), vs4) ensureNewValidBlock(t, validBlockCh, height, round) @@ -1835,7 +2013,7 @@ func TestResetTimeoutPrecommitUponNewHeight(t *testing.T) { // TODO: Slashing /* -func TestStateSlashingPrevotes(t *testing.T) { +func TestStateSlashing_Prevotes(t *testing.T) { cs1, vss := randState(2) vs2 := vss[1] @@ -1870,7 +2048,7 @@ func TestStateSlashingPrevotes(t *testing.T) { // XXX: Check for existence of Dupeout info } -func TestStateSlashingPrecommits(t *testing.T) { +func TestStateSlashing_Precommits(t *testing.T) { cs1, vss := randState(2) vs2 := vss[1] diff --git a/internal/eventbus/event_bus.go b/internal/eventbus/event_bus.go index 58f357165..327330ba7 100644 --- a/internal/eventbus/event_bus.go +++ b/internal/eventbus/event_bus.go @@ -185,10 +185,6 @@ func (b *EventBus) PublishEventPolka(ctx context.Context, data types.EventDataRo return b.Publish(ctx, types.EventPolkaValue, data) } -func (b *EventBus) PublishEventUnlock(ctx context.Context, data types.EventDataRoundState) error { - return b.Publish(ctx, types.EventUnlockValue, data) -} - func (b *EventBus) PublishEventRelock(ctx context.Context, data types.EventDataRoundState) error { return b.Publish(ctx, types.EventRelockValue, data) } diff --git a/internal/eventbus/event_bus_test.go b/internal/eventbus/event_bus_test.go index 6569d54fd..ffe14dd84 100644 --- a/internal/eventbus/event_bus_test.go +++ b/internal/eventbus/event_bus_test.go @@ -385,7 +385,6 @@ func TestEventBusPublish(t *testing.T) { require.NoError(t, eventBus.PublishEventNewRound(ctx, types.EventDataNewRound{})) require.NoError(t, eventBus.PublishEventCompleteProposal(ctx, types.EventDataCompleteProposal{})) require.NoError(t, eventBus.PublishEventPolka(ctx, types.EventDataRoundState{})) - require.NoError(t, eventBus.PublishEventUnlock(ctx, types.EventDataRoundState{})) require.NoError(t, eventBus.PublishEventRelock(ctx, types.EventDataRoundState{})) require.NoError(t, eventBus.PublishEventLock(ctx, types.EventDataRoundState{})) require.NoError(t, eventBus.PublishEventValidatorSetUpdates(ctx, types.EventDataValidatorSetUpdates{})) @@ -487,7 +486,6 @@ var events = []string{ types.EventTimeoutProposeValue, types.EventCompleteProposalValue, types.EventPolkaValue, - types.EventUnlockValue, types.EventLockValue, types.EventRelockValue, types.EventTimeoutWaitValue, @@ -508,7 +506,6 @@ var queries = []tmpubsub.Query{ types.EventQueryTimeoutPropose, types.EventQueryCompleteProposal, types.EventQueryPolka, - types.EventQueryUnlock, types.EventQueryLock, types.EventQueryRelock, types.EventQueryTimeoutWait, diff --git a/internal/test/factory/block.go b/internal/test/factory/block.go index 654572ddf..3fd34cdc5 100644 --- a/internal/test/factory/block.go +++ b/internal/test/factory/block.go @@ -51,7 +51,7 @@ func MakeHeader(t *testing.T, h *types.Header) *types.Header { if h.Height == 0 { h.Height = 1 } - if h.LastBlockID.IsZero() { + if h.LastBlockID.IsNil() { h.LastBlockID = MakeBlockID() } if h.ChainID == "" { diff --git a/types/block.go b/types/block.go index e09d1830a..475f88953 100644 --- a/types/block.go +++ b/types/block.go @@ -883,7 +883,7 @@ func (commit *Commit) ValidateBasic() error { } if commit.Height >= 1 { - if commit.BlockID.IsZero() { + if commit.BlockID.IsNil() { return errors.New("commit cannot be for nil block") } @@ -1204,8 +1204,8 @@ func (blockID BlockID) ValidateBasic() error { return nil } -// IsZero returns true if this is the BlockID of a nil block. -func (blockID BlockID) IsZero() bool { +// IsNil returns true if this is the BlockID of a nil block. +func (blockID BlockID) IsNil() bool { return len(blockID.Hash) == 0 && blockID.PartSetHeader.IsZero() } diff --git a/types/canonical.go b/types/canonical.go index 01c3d851d..fc07ab477 100644 --- a/types/canonical.go +++ b/types/canonical.go @@ -21,7 +21,7 @@ func CanonicalizeBlockID(bid tmproto.BlockID) *tmproto.CanonicalBlockID { panic(err) } var cbid *tmproto.CanonicalBlockID - if rbid == nil || rbid.IsZero() { + if rbid == nil || rbid.IsNil() { cbid = nil } else { cbid = &tmproto.CanonicalBlockID{ diff --git a/types/events.go b/types/events.go index 00bed4b60..02e81ac71 100644 --- a/types/events.go +++ b/types/events.go @@ -39,7 +39,6 @@ const ( EventStateSyncStatusValue = "StateSyncStatus" EventTimeoutProposeValue = "TimeoutPropose" EventTimeoutWaitValue = "TimeoutWait" - EventUnlockValue = "Unlock" EventValidBlockValue = "ValidBlock" EventVoteValue = "Vote" ) @@ -224,7 +223,6 @@ var ( EventQueryTimeoutPropose = QueryForEvent(EventTimeoutProposeValue) EventQueryTimeoutWait = QueryForEvent(EventTimeoutWaitValue) EventQueryTx = QueryForEvent(EventTxValue) - EventQueryUnlock = QueryForEvent(EventUnlockValue) EventQueryValidatorSetUpdates = QueryForEvent(EventValidatorSetUpdatesValue) EventQueryValidBlock = QueryForEvent(EventValidBlockValue) EventQueryVote = QueryForEvent(EventVoteValue) diff --git a/types/vote.go b/types/vote.go index 52fb73ec5..834af826b 100644 --- a/types/vote.go +++ b/types/vote.go @@ -68,7 +68,7 @@ func (vote *Vote) CommitSig() CommitSig { switch { case vote.BlockID.IsComplete(): blockIDFlag = BlockIDFlagCommit - case vote.BlockID.IsZero(): + case vote.BlockID.IsNil(): blockIDFlag = BlockIDFlagNil default: panic(fmt.Sprintf("Invalid vote %v - expected BlockID to be either empty or complete", vote)) @@ -177,7 +177,7 @@ func (vote *Vote) ValidateBasic() error { // BlockID.ValidateBasic would not err if we for instance have an empty hash but a // non-empty PartsSetHeader: - if !vote.BlockID.IsZero() && !vote.BlockID.IsComplete() { + if !vote.BlockID.IsNil() && !vote.BlockID.IsComplete() { return fmt.Errorf("blockID must be either empty or complete, got: %v", vote.BlockID) } diff --git a/types/vote_set_test.go b/types/vote_set_test.go index a3ef9e802..8baa74172 100644 --- a/types/vote_set_test.go +++ b/types/vote_set_test.go @@ -31,7 +31,7 @@ func TestVoteSet_AddVote_Good(t *testing.T) { assert.Nil(t, voteSet.GetByAddress(val0Addr)) assert.False(t, voteSet.BitArray().GetIndex(0)) blockID, ok := voteSet.TwoThirdsMajority() - assert.False(t, ok || !blockID.IsZero(), "there should be no 2/3 majority") + assert.False(t, ok || !blockID.IsNil(), "there should be no 2/3 majority") vote := &Vote{ ValidatorAddress: val0Addr, @@ -48,7 +48,7 @@ func TestVoteSet_AddVote_Good(t *testing.T) { assert.NotNil(t, voteSet.GetByAddress(val0Addr)) assert.True(t, voteSet.BitArray().GetIndex(0)) blockID, ok = voteSet.TwoThirdsMajority() - assert.False(t, ok || !blockID.IsZero(), "there should be no 2/3 majority") + assert.False(t, ok || !blockID.IsNil(), "there should be no 2/3 majority") } func TestVoteSet_AddVote_Bad(t *testing.T) { @@ -155,7 +155,7 @@ func TestVoteSet_2_3Majority(t *testing.T) { require.NoError(t, err) } blockID, ok := voteSet.TwoThirdsMajority() - assert.False(t, ok || !blockID.IsZero(), "there should be no 2/3 majority") + assert.False(t, ok || !blockID.IsNil(), "there should be no 2/3 majority") // 7th validator voted for some blockhash { @@ -166,7 +166,7 @@ func TestVoteSet_2_3Majority(t *testing.T) { _, err = signAddVote(ctx, privValidators[6], withBlockHash(vote, tmrand.Bytes(32)), voteSet) require.NoError(t, err) blockID, ok = voteSet.TwoThirdsMajority() - assert.False(t, ok || !blockID.IsZero(), "there should be no 2/3 majority") + assert.False(t, ok || !blockID.IsNil(), "there should be no 2/3 majority") } // 8th validator voted for nil. @@ -178,7 +178,7 @@ func TestVoteSet_2_3Majority(t *testing.T) { _, err = signAddVote(ctx, privValidators[7], vote, voteSet) require.NoError(t, err) blockID, ok = voteSet.TwoThirdsMajority() - assert.True(t, ok || blockID.IsZero(), "there should be 2/3 majority for nil") + assert.True(t, ok || blockID.IsNil(), "there should be 2/3 majority for nil") } } @@ -213,7 +213,7 @@ func TestVoteSet_2_3MajorityRedux(t *testing.T) { require.NoError(t, err) } blockID, ok := voteSet.TwoThirdsMajority() - assert.False(t, ok || !blockID.IsZero(), + assert.False(t, ok || !blockID.IsNil(), "there should be no 2/3 majority") // 67th validator voted for nil @@ -225,7 +225,7 @@ func TestVoteSet_2_3MajorityRedux(t *testing.T) { _, err = signAddVote(ctx, privValidators[66], withBlockHash(vote, nil), voteSet) require.NoError(t, err) blockID, ok = voteSet.TwoThirdsMajority() - assert.False(t, ok || !blockID.IsZero(), + assert.False(t, ok || !blockID.IsNil(), "there should be no 2/3 majority: last vote added was nil") } @@ -239,7 +239,7 @@ func TestVoteSet_2_3MajorityRedux(t *testing.T) { _, err = signAddVote(ctx, privValidators[67], withBlockPartSetHeader(vote, blockPartsHeader), voteSet) require.NoError(t, err) blockID, ok = voteSet.TwoThirdsMajority() - assert.False(t, ok || !blockID.IsZero(), + assert.False(t, ok || !blockID.IsNil(), "there should be no 2/3 majority: last vote added had different PartSetHeader Hash") } @@ -253,7 +253,7 @@ func TestVoteSet_2_3MajorityRedux(t *testing.T) { _, err = signAddVote(ctx, privValidators[68], withBlockPartSetHeader(vote, blockPartsHeader), voteSet) require.NoError(t, err) blockID, ok = voteSet.TwoThirdsMajority() - assert.False(t, ok || !blockID.IsZero(), + assert.False(t, ok || !blockID.IsNil(), "there should be no 2/3 majority: last vote added had different PartSetHeader Total") } @@ -266,7 +266,7 @@ func TestVoteSet_2_3MajorityRedux(t *testing.T) { _, err = signAddVote(ctx, privValidators[69], withBlockHash(vote, tmrand.Bytes(32)), voteSet) require.NoError(t, err) blockID, ok = voteSet.TwoThirdsMajority() - assert.False(t, ok || !blockID.IsZero(), + assert.False(t, ok || !blockID.IsNil(), "there should be no 2/3 majority: last vote added had different BlockHash") }