github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/storage/badger/my_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 // MyExecutionReceipts holds and indexes Execution Receipts. 18 // MyExecutionReceipts is implemented as a wrapper around badger.ExecutionReceipts 19 // The wrapper adds the ability to "MY execution receipt", from the viewpoint 20 // of an individual Execution Node. 21 type MyExecutionReceipts struct { 22 genericReceipts *ExecutionReceipts 23 db *badger.DB 24 cache *Cache[flow.Identifier, *flow.ExecutionReceipt] 25 } 26 27 // NewMyExecutionReceipts creates instance of MyExecutionReceipts which is a wrapper wrapper around badger.ExecutionReceipts 28 // It's useful for execution nodes to keep track of produced execution receipts. 29 func NewMyExecutionReceipts(collector module.CacheMetrics, db *badger.DB, receipts *ExecutionReceipts) *MyExecutionReceipts { 30 store := func(key flow.Identifier, receipt *flow.ExecutionReceipt) func(*transaction.Tx) error { 31 // assemble DB operations to store receipt (no execution) 32 storeReceiptOps := receipts.storeTx(receipt) 33 // assemble DB operations to index receipt as one of my own (no execution) 34 blockID := receipt.ExecutionResult.BlockID 35 receiptID := receipt.ID() 36 indexOwnReceiptOps := transaction.WithTx(func(tx *badger.Txn) error { 37 err := operation.IndexOwnExecutionReceipt(blockID, receiptID)(tx) 38 // check if we are storing same receipt 39 if errors.Is(err, storage.ErrAlreadyExists) { 40 var savedReceiptID flow.Identifier 41 err := operation.LookupOwnExecutionReceipt(blockID, &savedReceiptID)(tx) 42 if err != nil { 43 return err 44 } 45 46 if savedReceiptID == receiptID { 47 // if we are storing same receipt we shouldn't error 48 return nil 49 } 50 51 return fmt.Errorf("indexing my receipt %v failed: different receipt %v for the same block %v is already indexed", receiptID, 52 savedReceiptID, blockID) 53 } 54 return err 55 }) 56 57 return func(tx *transaction.Tx) error { 58 err := storeReceiptOps(tx) // execute operations to store receipt 59 if err != nil { 60 return fmt.Errorf("could not store receipt: %w", err) 61 } 62 err = indexOwnReceiptOps(tx) // execute operations to index receipt as one of my own 63 if err != nil { 64 return fmt.Errorf("could not index receipt as one of my own: %w", err) 65 } 66 return nil 67 } 68 } 69 70 retrieve := func(blockID flow.Identifier) func(tx *badger.Txn) (*flow.ExecutionReceipt, error) { 71 return func(tx *badger.Txn) (*flow.ExecutionReceipt, error) { 72 var receiptID flow.Identifier 73 err := operation.LookupOwnExecutionReceipt(blockID, &receiptID)(tx) 74 if err != nil { 75 return nil, fmt.Errorf("could not lookup receipt ID: %w", err) 76 } 77 receipt, err := receipts.byID(receiptID)(tx) 78 if err != nil { 79 return nil, err 80 } 81 return receipt, nil 82 } 83 } 84 85 return &MyExecutionReceipts{ 86 genericReceipts: receipts, 87 db: db, 88 cache: newCache[flow.Identifier, *flow.ExecutionReceipt](collector, metrics.ResourceMyReceipt, 89 withLimit[flow.Identifier, *flow.ExecutionReceipt](flow.DefaultTransactionExpiry+100), 90 withStore(store), 91 withRetrieve(retrieve)), 92 } 93 } 94 95 // storeMyReceipt assembles the operations to store the receipt and marks it as mine (trusted). 96 func (m *MyExecutionReceipts) storeMyReceipt(receipt *flow.ExecutionReceipt) func(*transaction.Tx) error { 97 return m.cache.PutTx(receipt.ExecutionResult.BlockID, receipt) 98 } 99 100 // storeMyReceipt assembles the operations to retrieve my receipt for the given block ID. 101 func (m *MyExecutionReceipts) myReceipt(blockID flow.Identifier) func(*badger.Txn) (*flow.ExecutionReceipt, error) { 102 retrievalOps := m.cache.Get(blockID) // assemble DB operations to retrieve receipt (no execution) 103 return func(tx *badger.Txn) (*flow.ExecutionReceipt, error) { 104 val, err := retrievalOps(tx) // execute operations to retrieve receipt 105 if err != nil { 106 return nil, err 107 } 108 return val, nil 109 } 110 } 111 112 // StoreMyReceipt stores the receipt and marks it as mine (trusted). My 113 // receipts are indexed by the block whose result they compute. Currently, 114 // we only support indexing a _single_ receipt per block. Attempting to 115 // store conflicting receipts for the same block will error. 116 func (m *MyExecutionReceipts) StoreMyReceipt(receipt *flow.ExecutionReceipt) error { 117 return operation.RetryOnConflictTx(m.db, transaction.Update, m.storeMyReceipt(receipt)) 118 } 119 120 // BatchStoreMyReceipt stores blockID-to-my-receipt index entry keyed by blockID in a provided batch. 121 // No errors are expected during normal operation 122 // If entity fails marshalling, the error is wrapped in a generic error and returned. 123 // If Badger unexpectedly fails to process the request, the error is wrapped in a generic error and returned. 124 func (m *MyExecutionReceipts) BatchStoreMyReceipt(receipt *flow.ExecutionReceipt, batch storage.BatchStorage) error { 125 126 writeBatch := batch.GetWriter() 127 128 err := m.genericReceipts.BatchStore(receipt, batch) 129 if err != nil { 130 return fmt.Errorf("cannot batch store generic execution receipt inside my execution receipt batch store: %w", err) 131 } 132 133 err = operation.BatchIndexOwnExecutionReceipt(receipt.ExecutionResult.BlockID, receipt.ID())(writeBatch) 134 if err != nil { 135 return fmt.Errorf("cannot batch index own execution receipt inside my execution receipt batch store: %w", err) 136 } 137 138 return nil 139 } 140 141 // MyReceipt retrieves my receipt for the given block. 142 // Returns storage.ErrNotFound if no receipt was persisted for the block. 143 func (m *MyExecutionReceipts) MyReceipt(blockID flow.Identifier) (*flow.ExecutionReceipt, error) { 144 tx := m.db.NewTransaction(false) 145 defer tx.Discard() 146 return m.myReceipt(blockID)(tx) 147 } 148 149 func (m *MyExecutionReceipts) RemoveIndexByBlockID(blockID flow.Identifier) error { 150 return m.db.Update(operation.SkipNonExist(operation.RemoveOwnExecutionReceipt(blockID))) 151 } 152 153 // BatchRemoveIndexByBlockID removes blockID-to-my-execution-receipt index entry keyed by a blockID in a provided batch 154 // No errors are expected during normal operation, even if no entries are matched. 155 // If Badger unexpectedly fails to process the request, the error is wrapped in a generic error and returned. 156 func (m *MyExecutionReceipts) BatchRemoveIndexByBlockID(blockID flow.Identifier, batch storage.BatchStorage) error { 157 writeBatch := batch.GetWriter() 158 return operation.BatchRemoveOwnExecutionReceipt(blockID)(writeBatch) 159 }