|
|
|
|
@@ -46,6 +46,7 @@ x * TestStateLock_MissingProposalWhenPOLSeenDoesNotUpdateLock - 4 vals, 1 misses
|
|
|
|
|
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
|
|
|
|
|
x_*_TestState_PrevotePOLFromPreviousRound 4 vals, prevote a proposal if a POL was seen for it in a previous round.
|
|
|
|
|
* TestNetworkLock - once +1/3 precommits, network should be locked
|
|
|
|
|
* TestNetworkLockPOL - once +1/3 precommits, the block with more recent polka is committed
|
|
|
|
|
SlashingSuite
|
|
|
|
|
@@ -459,8 +460,8 @@ func TestStateLock_NoPOL(t *testing.T) {
|
|
|
|
|
signAddVotes(config, cs1, tmproto.PrevoteType, theBlockHash, thePartSetHeader, vs2)
|
|
|
|
|
ensurePrevote(t, voteCh, height, round) // prevote
|
|
|
|
|
|
|
|
|
|
ensurePrecommit(t, voteCh, height, round) // precommit
|
|
|
|
|
// the proposed block should now be locked and our precommit added
|
|
|
|
|
ensurePrecommit(t, voteCh, height, round)
|
|
|
|
|
validatePrecommit(t, cs1, round, round, vss[0], theBlockHash, theBlockHash)
|
|
|
|
|
|
|
|
|
|
// we should now be stuck in limbo forever, waiting for more precommits
|
|
|
|
|
@@ -492,13 +493,12 @@ func TestStateLock_NoPOL(t *testing.T) {
|
|
|
|
|
rs := cs1.GetRoundState()
|
|
|
|
|
|
|
|
|
|
if rs.ProposalBlock != nil {
|
|
|
|
|
panic("Expected proposal block to be nil")
|
|
|
|
|
t.Fatal("Expected proposal block to be nil")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// wait to finish prevote
|
|
|
|
|
// we should have prevoted nil since we did not see a proposal in the round.
|
|
|
|
|
ensurePrevote(t, voteCh, height, round)
|
|
|
|
|
// we should have prevoted our locked block
|
|
|
|
|
validatePrevote(t, cs1, round, vss[0], rs.LockedBlock.Hash())
|
|
|
|
|
validatePrevote(t, cs1, round, vss[0], nil)
|
|
|
|
|
|
|
|
|
|
// add a conflicting prevote from the other validator
|
|
|
|
|
signAddVotes(config, cs1, tmproto.PrevoteType, hash, rs.LockedBlock.MakePartSet(partSize).Header(), vs2)
|
|
|
|
|
@@ -508,9 +508,9 @@ func TestStateLock_NoPOL(t *testing.T) {
|
|
|
|
|
// and then prevote wait, which should timeout. then wait for precommit
|
|
|
|
|
ensureNewTimeout(t, timeoutWaitCh, height, round, cs1.config.Prevote(round).Nanoseconds())
|
|
|
|
|
|
|
|
|
|
ensurePrecommit(t, voteCh, height, round) // precommit
|
|
|
|
|
// the proposed block should still be locked and our precommit added
|
|
|
|
|
// we should precommit nil and be locked on the proposal
|
|
|
|
|
// the proposed block should still be locked block.
|
|
|
|
|
// we should precommit nil and be locked on the proposal.
|
|
|
|
|
ensurePrecommit(t, voteCh, height, round)
|
|
|
|
|
validatePrecommit(t, cs1, round, 0, vss[0], nil, theBlockHash)
|
|
|
|
|
|
|
|
|
|
// add conflicting precommit from vs2
|
|
|
|
|
@@ -586,9 +586,10 @@ func TestStateLock_NoPOL(t *testing.T) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ensureNewProposal(t, proposalCh, height, round)
|
|
|
|
|
ensurePrevote(t, voteCh, height, round) // prevote
|
|
|
|
|
// prevote for locked block (not proposal)
|
|
|
|
|
validatePrevote(t, cs1, 3, vss[0], cs1.LockedBlock.Hash())
|
|
|
|
|
|
|
|
|
|
// prevote for nil since we did not see a proposal for our locked block in the round.
|
|
|
|
|
ensurePrevote(t, voteCh, height, round)
|
|
|
|
|
validatePrevote(t, cs1, 3, vss[0], nil)
|
|
|
|
|
|
|
|
|
|
// prevote for proposed block
|
|
|
|
|
signAddVotes(config, cs1, tmproto.PrevoteType, propBlock.Hash(), propBlock.MakePartSet(partSize).Header(), vs2)
|
|
|
|
|
@@ -693,11 +694,9 @@ func TestStateLock_POLUpdateLock(t *testing.T) {
|
|
|
|
|
// 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.
|
|
|
|
|
// Prevote our nil since the proposal does not match our locked block.
|
|
|
|
|
ensurePrevote(t, voteCh, height, round)
|
|
|
|
|
validatePrevote(t, cs1, round, vss[0], theBlockHash)
|
|
|
|
|
validatePrevote(t, cs1, round, vss[0], nil)
|
|
|
|
|
|
|
|
|
|
// Add prevotes from the remainder of the validators for the new locked block.
|
|
|
|
|
signAddVotes(config, cs1, tmproto.PrevoteType, propBlockR1Hash, propBlockR1Parts.Header(), vs2, vs3, vs4)
|
|
|
|
|
@@ -795,9 +794,7 @@ func TestStateLock_POLRelock(t *testing.T) {
|
|
|
|
|
// 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.
|
|
|
|
|
// Prevote our locked block since it matches the propsal seen in this round.
|
|
|
|
|
ensurePrevote(t, voteCh, height, round)
|
|
|
|
|
validatePrevote(t, cs1, round, vss[0], theBlockHash)
|
|
|
|
|
|
|
|
|
|
@@ -813,6 +810,178 @@ func TestStateLock_POLRelock(t *testing.T) {
|
|
|
|
|
validatePrecommit(t, cs1, round, round, vss[0], theBlockHash, theBlockHash)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TestStateLock_PrevoteNilWhenLockedAndMissProposal tests that a validator prevotes nil
|
|
|
|
|
// if it is locked on a block and misses the proposal in a round.
|
|
|
|
|
func TestStateLock_PrevoteNilWhenLockedAndMissProposal(t *testing.T) {
|
|
|
|
|
config := configSetup(t)
|
|
|
|
|
|
|
|
|
|
cs1, vss := randState(config, 4)
|
|
|
|
|
vs2, vs3, vs4 := vss[1], vss[2], vss[3]
|
|
|
|
|
height, round := cs1.Height, cs1.Round
|
|
|
|
|
|
|
|
|
|
timeoutWaitCh := subscribe(cs1.eventBus, types.EventQueryTimeoutWait)
|
|
|
|
|
proposalCh := subscribe(cs1.eventBus, types.EventQueryCompleteProposal)
|
|
|
|
|
pv1, err := cs1.privValidator.GetPubKey(context.Background())
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
addr := pv1.Address()
|
|
|
|
|
voteCh := subscribeToVoter(cs1, addr)
|
|
|
|
|
lockCh := subscribe(cs1.eventBus, types.EventQueryLock)
|
|
|
|
|
newRoundCh := subscribe(cs1.eventBus, types.EventQueryNewRound)
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
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(cs1, height, round)
|
|
|
|
|
|
|
|
|
|
ensureNewRound(t, newRoundCh, height, round)
|
|
|
|
|
ensureNewProposal(t, proposalCh, height, round)
|
|
|
|
|
rs := cs1.GetRoundState()
|
|
|
|
|
theBlockHash := rs.ProposalBlock.Hash()
|
|
|
|
|
theBlockParts := rs.ProposalBlockParts
|
|
|
|
|
|
|
|
|
|
ensurePrevote(t, voteCh, height, round)
|
|
|
|
|
|
|
|
|
|
signAddVotes(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(t, cs1, round, round, vss[0], theBlockHash, theBlockHash)
|
|
|
|
|
|
|
|
|
|
// add precommits from the rest of the validators.
|
|
|
|
|
signAddVotes(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:
|
|
|
|
|
Send a prevote for nil from each of the validators to cs1.
|
|
|
|
|
Send a precommit for nil from all of the validtors to cs1.
|
|
|
|
|
|
|
|
|
|
Check that cs1 prevotes nil instead of its locked block, but ensure
|
|
|
|
|
that it maintains its locked block.
|
|
|
|
|
*/
|
|
|
|
|
t.Log("### Starting Round 1")
|
|
|
|
|
incrementRound(vs2, vs3, vs4)
|
|
|
|
|
round++
|
|
|
|
|
|
|
|
|
|
ensureNewRound(t, newRoundCh, height, round)
|
|
|
|
|
|
|
|
|
|
// Prevote our nil.
|
|
|
|
|
ensurePrevote(t, voteCh, height, round)
|
|
|
|
|
validatePrevote(t, cs1, round, vss[0], nil)
|
|
|
|
|
|
|
|
|
|
// Add prevotes from the remainder of the validators nil.
|
|
|
|
|
signAddVotes(config, cs1, tmproto.PrevoteType, nil, types.PartSetHeader{}, vs2, vs3, vs4)
|
|
|
|
|
ensurePrecommit(t, voteCh, height, round)
|
|
|
|
|
// We should now be locked on the same block but with an updated locked round.
|
|
|
|
|
validatePrecommit(t, cs1, round, 0, vss[0], nil, theBlockHash)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TestStateLock_PrevoteNilWhenLockedAndMissProposal tests that a validator prevotes nil
|
|
|
|
|
// if it is locked on a block and misses the proposal in a round.
|
|
|
|
|
func TestStateLock_PrevoteNilWhenLockedAndDifferentProposal(t *testing.T) {
|
|
|
|
|
config := configSetup(t)
|
|
|
|
|
/*
|
|
|
|
|
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(config, 4)
|
|
|
|
|
vs2, vs3, vs4 := vss[1], vss[2], vss[3]
|
|
|
|
|
height, round := cs1.Height, cs1.Round
|
|
|
|
|
|
|
|
|
|
timeoutWaitCh := subscribe(cs1.eventBus, types.EventQueryTimeoutWait)
|
|
|
|
|
proposalCh := subscribe(cs1.eventBus, types.EventQueryCompleteProposal)
|
|
|
|
|
pv1, err := cs1.privValidator.GetPubKey(context.Background())
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
addr := pv1.Address()
|
|
|
|
|
voteCh := subscribeToVoter(cs1, addr)
|
|
|
|
|
lockCh := subscribe(cs1.eventBus, types.EventQueryLock)
|
|
|
|
|
newRoundCh := subscribe(cs1.eventBus, types.EventQueryNewRound)
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
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(cs1, height, round)
|
|
|
|
|
|
|
|
|
|
ensureNewRound(t, newRoundCh, height, round)
|
|
|
|
|
ensureNewProposal(t, proposalCh, height, round)
|
|
|
|
|
rs := cs1.GetRoundState()
|
|
|
|
|
theBlockHash := rs.ProposalBlock.Hash()
|
|
|
|
|
theBlockParts := rs.ProposalBlockParts
|
|
|
|
|
|
|
|
|
|
ensurePrevote(t, voteCh, height, round)
|
|
|
|
|
|
|
|
|
|
signAddVotes(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(t, cs1, round, round, vss[0], theBlockHash, theBlockHash)
|
|
|
|
|
|
|
|
|
|
// add precommits from the rest of the validators.
|
|
|
|
|
signAddVotes(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 a new block.
|
|
|
|
|
Send a prevote for nil from each of the validators to cs1.
|
|
|
|
|
Send a precommit for nil from all of the validtors to cs1.
|
|
|
|
|
|
|
|
|
|
Check that cs1 prevotes nil instead of its locked block, but ensure
|
|
|
|
|
that it maintains its locked block.
|
|
|
|
|
*/
|
|
|
|
|
t.Log("### Starting Round 1")
|
|
|
|
|
incrementRound(vs2, vs3, vs4)
|
|
|
|
|
round++
|
|
|
|
|
cs2 := newState(cs1.state, vs2, kvstore.NewApplication())
|
|
|
|
|
propR1, propBlockR1 := decideProposal(t, cs2, vs2, vs2.Height, vs2.Round)
|
|
|
|
|
propBlockR1Parts := propBlockR1.MakePartSet(types.BlockPartSizeBytes)
|
|
|
|
|
propBlockR1Hash := propBlockR1.Hash()
|
|
|
|
|
require.NotEqual(t, propBlockR1Hash, theBlockHash)
|
|
|
|
|
if err := cs1.SetProposalAndBlock(propR1, propBlockR1, propBlockR1Parts, "some peer"); err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ensureNewRound(t, newRoundCh, height, round)
|
|
|
|
|
ensureNewProposal(t, proposalCh, height, round)
|
|
|
|
|
|
|
|
|
|
// Prevote our nil.
|
|
|
|
|
ensurePrevote(t, voteCh, height, round)
|
|
|
|
|
validatePrevote(t, cs1, round, vss[0], nil)
|
|
|
|
|
|
|
|
|
|
// Add prevotes from the remainder of the validators for nil.
|
|
|
|
|
signAddVotes(config, cs1, tmproto.PrevoteType, nil, types.PartSetHeader{}, vs2, vs3, vs4)
|
|
|
|
|
|
|
|
|
|
// We should now be locked on the same block but prevote nil.
|
|
|
|
|
ensurePrecommit(t, voteCh, height, round)
|
|
|
|
|
validatePrecommit(t, cs1, round, 0, vss[0], nil, 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
|
|
|
|
|
@@ -892,8 +1061,10 @@ func TestStateLock_POLDoesNotUnlock(t *testing.T) {
|
|
|
|
|
t.Log("#### ONTO ROUND 1")
|
|
|
|
|
round++
|
|
|
|
|
incrementRound(vs2, vs3, vs4)
|
|
|
|
|
prop, propBlock := decideProposal(t, cs1, vs2, vs2.Height, vs2.Round)
|
|
|
|
|
cs2 := newState(cs1.state, vs2, kvstore.NewApplication())
|
|
|
|
|
prop, propBlock := decideProposal(t, cs2, vs2, vs2.Height, vs2.Round)
|
|
|
|
|
propBlockParts := propBlock.MakePartSet(types.BlockPartSizeBytes)
|
|
|
|
|
require.NotEqual(t, propBlock.Hash(), theBlockHash)
|
|
|
|
|
if err := cs1.SetProposalAndBlock(prop, propBlock, propBlockParts, ""); err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
@@ -902,12 +1073,10 @@ func TestStateLock_POLDoesNotUnlock(t *testing.T) {
|
|
|
|
|
|
|
|
|
|
ensureNewProposal(t, proposalCh, height, round)
|
|
|
|
|
|
|
|
|
|
// 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)
|
|
|
|
|
// Prevote for nil since the proposed block does not match our locked block.
|
|
|
|
|
ensurePrevote(t, voteCh, height, round)
|
|
|
|
|
validatePrevote(t, cs1, round, vss[0], theBlockHash)
|
|
|
|
|
validatePrevote(t, cs1, round, vss[0], nil)
|
|
|
|
|
|
|
|
|
|
// add >2/3 prevotes for nil from all other validators
|
|
|
|
|
signAddVotes(config, cs1, tmproto.PrevoteType, nil, types.PartSetHeader{}, vs2, vs3, vs4)
|
|
|
|
|
|
|
|
|
|
@@ -928,7 +1097,8 @@ func TestStateLock_POLDoesNotUnlock(t *testing.T) {
|
|
|
|
|
t.Log("#### ONTO ROUND 2")
|
|
|
|
|
round++
|
|
|
|
|
incrementRound(vs2, vs3, vs4)
|
|
|
|
|
prop, propBlock = decideProposal(t, cs1, vs3, vs3.Height, vs3.Round)
|
|
|
|
|
cs3 := newState(cs1.state, vs2, kvstore.NewApplication())
|
|
|
|
|
prop, propBlock = decideProposal(t, cs3, vs3, vs3.Height, vs3.Round)
|
|
|
|
|
propBlockParts = propBlock.MakePartSet(types.BlockPartSizeBytes)
|
|
|
|
|
if err := cs1.SetProposalAndBlock(prop, propBlock, propBlockParts, ""); err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
@@ -938,8 +1108,9 @@ func TestStateLock_POLDoesNotUnlock(t *testing.T) {
|
|
|
|
|
|
|
|
|
|
ensureNewProposal(t, proposalCh, height, round)
|
|
|
|
|
|
|
|
|
|
// Prevote for nil since the proposal does not match our locked block.
|
|
|
|
|
ensurePrevote(t, voteCh, height, round)
|
|
|
|
|
validatePrevote(t, cs1, round, vss[0], theBlockHash)
|
|
|
|
|
validatePrevote(t, cs1, round, vss[0], nil)
|
|
|
|
|
|
|
|
|
|
signAddVotes(config, cs1, tmproto.PrevoteType, nil, types.PartSetHeader{}, vs2, vs3, vs4)
|
|
|
|
|
|
|
|
|
|
@@ -1021,9 +1192,9 @@ func TestStateLock_MissingProposalWhenPOLSeenDoesNotUpdateLock(t *testing.T) {
|
|
|
|
|
|
|
|
|
|
ensureNewRound(t, newRoundCh, height, round)
|
|
|
|
|
|
|
|
|
|
// go to prevote, node should prevote for locked block (not the new proposal) - this is relocking
|
|
|
|
|
// prevote for nil since the proposal was not seen.
|
|
|
|
|
ensurePrevote(t, voteCh, height, round)
|
|
|
|
|
validatePrevote(t, cs1, round, vss[0], firstBlockHash)
|
|
|
|
|
validatePrevote(t, cs1, round, vss[0], nil)
|
|
|
|
|
|
|
|
|
|
// now lets add prevotes from everyone else for the new block
|
|
|
|
|
signAddVotes(config, cs1, tmproto.PrevoteType, secondBlockHash, secondBlockParts.Header(), vs2, vs3, vs4)
|
|
|
|
|
@@ -1144,8 +1315,6 @@ func TestStateLock_POLSafety1(t *testing.T) {
|
|
|
|
|
propBlock.Hash(), propBlock.MakePartSet(partSize).Header(),
|
|
|
|
|
vs2, vs3, vs4)
|
|
|
|
|
|
|
|
|
|
t.Logf("old prop hash %v", fmt.Sprintf("%X", propBlock.Hash()))
|
|
|
|
|
|
|
|
|
|
// we do see them precommit nil
|
|
|
|
|
signAddVotes(config, cs1, tmproto.PrecommitType, nil, types.PartSetHeader{}, vs2, vs3, vs4)
|
|
|
|
|
|
|
|
|
|
@@ -1154,14 +1323,13 @@ func TestStateLock_POLSafety1(t *testing.T) {
|
|
|
|
|
ensureNewTimeout(t, timeoutWaitCh, height, round, cs1.config.Precommit(round).Nanoseconds())
|
|
|
|
|
|
|
|
|
|
t.Log("### ONTO ROUND 1")
|
|
|
|
|
|
|
|
|
|
prop, propBlock := decideProposal(t, cs1, vs2, vs2.Height, vs2.Round+1)
|
|
|
|
|
incrementRound(vs2, vs3, vs4)
|
|
|
|
|
round++ // moving to the next round
|
|
|
|
|
cs2 := newState(cs1.state, vs2, kvstore.NewApplication())
|
|
|
|
|
prop, propBlock := decideProposal(t, cs2, vs2, vs2.Height, vs2.Round)
|
|
|
|
|
propBlockHash := propBlock.Hash()
|
|
|
|
|
propBlockParts := propBlock.MakePartSet(partSize)
|
|
|
|
|
|
|
|
|
|
incrementRound(vs2, vs3, vs4)
|
|
|
|
|
|
|
|
|
|
round++ // moving to the next round
|
|
|
|
|
ensureNewRound(t, newRoundCh, height, round)
|
|
|
|
|
|
|
|
|
|
//XXX: this isnt guaranteed to get there before the timeoutPropose ...
|
|
|
|
|
@@ -1178,9 +1346,8 @@ func TestStateLock_POLSafety1(t *testing.T) {
|
|
|
|
|
rs = cs1.GetRoundState()
|
|
|
|
|
|
|
|
|
|
if rs.LockedBlock != nil {
|
|
|
|
|
panic("we should not be locked!")
|
|
|
|
|
t.Fatalf("was not expected to be locked on a block")
|
|
|
|
|
}
|
|
|
|
|
t.Logf("new prop hash %v", fmt.Sprintf("%X", propBlockHash))
|
|
|
|
|
|
|
|
|
|
// go to prevote, prevote for proposal block
|
|
|
|
|
ensurePrevote(t, voteCh, height, round)
|
|
|
|
|
@@ -1212,8 +1379,8 @@ func TestStateLock_POLSafety1(t *testing.T) {
|
|
|
|
|
|
|
|
|
|
// finish prevote
|
|
|
|
|
ensurePrevote(t, voteCh, height, round)
|
|
|
|
|
// we should prevote what we're locked on
|
|
|
|
|
validatePrevote(t, cs1, round, vss[0], propBlockHash)
|
|
|
|
|
// we should prevote for nil
|
|
|
|
|
validatePrevote(t, cs1, round, vss[0], nil)
|
|
|
|
|
|
|
|
|
|
newStepCh := subscribe(cs1.eventBus, types.EventQueryNewRoundStep)
|
|
|
|
|
|
|
|
|
|
@@ -1221,8 +1388,6 @@ func TestStateLock_POLSafety1(t *testing.T) {
|
|
|
|
|
// add prevotes from the earlier round
|
|
|
|
|
addVotes(cs1, prevotes...)
|
|
|
|
|
|
|
|
|
|
t.Log("Done adding prevotes!")
|
|
|
|
|
|
|
|
|
|
ensureNoNewRoundStep(t, newStepCh)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -1325,6 +1490,142 @@ func TestStateLock_POLSafety2(t *testing.T) {
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TestState_PrevotePOLFromPreviousRound tests that a validator will prevote
|
|
|
|
|
// for a block if it is locked on a different block but saw a POL for the block
|
|
|
|
|
// it is not locked on in a previous round.
|
|
|
|
|
func TestState_PrevotePOLFromPreviousRound(t *testing.T) {
|
|
|
|
|
config := configSetup(t)
|
|
|
|
|
|
|
|
|
|
cs1, vss := randState(config, 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(context.Background())
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
addr := pv1.Address()
|
|
|
|
|
voteCh := subscribeToVoter(cs1, addr)
|
|
|
|
|
lockCh := subscribe(cs1.eventBus, types.EventQueryLock)
|
|
|
|
|
newRoundCh := subscribe(cs1.eventBus, types.EventQueryNewRound)
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
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(cs1, height, round)
|
|
|
|
|
|
|
|
|
|
ensureNewRound(t, newRoundCh, height, round)
|
|
|
|
|
ensureNewProposal(t, proposalCh, height, round)
|
|
|
|
|
rs := cs1.GetRoundState()
|
|
|
|
|
theBlockHash := rs.ProposalBlock.Hash()
|
|
|
|
|
theBlockParts := rs.ProposalBlockParts.Header()
|
|
|
|
|
|
|
|
|
|
ensurePrevote(t, voteCh, height, round)
|
|
|
|
|
|
|
|
|
|
signAddVotes(config, cs1, tmproto.PrevoteType, theBlockHash, theBlockParts, 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(t, cs1, round, round, vss[0], theBlockHash, theBlockHash)
|
|
|
|
|
|
|
|
|
|
// add precommits from the rest of the validators.
|
|
|
|
|
signAddVotes(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 block, D but do not send a proposal for it to cs1.
|
|
|
|
|
Send a prevote for D from each of the validators to cs1 so that cs1 sees a POL.
|
|
|
|
|
Send a precommit for nil from all of the validtors to cs1.
|
|
|
|
|
|
|
|
|
|
cs1 has now seen greater than 2/3 of the voting power prevote D in this round
|
|
|
|
|
but cs1 did not see the proposal for D in this round so it will not prevote or precommit it.
|
|
|
|
|
*/
|
|
|
|
|
t.Log("### Starting Round 1")
|
|
|
|
|
incrementRound(vs2, vs3, vs4)
|
|
|
|
|
round++
|
|
|
|
|
// Generate a new proposal block.
|
|
|
|
|
cs2 := newState(cs1.state, vs2, kvstore.NewApplication())
|
|
|
|
|
cs2.ValidRound = 1
|
|
|
|
|
propR1, propBlockR1 := decideProposal(t, cs2, vs2, vs2.Height, round)
|
|
|
|
|
t.Log(propR1.POLRound)
|
|
|
|
|
propBlockR1Parts := propBlockR1.MakePartSet(partSize)
|
|
|
|
|
propBlockR1Hash := propBlockR1.Hash()
|
|
|
|
|
require.NotEqual(t, propBlockR1Hash, theBlockHash)
|
|
|
|
|
|
|
|
|
|
ensureNewRound(t, newRoundCh, height, round)
|
|
|
|
|
|
|
|
|
|
signAddVotes(config, cs1, tmproto.PrevoteType, propBlockR1Hash, propBlockR1Parts.Header(), vs2, vs3, vs4)
|
|
|
|
|
|
|
|
|
|
ensurePrevote(t, voteCh, height, round)
|
|
|
|
|
validatePrevote(t, cs1, round, vss[0], nil)
|
|
|
|
|
|
|
|
|
|
signAddVotes(config, cs1, tmproto.PrecommitType, nil, types.PartSetHeader{}, vs2, vs3, vs4)
|
|
|
|
|
|
|
|
|
|
ensurePrecommit(t, voteCh, height, round)
|
|
|
|
|
|
|
|
|
|
// timeout to new round.
|
|
|
|
|
ensureNewTimeout(t, timeoutWaitCh, height, round, cs1.config.Precommit(round).Nanoseconds())
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
Round 2:
|
|
|
|
|
Create a new proposal for D, the same block from Round 1.
|
|
|
|
|
cs1 already saw greater than 2/3 of the voting power on the network vote for
|
|
|
|
|
D in a previous round, so it should prevote D once it receives a proposal for it.
|
|
|
|
|
|
|
|
|
|
cs1 does not need to receive prevotes from other validators before the proposal
|
|
|
|
|
in this round. It will still prevote the block.
|
|
|
|
|
|
|
|
|
|
Send cs1 prevotes for nil and check that it still prevotes its locked block
|
|
|
|
|
and not the block that it prevoted.
|
|
|
|
|
*/
|
|
|
|
|
t.Log("### Starting Round 2")
|
|
|
|
|
incrementRound(vs2, vs3, vs4)
|
|
|
|
|
round++
|
|
|
|
|
propBlockID := types.BlockID{Hash: propBlockR1Hash, PartSetHeader: propBlockR1Parts.Header()}
|
|
|
|
|
propR2 := types.NewProposal(height, round, 1, propBlockID)
|
|
|
|
|
p := propR2.ToProto()
|
|
|
|
|
if err := vs3.SignProposal(context.Background(), cs1.state.ChainID, p); err != nil {
|
|
|
|
|
t.Fatalf("error signing proposal: %s", err)
|
|
|
|
|
}
|
|
|
|
|
propR2.Signature = p.Signature
|
|
|
|
|
|
|
|
|
|
// cs1 receives a proposal for D, the block that received a POL in round 1.
|
|
|
|
|
if err := cs1.SetProposalAndBlock(propR2, propBlockR1, propBlockR1Parts, ""); err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ensureNewRound(t, newRoundCh, height, round)
|
|
|
|
|
|
|
|
|
|
ensureNewProposal(t, proposalCh, height, round)
|
|
|
|
|
|
|
|
|
|
// We should now prevote this block, despite being locked on the block from
|
|
|
|
|
// round 0.
|
|
|
|
|
ensurePrevote(t, voteCh, height, round)
|
|
|
|
|
validatePrevote(t, cs1, round, vss[0], propBlockR1Hash)
|
|
|
|
|
|
|
|
|
|
signAddVotes(config, cs1, tmproto.PrevoteType, nil, types.PartSetHeader{}, vs2, vs3, vs4)
|
|
|
|
|
|
|
|
|
|
// cs1 did not receive a POL within this round, so it should remain locked
|
|
|
|
|
// on the block from round 0.
|
|
|
|
|
ensurePrecommit(t, voteCh, height, round)
|
|
|
|
|
validatePrecommit(t, cs1, round, 0, vss[0], nil, theBlockHash)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 4 vals.
|
|
|
|
|
// polka P0 at R0 for B0. We lock B0 on P0 at R0.
|
|
|
|
|
|
|
|
|
|
@@ -1381,8 +1682,9 @@ func TestProposeValidBlock(t *testing.T) {
|
|
|
|
|
// timeout of propose
|
|
|
|
|
ensureNewTimeout(t, timeoutProposeCh, height, round, cs1.config.Propose(round).Nanoseconds())
|
|
|
|
|
|
|
|
|
|
// We did not see a valid proposal within this round, so prevote nil.
|
|
|
|
|
ensurePrevote(t, voteCh, height, round)
|
|
|
|
|
validatePrevote(t, cs1, round, vss[0], propBlockHash)
|
|
|
|
|
validatePrevote(t, cs1, round, vss[0], nil)
|
|
|
|
|
|
|
|
|
|
signAddVotes(config, cs1, tmproto.PrecommitType, nil, types.PartSetHeader{}, vs2, vs3, vs4)
|
|
|
|
|
|
|
|
|
|
@@ -2014,17 +2316,16 @@ func TestStateHalt1(t *testing.T) {
|
|
|
|
|
round++ // moving to the next round
|
|
|
|
|
|
|
|
|
|
ensureNewRound(t, newRoundCh, height, round)
|
|
|
|
|
rs = cs1.GetRoundState()
|
|
|
|
|
|
|
|
|
|
t.Log("### ONTO ROUND 1")
|
|
|
|
|
/*Round2
|
|
|
|
|
// we timeout and prevote our lock
|
|
|
|
|
// we timeout and prevote
|
|
|
|
|
// a polka happened but we didn't see it!
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
// go to prevote, prevote for locked block
|
|
|
|
|
// prevote for nil since we did not receive a proposal in this round.
|
|
|
|
|
ensurePrevote(t, voteCh, height, round)
|
|
|
|
|
validatePrevote(t, cs1, round, vss[0], rs.LockedBlock.Hash())
|
|
|
|
|
validatePrevote(t, cs1, round, vss[0], nil)
|
|
|
|
|
|
|
|
|
|
// now we receive the precommit from the previous round
|
|
|
|
|
addVotes(cs1, precommit4)
|
|
|
|
|
|