From 3359e0bf2f8414d9687f9eecda67b899d64a9cd1 Mon Sep 17 00:00:00 2001 From: Callum Waters Date: Mon, 7 Sep 2020 07:54:13 +0200 Subject: [PATCH] resurrect consensus tests (#5334) --- consensus/state_test.go | 217 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 216 insertions(+), 1 deletion(-) diff --git a/consensus/state_test.go b/consensus/state_test.go index a2bfb9c16..5b0c581c2 100644 --- a/consensus/state_test.go +++ b/consensus/state_test.go @@ -526,7 +526,7 @@ func TestStateLockNoPOL(t *testing.T) { // 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 TestStateLockPOLRelockThenChangeLock(t *testing.T) { +func TestStateLockPOLRelock(t *testing.T) { cs1, vss := randState(4) vs2, vs3, vs4 := vss[1], vss[2], vss[3] height, round := cs1.Height, cs1.Round @@ -622,6 +622,221 @@ func TestStateLockPOLRelockThenChangeLock(t *testing.T) { ensureNewRound(newRoundCh, height+1, 0) } +// 4 vals, one precommits, other 3 polka at next round, so we unlock and precomit the polka +func TestStateLockPOLUnlock(t *testing.T) { + cs1, vss := randState(4) + vs2, vs3, vs4 := vss[1], vss[2], vss[3] + height, round := cs1.Height, cs1.Round + + partSize := types.BlockPartSizeBytes + + proposalCh := subscribe(cs1.eventBus, types.EventQueryCompleteProposal) + timeoutWaitCh := subscribe(cs1.eventBus, types.EventQueryTimeoutWait) + newRoundCh := subscribe(cs1.eventBus, types.EventQueryNewRound) + unlockCh := subscribe(cs1.eventBus, types.EventQueryUnlock) + pv1, err := cs1.privValidator.GetPubKey() + require.NoError(t, err) + addr := pv1.Address() + voteCh := subscribeToVoter(cs1, addr) + + // everything done from perspective of cs1 + + /* + Round1 (cs1, B) // B B B B // B nil B nil + eg. didn't see the 2/3 prevotes + */ + + // start round and wait for propose and prevote + startTestRound(cs1, height, round) + ensureNewRound(newRoundCh, height, round) + + ensureNewProposal(proposalCh, height, round) + rs := cs1.GetRoundState() + theBlockHash := rs.ProposalBlock.Hash() + theBlockParts := rs.ProposalBlockParts.Header() + + ensurePrevote(voteCh, height, round) + validatePrevote(t, cs1, round, vss[0], theBlockHash) + + signAddVotes(cs1, tmproto.PrevoteType, theBlockHash, theBlockParts, vs2, vs3, vs4) + + ensurePrecommit(voteCh, height, round) + // the proposed block should now be locked and our precommit added + validatePrecommit(t, cs1, round, round, vss[0], theBlockHash, theBlockHash) + + // add precommits from the rest + signAddVotes(cs1, tmproto.PrecommitType, nil, types.PartSetHeader{}, vs2, vs4) + signAddVotes(cs1, tmproto.PrecommitType, theBlockHash, theBlockParts, vs3) + + // before we time out into new round, set next proposal block + prop, propBlock := decideProposal(cs1, vs2, vs2.Height, vs2.Round+1) + propBlockParts := propBlock.MakePartSet(partSize) + + // timeout to new round + ensureNewTimeout(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(newRoundCh, height, round) + t.Log("#### ONTO ROUND 1") + /* + Round2 (vs2, C) // B nil nil nil // nil nil nil _ + cs1 unlocks! + */ + //XXX: this isnt guaranteed to get there before the timeoutPropose ... + if err := cs1.SetProposalAndBlock(prop, propBlock, propBlockParts, "some peer"); err != nil { + t.Fatal(err) + } + + ensureNewProposal(proposalCh, height, round) + + // go to prevote, prevote for locked block (not proposal) + ensurePrevote(voteCh, height, round) + validatePrevote(t, cs1, round, vss[0], lockedBlockHash) + // now lets add prevotes from everyone else for nil (a polka!) + signAddVotes(cs1, tmproto.PrevoteType, nil, types.PartSetHeader{}, vs2, vs3, vs4) + + // the polka makes us unlock and precommit nil + ensureNewUnlock(unlockCh, height, round) + ensurePrecommit(voteCh, height, round) + + // we should have unlocked and committed nil + // NOTE: since we don't relock on nil, the lock round is -1 + validatePrecommit(t, cs1, round, -1, vss[0], nil, nil) + + signAddVotes(cs1, tmproto.PrecommitType, nil, types.PartSetHeader{}, vs2, vs3) + ensureNewRound(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) { + cs1, vss := randState(4) + vs2, vs3, vs4 := vss[1], vss[2], vss[3] + height, round := cs1.Height, cs1.Round + + partSize := types.BlockPartSizeBytes + + timeoutWaitCh := subscribe(cs1.eventBus, types.EventQueryTimeoutWait) + proposalCh := subscribe(cs1.eventBus, types.EventQueryCompleteProposal) + pv1, err := cs1.privValidator.GetPubKey() + require.NoError(t, err) + addr := pv1.Address() + voteCh := subscribeToVoter(cs1, addr) + newRoundCh := subscribe(cs1.eventBus, types.EventQueryNewRound) + // everything done from perspective of cs1 + + /* + Round0 (cs1, A) // A A A A// A nil nil nil + */ + + // start round and wait for propose and prevote + startTestRound(cs1, height, round) + + ensureNewRound(newRoundCh, height, round) + ensureNewProposal(proposalCh, height, round) + rs := cs1.GetRoundState() + firstBlockHash := rs.ProposalBlock.Hash() + firstBlockParts := rs.ProposalBlockParts.Header() + + ensurePrevote(voteCh, height, round) // prevote + + signAddVotes(cs1, tmproto.PrevoteType, firstBlockHash, firstBlockParts, vs2, vs3, vs4) + + ensurePrecommit(voteCh, height, round) // our precommit + // the proposed block should now be locked and our precommit added + validatePrecommit(t, cs1, round, round, vss[0], firstBlockHash, firstBlockHash) + + // add precommits from the rest + signAddVotes(cs1, tmproto.PrecommitType, nil, types.PartSetHeader{}, vs2, vs3, vs4) + + // before we timeout to the new round set the new proposal + cs2 := newState(cs1.state, vs2, counter.NewApplication(true)) + prop, propBlock := decideProposal(cs2, vs2, vs2.Height, vs2.Round+1) + if prop == nil || propBlock == nil { + t.Fatal("Failed to create proposal block with vs2") + } + secondBlockParts := propBlock.MakePartSet(partSize) + secondBlockHash := propBlock.Hash() + require.NotEqual(t, secondBlockHash, firstBlockHash) + + incrementRound(vs2, vs3, vs4) + + // timeout to new round + ensureNewTimeout(timeoutWaitCh, height, round, cs1.config.Precommit(round).Nanoseconds()) + + round++ // moving to the next round + + ensureNewRound(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(voteCh, height, round) + validatePrevote(t, cs1, round, vss[0], firstBlockHash) + + // now lets add prevotes from everyone else for the new block + signAddVotes(cs1, tmproto.PrevoteType, secondBlockHash, secondBlockParts.Header(), vs2, vs3, vs4) + + ensurePrecommit(voteCh, height, round) + // we should have unlocked and locked on the new block, sending a precommit for this new block + validatePrecommit(t, cs1, round, -1, vss[0], nil, nil) + + if err := cs1.SetProposalAndBlock(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(cs1, tmproto.PrecommitType, nil, types.PartSetHeader{}, vs2, vs3, vs4) + + // before we timeout to the new round set the new proposal + cs3 := newState(cs1.state, vs3, counter.NewApplication(true)) + prop, propBlock = decideProposal(cs3, vs3, vs3.Height, vs3.Round+1) + if prop == nil || propBlock == nil { + t.Fatal("Failed to create proposal block with vs2") + } + thirdPropBlockParts := propBlock.MakePartSet(partSize) + thirdPropBlockHash := propBlock.Hash() + require.NotEqual(t, secondBlockHash, thirdPropBlockHash) + + incrementRound(vs2, vs3, vs4) + + // timeout to new round + ensureNewTimeout(timeoutWaitCh, height, round, cs1.config.Precommit(round).Nanoseconds()) + + round++ // moving to the next round + ensureNewRound(newRoundCh, height, round) + t.Log("### ONTO ROUND 2") + + /* + Round2 (vs3, C) // C C C C // C nil nil nil) + */ + + if err := cs1.SetProposalAndBlock(prop, propBlock, thirdPropBlockParts, "some peer"); err != nil { + t.Fatal(err) + } + + ensurePrevote(voteCh, height, round) + // we are no longer locked to the first block so we should be able to prevote + validatePrevote(t, cs1, round, vss[0], thirdPropBlockHash) + + signAddVotes(cs1, tmproto.PrevoteType, thirdPropBlockHash, thirdPropBlockParts.Header(), vs2, vs3, vs4) + + ensurePrecommit(voteCh, height, round) + // we have a majority, now vs1 can change lock to the third block + validatePrecommit(t, cs1, round, round, vss[0], thirdPropBlockHash, thirdPropBlockHash) +} + // 4 vals // a polka at round 1 but we miss it // then a polka at round 2 that we lock on