github.com/koko1123/flow-go-1@v0.29.6/storage/badger/transaction_results.go (about) 1 package badger 2 3 import ( 4 "encoding/binary" 5 "encoding/hex" 6 "fmt" 7 8 "github.com/dgraph-io/badger/v3" 9 10 "github.com/koko1123/flow-go-1/model/flow" 11 "github.com/koko1123/flow-go-1/module" 12 "github.com/koko1123/flow-go-1/module/metrics" 13 "github.com/koko1123/flow-go-1/storage" 14 "github.com/koko1123/flow-go-1/storage/badger/operation" 15 ) 16 17 type TransactionResults struct { 18 db *badger.DB 19 cache *Cache 20 indexCache *Cache 21 blockCache *Cache 22 } 23 24 func KeyFromBlockIDTransactionID(blockID flow.Identifier, txID flow.Identifier) string { 25 return fmt.Sprintf("%x%x", blockID, txID) 26 } 27 28 func KeyFromBlockIDIndex(blockID flow.Identifier, txIndex uint32) string { 29 idData := make([]byte, 4) //uint32 fits into 4 bytes 30 binary.BigEndian.PutUint32(idData, txIndex) 31 return fmt.Sprintf("%x%x", blockID, idData) 32 } 33 34 func KeyFromBlockID(blockID flow.Identifier) string { 35 return blockID.String() 36 } 37 38 func KeyToBlockIDTransactionID(key string) (flow.Identifier, flow.Identifier, error) { 39 blockIDStr := key[:64] 40 txIDStr := key[64:] 41 blockID, err := flow.HexStringToIdentifier(blockIDStr) 42 if err != nil { 43 return flow.ZeroID, flow.ZeroID, fmt.Errorf("could not get block ID: %w", err) 44 } 45 46 txID, err := flow.HexStringToIdentifier(txIDStr) 47 if err != nil { 48 return flow.ZeroID, flow.ZeroID, fmt.Errorf("could not get transaction id: %w", err) 49 } 50 51 return blockID, txID, nil 52 } 53 54 func KeyToBlockIDIndex(key string) (flow.Identifier, uint32, error) { 55 blockIDStr := key[:64] 56 indexStr := key[64:] 57 blockID, err := flow.HexStringToIdentifier(blockIDStr) 58 if err != nil { 59 return flow.ZeroID, 0, fmt.Errorf("could not get block ID: %w", err) 60 } 61 62 txIndexBytes, err := hex.DecodeString(indexStr) 63 if err != nil { 64 return flow.ZeroID, 0, fmt.Errorf("could not get transaction index: %w", err) 65 } 66 if len(txIndexBytes) != 4 { 67 return flow.ZeroID, 0, fmt.Errorf("could not get transaction index - invalid length: %d", len(txIndexBytes)) 68 } 69 70 txIndex := binary.BigEndian.Uint32(txIndexBytes) 71 72 return blockID, txIndex, nil 73 } 74 75 func KeyToBlockID(key string) (flow.Identifier, error) { 76 77 blockID, err := flow.HexStringToIdentifier(key) 78 if err != nil { 79 return flow.ZeroID, fmt.Errorf("could not get block ID: %w", err) 80 } 81 82 return blockID, err 83 } 84 85 func NewTransactionResults(collector module.CacheMetrics, db *badger.DB, transactionResultsCacheSize uint) *TransactionResults { 86 retrieve := func(key interface{}) func(tx *badger.Txn) (interface{}, error) { 87 var txResult flow.TransactionResult 88 return func(tx *badger.Txn) (interface{}, error) { 89 90 blockID, txID, err := KeyToBlockIDTransactionID(key.(string)) 91 if err != nil { 92 return nil, fmt.Errorf("could not convert key: %w", err) 93 } 94 95 err = operation.RetrieveTransactionResult(blockID, txID, &txResult)(tx) 96 if err != nil { 97 return nil, handleError(err, flow.TransactionResult{}) 98 } 99 return txResult, nil 100 } 101 } 102 retrieveIndex := func(key interface{}) func(tx *badger.Txn) (interface{}, error) { 103 var txResult flow.TransactionResult 104 return func(tx *badger.Txn) (interface{}, error) { 105 106 blockID, txIndex, err := KeyToBlockIDIndex(key.(string)) 107 if err != nil { 108 return nil, fmt.Errorf("could not convert index key: %w", err) 109 } 110 111 err = operation.RetrieveTransactionResultByIndex(blockID, txIndex, &txResult)(tx) 112 if err != nil { 113 return nil, handleError(err, flow.TransactionResult{}) 114 } 115 return txResult, nil 116 } 117 } 118 retrieveForBlock := func(key interface{}) func(tx *badger.Txn) (interface{}, error) { 119 var txResults []flow.TransactionResult 120 return func(tx *badger.Txn) (interface{}, error) { 121 122 blockID, err := KeyToBlockID(key.(string)) 123 if err != nil { 124 return nil, fmt.Errorf("could not convert index key: %w", err) 125 } 126 127 err = operation.LookupTransactionResultsByBlockIDUsingIndex(blockID, &txResults)(tx) 128 if err != nil { 129 return nil, handleError(err, flow.TransactionResult{}) 130 } 131 return txResults, nil 132 } 133 } 134 return &TransactionResults{ 135 db: db, 136 cache: newCache(collector, metrics.ResourceTransactionResults, 137 withLimit(transactionResultsCacheSize), 138 withStore(noopStore), 139 withRetrieve(retrieve), 140 ), 141 indexCache: newCache(collector, metrics.ResourceTransactionResultIndices, 142 withLimit(transactionResultsCacheSize), 143 withStore(noopStore), 144 withRetrieve(retrieveIndex), 145 ), 146 blockCache: newCache(collector, metrics.ResourceTransactionResultIndices, 147 withLimit(transactionResultsCacheSize), 148 withStore(noopStore), 149 withRetrieve(retrieveForBlock), 150 ), 151 } 152 } 153 154 // BatchStore will store the transaction results for the given block ID in a batch 155 func (tr *TransactionResults) BatchStore(blockID flow.Identifier, transactionResults []flow.TransactionResult, batch storage.BatchStorage) error { 156 writeBatch := batch.GetWriter() 157 158 for i, result := range transactionResults { 159 err := operation.BatchInsertTransactionResult(blockID, &result)(writeBatch) 160 if err != nil { 161 return fmt.Errorf("cannot batch insert tx result: %w", err) 162 } 163 164 err = operation.BatchIndexTransactionResult(blockID, uint32(i), &result)(writeBatch) 165 if err != nil { 166 return fmt.Errorf("cannot batch index tx result: %w", err) 167 } 168 } 169 170 batch.OnSucceed(func() { 171 for i, result := range transactionResults { 172 key := KeyFromBlockIDTransactionID(blockID, result.TransactionID) 173 // cache for each transaction, so that it's faster to retrieve 174 tr.cache.Insert(key, result) 175 176 index := uint32(i) 177 178 keyIndex := KeyFromBlockIDIndex(blockID, index) 179 tr.indexCache.Insert(keyIndex, result) 180 } 181 182 key := KeyFromBlockID(blockID) 183 tr.blockCache.Insert(key, transactionResults) 184 }) 185 return nil 186 } 187 188 // ByBlockIDTransactionID returns the runtime transaction result for the given block ID and transaction ID 189 func (tr *TransactionResults) ByBlockIDTransactionID(blockID flow.Identifier, txID flow.Identifier) (*flow.TransactionResult, error) { 190 tx := tr.db.NewTransaction(false) 191 defer tx.Discard() 192 key := KeyFromBlockIDTransactionID(blockID, txID) 193 val, err := tr.cache.Get(key)(tx) 194 if err != nil { 195 return nil, err 196 } 197 transactionResult, ok := val.(flow.TransactionResult) 198 if !ok { 199 return nil, fmt.Errorf("could not convert transaction result: %w", err) 200 } 201 return &transactionResult, nil 202 } 203 204 // ByBlockIDTransactionIndex returns the runtime transaction result for the given block ID and transaction index 205 func (tr *TransactionResults) ByBlockIDTransactionIndex(blockID flow.Identifier, txIndex uint32) (*flow.TransactionResult, error) { 206 tx := tr.db.NewTransaction(false) 207 defer tx.Discard() 208 key := KeyFromBlockIDIndex(blockID, txIndex) 209 val, err := tr.indexCache.Get(key)(tx) 210 if err != nil { 211 return nil, err 212 } 213 transactionResult, ok := val.(flow.TransactionResult) 214 if !ok { 215 return nil, fmt.Errorf("could not convert transaction result: %w", err) 216 } 217 return &transactionResult, nil 218 } 219 220 // ByBlockID gets all transaction results for a block, ordered by transaction index 221 func (tr *TransactionResults) ByBlockID(blockID flow.Identifier) ([]flow.TransactionResult, error) { 222 tx := tr.db.NewTransaction(false) 223 defer tx.Discard() 224 key := KeyFromBlockID(blockID) 225 val, err := tr.blockCache.Get(key)(tx) 226 if err != nil { 227 return nil, err 228 } 229 transactionResults, ok := val.([]flow.TransactionResult) 230 if !ok { 231 return nil, fmt.Errorf("could not convert transaction result: %w", err) 232 } 233 return transactionResults, nil 234 } 235 236 // RemoveByBlockID removes transaction results by block ID 237 func (tr *TransactionResults) RemoveByBlockID(blockID flow.Identifier) error { 238 return tr.db.Update(operation.RemoveTransactionResultsByBlockID(blockID)) 239 } 240 241 // BatchRemoveByBlockID batch removes transaction results by block ID 242 func (tr *TransactionResults) BatchRemoveByBlockID(blockID flow.Identifier, batch storage.BatchStorage) error { 243 writeBatch := batch.GetWriter() 244 return tr.db.View(operation.BatchRemoveTransactionResultsByBlockID(blockID, writeBatch)) 245 }