github.com/pokt-network/tendermint@v0.32.11-0.20230426215212-59310158d3e9/evidence/store.go (about)

     1  package evidence
     2  
     3  import (
     4  	"fmt"
     5  
     6  	dbm "github.com/tendermint/tm-db"
     7  
     8  	"github.com/tendermint/tendermint/types"
     9  )
    10  
    11  /*
    12  Requirements:
    13  	- Valid new evidence must be persisted immediately and never forgotten
    14  	- Uncommitted evidence must be continuously broadcast
    15  	- Uncommitted evidence has a partial order, the evidence's priority
    16  
    17  Impl:
    18  	- First commit atomically in outqueue, pending, lookup.
    19  	- Once broadcast, remove from outqueue. No need to sync
    20  	- Once committed, atomically remove from pending and update lookup.
    21  
    22  Schema for indexing evidence (note you need both height and hash to find a piece of evidence):
    23  
    24  "evidence-lookup"/<evidence-height>/<evidence-hash> -> Info
    25  "evidence-outqueue"/<priority>/<evidence-height>/<evidence-hash> -> Info
    26  "evidence-pending"/<evidence-height>/<evidence-hash> -> Info
    27  */
    28  
    29  type Info struct {
    30  	Committed bool
    31  	Priority  int64
    32  	Evidence  types.Evidence
    33  }
    34  
    35  const (
    36  	baseKeyLookup   = "evidence-lookup"   // all evidence
    37  	baseKeyOutqueue = "evidence-outqueue" // not-yet broadcast
    38  	baseKeyPending  = "evidence-pending"  // broadcast but not committed
    39  )
    40  
    41  func keyLookup(evidence types.Evidence) []byte {
    42  	return keyLookupFromHeightAndHash(evidence.Height(), evidence.Hash())
    43  }
    44  
    45  // big endian padded hex
    46  func bE(h int64) string {
    47  	return fmt.Sprintf("%0.16X", h)
    48  }
    49  
    50  func keyLookupFromHeightAndHash(height int64, hash []byte) []byte {
    51  	if hash == nil {
    52  		return _key("%s/%s", baseKeyLookup, bE(height))
    53  	}
    54  	return _key("%s/%s/%X", baseKeyLookup, bE(height), hash)
    55  }
    56  
    57  func keyOutqueue(evidence types.Evidence, priority int64) []byte {
    58  	return _key("%s/%s/%s/%X", baseKeyOutqueue, bE(priority), bE(evidence.Height()), evidence.Hash())
    59  }
    60  
    61  func keyPending(evidence types.Evidence) []byte {
    62  	return _key("%s/%s/%X", baseKeyPending, bE(evidence.Height()), evidence.Hash())
    63  }
    64  
    65  func keyPendingFromHeightAndHash(height int64, hash []byte) []byte {
    66  	if hash == nil {
    67  		return _key("%s/%s", baseKeyPending, bE(height))
    68  	}
    69  	return _key("%s/%s/%X", baseKeyPending, bE(height), hash)
    70  }
    71  
    72  func _key(format string, o ...interface{}) []byte {
    73  	return []byte(fmt.Sprintf(format, o...))
    74  }
    75  
    76  // Store is a store of all the evidence we've seen, including
    77  // evidence that has been committed, evidence that has been verified but not broadcast,
    78  // and evidence that has been broadcast but not yet committed.
    79  type Store struct {
    80  	db dbm.DB
    81  }
    82  
    83  func NewStore(db dbm.DB) *Store {
    84  	return &Store{
    85  		db: db,
    86  	}
    87  }
    88  
    89  // PriorityEvidence returns the evidence from the outqueue, sorted by highest priority.
    90  func (store *Store) PriorityEvidence() (evidence []types.Evidence) {
    91  	// reverse the order so highest priority is first
    92  	l := store.listEvidence(baseKeyOutqueue, -1)
    93  	for i, j := 0, len(l)-1; i < j; i, j = i+1, j-1 {
    94  		l[i], l[j] = l[j], l[i]
    95  	}
    96  
    97  	return l
    98  }
    99  
   100  // PendingEvidence returns up to maxNum known, uncommitted evidence.
   101  // If maxNum is -1, all evidence is returned.
   102  func (store *Store) PendingEvidence(maxNum int64) (evidence []types.Evidence) {
   103  	return store.listEvidence(baseKeyPending, maxNum)
   104  }
   105  
   106  func (store *Store) HasPendingEvidence(ev types.Evidence) (bool, error) {
   107  	key := keyPending(ev)
   108  	return store.db.Has(key)
   109  }
   110  
   111  // listEvidence lists up to maxNum pieces of evidence for the given prefix key.
   112  // It is wrapped by PriorityEvidence and PendingEvidence for convenience.
   113  // If maxNum is -1, there's no cap on the size of returned evidence.
   114  func (store *Store) listEvidence(prefixKey string, maxNum int64) (evidence []types.Evidence) {
   115  	var count int64
   116  	iter, _ := dbm.IteratePrefix(store.db, []byte(prefixKey))
   117  	defer iter.Close()
   118  	for ; iter.Valid(); iter.Next() {
   119  		val := iter.Value()
   120  
   121  		if count == maxNum {
   122  			return evidence
   123  		}
   124  		count++
   125  
   126  		var ei Info
   127  		err := cdc.UnmarshalBinaryBare(val, &ei)
   128  		if err != nil {
   129  			panic(err)
   130  		}
   131  		evidence = append(evidence, ei.Evidence)
   132  	}
   133  	return evidence
   134  }
   135  
   136  // GetInfo fetches the Info with the given height and hash.
   137  // If not found, ei.Evidence is nil.
   138  func (store *Store) GetInfo(height int64, hash []byte) Info {
   139  	key := keyLookupFromHeightAndHash(height, hash)
   140  	val, _ := store.db.Get(key)
   141  
   142  	if len(val) == 0 {
   143  		return Info{}
   144  	}
   145  	var ei Info
   146  	err := cdc.UnmarshalBinaryBare(val, &ei)
   147  	if err != nil {
   148  		panic(err)
   149  	}
   150  	return ei
   151  }
   152  
   153  func (store *Store) DeleteOutqueueEvidence() {
   154  	iter, _ := dbm.IteratePrefix(store.db, []byte(baseKeyOutqueue))
   155  	defer iter.Close()
   156  	for ; iter.Valid(); iter.Next() {
   157  		store.db.Delete(iter.Key())
   158  	}
   159  }
   160  
   161  func (store *Store) DeletePendingEvidence(height int64) {
   162  	iter, _ := dbm.IteratePrefix(store.db, keyPendingFromHeightAndHash(height, nil))
   163  	defer iter.Close()
   164  	for ; iter.Valid(); iter.Next() {
   165  		store.db.Delete(iter.Key())
   166  	}
   167  }
   168  
   169  func (store *Store) DeleteLookupEvidence(height int64) {
   170  	iter, _ := dbm.IteratePrefix(store.db, keyLookupFromHeightAndHash(height, nil))
   171  	defer iter.Close()
   172  	for ; iter.Valid(); iter.Next() {
   173  		store.db.Delete(iter.Key())
   174  	}
   175  }
   176  
   177  func (store *Store) DeleteEvidenceFromHeight(height int64, latestHeight int64) {
   178  	store.DeleteOutqueueEvidence()
   179  	for ; height <= latestHeight; height++ {
   180  		store.DeletePendingEvidence(height)
   181  		store.DeleteLookupEvidence(height)
   182  	}
   183  }
   184  
   185  // Has checks if the evidence is already stored
   186  func (store *Store) Has(evidence types.Evidence) bool {
   187  	key := keyLookup(evidence)
   188  	ok, _ := store.db.Has(key)
   189  	return ok
   190  }
   191  
   192  // AddNewEvidence adds the given evidence to the database.
   193  // It returns false if the evidence is already stored.
   194  func (store *Store) AddNewEvidence(evidence types.Evidence, priority int64) (bool, error) {
   195  	// check if we already have seen it
   196  	if store.Has(evidence) {
   197  		return false, nil
   198  	}
   199  
   200  	ei := Info{
   201  		Committed: false,
   202  		Priority:  priority,
   203  		Evidence:  evidence,
   204  	}
   205  	eiBytes := cdc.MustMarshalBinaryBare(ei)
   206  
   207  	// add it to the store
   208  	var err error
   209  	key := keyOutqueue(evidence, priority)
   210  	if err = store.db.Set(key, eiBytes); err != nil {
   211  		return false, err
   212  	}
   213  
   214  	key = keyPending(evidence)
   215  	if err = store.db.Set(key, eiBytes); err != nil {
   216  		return false, err
   217  	}
   218  
   219  	key = keyLookup(evidence)
   220  	if err = store.db.SetSync(key, eiBytes); err != nil {
   221  		return false, err
   222  	}
   223  
   224  	return true, nil
   225  }
   226  
   227  // MarkEvidenceAsBroadcasted removes evidence from Outqueue.
   228  func (store *Store) MarkEvidenceAsBroadcasted(evidence types.Evidence) {
   229  	ei := store.getInfo(evidence)
   230  	if ei.Evidence == nil {
   231  		// nothing to do; we did not store the evidence yet (AddNewEvidence):
   232  		return
   233  	}
   234  	// remove from the outqueue
   235  	key := keyOutqueue(evidence, ei.Priority)
   236  	store.db.Delete(key)
   237  }
   238  
   239  // MarkEvidenceAsCommitted removes evidence from pending and outqueue and sets the state to committed.
   240  func (store *Store) MarkEvidenceAsCommitted(evidence types.Evidence) {
   241  	// if its committed, its been broadcast
   242  	store.MarkEvidenceAsBroadcasted(evidence)
   243  
   244  	pendingKey := keyPending(evidence)
   245  	store.db.Delete(pendingKey)
   246  
   247  	// committed Info doens't need priority
   248  	ei := Info{
   249  		Committed: true,
   250  		Evidence:  evidence,
   251  		Priority:  0,
   252  	}
   253  
   254  	lookupKey := keyLookup(evidence)
   255  	store.db.SetSync(lookupKey, cdc.MustMarshalBinaryBare(ei))
   256  }
   257  
   258  //---------------------------------------------------
   259  // utils
   260  
   261  // getInfo is convenience for calling GetInfo if we have the full evidence.
   262  func (store *Store) getInfo(evidence types.Evidence) Info {
   263  	return store.GetInfo(evidence.Height(), evidence.Hash())
   264  }