github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/storage/badger/receipts.go (about) 1 package badger 2 3 import ( 4 "errors" 5 "fmt" 6 7 "github.com/dgraph-io/badger/v2" 8 9 "github.com/onflow/flow-go/model/flow" 10 "github.com/onflow/flow-go/module" 11 "github.com/onflow/flow-go/module/metrics" 12 "github.com/onflow/flow-go/storage" 13 "github.com/onflow/flow-go/storage/badger/operation" 14 "github.com/onflow/flow-go/storage/badger/transaction" 15 ) 16 17 // ExecutionReceipts implements storage for execution receipts. 18 type ExecutionReceipts struct { 19 db *badger.DB 20 results *ExecutionResults 21 cache *Cache[flow.Identifier, *flow.ExecutionReceipt] 22 } 23 24 // NewExecutionReceipts Creates ExecutionReceipts instance which is a database of receipts which 25 // supports storing and indexing receipts by receipt ID and block ID. 26 func NewExecutionReceipts(collector module.CacheMetrics, db *badger.DB, results *ExecutionResults, cacheSize uint) *ExecutionReceipts { 27 store := func(receiptTD flow.Identifier, receipt *flow.ExecutionReceipt) func(*transaction.Tx) error { 28 receiptID := receipt.ID() 29 30 // assemble DB operations to store result (no execution) 31 storeResultOps := results.store(&receipt.ExecutionResult) 32 // assemble DB operations to index receipt (no execution) 33 storeReceiptOps := transaction.WithTx(operation.SkipDuplicates(operation.InsertExecutionReceiptMeta(receiptID, receipt.Meta()))) 34 // assemble DB operations to index receipt by the block it computes (no execution) 35 indexReceiptOps := transaction.WithTx(operation.SkipDuplicates( 36 operation.IndexExecutionReceipts(receipt.ExecutionResult.BlockID, receiptID), 37 )) 38 39 return func(tx *transaction.Tx) error { 40 err := storeResultOps(tx) // execute operations to store results 41 if err != nil { 42 return fmt.Errorf("could not store result: %w", err) 43 } 44 err = storeReceiptOps(tx) // execute operations to store receipt-specific meta-data 45 if err != nil { 46 return fmt.Errorf("could not store receipt metadata: %w", err) 47 } 48 err = indexReceiptOps(tx) 49 if err != nil { 50 return fmt.Errorf("could not index receipt by the block it computes: %w", err) 51 } 52 return nil 53 } 54 } 55 56 retrieve := func(receiptID flow.Identifier) func(tx *badger.Txn) (*flow.ExecutionReceipt, error) { 57 return func(tx *badger.Txn) (*flow.ExecutionReceipt, error) { 58 var meta flow.ExecutionReceiptMeta 59 err := operation.RetrieveExecutionReceiptMeta(receiptID, &meta)(tx) 60 if err != nil { 61 return nil, fmt.Errorf("could not retrieve receipt meta: %w", err) 62 } 63 result, err := results.byID(meta.ResultID)(tx) 64 if err != nil { 65 return nil, fmt.Errorf("could not retrieve result: %w", err) 66 } 67 return flow.ExecutionReceiptFromMeta(meta, *result), nil 68 } 69 } 70 71 return &ExecutionReceipts{ 72 db: db, 73 results: results, 74 cache: newCache[flow.Identifier, *flow.ExecutionReceipt](collector, metrics.ResourceReceipt, 75 withLimit[flow.Identifier, *flow.ExecutionReceipt](cacheSize), 76 withStore(store), 77 withRetrieve(retrieve)), 78 } 79 } 80 81 // storeMyReceipt assembles the operations to store an arbitrary receipt. 82 func (r *ExecutionReceipts) storeTx(receipt *flow.ExecutionReceipt) func(*transaction.Tx) error { 83 return r.cache.PutTx(receipt.ID(), receipt) 84 } 85 86 func (r *ExecutionReceipts) byID(receiptID flow.Identifier) func(*badger.Txn) (*flow.ExecutionReceipt, error) { 87 retrievalOps := r.cache.Get(receiptID) // assemble DB operations to retrieve receipt (no execution) 88 return func(tx *badger.Txn) (*flow.ExecutionReceipt, error) { 89 val, err := retrievalOps(tx) // execute operations to retrieve receipt 90 if err != nil { 91 return nil, err 92 } 93 return val, nil 94 } 95 } 96 97 func (r *ExecutionReceipts) byBlockID(blockID flow.Identifier) func(*badger.Txn) ([]*flow.ExecutionReceipt, error) { 98 return func(tx *badger.Txn) ([]*flow.ExecutionReceipt, error) { 99 var receiptIDs []flow.Identifier 100 err := operation.LookupExecutionReceipts(blockID, &receiptIDs)(tx) 101 if err != nil && !errors.Is(err, storage.ErrNotFound) { 102 return nil, fmt.Errorf("could not find receipt index for block: %w", err) 103 } 104 105 var receipts []*flow.ExecutionReceipt 106 for _, id := range receiptIDs { 107 receipt, err := r.byID(id)(tx) 108 if err != nil { 109 return nil, fmt.Errorf("could not find receipt with id %v: %w", id, err) 110 } 111 receipts = append(receipts, receipt) 112 } 113 return receipts, nil 114 } 115 } 116 117 func (r *ExecutionReceipts) Store(receipt *flow.ExecutionReceipt) error { 118 return operation.RetryOnConflictTx(r.db, transaction.Update, r.storeTx(receipt)) 119 } 120 121 func (r *ExecutionReceipts) BatchStore(receipt *flow.ExecutionReceipt, batch storage.BatchStorage) error { 122 writeBatch := batch.GetWriter() 123 124 err := r.results.BatchStore(&receipt.ExecutionResult, batch) 125 if err != nil { 126 return fmt.Errorf("cannot batch store execution result inside execution receipt batch store: %w", err) 127 } 128 129 err = operation.BatchInsertExecutionReceiptMeta(receipt.ID(), receipt.Meta())(writeBatch) 130 if err != nil { 131 return fmt.Errorf("cannot batch store execution meta inside execution receipt batch store: %w", err) 132 } 133 134 err = operation.BatchIndexExecutionReceipts(receipt.ExecutionResult.BlockID, receipt.ID())(writeBatch) 135 if err != nil { 136 return fmt.Errorf("cannot batch index execution receipt inside execution receipt batch store: %w", err) 137 } 138 139 return nil 140 } 141 142 func (r *ExecutionReceipts) ByID(receiptID flow.Identifier) (*flow.ExecutionReceipt, error) { 143 tx := r.db.NewTransaction(false) 144 defer tx.Discard() 145 return r.byID(receiptID)(tx) 146 } 147 148 func (r *ExecutionReceipts) ByBlockID(blockID flow.Identifier) (flow.ExecutionReceiptList, error) { 149 tx := r.db.NewTransaction(false) 150 defer tx.Discard() 151 return r.byBlockID(blockID)(tx) 152 }