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  }