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 }