github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/storage/badger/approvals.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 // ResultApprovals implements persistent storage for result approvals. 18 type ResultApprovals struct { 19 db *badger.DB 20 cache *Cache[flow.Identifier, *flow.ResultApproval] 21 } 22 23 func NewResultApprovals(collector module.CacheMetrics, db *badger.DB) *ResultApprovals { 24 25 store := func(key flow.Identifier, val *flow.ResultApproval) func(*transaction.Tx) error { 26 return transaction.WithTx(operation.SkipDuplicates(operation.InsertResultApproval(val))) 27 } 28 29 retrieve := func(approvalID flow.Identifier) func(tx *badger.Txn) (*flow.ResultApproval, error) { 30 var approval flow.ResultApproval 31 return func(tx *badger.Txn) (*flow.ResultApproval, error) { 32 err := operation.RetrieveResultApproval(approvalID, &approval)(tx) 33 return &approval, err 34 } 35 } 36 37 res := &ResultApprovals{ 38 db: db, 39 cache: newCache[flow.Identifier, *flow.ResultApproval](collector, metrics.ResourceResultApprovals, 40 withLimit[flow.Identifier, *flow.ResultApproval](flow.DefaultTransactionExpiry+100), 41 withStore[flow.Identifier, *flow.ResultApproval](store), 42 withRetrieve[flow.Identifier, *flow.ResultApproval](retrieve)), 43 } 44 45 return res 46 } 47 48 func (r *ResultApprovals) store(approval *flow.ResultApproval) func(*transaction.Tx) error { 49 return r.cache.PutTx(approval.ID(), approval) 50 } 51 52 func (r *ResultApprovals) byID(approvalID flow.Identifier) func(*badger.Txn) (*flow.ResultApproval, error) { 53 return func(tx *badger.Txn) (*flow.ResultApproval, error) { 54 val, err := r.cache.Get(approvalID)(tx) 55 if err != nil { 56 return nil, err 57 } 58 return val, nil 59 } 60 } 61 62 func (r *ResultApprovals) byChunk(resultID flow.Identifier, chunkIndex uint64) func(*badger.Txn) (*flow.ResultApproval, error) { 63 return func(tx *badger.Txn) (*flow.ResultApproval, error) { 64 var approvalID flow.Identifier 65 err := operation.LookupResultApproval(resultID, chunkIndex, &approvalID)(tx) 66 if err != nil { 67 return nil, fmt.Errorf("could not lookup result approval ID: %w", err) 68 } 69 return r.byID(approvalID)(tx) 70 } 71 } 72 73 func (r *ResultApprovals) index(resultID flow.Identifier, chunkIndex uint64, approvalID flow.Identifier) func(*badger.Txn) error { 74 return func(tx *badger.Txn) error { 75 err := operation.IndexResultApproval(resultID, chunkIndex, approvalID)(tx) 76 if err == nil { 77 return nil 78 } 79 80 if !errors.Is(err, storage.ErrAlreadyExists) { 81 return err 82 } 83 84 // When trying to index an approval for a result, and there is already 85 // an approval for the result, double check if the indexed approval is 86 // the same. 87 // We don't allow indexing multiple approvals per chunk because the 88 // store is only used within Verification nodes, and it is impossible 89 // for a Verification node to compute different approvals for the same 90 // chunk. 91 var storedApprovalID flow.Identifier 92 err = operation.LookupResultApproval(resultID, chunkIndex, &storedApprovalID)(tx) 93 if err != nil { 94 return fmt.Errorf("there is an approval stored already, but cannot retrieve it: %w", err) 95 } 96 97 if storedApprovalID != approvalID { 98 return fmt.Errorf("attempting to store conflicting approval (result: %v, chunk index: %d): storing: %v, stored: %v. %w", 99 resultID, chunkIndex, approvalID, storedApprovalID, storage.ErrDataMismatch) 100 } 101 102 return nil 103 } 104 } 105 106 // Store stores a ResultApproval 107 func (r *ResultApprovals) Store(approval *flow.ResultApproval) error { 108 return operation.RetryOnConflictTx(r.db, transaction.Update, r.store(approval)) 109 } 110 111 // Index indexes a ResultApproval by chunk (ResultID + chunk index). 112 // operation is idempotent (repeated calls with the same value are equivalent to 113 // just calling the method once; still the method succeeds on each call). 114 func (r *ResultApprovals) Index(resultID flow.Identifier, chunkIndex uint64, approvalID flow.Identifier) error { 115 err := operation.RetryOnConflict(r.db.Update, r.index(resultID, chunkIndex, approvalID)) 116 if err != nil { 117 return fmt.Errorf("could not index result approval: %w", err) 118 } 119 return nil 120 } 121 122 // ByID retrieves a ResultApproval by its ID 123 func (r *ResultApprovals) ByID(approvalID flow.Identifier) (*flow.ResultApproval, error) { 124 tx := r.db.NewTransaction(false) 125 defer tx.Discard() 126 return r.byID(approvalID)(tx) 127 } 128 129 // ByChunk retrieves a ResultApproval by result ID and chunk index. The 130 // ResultApprovals store is only used within a verification node, where it is 131 // assumed that there is never more than one approval per chunk. 132 func (r *ResultApprovals) ByChunk(resultID flow.Identifier, chunkIndex uint64) (*flow.ResultApproval, error) { 133 tx := r.db.NewTransaction(false) 134 defer tx.Discard() 135 return r.byChunk(resultID, chunkIndex)(tx) 136 }