github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/storage/badger/headers.go (about)

     1  package badger
     2  
     3  import (
     4  	"fmt"
     5  
     6  	"github.com/dgraph-io/badger/v2"
     7  
     8  	"github.com/onflow/flow-go/model/flow"
     9  	"github.com/onflow/flow-go/module"
    10  	"github.com/onflow/flow-go/module/metrics"
    11  	"github.com/onflow/flow-go/storage/badger/operation"
    12  	"github.com/onflow/flow-go/storage/badger/procedure"
    13  	"github.com/onflow/flow-go/storage/badger/transaction"
    14  )
    15  
    16  // Headers implements a simple read-only header storage around a badger DB.
    17  type Headers struct {
    18  	db          *badger.DB
    19  	cache       *Cache[flow.Identifier, *flow.Header]
    20  	heightCache *Cache[uint64, flow.Identifier]
    21  }
    22  
    23  func NewHeaders(collector module.CacheMetrics, db *badger.DB) *Headers {
    24  
    25  	store := func(blockID flow.Identifier, header *flow.Header) func(*transaction.Tx) error {
    26  		return transaction.WithTx(operation.InsertHeader(blockID, header))
    27  	}
    28  
    29  	// CAUTION: should only be used to index FINALIZED blocks by their
    30  	// respective height
    31  	storeHeight := func(height uint64, id flow.Identifier) func(*transaction.Tx) error {
    32  		return transaction.WithTx(operation.IndexBlockHeight(height, id))
    33  	}
    34  
    35  	retrieve := func(blockID flow.Identifier) func(tx *badger.Txn) (*flow.Header, error) {
    36  		var header flow.Header
    37  		return func(tx *badger.Txn) (*flow.Header, error) {
    38  			err := operation.RetrieveHeader(blockID, &header)(tx)
    39  			return &header, err
    40  		}
    41  	}
    42  
    43  	retrieveHeight := func(height uint64) func(tx *badger.Txn) (flow.Identifier, error) {
    44  		return func(tx *badger.Txn) (flow.Identifier, error) {
    45  			var id flow.Identifier
    46  			err := operation.LookupBlockHeight(height, &id)(tx)
    47  			return id, err
    48  		}
    49  	}
    50  
    51  	h := &Headers{
    52  		db: db,
    53  		cache: newCache(collector, metrics.ResourceHeader,
    54  			withLimit[flow.Identifier, *flow.Header](4*flow.DefaultTransactionExpiry),
    55  			withStore(store),
    56  			withRetrieve(retrieve)),
    57  
    58  		heightCache: newCache(collector, metrics.ResourceFinalizedHeight,
    59  			withLimit[uint64, flow.Identifier](4*flow.DefaultTransactionExpiry),
    60  			withStore(storeHeight),
    61  			withRetrieve(retrieveHeight)),
    62  	}
    63  
    64  	return h
    65  }
    66  
    67  func (h *Headers) storeTx(header *flow.Header) func(*transaction.Tx) error {
    68  	return h.cache.PutTx(header.ID(), header)
    69  }
    70  
    71  func (h *Headers) retrieveTx(blockID flow.Identifier) func(*badger.Txn) (*flow.Header, error) {
    72  	return func(tx *badger.Txn) (*flow.Header, error) {
    73  		val, err := h.cache.Get(blockID)(tx)
    74  		if err != nil {
    75  			return nil, err
    76  		}
    77  		return val, nil
    78  	}
    79  }
    80  
    81  // results in `storage.ErrNotFound` for unknown height
    82  func (h *Headers) retrieveIdByHeightTx(height uint64) func(*badger.Txn) (flow.Identifier, error) {
    83  	return func(tx *badger.Txn) (flow.Identifier, error) {
    84  		blockID, err := h.heightCache.Get(height)(tx)
    85  		if err != nil {
    86  			return flow.ZeroID, fmt.Errorf("failed to retrieve block ID for height %d: %w", height, err)
    87  		}
    88  		return blockID, nil
    89  	}
    90  }
    91  
    92  func (h *Headers) Store(header *flow.Header) error {
    93  	return operation.RetryOnConflictTx(h.db, transaction.Update, h.storeTx(header))
    94  }
    95  
    96  func (h *Headers) ByBlockID(blockID flow.Identifier) (*flow.Header, error) {
    97  	tx := h.db.NewTransaction(false)
    98  	defer tx.Discard()
    99  	return h.retrieveTx(blockID)(tx)
   100  }
   101  
   102  func (h *Headers) ByHeight(height uint64) (*flow.Header, error) {
   103  	tx := h.db.NewTransaction(false)
   104  	defer tx.Discard()
   105  
   106  	blockID, err := h.retrieveIdByHeightTx(height)(tx)
   107  	if err != nil {
   108  		return nil, err
   109  	}
   110  	return h.retrieveTx(blockID)(tx)
   111  }
   112  
   113  // Exists returns true if a header with the given ID has been stored.
   114  // No errors are expected during normal operation.
   115  func (h *Headers) Exists(blockID flow.Identifier) (bool, error) {
   116  	// if the block is in the cache, return true
   117  	if ok := h.cache.IsCached(blockID); ok {
   118  		return ok, nil
   119  	}
   120  	// otherwise, check badger store
   121  	var exists bool
   122  	err := h.db.View(operation.BlockExists(blockID, &exists))
   123  	if err != nil {
   124  		return false, fmt.Errorf("could not check existence: %w", err)
   125  	}
   126  	return exists, nil
   127  }
   128  
   129  // BlockIDByHeight returns the block ID that is finalized at the given height. It is an optimized
   130  // version of `ByHeight` that skips retrieving the block. Expected errors during normal operations:
   131  //   - `storage.ErrNotFound` if no finalized block is known at given height.
   132  func (h *Headers) BlockIDByHeight(height uint64) (flow.Identifier, error) {
   133  	tx := h.db.NewTransaction(false)
   134  	defer tx.Discard()
   135  
   136  	blockID, err := h.retrieveIdByHeightTx(height)(tx)
   137  	if err != nil {
   138  		return flow.ZeroID, fmt.Errorf("could not lookup block id by height %d: %w", height, err)
   139  	}
   140  	return blockID, nil
   141  }
   142  
   143  func (h *Headers) ByParentID(parentID flow.Identifier) ([]*flow.Header, error) {
   144  	var blockIDs flow.IdentifierList
   145  	err := h.db.View(procedure.LookupBlockChildren(parentID, &blockIDs))
   146  	if err != nil {
   147  		return nil, fmt.Errorf("could not look up children: %w", err)
   148  	}
   149  	headers := make([]*flow.Header, 0, len(blockIDs))
   150  	for _, blockID := range blockIDs {
   151  		header, err := h.ByBlockID(blockID)
   152  		if err != nil {
   153  			return nil, fmt.Errorf("could not retrieve child (%x): %w", blockID, err)
   154  		}
   155  		headers = append(headers, header)
   156  	}
   157  	return headers, nil
   158  }
   159  
   160  func (h *Headers) FindHeaders(filter func(header *flow.Header) bool) ([]flow.Header, error) {
   161  	blocks := make([]flow.Header, 0, 1)
   162  	err := h.db.View(operation.FindHeaders(filter, &blocks))
   163  	return blocks, err
   164  }
   165  
   166  // RollbackExecutedBlock update the executed block header to the given header.
   167  // only useful for execution node to roll back executed block height
   168  func (h *Headers) RollbackExecutedBlock(header *flow.Header) error {
   169  	return operation.RetryOnConflict(h.db.Update, func(txn *badger.Txn) error {
   170  		var blockID flow.Identifier
   171  		err := operation.RetrieveExecutedBlock(&blockID)(txn)
   172  		if err != nil {
   173  			return fmt.Errorf("cannot lookup executed block: %w", err)
   174  		}
   175  
   176  		var highest flow.Header
   177  		err = operation.RetrieveHeader(blockID, &highest)(txn)
   178  		if err != nil {
   179  			return fmt.Errorf("cannot retrieve executed header: %w", err)
   180  		}
   181  
   182  		// only rollback if the given height is below the current executed height
   183  		if header.Height >= highest.Height {
   184  			return fmt.Errorf("cannot roolback. expect the target height %v to be lower than highest executed height %v, but actually is not",
   185  				header.Height, highest.Height,
   186  			)
   187  		}
   188  
   189  		err = operation.UpdateExecutedBlock(header.ID())(txn)
   190  		if err != nil {
   191  			return fmt.Errorf("cannot update highest executed block: %w", err)
   192  		}
   193  
   194  		return nil
   195  	})
   196  }