github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/engine/verification/fetcher/engine.go (about) 1 package fetcher 2 3 import ( 4 "context" 5 "fmt" 6 7 "github.com/rs/zerolog" 8 "go.opentelemetry.io/otel/attribute" 9 10 "github.com/onflow/flow-go/engine" 11 "github.com/onflow/flow-go/model/chunks" 12 "github.com/onflow/flow-go/model/flow" 13 "github.com/onflow/flow-go/model/flow/filter" 14 "github.com/onflow/flow-go/model/verification" 15 "github.com/onflow/flow-go/module" 16 "github.com/onflow/flow-go/module/mempool" 17 "github.com/onflow/flow-go/module/trace" 18 "github.com/onflow/flow-go/network" 19 "github.com/onflow/flow-go/state/protocol" 20 "github.com/onflow/flow-go/storage" 21 "github.com/onflow/flow-go/utils/logging" 22 ) 23 24 // Engine implements the fetcher engine functionality. It works between a chunk consumer queue, and a verifier engine. 25 // Its input is an assigned chunk locator from the chunk consumer it is subscribed to. 26 // 27 // Its output is a verifiable chunk that it passes to the verifier engine. 28 // 29 // Fetcher engine is an AssignedChunkProcessor implementation: it receives assigned chunks to this verification node from the chunk consumer. 30 // The assigned chunks are passed on concurrent executions of its ProcessAssignedChunk method. 31 // 32 // On receiving an assigned chunk, the engine requests their chunk data pack through the requester that is attached to it. 33 // On receiving a chunk data pack response, the fetcher engine validates it, and shapes a verifiable chunk out of it, and passes it 34 // to the verifier engine. 35 type Engine struct { 36 // common 37 unit *engine.Unit 38 state protocol.State // used to verify the origin ID of chunk data response, and sealing status. 39 40 // monitoring 41 log zerolog.Logger 42 tracer module.Tracer 43 metrics module.VerificationMetrics 44 45 // memory and storage 46 pendingChunks mempool.ChunkStatuses // stores all pending chunks that their chunk data is requested from requester. 47 blocks storage.Blocks // used to for verifying collection ID. 48 headers storage.Headers // used for building verifiable chunk data. 49 results storage.ExecutionResults // used to retrieve execution result of an assigned chunk. 50 receipts storage.ExecutionReceipts // used to find executor ids of a chunk, for requesting chunk data pack. 51 52 // output interfaces 53 verifier network.Engine // used to push verifiable chunk down the verification pipeline. 54 requester ChunkDataPackRequester // used to request chunk data packs from network. 55 chunkConsumerNotifier module.ProcessingNotifier // used to notify chunk consumer that it is done processing a chunk. 56 57 stopAtHeight uint64 58 } 59 60 func New( 61 log zerolog.Logger, 62 metrics module.VerificationMetrics, 63 tracer module.Tracer, 64 verifier network.Engine, 65 state protocol.State, 66 pendingChunks mempool.ChunkStatuses, 67 headers storage.Headers, 68 blocks storage.Blocks, 69 results storage.ExecutionResults, 70 receipts storage.ExecutionReceipts, 71 requester ChunkDataPackRequester, 72 stopAtHeight uint64, 73 ) *Engine { 74 e := &Engine{ 75 unit: engine.NewUnit(), 76 metrics: metrics, 77 tracer: tracer, 78 log: log.With().Str("engine", "fetcher").Logger(), 79 verifier: verifier, 80 state: state, 81 pendingChunks: pendingChunks, 82 blocks: blocks, 83 headers: headers, 84 results: results, 85 receipts: receipts, 86 requester: requester, 87 stopAtHeight: stopAtHeight, 88 } 89 90 e.requester.WithChunkDataPackHandler(e) 91 92 return e 93 } 94 95 // WithChunkConsumerNotifier sets the processing notifier of fetcher. 96 // The fetcher engine uses this notifier to inform the chunk consumer that it is done processing a given chunk, and 97 // is ready to receive a new chunk to process. 98 func (e *Engine) WithChunkConsumerNotifier(notifier module.ProcessingNotifier) { 99 e.chunkConsumerNotifier = notifier 100 } 101 102 // Ready initializes the engine and returns a channel that is closed when the initialization is done 103 func (e *Engine) Ready() <-chan struct{} { 104 if e.chunkConsumerNotifier == nil { 105 e.log.Fatal().Msg("missing chunk consumer notifier callback in verification fetcher engine") 106 } 107 return e.unit.Ready(func() { 108 <-e.requester.Ready() 109 }) 110 } 111 112 // Done terminates the engine and returns a channel that is closed when the termination is done 113 func (e *Engine) Done() <-chan struct{} { 114 return e.unit.Done(func() { 115 <-e.requester.Done() 116 }) 117 } 118 119 // ProcessAssignedChunk is the entry point of fetcher engine. 120 // It pushes the assigned chunk down the pipeline. 121 // Through the pipeline the chunk data pack for this chunk is requested, 122 // a verifiable chunk is shaped for it, 123 // and is pushed to the verifier engine for verification. 124 // 125 // It should not be blocking since multiple chunk consumer workers might be calling it concurrently. 126 // It fetches the chunk data pack, once received, verifier engine will be verifying 127 // Once a chunk has been processed, it will call the processing notifier callback to notify 128 // the chunk consumer in order to process the next chunk. 129 func (e *Engine) ProcessAssignedChunk(locator *chunks.Locator) { 130 locatorID := locator.ID() 131 lg := e.log.With(). 132 Hex("locator_id", logging.ID(locatorID)). 133 Hex("result_id", logging.ID(locator.ResultID)). 134 Uint64("chunk_index", locator.Index). 135 Logger() 136 137 e.metrics.OnAssignedChunkReceivedAtFetcher() 138 139 // retrieves result and chunk using the locator 140 result, err := e.results.ByID(locator.ResultID) 141 if err != nil { 142 // a missing result for a chunk locator is a fatal error potentially a database leak. 143 lg.Fatal().Err(err).Msg("could not retrieve result for chunk locator") 144 } 145 chunk := result.Chunks[locator.Index] 146 chunkID := chunk.ID() 147 148 lg = lg.With(). 149 Hex("chunk_id", logging.ID(chunkID)). 150 Hex("block_id", logging.ID(chunk.ChunkBody.BlockID)). 151 Logger() 152 lg.Debug().Msg("result and chunk for locator retrieved") 153 154 requested, blockHeight, err := e.processAssignedChunkWithTracing(chunk, result, locatorID) 155 lg = lg.With().Uint64("block_height", blockHeight).Logger() 156 157 if err != nil { 158 lg.Fatal().Err(err).Msg("could not process assigned chunk") 159 } 160 161 lg.Info().Bool("requested", requested).Msg("assigned chunk processed successfully") 162 163 if requested { 164 e.metrics.OnChunkDataPackRequestSentByFetcher() 165 } 166 167 } 168 169 // processAssignedChunkWithTracing encapsulates the logic of processing assigned chunk with tracing enabled. 170 func (e *Engine) processAssignedChunkWithTracing(chunk *flow.Chunk, result *flow.ExecutionResult, chunkLocatorID flow.Identifier) (bool, uint64, error) { 171 172 span, _ := e.tracer.StartBlockSpan(e.unit.Ctx(), result.BlockID, trace.VERProcessAssignedChunk) 173 span.SetAttributes(attribute.Int("collection_index", int(chunk.CollectionIndex))) 174 defer span.End() 175 176 requested, blockHeight, err := e.processAssignedChunk(chunk, result, chunkLocatorID) 177 178 return requested, blockHeight, err 179 } 180 181 // processAssignedChunk receives an assigned chunk and its result and requests its chunk data pack from requester. 182 // Boolean return value determines whether chunk data pack was requested or not. 183 func (e *Engine) processAssignedChunk(chunk *flow.Chunk, result *flow.ExecutionResult, chunkLocatorID flow.Identifier) (bool, uint64, error) { 184 // skips processing a chunk if it belongs to a sealed block. 185 chunkID := chunk.ID() 186 sealed, blockHeight, err := e.blockIsSealed(chunk.ChunkBody.BlockID) 187 if err != nil { 188 return false, 0, fmt.Errorf("could not determine whether block has been sealed: %w", err) 189 } 190 if sealed { 191 e.chunkConsumerNotifier.Notify(chunkLocatorID) // tells consumer that we are done with this chunk. 192 return false, blockHeight, nil 193 } 194 195 // skip chunk if it verifies a block at or above stop height 196 if e.stopAtHeight > 0 && blockHeight >= e.stopAtHeight { 197 e.log.Warn().Msgf("Skipping chunk %s - height %d at or above stop height requested (%d)", chunkID, blockHeight, e.stopAtHeight) 198 e.chunkConsumerNotifier.Notify(chunkLocatorID) // tells consumer that we are done with this chunk. 199 return false, blockHeight, nil 200 } 201 202 // adds chunk status as a pending chunk to mempool. 203 status := &verification.ChunkStatus{ 204 ChunkIndex: chunk.Index, 205 ExecutionResult: result, 206 BlockHeight: blockHeight, 207 } 208 added := e.pendingChunks.Add(status) 209 if !added { 210 return false, blockHeight, nil 211 } 212 213 err = e.requestChunkDataPack(chunk.Index, chunkID, result.ID(), chunk.BlockID) 214 if err != nil { 215 return false, blockHeight, fmt.Errorf("could not request chunk data pack: %w", err) 216 } 217 218 // requesting a chunk data pack is async, i.e., once engine reaches this point 219 // it gracefully waits (unblocking) for the requested 220 // till it either delivers us the requested chunk data pack 221 // or cancels our request (when chunk belongs to a sealed block). 222 // 223 // both these events happen through requester module calling fetchers callbacks. 224 // it is during those callbacks that we notify the consumer that we are done with this job. 225 return true, blockHeight, nil 226 } 227 228 // HandleChunkDataPack is called by the chunk requester module everytime a new requested chunk data pack arrives. 229 // The chunks are supposed to be deduplicated by the requester. 230 // So invocation of this method indicates arrival of a distinct requested chunk. 231 func (e *Engine) HandleChunkDataPack(originID flow.Identifier, response *verification.ChunkDataPackResponse) { 232 lg := e.log.With(). 233 Hex("origin_id", logging.ID(originID)). 234 Hex("chunk_id", logging.ID(response.Cdp.ChunkID)). 235 Logger() 236 237 if response.Cdp.Collection != nil { 238 // non-system chunk data packs have non-nil collection 239 lg = lg.With(). 240 Hex("collection_id", logging.ID(response.Cdp.Collection.ID())). 241 Logger() 242 lg.Info().Msg("chunk data pack arrived") 243 } else { 244 lg.Info().Msg("system chunk data pack arrived") 245 } 246 247 e.metrics.OnChunkDataPackArrivedAtFetcher() 248 249 // make sure we still need it 250 status, exists := e.pendingChunks.Get(response.Index, response.ResultID) 251 if !exists { 252 lg.Debug().Msg("could not fetch pending status from mempool, dropping chunk data") 253 return 254 } 255 256 resultID := status.ExecutionResult.ID() 257 lg = lg.With(). 258 Hex("block_id", logging.ID(status.ExecutionResult.BlockID)). 259 Uint64("block_height", status.BlockHeight). 260 Hex("result_id", logging.ID(resultID)). 261 Uint64("chunk_index", status.ChunkIndex). 262 Bool("system_chunk", IsSystemChunk(status.ChunkIndex, status.ExecutionResult)). 263 Logger() 264 265 span, ctx := e.tracer.StartBlockSpan(context.Background(), status.ExecutionResult.BlockID, trace.VERFetcherHandleChunkDataPack) 266 defer span.End() 267 268 processed, err := e.handleChunkDataPackWithTracing(ctx, originID, status, response.Cdp) 269 if IsChunkDataPackValidationError(err) { 270 lg.Error().Err(err).Msg("could not validate chunk data pack") 271 return 272 } 273 274 if err != nil { 275 // TODO: byzantine fault 276 lg.Fatal().Err(err).Msg("could not handle chunk data pack") 277 return 278 } 279 280 if processed { 281 e.metrics.OnVerifiableChunkSentToVerifier() 282 283 // we need to report that the job has been finished eventually 284 e.chunkConsumerNotifier.Notify(status.ChunkLocatorID()) 285 lg.Info().Msg("verifiable chunk pushed to verifier engine") 286 } 287 288 } 289 290 // handleChunkDataPackWithTracing encapsulates the logic of handling chunk data pack with tracing enabled. 291 // 292 // Boolean returned value determines whether the chunk data pack passed validation and its verifiable chunk 293 // submitted to verifier. 294 // The first returned value determines non-critical errors (i.e., expected ones). 295 // The last returned value determines the critical errors that are unexpected, and should lead program to halt. 296 func (e *Engine) handleChunkDataPackWithTracing( 297 ctx context.Context, 298 originID flow.Identifier, 299 status *verification.ChunkStatus, 300 chunkDataPack *flow.ChunkDataPack) (bool, error) { 301 302 // make sure the chunk data pack is valid 303 err := e.validateChunkDataPackWithTracing(ctx, status.ChunkIndex, originID, chunkDataPack, status.ExecutionResult) 304 if err != nil { 305 return false, NewChunkDataPackValidationError(originID, 306 status.ExecutionResult.ID(), 307 status.ChunkIndex, 308 chunkDataPack.ID(), 309 chunkDataPack.ChunkID, 310 chunkDataPack.Collection.ID(), 311 err) 312 } 313 314 processed, err := e.handleValidatedChunkDataPack(ctx, status, chunkDataPack) 315 if err != nil { 316 return processed, fmt.Errorf("could not handle validated chunk data pack: %w", err) 317 } 318 319 return processed, nil 320 } 321 322 // handleValidatedChunkDataPack receives a validated chunk data pack, removes its status from the memory, and pushes a verifiable chunk for it to 323 // verifier engine. 324 // Boolean return value determines whether verifiable chunk pushed to verifier or not. 325 func (e *Engine) handleValidatedChunkDataPack(ctx context.Context, 326 status *verification.ChunkStatus, 327 chunkDataPack *flow.ChunkDataPack) (bool, error) { 328 329 removed := e.pendingChunks.Remove(status.ChunkIndex, status.ExecutionResult.ID()) 330 if !removed { 331 // we deduplicate the chunk data responses at this point, reaching here means a 332 // duplicate chunk data response is under process concurrently, so we give up 333 // on processing current one. 334 return false, nil 335 } 336 337 // pushes chunk data pack to verifier, and waits for it to be verified. 338 chunk := status.ExecutionResult.Chunks[status.ChunkIndex] 339 err := e.pushToVerifierWithTracing(ctx, chunk, status.ExecutionResult, chunkDataPack) 340 if err != nil { 341 return false, fmt.Errorf("could not push the chunk to verifier engine") 342 } 343 344 return true, nil 345 } 346 347 // validateChunkDataPackWithTracing encapsulates the logic of validating a chunk data pack with tracing enabled. 348 func (e *Engine) validateChunkDataPackWithTracing(ctx context.Context, 349 chunkIndex uint64, 350 senderID flow.Identifier, 351 chunkDataPack *flow.ChunkDataPack, 352 result *flow.ExecutionResult) error { 353 354 var err error 355 e.tracer.WithSpanFromContext(ctx, trace.VERFetcherValidateChunkDataPack, func() { 356 err = e.validateChunkDataPack(chunkIndex, senderID, chunkDataPack, result) 357 }) 358 359 return err 360 } 361 362 // validateChunkDataPack validates the integrity of a received chunk data pack as well as the authenticity of its sender. 363 // Regarding the integrity: the chunk data pack should have a matching start state with the chunk itself, as well as a matching collection ID with the 364 // given collection. 365 // 366 // Regarding the authenticity: the chunk data pack should be coming from a sender that is an authorized execution node at the block of the chunk. 367 func (e *Engine) validateChunkDataPack(chunkIndex uint64, 368 senderID flow.Identifier, 369 chunkDataPack *flow.ChunkDataPack, 370 result *flow.ExecutionResult) error { 371 372 chunk := result.Chunks[chunkIndex] 373 // 1. chunk ID of chunk data pack should map the chunk ID on execution result 374 expectedChunkID := chunk.ID() 375 if chunkDataPack.ChunkID != expectedChunkID { 376 return fmt.Errorf("chunk ID of chunk data pack does not match corresponding chunk on execution result, expected: %x, got:%x", 377 expectedChunkID, chunkDataPack.ChunkID) 378 } 379 380 // 2. sender must be a authorized execution node at that block 381 blockID := chunk.BlockID 382 authorized := e.validateAuthorizedExecutionNodeAtBlockID(senderID, blockID) 383 if !authorized { 384 return fmt.Errorf("unauthorized execution node sender at block ID: %x, resultID: %x, chunk ID: %x", 385 blockID, 386 result.ID(), 387 chunk.ID()) 388 } 389 390 // 3. start state must match 391 if chunkDataPack.StartState != chunk.ChunkBody.StartState { 392 return engine.NewInvalidInputErrorf("expecting chunk data pack's start state: %x, but got: %x, block ID: %x, resultID: %x, chunk ID: %x", 393 chunk.ChunkBody.StartState, 394 chunkDataPack.StartState, 395 blockID, 396 result.ID(), 397 chunk.ID()) 398 } 399 400 // 3. collection id must match 401 err := e.validateCollectionID(chunkDataPack, result, chunk) 402 if err != nil { 403 return fmt.Errorf("could not validate collection: %w", err) 404 } 405 406 return nil 407 } 408 409 // validateCollectionID returns error for an invalid collection of a chunk data pack, 410 // and returns nil otherwise. 411 func (e Engine) validateCollectionID( 412 chunkDataPack *flow.ChunkDataPack, 413 result *flow.ExecutionResult, 414 chunk *flow.Chunk) error { 415 416 if IsSystemChunk(chunk.Index, result) { 417 return e.validateSystemChunkCollection(chunkDataPack) 418 } 419 420 return e.validateNonSystemChunkCollection(chunkDataPack, chunk) 421 } 422 423 // validateSystemChunkCollection returns nil if the system chunk data pack has a nil collection. 424 func (e Engine) validateSystemChunkCollection(chunkDataPack *flow.ChunkDataPack) error { 425 // collection of a system chunk should be nil 426 if chunkDataPack.Collection != nil { 427 return engine.NewInvalidInputErrorf("non-nil collection for system chunk, collection ID: %v, len: %d", 428 chunkDataPack.Collection.ID(), chunkDataPack.Collection.Len()) 429 } 430 431 return nil 432 } 433 434 // validateNonSystemChunkCollection returns nil if the collection is matching the non-system chunk data pack. 435 // A collection is valid against a non-system chunk if it has a matching ID with the 436 // collection ID of corresponding guarantee of the chunk in the referenced block payload. 437 func (e Engine) validateNonSystemChunkCollection(chunkDataPack *flow.ChunkDataPack, chunk *flow.Chunk) error { 438 collID := chunkDataPack.Collection.ID() 439 440 block, err := e.blocks.ByID(chunk.BlockID) 441 if err != nil { 442 return fmt.Errorf("could not get block: %w", err) 443 } 444 445 if block.Payload.Guarantees[chunk.Index].CollectionID != collID { 446 return engine.NewInvalidInputErrorf("mismatch collection id with guarantee, expected: %v, got: %v", 447 block.Payload.Guarantees[chunk.Index].CollectionID, 448 collID) 449 } 450 451 return nil 452 } 453 454 // validateAuthorizedExecutionNodeAtBlockID validates sender ID of a chunk data pack response as an authorized 455 // execution node at the given block ID. 456 func (e Engine) validateAuthorizedExecutionNodeAtBlockID(senderID flow.Identifier, blockID flow.Identifier) bool { 457 snapshot := e.state.AtBlockID(blockID) 458 valid, err := protocol.IsNodeAuthorizedWithRoleAt(snapshot, senderID, flow.RoleExecution) 459 460 if err != nil { 461 e.log.Fatal(). 462 Err(err). 463 Hex("block_id", logging.ID(blockID)). 464 Hex("sender_id", logging.ID(senderID)). 465 Msg("could not validate sender identity at specified block ID snapshot as execution node") 466 } 467 468 return valid 469 } 470 471 // NotifyChunkDataPackSealed is called by the ChunkDataPackRequester to notify the ChunkDataPackHandler that the specified chunk 472 // has been sealed and hence the requester will no longer request it. 473 // 474 // When the requester calls this callback method, it will never return a chunk data pack for this specified chunk to the handler (i.e., 475 // through HandleChunkDataPack). 476 func (e *Engine) NotifyChunkDataPackSealed(chunkIndex uint64, resultID flow.Identifier) { 477 lg := e.log.With(). 478 Uint64("chunk_index", chunkIndex). 479 Hex("result_id", logging.ID(resultID)). 480 Logger() 481 482 // we need to report that the job has been finished eventually 483 status, exists := e.pendingChunks.Get(chunkIndex, resultID) 484 if !exists { 485 lg.Debug(). 486 Msg("could not fetch pending status for sealed chunk from mempool, dropping chunk data") 487 return 488 } 489 490 chunkLocatorID := status.ChunkLocatorID() 491 lg = lg.With(). 492 Uint64("block_height", status.BlockHeight). 493 Hex("result_id", logging.ID(status.ExecutionResult.ID())).Logger() 494 removed := e.pendingChunks.Remove(chunkIndex, resultID) 495 496 e.chunkConsumerNotifier.Notify(chunkLocatorID) 497 lg.Info(). 498 Bool("removed", removed). 499 Msg("discards fetching chunk of an already sealed block and notified consumer") 500 } 501 502 // pushToVerifierWithTracing encapsulates the logic of pushing a verifiable chunk to verifier engine with tracing enabled. 503 func (e *Engine) pushToVerifierWithTracing( 504 ctx context.Context, 505 chunk *flow.Chunk, 506 result *flow.ExecutionResult, 507 chunkDataPack *flow.ChunkDataPack) error { 508 509 var err error 510 e.tracer.WithSpanFromContext(ctx, trace.VERFetcherPushToVerifier, func() { 511 err = e.pushToVerifier(chunk, result, chunkDataPack) 512 }) 513 514 return err 515 } 516 517 // pushToVerifier makes a verifiable chunk data out of the input and pass it to the verifier for verification. 518 // 519 // When this method returns without any error, it means that the verification of the chunk at the verifier engine is done (either successfully, 520 // or unsuccessfully) 521 func (e *Engine) pushToVerifier(chunk *flow.Chunk, 522 result *flow.ExecutionResult, 523 chunkDataPack *flow.ChunkDataPack) error { 524 525 header, err := e.headers.ByBlockID(chunk.BlockID) 526 if err != nil { 527 return fmt.Errorf("could not get block: %w", err) 528 } 529 snapshot := e.state.AtBlockID(header.ID()) 530 vchunk, err := e.makeVerifiableChunkData(chunk, header, snapshot, result, chunkDataPack) 531 if err != nil { 532 return fmt.Errorf("could not verify chunk: %w", err) 533 } 534 535 err = e.verifier.ProcessLocal(vchunk) 536 if err != nil { 537 return fmt.Errorf("verifier could not verify chunk: %w", err) 538 } 539 540 return nil 541 } 542 543 // makeVerifiableChunkData creates and returns a verifiable chunk data for the chunk data. 544 // The verifier engine, which is the last engine in the pipeline of verification, uses this verifiable 545 // chunk data to verify it. 546 func (e *Engine) makeVerifiableChunkData(chunk *flow.Chunk, 547 header *flow.Header, 548 snapshot protocol.Snapshot, 549 result *flow.ExecutionResult, 550 chunkDataPack *flow.ChunkDataPack, 551 ) (*verification.VerifiableChunkData, error) { 552 553 // system chunk is the last chunk 554 isSystemChunk := IsSystemChunk(chunk.Index, result) 555 556 endState, err := EndStateCommitment(result, chunk.Index, isSystemChunk) 557 if err != nil { 558 return nil, fmt.Errorf("could not compute end state of chunk: %w", err) 559 } 560 561 transactionOffset, err := TransactionOffsetForChunk(result.Chunks, chunk.Index) 562 if err != nil { 563 return nil, fmt.Errorf("cannot compute transaction offset for chunk: %w", err) 564 } 565 566 return &verification.VerifiableChunkData{ 567 IsSystemChunk: isSystemChunk, 568 Chunk: chunk, 569 Header: header, 570 Snapshot: snapshot, 571 Result: result, 572 ChunkDataPack: chunkDataPack, 573 EndState: endState, 574 TransactionOffset: transactionOffset, 575 }, nil 576 } 577 578 // requestChunkDataPack creates and dispatches a chunk data pack request to the requester engine. 579 func (e *Engine) requestChunkDataPack(chunkIndex uint64, chunkID flow.Identifier, resultID flow.Identifier, blockID flow.Identifier) error { 580 agrees, disagrees, err := e.getAgreeAndDisagreeExecutors(blockID, resultID) 581 if err != nil { 582 return fmt.Errorf("could not segregate the agree and disagree executors for result: %x of block: %x", resultID, blockID) 583 } 584 585 header, err := e.headers.ByBlockID(blockID) 586 if err != nil { 587 return fmt.Errorf("could not get header for block: %x", blockID) 588 } 589 590 allExecutors, err := e.state.AtBlockID(blockID).Identities(filter.HasRole[flow.Identity](flow.RoleExecution)) 591 if err != nil { 592 return fmt.Errorf("could not fetch execution node ids at block %x: %w", blockID, err) 593 } 594 595 request := &verification.ChunkDataPackRequest{ 596 Locator: chunks.Locator{ 597 ResultID: resultID, 598 Index: chunkIndex, 599 }, 600 ChunkDataPackRequestInfo: verification.ChunkDataPackRequestInfo{ 601 ChunkID: chunkID, 602 Height: header.Height, 603 Agrees: agrees, 604 Disagrees: disagrees, 605 Targets: allExecutors, 606 }, 607 } 608 609 e.requester.Request(request) 610 611 return nil 612 } 613 614 // getAgreeAndDisagreeExecutors segregates the execution nodes identifiers based on the given execution result id at the given block into agree and 615 // disagree sets. 616 // The agree set contains the executors who made receipt with the same result as the given result id. 617 // The disagree set contains the executors who made receipt with different result than the given result id. 618 func (e *Engine) getAgreeAndDisagreeExecutors(blockID flow.Identifier, resultID flow.Identifier) (flow.IdentifierList, flow.IdentifierList, error) { 619 receipts, err := e.receipts.ByBlockID(blockID) 620 if err != nil { 621 return nil, nil, fmt.Errorf("could not retrieve receipts for block: %v: %w", blockID, err) 622 } 623 624 agrees, disagrees := executorsOf(receipts, resultID) 625 return agrees, disagrees, nil 626 } 627 628 // blockIsSealed returns true if the block at specified height by block ID is sealed. 629 func (e Engine) blockIsSealed(blockID flow.Identifier) (bool, uint64, error) { 630 // TODO: as an optimization, we can keep record of last sealed height on a local variable. 631 header, err := e.headers.ByBlockID(blockID) 632 if err != nil { 633 return false, 0, fmt.Errorf("could not get block: %w", err) 634 } 635 636 lastSealed, err := e.state.Sealed().Head() 637 if err != nil { 638 return false, 0, fmt.Errorf("could not get last sealed: %w", err) 639 } 640 641 sealed := header.Height <= lastSealed.Height 642 return sealed, header.Height, nil 643 } 644 645 // executorsOf segregates the executors of the given receipts based on the given execution result id. 646 // The agree set contains the executors who made receipt with the same result as the given result id. 647 // The disagree set contains the executors who made receipt with different result than the given result id. 648 func executorsOf(receipts []*flow.ExecutionReceipt, resultID flow.Identifier) (flow.IdentifierList, flow.IdentifierList) { 649 var agrees flow.IdentifierList 650 var disagrees flow.IdentifierList 651 652 for _, receipt := range receipts { 653 executor := receipt.ExecutorID 654 655 if receipt.ExecutionResult.ID() == resultID { 656 agrees = append(agrees, executor) 657 } else { 658 disagrees = append(disagrees, executor) 659 } 660 } 661 662 return agrees, disagrees 663 } 664 665 // EndStateCommitment computes the end state of the given chunk. 666 func EndStateCommitment(result *flow.ExecutionResult, chunkIndex uint64, systemChunk bool) (flow.StateCommitment, error) { 667 var endState flow.StateCommitment 668 if systemChunk { 669 var err error 670 // last chunk in a result is the system chunk and takes final state commitment 671 endState, err = result.FinalStateCommitment() 672 if err != nil { 673 return flow.DummyStateCommitment, fmt.Errorf("can not read final state commitment, likely a bug:%w", err) 674 } 675 } else { 676 // any chunk except last takes the subsequent chunk's start state 677 endState = result.Chunks[chunkIndex+1].StartState 678 } 679 680 return endState, nil 681 } 682 683 // TransactionOffsetForChunk calculates transaction offset for a given chunk which is the index of the first 684 // transaction of this chunk within the whole block 685 func TransactionOffsetForChunk(chunks flow.ChunkList, chunkIndex uint64) (uint32, error) { 686 if int(chunkIndex) > len(chunks)-1 { 687 return 0, fmt.Errorf("chunk list out of bounds, len %d asked for chunk %d", len(chunks), chunkIndex) 688 } 689 var offset uint32 = 0 690 for i := 0; i < int(chunkIndex); i++ { 691 offset += uint32(chunks[i].NumberOfTransactions) 692 } 693 return offset, nil 694 } 695 696 // IsSystemChunk returns true if `chunkIndex` points to a system chunk in `result`. 697 // Otherwise, it returns false. 698 // In the current version, a chunk is a system chunk if it is the last chunk of the 699 // execution result. 700 func IsSystemChunk(chunkIndex uint64, result *flow.ExecutionResult) bool { 701 return chunkIndex == uint64(len(result.Chunks)-1) 702 }