@ -14,12 +14,17 @@ import (
"github.com/tendermint/tendermint/types"
"github.com/tendermint/tendermint/types"
)
)
// Pool maintains a pool of valid evidence in an Store.
const (
baseKeyCommitted = byte ( 0x00 ) // committed evidence
baseKeyPending = byte ( 0x01 ) // pending evidence
)
// Pool maintains a pool of valid evidence to be broadcasted and committed
type Pool struct {
type Pool struct {
logger log . Logger
logger log . Logger
store * Store
evidenceList * clist . CList // concurrent linked-list of evidence
evidenceStore dbm . DB
evidenceList * clist . CList // concurrent linked-list of evidence
// needed to load validators to verify evidence
// needed to load validators to verify evidence
stateDB dbm . DB
stateDB dbm . DB
@ -41,7 +46,6 @@ type valToLastHeightMap map[string]int64
func NewPool ( stateDB , evidenceDB dbm . DB , blockStore * store . BlockStore ) ( * Pool , error ) {
func NewPool ( stateDB , evidenceDB dbm . DB , blockStore * store . BlockStore ) ( * Pool , error ) {
var (
var (
store = NewStore ( evidenceDB )
state = sm . LoadState ( stateDB )
state = sm . LoadState ( stateDB )
)
)
@ -50,49 +54,44 @@ func NewPool(stateDB, evidenceDB dbm.DB, blockStore *store.BlockStore) (*Pool, e
return nil , err
return nil , err
}
}
return & Pool {
pool := & Pool {
stateDB : stateDB ,
stateDB : stateDB ,
blockStore : blockStore ,
blockStore : blockStore ,
state : state ,
state : state ,
logger : log . NewNopLogger ( ) ,
logger : log . NewNopLogger ( ) ,
store : store ,
evidenceStore : evidenceDB ,
evidenceList : clist . New ( ) ,
evidenceList : clist . New ( ) ,
valToLastHeight : valToLastHeight ,
valToLastHeight : valToLastHeight ,
} , nil
}
func ( evpool * Pool ) EvidenceFront ( ) * clist . CElement {
return evpool . evidenceList . Front ( )
}
func ( evpool * Pool ) EvidenceWaitChan ( ) <- chan struct { } {
return evpool . evidenceList . WaitChan ( )
}
}
// SetLogger sets the Logger.
func ( evpool * Pool ) SetLogger ( l log . Logger ) {
evpool . logger = l
}
// if pending evidence already in db, in event of prior failure, then load it back to the evidenceList
evList , err := pool . listEvidence ( baseKeyPending , - 1 )
if err != nil {
return nil , err
}
for _ , ev := range evList {
if pool . IsExpired ( ev ) {
pool . removePendingEvidence ( ev )
continue
}
pool . evidenceList . PushBack ( ev )
}
// PriorityEvidence returns the priority evidence.
func ( evpool * Pool ) PriorityEvidence ( ) [ ] types . Evidence {
return evpool . store . PriorityEvidence ( )
return pool , nil
}
}
// PendingEvidence returns up to maxNum uncommitted evidence.
// If maxNum is -1, all evidence is returned.
// PendingEvidence is used primarily as part of block proposal and returns up to maxNum of uncommitted evidence.
// If maxNum is -1, all evidence is returned. Pending evidence is prioritised based on time.
func ( evpool * Pool ) PendingEvidence ( maxNum int64 ) [ ] types . Evidence {
func ( evpool * Pool ) PendingEvidence ( maxNum int64 ) [ ] types . Evidence {
return evpool . store . PendingEvidence ( maxNum )
}
// State returns the current state of the evpool.
func ( evpool * Pool ) State ( ) sm . State {
evpool . mtx . Lock ( )
defer evpool . mtx . Unlock ( )
return evpool . state
evidence , err := evpool . listEvidence ( baseKeyPending , maxNum )
if err != nil {
evpool . logger . Error ( "Unable to retrieve pending evidence" , "err" , err )
}
return evidence
}
}
// Update loads the latest
// Update uses the latest block to update the state, the ValToLastHeight map for evidence expiration
// and to mark committed evidence
func ( evpool * Pool ) Update ( block * types . Block , state sm . State ) {
func ( evpool * Pool ) Update ( block * types . Block , state sm . State ) {
// sanity check
// sanity check
if state . LastBlockHeight != block . Height {
if state . LastBlockHeight != block . Height {
@ -146,8 +145,8 @@ func (evpool *Pool) AddEvidence(evidence types.Evidence) error {
}
}
for _ , ev := range evList {
for _ , ev := range evList {
if evpool . store . Has ( evidence ) {
return ErrEvidenceAlreadyStored { }
if evpool . Has ( ev ) {
continue
}
}
// For lunatic validator evidence, a header needs to be fetched.
// For lunatic validator evidence, a header needs to be fetched.
@ -165,17 +164,12 @@ func (evpool *Pool) AddEvidence(evidence types.Evidence) error {
return fmt . Errorf ( "failed to verify %v: %w" , ev , err )
return fmt . Errorf ( "failed to verify %v: %w" , ev , err )
}
}
// 2) Compute priority.
_ , val := valSet . GetByAddress ( ev . Address ( ) )
priority := val . VotingPower
// 3) Save to store.
_ , err := evpool . store . AddNewEvidence ( ev , priority )
if err != nil {
return fmt . Errorf ( "failed to add new evidence %v: %w" , ev , err )
// 2) Save to store.
if err := evpool . addPendingEvidence ( ev ) ; err != nil {
return fmt . Errorf ( "database error: %v" , err )
}
}
// 4 ) Add evidence to clist.
// 3) Add evidence to clist.
evpool . evidenceList . PushBack ( ev )
evpool . evidenceList . PushBack ( ev )
evpool . logger . Info ( "Verified new evidence of byzantine behaviour" , "evidence" , ev )
evpool . logger . Info ( "Verified new evidence of byzantine behaviour" , "evidence" , ev )
@ -190,20 +184,77 @@ func (evpool *Pool) MarkEvidenceAsCommitted(height int64, lastBlockTime time.Tim
// make a map of committed evidence to remove from the clist
// make a map of committed evidence to remove from the clist
blockEvidenceMap := make ( map [ string ] struct { } )
blockEvidenceMap := make ( map [ string ] struct { } )
for _ , ev := range evidence {
for _ , ev := range evidence {
evpool . store . MarkEvidenceAsCommitted ( ev )
blockEvidenceMap [ evMapKey ( ev ) ] = struct { } { }
// As the evidence is stored in the block store we only need to record the height that it was saved at.
key := keyCommitted ( ev )
evBytes := cdc . MustMarshalBinaryBare ( height )
if err := evpool . evidenceStore . Set ( key , evBytes ) ; err != nil {
evpool . logger . Error ( "Unable to add committed evidence" , "err" , err )
// if we can't move evidence to committed then don't remove the evidence from pending
continue
}
// if pending, remove from that bucket, remember not all evidence has been seen before
if evpool . IsPending ( ev ) {
evpool . removePendingEvidence ( ev )
blockEvidenceMap [ evMapKey ( ev ) ] = struct { } { }
}
}
}
// remove committed evidence from the clist
// remove committed evidence from the clist
evidenceParams := evpool . State ( ) . ConsensusParams . Evidence
evpool . removeEvidence ( height , lastBlockTime , evidenceParams , blockEvidenceMap )
if len ( blockEvidenceMap ) != 0 {
evidenceParams := evpool . State ( ) . ConsensusParams . Evidence
evpool . removeEvidenceFromList ( height , lastBlockTime , evidenceParams , blockEvidenceMap )
}
}
}
// IsCommitted returns true if we have already seen this exact evidence and it
// is already marked as committed.
// Has checks whether the evidence exists either pending or already committed
func ( evpool * Pool ) Has ( evidence types . Evidence ) bool {
return evpool . IsPending ( evidence ) || evpool . IsCommitted ( evidence )
}
// IsExpired checks whether evidence is past the maximum age where it can be used
func ( evpool * Pool ) IsExpired ( evidence types . Evidence ) bool {
var (
params = evpool . State ( ) . ConsensusParams . Evidence
ageDuration = evpool . State ( ) . LastBlockTime . Sub ( evidence . Time ( ) )
ageNumBlocks = evpool . State ( ) . LastBlockHeight - evidence . Height ( )
)
return ageNumBlocks > params . MaxAgeNumBlocks &&
ageDuration > params . MaxAgeDuration
}
// IsCommitted returns true if we have already seen this exact evidence and it is already marked as committed.
func ( evpool * Pool ) IsCommitted ( evidence types . Evidence ) bool {
func ( evpool * Pool ) IsCommitted ( evidence types . Evidence ) bool {
ei := evpool . store . getInfo ( evidence )
return ei . Evidence != nil && ei . Committed
key := keyCommitted ( evidence )
ok , err := evpool . evidenceStore . Has ( key )
if err != nil {
evpool . logger . Error ( "Unable to find committed evidence" , "err" , err )
}
return ok
}
// Checks whether the evidence is already pending. DB errors are passed to the logger.
func ( evpool * Pool ) IsPending ( evidence types . Evidence ) bool {
key := keyPending ( evidence )
ok , err := evpool . evidenceStore . Has ( key )
if err != nil {
evpool . logger . Error ( "Unable to find pending evidence" , "err" , err )
}
return ok
}
// EvidenceFront goes to the first evidence in the clist
func ( evpool * Pool ) EvidenceFront ( ) * clist . CElement {
return evpool . evidenceList . Front ( )
}
// EvidenceWaitChan is a channel that closes once the first evidence in the list is there. i.e Front is not nil
func ( evpool * Pool ) EvidenceWaitChan ( ) <- chan struct { } {
return evpool . evidenceList . WaitChan ( )
}
// SetLogger sets the Logger.
func ( evpool * Pool ) SetLogger ( l log . Logger ) {
evpool . logger = l
}
}
// ValidatorLastHeight returns the last height of the validator w/ the
// ValidatorLastHeight returns the last height of the validator w/ the
@ -218,7 +269,56 @@ func (evpool *Pool) ValidatorLastHeight(address []byte) int64 {
return h
return h
}
}
func ( evpool * Pool ) removeEvidence (
// State returns the current state of the evpool.
func ( evpool * Pool ) State ( ) sm . State {
evpool . mtx . Lock ( )
defer evpool . mtx . Unlock ( )
return evpool . state
}
func ( evpool * Pool ) addPendingEvidence ( evidence types . Evidence ) error {
evBytes := cdc . MustMarshalBinaryBare ( evidence )
key := keyPending ( evidence )
return evpool . evidenceStore . Set ( key , evBytes )
}
func ( evpool * Pool ) removePendingEvidence ( evidence types . Evidence ) {
key := keyPending ( evidence )
if err := evpool . evidenceStore . Delete ( key ) ; err != nil {
evpool . logger . Error ( "Unable to delete pending evidence" , "err" , err )
}
}
// listEvidence lists up to maxNum pieces of evidence for the given prefix key.
// It is wrapped by PriorityEvidence and PendingEvidence for convenience.
// If maxNum is -1, there's no cap on the size of returned evidence.
func ( evpool * Pool ) listEvidence ( prefixKey byte , maxNum int64 ) ( [ ] types . Evidence , error ) {
var count int64
var evidence [ ] types . Evidence
iter , err := dbm . IteratePrefix ( evpool . evidenceStore , [ ] byte { prefixKey } )
if err != nil {
return nil , fmt . Errorf ( "database error: %v" , err )
}
defer iter . Close ( )
for ; iter . Valid ( ) ; iter . Next ( ) {
val := iter . Value ( )
if count == maxNum {
return evidence , nil
}
count ++
var ev types . Evidence
err := cdc . UnmarshalBinaryBare ( val , & ev )
if err != nil {
return nil , err
}
evidence = append ( evidence , ev )
}
return evidence , nil
}
func ( evpool * Pool ) removeEvidenceFromList (
height int64 ,
height int64 ,
lastBlockTime time . Time ,
lastBlockTime time . Time ,
params types . EvidenceParams ,
params types . EvidenceParams ,
@ -324,3 +424,29 @@ func buildValToLastHeightMap(state sm.State, stateDB dbm.DB, blockStore *store.B
return valToLastHeight , nil
return valToLastHeight , nil
}
}
// big endian padded hex
func bE ( h int64 ) string {
return fmt . Sprintf ( "%0.16X" , h )
}
func keyCommitted ( evidence types . Evidence ) [ ] byte {
return append ( [ ] byte { baseKeyCommitted } , keySuffix ( evidence ) ... )
}
func keyPending ( evidence types . Evidence ) [ ] byte {
return append ( [ ] byte { baseKeyPending } , keySuffix ( evidence ) ... )
}
func keySuffix ( evidence types . Evidence ) [ ] byte {
return [ ] byte ( fmt . Sprintf ( "%s/%X" , bE ( evidence . Height ( ) ) , evidence . Hash ( ) ) )
}
// ErrInvalidEvidence returns when evidence failed to validate
type ErrInvalidEvidence struct {
Reason error
}
func ( e ErrInvalidEvidence ) Error ( ) string {
return fmt . Sprintf ( "evidence is not valid: %v " , e . Reason )
}