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