mirror of
https://github.com/tendermint/tendermint.git
synced 2026-09-20 15:04:22 +00:00
evidence: structs can independently form abci evidence (#5610)
This commit is contained in:
+123
-219
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user