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 }