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 }