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  }