github.com/aakash4dev/cometbft@v0.38.2/evidence/pool.go (about)

     1  package evidence
     2  
     3  import (
     4  	"bytes"
     5  	"errors"
     6  	"fmt"
     7  	"sync"
     8  	"sync/atomic"
     9  	"time"
    10  
    11  	"github.com/cosmos/gogoproto/proto"
    12  	gogotypes "github.com/cosmos/gogoproto/types"
    13  
    14  	dbm "github.com/aakash4dev/cometbft-db"
    15  
    16  	clist "github.com/aakash4dev/cometbft/libs/clist"
    17  	"github.com/aakash4dev/cometbft/libs/log"
    18  	cmtproto "github.com/aakash4dev/cometbft/proto/tendermint/types"
    19  	sm "github.com/aakash4dev/cometbft/state"
    20  	"github.com/aakash4dev/cometbft/types"
    21  )
    22  
    23  const (
    24  	baseKeyCommitted = byte(0x00)
    25  	baseKeyPending   = byte(0x01)
    26  )
    27  
    28  // Pool maintains a pool of valid evidence to be broadcasted and committed
    29  type Pool struct {
    30  	logger log.Logger
    31  
    32  	evidenceStore dbm.DB
    33  	evidenceList  *clist.CList // concurrent linked-list of evidence
    34  	evidenceSize  uint32       // amount of pending evidence
    35  
    36  	// needed to load validators to verify evidence
    37  	stateDB sm.Store
    38  	// needed to load headers and commits to verify evidence
    39  	blockStore BlockStore
    40  
    41  	mtx sync.Mutex
    42  	// latest state
    43  	state sm.State
    44  	// evidence from consensus is buffered to this slice, awaiting until the next height
    45  	// before being flushed to the pool. This prevents broadcasting and proposing of
    46  	// evidence before the height with which the evidence happened is finished.
    47  	consensusBuffer []duplicateVoteSet
    48  
    49  	pruningHeight int64
    50  	pruningTime   time.Time
    51  }
    52  
    53  // NewPool creates an evidence pool. If using an existing evidence store,
    54  // it will add all pending evidence to the concurrent list.
    55  func NewPool(evidenceDB dbm.DB, stateDB sm.Store, blockStore BlockStore) (*Pool, error) {
    56  	state, err := stateDB.Load()
    57  	if err != nil {
    58  		return nil, fmt.Errorf("cannot load state: %w", err)
    59  	}
    60  
    61  	pool := &Pool{
    62  		stateDB:         stateDB,
    63  		blockStore:      blockStore,
    64  		state:           state,
    65  		logger:          log.NewNopLogger(),
    66  		evidenceStore:   evidenceDB,
    67  		evidenceList:    clist.New(),
    68  		consensusBuffer: make([]duplicateVoteSet, 0),
    69  	}
    70  
    71  	// if pending evidence already in db, in event of prior failure, then check for expiration,
    72  	// update the size and load it back to the evidenceList
    73  	pool.pruningHeight, pool.pruningTime = pool.removeExpiredPendingEvidence()
    74  	evList, _, err := pool.listEvidence(baseKeyPending, -1)
    75  	if err != nil {
    76  		return nil, err
    77  	}
    78  	atomic.StoreUint32(&pool.evidenceSize, uint32(len(evList)))
    79  	for _, ev := range evList {
    80  		pool.evidenceList.PushBack(ev)
    81  	}
    82  
    83  	return pool, nil
    84  }
    85  
    86  // PendingEvidence is used primarily as part of block proposal and returns up to maxNum of uncommitted evidence.
    87  func (evpool *Pool) PendingEvidence(maxBytes int64) ([]types.Evidence, int64) {
    88  	if evpool.Size() == 0 {
    89  		return []types.Evidence{}, 0
    90  	}
    91  	evidence, size, err := evpool.listEvidence(baseKeyPending, maxBytes)
    92  	if err != nil {
    93  		evpool.logger.Error("Unable to retrieve pending evidence", "err", err)
    94  	}
    95  	return evidence, size
    96  }
    97  
    98  // Update takes both the new state and the evidence committed at that height and performs
    99  // the following operations:
   100  //  1. Take any conflicting votes from consensus and use the state's LastBlockTime to form
   101  //     DuplicateVoteEvidence and add it to the pool.
   102  //  2. Update the pool's state which contains evidence params relating to expiry.
   103  //  3. Moves pending evidence that has now been committed into the committed pool.
   104  //  4. Removes any expired evidence based on both height and time.
   105  func (evpool *Pool) Update(state sm.State, ev types.EvidenceList) {
   106  	// sanity check
   107  	if state.LastBlockHeight <= evpool.state.LastBlockHeight {
   108  		panic(fmt.Sprintf(
   109  			"failed EvidencePool.Update new state height is less than or equal to previous state height: %d <= %d",
   110  			state.LastBlockHeight,
   111  			evpool.state.LastBlockHeight,
   112  		))
   113  	}
   114  	evpool.logger.Debug("Updating evidence pool", "last_block_height", state.LastBlockHeight,
   115  		"last_block_time", state.LastBlockTime)
   116  
   117  	// flush conflicting vote pairs from the buffer, producing DuplicateVoteEvidence and
   118  	// adding it to the pool
   119  	evpool.processConsensusBuffer(state)
   120  	// update state
   121  	evpool.updateState(state)
   122  
   123  	// move committed evidence out from the pending pool and into the committed pool
   124  	evpool.markEvidenceAsCommitted(ev)
   125  
   126  	// prune pending evidence when it has expired. This also updates when the next evidence will expire
   127  	if evpool.Size() > 0 && state.LastBlockHeight > evpool.pruningHeight &&
   128  		state.LastBlockTime.After(evpool.pruningTime) {
   129  		evpool.pruningHeight, evpool.pruningTime = evpool.removeExpiredPendingEvidence()
   130  	}
   131  }
   132  
   133  // AddEvidence checks the evidence is valid and adds it to the pool.
   134  func (evpool *Pool) AddEvidence(ev types.Evidence) error {
   135  	evpool.logger.Debug("Attempting to add evidence", "ev", ev)
   136  
   137  	// We have already verified this piece of evidence - no need to do it again
   138  	if evpool.isPending(ev) {
   139  		evpool.logger.Debug("Evidence already pending, ignoring this one", "ev", ev)
   140  		return nil
   141  	}
   142  
   143  	// check that the evidence isn't already committed
   144  	if evpool.isCommitted(ev) {
   145  		// this can happen if the peer that sent us the evidence is behind so we shouldn't
   146  		// punish the peer.
   147  		evpool.logger.Debug("Evidence was already committed, ignoring this one", "ev", ev)
   148  		return nil
   149  	}
   150  
   151  	// 1) Verify against state.
   152  	err := evpool.verify(ev)
   153  	if err != nil {
   154  		return types.NewErrInvalidEvidence(ev, err)
   155  	}
   156  
   157  	// 2) Save to store.
   158  	if err := evpool.addPendingEvidence(ev); err != nil {
   159  		return fmt.Errorf("can't add evidence to pending list: %w", err)
   160  	}
   161  
   162  	// 3) Add evidence to clist.
   163  	evpool.evidenceList.PushBack(ev)
   164  
   165  	evpool.logger.Info("Verified new evidence of byzantine behavior", "evidence", ev)
   166  
   167  	return nil
   168  }
   169  
   170  // ReportConflictingVotes takes two conflicting votes and forms duplicate vote evidence,
   171  // adding it eventually to the evidence pool.
   172  //
   173  // Duplicate vote attacks happen before the block is committed and the timestamp is
   174  // finalized, thus the evidence pool holds these votes in a buffer, forming the
   175  // evidence from them once consensus at that height has been reached and `Update()` with
   176  // the new state called.
   177  //
   178  // Votes are not verified.
   179  func (evpool *Pool) ReportConflictingVotes(voteA, voteB *types.Vote) {
   180  	evpool.mtx.Lock()
   181  	defer evpool.mtx.Unlock()
   182  	evpool.consensusBuffer = append(evpool.consensusBuffer, duplicateVoteSet{
   183  		VoteA: voteA,
   184  		VoteB: voteB,
   185  	})
   186  }
   187  
   188  // CheckEvidence takes an array of evidence from a block and verifies all the evidence there.
   189  // If it has already verified the evidence then it jumps to the next one. It ensures that no
   190  // evidence has already been committed or is being proposed twice. It also adds any
   191  // evidence that it doesn't currently have so that it can quickly form ABCI Evidence later.
   192  func (evpool *Pool) CheckEvidence(evList types.EvidenceList) error {
   193  	hashes := make([][]byte, len(evList))
   194  	for idx, ev := range evList {
   195  
   196  		_, isLightEv := ev.(*types.LightClientAttackEvidence)
   197  
   198  		// We must verify light client attack evidence regardless because there could be a
   199  		// different conflicting block with the same hash.
   200  		if isLightEv || !evpool.isPending(ev) {
   201  			// check that the evidence isn't already committed
   202  			if evpool.isCommitted(ev) {
   203  				return &types.ErrInvalidEvidence{Evidence: ev, Reason: errors.New("evidence was already committed")}
   204  			}
   205  
   206  			err := evpool.verify(ev)
   207  			if err != nil {
   208  				return err
   209  			}
   210  
   211  			if err := evpool.addPendingEvidence(ev); err != nil {
   212  				// Something went wrong with adding the evidence but we already know it is valid
   213  				// hence we log an error and continue
   214  				evpool.logger.Error("Can't add evidence to pending list", "err", err, "ev", ev)
   215  			}
   216  
   217  			evpool.logger.Info("Check evidence: verified evidence of byzantine behavior", "evidence", ev)
   218  		}
   219  
   220  		// check for duplicate evidence. We cache hashes so we don't have to work them out again.
   221  		hashes[idx] = ev.Hash()
   222  		for i := idx - 1; i >= 0; i-- {
   223  			if bytes.Equal(hashes[i], hashes[idx]) {
   224  				return &types.ErrInvalidEvidence{Evidence: ev, Reason: errors.New("duplicate evidence")}
   225  			}
   226  		}
   227  	}
   228  
   229  	return nil
   230  }
   231  
   232  // EvidenceFront goes to the first evidence in the clist
   233  func (evpool *Pool) EvidenceFront() *clist.CElement {
   234  	return evpool.evidenceList.Front()
   235  }
   236  
   237  // EvidenceWaitChan is a channel that closes once the first evidence in the list is there. i.e Front is not nil
   238  func (evpool *Pool) EvidenceWaitChan() <-chan struct{} {
   239  	return evpool.evidenceList.WaitChan()
   240  }
   241  
   242  // SetLogger sets the Logger.
   243  func (evpool *Pool) SetLogger(l log.Logger) {
   244  	evpool.logger = l
   245  }
   246  
   247  // Size returns the number of evidence in the pool.
   248  func (evpool *Pool) Size() uint32 {
   249  	return atomic.LoadUint32(&evpool.evidenceSize)
   250  }
   251  
   252  // State returns the current state of the evpool.
   253  func (evpool *Pool) State() sm.State {
   254  	evpool.mtx.Lock()
   255  	defer evpool.mtx.Unlock()
   256  	return evpool.state
   257  }
   258  
   259  func (evpool *Pool) Close() error {
   260  	return evpool.evidenceStore.Close()
   261  }
   262  
   263  // IsExpired checks whether evidence or a polc is expired by checking whether a height and time is older
   264  // than set by the evidence consensus parameters
   265  func (evpool *Pool) isExpired(height int64, time time.Time) bool {
   266  	var (
   267  		params       = evpool.State().ConsensusParams.Evidence
   268  		ageDuration  = evpool.State().LastBlockTime.Sub(time)
   269  		ageNumBlocks = evpool.State().LastBlockHeight - height
   270  	)
   271  	return ageNumBlocks > params.MaxAgeNumBlocks &&
   272  		ageDuration > params.MaxAgeDuration
   273  }
   274  
   275  // IsCommitted returns true if we have already seen this exact evidence and it is already marked as committed.
   276  func (evpool *Pool) isCommitted(evidence types.Evidence) bool {
   277  	key := keyCommitted(evidence)
   278  	ok, err := evpool.evidenceStore.Has(key)
   279  	if err != nil {
   280  		evpool.logger.Error("Unable to find committed evidence", "err", err)
   281  	}
   282  	return ok
   283  }
   284  
   285  // IsPending checks whether the evidence is already pending. DB errors are passed to the logger.
   286  func (evpool *Pool) isPending(evidence types.Evidence) bool {
   287  	key := keyPending(evidence)
   288  	ok, err := evpool.evidenceStore.Has(key)
   289  	if err != nil {
   290  		evpool.logger.Error("Unable to find pending evidence", "err", err)
   291  	}
   292  	return ok
   293  }
   294  
   295  func (evpool *Pool) addPendingEvidence(ev types.Evidence) error {
   296  	evpb, err := types.EvidenceToProto(ev)
   297  	if err != nil {
   298  		return fmt.Errorf("unable to convert to proto, err: %w", err)
   299  	}
   300  
   301  	evBytes, err := evpb.Marshal()
   302  	if err != nil {
   303  		return fmt.Errorf("unable to marshal evidence: %w", err)
   304  	}
   305  
   306  	key := keyPending(ev)
   307  
   308  	err = evpool.evidenceStore.Set(key, evBytes)
   309  	if err != nil {
   310  		return fmt.Errorf("can't persist evidence: %w", err)
   311  	}
   312  	atomic.AddUint32(&evpool.evidenceSize, 1)
   313  	return nil
   314  }
   315  
   316  func (evpool *Pool) removePendingEvidence(evidence types.Evidence) {
   317  	key := keyPending(evidence)
   318  	if err := evpool.evidenceStore.Delete(key); err != nil {
   319  		evpool.logger.Error("Unable to delete pending evidence", "err", err)
   320  	} else {
   321  		atomic.AddUint32(&evpool.evidenceSize, ^uint32(0))
   322  		evpool.logger.Debug("Deleted pending evidence", "evidence", evidence)
   323  	}
   324  }
   325  
   326  // markEvidenceAsCommitted processes all the evidence in the block, marking it as
   327  // committed and removing it from the pending database.
   328  func (evpool *Pool) markEvidenceAsCommitted(evidence types.EvidenceList) {
   329  	blockEvidenceMap := make(map[string]struct{}, len(evidence))
   330  	for _, ev := range evidence {
   331  		if evpool.isPending(ev) {
   332  			evpool.removePendingEvidence(ev)
   333  			blockEvidenceMap[evMapKey(ev)] = struct{}{}
   334  		}
   335  
   336  		// Add evidence to the committed list. As the evidence is stored in the block store
   337  		// we only need to record the height that it was saved at.
   338  		key := keyCommitted(ev)
   339  
   340  		h := gogotypes.Int64Value{Value: ev.Height()}
   341  		evBytes, err := proto.Marshal(&h)
   342  		if err != nil {
   343  			evpool.logger.Error("failed to marshal committed evidence", "err", err, "key(height/hash)", key)
   344  			continue
   345  		}
   346  
   347  		if err := evpool.evidenceStore.Set(key, evBytes); err != nil {
   348  			evpool.logger.Error("Unable to save committed evidence", "err", err, "key(height/hash)", key)
   349  		}
   350  	}
   351  
   352  	// remove committed evidence from the clist
   353  	if len(blockEvidenceMap) != 0 {
   354  		evpool.removeEvidenceFromList(blockEvidenceMap)
   355  	}
   356  }
   357  
   358  // listEvidence retrieves lists evidence from oldest to newest within maxBytes.
   359  // If maxBytes is -1, there's no cap on the size of returned evidence.
   360  func (evpool *Pool) listEvidence(prefixKey byte, maxBytes int64) ([]types.Evidence, int64, error) {
   361  	var (
   362  		evSize    int64
   363  		totalSize int64
   364  		evidence  []types.Evidence
   365  		evList    cmtproto.EvidenceList // used for calculating the bytes size
   366  	)
   367  
   368  	iter, err := dbm.IteratePrefix(evpool.evidenceStore, []byte{prefixKey})
   369  	if err != nil {
   370  		return nil, totalSize, fmt.Errorf("database error: %v", err)
   371  	}
   372  	defer iter.Close()
   373  	for ; iter.Valid(); iter.Next() {
   374  		var evpb cmtproto.Evidence
   375  		err := evpb.Unmarshal(iter.Value())
   376  		if err != nil {
   377  			return evidence, totalSize, err
   378  		}
   379  		evList.Evidence = append(evList.Evidence, evpb)
   380  		evSize = int64(evList.Size())
   381  		if maxBytes != -1 && evSize > maxBytes {
   382  			if err := iter.Error(); err != nil {
   383  				return evidence, totalSize, err
   384  			}
   385  			return evidence, totalSize, nil
   386  		}
   387  
   388  		ev, err := types.EvidenceFromProto(&evpb)
   389  		if err != nil {
   390  			return nil, totalSize, err
   391  		}
   392  
   393  		totalSize = evSize
   394  		evidence = append(evidence, ev)
   395  	}
   396  
   397  	if err := iter.Error(); err != nil {
   398  		return evidence, totalSize, err
   399  	}
   400  	return evidence, totalSize, nil
   401  }
   402  
   403  func (evpool *Pool) removeExpiredPendingEvidence() (int64, time.Time) {
   404  	iter, err := dbm.IteratePrefix(evpool.evidenceStore, []byte{baseKeyPending})
   405  	if err != nil {
   406  		evpool.logger.Error("Unable to iterate over pending evidence", "err", err)
   407  		return evpool.State().LastBlockHeight, evpool.State().LastBlockTime
   408  	}
   409  	defer iter.Close()
   410  	blockEvidenceMap := make(map[string]struct{})
   411  	for ; iter.Valid(); iter.Next() {
   412  		ev, err := bytesToEv(iter.Value())
   413  		if err != nil {
   414  			evpool.logger.Error("Error in transition evidence from protobuf", "err", err)
   415  			continue
   416  		}
   417  		if !evpool.isExpired(ev.Height(), ev.Time()) {
   418  			if len(blockEvidenceMap) != 0 {
   419  				evpool.removeEvidenceFromList(blockEvidenceMap)
   420  			}
   421  
   422  			// return the height and time with which this evidence will have expired so we know when to prune next
   423  			return ev.Height() + evpool.State().ConsensusParams.Evidence.MaxAgeNumBlocks + 1,
   424  				ev.Time().Add(evpool.State().ConsensusParams.Evidence.MaxAgeDuration).Add(time.Second)
   425  		}
   426  		evpool.removePendingEvidence(ev)
   427  		blockEvidenceMap[evMapKey(ev)] = struct{}{}
   428  	}
   429  	// We either have no pending evidence or all evidence has expired
   430  	if len(blockEvidenceMap) != 0 {
   431  		evpool.removeEvidenceFromList(blockEvidenceMap)
   432  	}
   433  	return evpool.State().LastBlockHeight, evpool.State().LastBlockTime
   434  }
   435  
   436  func (evpool *Pool) removeEvidenceFromList(
   437  	blockEvidenceMap map[string]struct{},
   438  ) {
   439  	for e := evpool.evidenceList.Front(); e != nil; e = e.Next() {
   440  		// Remove from clist
   441  		ev := e.Value.(types.Evidence)
   442  		if _, ok := blockEvidenceMap[evMapKey(ev)]; ok {
   443  			evpool.evidenceList.Remove(e)
   444  			e.DetachPrev()
   445  		}
   446  	}
   447  }
   448  
   449  func (evpool *Pool) updateState(state sm.State) {
   450  	evpool.mtx.Lock()
   451  	defer evpool.mtx.Unlock()
   452  	evpool.state = state
   453  }
   454  
   455  // processConsensusBuffer converts all the duplicate votes witnessed from consensus
   456  // into DuplicateVoteEvidence. It sets the evidence timestamp to the block height
   457  // from the most recently committed block.
   458  // Evidence is then added to the pool so as to be ready to be broadcasted and proposed.
   459  func (evpool *Pool) processConsensusBuffer(state sm.State) {
   460  	evpool.mtx.Lock()
   461  	defer evpool.mtx.Unlock()
   462  	for _, voteSet := range evpool.consensusBuffer {
   463  
   464  		// Check the height of the conflicting votes and fetch the corresponding time and validator set
   465  		// to produce the valid evidence
   466  		var (
   467  			dve *types.DuplicateVoteEvidence
   468  			err error
   469  		)
   470  		switch {
   471  		case voteSet.VoteA.Height == state.LastBlockHeight:
   472  			dve, err = types.NewDuplicateVoteEvidence(
   473  				voteSet.VoteA,
   474  				voteSet.VoteB,
   475  				state.LastBlockTime,
   476  				state.LastValidators,
   477  			)
   478  
   479  		case voteSet.VoteA.Height < state.LastBlockHeight:
   480  			var valSet *types.ValidatorSet
   481  			valSet, err = evpool.stateDB.LoadValidators(voteSet.VoteA.Height)
   482  			if err != nil {
   483  				evpool.logger.Error("failed to load validator set for conflicting votes", "height",
   484  					voteSet.VoteA.Height, "err", err,
   485  				)
   486  				continue
   487  			}
   488  			blockMeta := evpool.blockStore.LoadBlockMeta(voteSet.VoteA.Height)
   489  			if blockMeta == nil {
   490  				evpool.logger.Error("failed to load block time for conflicting votes", "height", voteSet.VoteA.Height)
   491  				continue
   492  			}
   493  			dve, err = types.NewDuplicateVoteEvidence(
   494  				voteSet.VoteA,
   495  				voteSet.VoteB,
   496  				blockMeta.Header.Time,
   497  				valSet,
   498  			)
   499  
   500  		default:
   501  			// evidence pool shouldn't expect to get votes from consensus of a height that is above the current
   502  			// state. If this error is seen then perhaps consider keeping the votes in the buffer and retry
   503  			// in following heights
   504  			evpool.logger.Error("inbound duplicate votes from consensus are of a greater height than current state",
   505  				"duplicate vote height", voteSet.VoteA.Height,
   506  				"state.LastBlockHeight", state.LastBlockHeight)
   507  			continue
   508  		}
   509  		if err != nil {
   510  			evpool.logger.Error("error in generating evidence from votes", "err", err)
   511  			continue
   512  		}
   513  
   514  		// check if we already have this evidence
   515  		if evpool.isPending(dve) {
   516  			evpool.logger.Debug("evidence already pending; ignoring", "evidence", dve)
   517  			continue
   518  		}
   519  
   520  		// check that the evidence is not already committed on chain
   521  		if evpool.isCommitted(dve) {
   522  			evpool.logger.Debug("evidence already committed; ignoring", "evidence", dve)
   523  			continue
   524  		}
   525  
   526  		if err := evpool.addPendingEvidence(dve); err != nil {
   527  			evpool.logger.Error("failed to flush evidence from consensus buffer to pending list: %w", err)
   528  			continue
   529  		}
   530  
   531  		evpool.evidenceList.PushBack(dve)
   532  
   533  		evpool.logger.Info("verified new evidence of byzantine behavior", "evidence", dve)
   534  	}
   535  	// reset consensus buffer
   536  	evpool.consensusBuffer = make([]duplicateVoteSet, 0)
   537  }
   538  
   539  type duplicateVoteSet struct {
   540  	VoteA *types.Vote
   541  	VoteB *types.Vote
   542  }
   543  
   544  func bytesToEv(evBytes []byte) (types.Evidence, error) {
   545  	var evpb cmtproto.Evidence
   546  	err := evpb.Unmarshal(evBytes)
   547  	if err != nil {
   548  		return &types.DuplicateVoteEvidence{}, err
   549  	}
   550  
   551  	return types.EvidenceFromProto(&evpb)
   552  }
   553  
   554  func evMapKey(ev types.Evidence) string {
   555  	return string(ev.Hash())
   556  }
   557  
   558  // big endian padded hex
   559  func bE(h int64) string {
   560  	return fmt.Sprintf("%0.16X", h)
   561  }
   562  
   563  func keyCommitted(evidence types.Evidence) []byte {
   564  	return append([]byte{baseKeyCommitted}, keySuffix(evidence)...)
   565  }
   566  
   567  func keyPending(evidence types.Evidence) []byte {
   568  	return append([]byte{baseKeyPending}, keySuffix(evidence)...)
   569  }
   570  
   571  func keySuffix(evidence types.Evidence) []byte {
   572  	return []byte(fmt.Sprintf("%s/%X", bE(evidence.Height()), evidence.Hash()))
   573  }