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

This commit is contained in:
Callum Waters
2020-11-04 17:14:48 +01:00
parent 70a62be5c6
commit 9d354c842e
35 changed files with 1532 additions and 1917 deletions

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 {