package state_test import ( "fmt" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/libs/bytes" "github.com/tendermint/tendermint/proto/tendermint/version" "github.com/tendermint/tendermint/crypto/ed25519" "github.com/tendermint/tendermint/crypto/tmhash" "github.com/tendermint/tendermint/libs/log" memmock "github.com/tendermint/tendermint/mempool/mock" protostate "github.com/tendermint/tendermint/proto/tendermint/state" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" sm "github.com/tendermint/tendermint/state" "github.com/tendermint/tendermint/state/mocks" "github.com/tendermint/tendermint/types" tmtime "github.com/tendermint/tendermint/types/time" ) const validationTestsStopHeight int64 = 10 var defaultTestTime = time.Date(2019, 1, 1, 0, 0, 0, 0, time.UTC) func TestValidateBlockHeader(t *testing.T) { proxyApp := newTestApp() require.NoError(t, proxyApp.Start()) defer proxyApp.Stop() state, stateDB, privVals := makeState(3, 1) blockExec := sm.NewBlockExecutor( stateDB, log.TestingLogger(), proxyApp.Consensus(), memmock.Mempool{}, sm.MockEvidencePool{}, ) lastCommit := types.NewCommit(0, 0, types.BlockID{}, nil) // some bad values wrongHash := tmhash.Sum([]byte("this hash is wrong")) wrongVersion1 := state.Version.Consensus wrongVersion1.Block += 2 wrongVersion2 := state.Version.Consensus wrongVersion2.App += 2 // Manipulation of any header field causes failure. testCases := []struct { name string malleateBlock func(block *types.Block) }{ {"Version wrong1", func(block *types.Block) { block.Version = wrongVersion1 }}, {"Version wrong2", func(block *types.Block) { block.Version = wrongVersion2 }}, {"ChainID wrong", func(block *types.Block) { block.ChainID = "not-the-real-one" }}, {"Height wrong", func(block *types.Block) { block.Height += 10 }}, {"Time wrong", func(block *types.Block) { block.Time = block.Time.Add(-time.Second * 1) }}, {"LastBlockID wrong", func(block *types.Block) { block.LastBlockID.PartSetHeader.Total += 10 }}, {"LastCommitHash wrong", func(block *types.Block) { block.LastCommitHash = wrongHash }}, {"DataHash wrong", func(block *types.Block) { block.DataHash = wrongHash }}, {"ValidatorsHash wrong", func(block *types.Block) { block.ValidatorsHash = wrongHash }}, {"NextValidatorsHash wrong", func(block *types.Block) { block.NextValidatorsHash = wrongHash }}, {"ConsensusHash wrong", func(block *types.Block) { block.ConsensusHash = wrongHash }}, {"AppHash wrong", func(block *types.Block) { block.AppHash = wrongHash }}, {"LastResultsHash wrong", func(block *types.Block) { block.LastResultsHash = wrongHash }}, {"EvidenceHash wrong", func(block *types.Block) { block.EvidenceHash = wrongHash }}, {"Proposer wrong", func(block *types.Block) { block.ProposerAddress = ed25519.GenPrivKey().PubKey().Address() }}, {"Proposer invalid", func(block *types.Block) { block.ProposerAddress = []byte("wrong size") }}, } // Build up state for multiple heights for height := int64(1); height < validationTestsStopHeight; height++ { proposerAddr := state.Validators.GetProposer().Address /* Invalid blocks don't pass */ for _, tc := range testCases { block, _ := state.MakeBlock(height, makeTxs(height), lastCommit, nil, proposerAddr) tc.malleateBlock(block) err := blockExec.ValidateBlock(state, block) require.Error(t, err, tc.name) } /* A good block passes */ var err error state, _, lastCommit, err = makeAndCommitGoodBlock(state, height, lastCommit, proposerAddr, blockExec, privVals, nil) require.NoError(t, err, "height %d", height) } } func TestValidateBlockCommit(t *testing.T) { proxyApp := newTestApp() require.NoError(t, proxyApp.Start()) defer proxyApp.Stop() state, stateDB, privVals := makeState(1, 1) blockExec := sm.NewBlockExecutor( stateDB, log.TestingLogger(), proxyApp.Consensus(), memmock.Mempool{}, sm.MockEvidencePool{}, ) lastCommit := types.NewCommit(0, 0, types.BlockID{}, nil) wrongSigsCommit := types.NewCommit(1, 0, types.BlockID{}, nil) badPrivVal := types.NewMockPV() for height := int64(1); height < validationTestsStopHeight; height++ { proposerAddr := state.Validators.GetProposer().Address if height > 1 { /* #2589: ensure state.LastValidators.VerifyCommit fails here */ // should be height-1 instead of height wrongHeightVote, err := types.MakeVote( height, state.LastBlockID, state.Validators, privVals[proposerAddr.String()], chainID, time.Now(), ) require.NoError(t, err, "height %d", height) wrongHeightCommit := types.NewCommit( wrongHeightVote.Height, wrongHeightVote.Round, state.LastBlockID, []types.CommitSig{wrongHeightVote.CommitSig()}, ) block, _ := state.MakeBlock(height, makeTxs(height), wrongHeightCommit, nil, proposerAddr) err = blockExec.ValidateBlock(state, block) _, isErrInvalidCommitHeight := err.(types.ErrInvalidCommitHeight) require.True(t, isErrInvalidCommitHeight, "expected ErrInvalidCommitHeight at height %d but got: %v", height, err) /* #2589: test len(block.LastCommit.Signatures) == state.LastValidators.Size() */ block, _ = state.MakeBlock(height, makeTxs(height), wrongSigsCommit, nil, proposerAddr) err = blockExec.ValidateBlock(state, block) _, isErrInvalidCommitSignatures := err.(types.ErrInvalidCommitSignatures) require.True(t, isErrInvalidCommitSignatures, "expected ErrInvalidCommitSignatures at height %d, but got: %v", height, err, ) } /* A good block passes */ var err error var blockID types.BlockID state, blockID, lastCommit, err = makeAndCommitGoodBlock( state, height, lastCommit, proposerAddr, blockExec, privVals, nil, ) require.NoError(t, err, "height %d", height) /* wrongSigsCommit is fine except for the extra bad precommit */ goodVote, err := types.MakeVote(height, blockID, state.Validators, privVals[proposerAddr.String()], chainID, time.Now(), ) require.NoError(t, err, "height %d", height) bpvPubKey, err := badPrivVal.GetPubKey() require.NoError(t, err) badVote := &types.Vote{ ValidatorAddress: bpvPubKey.Address(), ValidatorIndex: 0, Height: height, Round: 0, Timestamp: tmtime.Now(), Type: tmproto.PrecommitType, BlockID: blockID, } g := goodVote.ToProto() b := badVote.ToProto() err = badPrivVal.SignVote(chainID, g) require.NoError(t, err, "height %d", height) err = badPrivVal.SignVote(chainID, b) require.NoError(t, err, "height %d", height) goodVote.Signature, badVote.Signature = g.Signature, b.Signature wrongSigsCommit = types.NewCommit(goodVote.Height, goodVote.Round, blockID, []types.CommitSig{goodVote.CommitSig(), badVote.CommitSig()}) } } func TestValidateBlockEvidence(t *testing.T) { proxyApp := newTestApp() require.NoError(t, proxyApp.Start()) defer proxyApp.Stop() state, stateDB, privVals := makeState(4, 1) state.ConsensusParams.Evidence.MaxNum = 3 blockExec := sm.NewBlockExecutor( stateDB, log.TestingLogger(), proxyApp.Consensus(), memmock.Mempool{}, sm.MockEvidencePool{}, ) lastCommit := types.NewCommit(0, 0, types.BlockID{}, nil) for height := int64(1); height < validationTestsStopHeight; height++ { proposerAddr := state.Validators.GetProposer().Address maxNumEvidence := state.ConsensusParams.Evidence.MaxNum t.Log(maxNumEvidence) if height > 1 { /* A block with too much evidence fails */ require.True(t, maxNumEvidence > 2) evidence := make([]types.Evidence, 0) // one more than the maximum allowed evidence for i := uint32(0); i <= maxNumEvidence; i++ { evidence = append(evidence, types.NewMockEvidence(height, time.Now(), proposerAddr)) } block, _ := state.MakeBlock(height, makeTxs(height), lastCommit, evidence, proposerAddr) err := blockExec.ValidateBlock(state, block) _, ok := err.(*types.ErrEvidenceOverflow) require.True(t, ok, "expected error to be of type ErrEvidenceOverflow at height %d", height) } /* A good block with several pieces of good evidence passes */ require.True(t, maxNumEvidence > 2) evidence := make([]types.Evidence, 0) // precisely the amount of allowed evidence for i := int32(0); uint32(i) < maxNumEvidence; i++ { // make different evidence for each validator addr, _ := state.Validators.GetByIndex(i) evidence = append(evidence, types.NewMockEvidence(height, time.Now(), addr)) } var err error state, _, lastCommit, err = makeAndCommitGoodBlock( state, height, lastCommit, proposerAddr, blockExec, privVals, evidence, ) require.NoError(t, err, "height %d", height) } } func TestValidateFailBlockOnCommittedEvidence(t *testing.T) { var height int64 = 1 state, stateDB, _ := makeState(2, int(height)) addr, _ := state.Validators.GetByIndex(0) addr2, _ := state.Validators.GetByIndex(1) ev := types.NewMockEvidence(height, defaultTestTime, addr) ev2 := types.NewMockEvidence(height, defaultTestTime, addr2) evpool := &mocks.EvidencePool{} evpool.On("IsPending", mock.AnythingOfType("types.MockEvidence")).Return(false) evpool.On("IsCommitted", ev).Return(false) evpool.On("IsCommitted", ev2).Return(true) blockExec := sm.NewBlockExecutor( stateDB, log.TestingLogger(), nil, nil, evpool) // A block with a couple pieces of evidence passes. block := makeBlock(state, height) block.Evidence.Evidence = []types.Evidence{ev, ev2} block.EvidenceHash = block.Evidence.Hash() err := blockExec.ValidateBlock(state, block) assert.Error(t, err) assert.IsType(t, err, &types.ErrEvidenceInvalid{}) } func TestValidateAlreadyPendingEvidence(t *testing.T) { var height int64 = 1 state, stateDB, _ := makeState(2, int(height)) addr, _ := state.Validators.GetByIndex(0) addr2, _ := state.Validators.GetByIndex(1) ev := types.NewMockEvidence(height, defaultTestTime, addr) ev2 := types.NewMockEvidence(height, defaultTestTime, addr2) evpool := &mocks.EvidencePool{} evpool.On("IsPending", ev).Return(false) evpool.On("IsPending", ev2).Return(true) evpool.On("IsCommitted", mock.AnythingOfType("types.MockEvidence")).Return(false) blockExec := sm.NewBlockExecutor( stateDB, log.TestingLogger(), nil, nil, evpool) // A block with a couple pieces of evidence passes. block := makeBlock(state, height) // add one evidence seen before and one evidence that hasn't block.Evidence.Evidence = []types.Evidence{ev, ev2} block.EvidenceHash = block.Evidence.Hash() err := blockExec.ValidateBlock(state, block) assert.NoError(t, err) } func TestValidateDuplicateEvidenceShouldFail(t *testing.T) { var height int64 = 1 state, stateDB, _ := makeState(1, int(height)) addr, _ := state.Validators.GetByIndex(0) addr2, _ := state.Validators.GetByIndex(1) ev := types.NewMockEvidence(height, defaultTestTime, addr) ev2 := types.NewMockEvidence(height, defaultTestTime, addr2) blockExec := sm.NewBlockExecutor( stateDB, log.TestingLogger(), nil, nil, sm.MockEvidencePool{}) // A block with a couple pieces of evidence passes. block := makeBlock(state, height) block.Evidence.Evidence = []types.Evidence{ev, ev2, ev2} block.EvidenceHash = block.Evidence.Hash() err := blockExec.ValidateBlock(state, block) assert.Error(t, err) } var blockID = types.BlockID{ Hash: []byte("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"), PartSetHeader: types.PartSetHeader{ Total: 1, Hash: []byte("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"), }, } func TestValidateAmnesiaEvidence(t *testing.T) { var height int64 = 1 state, stateDB, vals := makeState(1, int(height)) addr, val := state.Validators.GetByIndex(0) voteA := makeVote(height, 1, 0, addr, blockID) vA := voteA.ToProto() err := vals[val.Address.String()].SignVote(chainID, vA) voteA.Signature = vA.Signature require.NoError(t, err) voteB := makeVote(height, 2, 0, addr, types.BlockID{}) vB := voteB.ToProto() err = vals[val.Address.String()].SignVote(chainID, vB) voteB.Signature = vB.Signature require.NoError(t, err) ae := types.AmnesiaEvidence{ PotentialAmnesiaEvidence: types.PotentialAmnesiaEvidence{ VoteA: voteA, VoteB: voteB, }, Polc: types.EmptyPOLC(), } evpool := &mocks.EvidencePool{} evpool.On("IsPending", ae).Return(false) evpool.On("IsCommitted", ae).Return(false) evpool.On("AddEvidence", ae).Return(fmt.Errorf("test error")) blockExec := sm.NewBlockExecutor( stateDB, log.TestingLogger(), nil, nil, evpool) // A block with a couple pieces of evidence passes. block := makeBlock(state, height) block.Evidence.Evidence = []types.Evidence{ae} block.EvidenceHash = block.Evidence.Hash() err = blockExec.ValidateBlock(state, block) errMsg := "Invalid evidence: unknown amnesia evidence, trying to add to evidence pool, err: test error" if assert.Error(t, err) { assert.Equal(t, err.Error()[:len(errMsg)], errMsg) } } func TestVerifyEvidenceWrongAddress(t *testing.T) { var height int64 = 1 state, stateDB, _ := makeState(1, int(height)) randomAddr := []byte("wrong address") ev := types.NewMockEvidence(height, defaultTestTime, randomAddr) blockExec := sm.NewBlockExecutor( stateDB, log.TestingLogger(), nil, nil, sm.MockEvidencePool{}) // A block with a couple pieces of evidence passes. block := makeBlock(state, height) block.Evidence.Evidence = []types.Evidence{ev} block.EvidenceHash = block.Evidence.Hash() err := blockExec.ValidateBlock(state, block) errMsg := "Invalid evidence: address 77726F6E672061646472657373 was not a validator at height 1" if assert.Error(t, err) { assert.Equal(t, err.Error()[:len(errMsg)], errMsg) } } func TestVerifyEvidenceExpiredEvidence(t *testing.T) { var height int64 = 4 state, stateDB, _ := makeState(1, int(height)) state.ConsensusParams.Evidence.MaxAgeNumBlocks = 1 addr, _ := state.Validators.GetByIndex(0) ev := types.NewMockEvidence(1, defaultTestTime, addr) err := sm.VerifyEvidence(stateDB, state, ev, nil) errMsg := "evidence from height 1 (created at: 2019-01-01 00:00:00 +0000 UTC) is too old" if assert.Error(t, err) { assert.Equal(t, err.Error()[:len(errMsg)], errMsg) } } func TestVerifyEvidenceWithAmnesiaEvidence(t *testing.T) { var height int64 = 1 state, stateDB, vals := makeState(4, int(height)) addr, val := state.Validators.GetByIndex(0) addr2, val2 := state.Validators.GetByIndex(1) voteA := makeVote(height, 1, 0, addr, types.BlockID{}) vA := voteA.ToProto() err := vals[val.Address.String()].SignVote(chainID, vA) voteA.Signature = vA.Signature require.NoError(t, err) voteB := makeVote(height, 2, 0, addr, blockID) vB := voteB.ToProto() err = vals[val.Address.String()].SignVote(chainID, vB) voteB.Signature = vB.Signature require.NoError(t, err) voteC := makeVote(height, 2, 1, addr2, blockID) vC := voteC.ToProto() err = vals[val2.Address.String()].SignVote(chainID, vC) voteC.Signature = vC.Signature require.NoError(t, err) //var ae types.Evidence badAe := types.AmnesiaEvidence{ PotentialAmnesiaEvidence: types.PotentialAmnesiaEvidence{ VoteA: voteA, VoteB: voteB, }, Polc: types.ProofOfLockChange{ Votes: []types.Vote{*voteC}, PubKey: val.PubKey, }, } err = sm.VerifyEvidence(stateDB, state, badAe, nil) if assert.Error(t, err) { assert.Equal(t, err.Error(), "amnesia evidence contains invalid polc, err: "+ "invalid commit -- insufficient voting power: got 1000, needed more than 2667") } addr3, val3 := state.Validators.GetByIndex(2) voteD := makeVote(height, 2, 2, addr3, blockID) vD := voteD.ToProto() err = vals[val3.Address.String()].SignVote(chainID, vD) require.NoError(t, err) voteD.Signature = vD.Signature addr4, val4 := state.Validators.GetByIndex(3) voteE := makeVote(height, 2, 3, addr4, blockID) vE := voteE.ToProto() err = vals[val4.Address.String()].SignVote(chainID, vE) voteE.Signature = vE.Signature require.NoError(t, err) goodAe := types.AmnesiaEvidence{ PotentialAmnesiaEvidence: types.PotentialAmnesiaEvidence{ VoteA: voteA, VoteB: voteB, }, Polc: types.ProofOfLockChange{ Votes: []types.Vote{*voteC, *voteD, *voteE}, PubKey: val.PubKey, }, } err = sm.VerifyEvidence(stateDB, state, goodAe, nil) assert.NoError(t, err) goodAe = types.AmnesiaEvidence{ PotentialAmnesiaEvidence: types.PotentialAmnesiaEvidence{ VoteA: voteA, VoteB: voteB, }, Polc: types.EmptyPOLC(), } err = sm.VerifyEvidence(stateDB, state, goodAe, nil) assert.NoError(t, err) } func TestVerifyEvidenceWithLunaticValidatorEvidence(t *testing.T) { state, stateDB, vals := makeState(4, 4) state.ConsensusParams.Evidence.MaxAgeNumBlocks = 1 addr, val := state.Validators.GetByIndex(0) h := &types.Header{ Version: version.Consensus{Block: 1, App: 2}, ChainID: chainID, Height: 3, Time: defaultTestTime, LastBlockID: blockID, LastCommitHash: tmhash.Sum([]byte("last_commit_hash")), DataHash: tmhash.Sum([]byte("data_hash")), ValidatorsHash: tmhash.Sum([]byte("validators_hash")), NextValidatorsHash: tmhash.Sum([]byte("next_validators_hash")), ConsensusHash: tmhash.Sum([]byte("consensus_hash")), AppHash: tmhash.Sum([]byte("app_hash")), LastResultsHash: tmhash.Sum([]byte("last_results_hash")), EvidenceHash: tmhash.Sum([]byte("evidence_hash")), ProposerAddress: crypto.AddressHash([]byte("proposer_address")), } vote := makeVote(3, 1, 0, addr, blockID) v := vote.ToProto() err := vals[val.Address.String()].SignVote(chainID, v) vote.Signature = v.Signature require.NoError(t, err) ev := types.LunaticValidatorEvidence{ Header: h, Vote: vote, InvalidHeaderField: "ConsensusHash", } err = ev.ValidateBasic() require.NoError(t, err) err = sm.VerifyEvidence(stateDB, state, ev, h) if assert.Error(t, err) { assert.Equal(t, "ConsensusHash matches committed hash", err.Error()) } } func TestVerifyEvidenceWithPhantomValidatorEvidence(t *testing.T) { state, stateDB, vals := makeState(4, 4) state.ConsensusParams.Evidence.MaxAgeNumBlocks = 1 addr, val := state.Validators.GetByIndex(0) vote := makeVote(3, 1, 0, addr, blockID) v := vote.ToProto() err := vals[val.Address.String()].SignVote(chainID, v) vote.Signature = v.Signature require.NoError(t, err) ev := types.PhantomValidatorEvidence{ Vote: vote, LastHeightValidatorWasInSet: 1, } err = ev.ValidateBasic() require.NoError(t, err) err = sm.VerifyEvidence(stateDB, state, ev, nil) if assert.Error(t, err) { assert.Equal(t, "address 576585A00DD4D58318255611D8AAC60E8E77CB32 was a validator at height 3", err.Error()) } privVal := types.NewMockPV() pubKey, _ := privVal.GetPubKey() vote2 := makeVote(3, 1, 0, pubKey.Address(), blockID) v2 := vote2.ToProto() err = privVal.SignVote(chainID, v2) vote2.Signature = v2.Signature require.NoError(t, err) ev = types.PhantomValidatorEvidence{ Vote: vote2, LastHeightValidatorWasInSet: 1, } err = ev.ValidateBasic() assert.NoError(t, err) err = sm.VerifyEvidence(stateDB, state, ev, nil) if assert.Error(t, err) { assert.Equal(t, "last time validator was in the set at height 1, min: 2", err.Error()) } ev = types.PhantomValidatorEvidence{ Vote: vote2, LastHeightValidatorWasInSet: 2, } err = ev.ValidateBasic() assert.NoError(t, err) err = sm.VerifyEvidence(stateDB, state, ev, nil) errMsg := "phantom validator" if assert.Error(t, err) { assert.Equal(t, errMsg, err.Error()[:len(errMsg)]) } vals2, err := sm.LoadValidators(stateDB, 2) require.NoError(t, err) vals2.Validators = append(vals2.Validators, types.NewValidator(pubKey, 1000)) valKey := []byte("validatorsKey:2") protoVals, err := vals2.ToProto() require.NoError(t, err) valInfo := &protostate.ValidatorsInfo{ LastHeightChanged: 2, ValidatorSet: protoVals, } bz, err := valInfo.Marshal() require.NoError(t, err) stateDB.Set(valKey, bz) ev = types.PhantomValidatorEvidence{ Vote: vote2, LastHeightValidatorWasInSet: 2, } err = ev.ValidateBasic() assert.NoError(t, err) err = sm.VerifyEvidence(stateDB, state, ev, nil) if !assert.NoError(t, err) { t.Log(err) } } func makeVote(height int64, round, index int32, addr bytes.HexBytes, blockID types.BlockID) *types.Vote { return &types.Vote{ Type: tmproto.SignedMsgType(2), Height: height, Round: round, BlockID: blockID, Timestamp: time.Now(), ValidatorAddress: addr, ValidatorIndex: index, } }