github.com/devwanda/aphelion-staking@v0.33.9/evidence/store.go (about) 1 package evidence 2 3 import ( 4 "fmt" 5 6 dbm "github.com/tendermint/tm-db" 7 8 "github.com/devwanda/aphelion-staking/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 return _key("%s/%s/%X", baseKeyLookup, bE(height), hash) 52 } 53 54 func keyOutqueue(evidence types.Evidence, priority int64) []byte { 55 return _key("%s/%s/%s/%X", baseKeyOutqueue, bE(priority), bE(evidence.Height()), evidence.Hash()) 56 } 57 58 func keyPending(evidence types.Evidence) []byte { 59 return _key("%s/%s/%X", baseKeyPending, bE(evidence.Height()), evidence.Hash()) 60 } 61 62 func _key(format string, o ...interface{}) []byte { 63 return []byte(fmt.Sprintf(format, o...)) 64 } 65 66 // Store is a store of all the evidence we've seen, including 67 // evidence that has been committed, evidence that has been verified but not broadcast, 68 // and evidence that has been broadcast but not yet committed. 69 type Store struct { 70 db dbm.DB 71 } 72 73 func NewStore(db dbm.DB) *Store { 74 return &Store{ 75 db: db, 76 } 77 } 78 79 // PriorityEvidence returns the evidence from the outqueue, sorted by highest priority. 80 func (store *Store) PriorityEvidence() (evidence []types.Evidence) { 81 // reverse the order so highest priority is first 82 l := store.listEvidence(baseKeyOutqueue, -1) 83 for i, j := 0, len(l)-1; i < j; i, j = i+1, j-1 { 84 l[i], l[j] = l[j], l[i] 85 } 86 87 return l 88 } 89 90 // PendingEvidence returns up to maxNum known, uncommitted evidence. 91 // If maxNum is -1, all evidence is returned. 92 func (store *Store) PendingEvidence(maxNum int64) (evidence []types.Evidence) { 93 return store.listEvidence(baseKeyPending, maxNum) 94 } 95 96 // listEvidence lists up to maxNum pieces of evidence for the given prefix key. 97 // It is wrapped by PriorityEvidence and PendingEvidence for convenience. 98 // If maxNum is -1, there's no cap on the size of returned evidence. 99 func (store *Store) listEvidence(prefixKey string, maxNum int64) (evidence []types.Evidence) { 100 var count int64 101 iter, err := dbm.IteratePrefix(store.db, []byte(prefixKey)) 102 if err != nil { 103 panic(err) 104 } 105 defer iter.Close() 106 for ; iter.Valid(); iter.Next() { 107 val := iter.Value() 108 109 if count == maxNum { 110 return evidence 111 } 112 count++ 113 114 var ei Info 115 err := cdc.UnmarshalBinaryBare(val, &ei) 116 if err != nil { 117 panic(err) 118 } 119 evidence = append(evidence, ei.Evidence) 120 } 121 return evidence 122 } 123 124 // GetInfo fetches the Info with the given height and hash. 125 // If not found, ei.Evidence is nil. 126 func (store *Store) GetInfo(height int64, hash []byte) Info { 127 key := keyLookupFromHeightAndHash(height, hash) 128 val, err := store.db.Get(key) 129 if err != nil { 130 panic(err) 131 } 132 if len(val) == 0 { 133 return Info{} 134 } 135 var ei Info 136 err = cdc.UnmarshalBinaryBare(val, &ei) 137 if err != nil { 138 panic(err) 139 } 140 return ei 141 } 142 143 // Has checks if the evidence is already stored 144 func (store *Store) Has(evidence types.Evidence) bool { 145 key := keyLookup(evidence) 146 ok, _ := store.db.Has(key) 147 return ok 148 } 149 150 // AddNewEvidence adds the given evidence to the database. 151 // It returns false if the evidence is already stored. 152 func (store *Store) AddNewEvidence(evidence types.Evidence, priority int64) (bool, error) { 153 // check if we already have seen it 154 if store.Has(evidence) { 155 return false, nil 156 } 157 158 ei := Info{ 159 Committed: false, 160 Priority: priority, 161 Evidence: evidence, 162 } 163 eiBytes := cdc.MustMarshalBinaryBare(ei) 164 165 // add it to the store 166 var err error 167 key := keyOutqueue(evidence, priority) 168 if err = store.db.Set(key, eiBytes); err != nil { 169 return false, err 170 } 171 172 key = keyPending(evidence) 173 if err = store.db.Set(key, eiBytes); err != nil { 174 return false, err 175 } 176 177 key = keyLookup(evidence) 178 if err = store.db.SetSync(key, eiBytes); err != nil { 179 return false, err 180 } 181 182 return true, nil 183 } 184 185 // MarkEvidenceAsBroadcasted removes evidence from Outqueue. 186 func (store *Store) MarkEvidenceAsBroadcasted(evidence types.Evidence) { 187 ei := store.getInfo(evidence) 188 if ei.Evidence == nil { 189 // nothing to do; we did not store the evidence yet (AddNewEvidence): 190 return 191 } 192 // remove from the outqueue 193 key := keyOutqueue(evidence, ei.Priority) 194 store.db.Delete(key) 195 } 196 197 // MarkEvidenceAsCommitted removes evidence from pending and outqueue and sets the state to committed. 198 func (store *Store) MarkEvidenceAsCommitted(evidence types.Evidence) { 199 // if its committed, its been broadcast 200 store.MarkEvidenceAsBroadcasted(evidence) 201 202 pendingKey := keyPending(evidence) 203 store.db.Delete(pendingKey) 204 205 // committed Info doens't need priority 206 ei := Info{ 207 Committed: true, 208 Evidence: evidence, 209 Priority: 0, 210 } 211 212 lookupKey := keyLookup(evidence) 213 store.db.SetSync(lookupKey, cdc.MustMarshalBinaryBare(ei)) 214 } 215 216 //--------------------------------------------------- 217 // utils 218 219 // getInfo is convenience for calling GetInfo if we have the full evidence. 220 func (store *Store) getInfo(evidence types.Evidence) Info { 221 return store.GetInfo(evidence.Height(), evidence.Hash()) 222 }