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