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