|
@ -4,7 +4,7 @@ import ( |
|
|
"bytes" |
|
|
"bytes" |
|
|
"errors" |
|
|
"errors" |
|
|
"fmt" |
|
|
"fmt" |
|
|
"reflect" |
|
|
|
|
|
|
|
|
"sort" |
|
|
"sync" |
|
|
"sync" |
|
|
"sync/atomic" |
|
|
"sync/atomic" |
|
|
"time" |
|
|
"time" |
|
@ -13,10 +13,8 @@ import ( |
|
|
gogotypes "github.com/gogo/protobuf/types" |
|
|
gogotypes "github.com/gogo/protobuf/types" |
|
|
dbm "github.com/tendermint/tm-db" |
|
|
dbm "github.com/tendermint/tm-db" |
|
|
|
|
|
|
|
|
abci "github.com/tendermint/tendermint/abci/types" |
|
|
|
|
|
clist "github.com/tendermint/tendermint/libs/clist" |
|
|
clist "github.com/tendermint/tendermint/libs/clist" |
|
|
"github.com/tendermint/tendermint/libs/log" |
|
|
"github.com/tendermint/tendermint/libs/log" |
|
|
evproto "github.com/tendermint/tendermint/proto/tendermint/evidence" |
|
|
|
|
|
tmproto "github.com/tendermint/tendermint/proto/tendermint/types" |
|
|
tmproto "github.com/tendermint/tendermint/proto/tendermint/types" |
|
|
sm "github.com/tendermint/tendermint/state" |
|
|
sm "github.com/tendermint/tendermint/state" |
|
|
"github.com/tendermint/tendermint/types" |
|
|
"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
|
|
|
// 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
|
|
|
// sanity check
|
|
|
if state.LastBlockHeight <= evpool.state.LastBlockHeight { |
|
|
if state.LastBlockHeight <= evpool.state.LastBlockHeight { |
|
|
panic(fmt.Sprintf( |
|
|
panic(fmt.Sprintf( |
|
@ -109,6 +107,8 @@ func (evpool *Pool) Update(state sm.State) { |
|
|
// update the state
|
|
|
// update the state
|
|
|
evpool.updateState(state) |
|
|
evpool.updateState(state) |
|
|
|
|
|
|
|
|
|
|
|
evpool.markEvidenceAsCommitted(ev) |
|
|
|
|
|
|
|
|
// prune pending evidence when it has expired. This also updates when the next evidence will expire
|
|
|
// prune pending evidence when it has expired. This also updates when the next evidence will expire
|
|
|
if evpool.Size() > 0 && state.LastBlockHeight > evpool.pruningHeight && |
|
|
if evpool.Size() > 0 && state.LastBlockHeight > evpool.pruningHeight && |
|
|
state.LastBlockTime.After(evpool.pruningTime) { |
|
|
state.LastBlockTime.After(evpool.pruningTime) { |
|
@ -135,13 +135,13 @@ func (evpool *Pool) AddEvidence(ev types.Evidence) error { |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 1) Verify against state.
|
|
|
// 1) Verify against state.
|
|
|
evInfo, err := evpool.verify(ev) |
|
|
|
|
|
|
|
|
err := evpool.verify(ev) |
|
|
if err != nil { |
|
|
if err != nil { |
|
|
return types.NewErrInvalidEvidence(ev, err) |
|
|
return types.NewErrInvalidEvidence(ev, err) |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 2) Save to store.
|
|
|
// 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) |
|
|
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 |
|
|
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.
|
|
|
// we already have this evidence, log this but don't return an error.
|
|
|
if evpool.isPending(ev) { |
|
|
if evpool.isPending(ev) { |
|
@ -167,23 +163,7 @@ func (evpool *Pool) AddEvidenceFromConsensus(ev types.Evidence, time time.Time, |
|
|
return nil |
|
|
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) |
|
|
return fmt.Errorf("can't add evidence to pending list: %w", err) |
|
|
} |
|
|
} |
|
|
// add evidence to be gossiped with peers
|
|
|
// 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")} |
|
|
return &types.ErrInvalidEvidence{Evidence: ev, Reason: errors.New("evidence was already committed")} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
evInfo, err := evpool.verify(ev) |
|
|
|
|
|
|
|
|
err := evpool.verify(ev) |
|
|
if err != nil { |
|
|
if err != nil { |
|
|
return &types.ErrInvalidEvidence{Evidence: ev, Reason: err} |
|
|
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
|
|
|
// Something went wrong with adding the evidence but we already know it is valid
|
|
|
// hence we log an error and continue
|
|
|
// 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) |
|
|
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 |
|
|
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
|
|
|
// EvidenceFront goes to the first evidence in the clist
|
|
|
func (evpool *Pool) EvidenceFront() *clist.CElement { |
|
|
func (evpool *Pool) EvidenceFront() *clist.CElement { |
|
|
return evpool.evidenceList.Front() |
|
|
return evpool.evidenceList.Front() |
|
@ -330,6 +231,7 @@ func (evpool *Pool) SetLogger(l log.Logger) { |
|
|
evpool.logger = l |
|
|
evpool.logger = l |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// Size returns the number of evidence in the pool.
|
|
|
func (evpool *Pool) Size() uint32 { |
|
|
func (evpool *Pool) Size() uint32 { |
|
|
return atomic.LoadUint32(&evpool.evidenceSize) |
|
|
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
|
|
|
// 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.
|
|
|
// quickly conclude that the evidence is already valid.
|
|
|
func (evpool *Pool) fastCheck(ev types.Evidence) bool { |
|
|
func (evpool *Pool) fastCheck(ev types.Evidence) bool { |
|
|
key := keyPending(ev) |
|
|
|
|
|
if lcae, ok := ev.(*types.LightClientAttackEvidence); ok { |
|
|
if lcae, ok := ev.(*types.LightClientAttackEvidence); ok { |
|
|
|
|
|
key := keyPending(ev) |
|
|
evBytes, err := evpool.evidenceStore.Get(key) |
|
|
evBytes, err := evpool.evidenceStore.Get(key) |
|
|
if evBytes == nil { // the evidence is not in the nodes pending list
|
|
|
if evBytes == nil { // the evidence is not in the nodes pending list
|
|
|
return false |
|
|
return false |
|
|
} |
|
|
} |
|
|
if err != nil { |
|
|
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 |
|
|
return false |
|
|
} |
|
|
} |
|
|
evInfo, err := bytesToInfo(evBytes) |
|
|
|
|
|
|
|
|
var trustedPb tmproto.LightClientAttackEvidence |
|
|
|
|
|
err = trustedPb.Unmarshal(evBytes) |
|
|
if err != nil { |
|
|
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 |
|
|
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 |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
// 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") |
|
|
|
|
|
|
|
|
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 |
|
|
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 |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
return true |
|
|
return true |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
@ -482,8 +337,8 @@ func (evpool *Pool) isPending(evidence types.Evidence) bool { |
|
|
return ok |
|
|
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 { |
|
|
if err != nil { |
|
|
return fmt.Errorf("unable to convert to proto, err: %w", err) |
|
|
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) |
|
|
return fmt.Errorf("unable to marshal evidence: %w", err) |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
key := keyPending(evInfo.Evidence) |
|
|
|
|
|
|
|
|
key := keyPending(ev) |
|
|
|
|
|
|
|
|
err = evpool.evidenceStore.Set(key, evBytes) |
|
|
err = evpool.evidenceStore.Set(key, evBytes) |
|
|
if err != nil { |
|
|
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.
|
|
|
// listEvidence retrieves lists evidence from oldest to newest within maxBytes.
|
|
|
// If maxBytes is -1, there's no cap on the size of returned evidence.
|
|
|
// 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) { |
|
|
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}) |
|
|
iter, err := dbm.IteratePrefix(evpool.evidenceStore, []byte{prefixKey}) |
|
|
if err != nil { |
|
|
if err != nil { |
|
|
return nil, totalSize, fmt.Errorf("database error: %v", err) |
|
|
return nil, totalSize, fmt.Errorf("database error: %v", err) |
|
|
} |
|
|
} |
|
|
defer iter.Close() |
|
|
defer iter.Close() |
|
|
for ; iter.Valid(); iter.Next() { |
|
|
for ; iter.Valid(); iter.Next() { |
|
|
evInfo, err := bytesToInfo(iter.Value()) |
|
|
|
|
|
|
|
|
var evpb tmproto.Evidence |
|
|
|
|
|
err := evpb.Unmarshal(iter.Value()) |
|
|
if err != nil { |
|
|
if err != nil { |
|
|
return nil, totalSize, err |
|
|
|
|
|
|
|
|
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 |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
totalSize += evInfo.ByteSize |
|
|
|
|
|
|
|
|
|
|
|
if maxBytes != -1 && totalSize > maxBytes { |
|
|
|
|
|
return evidence, totalSize - evInfo.ByteSize, nil |
|
|
|
|
|
|
|
|
ev, err := types.EvidenceFromProto(&evpb) |
|
|
|
|
|
if err != nil { |
|
|
|
|
|
return nil, totalSize, err |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
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 |
|
|
return evidence, totalSize, nil |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
@ -550,22 +454,22 @@ func (evpool *Pool) removeExpiredPendingEvidence() (int64, time.Time) { |
|
|
defer iter.Close() |
|
|
defer iter.Close() |
|
|
blockEvidenceMap := make(map[string]struct{}) |
|
|
blockEvidenceMap := make(map[string]struct{}) |
|
|
for ; iter.Valid(); iter.Next() { |
|
|
for ; iter.Valid(); iter.Next() { |
|
|
evInfo, err := bytesToInfo(iter.Value()) |
|
|
|
|
|
|
|
|
ev, err := bytesToEv(iter.Value()) |
|
|
if err != nil { |
|
|
if err != nil { |
|
|
evpool.logger.Error("Error in transition evidence from protobuf", "err", err) |
|
|
evpool.logger.Error("Error in transition evidence from protobuf", "err", err) |
|
|
continue |
|
|
continue |
|
|
} |
|
|
} |
|
|
if !evpool.isExpired(evInfo.Evidence.Height(), evInfo.Time) { |
|
|
|
|
|
|
|
|
if !evpool.isExpired(ev.Height(), ev.Time()) { |
|
|
if len(blockEvidenceMap) != 0 { |
|
|
if len(blockEvidenceMap) != 0 { |
|
|
evpool.removeEvidenceFromList(blockEvidenceMap) |
|
|
evpool.removeEvidenceFromList(blockEvidenceMap) |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// return the height and time with which this evidence will have expired so we know when to prune next
|
|
|
// 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
|
|
|
// We either have no pending evidence or all evidence has expired
|
|
|
if len(blockEvidenceMap) != 0 { |
|
|
if len(blockEvidenceMap) != 0 { |
|
@ -593,14 +497,14 @@ func (evpool *Pool) updateState(state sm.State) { |
|
|
evpool.state = 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) |
|
|
err := evpb.Unmarshal(evBytes) |
|
|
if err != nil { |
|
|
if err != nil { |
|
|
return info{}, err |
|
|
|
|
|
|
|
|
return &types.DuplicateVoteEvidence{}, err |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
return infoFromProto(&evpb) |
|
|
|
|
|
|
|
|
return types.EvidenceFromProto(&evpb) |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
func evMapKey(ev types.Evidence) string { |
|
|
func evMapKey(ev types.Evidence) string { |
|
|