github.com/evdatsion/aphelion-dpos-bft@v0.32.1/evidence/store.go (about)

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