add tests for vote extension cases

This commit is contained in:
William Banfield
2022-04-01 14:59:58 -04:00
parent b958ca22a5
commit 431c85be60
6 changed files with 322 additions and 4 deletions
+4 -3
View File
@@ -112,7 +112,8 @@ func (vs *validatorStub) signVote(
ctx context.Context,
voteType tmproto.SignedMsgType,
chainID string,
blockID types.BlockID) (*types.Vote, error) {
blockID types.BlockID,
voteExtension []byte) (*types.Vote, error) {
pubKey, err := vs.PrivValidator.GetPubKey(ctx)
if err != nil {
@@ -127,7 +128,7 @@ func (vs *validatorStub) signVote(
Timestamp: vs.clock.Now(),
ValidatorAddress: pubKey.Address(),
ValidatorIndex: vs.Index,
Extension: []byte("extension"),
Extension: voteExtension,
}
v := vote.ToProto()
if err = vs.PrivValidator.SignVote(ctx, chainID, v); err != nil {
@@ -157,7 +158,7 @@ func signVote(
chainID string,
blockID types.BlockID) *types.Vote {
v, err := vs.signVote(ctx, voteType, chainID, blockID)
v, err := vs.signVote(ctx, voteType, chainID, blockID, []byte("extension"))
require.NoError(t, err, "failed to sign vote")
vs.lastVote = v
-1
View File
@@ -911,7 +911,6 @@ func (cs *State) receiveRoutine(ctx context.Context, maxSteps int) {
if err := cs.wal.Write(mi); err != nil {
cs.logger.Error("failed writing to WAL", "err", err)
}
// handles proposals, block parts, votes
// may generate internal events (votes, complete proposals, 2/3 majorities)
cs.handleMsg(ctx, mi)
+243
View File
@@ -2015,6 +2015,237 @@ func TestFinalizeBlockCalled(t *testing.T) {
}
}
// TestExtendVoteCalled tests that the vote extension methods are called at the
// correct point in the consensus algorithm.
func TestExtendVoteCalled(t *testing.T) {
config := configSetup(t)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
m := abcimocks.NewBaseMock()
m.On("ProcessProposal", mock.Anything).Return(abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_ACCEPT})
m.On("ExtendVote", mock.Anything).Return(abci.ResponseExtendVote{
VoteExtension: []byte("extension"),
})
m.On("VerifyVoteExtension", mock.Anything).Return(abci.ResponseVerifyVoteExtension{
Status: abci.ResponseVerifyVoteExtension_ACCEPT,
})
m.On("FinalizeBlock", mock.Anything).Return(abci.ResponseFinalizeBlock{}).Maybe()
cs1, vss := makeState(ctx, t, makeStateArgs{config: config, application: m})
height, round := cs1.Height, cs1.Round
proposalCh := subscribe(ctx, t, cs1.eventBus, types.EventQueryCompleteProposal)
newRoundCh := subscribe(ctx, t, cs1.eventBus, types.EventQueryNewRound)
pv1, err := cs1.privValidator.GetPubKey(ctx)
require.NoError(t, err)
addr := pv1.Address()
voteCh := subscribeToVoter(ctx, t, cs1, addr)
startTestRound(ctx, cs1, cs1.Height, round)
ensureNewRound(t, newRoundCh, height, round)
ensureNewProposal(t, proposalCh, height, round)
m.AssertNotCalled(t, "ExtendVote", mock.Anything)
rs := cs1.GetRoundState()
blockID := types.BlockID{
Hash: rs.ProposalBlock.Hash(),
PartSetHeader: rs.ProposalBlockParts.Header(),
}
signAddVotes(ctx, t, cs1, tmproto.PrevoteType, config.ChainID(), blockID, vss[1:]...)
ensurePrevoteMatch(t, voteCh, height, round, blockID.Hash)
ensurePrecommit(t, voteCh, height, round)
m.AssertCalled(t, "ExtendVote", abci.RequestExtendVote{
Height: height,
Hash: blockID.Hash,
})
m.AssertCalled(t, "VerifyVoteExtension", abci.RequestVerifyVoteExtension{
Hash: blockID.Hash,
ValidatorAddress: addr,
Height: height,
VoteExtension: []byte("extension"),
})
signAddVotes(ctx, t, cs1, tmproto.PrecommitType, config.ChainID(), blockID, vss[1:]...)
ensureNewRound(t, newRoundCh, height+1, 0)
m.AssertExpectations(t)
// Only 3 of the vote extensions are seen, as consensus proceeds as soon as the +2/3 threshold
// is observed by the consensus engine.
for _, pv := range vss[:3] {
pv, err := pv.GetPubKey(ctx)
require.NoError(t, err)
addr := pv.Address()
m.AssertCalled(t, "VerifyVoteExtension", abci.RequestVerifyVoteExtension{
Hash: blockID.Hash,
ValidatorAddress: addr,
Height: height,
VoteExtension: []byte("extension"),
})
}
}
// TestVerifyVoteExtensionNotCalledOnAbsentPrecommit tests that the VerifyVoteExtension
// method is not called for a validator's vote that is never delivered.
func TestVerifyVoteExtensionNotCalledOnAbsentPrecommit(t *testing.T) {
config := configSetup(t)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
m := abcimocks.NewBaseMock()
m.On("ProcessProposal", mock.Anything).Return(abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_ACCEPT})
m.On("ExtendVote", mock.Anything).Return(abci.ResponseExtendVote{
VoteExtension: []byte("extension"),
})
m.On("VerifyVoteExtension", mock.Anything).Return(abci.ResponseVerifyVoteExtension{
Status: abci.ResponseVerifyVoteExtension_ACCEPT,
})
m.On("FinalizeBlock", mock.Anything).Return(abci.ResponseFinalizeBlock{}).Maybe()
cs1, vss := makeState(ctx, t, makeStateArgs{config: config, application: m})
height, round := cs1.Height, cs1.Round
proposalCh := subscribe(ctx, t, cs1.eventBus, types.EventQueryCompleteProposal)
newRoundCh := subscribe(ctx, t, cs1.eventBus, types.EventQueryNewRound)
pv1, err := cs1.privValidator.GetPubKey(ctx)
require.NoError(t, err)
addr := pv1.Address()
voteCh := subscribeToVoter(ctx, t, cs1, addr)
startTestRound(ctx, cs1, cs1.Height, round)
ensureNewRound(t, newRoundCh, height, round)
ensureNewProposal(t, proposalCh, height, round)
rs := cs1.GetRoundState()
blockID := types.BlockID{
Hash: rs.ProposalBlock.Hash(),
PartSetHeader: rs.ProposalBlockParts.Header(),
}
signAddVotes(ctx, t, cs1, tmproto.PrevoteType, config.ChainID(), blockID, vss[2:]...)
ensurePrevoteMatch(t, voteCh, height, round, blockID.Hash)
ensurePrecommit(t, voteCh, height, round)
m.AssertCalled(t, "ExtendVote", abci.RequestExtendVote{
Height: height,
Hash: blockID.Hash,
})
m.AssertCalled(t, "VerifyVoteExtension", abci.RequestVerifyVoteExtension{
Hash: blockID.Hash,
ValidatorAddress: addr,
Height: height,
VoteExtension: []byte("extension"),
})
signAddVotes(ctx, t, cs1, tmproto.PrecommitType, config.ChainID(), blockID, vss[2:]...)
ensureNewRound(t, newRoundCh, height+1, 0)
m.AssertExpectations(t)
// vss[1] did not issue a precommit for the block, ensure that a vote extension
// for its address was not sent to the application.
pv, err := vss[1].GetPubKey(ctx)
require.NoError(t, err)
addr = pv.Address()
m.AssertNotCalled(t, "VerifyVoteExtension", abci.RequestVerifyVoteExtension{
Hash: blockID.Hash,
ValidatorAddress: addr,
Height: height,
VoteExtension: []byte("extension"),
})
}
// TestPrepareProposalReceivesVoteExtensions tests that the PrepareProposal method
// is called with the vote extensions from the previous height. The test functions
// be completing a consensus height with a mock application as the proposer. The
// test then proceeds to fail sever rounds of consensus until the mock application
// is the proposer again and ensures that the mock application receives the set of
// vote extensions from the previous consensus instance.
func TestPrepareProposalReceivesVoteExtensions(t *testing.T) {
config := configSetup(t)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// create a list of vote extensions, one for each validator.
voteExtensions := [][]byte{
[]byte("extension 0"),
[]byte("extension 1"),
[]byte("extension 2"),
[]byte("extension 3"),
}
m := abcimocks.NewBaseMock()
m.On("ExtendVote", mock.Anything).Return(abci.ResponseExtendVote{
VoteExtension: voteExtensions[0],
})
m.On("PrepareProposal", mock.Anything).Return(abci.ResponsePrepareProposal{
ModifiedTxStatus: abci.ResponsePrepareProposal_UNMODIFIED,
}).Once()
cs1, vss := makeState(ctx, t, makeStateArgs{config: config, application: m})
height, round := cs1.Height, cs1.Round
newRoundCh := subscribe(ctx, t, cs1.eventBus, types.EventQueryNewRound)
proposalCh := subscribe(ctx, t, cs1.eventBus, types.EventQueryCompleteProposal)
startTestRound(ctx, cs1, height, round)
ensureNewRound(t, newRoundCh, height, round)
ensureNewProposal(t, proposalCh, height, round)
rs := cs1.GetRoundState()
blockID := types.BlockID{
Hash: rs.ProposalBlock.Hash(),
PartSetHeader: rs.ProposalBlockParts.Header(),
}
signAddVotes(ctx, t, cs1, tmproto.PrevoteType, config.ChainID(), blockID, vss[1:]...)
// create a precommit for each validator with the associated vote extension.
for i, vs := range vss[1:] {
signAddPrecommitWithExtension(ctx, t, cs1, config.ChainID(), blockID, voteExtensions[i+1], vs)
}
pv1, err := cs1.privValidator.GetPubKey(ctx)
require.NoError(t, err)
addr := pv1.Address()
voteCh := subscribeToVoter(ctx, t, cs1, addr)
// ensure that the height is commited.
ensurePrecommit(t, voteCh, height, round)
validatePrecommit(ctx, t, cs1, round, round, vss[0], blockID.Hash, blockID.Hash)
incrementHeight(vss[1:]...)
height++
round = 0
ensureNewRound(t, newRoundCh, height, round)
incrementRound(vss[1:]...)
incrementRound(vss[1:]...)
incrementRound(vss[1:]...)
round = 3
// capture the prepare proposal request.
rpp := abci.RequestPrepareProposal{}
m.On("PrepareProposal", mock.MatchedBy(func(r abci.RequestPrepareProposal) bool {
rpp = r
return true
})).Return(abci.ResponsePrepareProposal{ModifiedTxStatus: abci.ResponsePrepareProposal_UNMODIFIED})
signAddVotes(ctx, t, cs1, tmproto.PrecommitType, config.ChainID(), types.BlockID{}, vss[1:]...)
ensureNewRound(t, newRoundCh, height, round)
ensureNewProposal(t, proposalCh, height, round)
// ensure that the proposer received the list of vote extensions from the
// previous height.
for i := range vss {
require.Equal(t, rpp.LocalLastCommit.Votes[i].VoteExtension, voteExtensions[i])
}
}
// 4 vals, 3 Nil Precommits at P0
// What we want:
// P0 waits for timeoutPrecommit before starting next round
@@ -2719,3 +2950,15 @@ func subscribe(
}()
return ch
}
func signAddPrecommitWithExtension(ctx context.Context,
t *testing.T,
cs *State,
chainID string,
blockID types.BlockID,
extension []byte,
stub *validatorStub) {
v, err := stub.signVote(ctx, tmproto.PrecommitType, chainID, blockID, extension)
require.NoError(t, err, "failed to sign vote")
addVotes(cs, v)
}