github.com/arcology-network/consensus-engine@v1.9.0/evidence/pool.go (about)

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