evidence: introduction of LightClientAttackEvidence and refactor of evidence lifecycle (#5361)

evidence: modify evidence types (#5342)

light: detect light client attacks (#5344)

evidence: refactor evidence pool (#5345)

abci: application evidence prepared by evidence pool (#5354)
This commit is contained in:
Callum Waters
2020-09-22 10:22:54 +02:00
committed by GitHub
parent 309e29c245
commit ed002cea7e
57 changed files with 3240 additions and 1241 deletions
+11 -19
View File
@@ -131,9 +131,12 @@ func (blockExec *BlockExecutor) ApplyBlock(
return state, 0, ErrInvalidBlock(err)
}
// Update evpool with the block and state and get any byzantine validators for that block
byzVals := blockExec.evpool.ABCIEvidence(block.Height, block.Evidence.Evidence)
startTime := time.Now().UnixNano()
abciResponses, err := execBlockOnProxyApp(blockExec.logger, blockExec.proxyApp, block,
blockExec.store, state.InitialHeight)
blockExec.store, state.InitialHeight, byzVals)
endTime := time.Now().UnixNano()
blockExec.metrics.BlockProcessingTime.Observe(float64(endTime-startTime) / 1000000)
if err != nil {
@@ -175,8 +178,8 @@ func (blockExec *BlockExecutor) ApplyBlock(
return state, 0, fmt.Errorf("commit failed for application: %v", err)
}
// Update evpool with the block and state.
blockExec.evpool.Update(block, state)
// Update evpool with the latest state.
blockExec.evpool.Update(state)
fail.Fail() // XXX
@@ -258,6 +261,7 @@ func execBlockOnProxyApp(
block *types.Block,
store Store,
initialHeight int64,
byzVals []abci.Evidence,
) (*tmstate.ABCIResponses, error) {
var validTxs, invalidTxs = 0, 0
@@ -285,7 +289,7 @@ func execBlockOnProxyApp(
}
proxyAppConn.SetResponseCallback(proxyCb)
commitInfo, byzVals := getBeginBlockValidatorInfo(block, store, initialHeight)
commitInfo := getBeginBlockValidatorInfo(block, store, initialHeight)
// Begin block
var err error
@@ -325,7 +329,7 @@ func execBlockOnProxyApp(
}
func getBeginBlockValidatorInfo(block *types.Block, store Store,
initialHeight int64) (abci.LastCommitInfo, []abci.Evidence) {
initialHeight int64) abci.LastCommitInfo {
voteInfos := make([]abci.VoteInfo, block.LastCommit.Size())
// Initial block -> LastCommitInfo.Votes are empty.
// Remember that the first LastCommit is intentionally empty, so it makes
@@ -356,22 +360,10 @@ func getBeginBlockValidatorInfo(block *types.Block, store Store,
}
}
byzVals := make([]abci.Evidence, len(block.Evidence.Evidence))
for i, ev := range block.Evidence.Evidence {
// We need the validator set. We already did this in validateBlock.
// TODO: Should we instead cache the valset in the evidence itself and add
// `SetValidatorSet()` and `ToABCI` methods ?
valset, err := store.LoadValidators(ev.Height())
if err != nil {
panic(err)
}
byzVals[i] = types.TM2PB.Evidence(ev, valset)
}
return abci.LastCommitInfo{
Round: block.LastCommit.Round,
Votes: voteInfos,
}, byzVals
}
}
func validateValidatorUpdates(abciUpdates []abci.ValidatorUpdate,
@@ -533,7 +525,7 @@ func ExecCommitBlock(
store Store,
initialHeight int64,
) ([]byte, error) {
_, err := execBlockOnProxyApp(logger, appConnConsensus, block, store, initialHeight)
_, err := execBlockOnProxyApp(logger, appConnConsensus, block, store, initialHeight, []abci.Evidence{})
if err != nil {
logger.Error("Error executing block on proxy app", "height", block.Height, "err", err)
return nil, err
+40 -48
View File
@@ -6,16 +6,18 @@ import (
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
abci "github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/crypto/ed25519"
cryptoenc "github.com/tendermint/tendermint/crypto/encoding"
"github.com/tendermint/tendermint/libs/log"
"github.com/tendermint/tendermint/mempool/mock"
mmock "github.com/tendermint/tendermint/mempool/mock"
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
"github.com/tendermint/tendermint/proxy"
sm "github.com/tendermint/tendermint/state"
"github.com/tendermint/tendermint/state/mocks"
"github.com/tendermint/tendermint/types"
tmtime "github.com/tendermint/tendermint/types/time"
)
@@ -38,7 +40,7 @@ func TestApplyBlock(t *testing.T) {
stateStore := sm.NewStore(stateDB)
blockExec := sm.NewBlockExecutor(stateStore, log.TestingLogger(), proxyApp.Consensus(),
mock.Mempool{}, sm.MockEvidencePool{})
mmock.Mempool{}, sm.EmptyEvidencePool{})
block := makeBlock(state, 1)
blockID := types.BlockID{Hash: block.Hash(), PartSetHeader: block.MakePartSet(testPartSize).Header()}
@@ -123,55 +125,45 @@ func TestBeginBlockByzantineValidators(t *testing.T) {
require.Nil(t, err)
defer proxyApp.Stop() //nolint:errcheck // ignore for tests
state, stateDB, privVals := makeState(2, 12)
state, stateDB, _ := makeState(1, 1)
stateStore := sm.NewStore(stateDB)
prevHash := state.LastBlockID.Hash
prevParts := types.PartSetHeader{}
prevBlockID := types.BlockID{Hash: prevHash, PartSetHeader: prevParts}
defaultEvidenceTime := time.Date(2019, 1, 1, 0, 0, 0, 0, time.UTC)
height1, val1 := int64(8), state.Validators.Validators[0].Address
height2, val2 := int64(3), state.Validators.Validators[1].Address
ev1 := types.NewMockDuplicateVoteEvidenceWithValidator(height1, time.Now(), privVals[val1.String()], chainID)
ev2 := types.NewMockDuplicateVoteEvidenceWithValidator(height2, time.Now(), privVals[val2.String()], chainID)
now := tmtime.Now()
valSet := state.Validators
testCases := []struct {
desc string
evidence []types.Evidence
expectedByzantineValidators []abci.Evidence
}{
{"none byzantine", []types.Evidence{}, []abci.Evidence{}},
{"one byzantine", []types.Evidence{ev1}, []abci.Evidence{types.TM2PB.Evidence(ev1, valSet)}},
{"multiple byzantine", []types.Evidence{ev1, ev2}, []abci.Evidence{
types.TM2PB.Evidence(ev1, valSet),
types.TM2PB.Evidence(ev2, valSet)}},
abciEv := []abci.Evidence{
{
Type: abci.EvidenceType_DUPLICATE_VOTE,
Height: 3,
Time: defaultEvidenceTime,
Validator: types.TM2PB.Validator(state.Validators.Validators[0]),
TotalVotingPower: 33,
},
{
Type: abci.EvidenceType_LIGHT_CLIENT_ATTACK,
Height: 8,
Time: defaultEvidenceTime,
Validator: types.TM2PB.Validator(state.Validators.Validators[0]),
TotalVotingPower: 12,
},
}
var (
commitSig0 = types.NewCommitSigForBlock(
[]byte("Signature1"),
state.Validators.Validators[0].Address,
now)
commitSig1 = types.NewCommitSigForBlock(
[]byte("Signature2"),
state.Validators.Validators[1].Address,
now)
)
commitSigs := []types.CommitSig{commitSig0, commitSig1}
lastCommit := types.NewCommit(9, 0, prevBlockID, commitSigs)
for _, tc := range testCases {
evpool := &mocks.EvidencePool{}
evpool.On("ABCIEvidence", mock.AnythingOfType("int64"), mock.AnythingOfType("[]types.Evidence")).Return(abciEv)
evpool.On("Update", mock.AnythingOfType("state.State")).Return()
evpool.On("CheckEvidence", mock.AnythingOfType("types.EvidenceList")).Return(nil)
block, _ := state.MakeBlock(10, makeTxs(2), lastCommit, nil, state.Validators.GetProposer().Address)
block.Time = now
block.Evidence.Evidence = tc.evidence
_, err = sm.ExecCommitBlock(proxyApp.Consensus(), block, log.TestingLogger(), stateStore, 1)
require.Nil(t, err, tc.desc)
blockExec := sm.NewBlockExecutor(stateStore, log.TestingLogger(), proxyApp.Consensus(),
mmock.Mempool{}, evpool)
// -> app must receive an index of the byzantine validator
assert.Equal(t, tc.expectedByzantineValidators, app.ByzantineValidators, tc.desc)
}
block := makeBlock(state, 1)
blockID := types.BlockID{Hash: block.Hash(), PartSetHeader: block.MakePartSet(testPartSize).Header()}
state, retainHeight, err := blockExec.ApplyBlock(state, blockID, block)
require.Nil(t, err)
assert.EqualValues(t, retainHeight, 1)
// TODO check state and mempool
assert.Equal(t, abciEv, app.ByzantineValidators)
}
func TestValidateValidatorUpdates(t *testing.T) {
@@ -320,8 +312,8 @@ func TestEndBlockValidatorUpdates(t *testing.T) {
stateStore,
log.TestingLogger(),
proxyApp.Consensus(),
mock.Mempool{},
sm.MockEvidencePool{},
mmock.Mempool{},
sm.EmptyEvidencePool{},
)
eventBus := types.NewEventBus()
@@ -390,8 +382,8 @@ func TestEndBlockValidatorUpdatesResultingInEmptySet(t *testing.T) {
stateStore,
log.TestingLogger(),
proxyApp.Consensus(),
mock.Mempool{},
sm.MockEvidencePool{},
mmock.Mempool{},
sm.EmptyEvidencePool{},
)
block := makeBlock(state, 1)
+36 -18
View File
@@ -1,9 +1,11 @@
// Code generated by mockery v2.2.1. DO NOT EDIT.
// Code generated by mockery v2.1.0. DO NOT EDIT.
package mocks
import (
mock "github.com/stretchr/testify/mock"
abcitypes "github.com/tendermint/tendermint/abci/types"
state "github.com/tendermint/tendermint/state"
types "github.com/tendermint/tendermint/types"
@@ -14,6 +16,22 @@ type EvidencePool struct {
mock.Mock
}
// ABCIEvidence provides a mock function with given fields: _a0, _a1
func (_m *EvidencePool) ABCIEvidence(_a0 int64, _a1 []types.Evidence) []abcitypes.Evidence {
ret := _m.Called(_a0, _a1)
var r0 []abcitypes.Evidence
if rf, ok := ret.Get(0).(func(int64, []types.Evidence) []abcitypes.Evidence); ok {
r0 = rf(_a0, _a1)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]abcitypes.Evidence)
}
}
return r0
}
// AddEvidence provides a mock function with given fields: _a0
func (_m *EvidencePool) AddEvidence(_a0 types.Evidence) error {
ret := _m.Called(_a0)
@@ -28,6 +46,20 @@ func (_m *EvidencePool) AddEvidence(_a0 types.Evidence) error {
return r0
}
// CheckEvidence provides a mock function with given fields: _a0
func (_m *EvidencePool) CheckEvidence(_a0 types.EvidenceList) error {
ret := _m.Called(_a0)
var r0 error
if rf, ok := ret.Get(0).(func(types.EvidenceList) error); ok {
r0 = rf(_a0)
} else {
r0 = ret.Error(0)
}
return r0
}
// PendingEvidence provides a mock function with given fields: _a0
func (_m *EvidencePool) PendingEvidence(_a0 uint32) []types.Evidence {
ret := _m.Called(_a0)
@@ -44,21 +76,7 @@ func (_m *EvidencePool) PendingEvidence(_a0 uint32) []types.Evidence {
return r0
}
// Update provides a mock function with given fields: _a0, _a1
func (_m *EvidencePool) Update(_a0 *types.Block, _a1 state.State) {
_m.Called(_a0, _a1)
}
// Verify provides a mock function with given fields: _a0
func (_m *EvidencePool) Verify(_a0 types.Evidence) error {
ret := _m.Called(_a0)
var r0 error
if rf, ok := ret.Get(0).(func(types.Evidence) error); ok {
r0 = rf(_a0)
} else {
r0 = ret.Error(0)
}
return r0
// Update provides a mock function with given fields: _a0
func (_m *EvidencePool) Update(_a0 state.State) {
_m.Called(_a0)
}
+205
View File
@@ -0,0 +1,205 @@
// Code generated by mockery v2.1.0. DO NOT EDIT.
package mocks
import (
mock "github.com/stretchr/testify/mock"
state "github.com/tendermint/tendermint/state"
tendermintstate "github.com/tendermint/tendermint/proto/tendermint/state"
tenderminttypes "github.com/tendermint/tendermint/types"
types "github.com/tendermint/tendermint/proto/tendermint/types"
)
// Store is an autogenerated mock type for the Store type
type Store struct {
mock.Mock
}
// Bootstrap provides a mock function with given fields: _a0
func (_m *Store) Bootstrap(_a0 state.State) error {
ret := _m.Called(_a0)
var r0 error
if rf, ok := ret.Get(0).(func(state.State) error); ok {
r0 = rf(_a0)
} else {
r0 = ret.Error(0)
}
return r0
}
// Load provides a mock function with given fields:
func (_m *Store) Load() (state.State, error) {
ret := _m.Called()
var r0 state.State
if rf, ok := ret.Get(0).(func() state.State); ok {
r0 = rf()
} else {
r0 = ret.Get(0).(state.State)
}
var r1 error
if rf, ok := ret.Get(1).(func() error); ok {
r1 = rf()
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// LoadABCIResponses provides a mock function with given fields: _a0
func (_m *Store) LoadABCIResponses(_a0 int64) (*tendermintstate.ABCIResponses, error) {
ret := _m.Called(_a0)
var r0 *tendermintstate.ABCIResponses
if rf, ok := ret.Get(0).(func(int64) *tendermintstate.ABCIResponses); ok {
r0 = rf(_a0)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*tendermintstate.ABCIResponses)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(int64) error); ok {
r1 = rf(_a0)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// LoadConsensusParams provides a mock function with given fields: _a0
func (_m *Store) LoadConsensusParams(_a0 int64) (types.ConsensusParams, error) {
ret := _m.Called(_a0)
var r0 types.ConsensusParams
if rf, ok := ret.Get(0).(func(int64) types.ConsensusParams); ok {
r0 = rf(_a0)
} else {
r0 = ret.Get(0).(types.ConsensusParams)
}
var r1 error
if rf, ok := ret.Get(1).(func(int64) error); ok {
r1 = rf(_a0)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// LoadFromDBOrGenesisDoc provides a mock function with given fields: _a0
func (_m *Store) LoadFromDBOrGenesisDoc(_a0 *tenderminttypes.GenesisDoc) (state.State, error) {
ret := _m.Called(_a0)
var r0 state.State
if rf, ok := ret.Get(0).(func(*tenderminttypes.GenesisDoc) state.State); ok {
r0 = rf(_a0)
} else {
r0 = ret.Get(0).(state.State)
}
var r1 error
if rf, ok := ret.Get(1).(func(*tenderminttypes.GenesisDoc) error); ok {
r1 = rf(_a0)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// LoadFromDBOrGenesisFile provides a mock function with given fields: _a0
func (_m *Store) LoadFromDBOrGenesisFile(_a0 string) (state.State, error) {
ret := _m.Called(_a0)
var r0 state.State
if rf, ok := ret.Get(0).(func(string) state.State); ok {
r0 = rf(_a0)
} else {
r0 = ret.Get(0).(state.State)
}
var r1 error
if rf, ok := ret.Get(1).(func(string) error); ok {
r1 = rf(_a0)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// LoadValidators provides a mock function with given fields: _a0
func (_m *Store) LoadValidators(_a0 int64) (*tenderminttypes.ValidatorSet, error) {
ret := _m.Called(_a0)
var r0 *tenderminttypes.ValidatorSet
if rf, ok := ret.Get(0).(func(int64) *tenderminttypes.ValidatorSet); ok {
r0 = rf(_a0)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*tenderminttypes.ValidatorSet)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(int64) error); ok {
r1 = rf(_a0)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// PruneStates provides a mock function with given fields: _a0, _a1
func (_m *Store) PruneStates(_a0 int64, _a1 int64) error {
ret := _m.Called(_a0, _a1)
var r0 error
if rf, ok := ret.Get(0).(func(int64, int64) error); ok {
r0 = rf(_a0, _a1)
} else {
r0 = ret.Error(0)
}
return r0
}
// Save provides a mock function with given fields: _a0
func (_m *Store) Save(_a0 state.State) error {
ret := _m.Called(_a0)
var r0 error
if rf, ok := ret.Get(0).(func(state.State) error); ok {
r0 = rf(_a0)
} else {
r0 = ret.Error(0)
}
return r0
}
// SaveABCIResponses provides a mock function with given fields: _a0, _a1
func (_m *Store) SaveABCIResponses(_a0 int64, _a1 *tendermintstate.ABCIResponses) error {
ret := _m.Called(_a0, _a1)
var r0 error
if rf, ok := ret.Get(0).(func(int64, *tendermintstate.ABCIResponses) error); ok {
r0 = rf(_a0, _a1)
} else {
r0 = ret.Error(0)
}
return r0
}
+20 -10
View File
@@ -1,6 +1,9 @@
package state
import (
"time"
abci "github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/types"
)
@@ -37,19 +40,26 @@ type BlockStore interface {
//go:generate mockery --case underscore --name EvidencePool
// EvidencePool defines the EvidencePool interface used by the ConsensusState.
// Get/Set/Commit
// EvidencePool defines the EvidencePool interface used by State.
type EvidencePool interface {
PendingEvidence(uint32) []types.Evidence
AddEvidence(types.Evidence) error
Update(*types.Block, State)
Verify(types.Evidence) error
Update(State)
CheckEvidence(types.EvidenceList) error
ABCIEvidence(int64, []types.Evidence) []abci.Evidence
}
// MockEvidencePool is an empty implementation of EvidencePool, useful for testing.
type MockEvidencePool struct{}
// EmptyEvidencePool is an empty implementation of EvidencePool, useful for testing. It also complies
// to the consensus evidence pool interface
type EmptyEvidencePool struct{}
func (me MockEvidencePool) PendingEvidence(uint32) []types.Evidence { return nil }
func (me MockEvidencePool) AddEvidence(types.Evidence) error { return nil }
func (me MockEvidencePool) Update(*types.Block, State) {}
func (me MockEvidencePool) Verify(types.Evidence) error { return nil }
func (EmptyEvidencePool) PendingEvidence(uint32) []types.Evidence { return nil }
func (EmptyEvidencePool) AddEvidence(types.Evidence) error { return nil }
func (EmptyEvidencePool) Update(State) {}
func (EmptyEvidencePool) CheckEvidence(evList types.EvidenceList) error { return nil }
func (EmptyEvidencePool) ABCIEvidence(int64, []types.Evidence) []abci.Evidence {
return []abci.Evidence{}
}
func (EmptyEvidencePool) AddEvidenceFromConsensus(types.Evidence, time.Time, *types.ValidatorSet) error {
return nil
}
+6
View File
@@ -39,6 +39,12 @@ func calcABCIResponsesKey(height int64) []byte {
//----------------------
//go:generate mockery --case underscore --name Store
// Store defines the state store interface
//
// It is used to retrieve current state and save and load ABCI responses,
// validators and consensus parameters
type Store interface {
// LoadFromDBOrGenesisFile loads the most recent state.
// If the chain is new it will use the genesis file from the provided genesis file path as the current state.
+2 -18
View File
@@ -96,7 +96,7 @@ func validateBlock(evidencePool EvidencePool, state State, block *types.Block) e
}
}
// NOTE: We can't actually verify it's the right proposer because we dont
// NOTE: We can't actually verify it's the right proposer because we don't
// know what round the block was first proposed. So just check that it's
// a legit address and a known validator.
if len(block.ProposerAddress) != crypto.AddressSize {
@@ -148,21 +148,5 @@ func validateBlock(evidencePool EvidencePool, state State, block *types.Block) e
}
// Validate all evidence.
for idx, ev := range block.Evidence.Evidence {
// Check that no evidence has been submitted more than once
for i := idx + 1; i < len(block.Evidence.Evidence); i++ {
if ev.Equal(block.Evidence.Evidence[i]) {
return types.NewErrEvidenceInvalid(ev, errors.New("evidence was submitted twice"))
}
}
// Verify evidence using the evidence pool
err := evidencePool.Verify(ev)
if err != nil {
return types.NewErrEvidenceInvalid(ev, err)
}
}
return nil
return evidencePool.CheckEvidence(block.Evidence.Evidence)
}
+7 -31
View File
@@ -8,6 +8,7 @@ import (
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
abci "github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/crypto/ed25519"
"github.com/tendermint/tendermint/crypto/tmhash"
"github.com/tendermint/tendermint/libs/log"
@@ -21,8 +22,6 @@ import (
const validationTestsStopHeight int64 = 10
var defaultTestTime = time.Date(2019, 1, 1, 0, 0, 0, 0, time.UTC)
func TestValidateBlockHeader(t *testing.T) {
proxyApp := newTestApp()
require.NoError(t, proxyApp.Start())
@@ -35,7 +34,7 @@ func TestValidateBlockHeader(t *testing.T) {
log.TestingLogger(),
proxyApp.Consensus(),
memmock.Mempool{},
sm.MockEvidencePool{},
sm.EmptyEvidencePool{},
)
lastCommit := types.NewCommit(0, 0, types.BlockID{}, nil)
@@ -106,7 +105,7 @@ func TestValidateBlockCommit(t *testing.T) {
log.TestingLogger(),
proxyApp.Consensus(),
memmock.Mempool{},
sm.MockEvidencePool{},
sm.EmptyEvidencePool{},
)
lastCommit := types.NewCommit(0, 0, types.BlockID{}, nil)
wrongSigsCommit := types.NewCommit(1, 0, types.BlockID{}, nil)
@@ -218,8 +217,10 @@ func TestValidateBlockEvidence(t *testing.T) {
defaultEvidenceTime := time.Date(2019, 1, 1, 0, 0, 0, 0, time.UTC)
evpool := &mocks.EvidencePool{}
evpool.On("Verify", mock.AnythingOfType("*types.DuplicateVoteEvidence")).Return(nil)
evpool.On("Update", mock.AnythingOfType("*types.Block"), mock.AnythingOfType("state.State")).Return()
evpool.On("CheckEvidence", mock.AnythingOfType("types.EvidenceList")).Return(nil)
evpool.On("Update", mock.AnythingOfType("state.State")).Return()
evpool.On("ABCIEvidence", mock.AnythingOfType("int64"), mock.AnythingOfType("[]types.Evidence")).Return(
[]abci.Evidence{})
state.ConsensusParams.Evidence.MaxNum = 3
blockExec := sm.NewBlockExecutor(
@@ -279,28 +280,3 @@ func TestValidateBlockEvidence(t *testing.T) {
require.NoError(t, err, "height %d", height)
}
}
func TestValidateDuplicateEvidenceShouldFail(t *testing.T) {
var height int64 = 1
state, stateDB, privVals := makeState(2, int(height))
stateStore := sm.NewStore(stateDB)
_, val := state.Validators.GetByIndex(0)
_, val2 := state.Validators.GetByIndex(1)
ev := types.NewMockDuplicateVoteEvidenceWithValidator(height, defaultTestTime,
privVals[val.Address.String()], chainID)
ev2 := types.NewMockDuplicateVoteEvidenceWithValidator(height, defaultTestTime,
privVals[val2.Address.String()], chainID)
blockExec := sm.NewBlockExecutor(
stateStore, log.TestingLogger(),
nil,
nil,
sm.MockEvidencePool{})
// A block with a couple pieces of evidence passes.
block := makeBlock(state, height)
block.Evidence.Evidence = []types.Evidence{ev, ev2, ev2}
block.EvidenceHash = block.Evidence.Hash()
err := blockExec.ValidateBlock(state, block)
assert.Error(t, err)
}