github.com/okex/exchain@v1.8.0/libs/tendermint/evidence/store.go (about) 1 package evidence 2 3 import ( 4 "fmt" 5 dbm "github.com/okex/exchain/libs/tm-db" 6 7 "github.com/okex/exchain/libs/tendermint/consensus" 8 "github.com/okex/exchain/libs/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 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 if consensus.GetActiveVC() { 120 if ev, ok := ei.Evidence.(*types.DuplicateVoteEvidence); ok { 121 if ev.VoteA.Round == 0 && ev.VoteB.Round == 0 { 122 continue 123 } 124 } 125 } 126 evidence = append(evidence, ei.Evidence) 127 } 128 return evidence 129 } 130 131 // GetInfo fetches the Info with the given height and hash. 132 // If not found, ei.Evidence is nil. 133 func (store *Store) GetInfo(height int64, hash []byte) Info { 134 key := keyLookupFromHeightAndHash(height, hash) 135 val, err := store.db.Get(key) 136 if err != nil { 137 panic(err) 138 } 139 if len(val) == 0 { 140 return Info{} 141 } 142 var ei Info 143 err = cdc.UnmarshalBinaryBare(val, &ei) 144 if err != nil { 145 panic(err) 146 } 147 return ei 148 } 149 150 // Has checks if the evidence is already stored 151 func (store *Store) Has(evidence types.Evidence) bool { 152 key := keyLookup(evidence) 153 ok, _ := store.db.Has(key) 154 return ok 155 } 156 157 // AddNewEvidence adds the given evidence to the database. 158 // It returns false if the evidence is already stored. 159 func (store *Store) AddNewEvidence(evidence types.Evidence, priority int64) (bool, error) { 160 // check if we already have seen it 161 if store.Has(evidence) { 162 return false, nil 163 } 164 165 ei := Info{ 166 Committed: false, 167 Priority: priority, 168 Evidence: evidence, 169 } 170 eiBytes := cdc.MustMarshalBinaryBare(ei) 171 172 // add it to the store 173 var err error 174 key := keyOutqueue(evidence, priority) 175 if err = store.db.Set(key, eiBytes); err != nil { 176 return false, err 177 } 178 179 key = keyPending(evidence) 180 if err = store.db.Set(key, eiBytes); err != nil { 181 return false, err 182 } 183 184 key = keyLookup(evidence) 185 if err = store.db.SetSync(key, eiBytes); err != nil { 186 return false, err 187 } 188 189 return true, nil 190 } 191 192 // MarkEvidenceAsBroadcasted removes evidence from Outqueue. 193 func (store *Store) MarkEvidenceAsBroadcasted(evidence types.Evidence) { 194 ei := store.getInfo(evidence) 195 if ei.Evidence == nil { 196 // nothing to do; we did not store the evidence yet (AddNewEvidence): 197 return 198 } 199 // remove from the outqueue 200 key := keyOutqueue(evidence, ei.Priority) 201 store.db.Delete(key) 202 } 203 204 // MarkEvidenceAsCommitted removes evidence from pending and outqueue and sets the state to committed. 205 func (store *Store) MarkEvidenceAsCommitted(evidence types.Evidence) { 206 // if its committed, its been broadcast 207 store.MarkEvidenceAsBroadcasted(evidence) 208 209 pendingKey := keyPending(evidence) 210 store.db.Delete(pendingKey) 211 212 // committed Info doens't need priority 213 ei := Info{ 214 Committed: true, 215 Evidence: evidence, 216 Priority: 0, 217 } 218 219 lookupKey := keyLookup(evidence) 220 store.db.SetSync(lookupKey, cdc.MustMarshalBinaryBare(ei)) 221 } 222 223 //--------------------------------------------------- 224 // utils 225 226 // getInfo is convenience for calling GetInfo if we have the full evidence. 227 func (store *Store) getInfo(evidence types.Evidence) Info { 228 return store.GetInfo(evidence.Height(), evidence.Hash()) 229 }