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/evidence/pool.go b/evidence/pool.go index dfeb7a717..6489dc758 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, @@ -486,7 +489,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 +505,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 365a3be88..961de6e4a 100644 --- a/evidence/pool_test.go +++ b/evidence/pool_test.go @@ -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(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(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(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()) } @@ -311,10 +318,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) 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..fe63730db --- /dev/null +++ b/internal/test/block.go @@ -0,0 +1,92 @@ +package test + +import ( + "testing" + "time" + + "github.com/stretchr/testify/require" + + "github.com/tendermint/tendermint/crypto" + "github.com/tendermint/tendermint/crypto/tmhash" + "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 +} diff --git a/internal/test/commit.go b/internal/test/commit.go new file mode 100644 index 000000000..4f933aca5 --- /dev/null +++ b/internal/test/commit.go @@ -0,0 +1,40 @@ +package test + +import ( + "context" + "time" + + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" + "github.com/tendermint/tendermint/types" +) + +func MakeCommit(ctx context.Context, blockID types.BlockID, height int64, round int32, 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: height, + Round: round, + 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 +} 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..66323894f --- /dev/null +++ b/internal/test/genesis.go @@ -0,0 +1,34 @@ +package test + +import ( + "time" + + cfg "github.com/tendermint/tendermint/config" + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" + "github.com/tendermint/tendermint/types" +) + +func GenesisDoc( + config *cfg.Config, + time time.Time, + validators []*types.Validator, + consensusParams *tmproto.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/internal/test/tx.go b/internal/test/tx.go new file mode 100644 index 000000000..c61d0cfe0 --- /dev/null +++ b/internal/test/tx.go @@ -0,0 +1,11 @@ +package test + +import "github.com/tendermint/tendermint/types" + +func MakeNTxs(height, n int64) []types.Tx { + txs := make([]types.Tx, n) + for i := range txs { + txs[i] = types.Tx([]byte{byte(height), byte(i / 256), byte(i % 256)}) + } + return txs +} 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..e2a8f15fa --- /dev/null +++ b/internal/test/vote.go @@ -0,0 +1,44 @@ +package test + +import ( + "context" + "time" + + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" + "github.com/tendermint/tendermint/types" +) + +func MakeVote( + ctx context.Context, + 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 e60897cb0..58b96ed13 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 8b72dc952..7e5cf2124 100644 --- a/state/execution_test.go +++ b/state/execution_test.go @@ -153,7 +153,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{ diff --git a/state/validation_test.go b/state/validation_test.go index afd47a650..ca7356cb9 100644 --- a/state/validation_test.go +++ b/state/validation_test.go @@ -243,8 +243,9 @@ 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())) } @@ -263,8 +264,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/test/e2e/networks/ci.toml b/test/e2e/networks/ci.toml index 1fff574ca..fad578bf7 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" } [validators] @@ -27,11 +28,7 @@ validator05 = 50 [node.seed01] mode = "seed" -seeds = ["seed02"] - -[node.seed02] -mode = "seed" -seeds = ["seed01"] +perturb = ["restart"] [node.validator01] seeds = ["seed01"] @@ -39,7 +36,7 @@ snapshot_interval = 5 perturb = ["disconnect"] [node.validator02] -seeds = ["seed02"] +seeds = ["seed01"] database = "boltdb" abci_protocol = "tcp" privval_protocol = "tcp" @@ -53,7 +50,7 @@ database = "badgerdb" #abci_protocol = "grpc" privval_protocol = "unix" persist_interval = 3 -retain_blocks = 3 +retain_blocks = 10 perturb = ["kill"] [node.validator04] @@ -64,7 +61,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" @@ -78,7 +75,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 44c8e9118..5f00682d9 100644 --- a/test/e2e/pkg/manifest.go +++ b/test/e2e/pkg/manifest.go @@ -51,6 +51,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 1123c26a9..fb17acb46 100644 --- a/test/e2e/pkg/testnet.go +++ b/test/e2e/pkg/testnet.go @@ -11,6 +11,7 @@ import ( "sort" "strconv" "strings" + "time" "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto/ed25519" @@ -45,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. @@ -59,6 +63,7 @@ type Testnet struct { ValidatorUpdates map[int64]map[*Node]int64 Nodes []*Node KeyType string + Evidence int ABCIProtocol string } @@ -122,6 +127,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, } if len(manifest.KeyType) != 0 { @@ -328,6 +334,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..f0a3838e9 --- /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.MakeCommit(ctx, blockID, forgedHeight, 0, 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(ctx, privVal, chainID, valIdx, height, 0, 2, makeRandomBlockID(), time) + if err != nil { + return nil, err + } + voteB, err := test.MakeVote(ctx, 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 2355cb0f1..2f477759b 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 { @@ -127,7 +129,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 := MakeBlock(h, []Tx{Tx("Hello World")}, commit, evList).MakePartSet(512) @@ -144,7 +147,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) @@ -635,7 +639,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() @@ -699,7 +704,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 35acaaed1..b6126bdc5 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, errors.New("validator not in validator set") } 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 @@ -554,23 +559,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 946373aad..097ea85aa 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()) }