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 }