github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/engine/execution/storehouse/register_store.go (about) 1 package storehouse 2 3 import ( 4 "errors" 5 "fmt" 6 7 "go.uber.org/atomic" 8 9 "github.com/rs/zerolog" 10 11 "github.com/onflow/flow-go/engine/execution" 12 "github.com/onflow/flow-go/model/flow" 13 "github.com/onflow/flow-go/storage" 14 ) 15 16 type RegisterStore struct { 17 memStore *InMemoryRegisterStore 18 diskStore execution.OnDiskRegisterStore 19 wal execution.ExecutedFinalizedWAL 20 finalized execution.FinalizedReader 21 log zerolog.Logger 22 finalizing *atomic.Bool // making sure only one goroutine is finalizing at a time 23 notifier execution.RegisterStoreNotifier 24 } 25 26 var _ execution.RegisterStore = (*RegisterStore)(nil) 27 28 type NoopNotifier struct{} 29 30 func NewNoopNotifier() *NoopNotifier { return &NoopNotifier{} } 31 32 func (n *NoopNotifier) OnFinalizedAndExecutedHeightUpdated(height uint64) {} 33 34 var _ execution.RegisterStoreNotifier = (*NoopNotifier)(nil) 35 36 func NewRegisterStore( 37 diskStore execution.OnDiskRegisterStore, 38 wal execution.ExecutedFinalizedWAL, 39 finalized execution.FinalizedReader, 40 log zerolog.Logger, 41 notifier execution.RegisterStoreNotifier, 42 ) (*RegisterStore, error) { 43 if notifier == nil { 44 return nil, fmt.Errorf("notifier is empty, use NoopNotifier if you don't need it") 45 } 46 47 // replay the executed and finalized blocks from the write ahead logs 48 // to the OnDiskRegisterStore 49 height, err := syncDiskStore(wal, diskStore, log) 50 if err != nil { 51 return nil, fmt.Errorf("cannot sync disk store: %w", err) 52 } 53 54 // fetch the last executed and finalized block ID 55 finalizedID, err := finalized.FinalizedBlockIDAtHeight(height) 56 if err != nil { 57 return nil, fmt.Errorf("cannot get finalized block ID at height %d: %w", height, err) 58 } 59 60 // init the memStore with the last executed and finalized block ID 61 memStore := NewInMemoryRegisterStore(height, finalizedID) 62 63 log.Info().Msgf("initialized in memory register store at block %v, height %v", finalizedID, height) 64 65 return &RegisterStore{ 66 memStore: memStore, 67 diskStore: diskStore, 68 wal: wal, 69 finalized: finalized, 70 finalizing: atomic.NewBool(false), 71 log: log.With().Str("module", "register-store").Logger(), 72 notifier: notifier, 73 }, nil 74 } 75 76 // GetRegister first try to get the register from InMemoryRegisterStore, then OnDiskRegisterStore 77 // 1. below pruned height, and is conflicting 78 // 2. below pruned height, and is finalized 79 // 3. above pruned height, and is not executed 80 // 4. above pruned height, and is executed, and register is updated 81 // 5. above pruned height, and is executed, but register is not updated since pruned height 82 // It returns: 83 // - (value, nil) if the register value is found at the given block 84 // - (nil, nil) if the register is not found 85 // - (nil, storage.ErrHeightNotIndexed) if the height is below the first height that is indexed. 86 // - (nil, storehouse.ErrNotExecuted) if the block is not executed yet 87 // - (nil, storehouse.ErrNotExecuted) if the block is conflicting iwth finalized block 88 // - (nil, err) for any other exceptions 89 func (r *RegisterStore) GetRegister(height uint64, blockID flow.Identifier, register flow.RegisterID) (flow.RegisterValue, error) { 90 reg, err := r.memStore.GetRegister(height, blockID, register) 91 // the height might be lower than the lowest height in memStore, 92 // or the register might not be found in memStore. 93 if err == nil { 94 // this register was updated before its block is finalized 95 return reg, nil 96 } 97 98 prunedError, ok := IsPrunedError(err) 99 if !ok { 100 // this means we ran into an exception. finding a register from in-memory store should either 101 // getting the register value or getting a ErrPruned error. 102 return flow.RegisterValue{}, fmt.Errorf("cannot get register from memStore: %w", err) 103 } 104 105 // if in memory store returns PrunedError, and register height is above the pruned height, 106 // then it means the block is connected to the pruned block of in memory store, which is 107 // a finalized block and executed block, so we can get its value from on disk store. 108 if height > prunedError.PrunedHeight { 109 return r.getAndConvertNotFoundErr(register, prunedError.PrunedHeight) 110 } 111 112 // if the block is below or equal to the pruned height, then there are two cases: 113 // the block is a finalized block, or a conflicting block. 114 // In order to distinguish, we need to query the finalized block ID at that height 115 116 var finalizedID flow.Identifier 117 if height == prunedError.PrunedHeight { 118 // if the block is at the pruned height, then the finalized ID is the pruned ID from in memory store, 119 // this saves a DB query 120 finalizedID = prunedError.PrunedID 121 } else { 122 // if the block is below the pruned height, we query the finalized ID from the finalized reader 123 finalizedID, err = r.finalized.FinalizedBlockIDAtHeight(height) 124 if err != nil { 125 return nil, fmt.Errorf("cannot get finalized block ID at height %d: %w", height, err) 126 } 127 } 128 129 isConflictingBlock := blockID != finalizedID 130 if isConflictingBlock { 131 // conflicting blocks are considered as un-executed 132 return flow.RegisterValue{}, fmt.Errorf("getting registers from conflicting block %v at height %v: %w", blockID, height, ErrNotExecuted) 133 } 134 return r.getAndConvertNotFoundErr(register, height) 135 } 136 137 // getAndConvertNotFoundErr returns nil if the register is not found from storage 138 func (r *RegisterStore) getAndConvertNotFoundErr(register flow.RegisterID, height uint64) (flow.RegisterValue, error) { 139 val, err := r.diskStore.Get(register, height) 140 if errors.Is(err, storage.ErrNotFound) { 141 // FVM expects the error to be nil when register is not found 142 return nil, nil 143 } 144 return val, err 145 } 146 147 // SaveRegisters saves to InMemoryRegisterStore first, then trigger the same check as OnBlockFinalized 148 // Depend on InMemoryRegisterStore.SaveRegisters 149 // It returns: 150 // - nil if the registers are saved successfully 151 // - exception is the block is above the pruned height but does not connect to the pruned height (conflicting block). 152 // - exception if the block is below the pruned height 153 // - exception if the save block is saved again 154 // - exception for any other exception 155 func (r *RegisterStore) SaveRegisters(header *flow.Header, registers flow.RegisterEntries) error { 156 err := r.memStore.SaveRegisters(header.Height, header.ID(), header.ParentID, registers) 157 if err != nil { 158 return fmt.Errorf("cannot save register to memStore: %w", err) 159 } 160 161 err = r.OnBlockFinalized() 162 if err != nil { 163 return fmt.Errorf("cannot trigger OnBlockFinalized: %w", err) 164 } 165 return nil 166 } 167 168 // Depend on FinalizedReader's FinalizedBlockIDAtHeight 169 // Depend on ExecutedFinalizedWAL.Append 170 // Depend on OnDiskRegisterStore.SaveRegisters 171 // OnBlockFinalized trigger the check of whether a block at the next height becomes finalized and executed. 172 // the next height is the existing finalized and executed block's height + 1. 173 // If a block at next height becomes finalized and executed, then: 174 // 1. write the registers to write ahead logs 175 // 2. save the registers of the block to OnDiskRegisterStore 176 // 3. prune the height in InMemoryRegisterStore 177 func (r *RegisterStore) OnBlockFinalized() error { 178 // only one goroutine can execute OnBlockFinalized at a time 179 if !r.finalizing.CompareAndSwap(false, true) { 180 return nil 181 } 182 183 defer r.finalizing.Store(false) 184 return r.onBlockFinalized() 185 } 186 187 func (r *RegisterStore) onBlockFinalized() error { 188 latest := r.diskStore.LatestHeight() 189 next := latest + 1 190 blockID, err := r.finalized.FinalizedBlockIDAtHeight(next) 191 if errors.Is(err, storage.ErrNotFound) { 192 // next block is not finalized yet 193 return nil 194 } 195 196 regs, err := r.memStore.GetUpdatedRegisters(next, blockID) 197 if errors.Is(err, ErrNotExecuted) { 198 // next block is not executed yet 199 return nil 200 } 201 202 // TODO: append WAL 203 // err = r.wal.Append(next, regs) 204 // if err != nil { 205 // return fmt.Errorf("cannot write %v registers to write ahead logs for height %v: %w", len(regs), next, err) 206 // } 207 208 err = r.diskStore.Store(regs, next) 209 if err != nil { 210 return fmt.Errorf("cannot save %v registers to disk store for height %v: %w", len(regs), next, err) 211 } 212 213 r.notifier.OnFinalizedAndExecutedHeightUpdated(next) 214 215 err = r.memStore.Prune(next, blockID) 216 if err != nil { 217 return fmt.Errorf("cannot prune memStore for height %v: %w", next, err) 218 } 219 220 return r.onBlockFinalized() // check again until there is no more finalized block 221 } 222 223 // LastFinalizedAndExecutedHeight returns the height of the last finalized and executed block, 224 // which has been saved in OnDiskRegisterStore 225 func (r *RegisterStore) LastFinalizedAndExecutedHeight() uint64 { 226 // diskStore caches the latest height in memory 227 return r.diskStore.LatestHeight() 228 } 229 230 // IsBlockExecuted returns true if the block is executed, false if not executed 231 // Note: it returns (true, nil) even if the block has been pruned from on disk register store, 232 func (r *RegisterStore) IsBlockExecuted(height uint64, blockID flow.Identifier) (bool, error) { 233 executed, err := r.memStore.IsBlockExecuted(height, blockID) 234 if err != nil { 235 // the only error memStore would return is when the given height is lower than the pruned height in memStore. 236 // Since the pruned height in memStore is a finalized and executed height, in order to know if the block 237 // is executed, we just need to check if this block is the finalized blcok at the given height. 238 executed, err = r.isBlockFinalized(height, blockID) 239 return executed, err 240 } 241 242 return executed, nil 243 } 244 245 func (r *RegisterStore) isBlockFinalized(height uint64, blockID flow.Identifier) (bool, error) { 246 finalizedID, err := r.finalized.FinalizedBlockIDAtHeight(height) 247 if err != nil { 248 return false, fmt.Errorf("cannot get finalized block ID at height %d: %w", height, err) 249 } 250 return finalizedID == blockID, nil 251 } 252 253 // syncDiskStore replay WAL to disk store 254 func syncDiskStore( 255 wal execution.ExecutedFinalizedWAL, 256 diskStore execution.OnDiskRegisterStore, 257 log zerolog.Logger, 258 ) (uint64, error) { 259 // TODO: replace diskStore.Latest with wal.Latest 260 // latest, err := r.wal.Latest() 261 var err error 262 latest := diskStore.LatestHeight() // tmp 263 if err != nil { 264 return 0, fmt.Errorf("cannot get latest height from write ahead logs: %w", err) 265 } 266 267 stored := diskStore.LatestHeight() 268 269 if stored > latest { 270 return 0, fmt.Errorf("latest height in storehouse %v is larger than latest height %v in write ahead logs", stored, latest) 271 } 272 273 if stored < latest { 274 // replay 275 reader := wal.GetReader(stored + 1) 276 for { 277 height, registers, err := reader.Next() 278 // TODO: to rename 279 if errors.Is(err, storage.ErrNotFound) { 280 break 281 } 282 if err != nil { 283 return 0, fmt.Errorf("cannot read registers from write ahead logs: %w", err) 284 } 285 286 err = diskStore.Store(registers, height) 287 if err != nil { 288 return 0, fmt.Errorf("cannot save registers to disk store at height %v : %w", height, err) 289 } 290 } 291 } 292 293 return latest, nil 294 }