From b37f062619d641077495678c5ca1153db112878f Mon Sep 17 00:00:00 2001 From: Callum Waters Date: Mon, 22 Aug 2022 13:33:47 +0200 Subject: [PATCH] e2e: add evidence tests (#9292) --- blockchain/reactor_test.go | 3 +- consensus/reactor_test.go | 5 +- consensus/replay_test.go | 4 +- evidence/pool.go | 16 +- evidence/pool_test.go | 39 ++-- evidence/reactor_test.go | 13 +- evidence/verify_test.go | 9 +- internal/test/block.go | 123 ++++++++++ internal/test/commit.go | 90 ++++++++ internal/test/doc.go | 6 + internal/test/factory_test.go | 15 ++ internal/test/genesis.go | 33 +++ {test/factory => internal/test}/tx.go | 2 +- internal/test/validator.go | 41 ++++ internal/test/vote.go | 42 ++++ node/node_test.go | 3 +- rpc/client/evidence_test.go | 4 +- state/execution_test.go | 35 +-- state/helpers_test.go | 21 +- state/state.go | 6 +- state/state_test.go | 23 +- state/test/factory/block.go | 75 ------ state/validation_test.go | 28 ++- store/store_test.go | 12 +- test/e2e/networks/ci.toml | 15 +- test/e2e/pkg/manifest.go | 4 + test/e2e/pkg/testnet.go | 9 + test/e2e/runner/evidence.go | 320 ++++++++++++++++++++++++++ test/e2e/runner/main.go | 41 +++- test/e2e/tests/evidence_test.go | 22 ++ types/block_test.go | 18 +- types/event_bus_test.go | 3 +- types/evidence.go | 36 ++- types/evidence_test.go | 9 +- 34 files changed, 925 insertions(+), 200 deletions(-) create mode 100644 internal/test/block.go create mode 100644 internal/test/commit.go create mode 100644 internal/test/doc.go create mode 100644 internal/test/factory_test.go create mode 100644 internal/test/genesis.go rename {test/factory => internal/test}/tx.go (93%) create mode 100644 internal/test/validator.go create mode 100644 internal/test/vote.go delete mode 100644 state/test/factory/block.go create mode 100644 test/e2e/runner/evidence.go create mode 100644 test/e2e/tests/evidence_test.go diff --git a/blockchain/reactor_test.go b/blockchain/reactor_test.go index e23dbdd35..b041a4130 100644 --- a/blockchain/reactor_test.go +++ b/blockchain/reactor_test.go @@ -20,7 +20,6 @@ import ( "github.com/tendermint/tendermint/p2p" "github.com/tendermint/tendermint/proxy" sm "github.com/tendermint/tendermint/state" - sf "github.com/tendermint/tendermint/state/test/factory" "github.com/tendermint/tendermint/store" "github.com/tendermint/tendermint/types" tmtime "github.com/tendermint/tendermint/types/time" @@ -127,7 +126,7 @@ func newReactor( lastBlockMeta.BlockID, []types.CommitSig{vote.CommitSig()}) } - thisBlock := sf.MakeBlock(state, blockHeight, lastCommit) + thisBlock := state.MakeBlock(blockHeight, nil, lastCommit, nil, state.Validators.Proposer.Address) thisParts, err := thisBlock.MakePartSet(types.BlockPartSizeBytes) require.NoError(t, err) diff --git a/consensus/reactor_test.go b/consensus/reactor_test.go index 1997bcbfa..51fd3d223 100644 --- a/consensus/reactor_test.go +++ b/consensus/reactor_test.go @@ -188,7 +188,8 @@ func TestReactorWithEvidence(t *testing.T) { // mock the evidence pool // everyone includes evidence of another double signing vIdx := (i + 1) % nValidators - ev := types.NewMockDuplicateVoteEvidenceWithValidator(1, defaultTestTime, privVals[vIdx], config.ChainID()) + ev, err := types.NewMockDuplicateVoteEvidenceWithValidator(1, defaultTestTime, privVals[vIdx], config.ChainID()) + require.NoError(t, err) evpool := &statemocks.EvidencePool{} evpool.On("CheckEvidence", mock.AnythingOfType("types.EvidenceList")).Return(nil) evpool.On("PendingEvidence", mock.AnythingOfType("int64")).Return([]types.Evidence{ @@ -205,7 +206,7 @@ func TestReactorWithEvidence(t *testing.T) { eventBus := types.NewEventBus() eventBus.SetLogger(log.TestingLogger().With("module", "events")) - err := eventBus.Start() + err = eventBus.Start() require.NoError(t, err) cs.SetEventBus(eventBus) diff --git a/consensus/replay_test.go b/consensus/replay_test.go index f9b44cbf8..97a45951e 100644 --- a/consensus/replay_test.go +++ b/consensus/replay_test.go @@ -22,6 +22,7 @@ import ( cfg "github.com/tendermint/tendermint/config" "github.com/tendermint/tendermint/crypto" cryptoenc "github.com/tendermint/tendermint/crypto/encoding" + "github.com/tendermint/tendermint/internal/test" "github.com/tendermint/tendermint/libs/log" tmrand "github.com/tendermint/tendermint/libs/rand" mempl "github.com/tendermint/tendermint/mempool" @@ -30,7 +31,6 @@ import ( tmproto "github.com/tendermint/tendermint/proto/tendermint/types" "github.com/tendermint/tendermint/proxy" sm "github.com/tendermint/tendermint/state" - sf "github.com/tendermint/tendermint/state/test/factory" "github.com/tendermint/tendermint/types" ) @@ -904,7 +904,7 @@ func TestHandshakePanicsIfAppReturnsWrongAppHash(t *testing.T) { genDoc, _ := sm.MakeGenesisDocFromFile(config.GenesisFile()) state.LastValidators = state.Validators.Copy() // mode = 0 for committing all the blocks - blocks, err := sf.MakeBlocks(3, &state, privVal) + blocks, err := test.MakeBlocks(3, state, []types.PrivValidator{privVal}) require.NoError(t, err) store.chain = blocks diff --git a/evidence/pool.go b/evidence/pool.go index dfeb7a717..4d82de971 100644 --- a/evidence/pool.go +++ b/evidence/pool.go @@ -463,10 +463,13 @@ func (evpool *Pool) processConsensusBuffer(state sm.State) { // Check the height of the conflicting votes and fetch the corresponding time and validator set // to produce the valid evidence - var dve *types.DuplicateVoteEvidence + var ( + dve *types.DuplicateVoteEvidence + err error + ) switch { case voteSet.VoteA.Height == state.LastBlockHeight: - dve = types.NewDuplicateVoteEvidence( + dve, err = types.NewDuplicateVoteEvidence( voteSet.VoteA, voteSet.VoteB, state.LastBlockTime, @@ -474,7 +477,8 @@ func (evpool *Pool) processConsensusBuffer(state sm.State) { ) case voteSet.VoteA.Height < state.LastBlockHeight: - valSet, err := evpool.stateDB.LoadValidators(voteSet.VoteA.Height) + var valSet *types.ValidatorSet + valSet, err = evpool.stateDB.LoadValidators(voteSet.VoteA.Height) if err != nil { evpool.logger.Error("failed to load validator set for conflicting votes", "height", voteSet.VoteA.Height, "err", err, @@ -486,7 +490,7 @@ func (evpool *Pool) processConsensusBuffer(state sm.State) { evpool.logger.Error("failed to load block time for conflicting votes", "height", voteSet.VoteA.Height) continue } - dve = types.NewDuplicateVoteEvidence( + dve, err = types.NewDuplicateVoteEvidence( voteSet.VoteA, voteSet.VoteB, blockMeta.Header.Time, @@ -502,6 +506,10 @@ func (evpool *Pool) processConsensusBuffer(state sm.State) { "state.LastBlockHeight", state.LastBlockHeight) continue } + if err != nil { + evpool.logger.Error("error in generating evidence from votes", "err", err) + continue + } // check if we already have this evidence if evpool.isPending(dve) { diff --git a/evidence/pool_test.go b/evidence/pool_test.go index f6c9ec9e6..94d86f271 100644 --- a/evidence/pool_test.go +++ b/evidence/pool_test.go @@ -13,11 +13,11 @@ import ( "github.com/tendermint/tendermint/evidence" "github.com/tendermint/tendermint/evidence/mocks" + "github.com/tendermint/tendermint/internal/test" "github.com/tendermint/tendermint/libs/log" tmversion "github.com/tendermint/tendermint/proto/tendermint/version" sm "github.com/tendermint/tendermint/state" smmocks "github.com/tendermint/tendermint/state/mocks" - sf "github.com/tendermint/tendermint/state/test/factory" "github.com/tendermint/tendermint/store" "github.com/tendermint/tendermint/types" "github.com/tendermint/tendermint/version" @@ -61,7 +61,8 @@ func TestEvidencePoolBasic(t *testing.T) { assert.Equal(t, 0, len(evs)) assert.Zero(t, size) - ev := types.NewMockDuplicateVoteEvidenceWithValidator(height, defaultEvidenceTime, privVals[0], evidenceChainID) + ev, err := types.NewMockDuplicateVoteEvidenceWithValidator(height, defaultEvidenceTime, privVals[0], evidenceChainID) + require.NoError(t, err) // good evidence evAdded := make(chan struct{}) @@ -133,8 +134,9 @@ func TestAddExpiredEvidence(t *testing.T) { for _, tc := range testCases { tc := tc t.Run(tc.evDescription, func(t *testing.T) { - ev := types.NewMockDuplicateVoteEvidenceWithValidator(tc.evHeight, tc.evTime, val, evidenceChainID) - err := pool.AddEvidence(ev) + ev, err := types.NewMockDuplicateVoteEvidenceWithValidator(tc.evHeight, tc.evTime, val, evidenceChainID) + require.NoError(t, err) + err = pool.AddEvidence(ev) if tc.expErr { assert.Error(t, err) } else { @@ -149,7 +151,8 @@ func TestReportConflictingVotes(t *testing.T) { pool, pv := defaultTestPool(t, height) val := types.NewValidator(pv.PrivKey.PubKey(), 10) - ev := types.NewMockDuplicateVoteEvidenceWithValidator(height+1, defaultEvidenceTime, pv, evidenceChainID) + ev, err := types.NewMockDuplicateVoteEvidenceWithValidator(height+1, defaultEvidenceTime, pv, evidenceChainID) + require.NoError(t, err) pool.ReportConflictingVotes(ev.VoteA, ev.VoteB) @@ -185,12 +188,14 @@ func TestEvidencePoolUpdate(t *testing.T) { state := pool.State() // create new block (no need to save it to blockStore) - prunedEv := types.NewMockDuplicateVoteEvidenceWithValidator(1, defaultEvidenceTime.Add(1*time.Minute), + prunedEv, err := types.NewMockDuplicateVoteEvidenceWithValidator(1, defaultEvidenceTime.Add(1*time.Minute), val, evidenceChainID) - err := pool.AddEvidence(prunedEv) require.NoError(t, err) - ev := types.NewMockDuplicateVoteEvidenceWithValidator(height, defaultEvidenceTime.Add(21*time.Minute), + err = pool.AddEvidence(prunedEv) + require.NoError(t, err) + ev, err := types.NewMockDuplicateVoteEvidenceWithValidator(height, defaultEvidenceTime.Add(21*time.Minute), val, evidenceChainID) + require.NoError(t, err) lastCommit := makeCommit(height, val.PrivKey.PubKey().Address()) block := types.MakeBlock(height+1, []types.Tx{}, lastCommit, []types.Evidence{ev}) // update state (partially) @@ -215,9 +220,10 @@ func TestEvidencePoolUpdate(t *testing.T) { func TestVerifyPendingEvidencePasses(t *testing.T) { var height int64 = 1 pool, val := defaultTestPool(t, height) - ev := types.NewMockDuplicateVoteEvidenceWithValidator(height, defaultEvidenceTime.Add(1*time.Minute), + ev, err := types.NewMockDuplicateVoteEvidenceWithValidator(height, defaultEvidenceTime.Add(1*time.Minute), val, evidenceChainID) - err := pool.AddEvidence(ev) + require.NoError(t, err) + err = pool.AddEvidence(ev) require.NoError(t, err) err = pool.CheckEvidence(types.EvidenceList{ev}) @@ -227,9 +233,10 @@ func TestVerifyPendingEvidencePasses(t *testing.T) { func TestVerifyDuplicatedEvidenceFails(t *testing.T) { var height int64 = 1 pool, val := defaultTestPool(t, height) - ev := types.NewMockDuplicateVoteEvidenceWithValidator(height, defaultEvidenceTime.Add(1*time.Minute), + ev, err := types.NewMockDuplicateVoteEvidenceWithValidator(height, defaultEvidenceTime.Add(1*time.Minute), val, evidenceChainID) - err := pool.CheckEvidence(types.EvidenceList{ev, ev}) + require.NoError(t, err) + err = pool.CheckEvidence(types.EvidenceList{ev, ev}) if assert.Error(t, err) { assert.Equal(t, "duplicate evidence", err.(*types.ErrInvalidEvidence).Reason.Error()) } @@ -312,10 +319,12 @@ func TestRecoverPendingEvidence(t *testing.T) { pool, err := evidence.NewPool(evidenceDB, stateStore, blockStore) require.NoError(t, err) pool.SetLogger(log.TestingLogger()) - goodEvidence := types.NewMockDuplicateVoteEvidenceWithValidator(height, + goodEvidence, err := types.NewMockDuplicateVoteEvidenceWithValidator(height, defaultEvidenceTime.Add(10*time.Minute), val, evidenceChainID) - expiredEvidence := types.NewMockDuplicateVoteEvidenceWithValidator(int64(1), + require.NoError(t, err) + expiredEvidence, err := types.NewMockDuplicateVoteEvidenceWithValidator(int64(1), defaultEvidenceTime.Add(1*time.Minute), val, evidenceChainID) + require.NoError(t, err) err = pool.AddEvidence(goodEvidence) require.NoError(t, err) err = pool.AddEvidence(expiredEvidence) @@ -404,7 +413,7 @@ func initializeBlockStore(db dbm.DB, state sm.State, valAddr []byte) (*store.Blo for i := int64(1); i <= state.LastBlockHeight; i++ { lastCommit := makeCommit(i-1, valAddr) - block := sf.MakeBlock(state, i, lastCommit) + block := state.MakeBlock(i, test.MakeNTxs(i, 1), lastCommit, nil, state.Validators.Proposer.Address) block.Header.Time = defaultEvidenceTime.Add(time.Duration(i) * time.Minute) block.Header.Version = tmversion.Consensus{Block: version.BlockProtocol, App: 1} const parts = 1 diff --git a/evidence/reactor_test.go b/evidence/reactor_test.go index c0a22be26..4f8e73261 100644 --- a/evidence/reactor_test.go +++ b/evidence/reactor_test.go @@ -152,9 +152,10 @@ func TestReactorsGossipNoCommittedEvidence(t *testing.T) { // the first reactor receives three more evidence evList = make([]types.Evidence, 3) for i := 0; i < 3; i++ { - ev := types.NewMockDuplicateVoteEvidenceWithValidator(height-3+int64(i), + ev, err := types.NewMockDuplicateVoteEvidenceWithValidator(height-3+int64(i), time.Date(2019, 1, 1, 0, 0, 0, 0, time.UTC), val, state.ChainID) - err := pools[0].AddEvidence(ev) + require.NoError(t, err) + err = pools[0].AddEvidence(ev) require.NoError(t, err) evList[i] = ev } @@ -327,9 +328,10 @@ func _waitForEvidence( func sendEvidence(t *testing.T, evpool *evidence.Pool, val types.PrivValidator, n int) types.EvidenceList { evList := make([]types.Evidence, n) for i := 0; i < n; i++ { - ev := types.NewMockDuplicateVoteEvidenceWithValidator(int64(i+1), + ev, err := types.NewMockDuplicateVoteEvidenceWithValidator(int64(i+1), time.Date(2019, 1, 1, 0, 0, 0, 0, time.UTC), val, evidenceChainID) - err := evpool.AddEvidence(ev) + require.NoError(t, err) + err = evpool.AddEvidence(ev) require.NoError(t, err) evList[i] = ev } @@ -377,12 +379,13 @@ func TestEvidenceVectors(t *testing.T) { valSet := types.NewValidatorSet([]*types.Validator{val}) - dupl := types.NewDuplicateVoteEvidence( + dupl, err := types.NewDuplicateVoteEvidence( exampleVote(1), exampleVote(2), defaultEvidenceTime, valSet, ) + require.NoError(t, err) testCases := []struct { testName string diff --git a/evidence/verify_test.go b/evidence/verify_test.go index 7825f9d43..f8021f436 100644 --- a/evidence/verify_test.go +++ b/evidence/verify_test.go @@ -412,11 +412,14 @@ func TestVerifyDuplicateVoteEvidence(t *testing.T) { } // create good evidence and correct validator power - goodEv := types.NewMockDuplicateVoteEvidenceWithValidator(10, defaultEvidenceTime, val, chainID) + goodEv, err := types.NewMockDuplicateVoteEvidenceWithValidator(10, defaultEvidenceTime, val, chainID) + require.NoError(t, err) goodEv.ValidatorPower = 1 goodEv.TotalVotingPower = 1 - badEv := types.NewMockDuplicateVoteEvidenceWithValidator(10, defaultEvidenceTime, val, chainID) - badTimeEv := types.NewMockDuplicateVoteEvidenceWithValidator(10, defaultEvidenceTime.Add(1*time.Minute), val, chainID) + badEv, err := types.NewMockDuplicateVoteEvidenceWithValidator(10, defaultEvidenceTime, val, chainID) + require.NoError(t, err) + badTimeEv, err := types.NewMockDuplicateVoteEvidenceWithValidator(10, defaultEvidenceTime.Add(1*time.Minute), val, chainID) + require.NoError(t, err) badTimeEv.ValidatorPower = 1 badTimeEv.TotalVotingPower = 1 state := sm.State{ diff --git a/internal/test/block.go b/internal/test/block.go new file mode 100644 index 000000000..70e4d607e --- /dev/null +++ b/internal/test/block.go @@ -0,0 +1,123 @@ +package test + +import ( + "testing" + "time" + + "github.com/stretchr/testify/require" + + "github.com/tendermint/tendermint/crypto" + "github.com/tendermint/tendermint/crypto/tmhash" + sm "github.com/tendermint/tendermint/state" + "github.com/tendermint/tendermint/types" + "github.com/tendermint/tendermint/version" +) + +const ( + DefaultTestChainID = "test-chain" +) + +var ( + DefaultTestTime = time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC) +) + +func RandomAddress() []byte { + return crypto.CRandBytes(crypto.AddressSize) +} + +func RandomHash() []byte { + return crypto.CRandBytes(tmhash.Size) +} + +func MakeBlockID() types.BlockID { + return MakeBlockIDWithHash(RandomHash()) +} + +func MakeBlockIDWithHash(hash []byte) types.BlockID { + return types.BlockID{ + Hash: hash, + PartSetHeader: types.PartSetHeader{ + Total: 100, + Hash: RandomHash(), + }, + } +} + +// MakeHeader fills the rest of the contents of the header such that it passes +// validate basic +func MakeHeader(t *testing.T, h *types.Header) *types.Header { + t.Helper() + if h.Version.Block == 0 { + h.Version.Block = version.BlockProtocol + } + if h.Height == 0 { + h.Height = 1 + } + if h.LastBlockID.IsZero() { + h.LastBlockID = MakeBlockID() + } + if h.ChainID == "" { + h.ChainID = DefaultTestChainID + } + if len(h.LastCommitHash) == 0 { + h.LastCommitHash = RandomHash() + } + if len(h.DataHash) == 0 { + h.DataHash = RandomHash() + } + if len(h.ValidatorsHash) == 0 { + h.ValidatorsHash = RandomHash() + } + if len(h.NextValidatorsHash) == 0 { + h.NextValidatorsHash = RandomHash() + } + if len(h.ConsensusHash) == 0 { + h.ConsensusHash = RandomHash() + } + if len(h.AppHash) == 0 { + h.AppHash = RandomHash() + } + if len(h.LastResultsHash) == 0 { + h.LastResultsHash = RandomHash() + } + if len(h.EvidenceHash) == 0 { + h.EvidenceHash = RandomHash() + } + if len(h.ProposerAddress) == 0 { + h.ProposerAddress = RandomAddress() + } + + require.NoError(t, h.ValidateBasic()) + + return h +} + +func MakeBlock(state sm.State) *types.Block { + return state.MakeBlock(state.LastBlockHeight+1, MakeNTxs(state.LastBlockHeight+1, 10), new(types.Commit), nil, state.NextValidators.Proposer.Address) +} + +func MakeBlocks(n int, state sm.State, privVals []types.PrivValidator) ([]*types.Block, error) { + blockID := MakeBlockID() + blocks := make([]*types.Block, n) + + for i := 0; i < n; i++ { + height := state.LastBlockHeight + 1 + int64(i) + lastCommit, err := MakeCommit(blockID, height-1, 0, state.LastValidators, privVals, state.ChainID, state.LastBlockTime) + if err != nil { + return nil, err + } + block := state.MakeBlock(height, MakeNTxs(height, 10), lastCommit, nil, state.LastValidators.Proposer.Address) + blocks[i] = block + state.LastBlockID = blockID + state.LastBlockHeight = height + state.LastBlockTime = state.LastBlockTime.Add(1 * time.Second) + state.LastValidators = state.Validators.Copy() + state.Validators = state.NextValidators.Copy() + state.NextValidators = state.NextValidators.CopyIncrementProposerPriority(1) + state.AppHash = RandomHash() + + blockID = MakeBlockIDWithHash(block.Hash()) + } + + return blocks, nil +} diff --git a/internal/test/commit.go b/internal/test/commit.go new file mode 100644 index 000000000..fd3a8ac5a --- /dev/null +++ b/internal/test/commit.go @@ -0,0 +1,90 @@ +package test + +import ( + "fmt" + "time" + + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" + sm "github.com/tendermint/tendermint/state" + "github.com/tendermint/tendermint/types" +) + +func MakeCommitFromVoteSet(blockID types.BlockID, voteSet *types.VoteSet, validators []types.PrivValidator, now time.Time) (*types.Commit, error) { + // all sign + for i := 0; i < len(validators); i++ { + pubKey, err := validators[i].GetPubKey() + if err != nil { + return nil, err + } + vote := &types.Vote{ + ValidatorAddress: pubKey.Address(), + ValidatorIndex: int32(i), + Height: voteSet.GetHeight(), + Round: voteSet.GetRound(), + Type: tmproto.PrecommitType, + BlockID: blockID, + Timestamp: now, + } + + v := vote.ToProto() + + if err := validators[i].SignVote(voteSet.ChainID(), v); err != nil { + return nil, err + } + vote.Signature = v.Signature + if _, err := voteSet.AddVote(vote); err != nil { + return nil, err + } + } + + return voteSet.MakeCommit(), nil +} + +func MakeVoteSet(lastState sm.State, round int32) *types.VoteSet { + return types.NewVoteSet(lastState.ChainID, lastState.LastBlockHeight+1, round, tmproto.PrecommitType, lastState.NextValidators) +} + +func MakeCommit(blockID types.BlockID, height int64, round int32, valSet *types.ValidatorSet, privVals []types.PrivValidator, chainID string, now time.Time) (*types.Commit, error) { + sigs := make([]types.CommitSig, len(valSet.Validators)) + for i := 0; i < len(valSet.Validators); i++ { + sigs[i] = types.NewCommitSigAbsent() + } + + for _, privVal := range privVals { + pk, err := privVal.GetPubKey() + if err != nil { + return nil, err + } + addr := pk.Address() + + idx, _ := valSet.GetByAddress(addr) + if idx < 0 { + return nil, fmt.Errorf("validator with address %s not in validator set", addr) + } + + vote := &types.Vote{ + ValidatorAddress: addr, + ValidatorIndex: idx, + Height: height, + Round: round, + Type: tmproto.PrecommitType, + BlockID: blockID, + Timestamp: now, + } + + v := vote.ToProto() + + if err := privVal.SignVote(chainID, v); err != nil { + return nil, err + } + + sigs[idx] = types.CommitSig{ + BlockIDFlag: types.BlockIDFlagCommit, + ValidatorAddress: addr, + Timestamp: now, + Signature: v.Signature, + } + } + + return types.NewCommit(height, round, blockID, sigs), nil +} diff --git a/internal/test/doc.go b/internal/test/doc.go new file mode 100644 index 000000000..86438e290 --- /dev/null +++ b/internal/test/doc.go @@ -0,0 +1,6 @@ +/* +Package factory provides generation code for common structs in Tendermint. +It is used primarily for the testing of internal components such as statesync, +consensus, blocksync etc.. +*/ +package test diff --git a/internal/test/factory_test.go b/internal/test/factory_test.go new file mode 100644 index 000000000..6231cc7cc --- /dev/null +++ b/internal/test/factory_test.go @@ -0,0 +1,15 @@ +package test + +import ( + "testing" + + "github.com/stretchr/testify/require" + "github.com/tendermint/tendermint/types" +) + +func TestMakeHeader(t *testing.T) { + header := MakeHeader(t, &types.Header{}) + require.NotNil(t, header) + + require.NoError(t, header.ValidateBasic()) +} diff --git a/internal/test/genesis.go b/internal/test/genesis.go new file mode 100644 index 000000000..732adf5a3 --- /dev/null +++ b/internal/test/genesis.go @@ -0,0 +1,33 @@ +package test + +import ( + "time" + + cfg "github.com/tendermint/tendermint/config" + "github.com/tendermint/tendermint/types" +) + +func GenesisDoc( + config *cfg.Config, + time time.Time, + validators []*types.Validator, + consensusParams *types.ConsensusParams, +) *types.GenesisDoc { + + genesisValidators := make([]types.GenesisValidator, len(validators)) + + for i := range validators { + genesisValidators[i] = types.GenesisValidator{ + Power: validators[i].VotingPower, + PubKey: validators[i].PubKey, + } + } + + return &types.GenesisDoc{ + GenesisTime: time, + InitialHeight: 1, + ChainID: config.ChainID(), + Validators: genesisValidators, + ConsensusParams: consensusParams, + } +} diff --git a/test/factory/tx.go b/internal/test/tx.go similarity index 93% rename from test/factory/tx.go rename to internal/test/tx.go index 725f3c720..c61d0cfe0 100644 --- a/test/factory/tx.go +++ b/internal/test/tx.go @@ -1,4 +1,4 @@ -package factory +package test import "github.com/tendermint/tendermint/types" diff --git a/internal/test/validator.go b/internal/test/validator.go new file mode 100644 index 000000000..d0fdf8007 --- /dev/null +++ b/internal/test/validator.go @@ -0,0 +1,41 @@ +package test + +import ( + "context" + "sort" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/tendermint/tendermint/types" +) + +func Validator(ctx context.Context, votingPower int64) (*types.Validator, types.PrivValidator, error) { + privVal := types.NewMockPV() + pubKey, err := privVal.GetPubKey() + if err != nil { + return nil, nil, err + } + + val := types.NewValidator(pubKey, votingPower) + return val, privVal, nil +} + +func ValidatorSet(ctx context.Context, t *testing.T, numValidators int, votingPower int64) (*types.ValidatorSet, []types.PrivValidator) { + var ( + valz = make([]*types.Validator, numValidators) + privValidators = make([]types.PrivValidator, numValidators) + ) + t.Helper() + + for i := 0; i < numValidators; i++ { + val, privValidator, err := Validator(ctx, votingPower) + require.NoError(t, err) + valz[i] = val + privValidators[i] = privValidator + } + + sort.Sort(types.PrivValidatorsByAddress(privValidators)) + + return types.NewValidatorSet(valz), privValidators +} diff --git a/internal/test/vote.go b/internal/test/vote.go new file mode 100644 index 000000000..180cb943d --- /dev/null +++ b/internal/test/vote.go @@ -0,0 +1,42 @@ +package test + +import ( + "time" + + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" + "github.com/tendermint/tendermint/types" +) + +func MakeVote( + val types.PrivValidator, + chainID string, + valIndex int32, + height int64, + round int32, + step int, + blockID types.BlockID, + time time.Time, +) (*types.Vote, error) { + pubKey, err := val.GetPubKey() + if err != nil { + return nil, err + } + + v := &types.Vote{ + ValidatorAddress: pubKey.Address(), + ValidatorIndex: valIndex, + Height: height, + Round: round, + Type: tmproto.SignedMsgType(step), + BlockID: blockID, + Timestamp: time, + } + + vpb := v.ToProto() + if err := val.SignVote(chainID, vpb); err != nil { + return nil, err + } + + v.Signature = vpb.Signature + return v, nil +} diff --git a/node/node_test.go b/node/node_test.go index 56f8ecf68..10bd5cd53 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -277,7 +277,8 @@ func TestCreateProposalBlock(t *testing.T) { // than can fit in a block var currentBytes int64 for currentBytes <= maxEvidenceBytes { - ev := types.NewMockDuplicateVoteEvidenceWithValidator(height, time.Now(), privVals[0], "test-chain") + ev, err := types.NewMockDuplicateVoteEvidenceWithValidator(height, time.Now(), privVals[0], "test-chain") + require.NoError(t, err) currentBytes += int64(len(ev.Bytes())) evidencePool.ReportConflictingVotes(ev.VoteA, ev.VoteB) } diff --git a/rpc/client/evidence_test.go b/rpc/client/evidence_test.go index 527b8a9b5..a813d3912 100644 --- a/rpc/client/evidence_test.go +++ b/rpc/client/evidence_test.go @@ -45,7 +45,9 @@ func newEvidence(t *testing.T, val *privval.FilePV, validator := types.NewValidator(val.Key.PubKey, 10) valSet := types.NewValidatorSet([]*types.Validator{validator}) - return types.NewDuplicateVoteEvidence(vote, vote2, defaultTestTime, valSet) + ev, err := types.NewDuplicateVoteEvidence(vote, vote2, defaultTestTime, valSet) + require.NoError(t, err) + return ev } func makeEvidences( diff --git a/state/execution_test.go b/state/execution_test.go index 007489ed2..7b9d15e36 100644 --- a/state/execution_test.go +++ b/state/execution_test.go @@ -17,6 +17,7 @@ import ( "github.com/tendermint/tendermint/crypto/ed25519" cryptoenc "github.com/tendermint/tendermint/crypto/encoding" "github.com/tendermint/tendermint/crypto/tmhash" + "github.com/tendermint/tendermint/internal/test" "github.com/tendermint/tendermint/libs/log" mpmocks "github.com/tendermint/tendermint/mempool/mocks" tmversion "github.com/tendermint/tendermint/proto/tendermint/version" @@ -24,8 +25,6 @@ import ( pmocks "github.com/tendermint/tendermint/proxy/mocks" sm "github.com/tendermint/tendermint/state" "github.com/tendermint/tendermint/state/mocks" - sf "github.com/tendermint/tendermint/state/test/factory" - "github.com/tendermint/tendermint/test/factory" "github.com/tendermint/tendermint/types" tmtime "github.com/tendermint/tendermint/types/time" "github.com/tendermint/tendermint/version" @@ -60,7 +59,7 @@ func TestApplyBlock(t *testing.T) { blockExec := sm.NewBlockExecutor(stateStore, log.TestingLogger(), proxyApp.Consensus(), mp, sm.EmptyEvidencePool{}) - block := sf.MakeBlock(state, 1, new(types.Commit)) + block := makeBlock(state, 1, new(types.Commit)) bps, err := block.MakePartSet(testPartSize) require.NoError(t, err) blockID := types.BlockID{Hash: block.Hash(), PartSetHeader: bps.Header()} @@ -116,7 +115,7 @@ func TestBeginBlockValidators(t *testing.T) { lastCommit := types.NewCommit(1, 0, prevBlockID, tc.lastCommitSigs) // block for height 2 - block := sf.MakeBlock(state, 2, lastCommit) + block := makeBlock(state, 2, lastCommit) _, err = sm.ExecCommitBlock(proxyApp.Consensus(), block, log.TestingLogger(), stateStore, 1) require.Nil(t, err, tc.desc) @@ -169,7 +168,8 @@ func TestBeginBlockByzantineValidators(t *testing.T) { } // we don't need to worry about validating the evidence as long as they pass validate basic - dve := types.NewMockDuplicateVoteEvidenceWithValidator(3, defaultEvidenceTime, privVal, state.ChainID) + dve, err := types.NewMockDuplicateVoteEvidenceWithValidator(3, defaultEvidenceTime, privVal, state.ChainID) + require.NoError(t, err) dve.ValidatorPower = 1000 lcae := &types.LightClientAttackEvidence{ ConflictingBlock: &types.LightBlock{ @@ -228,7 +228,7 @@ func TestBeginBlockByzantineValidators(t *testing.T) { blockExec := sm.NewBlockExecutor(stateStore, log.TestingLogger(), proxyApp.Consensus(), mp, evpool) - block := sf.MakeBlock(state, 1, new(types.Commit)) + block := makeBlock(state, 1, new(types.Commit)) block.Evidence = types.EvidenceData{Evidence: ev} block.Header.EvidenceHash = block.Evidence.Hash() bps, err := block.MakePartSet(testPartSize) @@ -246,7 +246,7 @@ func TestBeginBlockByzantineValidators(t *testing.T) { func TestProcessProposal(t *testing.T) { const height = 2 - txs := factory.MakeNTxs(height, 10) + txs := test.MakeNTxs(height, 10) logger := log.NewNopLogger() app := abcimocks.NewBaseMock() @@ -273,17 +273,18 @@ func TestProcessProposal(t *testing.T) { sm.EmptyEvidencePool{}, ) - block0 := sf.MakeBlock(state, height-1, new(types.Commit)) + block0 := makeBlock(state, height-1, new(types.Commit)) lastCommitSig := []types.CommitSig{} partSet, err := block0.MakePartSet(types.BlockPartSizeBytes) require.NoError(t, err) blockID := types.BlockID{Hash: block0.Hash(), PartSetHeader: partSet.Header()} voteInfos := []abci.VoteInfo{} for _, privVal := range privVals { - vote, err := types.MakeVote(height, blockID, state.Validators, privVal, block0.Header.ChainID, time.Now()) - require.NoError(t, err) pk, err := privVal.GetPubKey() require.NoError(t, err) + idx, _ := state.Validators.GetByAddress(pk.Address()) + vote, err := test.MakeVote(privVal, block0.Header.ChainID, idx, height-1, 0, 2, blockID, time.Now()) + require.NoError(t, err) addr := pk.Address() voteInfos = append(voteInfos, abci.VoteInfo{ @@ -296,7 +297,7 @@ func TestProcessProposal(t *testing.T) { lastCommitSig = append(lastCommitSig, vote.CommitSig()) } - block1 := sf.MakeBlock(state, height, &types.Commit{ + block1 := makeBlock(state, height, &types.Commit{ Height: height - 1, Signatures: lastCommitSig, }) @@ -499,7 +500,7 @@ func TestEndBlockValidatorUpdates(t *testing.T) { ) require.NoError(t, err) - block := sf.MakeBlock(state, 1, new(types.Commit)) + block := makeBlock(state, 1, new(types.Commit)) bps, err := block.MakePartSet(testPartSize) require.NoError(t, err) blockID := types.BlockID{Hash: block.Hash(), PartSetHeader: bps.Header()} @@ -557,7 +558,7 @@ func TestEndBlockValidatorUpdatesResultingInEmptySet(t *testing.T) { sm.EmptyEvidencePool{}, ) - block := sf.MakeBlock(state, 1, new(types.Commit)) + block := makeBlock(state, 1, new(types.Commit)) bps, err := block.MakePartSet(testPartSize) require.NoError(t, err) blockID := types.BlockID{Hash: block.Hash(), PartSetHeader: bps.Header()} @@ -624,7 +625,7 @@ func TestPrepareProposalTxsAllIncluded(t *testing.T) { evpool := &mocks.EvidencePool{} evpool.On("PendingEvidence", mock.Anything).Return([]types.Evidence{}, int64(0)) - txs := factory.MakeNTxs(height, 10) + txs := test.MakeNTxs(height, 10) mp := &mpmocks.Mempool{} mp.On("ReapMaxBytesMaxGas", mock.Anything, mock.Anything).Return(types.Txs(txs[2:])) @@ -669,7 +670,7 @@ func TestPrepareProposalReorderTxs(t *testing.T) { evpool := &mocks.EvidencePool{} evpool.On("PendingEvidence", mock.Anything).Return([]types.Evidence{}, int64(0)) - txs := factory.MakeNTxs(height, 10) + txs := test.MakeNTxs(height, 10) mp := &mpmocks.Mempool{} mp.On("ReapMaxBytesMaxGas", mock.Anything, mock.Anything).Return(types.Txs(txs)) @@ -723,7 +724,7 @@ func TestPrepareProposalErrorOnTooManyTxs(t *testing.T) { const nValidators = 1 var bytesPerTx int64 = 3 maxDataBytes := types.MaxDataBytes(state.ConsensusParams.Block.MaxBytes, 0, nValidators) - txs := factory.MakeNTxs(height, maxDataBytes/bytesPerTx+2) // +2 so that tx don't fit + txs := test.MakeNTxs(height, maxDataBytes/bytesPerTx+2) // +2 so that tx don't fit mp := &mpmocks.Mempool{} mp.On("ReapMaxBytesMaxGas", mock.Anything, mock.Anything).Return(types.Txs(txs)) @@ -767,7 +768,7 @@ func TestPrepareProposalErrorOnPrepareProposalError(t *testing.T) { evpool := &mocks.EvidencePool{} evpool.On("PendingEvidence", mock.Anything).Return([]types.Evidence{}, int64(0)) - txs := factory.MakeNTxs(height, 10) + txs := test.MakeNTxs(height, 10) mp := &mpmocks.Mempool{} mp.On("ReapMaxBytesMaxGas", mock.Anything, mock.Anything).Return(types.Txs(txs)) diff --git a/state/helpers_test.go b/state/helpers_test.go index ec5f21a1c..38dcf7e1c 100644 --- a/state/helpers_test.go +++ b/state/helpers_test.go @@ -11,12 +11,11 @@ import ( abci "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto/ed25519" + "github.com/tendermint/tendermint/internal/test" tmstate "github.com/tendermint/tendermint/proto/tendermint/state" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" "github.com/tendermint/tendermint/proxy" sm "github.com/tendermint/tendermint/state" - sf "github.com/tendermint/tendermint/state/test/factory" - "github.com/tendermint/tendermint/test/factory" "github.com/tendermint/tendermint/types" tmtime "github.com/tendermint/tendermint/types/time" ) @@ -56,7 +55,7 @@ func makeAndCommitGoodBlock( func makeAndApplyGoodBlock(state sm.State, height int64, lastCommit *types.Commit, proposerAddr []byte, blockExec *sm.BlockExecutor, evidence []types.Evidence) (sm.State, types.BlockID, error) { - block := state.MakeBlock(height, factory.MakeNTxs(height, 10), lastCommit, evidence, proposerAddr) + block := state.MakeBlock(height, test.MakeNTxs(height, 10), lastCommit, evidence, proposerAddr) partSet, err := block.MakePartSet(types.BlockPartSizeBytes) if err != nil { return state, types.BlockID{}, err @@ -74,6 +73,16 @@ func makeAndApplyGoodBlock(state sm.State, height int64, lastCommit *types.Commi return state, blockID, nil } +func makeBlock(state sm.State, height int64, c *types.Commit) *types.Block { + return state.MakeBlock( + height, + test.MakeNTxs(state.LastBlockHeight, 10), + c, + nil, + state.Validators.GetProposer().Address, + ) +} + func makeValidCommit( height int64, blockID types.BlockID, @@ -144,7 +153,7 @@ func makeHeaderPartsResponsesValPubKeyChange( pubkey crypto.PubKey, ) (types.Header, types.BlockID, *tmstate.ABCIResponses) { - block := sf.MakeBlock(state, state.LastBlockHeight+1, new(types.Commit)) + block := makeBlock(state, state.LastBlockHeight+1, new(types.Commit)) abciResponses := &tmstate.ABCIResponses{ BeginBlock: &abci.ResponseBeginBlock{}, EndBlock: &abci.ResponseEndBlock{ValidatorUpdates: nil}, @@ -169,7 +178,7 @@ func makeHeaderPartsResponsesValPowerChange( power int64, ) (types.Header, types.BlockID, *tmstate.ABCIResponses) { - block := sf.MakeBlock(state, state.LastBlockHeight+1, new(types.Commit)) + block := makeBlock(state, state.LastBlockHeight+1, new(types.Commit)) abciResponses := &tmstate.ABCIResponses{ BeginBlock: &abci.ResponseBeginBlock{}, EndBlock: &abci.ResponseEndBlock{ValidatorUpdates: nil}, @@ -194,7 +203,7 @@ func makeHeaderPartsResponsesParams( params tmproto.ConsensusParams, ) (types.Header, types.BlockID, *tmstate.ABCIResponses) { - block := sf.MakeBlock(state, state.LastBlockHeight+1, new(types.Commit)) + block := makeBlock(state, state.LastBlockHeight+1, new(types.Commit)) abciResponses := &tmstate.ABCIResponses{ BeginBlock: &abci.ResponseBeginBlock{}, EndBlock: &abci.ResponseEndBlock{ConsensusParamUpdates: ¶ms}, diff --git a/state/state.go b/state/state.go index c955b4caf..b1c4c2926 100644 --- a/state/state.go +++ b/state/state.go @@ -234,20 +234,20 @@ func FromProto(pb *tmstate.State) (*State, error) { //nolint:golint func (state State) MakeBlock( height int64, txs []types.Tx, - commit *types.Commit, + lastCommit *types.Commit, evidence []types.Evidence, proposerAddress []byte, ) *types.Block { // Build base block with block data. - block := types.MakeBlock(height, txs, commit, evidence) + block := types.MakeBlock(height, txs, lastCommit, evidence) // Set time. var timestamp time.Time if height == state.InitialHeight { timestamp = state.LastBlockTime // genesis time } else { - timestamp = MedianTime(commit, state.LastValidators) + timestamp = MedianTime(lastCommit, state.LastValidators) } // Fill rest of header with state data. diff --git a/state/state_test.go b/state/state_test.go index f74df6eb4..42e6ff410 100644 --- a/state/state_test.go +++ b/state/state_test.go @@ -20,7 +20,6 @@ import ( tmrand "github.com/tendermint/tendermint/libs/rand" tmstate "github.com/tendermint/tendermint/proto/tendermint/state" sm "github.com/tendermint/tendermint/state" - sf "github.com/tendermint/tendermint/state/test/factory" "github.com/tendermint/tendermint/types" ) @@ -101,7 +100,7 @@ func TestABCIResponsesSaveLoad1(t *testing.T) { state.LastBlockHeight++ // Build mock responses. - block := sf.MakeBlock(state, 2, new(types.Commit)) + block := makeBlock(state, 2, new(types.Commit)) abciResponses := new(tmstate.ABCIResponses) dtxs := make([]*abci.ResponseDeliverTx, 2) @@ -443,7 +442,7 @@ func TestProposerPriorityDoesNotGetResetToZero(t *testing.T) { // NewValidatorSet calls IncrementProposerPriority but uses on a copy of val1 assert.EqualValues(t, 0, val1.ProposerPriority) - block := sf.MakeBlock(state, state.LastBlockHeight+1, new(types.Commit)) + block := makeBlock(state, state.LastBlockHeight+1, new(types.Commit)) bps, err := block.MakePartSet(testPartSize) require.NoError(t, err) blockID := types.BlockID{Hash: block.Hash(), PartSetHeader: bps.Header()} @@ -559,7 +558,7 @@ func TestProposerPriorityProposerAlternates(t *testing.T) { // we only have one validator: assert.Equal(t, val1PubKey.Address(), state.Validators.Proposer.Address) - block := sf.MakeBlock(state, state.LastBlockHeight+1, new(types.Commit)) + block := makeBlock(state, state.LastBlockHeight+1, new(types.Commit)) bps, err := block.MakePartSet(testPartSize) require.NoError(t, err) blockID := types.BlockID{Hash: block.Hash(), PartSetHeader: bps.Header()} @@ -748,7 +747,7 @@ func TestLargeGenesisValidator(t *testing.T) { validatorUpdates, err := types.PB2TM.ValidatorUpdates(abciResponses.EndBlock.ValidatorUpdates) require.NoError(t, err) - block := sf.MakeBlock(oldState, oldState.LastBlockHeight+1, new(types.Commit)) + block := makeBlock(oldState, oldState.LastBlockHeight+1, new(types.Commit)) bps, err := block.MakePartSet(testPartSize) require.NoError(t, err) blockID := types.BlockID{Hash: block.Hash(), PartSetHeader: bps.Header()} @@ -779,7 +778,7 @@ func TestLargeGenesisValidator(t *testing.T) { BeginBlock: &abci.ResponseBeginBlock{}, EndBlock: &abci.ResponseEndBlock{ValidatorUpdates: []abci.ValidatorUpdate{firstAddedVal}}, } - block := sf.MakeBlock(oldState, oldState.LastBlockHeight+1, new(types.Commit)) + block := makeBlock(oldState, oldState.LastBlockHeight+1, new(types.Commit)) bps, err := block.MakePartSet(testPartSize) require.NoError(t, err) @@ -798,7 +797,7 @@ func TestLargeGenesisValidator(t *testing.T) { validatorUpdates, err := types.PB2TM.ValidatorUpdates(abciResponses.EndBlock.ValidatorUpdates) require.NoError(t, err) - block := sf.MakeBlock(lastState, lastState.LastBlockHeight+1, new(types.Commit)) + block := makeBlock(lastState, lastState.LastBlockHeight+1, new(types.Commit)) bps, err = block.MakePartSet(testPartSize) require.NoError(t, err) @@ -835,7 +834,7 @@ func TestLargeGenesisValidator(t *testing.T) { BeginBlock: &abci.ResponseBeginBlock{}, EndBlock: &abci.ResponseEndBlock{ValidatorUpdates: []abci.ValidatorUpdate{addedVal}}, } - block := sf.MakeBlock(oldState, oldState.LastBlockHeight+1, new(types.Commit)) + block := makeBlock(oldState, oldState.LastBlockHeight+1, new(types.Commit)) bps, err := block.MakePartSet(testPartSize) require.NoError(t, err) @@ -854,7 +853,7 @@ func TestLargeGenesisValidator(t *testing.T) { EndBlock: &abci.ResponseEndBlock{ValidatorUpdates: []abci.ValidatorUpdate{removeGenesisVal}}, } - block = sf.MakeBlock(oldState, oldState.LastBlockHeight+1, new(types.Commit)) + block = makeBlock(oldState, oldState.LastBlockHeight+1, new(types.Commit)) require.NoError(t, err) bps, err = block.MakePartSet(testPartSize) @@ -880,7 +879,7 @@ func TestLargeGenesisValidator(t *testing.T) { } validatorUpdates, err = types.PB2TM.ValidatorUpdates(abciResponses.EndBlock.ValidatorUpdates) require.NoError(t, err) - block = sf.MakeBlock(curState, curState.LastBlockHeight+1, new(types.Commit)) + block = makeBlock(curState, curState.LastBlockHeight+1, new(types.Commit)) bps, err := block.MakePartSet(testPartSize) require.NoError(t, err) @@ -909,7 +908,7 @@ func TestLargeGenesisValidator(t *testing.T) { validatorUpdates, err := types.PB2TM.ValidatorUpdates(abciResponses.EndBlock.ValidatorUpdates) require.NoError(t, err) - block := sf.MakeBlock(updatedState, updatedState.LastBlockHeight+1, new(types.Commit)) + block := makeBlock(updatedState, updatedState.LastBlockHeight+1, new(types.Commit)) bps, err := block.MakePartSet(testPartSize) require.NoError(t, err) @@ -1008,7 +1007,7 @@ func TestStateMakeBlock(t *testing.T) { proposerAddress := state.Validators.GetProposer().Address stateVersion := state.Version.Consensus - block := sf.MakeBlock(state, 2, new(types.Commit)) + block := makeBlock(state, 2, new(types.Commit)) // test we set some fields assert.Equal(t, stateVersion, block.Version) diff --git a/state/test/factory/block.go b/state/test/factory/block.go deleted file mode 100644 index 42d88d428..000000000 --- a/state/test/factory/block.go +++ /dev/null @@ -1,75 +0,0 @@ -package factory - -import ( - "time" - - sm "github.com/tendermint/tendermint/state" - "github.com/tendermint/tendermint/test/factory" - "github.com/tendermint/tendermint/types" -) - -func MakeBlocks(n int, state *sm.State, privVal types.PrivValidator) ([]*types.Block, error) { - blocks := make([]*types.Block, n) - - var ( - prevBlock *types.Block - prevBlockMeta *types.BlockMeta - ) - - appHeight := byte(0x01) - for i := 0; i < n; i++ { - height := int64(i + 1) - - block, parts, err := makeBlockAndPartSet(*state, prevBlock, prevBlockMeta, privVal, height) - if err != nil { - return nil, err - } - - blocks[i] = block - - prevBlock = block - prevBlockMeta = types.NewBlockMeta(block, parts) - - // update state - state.AppHash = []byte{appHeight} - appHeight++ - state.LastBlockHeight = height - } - - return blocks, nil -} - -func MakeBlock(state sm.State, height int64, c *types.Commit) *types.Block { - return state.MakeBlock( - height, - factory.MakeNTxs(state.LastBlockHeight, 10), - c, - nil, - state.Validators.GetProposer().Address, - ) -} - -func makeBlockAndPartSet( - state sm.State, - lastBlock *types.Block, - lastBlockMeta *types.BlockMeta, - privVal types.PrivValidator, - height int64, -) (*types.Block, *types.PartSet, error) { - lastCommit := types.NewCommit(height-1, 0, types.BlockID{}, nil) - if height > 1 { - vote, _ := types.MakeVote( - lastBlock.Header.Height, - lastBlockMeta.BlockID, - state.Validators, - privVal, - lastBlock.Header.ChainID, - time.Now()) - lastCommit = types.NewCommit(vote.Height, vote.Round, - lastBlockMeta.BlockID, []types.CommitSig{vote.CommitSig()}) - } - - block := state.MakeBlock(height, []types.Tx{}, lastCommit, nil, state.Validators.GetProposer().Address) - partSet, err := block.MakePartSet(types.BlockPartSizeBytes) - return block, partSet, err -} diff --git a/state/validation_test.go b/state/validation_test.go index 77918e9b1..f36a09473 100644 --- a/state/validation_test.go +++ b/state/validation_test.go @@ -11,13 +11,12 @@ import ( abci "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/crypto/ed25519" "github.com/tendermint/tendermint/crypto/tmhash" + "github.com/tendermint/tendermint/internal/test" "github.com/tendermint/tendermint/libs/log" mpmocks "github.com/tendermint/tendermint/mempool/mocks" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" sm "github.com/tendermint/tendermint/state" "github.com/tendermint/tendermint/state/mocks" - sf "github.com/tendermint/tendermint/state/test/factory" - "github.com/tendermint/tendermint/test/factory" "github.com/tendermint/tendermint/types" tmtime "github.com/tendermint/tendermint/types/time" ) @@ -91,7 +90,7 @@ func TestValidateBlockHeader(t *testing.T) { Invalid blocks don't pass */ for _, tc := range testCases { - block := sf.MakeBlock(state, height, lastCommit) + block := makeBlock(state, height, lastCommit) tc.malleateBlock(block) err := blockExec.ValidateBlock(state, block) require.Error(t, err, tc.name) @@ -144,12 +143,15 @@ func TestValidateBlockCommit(t *testing.T) { #2589: ensure state.LastValidators.VerifyCommit fails here */ // should be height-1 instead of height - wrongHeightVote, err := types.MakeVote( - height, - state.LastBlockID, - state.Validators, + idx, _ := state.Validators.GetByAddress(proposerAddr) + wrongHeightVote, err := test.MakeVote( privVals[proposerAddr.String()], chainID, + idx, + height, + 0, + 2, + state.LastBlockID, time.Now(), ) require.NoError(t, err, "height %d", height) @@ -159,7 +161,7 @@ func TestValidateBlockCommit(t *testing.T) { state.LastBlockID, []types.CommitSig{wrongHeightVote.CommitSig()}, ) - block := sf.MakeBlock(state, height, wrongHeightCommit) + block := makeBlock(state, height, wrongHeightCommit) err = blockExec.ValidateBlock(state, block) _, isErrInvalidCommitHeight := err.(types.ErrInvalidCommitHeight) require.True(t, isErrInvalidCommitHeight, "expected ErrInvalidCommitHeight at height %d but got: %v", height, err) @@ -167,7 +169,7 @@ func TestValidateBlockCommit(t *testing.T) { /* #2589: test len(block.LastCommit.Signatures) == state.LastValidators.Size() */ - block = sf.MakeBlock(state, height, wrongSigsCommit) + block = makeBlock(state, height, wrongSigsCommit) err = blockExec.ValidateBlock(state, block) _, isErrInvalidCommitSignatures := err.(types.ErrInvalidCommitSignatures) require.True(t, isErrInvalidCommitSignatures, @@ -280,12 +282,13 @@ func TestValidateBlockEvidence(t *testing.T) { var currentBytes int64 // more bytes than the maximum allowed for evidence for currentBytes <= maxBytesEvidence { - newEv := types.NewMockDuplicateVoteEvidenceWithValidator(height, time.Now(), + newEv, err := types.NewMockDuplicateVoteEvidenceWithValidator(height, time.Now(), privVals[proposerAddr.String()], chainID) + require.NoError(t, err) evidence = append(evidence, newEv) currentBytes += int64(len(newEv.Bytes())) } - block := state.MakeBlock(height, factory.MakeNTxs(height, 10), lastCommit, evidence, proposerAddr) + block := state.MakeBlock(height, test.MakeNTxs(height, 10), lastCommit, evidence, proposerAddr) err := blockExec.ValidateBlock(state, block) if assert.Error(t, err) { @@ -301,8 +304,9 @@ func TestValidateBlockEvidence(t *testing.T) { var currentBytes int64 // precisely the amount of allowed evidence for { - newEv := types.NewMockDuplicateVoteEvidenceWithValidator(height, defaultEvidenceTime, + newEv, err := types.NewMockDuplicateVoteEvidenceWithValidator(height, defaultEvidenceTime, privVals[proposerAddr.String()], chainID) + require.NoError(t, err) currentBytes += int64(len(newEv.Bytes())) if currentBytes >= maxBytesEvidence { break diff --git a/store/store_test.go b/store/store_test.go index db6cea4c2..602c28519 100644 --- a/store/store_test.go +++ b/store/store_test.go @@ -17,12 +17,12 @@ import ( cfg "github.com/tendermint/tendermint/config" "github.com/tendermint/tendermint/crypto" + "github.com/tendermint/tendermint/internal/test" "github.com/tendermint/tendermint/libs/log" tmrand "github.com/tendermint/tendermint/libs/rand" tmstore "github.com/tendermint/tendermint/proto/tendermint/store" tmversion "github.com/tendermint/tendermint/proto/tendermint/version" sm "github.com/tendermint/tendermint/state" - "github.com/tendermint/tendermint/state/test/factory" "github.com/tendermint/tendermint/types" tmtime "github.com/tendermint/tendermint/types/time" "github.com/tendermint/tendermint/version" @@ -136,7 +136,7 @@ func TestMain(m *testing.M) { var cleanup cleanupFunc var err error state, _, cleanup = makeStateAndBlockStore(log.NewTMLogger(new(bytes.Buffer))) - block = factory.MakeBlock(state, 1, new(types.Commit)) + block = state.MakeBlock(state.LastBlockHeight+1, test.MakeNTxs(state.LastBlockHeight+1, 10), new(types.Commit), nil, state.Validators.GetProposer().Address) partSet, err = block.MakePartSet(2) if err != nil { @@ -167,7 +167,7 @@ func TestBlockStoreSaveLoadBlock(t *testing.T) { } // save a block - block := factory.MakeBlock(state, bs.Height()+1, new(types.Commit)) + block := state.MakeBlock(bs.Height()+1, nil, new(types.Commit), nil, state.Validators.GetProposer().Address) validPartSet, err := block.MakePartSet(2) require.NoError(t, err) seenCommit := makeTestCommit(10, tmtime.Now()) @@ -371,7 +371,7 @@ func TestLoadBaseMeta(t *testing.T) { bs := NewBlockStore(dbm.NewMemDB()) for h := int64(1); h <= 10; h++ { - block := factory.MakeBlock(state, h, new(types.Commit)) + block := state.MakeBlock(h, test.MakeNTxs(h, 10), new(types.Commit), nil, state.Validators.GetProposer().Address) partSet, err := block.MakePartSet(2) require.NoError(t, err) seenCommit := makeTestCommit(h, tmtime.Now()) @@ -440,7 +440,7 @@ func TestPruneBlocks(t *testing.T) { // make more than 1000 blocks, to test batch deletions for h := int64(1); h <= 1500; h++ { - block := factory.MakeBlock(state, h, new(types.Commit)) + block := state.MakeBlock(h, test.MakeNTxs(h, 10), new(types.Commit), nil, state.Validators.GetProposer().Address) partSet, err := block.MakePartSet(2) require.NoError(t, err) seenCommit := makeTestCommit(h, tmtime.Now()) @@ -550,7 +550,7 @@ func TestBlockFetchAtHeight(t *testing.T) { state, bs, cleanup := makeStateAndBlockStore(log.NewTMLogger(new(bytes.Buffer))) defer cleanup() require.Equal(t, bs.Height(), int64(0), "initially the height should be zero") - block := factory.MakeBlock(state, bs.Height()+1, new(types.Commit)) + block := state.MakeBlock(bs.Height()+1, nil, new(types.Commit), nil, state.Validators.GetProposer().Address) partSet, err := block.MakePartSet(2) require.NoError(t, err) diff --git a/test/e2e/networks/ci.toml b/test/e2e/networks/ci.toml index c40a83814..b8933d567 100644 --- a/test/e2e/networks/ci.toml +++ b/test/e2e/networks/ci.toml @@ -3,6 +3,7 @@ ipv6 = true initial_height = 1000 +evidence = 5 initial_state = { initial01 = "a", initial02 = "b", initial03 = "c" } prepare_proposal_delay = "100ms" process_proposal_delay = "100ms" @@ -30,11 +31,7 @@ validator05 = 50 [node.seed01] mode = "seed" -seeds = ["seed02"] - -[node.seed02] -mode = "seed" -seeds = ["seed01"] +perturb = ["restart"] [node.validator01] seeds = ["seed01"] @@ -42,7 +39,7 @@ snapshot_interval = 5 perturb = ["disconnect"] [node.validator02] -seeds = ["seed02"] +seeds = ["seed01"] database = "boltdb" abci_protocol = "tcp" privval_protocol = "tcp" @@ -56,7 +53,7 @@ database = "badgerdb" #abci_protocol = "grpc" privval_protocol = "unix" persist_interval = 3 -retain_blocks = 3 +retain_blocks = 10 perturb = ["kill"] [node.validator04] @@ -67,7 +64,7 @@ perturb = ["pause"] [node.validator05] start_at = 1005 # Becomes part of the validator set at 1010 -seeds = ["seed02"] +persistent_peers = ["validator01", "full01"] database = "cleveldb" fast_sync = true mempool_version = "v1" @@ -81,7 +78,7 @@ start_at = 1010 mode = "full" fast_sync = true persistent_peers = ["validator01", "validator02", "validator03", "validator04", "validator05"] -retain_blocks = 1 +retain_blocks = 10 perturb = ["restart"] [node.full02] diff --git a/test/e2e/pkg/manifest.go b/test/e2e/pkg/manifest.go index 5f2206a6a..6f49abd2b 100644 --- a/test/e2e/pkg/manifest.go +++ b/test/e2e/pkg/manifest.go @@ -52,6 +52,10 @@ type Manifest struct { // Options are ed25519 & secp256k1 KeyType string `toml:"key_type"` + // Evidence indicates the amount of evidence that will be injected into the + // testnet via the RPC endpoint of a random node. Default is 0 + Evidence int `toml:"evidence"` + // ABCIProtocol specifies the protocol used to communicate with the ABCI // application: "unix", "tcp", "grpc", or "builtin". Defaults to builtin. // builtin will build a complete Tendermint node into the application and diff --git a/test/e2e/pkg/testnet.go b/test/e2e/pkg/testnet.go index 9b9bfc801..8e71f9fda 100644 --- a/test/e2e/pkg/testnet.go +++ b/test/e2e/pkg/testnet.go @@ -46,6 +46,9 @@ const ( PerturbationKill Perturbation = "kill" PerturbationPause Perturbation = "pause" PerturbationRestart Perturbation = "restart" + + EvidenceAgeHeight int64 = 7 + EvidenceAgeTime time.Duration = 500 * time.Millisecond ) // Testnet represents a single testnet. @@ -60,6 +63,7 @@ type Testnet struct { ValidatorUpdates map[int64]map[*Node]int64 Nodes []*Node KeyType string + Evidence int ABCIProtocol string PrepareProposalDelay time.Duration ProcessProposalDelay time.Duration @@ -126,6 +130,7 @@ func LoadTestnet(file string) (*Testnet, error) { Validators: map[*Node]int64{}, ValidatorUpdates: map[int64]map[*Node]int64{}, Nodes: []*Node{}, + Evidence: manifest.Evidence, ABCIProtocol: manifest.ABCIProtocol, PrepareProposalDelay: manifest.PrepareProposalDelay, ProcessProposalDelay: manifest.ProcessProposalDelay, @@ -335,6 +340,10 @@ func (n Node) Validate(testnet Testnet) error { if n.StateSync && n.StartAt == 0 { return errors.New("state synced nodes cannot start at the initial height") } + if n.RetainBlocks != 0 && n.RetainBlocks < uint64(EvidenceAgeHeight) { + return fmt.Errorf("retain_blocks must be greater or equal to max evidence age (%d)", + EvidenceAgeHeight) + } if n.PersistInterval == 0 && n.RetainBlocks > 0 { return errors.New("persist_interval=0 requires retain_blocks=0") } diff --git a/test/e2e/runner/evidence.go b/test/e2e/runner/evidence.go new file mode 100644 index 000000000..0cd56016f --- /dev/null +++ b/test/e2e/runner/evidence.go @@ -0,0 +1,320 @@ +package main + +import ( + "bytes" + "context" + "errors" + "fmt" + "io/ioutil" + "math/rand" + "path/filepath" + "time" + + "github.com/tendermint/tendermint/crypto" + "github.com/tendermint/tendermint/crypto/tmhash" + "github.com/tendermint/tendermint/internal/test" + tmjson "github.com/tendermint/tendermint/libs/json" + "github.com/tendermint/tendermint/privval" + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" + tmversion "github.com/tendermint/tendermint/proto/tendermint/version" + e2e "github.com/tendermint/tendermint/test/e2e/pkg" + "github.com/tendermint/tendermint/types" + "github.com/tendermint/tendermint/version" +) + +// 1 in 4 evidence is light client evidence, the rest is duplicate vote evidence +const lightClientEvidenceRatio = 4 + +// InjectEvidence takes a running testnet and generates an amount of valid +// evidence and broadcasts it to a random node through the rpc endpoint `/broadcast_evidence`. +// Evidence is random and can be a mixture of LightClientAttackEvidence and +// DuplicateVoteEvidence. +func InjectEvidence(ctx context.Context, r *rand.Rand, testnet *e2e.Testnet, amount int) error { + // select a random node + var targetNode *e2e.Node + + for _, idx := range r.Perm(len(testnet.Nodes)) { + targetNode = testnet.Nodes[idx] + + if targetNode.Mode == e2e.ModeSeed || targetNode.Mode == e2e.ModeLight { + targetNode = nil + continue + } + + break + } + + if targetNode == nil { + return errors.New("could not find node to inject evidence into") + } + + logger.Info(fmt.Sprintf("Injecting evidence through %v (amount: %d)...", targetNode.Name, amount)) + + client, err := targetNode.Client() + if err != nil { + return err + } + + // request the latest block and validator set from the node + blockRes, err := client.Block(ctx, nil) + if err != nil { + return err + } + evidenceHeight := blockRes.Block.Height + waitHeight := blockRes.Block.Height + 3 + + nValidators := 100 + valRes, err := client.Validators(ctx, &evidenceHeight, nil, &nValidators) + if err != nil { + return err + } + + valSet, err := types.ValidatorSetFromExistingValidators(valRes.Validators) + if err != nil { + return err + } + + // get the private keys of all the validators in the network + privVals, err := getPrivateValidatorKeys(testnet) + if err != nil { + return err + } + + // wait for the node to reach the height above the forged height so that + // it is able to validate the evidence + _, err = waitForNode(targetNode, waitHeight, time.Minute) + if err != nil { + return err + } + + var ev types.Evidence + for i := 1; i <= amount; i++ { + if i%lightClientEvidenceRatio == 0 { + ev, err = generateLightClientAttackEvidence( + ctx, privVals, evidenceHeight, valSet, testnet.Name, blockRes.Block.Time, + ) + } else { + ev, err = generateDuplicateVoteEvidence( + ctx, privVals, evidenceHeight, valSet, testnet.Name, blockRes.Block.Time, + ) + } + if err != nil { + return err + } + + _, err := client.BroadcastEvidence(ctx, ev) + if err != nil { + return err + } + } + + // wait for the node to reach the height above the forged height so that + // it is able to validate the evidence + _, err = waitForNode(targetNode, blockRes.Block.Height+2, 30*time.Second) + if err != nil { + return err + } + + logger.Info(fmt.Sprintf("Finished sending evidence (height %d)", blockRes.Block.Height+2)) + + return nil +} + +func getPrivateValidatorKeys(testnet *e2e.Testnet) ([]types.MockPV, error) { + privVals := []types.MockPV{} + + for _, node := range testnet.Nodes { + if node.Mode == e2e.ModeValidator { + privKeyPath := filepath.Join(testnet.Dir, node.Name, PrivvalKeyFile) + privKey, err := readPrivKey(privKeyPath) + if err != nil { + return nil, err + } + // Create mock private validators from the validators private key. MockPV is + // stateless which means we can double vote and do other funky stuff + privVals = append(privVals, types.NewMockPVWithParams(privKey, false, false)) + } + } + + return privVals, nil +} + +// creates evidence of a lunatic attack. The height provided is the common height. +// The forged height happens 2 blocks later. +func generateLightClientAttackEvidence( + ctx context.Context, + privVals []types.MockPV, + height int64, + vals *types.ValidatorSet, + chainID string, + evTime time.Time, +) (*types.LightClientAttackEvidence, error) { + // forge a random header + forgedHeight := height + 2 + forgedTime := evTime.Add(1 * time.Second) + header := makeHeaderRandom(chainID, forgedHeight) + header.Time = forgedTime + + // add a new bogus validator and remove an existing one to + // vary the validator set slightly + pv, conflictingVals, err := mutateValidatorSet(ctx, privVals, vals) + if err != nil { + return nil, err + } + + header.ValidatorsHash = conflictingVals.Hash() + + // create a commit for the forged header + blockID := makeBlockID(header.Hash(), 1000, []byte("partshash")) + voteSet := types.NewVoteSet(chainID, forgedHeight, 0, tmproto.SignedMsgType(2), conflictingVals) + commit, err := test.MakeCommitFromVoteSet(blockID, voteSet, pv, forgedTime) + if err != nil { + return nil, err + } + + ev := &types.LightClientAttackEvidence{ + ConflictingBlock: &types.LightBlock{ + SignedHeader: &types.SignedHeader{ + Header: header, + Commit: commit, + }, + ValidatorSet: conflictingVals, + }, + CommonHeight: height, + TotalVotingPower: vals.TotalVotingPower(), + Timestamp: evTime, + } + ev.ByzantineValidators = ev.GetByzantineValidators(vals, &types.SignedHeader{ + Header: makeHeaderRandom(chainID, forgedHeight), + }) + return ev, nil +} + +// generateDuplicateVoteEvidence picks a random validator from the val set and +// returns duplicate vote evidence against the validator +func generateDuplicateVoteEvidence( + ctx context.Context, + privVals []types.MockPV, + height int64, + vals *types.ValidatorSet, + chainID string, + time time.Time, +) (*types.DuplicateVoteEvidence, error) { + privVal, valIdx, err := getRandomValidatorIndex(privVals, vals) + if err != nil { + return nil, err + } + voteA, err := test.MakeVote(privVal, chainID, valIdx, height, 0, 2, makeRandomBlockID(), time) + if err != nil { + return nil, err + } + voteB, err := test.MakeVote(privVal, chainID, valIdx, height, 0, 2, makeRandomBlockID(), time) + if err != nil { + return nil, err + } + ev, err := types.NewDuplicateVoteEvidence(voteA, voteB, time, vals) + if err != nil { + return nil, fmt.Errorf("could not generate evidence: %w", err) + } + + return ev, nil +} + +// getRandomValidatorIndex picks a random validator from a slice of mock PrivVals that's +// also part of the validator set, returning the PrivVal and its index in the validator set +func getRandomValidatorIndex(privVals []types.MockPV, vals *types.ValidatorSet) (types.MockPV, int32, error) { + for _, idx := range rand.Perm(len(privVals)) { + pv := privVals[idx] + valIdx, _ := vals.GetByAddress(pv.PrivKey.PubKey().Address()) + if valIdx >= 0 { + return pv, valIdx, nil + } + } + return types.MockPV{}, -1, errors.New("no private validator found in validator set") +} + +func readPrivKey(keyFilePath string) (crypto.PrivKey, error) { + keyJSONBytes, err := ioutil.ReadFile(keyFilePath) + if err != nil { + return nil, err + } + pvKey := privval.FilePVKey{} + err = tmjson.Unmarshal(keyJSONBytes, &pvKey) + if err != nil { + return nil, fmt.Errorf("error reading PrivValidator key from %v: %w", keyFilePath, err) + } + + return pvKey.PrivKey, nil +} + +func makeHeaderRandom(chainID string, height int64) *types.Header { + return &types.Header{ + Version: tmversion.Consensus{Block: version.BlockProtocol, App: 1}, + ChainID: chainID, + Height: height, + Time: time.Now(), + LastBlockID: makeBlockID([]byte("headerhash"), 1000, []byte("partshash")), + LastCommitHash: crypto.CRandBytes(tmhash.Size), + DataHash: crypto.CRandBytes(tmhash.Size), + ValidatorsHash: crypto.CRandBytes(tmhash.Size), + NextValidatorsHash: crypto.CRandBytes(tmhash.Size), + ConsensusHash: crypto.CRandBytes(tmhash.Size), + AppHash: crypto.CRandBytes(tmhash.Size), + LastResultsHash: crypto.CRandBytes(tmhash.Size), + EvidenceHash: crypto.CRandBytes(tmhash.Size), + ProposerAddress: crypto.CRandBytes(crypto.AddressSize), + } +} + +func makeRandomBlockID() types.BlockID { + return makeBlockID(crypto.CRandBytes(tmhash.Size), 100, crypto.CRandBytes(tmhash.Size)) +} + +func makeBlockID(hash []byte, partSetSize uint32, partSetHash []byte) types.BlockID { + var ( + h = make([]byte, tmhash.Size) + psH = make([]byte, tmhash.Size) + ) + copy(h, hash) + copy(psH, partSetHash) + return types.BlockID{ + Hash: h, + PartSetHeader: types.PartSetHeader{ + Total: partSetSize, + Hash: psH, + }, + } +} + +func mutateValidatorSet(ctx context.Context, privVals []types.MockPV, vals *types.ValidatorSet, +) ([]types.PrivValidator, *types.ValidatorSet, error) { + newVal, newPrivVal, err := test.Validator(ctx, 10) + if err != nil { + return nil, nil, err + } + + var newVals *types.ValidatorSet + if vals.Size() > 2 { + newVals = types.NewValidatorSet(append(vals.Copy().Validators[:vals.Size()-1], newVal)) + } else { + newVals = types.NewValidatorSet(append(vals.Copy().Validators, newVal)) + } + + // we need to sort the priv validators with the same index as the validator set + pv := make([]types.PrivValidator, newVals.Size()) + for idx, val := range newVals.Validators { + found := false + for _, p := range append(privVals, newPrivVal.(types.MockPV)) { + if bytes.Equal(p.PrivKey.PubKey().Address(), val.Address) { + pv[idx] = p + found = true + break + } + } + if !found { + return nil, nil, fmt.Errorf("missing priv validator for %v", val.Address) + } + } + + return pv, newVals, nil +} diff --git a/test/e2e/runner/main.go b/test/e2e/runner/main.go index 80048ee98..798bf12e4 100644 --- a/test/e2e/runner/main.go +++ b/test/e2e/runner/main.go @@ -3,6 +3,7 @@ package main import ( "context" "fmt" + "math/rand" "os" "strconv" @@ -12,9 +13,9 @@ import ( e2e "github.com/tendermint/tendermint/test/e2e/pkg" ) -var ( - logger = log.NewTMLogger(log.NewSyncWriter(os.Stdout)) -) +const randomSeed = 2308084734268 + +var logger = log.NewTMLogger(log.NewSyncWriter(os.Stdout)) func main() { NewCLI().Run() @@ -56,6 +57,8 @@ func NewCLI() *CLI { return err } + r := rand.New(rand.NewSource(randomSeed)) // nolint: gosec + chLoadResult := make(chan error) ctx, loadCancel := context.WithCancel(context.Background()) defer loadCancel() @@ -84,6 +87,15 @@ func NewCLI() *CLI { } } + if cli.testnet.Evidence > 0 { + if err := InjectEvidence(ctx, r, cli.testnet, cli.testnet.Evidence); err != nil { + return err + } + if err := Wait(cli.testnet, 5); err != nil { // ensure chain progress + return err + } + } + loadCancel() if err := <-chLoadResult; err != nil { return err @@ -175,6 +187,29 @@ func NewCLI() *CLI { }, }) + cli.root.AddCommand(&cobra.Command{ + Use: "evidence [amount]", + Args: cobra.MaximumNArgs(1), + Short: "Generates and broadcasts evidence to a random node", + RunE: func(cmd *cobra.Command, args []string) (err error) { + amount := 1 + + if len(args) == 1 { + amount, err = strconv.Atoi(args[0]) + if err != nil { + return err + } + } + + return InjectEvidence( + cmd.Context(), + rand.New(rand.NewSource(randomSeed)), // nolint: gosec + cli.testnet, + amount, + ) + }, + }) + cli.root.AddCommand(&cobra.Command{ Use: "test", Short: "Runs test cases against a running testnet", diff --git a/test/e2e/tests/evidence_test.go b/test/e2e/tests/evidence_test.go new file mode 100644 index 000000000..f7f2ede79 --- /dev/null +++ b/test/e2e/tests/evidence_test.go @@ -0,0 +1,22 @@ +package e2e_test + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +// assert that all nodes that have blocks at the height of a misbehavior has evidence +// for that misbehavior +func TestEvidence_Misbehavior(t *testing.T) { + blocks := fetchBlockChain(t) + testnet := loadTestnet(t) + seenEvidence := 0 + for _, block := range blocks { + if len(block.Evidence.Evidence) != 0 { + seenEvidence += len(block.Evidence.Evidence) + } + } + require.Equal(t, testnet.Evidence, seenEvidence, + "difference between the amount of evidence produced and committed") +} diff --git a/types/block_test.go b/types/block_test.go index 30f3bfbdb..10a81d5d7 100644 --- a/types/block_test.go +++ b/types/block_test.go @@ -41,7 +41,8 @@ func TestBlockAddEvidence(t *testing.T) { commit, err := MakeCommit(lastID, h-1, 1, voteSet, vals, time.Now()) require.NoError(t, err) - ev := NewMockDuplicateVoteEvidenceWithValidator(h, time.Now(), vals[0], "block-test-chain") + ev, err := NewMockDuplicateVoteEvidenceWithValidator(h, time.Now(), vals[0], "block-test-chain") + require.NoError(t, err) evList := []Evidence{ev} block := MakeBlock(h, txs, commit, evList) @@ -61,7 +62,8 @@ func TestBlockValidateBasic(t *testing.T) { commit, err := MakeCommit(lastID, h-1, 1, voteSet, vals, time.Now()) require.NoError(t, err) - ev := NewMockDuplicateVoteEvidenceWithValidator(h, time.Now(), vals[0], "block-test-chain") + ev, err := NewMockDuplicateVoteEvidenceWithValidator(h, time.Now(), vals[0], "block-test-chain") + require.NoError(t, err) evList := []Evidence{ev} testCases := []struct { @@ -132,7 +134,8 @@ func TestBlockMakePartSetWithEvidence(t *testing.T) { commit, err := MakeCommit(lastID, h-1, 1, voteSet, vals, time.Now()) require.NoError(t, err) - ev := NewMockDuplicateVoteEvidenceWithValidator(h, time.Now(), vals[0], "block-test-chain") + ev, err := NewMockDuplicateVoteEvidenceWithValidator(h, time.Now(), vals[0], "block-test-chain") + require.NoError(t, err) evList := []Evidence{ev} partSet, err := MakeBlock(h, []Tx{Tx("Hello World")}, commit, evList).MakePartSet(512) @@ -151,7 +154,8 @@ func TestBlockHashesTo(t *testing.T) { commit, err := MakeCommit(lastID, h-1, 1, voteSet, vals, time.Now()) require.NoError(t, err) - ev := NewMockDuplicateVoteEvidenceWithValidator(h, time.Now(), vals[0], "block-test-chain") + ev, err := NewMockDuplicateVoteEvidenceWithValidator(h, time.Now(), vals[0], "block-test-chain") + require.NoError(t, err) evList := []Evidence{ev} block := MakeBlock(h, []Tx{Tx("Hello World")}, commit, evList) @@ -642,7 +646,8 @@ func TestBlockProtoBuf(t *testing.T) { b2 := MakeBlock(h, []Tx{Tx([]byte{1})}, c1, []Evidence{}) b2.ProposerAddress = tmrand.Bytes(crypto.AddressSize) evidenceTime := time.Date(2019, 1, 1, 0, 0, 0, 0, time.UTC) - evi := NewMockDuplicateVoteEvidence(h, evidenceTime, "block-test-chain") + evi, err := NewMockDuplicateVoteEvidence(h, evidenceTime, "block-test-chain") + require.NoError(t, err) b2.Evidence = EvidenceData{Evidence: EvidenceList{evi}} b2.EvidenceHash = b2.Evidence.Hash() @@ -706,7 +711,8 @@ func TestDataProtoBuf(t *testing.T) { // TestEvidenceDataProtoBuf ensures parity in converting to and from proto. func TestEvidenceDataProtoBuf(t *testing.T) { const chainID = "mychain" - ev := NewMockDuplicateVoteEvidence(math.MaxInt64, time.Now(), chainID) + ev, err := NewMockDuplicateVoteEvidence(math.MaxInt64, time.Now(), chainID) + require.NoError(t, err) data := &EvidenceData{Evidence: EvidenceList{ev}} _ = data.ByteSize() testCases := []struct { diff --git a/types/event_bus_test.go b/types/event_bus_test.go index a0a2e2e5f..30bd6b178 100644 --- a/types/event_bus_test.go +++ b/types/event_bus_test.go @@ -285,7 +285,8 @@ func TestEventBusPublishEventNewEvidence(t *testing.T) { } }) - ev := NewMockDuplicateVoteEvidence(1, time.Now(), "test-chain-id") + ev, err := NewMockDuplicateVoteEvidence(1, time.Now(), "test-chain-id") + require.NoError(t, err) query := "tm.event='NewEvidence'" evSub, err := eventBus.Subscribe(context.Background(), "test", tmquery.MustParse(query)) diff --git a/types/evidence.go b/types/evidence.go index bd61f51c3..df158de33 100644 --- a/types/evidence.go +++ b/types/evidence.go @@ -45,15 +45,20 @@ type DuplicateVoteEvidence struct { var _ Evidence = &DuplicateVoteEvidence{} // NewDuplicateVoteEvidence creates DuplicateVoteEvidence with right ordering given -// two conflicting votes. If one of the votes is nil, evidence returned is nil as well -func NewDuplicateVoteEvidence(vote1, vote2 *Vote, blockTime time.Time, valSet *ValidatorSet) *DuplicateVoteEvidence { +// two conflicting votes. If either of the votes is nil, the val set is nil or the voter is +// not in the val set, an error is returned +func NewDuplicateVoteEvidence(vote1, vote2 *Vote, blockTime time.Time, valSet *ValidatorSet, +) (*DuplicateVoteEvidence, error) { var voteA, voteB *Vote - if vote1 == nil || vote2 == nil || valSet == nil { - return nil + if vote1 == nil || vote2 == nil { + return nil, errors.New("missing vote") + } + if valSet == nil { + return nil, errors.New("missing validator set") } idx, val := valSet.GetByAddress(vote1.ValidatorAddress) if idx == -1 { - return nil + return nil, fmt.Errorf("validator %s not in validator set", vote1.ValidatorAddress.String()) } if strings.Compare(vote1.BlockID.Key(), vote2.BlockID.Key()) == -1 { @@ -69,7 +74,7 @@ func NewDuplicateVoteEvidence(vote1, vote2 *Vote, blockTime time.Time, valSet *V TotalVotingPower: valSet.TotalVotingPower(), ValidatorPower: val.VotingPower, Timestamp: blockTime, - } + }, nil } // ABCI returns the application relevant representation of the evidence @@ -564,23 +569,32 @@ func (err *ErrEvidenceOverflow) Error() string { // unstable - use only for testing // assumes the round to be 0 and the validator index to be 0 -func NewMockDuplicateVoteEvidence(height int64, time time.Time, chainID string) *DuplicateVoteEvidence { +func NewMockDuplicateVoteEvidence(height int64, time time.Time, chainID string) (*DuplicateVoteEvidence, error) { val := NewMockPV() return NewMockDuplicateVoteEvidenceWithValidator(height, time, val, chainID) } // assumes voting power to be 10 and validator to be the only one in the set func NewMockDuplicateVoteEvidenceWithValidator(height int64, time time.Time, - pv PrivValidator, chainID string) *DuplicateVoteEvidence { - pubKey, _ := pv.GetPubKey() + pv PrivValidator, chainID string) (*DuplicateVoteEvidence, error) { + pubKey, err := pv.GetPubKey() + if err != nil { + return nil, err + } val := NewValidator(pubKey, 10) voteA := makeMockVote(height, 0, 0, pubKey.Address(), randBlockID(), time) vA := voteA.ToProto() - _ = pv.SignVote(chainID, vA) + err = pv.SignVote(chainID, vA) + if err != nil { + return nil, err + } voteA.Signature = vA.Signature voteB := makeMockVote(height, 0, 0, pubKey.Address(), randBlockID(), time) vB := voteB.ToProto() - _ = pv.SignVote(chainID, vB) + err = pv.SignVote(chainID, vB) + if err != nil { + return nil, err + } voteB.Signature = vB.Signature return NewDuplicateVoteEvidence(voteA, voteB, time, NewValidatorSet([]*Validator{val})) } diff --git a/types/evidence_test.go b/types/evidence_test.go index 959eb5086..feb0b0195 100644 --- a/types/evidence_test.go +++ b/types/evidence_test.go @@ -43,7 +43,8 @@ func randomDuplicateVoteEvidence(t *testing.T) *DuplicateVoteEvidence { func TestDuplicateVoteEvidence(t *testing.T) { const height = int64(13) - ev := NewMockDuplicateVoteEvidence(height, time.Now(), "mock-chain-id") + ev, err := NewMockDuplicateVoteEvidence(height, time.Now(), "mock-chain-id") + require.NoError(t, err) assert.Equal(t, ev.Hash(), tmhash.Sum(ev.Bytes())) assert.NotNil(t, ev.String()) assert.Equal(t, ev.Height(), height) @@ -82,7 +83,8 @@ func TestDuplicateVoteEvidenceValidation(t *testing.T) { vote1 := makeVote(t, val, chainID, math.MaxInt32, math.MaxInt64, math.MaxInt32, 0x02, blockID, defaultVoteTime) vote2 := makeVote(t, val, chainID, math.MaxInt32, math.MaxInt64, math.MaxInt32, 0x02, blockID2, defaultVoteTime) valSet := NewValidatorSet([]*Validator{val.ExtractIntoValidator(10)}) - ev := NewDuplicateVoteEvidence(vote1, vote2, defaultVoteTime, valSet) + ev, err := NewDuplicateVoteEvidence(vote1, vote2, defaultVoteTime, valSet) + require.NoError(t, err) tc.malleateEvidence(ev) assert.Equal(t, tc.expectErr, ev.ValidateBasic() != nil, "Validate Basic had an unexpected result") }) @@ -224,7 +226,8 @@ func TestLightClientAttackEvidenceValidation(t *testing.T) { } func TestMockEvidenceValidateBasic(t *testing.T) { - goodEvidence := NewMockDuplicateVoteEvidence(int64(1), time.Now(), "mock-chain-id") + goodEvidence, err := NewMockDuplicateVoteEvidence(int64(1), time.Now(), "mock-chain-id") + require.NoError(t, err) assert.Nil(t, goodEvidence.ValidateBasic()) }