evidence: structs can independently form abci evidence (#5610)

This commit is contained in:
Callum Waters
2020-11-05 10:38:42 +01:00
parent 70a62be5c6
commit 9d354c842e
35 changed files with 1532 additions and 1917 deletions
+123 -219
View File
@@ -4,7 +4,7 @@ import (
"bytes"
"errors"
"fmt"
"reflect"
"sort"
"sync"
"sync/atomic"
"time"
@@ -13,10 +13,8 @@ import (
gogotypes "github.com/gogo/protobuf/types"
dbm "github.com/tendermint/tm-db"
abci "github.com/tendermint/tendermint/abci/types"
clist "github.com/tendermint/tendermint/libs/clist"
"github.com/tendermint/tendermint/libs/log"
evproto "github.com/tendermint/tendermint/proto/tendermint/evidence"
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
sm "github.com/tendermint/tendermint/state"
"github.com/tendermint/tendermint/types"
@@ -94,7 +92,7 @@ func (evpool *Pool) PendingEvidence(maxBytes int64) ([]types.Evidence, int64) {
}
// Update pulls the latest state to be used for expiration and evidence params and then prunes all expired evidence
func (evpool *Pool) Update(state sm.State) {
func (evpool *Pool) Update(state sm.State, ev types.EvidenceList) {
// sanity check
if state.LastBlockHeight <= evpool.state.LastBlockHeight {
panic(fmt.Sprintf(
@@ -109,6 +107,8 @@ func (evpool *Pool) Update(state sm.State) {
// update the state
evpool.updateState(state)
evpool.markEvidenceAsCommitted(ev)
// prune pending evidence when it has expired. This also updates when the next evidence will expire
if evpool.Size() > 0 && state.LastBlockHeight > evpool.pruningHeight &&
state.LastBlockTime.After(evpool.pruningTime) {
@@ -135,13 +135,13 @@ func (evpool *Pool) AddEvidence(ev types.Evidence) error {
}
// 1) Verify against state.
evInfo, err := evpool.verify(ev)
err := evpool.verify(ev)
if err != nil {
return types.NewErrInvalidEvidence(ev, err)
}
// 2) Save to store.
if err := evpool.addPendingEvidence(evInfo); err != nil {
if err := evpool.addPendingEvidence(ev); err != nil {
return fmt.Errorf("can't add evidence to pending list: %w", err)
}
@@ -153,13 +153,9 @@ func (evpool *Pool) AddEvidence(ev types.Evidence) error {
return nil
}
// AddEvidenceFromConsensus should be exposed only to the consensus so it can add evidence to the pool
// directly without the need for verification.
func (evpool *Pool) AddEvidenceFromConsensus(ev types.Evidence, time time.Time, valSet *types.ValidatorSet) error {
var (
vals []*types.Validator
totalPower int64
)
// AddEvidenceFromConsensus should be exposed only to the consensus reactor so it can add evidence
// to the pool directly without the need for verification.
func (evpool *Pool) AddEvidenceFromConsensus(ev types.Evidence) error {
// we already have this evidence, log this but don't return an error.
if evpool.isPending(ev) {
@@ -167,23 +163,7 @@ func (evpool *Pool) AddEvidenceFromConsensus(ev types.Evidence, time time.Time,
return nil
}
switch ev := ev.(type) {
case *types.DuplicateVoteEvidence:
_, val := valSet.GetByAddress(ev.VoteA.ValidatorAddress)
vals = append(vals, val)
totalPower = valSet.TotalVotingPower()
default:
return fmt.Errorf("unrecognized evidence type: %T", ev)
}
evInfo := &info{
Evidence: ev,
Time: time,
Validators: vals,
TotalVotingPower: totalPower,
}
if err := evpool.addPendingEvidence(evInfo); err != nil {
if err := evpool.addPendingEvidence(ev); err != nil {
return fmt.Errorf("can't add evidence to pending list: %w", err)
}
// add evidence to be gossiped with peers
@@ -210,15 +190,15 @@ func (evpool *Pool) CheckEvidence(evList types.EvidenceList) error {
return &types.ErrInvalidEvidence{Evidence: ev, Reason: errors.New("evidence was already committed")}
}
evInfo, err := evpool.verify(ev)
err := evpool.verify(ev)
if err != nil {
return &types.ErrInvalidEvidence{Evidence: ev, Reason: err}
}
if err := evpool.addPendingEvidence(evInfo); err != nil {
if err := evpool.addPendingEvidence(ev); err != nil {
// Something went wrong with adding the evidence but we already know it is valid
// hence we log an error and continue
evpool.logger.Error("Can't add evidence to pending list", "err", err, "evInfo", evInfo)
evpool.logger.Error("Can't add evidence to pending list", "err", err, "ev", ev)
}
evpool.logger.Info("Verified new evidence of byzantine behavior", "evidence", ev)
@@ -236,85 +216,6 @@ func (evpool *Pool) CheckEvidence(evList types.EvidenceList) error {
return nil
}
// ABCIEvidence processes all the evidence in the block, marking it as committed and removing it
// from the pending database. It then forms the individual abci evidence that will be passed back to
// the application.
func (evpool *Pool) ABCIEvidence(height int64, evidence []types.Evidence) []abci.Evidence {
// make a map of committed evidence to remove from the clist
blockEvidenceMap := make(map[string]struct{}, len(evidence))
abciEvidence := make([]abci.Evidence, 0)
for _, ev := range evidence {
// get entire evidence info from pending list
infoBytes, err := evpool.evidenceStore.Get(keyPending(ev))
if err != nil {
evpool.logger.Error("Unable to retrieve evidence to pass to ABCI. "+
"Evidence pool should have seen this evidence before",
"evidence", ev, "err", err)
continue
}
var infoProto evproto.Info
err = infoProto.Unmarshal(infoBytes)
if err != nil {
evpool.logger.Error("Decoding evidence info failed", "err", err, "height", ev.Height(), "hash", ev.Hash())
continue
}
evInfo, err := infoFromProto(&infoProto)
if err != nil {
evpool.logger.Error("Converting evidence info from proto failed", "err", err, "height", ev.Height(),
"hash", ev.Hash())
continue
}
var evType abci.EvidenceType
switch ev.(type) {
case *types.DuplicateVoteEvidence:
evType = abci.EvidenceType_DUPLICATE_VOTE
case *types.LightClientAttackEvidence:
evType = abci.EvidenceType_LIGHT_CLIENT_ATTACK
default:
evpool.logger.Error("Unknown evidence type", "T", reflect.TypeOf(ev))
continue
}
for _, val := range evInfo.Validators {
abciEv := abci.Evidence{
Type: evType,
Validator: types.TM2PB.Validator(val),
Height: ev.Height(),
Time: evInfo.Time,
TotalVotingPower: evInfo.TotalVotingPower,
}
abciEvidence = append(abciEvidence, abciEv)
evpool.logger.Info("Created ABCI evidence", "ev", abciEv)
}
// we can now remove the evidence from the pending list and the clist that we use for gossiping
evpool.removePendingEvidence(ev)
blockEvidenceMap[evMapKey(ev)] = struct{}{}
// Add evidence to the committed list
// As the evidence is stored in the block store we only need to record the height that it was saved at.
key := keyCommitted(ev)
h := gogotypes.Int64Value{Value: height}
evBytes, err := proto.Marshal(&h)
if err != nil {
panic(err)
}
if err := evpool.evidenceStore.Set(key, evBytes); err != nil {
evpool.logger.Error("Unable to add committed evidence", "err", err)
}
}
// remove committed evidence from the clist
if len(blockEvidenceMap) != 0 {
evpool.removeEvidenceFromList(blockEvidenceMap)
}
return abciEvidence
}
// EvidenceFront goes to the first evidence in the clist
func (evpool *Pool) EvidenceFront() *clist.CElement {
return evpool.evidenceList.Front()
@@ -330,6 +231,7 @@ func (evpool *Pool) SetLogger(l log.Logger) {
evpool.logger = l
}
// Size returns the number of evidence in the pool.
func (evpool *Pool) Size() uint32 {
return atomic.LoadUint32(&evpool.evidenceSize)
}
@@ -343,106 +245,59 @@ func (evpool *Pool) State() sm.State {
//--------------------------------------------------------------------------
// Info is a wrapper around the evidence that the evidence pool receives with extensive
// information of what validators were malicious, the time of the attack and the total voting power
// This is saved as a form of cache so that the evidence pool can easily produce the ABCI Evidence
// needed to be sent to the application.
type info struct {
Evidence types.Evidence
Time time.Time
Validators []*types.Validator
TotalVotingPower int64
ByteSize int64
}
// ToProto encodes into protobuf
func (ei info) ToProto() (*evproto.Info, error) {
evpb, err := types.EvidenceToProto(ei.Evidence)
if err != nil {
return nil, err
}
valsProto := make([]*tmproto.Validator, len(ei.Validators))
for i := 0; i < len(ei.Validators); i++ {
valp, err := ei.Validators[i].ToProto()
if err != nil {
return nil, err
}
valsProto[i] = valp
}
return &evproto.Info{
Evidence: *evpb,
Time: ei.Time,
Validators: valsProto,
TotalVotingPower: ei.TotalVotingPower,
}, nil
}
// InfoFromProto decodes from protobuf into Info
func infoFromProto(proto *evproto.Info) (info, error) {
if proto == nil {
return info{}, errors.New("nil evidence info")
}
ev, err := types.EvidenceFromProto(&proto.Evidence)
if err != nil {
return info{}, err
}
vals := make([]*types.Validator, len(proto.Validators))
for i := 0; i < len(proto.Validators); i++ {
val, err := types.ValidatorFromProto(proto.Validators[i])
if err != nil {
return info{}, err
}
vals[i] = val
}
return info{
Evidence: ev,
Time: proto.Time,
Validators: vals,
TotalVotingPower: proto.TotalVotingPower,
ByteSize: int64(proto.Evidence.Size()),
}, nil
}
//--------------------------------------------------------------------------
// fastCheck leverages the fact that the evidence pool may have already verified the evidence to see if it can
// quickly conclude that the evidence is already valid.
func (evpool *Pool) fastCheck(ev types.Evidence) bool {
key := keyPending(ev)
if lcae, ok := ev.(*types.LightClientAttackEvidence); ok {
key := keyPending(ev)
evBytes, err := evpool.evidenceStore.Get(key)
if evBytes == nil { // the evidence is not in the nodes pending list
return false
}
if err != nil {
evpool.logger.Error("Failed to load evidence", "err", err, "evidence", lcae)
evpool.logger.Error("Failed to load light client attack evidence", "err", err, "key(height/hash)", key)
return false
}
evInfo, err := bytesToInfo(evBytes)
var trustedPb tmproto.LightClientAttackEvidence
err = trustedPb.Unmarshal(evBytes)
if err != nil {
evpool.logger.Error("Failed to convert evidence from proto", "err", err, "evidence", lcae)
evpool.logger.Error("Failed to convert light client attack evidence from bytes",
"err", err, "key(height/hash)", key)
return false
}
// ensure that all the validators that the evidence pool have found to be malicious
// are present in the list of commit signatures in the conflicting block
OUTER:
for _, sig := range lcae.ConflictingBlock.Commit.Signatures {
for _, val := range evInfo.Validators {
if bytes.Equal(val.Address, sig.ValidatorAddress) {
continue OUTER
}
trustedEv, err := types.LightClientAttackEvidenceFromProto(&trustedPb)
if err != nil {
evpool.logger.Error("Failed to convert light client attack evidence from protobuf",
"err", err, "key(height/hash)", key)
return false
}
// ensure that all the byzantine validators that the evidence pool has match the byzantine validators
// in this evidence
if trustedEv.ByzantineValidators == nil && lcae.ByzantineValidators != nil {
return false
}
if len(trustedEv.ByzantineValidators) != len(lcae.ByzantineValidators) {
return false
}
byzValsCopy := make([]*types.Validator, len(lcae.ByzantineValidators))
for i, v := range lcae.ByzantineValidators {
byzValsCopy[i] = v.Copy()
}
// ensure that both validator arrays are in the same order
sort.Sort(types.ValidatorsByVotingPower(byzValsCopy))
for idx, val := range trustedEv.ByzantineValidators {
if !bytes.Equal(byzValsCopy[idx].Address, val.Address) {
return false
}
if byzValsCopy[idx].VotingPower != val.VotingPower {
return false
}
// a validator we know is malicious is not included in the commit
evpool.logger.Info("Fast check failed: a validator we know is malicious is not " +
"in the commit sigs. Reverting to full verification")
return false
}
return true
}
@@ -482,8 +337,8 @@ func (evpool *Pool) isPending(evidence types.Evidence) bool {
return ok
}
func (evpool *Pool) addPendingEvidence(evInfo *info) error {
evpb, err := evInfo.ToProto()
func (evpool *Pool) addPendingEvidence(ev types.Evidence) error {
evpb, err := types.EvidenceToProto(ev)
if err != nil {
return fmt.Errorf("unable to convert to proto, err: %w", err)
}
@@ -493,7 +348,7 @@ func (evpool *Pool) addPendingEvidence(evInfo *info) error {
return fmt.Errorf("unable to marshal evidence: %w", err)
}
key := keyPending(evInfo.Evidence)
key := keyPending(ev)
err = evpool.evidenceStore.Set(key, evBytes)
if err != nil {
@@ -513,31 +368,80 @@ func (evpool *Pool) removePendingEvidence(evidence types.Evidence) {
}
}
// markEvidenceAsCommitted processes all the evidence in the block, marking it as
// committed and removing it from the pending database.
func (evpool *Pool) markEvidenceAsCommitted(evidence types.EvidenceList) {
blockEvidenceMap := make(map[string]struct{}, len(evidence))
for _, ev := range evidence {
if evpool.isPending(ev) {
evpool.removePendingEvidence(ev)
blockEvidenceMap[evMapKey(ev)] = struct{}{}
}
// Add evidence to the committed list. As the evidence is stored in the block store
// we only need to record the height that it was saved at.
key := keyCommitted(ev)
h := gogotypes.Int64Value{Value: ev.Height()}
evBytes, err := proto.Marshal(&h)
if err != nil {
evpool.logger.Error("failed to marshal committed evidence", "err", err, "key(height/hash)", key)
continue
}
if err := evpool.evidenceStore.Set(key, evBytes); err != nil {
evpool.logger.Error("Unable to save committed evidence", "err", err, "key(height/hash)", key)
}
}
// remove committed evidence from the clist
if len(blockEvidenceMap) != 0 {
evpool.removeEvidenceFromList(blockEvidenceMap)
}
}
// listEvidence retrieves lists evidence from oldest to newest within maxBytes.
// If maxBytes is -1, there's no cap on the size of returned evidence.
func (evpool *Pool) listEvidence(prefixKey byte, maxBytes int64) ([]types.Evidence, int64, error) {
var totalSize int64
var evidence []types.Evidence
var (
evSize int64
totalSize int64
evidence []types.Evidence
evList tmproto.EvidenceList // used for calculating the bytes size
)
iter, err := dbm.IteratePrefix(evpool.evidenceStore, []byte{prefixKey})
if err != nil {
return nil, totalSize, fmt.Errorf("database error: %v", err)
}
defer iter.Close()
for ; iter.Valid(); iter.Next() {
evInfo, err := bytesToInfo(iter.Value())
var evpb tmproto.Evidence
err := evpb.Unmarshal(iter.Value())
if err != nil {
return evidence, totalSize, err
}
evList.Evidence = append(evList.Evidence, evpb)
evSize = int64(evList.Size())
if maxBytes != -1 && evSize > maxBytes {
if err := iter.Error(); err != nil {
return evidence, totalSize, err
}
return evidence, totalSize, nil
}
ev, err := types.EvidenceFromProto(&evpb)
if err != nil {
return nil, totalSize, err
}
totalSize += evInfo.ByteSize
if maxBytes != -1 && totalSize > maxBytes {
return evidence, totalSize - evInfo.ByteSize, nil
}
evidence = append(evidence, evInfo.Evidence)
totalSize = evSize
evidence = append(evidence, ev)
}
if err := iter.Error(); err != nil {
return evidence, totalSize, err
}
return evidence, totalSize, nil
}
@@ -550,22 +454,22 @@ func (evpool *Pool) removeExpiredPendingEvidence() (int64, time.Time) {
defer iter.Close()
blockEvidenceMap := make(map[string]struct{})
for ; iter.Valid(); iter.Next() {
evInfo, err := bytesToInfo(iter.Value())
ev, err := bytesToEv(iter.Value())
if err != nil {
evpool.logger.Error("Error in transition evidence from protobuf", "err", err)
continue
}
if !evpool.isExpired(evInfo.Evidence.Height(), evInfo.Time) {
if !evpool.isExpired(ev.Height(), ev.Time()) {
if len(blockEvidenceMap) != 0 {
evpool.removeEvidenceFromList(blockEvidenceMap)
}
// return the height and time with which this evidence will have expired so we know when to prune next
return evInfo.Evidence.Height() + evpool.State().ConsensusParams.Evidence.MaxAgeNumBlocks + 1,
evInfo.Time.Add(evpool.State().ConsensusParams.Evidence.MaxAgeDuration).Add(time.Second)
return ev.Height() + evpool.State().ConsensusParams.Evidence.MaxAgeNumBlocks + 1,
ev.Time().Add(evpool.State().ConsensusParams.Evidence.MaxAgeDuration).Add(time.Second)
}
evpool.removePendingEvidence(evInfo.Evidence)
blockEvidenceMap[evMapKey(evInfo.Evidence)] = struct{}{}
evpool.removePendingEvidence(ev)
blockEvidenceMap[evMapKey(ev)] = struct{}{}
}
// We either have no pending evidence or all evidence has expired
if len(blockEvidenceMap) != 0 {
@@ -593,14 +497,14 @@ func (evpool *Pool) updateState(state sm.State) {
evpool.state = state
}
func bytesToInfo(evBytes []byte) (info, error) {
var evpb evproto.Info
func bytesToEv(evBytes []byte) (types.Evidence, error) {
var evpb tmproto.Evidence
err := evpb.Unmarshal(evBytes)
if err != nil {
return info{}, err
return &types.DuplicateVoteEvidence{}, err
}
return infoFromProto(&evpb)
return types.EvidenceFromProto(&evpb)
}
func evMapKey(ev types.Evidence) string {
+44 -52
View File
@@ -11,7 +11,6 @@ import (
dbm "github.com/tendermint/tm-db"
abci "github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/evidence"
"github.com/tendermint/tendermint/evidence/mocks"
"github.com/tendermint/tendermint/libs/log"
@@ -45,7 +44,7 @@ func TestEvidencePoolBasic(t *testing.T) {
blockStore = &mocks.BlockStore{}
)
valSet, privVals := types.RandValidatorSet(3, 10)
valSet, privVals := types.RandValidatorSet(1, 10)
blockStore.On("LoadBlockMeta", mock.AnythingOfType("int64")).Return(
&types.BlockMeta{Header: types.Header{Time: defaultEvidenceTime}},
@@ -83,9 +82,10 @@ func TestEvidencePoolBasic(t *testing.T) {
next := pool.EvidenceFront()
assert.Equal(t, ev, next.Value.(types.Evidence))
evs, size = pool.PendingEvidence(defaultEvidenceMaxBytes)
const evidenceBytes int64 = 372
evs, size = pool.PendingEvidence(evidenceBytes)
assert.Equal(t, 1, len(evs))
assert.Equal(t, int64(357), size) // check that the size of the single evidence in bytes is correct
assert.Equal(t, evidenceBytes, size) // check that the size of the single evidence in bytes is correct
// shouldn't be able to add evidence twice
assert.NoError(t, pool.AddEvidence(ev))
@@ -108,7 +108,7 @@ func TestAddExpiredEvidence(t *testing.T) {
blockStore.On("LoadBlockMeta", mock.AnythingOfType("int64")).Return(func(h int64) *types.BlockMeta {
if h == height || h == expiredHeight {
return &types.BlockMeta{Header: types.Header{Time: defaultEvidenceTime.Add(time.Duration(height) * time.Minute)}}
return &types.BlockMeta{Header: types.Header{Time: defaultEvidenceTime}}
}
return &types.BlockMeta{Header: types.Header{Time: expiredEvidenceTime}}
})
@@ -127,6 +127,7 @@ func TestAddExpiredEvidence(t *testing.T) {
{height - 1, expiredEvidenceTime, false, "valid evidence (despite old time)"},
{expiredHeight - 1, expiredEvidenceTime, true,
"evidence from height 1 (created at: 2019-01-01 00:00:00 +0000 UTC) is too old"},
{height, defaultEvidenceTime.Add(1 * time.Minute), true, "evidence time and block time is different"},
}
for _, tc := range testCases {
@@ -147,15 +148,13 @@ func TestAddEvidenceFromConsensus(t *testing.T) {
var height int64 = 10
pool, val := defaultTestPool(height)
ev := types.NewMockDuplicateVoteEvidenceWithValidator(height, defaultEvidenceTime, val, evidenceChainID)
valSet := types.NewValidatorSet([]*types.Validator{val.ExtractIntoValidator(2)})
err := pool.AddEvidenceFromConsensus(ev, defaultEvidenceTime, valSet)
err := pool.AddEvidenceFromConsensus(ev)
assert.NoError(t, err)
next := pool.EvidenceFront()
assert.Equal(t, ev, next.Value.(types.Evidence))
// shouldn't be able to submit the same evidence twice
err = pool.AddEvidenceFromConsensus(ev, defaultEvidenceTime.Add(-1*time.Second),
types.NewValidatorSet([]*types.Validator{val.ExtractIntoValidator(3)}))
err = pool.AddEvidenceFromConsensus(ev)
assert.NoError(t, err)
evs, _ := pool.PendingEvidence(defaultEvidenceMaxBytes)
assert.Equal(t, 1, len(evs))
@@ -167,11 +166,12 @@ func TestEvidencePoolUpdate(t *testing.T) {
state := pool.State()
// create new block (no need to save it to blockStore)
prunedEv := types.NewMockDuplicateVoteEvidenceWithValidator(1, defaultEvidenceTime,
prunedEv := types.NewMockDuplicateVoteEvidenceWithValidator(1, defaultEvidenceTime.Add(1*time.Minute),
val, evidenceChainID)
err := pool.AddEvidence(prunedEv)
require.NoError(t, err)
ev := types.NewMockDuplicateVoteEvidenceWithValidator(height, defaultEvidenceTime, val, evidenceChainID)
ev := types.NewMockDuplicateVoteEvidenceWithValidator(height, defaultEvidenceTime.Add(21*time.Minute),
val, evidenceChainID)
lastCommit := makeCommit(height, val.PrivKey.PubKey().Address())
block := types.MakeBlock(height+1, []types.Tx{}, lastCommit, []types.Evidence{ev})
// update state (partially)
@@ -180,22 +180,7 @@ func TestEvidencePoolUpdate(t *testing.T) {
err = pool.CheckEvidence(types.EvidenceList{ev})
require.NoError(t, err)
byzVals := pool.ABCIEvidence(block.Height, block.Evidence.Evidence)
expectedByzVals := []abci.Evidence{
{
Type: abci.EvidenceType_DUPLICATE_VOTE,
Validator: types.TM2PB.Validator(val.ExtractIntoValidator(10)),
Height: height,
Time: defaultEvidenceTime.Add(time.Duration(height) * time.Minute),
TotalVotingPower: 10,
},
}
assert.Equal(t, expectedByzVals, byzVals)
evList, _ := pool.PendingEvidence(defaultEvidenceMaxBytes)
assert.Equal(t, 1, len(evList))
pool.Update(state)
pool.Update(state, block.Evidence.Evidence)
// a) Update marks evidence as committed so pending evidence should be empty
evList, evSize := pool.PendingEvidence(defaultEvidenceMaxBytes)
assert.Empty(t, evList)
@@ -206,14 +191,13 @@ func TestEvidencePoolUpdate(t *testing.T) {
if assert.Error(t, err) {
assert.Equal(t, "evidence was already committed", err.(*types.ErrInvalidEvidence).Reason.Error())
}
assert.Empty(t, pool.ABCIEvidence(height, []types.Evidence{}))
}
func TestVerifyPendingEvidencePasses(t *testing.T) {
var height int64 = 1
pool, val := defaultTestPool(height)
ev := types.NewMockDuplicateVoteEvidenceWithValidator(height, defaultEvidenceTime, val, evidenceChainID)
ev := types.NewMockDuplicateVoteEvidenceWithValidator(height, defaultEvidenceTime.Add(1*time.Minute),
val, evidenceChainID)
err := pool.AddEvidence(ev)
require.NoError(t, err)
@@ -224,20 +208,27 @@ func TestVerifyPendingEvidencePasses(t *testing.T) {
func TestVerifyDuplicatedEvidenceFails(t *testing.T) {
var height int64 = 1
pool, val := defaultTestPool(height)
ev := types.NewMockDuplicateVoteEvidenceWithValidator(height, defaultEvidenceTime, val, evidenceChainID)
ev := types.NewMockDuplicateVoteEvidenceWithValidator(height, defaultEvidenceTime.Add(1*time.Minute),
val, evidenceChainID)
err := pool.CheckEvidence(types.EvidenceList{ev, ev})
if assert.Error(t, err) {
assert.Equal(t, "duplicate evidence", err.(*types.ErrInvalidEvidence).Reason.Error())
}
}
// check that
// check that valid light client evidence is correctly validated and stored in
// evidence pool
func TestCheckEvidenceWithLightClientAttack(t *testing.T) {
nValidators := 5
conflictingVals, conflictingPrivVals := types.RandValidatorSet(nValidators, 10)
trustedHeader := makeHeaderRandom(10)
var (
nValidators = 5
validatorPower int64 = 10
height int64 = 10
)
conflictingVals, conflictingPrivVals := types.RandValidatorSet(nValidators, validatorPower)
trustedHeader := makeHeaderRandom(height)
trustedHeader.Time = defaultEvidenceTime
conflictingHeader := makeHeaderRandom(10)
conflictingHeader := makeHeaderRandom(height)
conflictingHeader.ValidatorsHash = conflictingVals.Hash()
trustedHeader.ValidatorsHash = conflictingHeader.ValidatorsHash
@@ -249,8 +240,8 @@ func TestCheckEvidenceWithLightClientAttack(t *testing.T) {
// for simplicity we are simulating a duplicate vote attack where all the validators in the
// conflictingVals set voted twice
blockID := makeBlockID(conflictingHeader.Hash(), 1000, []byte("partshash"))
voteSet := types.NewVoteSet(evidenceChainID, 10, 1, tmproto.SignedMsgType(2), conflictingVals)
commit, err := types.MakeCommit(blockID, 10, 1, voteSet, conflictingPrivVals, defaultEvidenceTime)
voteSet := types.NewVoteSet(evidenceChainID, height, 1, tmproto.SignedMsgType(2), conflictingVals)
commit, err := types.MakeCommit(blockID, height, 1, voteSet, conflictingPrivVals, defaultEvidenceTime)
require.NoError(t, err)
ev := &types.LightClientAttackEvidence{
ConflictingBlock: &types.LightBlock{
@@ -260,12 +251,16 @@ func TestCheckEvidenceWithLightClientAttack(t *testing.T) {
},
ValidatorSet: conflictingVals,
},
CommonHeight: 10,
CommonHeight: 10,
TotalVotingPower: int64(nValidators) * validatorPower,
ByzantineValidators: conflictingVals.Validators,
Timestamp: defaultEvidenceTime,
}
trustedBlockID := makeBlockID(trustedHeader.Hash(), 1000, []byte("partshash"))
trustedVoteSet := types.NewVoteSet(evidenceChainID, 10, 1, tmproto.SignedMsgType(2), conflictingVals)
trustedCommit, err := types.MakeCommit(trustedBlockID, 10, 1, trustedVoteSet, conflictingPrivVals, defaultEvidenceTime)
trustedVoteSet := types.NewVoteSet(evidenceChainID, height, 1, tmproto.SignedMsgType(2), conflictingVals)
trustedCommit, err := types.MakeCommit(trustedBlockID, height, 1, trustedVoteSet, conflictingPrivVals,
defaultEvidenceTime)
require.NoError(t, err)
state := sm.State{
@@ -274,11 +269,11 @@ func TestCheckEvidenceWithLightClientAttack(t *testing.T) {
ConsensusParams: *types.DefaultConsensusParams(),
}
stateStore := &smmocks.Store{}
stateStore.On("LoadValidators", int64(10)).Return(conflictingVals, nil)
stateStore.On("LoadValidators", height).Return(conflictingVals, nil)
stateStore.On("Load").Return(state, nil)
blockStore := &mocks.BlockStore{}
blockStore.On("LoadBlockMeta", int64(10)).Return(&types.BlockMeta{Header: *trustedHeader})
blockStore.On("LoadBlockCommit", int64(10)).Return(trustedCommit)
blockStore.On("LoadBlockMeta", height).Return(&types.BlockMeta{Header: *trustedHeader})
blockStore.On("LoadBlockCommit", height).Return(trustedCommit)
pool, err := evidence.NewPool(dbm.NewMemDB(), stateStore, blockStore)
require.NoError(t, err)
@@ -291,17 +286,14 @@ func TestCheckEvidenceWithLightClientAttack(t *testing.T) {
assert.NoError(t, err)
// take away the last signature -> there are less validators then what we have detected,
// hence we move to full verification where the evidence should still pass
// hence this should fail
commit.Signatures = append(commit.Signatures[:nValidators-1], types.NewCommitSigAbsent())
err = pool.CheckEvidence(types.EvidenceList{ev})
assert.NoError(t, err)
// take away the last two signatures -> should fail due to insufficient power
commit.Signatures = append(commit.Signatures[:nValidators-2], types.NewCommitSigAbsent(), types.NewCommitSigAbsent())
err = pool.CheckEvidence(types.EvidenceList{ev})
assert.Error(t, err)
}
// Tests that restarting the evidence pool after a potential failure will recover the
// pending evidence and continue to gossip it
func TestRecoverPendingEvidence(t *testing.T) {
height := int64(10)
val := types.NewMockPV()
@@ -316,9 +308,9 @@ func TestRecoverPendingEvidence(t *testing.T) {
require.NoError(t, err)
pool.SetLogger(log.TestingLogger())
goodEvidence := types.NewMockDuplicateVoteEvidenceWithValidator(height,
defaultEvidenceTime, val, evidenceChainID)
defaultEvidenceTime.Add(10*time.Minute), val, evidenceChainID)
expiredEvidence := types.NewMockDuplicateVoteEvidenceWithValidator(int64(1),
defaultEvidenceTime, val, evidenceChainID)
defaultEvidenceTime.Add(1*time.Minute), val, evidenceChainID)
err = pool.AddEvidence(goodEvidence)
require.NoError(t, err)
err = pool.AddEvidence(expiredEvidence)
+6 -8
View File
@@ -7,7 +7,6 @@ import (
clist "github.com/tendermint/tendermint/libs/clist"
"github.com/tendermint/tendermint/libs/log"
"github.com/tendermint/tendermint/p2p"
ep "github.com/tendermint/tendermint/proto/tendermint/evidence"
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
"github.com/tendermint/tendermint/types"
)
@@ -128,7 +127,7 @@ func (evR *Reactor) broadcastEvidenceRoutine(peer p2p.Peer) {
if err != nil {
panic(err)
}
evR.Logger.Debug("Gossiping evidence to peer", "ev", ev, "peer", peer.ID())
success := peer.Send(EvidenceChannel, msgBytes)
if !success {
time.Sleep(peerRetryMessageIntervalMS * time.Millisecond)
@@ -210,16 +209,15 @@ type PeerState interface {
// encodemsg takes a array of evidence
// returns the byte encoding of the List Message
func encodeMsg(evis []types.Evidence) ([]byte, error) {
evi := make([]*tmproto.Evidence, len(evis))
evi := make([]tmproto.Evidence, len(evis))
for i := 0; i < len(evis); i++ {
ev, err := types.EvidenceToProto(evis[i])
if err != nil {
return nil, err
}
evi[i] = ev
evi[i] = *ev
}
epl := ep.List{
epl := tmproto.EvidenceList{
Evidence: evi,
}
@@ -229,14 +227,14 @@ func encodeMsg(evis []types.Evidence) ([]byte, error) {
// decodemsg takes an array of bytes
// returns an array of evidence
func decodeMsg(bz []byte) (evis []types.Evidence, err error) {
lm := ep.List{}
lm := tmproto.EvidenceList{}
if err := lm.Unmarshal(bz); err != nil {
return nil, err
}
evis = make([]types.Evidence, len(lm.Evidence))
for i := 0; i < len(lm.Evidence); i++ {
ev, err := types.EvidenceFromProto(lm.Evidence[i])
ev, err := types.EvidenceFromProto(&lm.Evidence[i])
if err != nil {
return nil, err
}
+28 -14
View File
@@ -21,7 +21,6 @@ import (
"github.com/tendermint/tendermint/evidence/mocks"
"github.com/tendermint/tendermint/libs/log"
"github.com/tendermint/tendermint/p2p"
ep "github.com/tendermint/tendermint/proto/tendermint/evidence"
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
sm "github.com/tendermint/tendermint/state"
"github.com/tendermint/tendermint/types"
@@ -119,15 +118,17 @@ func TestReactorsGossipNoCommittedEvidence(t *testing.T) {
var height int64 = 10
// DB1 is ahead of DB2
stateDB1 := initializeValidatorState(val, height)
stateDB1 := initializeValidatorState(val, height-1)
stateDB2 := initializeValidatorState(val, height-2)
state, err := stateDB1.Load()
require.NoError(t, err)
state.LastBlockHeight++
// make reactors from statedb
reactors, pools := makeAndConnectReactorsAndPools(config, []sm.Store{stateDB1, stateDB2})
evList := sendEvidence(t, pools[0], val, 2)
abciEvs := pools[0].ABCIEvidence(height, evList)
require.EqualValues(t, 2, len(abciEvs))
pools[0].Update(state, evList)
require.EqualValues(t, uint32(0), pools[0].Size())
time.Sleep(100 * time.Millisecond)
@@ -150,7 +151,7 @@ func TestReactorsGossipNoCommittedEvidence(t *testing.T) {
evList = make([]types.Evidence, 3)
for i := 0; i < 3; i++ {
ev := types.NewMockDuplicateVoteEvidenceWithValidator(height-3+int64(i),
time.Date(2019, 1, 1, 0, 0, 0, 0, time.UTC), val, evidenceChainID)
time.Date(2019, 1, 1, 0, 0, 0, 0, time.UTC), val, state.ChainID)
err := pools[0].AddEvidence(ev)
require.NoError(t, err)
evList[i] = ev
@@ -160,18 +161,19 @@ func TestReactorsGossipNoCommittedEvidence(t *testing.T) {
time.Sleep(300 * time.Millisecond)
// the second pool should only have received the first evidence because it is behind
peerEv, _ := pools[1].PendingEvidence(1000)
peerEv, _ := pools[1].PendingEvidence(10000)
assert.EqualValues(t, []types.Evidence{evList[0]}, peerEv)
// the last evidence is committed and the second reactor catches up in state to the first
// reactor. We therefore expect that the second reactor only receives one more evidence, the
// one that is still pending and not the evidence that has already been committed.
_ = pools[0].ABCIEvidence(height, []types.Evidence{evList[2]})
state.LastBlockHeight++
pools[0].Update(state, []types.Evidence{evList[2]})
// the first reactor should have the two remaining pending evidence
require.EqualValues(t, uint32(2), pools[0].Size())
// now update the state of the second reactor
pools[1].Update(sm.State{LastBlockHeight: height})
pools[1].Update(state, types.EvidenceList{})
peer = reactors[0].Switch.Peers().List()[0]
ps = peerState{height}
peer.Set(types.PeerStateKey, ps)
@@ -180,7 +182,7 @@ func TestReactorsGossipNoCommittedEvidence(t *testing.T) {
time.Sleep(300 * time.Millisecond)
peerEv, _ = pools[1].PendingEvidence(1000)
assert.EqualValues(t, evList[0:1], peerEv)
assert.EqualValues(t, []types.Evidence{evList[0], evList[1]}, peerEv)
}
// evidenceLogger is a TestingLogger which uses a different
@@ -331,27 +333,39 @@ func exampleVote(t byte) *types.Vote {
// nolint:lll //ignore line length for tests
func TestEvidenceVectors(t *testing.T) {
dupl := types.NewDuplicateVoteEvidence(exampleVote(1), exampleVote(2))
val := &types.Validator{
Address: crypto.AddressHash([]byte("validator_address")),
VotingPower: 10,
}
valSet := types.NewValidatorSet([]*types.Validator{val})
dupl := types.NewDuplicateVoteEvidence(
exampleVote(1),
exampleVote(2),
defaultEvidenceTime,
valSet,
)
testCases := []struct {
testName string
evidenceList []types.Evidence
expBytes string
}{
{"DuplicateVoteEvidence", []types.Evidence{dupl}, "0af9010af6010a79080210031802224a0a208b01023386c371778ecb6368573e539afc3cc860ec3a2f614e54fe5652f4fc80122608c0843d122072db3d959635dff1bb567bedaa70573392c5159666a3f8caf11e413aac52207a2a0b08b1d381d20510809dca6f32146af1f4111082efb388211bc72c55bcd61e9ac3d538d5bb031279080110031802224a0a208b01023386c371778ecb6368573e539afc3cc860ec3a2f614e54fe5652f4fc80122608c0843d122072db3d959635dff1bb567bedaa70573392c5159666a3f8caf11e413aac52207a2a0b08b1d381d20510809dca6f32146af1f4111082efb388211bc72c55bcd61e9ac3d538d5bb03"},
{"DuplicateVoteEvidence", []types.Evidence{dupl}, "0a85020a82020a79080210031802224a0a208b01023386c371778ecb6368573e539afc3cc860ec3a2f614e54fe5652f4fc80122608c0843d122072db3d959635dff1bb567bedaa70573392c5159666a3f8caf11e413aac52207a2a0b08b1d381d20510809dca6f32146af1f4111082efb388211bc72c55bcd61e9ac3d538d5bb031279080110031802224a0a208b01023386c371778ecb6368573e539afc3cc860ec3a2f614e54fe5652f4fc80122608c0843d122072db3d959635dff1bb567bedaa70573392c5159666a3f8caf11e413aac52207a2a0b08b1d381d20510809dca6f32146af1f4111082efb388211bc72c55bcd61e9ac3d538d5bb03180a200a2a060880dbaae105"},
}
for _, tc := range testCases {
tc := tc
evi := make([]*tmproto.Evidence, len(tc.evidenceList))
evi := make([]tmproto.Evidence, len(tc.evidenceList))
for i := 0; i < len(tc.evidenceList); i++ {
ev, err := types.EvidenceToProto(tc.evidenceList[i])
require.NoError(t, err, tc.testName)
evi[i] = ev
evi[i] = *ev
}
epl := ep.List{
epl := tmproto.EvidenceList{
Evidence: evi,
}
+58 -90
View File
@@ -4,6 +4,7 @@ import (
"bytes"
"errors"
"fmt"
"sort"
"time"
"github.com/tendermint/tendermint/light"
@@ -16,7 +17,7 @@ import (
// - it is from a key who was a validator at the given height
// - it is internally consistent with state
// - it was properly signed by the alleged equivocator and meets the individual evidence verification requirements
func (evpool *Pool) verify(evidence types.Evidence) (*info, error) {
func (evpool *Pool) verify(evidence types.Evidence) error {
var (
state = evpool.State()
height = state.LastBlockHeight
@@ -27,14 +28,18 @@ func (evpool *Pool) verify(evidence types.Evidence) (*info, error) {
// verify the time of the evidence
blockMeta := evpool.blockStore.LoadBlockMeta(evidence.Height())
if blockMeta == nil {
return nil, fmt.Errorf("don't have header at height #%d", evidence.Height())
return fmt.Errorf("don't have header #%d", evidence.Height())
}
evTime := blockMeta.Header.Time
if evidence.Time() != evTime {
return fmt.Errorf("evidence has a different time to the block it is associated with (%v != %v)",
evidence.Time(), evTime)
}
ageDuration := state.LastBlockTime.Sub(evTime)
// check that the evidence hasn't expired
if ageDuration > evidenceParams.MaxAgeDuration && ageNumBlocks > evidenceParams.MaxAgeNumBlocks {
return nil, fmt.Errorf(
return fmt.Errorf(
"evidence from height %d (created at: %v) is too old; min height is %d and evidence can not be older than %v",
evidence.Height(),
evTime,
@@ -48,62 +53,66 @@ func (evpool *Pool) verify(evidence types.Evidence) (*info, error) {
case *types.DuplicateVoteEvidence:
valSet, err := evpool.stateDB.LoadValidators(evidence.Height())
if err != nil {
return nil, err
return err
}
err = VerifyDuplicateVote(ev, state.ChainID, valSet)
if err != nil {
return nil, fmt.Errorf("verifying duplicate vote evidence: %w", err)
}
_, val := valSet.GetByAddress(ev.VoteA.ValidatorAddress)
return &info{
Evidence: evidence,
Time: evTime,
Validators: []*types.Validator{val}, // just a single validator for duplicate vote evidence
TotalVotingPower: valSet.TotalVotingPower(),
}, nil
return VerifyDuplicateVote(ev, state.ChainID, valSet)
case *types.LightClientAttackEvidence:
commonHeader, err := getSignedHeader(evpool.blockStore, evidence.Height())
if err != nil {
return nil, err
return err
}
commonVals, err := evpool.stateDB.LoadValidators(evidence.Height())
if err != nil {
return nil, err
return err
}
trustedHeader := commonHeader
// in the case of lunatic the trusted header is different to the common header
if evidence.Height() != ev.ConflictingBlock.Height {
trustedHeader, err = getSignedHeader(evpool.blockStore, ev.ConflictingBlock.Height)
if err != nil {
return nil, err
return err
}
}
err = VerifyLightClientAttack(ev, commonHeader, trustedHeader, commonVals, state.LastBlockTime,
state.ConsensusParams.Evidence.MaxAgeDuration)
if err != nil {
return nil, err
return err
}
// find out what type of attack this was and thus extract the malicious validators. Note in the case of an
// Amnesia attack we don't have any malicious validators.
validators, attackType := getMaliciousValidators(ev, commonVals, trustedHeader)
totalVotingPower := ev.ConflictingBlock.ValidatorSet.TotalVotingPower()
if attackType == lunaticType {
totalVotingPower = commonVals.TotalVotingPower()
validators := ev.GetByzantineValidators(commonVals, trustedHeader)
// ensure this matches the validators that are listed in the evidence. They should be ordered based on power.
if validators == nil && ev.ByzantineValidators != nil {
return fmt.Errorf("expected nil validators from an amnesia light client attack but got %d",
len(ev.ByzantineValidators))
}
return &info{
Evidence: evidence,
Time: evTime,
Validators: validators,
TotalVotingPower: totalVotingPower,
}, nil
if exp, got := len(validators), len(ev.ByzantineValidators); exp != got {
return fmt.Errorf("expected %d byzantine validators from evidence but got %d",
exp, got)
}
// ensure that both validator arrays are in the same order
sort.Sort(types.ValidatorsByVotingPower(ev.ByzantineValidators))
for idx, val := range validators {
if !bytes.Equal(ev.ByzantineValidators[idx].Address, val.Address) {
return fmt.Errorf("evidence contained a different byzantine validator address to the one we were expecting."+
"Expected %v, got %v", val.Address, ev.ByzantineValidators[idx].Address)
}
if ev.ByzantineValidators[idx].VotingPower != val.VotingPower {
return fmt.Errorf("evidence contained a byzantine validator with a different power to the one we were expecting."+
"Expected %d, got %d", val.VotingPower, ev.ByzantineValidators[idx].VotingPower)
}
}
return nil
default:
return nil, fmt.Errorf("unrecognized evidence type: %T", evidence)
return fmt.Errorf("unrecognized evidence type: %T", evidence)
}
}
// VerifyLightClientAttack verifies LightClientAttackEvidence against the state of the full node. This involves
@@ -134,8 +143,13 @@ func VerifyLightClientAttack(e *types.LightClientAttackEvidence, commonHeader, t
}
}
if evTotal, valsTotal := e.TotalVotingPower, commonVals.TotalVotingPower(); evTotal != valsTotal {
return fmt.Errorf("total voting power from the evidence and our validator set does not match (%d != %d)",
evTotal, valsTotal)
}
if bytes.Equal(trustedHeader.Hash(), e.ConflictingBlock.Hash()) {
return fmt.Errorf("trusted header hash matches the evidence conflicting header hash: %X",
return fmt.Errorf("trusted header hash matches the evidence's conflicting header hash: %X",
trustedHeader.Hash())
}
@@ -186,6 +200,17 @@ func VerifyDuplicateVote(e *types.DuplicateVoteEvidence, chainID string, valSet
return fmt.Errorf("address (%X) doesn't match pubkey (%v - %X)",
addr, pubKey, pubKey.Address())
}
// validator voting power and total voting power must match
if val.VotingPower != e.ValidatorPower {
return fmt.Errorf("validator power from evidence and our validator set does not match (%d != %d)",
e.ValidatorPower, val.VotingPower)
}
if valSet.TotalVotingPower() != e.TotalVotingPower {
return fmt.Errorf("total voting power from the evidence and our validator set does not match (%d != %d)",
e.TotalVotingPower, valSet.TotalVotingPower())
}
va := e.VoteA.ToProto()
vb := e.VoteB.ToProto()
// Signatures must be valid
@@ -214,55 +239,6 @@ func getSignedHeader(blockStore BlockStore, height int64) (*types.SignedHeader,
}, nil
}
// getMaliciousValidators finds out what style of attack LightClientAttackEvidence was and then works out who
// the malicious validators were and returns them.
func getMaliciousValidators(evidence *types.LightClientAttackEvidence, commonVals *types.ValidatorSet,
trusted *types.SignedHeader) ([]*types.Validator, lightClientAttackType) {
var validators []*types.Validator
// First check if the header is invalid. This means that it is a lunatic attack and therefore we take the
// validators who are in the commonVals and voted for the lunatic header
if isInvalidHeader(trusted.Header, evidence.ConflictingBlock.Header) {
for _, commitSig := range evidence.ConflictingBlock.Commit.Signatures {
if !commitSig.ForBlock() {
continue
}
_, val := commonVals.GetByAddress(commitSig.ValidatorAddress)
if val == nil {
// validator wasn't in the common validator set
continue
}
validators = append(validators, val)
}
return validators, lunaticType
// Next, check to see if it is an equivocation attack and both commits are in the same round. If this is the
// case then we take the validators from the conflicting light block validator set that voted in both headers.
} else if trusted.Commit.Round == evidence.ConflictingBlock.Commit.Round {
// validator hashes are the same therefore the indexing order of validators are the same and thus we
// only need a single loop to find the validators that voted twice.
for i := 0; i < len(evidence.ConflictingBlock.Commit.Signatures); i++ {
sigA := evidence.ConflictingBlock.Commit.Signatures[i]
if sigA.Absent() {
continue
}
sigB := trusted.Commit.Signatures[i]
if sigB.Absent() {
continue
}
_, val := evidence.ConflictingBlock.ValidatorSet.GetByAddress(sigA.ValidatorAddress)
validators = append(validators, val)
}
return validators, equivocationType
}
// if the rounds are different then this is an amnesia attack. Unfortunately, given the nature of the attack,
// we aren't able yet to deduce which are malicious validators and which are not hence we return an
// empty validator set.
return validators, amnesiaType
}
// isInvalidHeader takes a trusted header and matches it againt a conflicting header
// to determine whether the conflicting header was the product of a valid state transition
// or not. If it is then all the deterministic fields of the header should be the same.
@@ -274,11 +250,3 @@ func isInvalidHeader(trusted, conflicting *types.Header) bool {
!bytes.Equal(trusted.AppHash, conflicting.AppHash) ||
!bytes.Equal(trusted.LastResultsHash, conflicting.LastResultsHash)
}
type lightClientAttackType int
const (
lunaticType lightClientAttackType = iota + 1
equivocationType
amnesiaType
)
+61 -73
View File
@@ -9,7 +9,6 @@ import (
dbm "github.com/tendermint/tm-db"
abci "github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/crypto"
"github.com/tendermint/tendermint/crypto/tmhash"
"github.com/tendermint/tendermint/evidence"
@@ -33,14 +32,14 @@ func TestVerifyLightClientAttack_Lunatic(t *testing.T) {
conflictingPrivVals := append(commonPrivVals, newPrivVal)
commonHeader := makeHeaderRandom(4)
commonHeader.Time = defaultEvidenceTime.Add(-1 * time.Hour)
commonHeader.Time = defaultEvidenceTime
trustedHeader := makeHeaderRandom(10)
conflictingHeader := makeHeaderRandom(10)
conflictingHeader.Time = defaultEvidenceTime.Add(1 * time.Hour)
conflictingHeader.ValidatorsHash = conflictingVals.Hash()
// we are simulating a duplicate vote attack where all the validators in the conflictingVals set
// vote twice
// we are simulating a lunatic light client attack
blockID := makeBlockID(conflictingHeader.Hash(), 1000, []byte("partshash"))
voteSet := types.NewVoteSet(evidenceChainID, 10, 1, tmproto.SignedMsgType(2), conflictingVals)
commit, err := types.MakeCommit(blockID, 10, 1, voteSet, conflictingPrivVals, defaultEvidenceTime)
@@ -53,7 +52,10 @@ func TestVerifyLightClientAttack_Lunatic(t *testing.T) {
},
ValidatorSet: conflictingVals,
},
CommonHeight: 4,
CommonHeight: 4,
TotalVotingPower: 20,
ByzantineValidators: commonVals.Validators,
Timestamp: defaultEvidenceTime,
}
commonSignedHeader := &types.SignedHeader{
@@ -72,16 +74,23 @@ func TestVerifyLightClientAttack_Lunatic(t *testing.T) {
// good pass -> no error
err = evidence.VerifyLightClientAttack(ev, commonSignedHeader, trustedSignedHeader, commonVals,
defaultEvidenceTime.Add(1*time.Minute), 2*time.Hour)
defaultEvidenceTime.Add(2*time.Hour), 3*time.Hour)
assert.NoError(t, err)
// trusted and conflicting hashes are the same -> an error should be returned
err = evidence.VerifyLightClientAttack(ev, commonSignedHeader, ev.ConflictingBlock.SignedHeader, commonVals,
defaultEvidenceTime.Add(1*time.Minute), 2*time.Hour)
defaultEvidenceTime.Add(2*time.Hour), 3*time.Hour)
assert.Error(t, err)
// evidence with different total validator power should fail
ev.TotalVotingPower = 1
err = evidence.VerifyLightClientAttack(ev, commonSignedHeader, trustedSignedHeader, commonVals,
defaultEvidenceTime.Add(2*time.Hour), 3*time.Hour)
assert.Error(t, err)
ev.TotalVotingPower = 20
state := sm.State{
LastBlockTime: defaultEvidenceTime.Add(1 * time.Minute),
LastBlockTime: defaultEvidenceTime.Add(2 * time.Hour),
LastBlockHeight: 11,
ConsensusParams: *types.DefaultConsensusParams(),
}
@@ -105,27 +114,18 @@ func TestVerifyLightClientAttack_Lunatic(t *testing.T) {
pendingEvs, _ := pool.PendingEvidence(state.ConsensusParams.Evidence.MaxBytes)
assert.Equal(t, 1, len(pendingEvs))
pubKey, err := newPrivVal.GetPubKey()
require.NoError(t, err)
lastCommit := makeCommit(state.LastBlockHeight, pubKey.Address())
block := types.MakeBlock(state.LastBlockHeight, []types.Tx{}, lastCommit, []types.Evidence{ev})
// if we submit evidence only against a single byzantine validator when we see there are more validators then this
// should return an error
ev.ByzantineValidators = []*types.Validator{commonVals.Validators[0]}
err = pool.CheckEvidence(evList)
assert.Error(t, err)
ev.ByzantineValidators = commonVals.Validators // restore evidence
abciEv := pool.ABCIEvidence(block.Height, block.Evidence.Evidence)
expectedAbciEv := make([]abci.Evidence, len(commonVals.Validators))
// If evidence is submitted with an altered timestamp it should return an error
ev.Timestamp = defaultEvidenceTime.Add(1 * time.Minute)
err = pool.CheckEvidence(evList)
assert.Error(t, err)
// we expect evidence to be made for all validators in the common validator set
for idx, val := range commonVals.Validators {
ev := abci.Evidence{
Type: abci.EvidenceType_LIGHT_CLIENT_ATTACK,
Validator: types.TM2PB.Validator(val),
Height: commonHeader.Height,
Time: commonHeader.Time,
TotalVotingPower: commonVals.TotalVotingPower(),
}
expectedAbciEv[idx] = ev
}
assert.Equal(t, expectedAbciEv, abciEv)
}
func TestVerifyLightClientAttack_Equivocation(t *testing.T) {
@@ -155,7 +155,10 @@ func TestVerifyLightClientAttack_Equivocation(t *testing.T) {
},
ValidatorSet: conflictingVals,
},
CommonHeight: 10,
CommonHeight: 10,
ByzantineValidators: conflictingVals.Validators[:4],
TotalVotingPower: 50,
Timestamp: defaultEvidenceTime,
}
trustedBlockID := makeBlockID(trustedHeader.Hash(), 1000, []byte("partshash"))
@@ -168,12 +171,12 @@ func TestVerifyLightClientAttack_Equivocation(t *testing.T) {
}
// good pass -> no error
err = evidence.VerifyLightClientAttack(ev, trustedSignedHeader, trustedSignedHeader, nil,
err = evidence.VerifyLightClientAttack(ev, trustedSignedHeader, trustedSignedHeader, conflictingVals,
defaultEvidenceTime.Add(1*time.Minute), 2*time.Hour)
assert.NoError(t, err)
// trusted and conflicting hashes are the same -> an error should be returned
err = evidence.VerifyLightClientAttack(ev, trustedSignedHeader, ev.ConflictingBlock.SignedHeader, nil,
err = evidence.VerifyLightClientAttack(ev, trustedSignedHeader, ev.ConflictingBlock.SignedHeader, conflictingVals,
defaultEvidenceTime.Add(1*time.Minute), 2*time.Hour)
assert.Error(t, err)
@@ -208,31 +211,6 @@ func TestVerifyLightClientAttack_Equivocation(t *testing.T) {
pendingEvs, _ := pool.PendingEvidence(state.ConsensusParams.Evidence.MaxBytes)
assert.Equal(t, 1, len(pendingEvs))
pubKey, err := conflictingPrivVals[0].GetPubKey()
require.NoError(t, err)
lastCommit := makeCommit(state.LastBlockHeight, pubKey.Address())
block := types.MakeBlock(state.LastBlockHeight, []types.Tx{}, lastCommit, []types.Evidence{ev})
abciEv := pool.ABCIEvidence(block.Height, block.Evidence.Evidence)
expectedAbciEv := make([]abci.Evidence, len(conflictingVals.Validators)-1)
// we expect evidence to be made for all validators except the last one
for idx, val := range conflictingVals.Validators {
if idx == 4 { // skip the last validator
continue
}
ev := abci.Evidence{
Type: abci.EvidenceType_LIGHT_CLIENT_ATTACK,
Validator: types.TM2PB.Validator(val),
Height: ev.ConflictingBlock.Height,
Time: ev.ConflictingBlock.Time,
TotalVotingPower: ev.ConflictingBlock.ValidatorSet.TotalVotingPower(),
}
expectedAbciEv[idx] = ev
}
assert.Equal(t, expectedAbciEv, abciEv)
}
func TestVerifyLightClientAttack_Amnesia(t *testing.T) {
@@ -261,7 +239,10 @@ func TestVerifyLightClientAttack_Amnesia(t *testing.T) {
},
ValidatorSet: conflictingVals,
},
CommonHeight: 10,
CommonHeight: 10,
ByzantineValidators: nil, // with amnesia evidence no validators are submitted as abci evidence
TotalVotingPower: 50,
Timestamp: defaultEvidenceTime,
}
trustedBlockID := makeBlockID(trustedHeader.Hash(), 1000, []byte("partshash"))
@@ -274,12 +255,12 @@ func TestVerifyLightClientAttack_Amnesia(t *testing.T) {
}
// good pass -> no error
err = evidence.VerifyLightClientAttack(ev, trustedSignedHeader, trustedSignedHeader, nil,
err = evidence.VerifyLightClientAttack(ev, trustedSignedHeader, trustedSignedHeader, conflictingVals,
defaultEvidenceTime.Add(1*time.Minute), 2*time.Hour)
assert.NoError(t, err)
// trusted and conflicting hashes are the same -> an error should be returned
err = evidence.VerifyLightClientAttack(ev, trustedSignedHeader, ev.ConflictingBlock.SignedHeader, nil,
err = evidence.VerifyLightClientAttack(ev, trustedSignedHeader, ev.ConflictingBlock.SignedHeader, conflictingVals,
defaultEvidenceTime.Add(1*time.Minute), 2*time.Hour)
assert.Error(t, err)
@@ -305,19 +286,6 @@ func TestVerifyLightClientAttack_Amnesia(t *testing.T) {
pendingEvs, _ := pool.PendingEvidence(state.ConsensusParams.Evidence.MaxBytes)
assert.Equal(t, 1, len(pendingEvs))
pubKey, err := conflictingPrivVals[0].GetPubKey()
require.NoError(t, err)
lastCommit := makeCommit(state.LastBlockHeight, pubKey.Address())
block := types.MakeBlock(state.LastBlockHeight, []types.Tx{}, lastCommit, []types.Evidence{ev})
abciEv := pool.ABCIEvidence(block.Height, block.Evidence.Evidence)
// as we are unable to find out which subset of validators in the commit were malicious, no information
// is sent to the application. We expect the array to be empty
emptyEvidenceBlock := types.MakeBlock(state.LastBlockHeight, []types.Tx{}, lastCommit, []types.Evidence{})
expectedAbciEv := pool.ABCIEvidence(emptyEvidenceBlock.Height, emptyEvidenceBlock.Evidence.Evidence)
assert.Equal(t, expectedAbciEv, abciEv)
}
type voteData struct {
@@ -368,8 +336,11 @@ func TestVerifyDuplicateVoteEvidence(t *testing.T) {
require.NoError(t, err)
for _, c := range cases {
ev := &types.DuplicateVoteEvidence{
VoteA: c.vote1,
VoteB: c.vote2,
VoteA: c.vote1,
VoteB: c.vote2,
ValidatorPower: 1,
TotalVotingPower: 1,
Timestamp: defaultEvidenceTime,
}
if c.valid {
assert.Nil(t, evidence.VerifyDuplicateVote(ev, chainID, valSet), "evidence should be valid")
@@ -378,7 +349,14 @@ func TestVerifyDuplicateVoteEvidence(t *testing.T) {
}
}
// create good evidence and correct validator power
goodEv := types.NewMockDuplicateVoteEvidenceWithValidator(10, defaultEvidenceTime, val, chainID)
goodEv.ValidatorPower = 1
goodEv.TotalVotingPower = 1
badEv := types.NewMockDuplicateVoteEvidenceWithValidator(10, defaultEvidenceTime, val, chainID)
badTimeEv := types.NewMockDuplicateVoteEvidenceWithValidator(10, defaultEvidenceTime.Add(1*time.Minute), val, chainID)
badTimeEv.ValidatorPower = 1
badTimeEv.TotalVotingPower = 1
state := sm.State{
ChainID: chainID,
LastBlockTime: defaultEvidenceTime.Add(1 * time.Minute),
@@ -397,6 +375,16 @@ func TestVerifyDuplicateVoteEvidence(t *testing.T) {
evList := types.EvidenceList{goodEv}
err = pool.CheckEvidence(evList)
assert.NoError(t, err)
// evidence with a different validator power should fail
evList = types.EvidenceList{badEv}
err = pool.CheckEvidence(evList)
assert.Error(t, err)
// evidence with a different timestamp should fail
evList = types.EvidenceList{badTimeEv}
err = pool.CheckEvidence(evList)
assert.Error(t, err)
}
func makeVote(