github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/storage/badger/results.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 // ExecutionResults implements persistent storage for execution results. 18 type ExecutionResults struct { 19 db *badger.DB 20 cache *Cache[flow.Identifier, *flow.ExecutionResult] 21 } 22 23 var _ storage.ExecutionResults = (*ExecutionResults)(nil) 24 25 func NewExecutionResults(collector module.CacheMetrics, db *badger.DB) *ExecutionResults { 26 27 store := func(_ flow.Identifier, result *flow.ExecutionResult) func(*transaction.Tx) error { 28 return transaction.WithTx(operation.SkipDuplicates(operation.InsertExecutionResult(result))) 29 } 30 31 retrieve := func(resultID flow.Identifier) func(tx *badger.Txn) (*flow.ExecutionResult, error) { 32 return func(tx *badger.Txn) (*flow.ExecutionResult, error) { 33 var result flow.ExecutionResult 34 err := operation.RetrieveExecutionResult(resultID, &result)(tx) 35 return &result, err 36 } 37 } 38 39 res := &ExecutionResults{ 40 db: db, 41 cache: newCache[flow.Identifier, *flow.ExecutionResult](collector, metrics.ResourceResult, 42 withLimit[flow.Identifier, *flow.ExecutionResult](flow.DefaultTransactionExpiry+100), 43 withStore(store), 44 withRetrieve(retrieve)), 45 } 46 47 return res 48 } 49 50 func (r *ExecutionResults) store(result *flow.ExecutionResult) func(*transaction.Tx) error { 51 return r.cache.PutTx(result.ID(), result) 52 } 53 54 func (r *ExecutionResults) byID(resultID flow.Identifier) func(*badger.Txn) (*flow.ExecutionResult, error) { 55 return func(tx *badger.Txn) (*flow.ExecutionResult, error) { 56 val, err := r.cache.Get(resultID)(tx) 57 if err != nil { 58 return nil, err 59 } 60 return val, nil 61 } 62 } 63 64 func (r *ExecutionResults) byBlockID(blockID flow.Identifier) func(*badger.Txn) (*flow.ExecutionResult, error) { 65 return func(tx *badger.Txn) (*flow.ExecutionResult, error) { 66 var resultID flow.Identifier 67 err := operation.LookupExecutionResult(blockID, &resultID)(tx) 68 if err != nil { 69 return nil, fmt.Errorf("could not lookup execution result ID: %w", err) 70 } 71 return r.byID(resultID)(tx) 72 } 73 } 74 75 func (r *ExecutionResults) index(blockID, resultID flow.Identifier, force bool) func(*transaction.Tx) error { 76 return func(tx *transaction.Tx) error { 77 err := transaction.WithTx(operation.IndexExecutionResult(blockID, resultID))(tx) 78 if err == nil { 79 return nil 80 } 81 82 if !errors.Is(err, storage.ErrAlreadyExists) { 83 return err 84 } 85 86 if force { 87 return transaction.WithTx(operation.ReindexExecutionResult(blockID, resultID))(tx) 88 } 89 90 // when trying to index a result for a block, and there is already a result indexed for this block, 91 // double check if the indexed result is the same 92 var storedResultID flow.Identifier 93 err = transaction.WithTx(operation.LookupExecutionResult(blockID, &storedResultID))(tx) 94 if err != nil { 95 return fmt.Errorf("there is a result stored already, but cannot retrieve it: %w", err) 96 } 97 98 if storedResultID != resultID { 99 return fmt.Errorf("storing result that is different from the already stored one for block: %v, storing result: %v, stored result: %v. %w", 100 blockID, resultID, storedResultID, storage.ErrDataMismatch) 101 } 102 103 return nil 104 } 105 } 106 107 func (r *ExecutionResults) Store(result *flow.ExecutionResult) error { 108 return operation.RetryOnConflictTx(r.db, transaction.Update, r.store(result)) 109 } 110 111 func (r *ExecutionResults) BatchStore(result *flow.ExecutionResult, batch storage.BatchStorage) error { 112 writeBatch := batch.GetWriter() 113 return operation.BatchInsertExecutionResult(result)(writeBatch) 114 } 115 116 func (r *ExecutionResults) BatchIndex(blockID flow.Identifier, resultID flow.Identifier, batch storage.BatchStorage) error { 117 writeBatch := batch.GetWriter() 118 return operation.BatchIndexExecutionResult(blockID, resultID)(writeBatch) 119 } 120 121 func (r *ExecutionResults) ByID(resultID flow.Identifier) (*flow.ExecutionResult, error) { 122 tx := r.db.NewTransaction(false) 123 defer tx.Discard() 124 return r.byID(resultID)(tx) 125 } 126 127 func (r *ExecutionResults) ByIDTx(resultID flow.Identifier) func(*transaction.Tx) (*flow.ExecutionResult, error) { 128 return func(tx *transaction.Tx) (*flow.ExecutionResult, error) { 129 result, err := r.byID(resultID)(tx.DBTxn) 130 return result, err 131 } 132 } 133 134 func (r *ExecutionResults) Index(blockID flow.Identifier, resultID flow.Identifier) error { 135 err := operation.RetryOnConflictTx(r.db, transaction.Update, r.index(blockID, resultID, false)) 136 if err != nil { 137 return fmt.Errorf("could not index execution result: %w", err) 138 } 139 return nil 140 } 141 142 func (r *ExecutionResults) ForceIndex(blockID flow.Identifier, resultID flow.Identifier) error { 143 err := operation.RetryOnConflictTx(r.db, transaction.Update, r.index(blockID, resultID, true)) 144 if err != nil { 145 return fmt.Errorf("could not index execution result: %w", err) 146 } 147 return nil 148 } 149 150 func (r *ExecutionResults) ByBlockID(blockID flow.Identifier) (*flow.ExecutionResult, error) { 151 tx := r.db.NewTransaction(false) 152 defer tx.Discard() 153 return r.byBlockID(blockID)(tx) 154 } 155 156 func (r *ExecutionResults) RemoveIndexByBlockID(blockID flow.Identifier) error { 157 return r.db.Update(operation.SkipNonExist(operation.RemoveExecutionResultIndex(blockID))) 158 } 159 160 // BatchRemoveIndexByBlockID removes blockID-to-executionResultID index entries keyed by blockID in a provided batch. 161 // No errors are expected during normal operation, even if no entries are matched. 162 // If Badger unexpectedly fails to process the request, the error is wrapped in a generic error and returned. 163 func (r *ExecutionResults) BatchRemoveIndexByBlockID(blockID flow.Identifier, batch storage.BatchStorage) error { 164 writeBatch := batch.GetWriter() 165 return operation.BatchRemoveExecutionResultIndex(blockID)(writeBatch) 166 }