github.com/onflow/flow-go@v0.33.17/storage/pebble/registers.go (about)

     1  package pebble
     2  
     3  import (
     4  	"encoding/binary"
     5  	"fmt"
     6  
     7  	"github.com/cockroachdb/pebble"
     8  	"github.com/pkg/errors"
     9  	"go.uber.org/atomic"
    10  
    11  	"github.com/onflow/flow-go/model/flow"
    12  	"github.com/onflow/flow-go/storage"
    13  )
    14  
    15  // Registers library that implements pebble storage for registers
    16  // given a pebble instance with root block and root height populated
    17  type Registers struct {
    18  	db           *pebble.DB
    19  	firstHeight  uint64
    20  	latestHeight *atomic.Uint64
    21  }
    22  
    23  var _ storage.RegisterIndex = (*Registers)(nil)
    24  
    25  // NewRegisters takes a populated pebble instance with LatestHeight and FirstHeight set.
    26  // return storage.ErrNotBootstrapped if they those two keys are unavailable as it implies a uninitialized state
    27  // return other error if database is in a corrupted state
    28  func NewRegisters(db *pebble.DB) (*Registers, error) {
    29  	// check height keys and populate cache. These two variables will have been set
    30  	firstHeight, latestHeight, err := ReadHeightsFromBootstrappedDB(db)
    31  	if err != nil {
    32  		// first height is found, but latest height is not found, this means that the DB is in a corrupted state
    33  		return nil, fmt.Errorf("unable to initialize register storage, latest height unavailable in db: %w", err)
    34  	}
    35  
    36  	// All registers between firstHeight and lastHeight have been indexed
    37  	return &Registers{
    38  		db:           db,
    39  		firstHeight:  firstHeight,
    40  		latestHeight: atomic.NewUint64(latestHeight),
    41  	}, nil
    42  }
    43  
    44  // Get returns the most recent updated payload for the given RegisterID.
    45  // "most recent" means the updates happens most recent up the given height.
    46  //
    47  // For example, if there are 2 values stored for register A at height 6 and 11, then
    48  // GetPayload(13, A) would return the value at height 11.
    49  //
    50  // - storage.ErrNotFound if no register values are found
    51  // - storage.ErrHeightNotIndexed if the requested height is out of the range of stored heights
    52  func (s *Registers) Get(
    53  	reg flow.RegisterID,
    54  	height uint64,
    55  ) (flow.RegisterValue, error) {
    56  	latestHeight := s.latestHeight.Load()
    57  	if height > latestHeight || height < s.firstHeight {
    58  		return nil, errors.Wrap(
    59  			storage.ErrHeightNotIndexed,
    60  			fmt.Sprintf("height %d not indexed, indexed range is [%d-%d]", height, s.firstHeight, latestHeight),
    61  		)
    62  	}
    63  	key := newLookupKey(height, reg)
    64  	return s.lookupRegister(key.Bytes())
    65  }
    66  
    67  func (s *Registers) lookupRegister(key []byte) (flow.RegisterValue, error) {
    68  	iter, err := s.db.NewIter(&pebble.IterOptions{
    69  		UseL6Filters: true,
    70  	})
    71  	if err != nil {
    72  		return nil, err
    73  	}
    74  
    75  	defer iter.Close()
    76  
    77  	ok := iter.SeekPrefixGE(key)
    78  	if !ok {
    79  		// no such register found
    80  		return nil, storage.ErrNotFound
    81  	}
    82  
    83  	binaryValue, err := iter.ValueAndErr()
    84  	if err != nil {
    85  		return nil, fmt.Errorf("failed to get value: %w", err)
    86  	}
    87  	// preventing caller from modifying the iterator's value slices
    88  	valueCopy := make([]byte, len(binaryValue))
    89  	copy(valueCopy, binaryValue)
    90  
    91  	return valueCopy, nil
    92  }
    93  
    94  // Store sets the given entries in a batch.
    95  // This function is expected to be called at one batch per height, sequentially. Under normal conditions,
    96  // it should be called wth the value of height set to LatestHeight + 1
    97  // CAUTION: This function is not safe for concurrent use.
    98  func (s *Registers) Store(
    99  	entries flow.RegisterEntries,
   100  	height uint64,
   101  ) error {
   102  	latestHeight := s.latestHeight.Load()
   103  	// This check is for a special case for the execution node.
   104  	// Upon restart, it may be in a state where registers are indexed in pebble for the latest height
   105  	// but the remaining execution data in badger is not, so we skip the indexing step without throwing an error
   106  	if height == latestHeight {
   107  		// already updated
   108  		return nil
   109  	}
   110  
   111  	nextHeight := latestHeight + 1
   112  	if height != nextHeight {
   113  		return fmt.Errorf("must store registers with the next height %v, but got %v", nextHeight, height)
   114  	}
   115  	batch := s.db.NewBatch()
   116  	defer batch.Close()
   117  
   118  	for _, entry := range entries {
   119  		encoded := newLookupKey(height, entry.Key).Bytes()
   120  
   121  		err := batch.Set(encoded, entry.Value, nil)
   122  		if err != nil {
   123  			return fmt.Errorf("failed to set key: %w", err)
   124  		}
   125  	}
   126  	// increment height and commit
   127  	err := batch.Set(latestHeightKey, encodedUint64(height), nil)
   128  	if err != nil {
   129  		return fmt.Errorf("failed to update latest height %d", height)
   130  	}
   131  	err = batch.Commit(pebble.Sync)
   132  	if err != nil {
   133  		return fmt.Errorf("failed to commit batch: %w", err)
   134  	}
   135  	s.latestHeight.Store(height)
   136  
   137  	return nil
   138  }
   139  
   140  // LatestHeight Gets the latest height of complete registers available
   141  func (s *Registers) LatestHeight() uint64 {
   142  	return s.latestHeight.Load()
   143  }
   144  
   145  // FirstHeight first indexed height found in the store, typically root block for the spork
   146  func (s *Registers) FirstHeight() uint64 {
   147  	return s.firstHeight
   148  }
   149  
   150  func firstStoredHeight(db *pebble.DB) (uint64, error) {
   151  	return heightLookup(db, firstHeightKey)
   152  }
   153  
   154  func latestStoredHeight(db *pebble.DB) (uint64, error) {
   155  	return heightLookup(db, latestHeightKey)
   156  }
   157  
   158  func heightLookup(db *pebble.DB, key []byte) (uint64, error) {
   159  	res, closer, err := db.Get(key)
   160  	if err != nil {
   161  		return 0, convertNotFoundError(err)
   162  	}
   163  	defer closer.Close()
   164  	return binary.BigEndian.Uint64(res), nil
   165  }
   166  
   167  // convert pebble NotFound error to storage NotFound error
   168  func convertNotFoundError(err error) error {
   169  	if errors.Is(err, pebble.ErrNotFound) {
   170  		return storage.ErrNotFound
   171  	}
   172  	return err
   173  }