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  }