github.com/onflow/flow-go@v0.33.17/engine/execution/state/state.go (about) 1 package state 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "sync" 8 9 "github.com/dgraph-io/badger/v2" 10 11 "github.com/onflow/flow-go/engine/execution" 12 "github.com/onflow/flow-go/engine/execution/storehouse" 13 "github.com/onflow/flow-go/fvm/storage/snapshot" 14 "github.com/onflow/flow-go/ledger" 15 "github.com/onflow/flow-go/ledger/common/convert" 16 "github.com/onflow/flow-go/model/flow" 17 "github.com/onflow/flow-go/module" 18 "github.com/onflow/flow-go/module/trace" 19 "github.com/onflow/flow-go/storage" 20 badgerstorage "github.com/onflow/flow-go/storage/badger" 21 "github.com/onflow/flow-go/storage/badger/operation" 22 "github.com/onflow/flow-go/storage/badger/procedure" 23 ) 24 25 var ErrExecutionStatePruned = fmt.Errorf("execution state is pruned") 26 var ErrNotExecuted = fmt.Errorf("block not executed") 27 28 // ReadOnlyExecutionState allows to read the execution state 29 type ReadOnlyExecutionState interface { 30 ScriptExecutionState 31 32 // ChunkDataPackByChunkID retrieve a chunk data pack given the chunk ID. 33 ChunkDataPackByChunkID(flow.Identifier) (*flow.ChunkDataPack, error) 34 35 GetExecutionResultID(context.Context, flow.Identifier) (flow.Identifier, error) 36 37 GetHighestExecutedBlockID(context.Context) (uint64, flow.Identifier, error) 38 } 39 40 // ScriptExecutionState is a subset of the `state.ExecutionState` interface purposed to only access the state 41 // used for script execution and not mutate the execution state of the blockchain. 42 type ScriptExecutionState interface { 43 // NewStorageSnapshot creates a new ready-only view at the given block. 44 NewStorageSnapshot(commit flow.StateCommitment, blockID flow.Identifier, height uint64) snapshot.StorageSnapshot 45 46 // CreateStorageSnapshot creates a new ready-only view at the given block. 47 // It returns: 48 // - (nil, nil, storage.ErrNotFound) if block is unknown 49 // - (nil, nil, state.ErrNotExecuted) if block is not executed 50 // - (nil, nil, state.ErrExecutionStatePruned) if the execution state has been pruned 51 CreateStorageSnapshot(blockID flow.Identifier) (snapshot.StorageSnapshot, *flow.Header, error) 52 53 // StateCommitmentByBlockID returns the final state commitment for the provided block ID. 54 StateCommitmentByBlockID(flow.Identifier) (flow.StateCommitment, error) 55 56 // Any error returned is exception 57 IsBlockExecuted(height uint64, blockID flow.Identifier) (bool, error) 58 } 59 60 func IsParentExecuted(state ReadOnlyExecutionState, header *flow.Header) (bool, error) { 61 // sanity check, caller should not pass a root block 62 if header.Height == 0 { 63 return false, fmt.Errorf("root block does not have parent block") 64 } 65 return state.IsBlockExecuted(header.Height-1, header.ParentID) 66 } 67 68 // FinalizedExecutionState is an interface used to access the finalized execution state 69 type FinalizedExecutionState interface { 70 GetHighestFinalizedExecuted() uint64 71 } 72 73 // TODO Many operations here are should be transactional, so we need to refactor this 74 // to store a reference to DB and compose operations and procedures rather then 75 // just being amalgamate of proxies for single transactions operation 76 77 // ExecutionState is an interface used to access and mutate the execution state of the blockchain. 78 type ExecutionState interface { 79 ReadOnlyExecutionState 80 81 UpdateHighestExecutedBlockIfHigher(context.Context, *flow.Header) error 82 83 SaveExecutionResults( 84 ctx context.Context, 85 result *execution.ComputationResult, 86 ) error 87 88 // only available with storehouse enabled 89 // panic when called with storehouse disabled (which should be a bug) 90 GetHighestFinalizedExecuted() uint64 91 } 92 93 type state struct { 94 tracer module.Tracer 95 ls ledger.Ledger 96 commits storage.Commits 97 blocks storage.Blocks 98 headers storage.Headers 99 collections storage.Collections 100 chunkDataPacks storage.ChunkDataPacks 101 results storage.ExecutionResults 102 myReceipts storage.MyExecutionReceipts 103 events storage.Events 104 serviceEvents storage.ServiceEvents 105 transactionResults storage.TransactionResults 106 db *badger.DB 107 108 registerStore execution.RegisterStore 109 // when it is true, registers are stored in both register store and ledger 110 // and register queries will send to the register store instead of ledger 111 enableRegisterStore bool 112 } 113 114 // NewExecutionState returns a new execution state access layer for the given ledger storage. 115 func NewExecutionState( 116 ls ledger.Ledger, 117 commits storage.Commits, 118 blocks storage.Blocks, 119 headers storage.Headers, 120 collections storage.Collections, 121 chunkDataPacks storage.ChunkDataPacks, 122 results storage.ExecutionResults, 123 myReceipts storage.MyExecutionReceipts, 124 events storage.Events, 125 serviceEvents storage.ServiceEvents, 126 transactionResults storage.TransactionResults, 127 db *badger.DB, 128 tracer module.Tracer, 129 registerStore execution.RegisterStore, 130 enableRegisterStore bool, 131 ) ExecutionState { 132 return &state{ 133 tracer: tracer, 134 ls: ls, 135 commits: commits, 136 blocks: blocks, 137 headers: headers, 138 collections: collections, 139 chunkDataPacks: chunkDataPacks, 140 results: results, 141 myReceipts: myReceipts, 142 events: events, 143 serviceEvents: serviceEvents, 144 transactionResults: transactionResults, 145 db: db, 146 registerStore: registerStore, 147 enableRegisterStore: enableRegisterStore, 148 } 149 150 } 151 152 func makeSingleValueQuery(commitment flow.StateCommitment, id flow.RegisterID) (*ledger.QuerySingleValue, error) { 153 return ledger.NewQuerySingleValue(ledger.State(commitment), 154 convert.RegisterIDToLedgerKey(id), 155 ) 156 } 157 158 func RegisterEntriesToKeysValues( 159 entries flow.RegisterEntries, 160 ) ( 161 []ledger.Key, 162 []ledger.Value, 163 ) { 164 keys := make([]ledger.Key, len(entries)) 165 values := make([]ledger.Value, len(entries)) 166 for i, entry := range entries { 167 keys[i] = convert.RegisterIDToLedgerKey(entry.Key) 168 values[i] = entry.Value 169 } 170 return keys, values 171 } 172 173 type LedgerStorageSnapshot struct { 174 ledger ledger.Ledger 175 commitment flow.StateCommitment 176 177 mutex sync.RWMutex 178 readCache map[flow.RegisterID]flow.RegisterValue // Guarded by mutex. 179 } 180 181 func NewLedgerStorageSnapshot( 182 ldg ledger.Ledger, 183 commitment flow.StateCommitment, 184 ) snapshot.StorageSnapshot { 185 return &LedgerStorageSnapshot{ 186 ledger: ldg, 187 commitment: commitment, 188 readCache: make(map[flow.RegisterID]flow.RegisterValue), 189 } 190 } 191 192 func (storage *LedgerStorageSnapshot) getFromCache( 193 id flow.RegisterID, 194 ) ( 195 flow.RegisterValue, 196 bool, 197 ) { 198 storage.mutex.RLock() 199 defer storage.mutex.RUnlock() 200 201 value, ok := storage.readCache[id] 202 return value, ok 203 } 204 205 func (storage *LedgerStorageSnapshot) getFromLedger( 206 id flow.RegisterID, 207 ) ( 208 flow.RegisterValue, 209 error, 210 ) { 211 query, err := makeSingleValueQuery(storage.commitment, id) 212 if err != nil { 213 return nil, fmt.Errorf("cannot create ledger query: %w", err) 214 } 215 216 value, err := storage.ledger.GetSingleValue(query) 217 if err != nil { 218 return nil, fmt.Errorf( 219 "error getting register (%s) value at %x: %w", 220 id, 221 storage.commitment, 222 err) 223 } 224 225 return value, nil 226 } 227 228 func (storage *LedgerStorageSnapshot) Get( 229 id flow.RegisterID, 230 ) ( 231 flow.RegisterValue, 232 error, 233 ) { 234 value, ok := storage.getFromCache(id) 235 if ok { 236 return value, nil 237 } 238 239 value, err := storage.getFromLedger(id) 240 if err != nil { 241 return nil, err 242 } 243 244 storage.mutex.Lock() 245 defer storage.mutex.Unlock() 246 247 storage.readCache[id] = value 248 return value, nil 249 } 250 251 func (s *state) NewStorageSnapshot( 252 commitment flow.StateCommitment, 253 blockID flow.Identifier, 254 height uint64, 255 ) snapshot.StorageSnapshot { 256 if s.enableRegisterStore { 257 return storehouse.NewBlockEndStateSnapshot(s.registerStore, blockID, height) 258 } 259 return NewLedgerStorageSnapshot(s.ls, commitment) 260 } 261 262 func (s *state) CreateStorageSnapshot( 263 blockID flow.Identifier, 264 ) (snapshot.StorageSnapshot, *flow.Header, error) { 265 header, err := s.headers.ByBlockID(blockID) 266 if err != nil { 267 return nil, nil, fmt.Errorf("cannot get header by block ID: %w", err) 268 } 269 270 // make sure the block is executed 271 commit, err := s.commits.ByBlockID(blockID) 272 if err != nil { 273 // statecommitment not exists means the block hasn't been executed yet 274 if errors.Is(err, storage.ErrNotFound) { 275 return nil, nil, fmt.Errorf("block %v is never executed: %w", blockID, ErrNotExecuted) 276 } 277 278 return nil, header, fmt.Errorf("cannot get commit by block ID: %w", err) 279 } 280 281 // make sure we have trie state for this block 282 ledgerHasState := s.ls.HasState(ledger.State(commit)) 283 if !ledgerHasState { 284 return nil, header, fmt.Errorf("state not found in ledger for commit %x (block %v): %w", commit, blockID, ErrExecutionStatePruned) 285 } 286 287 if s.enableRegisterStore { 288 isExecuted, err := s.registerStore.IsBlockExecuted(header.Height, blockID) 289 if err != nil { 290 return nil, header, fmt.Errorf("cannot check if block %v is executed: %w", blockID, err) 291 } 292 if !isExecuted { 293 return nil, header, fmt.Errorf("block %v is not executed yet: %w", blockID, ErrNotExecuted) 294 } 295 } 296 297 return s.NewStorageSnapshot(commit, blockID, header.Height), header, nil 298 } 299 300 type RegisterUpdatesHolder interface { 301 UpdatedRegisters() flow.RegisterEntries 302 UpdatedRegisterSet() map[flow.RegisterID]flow.RegisterValue 303 } 304 305 // CommitDelta takes a base storage snapshot and creates a new storage snapshot 306 // with the register updates from the given RegisterUpdatesHolder 307 // a new statecommitment is returned from the ledger, along with the trie update 308 // any error returned are exceptions 309 func CommitDelta( 310 ldg ledger.Ledger, 311 ruh RegisterUpdatesHolder, 312 baseStorageSnapshot execution.ExtendableStorageSnapshot, 313 ) (flow.StateCommitment, *ledger.TrieUpdate, execution.ExtendableStorageSnapshot, error) { 314 315 updatedRegisters := ruh.UpdatedRegisters() 316 keys, values := RegisterEntriesToKeysValues(updatedRegisters) 317 baseState := baseStorageSnapshot.Commitment() 318 update, err := ledger.NewUpdate(ledger.State(baseState), keys, values) 319 320 if err != nil { 321 return flow.DummyStateCommitment, nil, nil, fmt.Errorf("cannot create ledger update: %w", err) 322 } 323 324 newState, trieUpdate, err := ldg.Set(update) 325 if err != nil { 326 return flow.DummyStateCommitment, nil, nil, fmt.Errorf("could not update ledger: %w", err) 327 } 328 329 newCommit := flow.StateCommitment(newState) 330 331 newStorageSnapshot := baseStorageSnapshot.Extend(newCommit, ruh.UpdatedRegisterSet()) 332 333 return newCommit, trieUpdate, newStorageSnapshot, nil 334 } 335 336 func (s *state) StateCommitmentByBlockID(blockID flow.Identifier) (flow.StateCommitment, error) { 337 return s.commits.ByBlockID(blockID) 338 } 339 340 func (s *state) ChunkDataPackByChunkID(chunkID flow.Identifier) (*flow.ChunkDataPack, error) { 341 chunkDataPack, err := s.chunkDataPacks.ByChunkID(chunkID) 342 if err != nil { 343 return nil, fmt.Errorf("could not retrieve stored chunk data pack: %w", err) 344 } 345 346 return chunkDataPack, nil 347 } 348 349 func (s *state) GetExecutionResultID(ctx context.Context, blockID flow.Identifier) (flow.Identifier, error) { 350 span, _ := s.tracer.StartSpanFromContext(ctx, trace.EXEGetExecutionResultID) 351 defer span.End() 352 353 result, err := s.results.ByBlockID(blockID) 354 if err != nil { 355 return flow.ZeroID, err 356 } 357 return result.ID(), nil 358 } 359 360 func (s *state) SaveExecutionResults( 361 ctx context.Context, 362 result *execution.ComputationResult, 363 ) error { 364 span, childCtx := s.tracer.StartSpanFromContext( 365 ctx, 366 trace.EXEStateSaveExecutionResults) 367 defer span.End() 368 369 err := s.saveExecutionResults(ctx, result) 370 if err != nil { 371 return fmt.Errorf("could not save execution results: %w", err) 372 } 373 374 if s.enableRegisterStore { 375 // save registers to register store 376 err = s.registerStore.SaveRegisters( 377 result.BlockExecutionResult.ExecutableBlock.Block.Header, 378 result.BlockExecutionResult.AllUpdatedRegisters(), 379 ) 380 381 if err != nil { 382 return fmt.Errorf("could not save updated registers: %w", err) 383 } 384 } 385 386 //outside batch because it requires read access 387 err = s.UpdateHighestExecutedBlockIfHigher(childCtx, result.ExecutableBlock.Block.Header) 388 if err != nil { 389 return fmt.Errorf("cannot update highest executed block: %w", err) 390 } 391 return nil 392 } 393 394 func (s *state) saveExecutionResults( 395 ctx context.Context, 396 result *execution.ComputationResult, 397 ) (err error) { 398 header := result.ExecutableBlock.Block.Header 399 blockID := header.ID() 400 401 err = s.chunkDataPacks.Store(result.AllChunkDataPacks()) 402 if err != nil { 403 return fmt.Errorf("can not store multiple chunk data pack: %w", err) 404 } 405 406 // Write Batch is BadgerDB feature designed for handling lots of writes 407 // in efficient and atomic manner, hence pushing all the updates we can 408 // as tightly as possible to let Badger manage it. 409 // Note, that it does not guarantee atomicity as transactions has size limit, 410 // but it's the closest thing to atomicity we could have 411 batch := badgerstorage.NewBatch(s.db) 412 413 defer func() { 414 // Rollback if an error occurs during batch operations 415 if err != nil { 416 chunks := result.AllChunkDataPacks() 417 chunkIDs := make([]flow.Identifier, 0, len(chunks)) 418 for _, chunk := range chunks { 419 chunkIDs = append(chunkIDs, chunk.ID()) 420 } 421 _ = s.chunkDataPacks.Remove(chunkIDs) 422 } 423 }() 424 425 err = s.events.BatchStore(blockID, []flow.EventsList{result.AllEvents()}, batch) 426 if err != nil { 427 return fmt.Errorf("cannot store events: %w", err) 428 } 429 430 err = s.serviceEvents.BatchStore(blockID, result.AllServiceEvents(), batch) 431 if err != nil { 432 return fmt.Errorf("cannot store service events: %w", err) 433 } 434 435 err = s.transactionResults.BatchStore( 436 blockID, 437 result.AllTransactionResults(), 438 batch) 439 if err != nil { 440 return fmt.Errorf("cannot store transaction result: %w", err) 441 } 442 443 executionResult := &result.ExecutionReceipt.ExecutionResult 444 err = s.results.BatchStore(executionResult, batch) 445 if err != nil { 446 return fmt.Errorf("cannot store execution result: %w", err) 447 } 448 449 err = s.results.BatchIndex(blockID, executionResult.ID(), batch) 450 if err != nil { 451 return fmt.Errorf("cannot index execution result: %w", err) 452 } 453 454 err = s.myReceipts.BatchStoreMyReceipt(result.ExecutionReceipt, batch) 455 if err != nil { 456 return fmt.Errorf("could not persist execution result: %w", err) 457 } 458 459 // the state commitment is the last data item to be stored, so that 460 // IsBlockExecuted can be implemented by checking whether state commitment exists 461 // in the database 462 err = s.commits.BatchStore(blockID, result.CurrentEndState(), batch) 463 if err != nil { 464 return fmt.Errorf("cannot store state commitment: %w", err) 465 } 466 467 err = batch.Flush() 468 if err != nil { 469 return fmt.Errorf("batch flush error: %w", err) 470 } 471 472 return nil 473 } 474 475 func (s *state) UpdateHighestExecutedBlockIfHigher(ctx context.Context, header *flow.Header) error { 476 if s.tracer != nil { 477 span, _ := s.tracer.StartSpanFromContext(ctx, trace.EXEUpdateHighestExecutedBlockIfHigher) 478 defer span.End() 479 } 480 481 return operation.RetryOnConflict(s.db.Update, procedure.UpdateHighestExecutedBlockIfHigher(header)) 482 } 483 484 // deprecated by storehouse's GetHighestFinalizedExecuted 485 func (s *state) GetHighestExecutedBlockID(ctx context.Context) (uint64, flow.Identifier, error) { 486 if s.enableRegisterStore { 487 // when storehouse is enabled, the highest executed block is consisted as 488 // the highest finalized and executed block 489 height := s.GetHighestFinalizedExecuted() 490 finalizedID, err := s.headers.BlockIDByHeight(height) 491 if err != nil { 492 return 0, flow.ZeroID, fmt.Errorf("could not get header by height %v: %w", height, err) 493 } 494 return height, finalizedID, nil 495 } 496 497 var blockID flow.Identifier 498 var height uint64 499 err := s.db.View(procedure.GetHighestExecutedBlock(&height, &blockID)) 500 if err != nil { 501 return 0, flow.ZeroID, err 502 } 503 504 return height, blockID, nil 505 } 506 507 func (s *state) GetHighestFinalizedExecuted() uint64 { 508 if !s.enableRegisterStore { 509 panic("could not get highest finalized executed height without register store enabled") 510 } 511 return s.registerStore.LastFinalizedAndExecutedHeight() 512 } 513 514 // IsBlockExecuted returns true if the block is executed, which means registers, events, 515 // results, etc are all stored. 516 // otherwise returns false 517 func (s *state) IsBlockExecuted(height uint64, blockID flow.Identifier) (bool, error) { 518 if s.enableRegisterStore { 519 return s.registerStore.IsBlockExecuted(height, blockID) 520 } 521 522 // ledger-based execution state uses commitment to determine if a block has been executed 523 _, err := s.StateCommitmentByBlockID(blockID) 524 525 // statecommitment exists means the block has been executed 526 if err == nil { 527 return true, nil 528 } 529 530 // statecommitment not exists means the block hasn't been executed yet 531 if errors.Is(err, storage.ErrNotFound) { 532 return false, nil 533 } 534 535 return false, err 536 537 }