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  }