github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/engine/consensus/sealing/core.go (about) 1 package sealing 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "time" 8 9 "github.com/gammazero/workerpool" 10 "github.com/onflow/crypto/hash" 11 "github.com/rs/zerolog" 12 "go.opentelemetry.io/otel/attribute" 13 otelTrace "go.opentelemetry.io/otel/trace" 14 15 "github.com/onflow/flow-go/engine" 16 "github.com/onflow/flow-go/engine/consensus" 17 "github.com/onflow/flow-go/engine/consensus/approvals" 18 "github.com/onflow/flow-go/model/flow" 19 "github.com/onflow/flow-go/module" 20 "github.com/onflow/flow-go/module/counters" 21 "github.com/onflow/flow-go/module/mempool" 22 "github.com/onflow/flow-go/module/trace" 23 "github.com/onflow/flow-go/network" 24 "github.com/onflow/flow-go/state/fork" 25 "github.com/onflow/flow-go/state/protocol" 26 "github.com/onflow/flow-go/storage" 27 "github.com/onflow/flow-go/utils/logging" 28 ) 29 30 // Core is an implementation of SealingCore interface 31 // This struct is responsible for: 32 // - collecting approvals for execution results 33 // - processing multiple incorporated results 34 // - pre-validating approvals (if they are outdated or non-verifiable) 35 // - pruning already processed collectorTree 36 type Core struct { 37 unit *engine.Unit 38 workerPool *workerpool.WorkerPool // worker pool used by collectors 39 log zerolog.Logger // used to log relevant actions with context 40 collectorTree *approvals.AssignmentCollectorTree // levelled forest for assignment collectors 41 approvalsCache *approvals.LruCache // in-memory cache of approvals that weren't verified 42 counterLastSealedHeight counters.StrictMonotonousCounter // monotonous counter for last sealed block height 43 counterLastFinalizedHeight counters.StrictMonotonousCounter // monotonous counter for last finalized block height 44 headers storage.Headers // used to access block headers in storage 45 state protocol.State // used to access protocol state 46 seals storage.Seals // used to get last sealed block 47 sealsMempool mempool.IncorporatedResultSeals // used by tracker.SealingObservation to log info 48 requestTracker *approvals.RequestTracker // used to keep track of number of approval requests, and blackout periods, by chunk 49 metrics module.ConsensusMetrics // used to track consensus metrics 50 sealingTracker consensus.SealingTracker // logic-aware component for tracking sealing progress. 51 tracer module.Tracer // used to trace execution 52 sealingConfigsGetter module.SealingConfigsGetter // used to access configs for sealing conditions 53 } 54 55 func NewCore( 56 log zerolog.Logger, 57 workerPool *workerpool.WorkerPool, 58 tracer module.Tracer, 59 conMetrics module.ConsensusMetrics, 60 sealingTracker consensus.SealingTracker, 61 unit *engine.Unit, 62 headers storage.Headers, 63 state protocol.State, 64 sealsDB storage.Seals, 65 assigner module.ChunkAssigner, 66 signatureHasher hash.Hasher, 67 sealsMempool mempool.IncorporatedResultSeals, 68 approvalConduit network.Conduit, 69 sealingConfigsGetter module.SealingConfigsGetter, 70 ) (*Core, error) { 71 lastSealed, err := state.Sealed().Head() 72 if err != nil { 73 return nil, fmt.Errorf("could not retrieve last sealed block: %w", err) 74 } 75 76 core := &Core{ 77 log: log.With().Str("engine", "sealing.Core").Logger(), 78 workerPool: workerPool, 79 tracer: tracer, 80 metrics: conMetrics, 81 sealingTracker: sealingTracker, 82 unit: unit, 83 approvalsCache: approvals.NewApprovalsLRUCache(1000), 84 counterLastSealedHeight: counters.NewMonotonousCounter(lastSealed.Height), 85 counterLastFinalizedHeight: counters.NewMonotonousCounter(lastSealed.Height), 86 headers: headers, 87 state: state, 88 seals: sealsDB, 89 sealsMempool: sealsMempool, 90 requestTracker: approvals.NewRequestTracker(headers, 10, 30), 91 sealingConfigsGetter: sealingConfigsGetter, 92 } 93 94 factoryMethod := func(result *flow.ExecutionResult) (approvals.AssignmentCollector, error) { 95 requiredApprovalsForSealConstruction := sealingConfigsGetter.RequireApprovalsForSealConstructionDynamicValue() 96 base, err := approvals.NewAssignmentCollectorBase(core.log, core.workerPool, result, core.state, core.headers, 97 assigner, sealsMempool, signatureHasher, 98 approvalConduit, core.requestTracker, requiredApprovalsForSealConstruction) 99 if err != nil { 100 return nil, fmt.Errorf("could not create base collector: %w", err) 101 } 102 return approvals.NewAssignmentCollectorStateMachine(base), nil 103 } 104 105 core.collectorTree = approvals.NewAssignmentCollectorTree(lastSealed, headers, factoryMethod) 106 107 return core, nil 108 } 109 110 // RepopulateAssignmentCollectorTree restores latest state of assignment collector tree based on local chain state information. 111 // Repopulating is split into two parts: 112 // 1) traverse forward all finalized blocks starting from last sealed block till we reach last finalized block . (lastSealedHeight, lastFinalizedHeight] 113 // 2) traverse forward all unfinalized(pending) blocks starting from last finalized block. 114 // For each block that is being traversed we will collect execution results and process them using sealing.Core. 115 func (c *Core) RepopulateAssignmentCollectorTree(payloads storage.Payloads) error { 116 finalizedSnapshot := c.state.Final() 117 finalized, err := finalizedSnapshot.Head() 118 if err != nil { 119 return fmt.Errorf("could not retrieve finalized block: %w", err) 120 } 121 finalizedID := finalized.ID() 122 123 // Get the latest sealed block on this fork, ie the highest block for which 124 // there is a seal in this fork. 125 latestSeal, err := c.seals.HighestInFork(finalizedID) 126 if err != nil { 127 return fmt.Errorf("could not retrieve parent seal (%x): %w", finalizedID, err) 128 } 129 130 latestSealedBlockID := latestSeal.BlockID 131 latestSealedBlock, err := c.headers.ByBlockID(latestSealedBlockID) 132 if err != nil { 133 return fmt.Errorf("could not retrieve latest sealed block (%x): %w", latestSealedBlockID, err) 134 } 135 136 // Get the root block of our local state - we allow references to unknown 137 // blocks below the root height 138 rootHeader := c.state.Params().FinalizedRoot() 139 140 // Determine the list of unknown blocks referenced within the sealing segment 141 // if we are initializing with a latest sealed block below the root height 142 outdatedBlockIDs, err := c.getOutdatedBlockIDsFromRootSealingSegment(rootHeader) 143 if err != nil { 144 return fmt.Errorf("could not get outdated block IDs from root segment: %w", err) 145 } 146 147 blocksProcessed := uint64(0) 148 totalBlocks := finalized.Height - latestSealedBlock.Height 149 150 // resultProcessor adds _all known_ results for the given block to the assignment collector tree 151 resultProcessor := func(header *flow.Header) error { 152 blockID := header.ID() 153 payload, err := payloads.ByBlockID(blockID) 154 if err != nil { 155 return fmt.Errorf("could not retrieve index for block (%x): %w", blockID, err) 156 } 157 158 for _, result := range payload.Results { 159 // skip results referencing blocks before the root sealing segment 160 _, isOutdated := outdatedBlockIDs[result.BlockID] 161 if isOutdated { 162 c.log.Debug(). 163 Hex("container_block_id", logging.ID(blockID)). 164 Hex("result_id", logging.ID(result.ID())). 165 Hex("executed_block_id", logging.ID(result.BlockID)). 166 Msg("skipping outdated block referenced in root sealing segment") 167 continue 168 } 169 incorporatedResult := flow.NewIncorporatedResult(blockID, result) 170 err = c.ProcessIncorporatedResult(incorporatedResult) 171 if err != nil { 172 return fmt.Errorf("could not process incorporated result from block %s: %w", blockID, err) 173 } 174 } 175 176 blocksProcessed++ 177 if (blocksProcessed%20) == 0 || blocksProcessed >= totalBlocks { 178 c.log.Debug().Msgf("%d/%d have been loaded to collector tree", blocksProcessed, totalBlocks) 179 } 180 181 return nil 182 } 183 184 c.log.Info().Msgf("reloading assignments from %d finalized, unsealed blocks into collector tree", totalBlocks) 185 186 // traverse chain forward to collect all execution results that were incorporated in this fork 187 // we start with processing the direct child of the last finalized block and end with the last finalized block 188 err = fork.TraverseForward(c.headers, finalizedID, resultProcessor, fork.ExcludingBlock(latestSealedBlockID)) 189 if err != nil { 190 return fmt.Errorf("internal error while traversing fork: %w", err) 191 } 192 193 // at this point we have processed all results in range (lastSealedBlock, lastFinalizedBlock]. 194 // Now, we add all known results for any valid block that descends from the latest finalized block: 195 validPending, err := finalizedSnapshot.Descendants() 196 if err != nil { 197 return fmt.Errorf("could not retrieve valid pending blocks from finalized snapshot: %w", err) 198 } 199 200 blocksProcessed = 0 201 totalBlocks = uint64(len(validPending)) 202 203 c.log.Info().Msgf("reloading assignments from %d unfinalized blocks into collector tree", len(validPending)) 204 205 // We use AssignmentCollectorTree for collecting approvals for each incorporated result. 206 // In order to verify the received approvals, the verifier assignment for each incorporated result 207 // needs to be known. 208 // The verifier assignment is random, its Source of Randomness (SoR) is only available if a valid 209 // child block exists. 210 // In other words, the parent of a valid block must have the SoR available. Therefore, we traverse 211 // through valid pending blocks which already have a valid child, and load each result in those block 212 // into the AssignmentCollectorTree. 213 for _, blockID := range validPending { 214 block, err := c.headers.ByBlockID(blockID) 215 if err != nil { 216 return fmt.Errorf("could not retrieve header for unfinalized block %x: %w", blockID, err) 217 } 218 219 parent, err := c.headers.ByBlockID(block.ParentID) 220 if err != nil { 221 return fmt.Errorf("could not retrieve header for unfinalized block %x: %w", block.ParentID, err) 222 } 223 224 err = resultProcessor(parent) 225 if err != nil { 226 return fmt.Errorf("failed to process results for unfinalized block %x at height %d: %w", blockID, block.Height, err) 227 } 228 } 229 230 return nil 231 } 232 233 // processIncorporatedResult implements business logic for processing single incorporated result 234 // Returns: 235 // * engine.InvalidInputError - incorporated result is invalid 236 // * engine.UnverifiableInputError - result is unverifiable since referenced block cannot be found 237 // * engine.OutdatedInputError - result is outdated for instance block was already sealed 238 // * exception in case of any other error, usually this is not expected 239 // * nil - successfully processed incorporated result 240 func (c *Core) processIncorporatedResult(incRes *flow.IncorporatedResult) error { 241 err := c.checkBlockOutdated(incRes.Result.BlockID) 242 if err != nil { 243 return fmt.Errorf("won't process outdated or unverifiable execution incRes %s: %w", incRes.Result.BlockID, err) 244 } 245 incorporatedBlock, err := c.headers.ByBlockID(incRes.IncorporatedBlockID) 246 if err != nil { 247 return fmt.Errorf("could not get block height for incorporated block %s: %w", 248 incRes.IncorporatedBlockID, err) 249 } 250 incorporatedAtHeight := incorporatedBlock.Height 251 252 // For incorporating blocks at heights that are already finalized, we check that the incorporating block 253 // is on the finalized fork. Otherwise, the incorporating block is orphaned, and we can drop the result. 254 if incorporatedAtHeight <= c.counterLastFinalizedHeight.Value() { 255 finalizedID, err := c.headers.BlockIDByHeight(incorporatedAtHeight) 256 if err != nil { 257 return fmt.Errorf("could not retrieve finalized block at height %d: %w", incorporatedAtHeight, err) 258 } 259 if finalizedID != incRes.IncorporatedBlockID { 260 // it means that we got incorporated incRes for a block which doesn't extend our chain 261 // and should be discarded from future processing 262 return engine.NewOutdatedInputErrorf("won't process incorporated incRes from orphan block %s", incRes.IncorporatedBlockID) 263 } 264 } 265 266 // Get (or create) assignment collector for the respective result (atomic operation) and 267 // add the assignment for the incorporated result to it. (No-op if assignment already known). 268 // Here, we just add assignment collectors to the tree. Cleanup of orphaned and sealed assignments 269 // IRs whenever new finalized block is processed 270 lazyCollector, err := c.collectorTree.GetOrCreateCollector(incRes.Result) 271 if err != nil { 272 return fmt.Errorf("cannot create collector: %w", err) 273 } 274 err = lazyCollector.Collector.ProcessIncorporatedResult(incRes) 275 if err != nil { 276 return fmt.Errorf("could not process incorporated incRes: %w", err) 277 } 278 279 // process pending approvals only if it's a new collector 280 // pending approvals are those we haven't received its incRes yet, 281 // once we received a incRes and created a new collector, we find the pending 282 // approvals for this incRes, and process them 283 // newIncorporatedResult should be true only for one goroutine even if multiple access this code at the same 284 // time, ensuring that processing of pending approvals happens once for particular assignment 285 if lazyCollector.Created { 286 err = c.processPendingApprovals(lazyCollector.Collector) 287 if err != nil { 288 return fmt.Errorf("could not process cached approvals: %w", err) 289 } 290 } 291 292 return nil 293 } 294 295 // ProcessIncorporatedResult processes incorporated result in blocking way. Concurrency safe. 296 // Returns: 297 // * exception in case of unexpected error 298 // * nil - successfully processed incorporated result 299 func (c *Core) ProcessIncorporatedResult(result *flow.IncorporatedResult) error { 300 301 span, _ := c.tracer.StartBlockSpan(context.Background(), result.Result.BlockID, trace.CONSealingProcessIncorporatedResult) 302 defer span.End() 303 304 err := c.processIncorporatedResult(result) 305 // We expect only engine.OutdatedInputError. If we encounter UnverifiableInputError or InvalidInputError, we 306 // have a serious problem, because these results are coming from the node's local HotStuff, which is trusted. 307 if engine.IsOutdatedInputError(err) { 308 c.log.Debug().Err(err).Msgf("dropping outdated incorporated result %v", result.ID()) 309 return nil 310 } 311 312 return err 313 } 314 315 // checkBlockOutdated performs a sanity check if block is outdated 316 // Returns: 317 // * engine.UnverifiableInputError - sentinel error in case we haven't discovered requested blockID 318 // * engine.OutdatedInputError - sentinel error in case block is outdated 319 // * exception in case of unknown internal error 320 // * nil - block isn't sealed 321 func (c *Core) checkBlockOutdated(blockID flow.Identifier) error { 322 block, err := c.headers.ByBlockID(blockID) 323 if err != nil { 324 if !errors.Is(err, storage.ErrNotFound) { 325 return fmt.Errorf("failed to retrieve header for block %x: %w", blockID, err) 326 } 327 return engine.NewUnverifiableInputError("no header for block: %v", blockID) 328 } 329 330 // it's important to use atomic operation to make sure that we have correct ordering 331 lastSealedHeight := c.counterLastSealedHeight.Value() 332 // drop approval, if it is for block whose height is lower or equal to already sealed height 333 if lastSealedHeight >= block.Height { 334 return engine.NewOutdatedInputErrorf("requested processing for already sealed block height") 335 } 336 337 return nil 338 } 339 340 // ProcessApproval processes approval in blocking way. Concurrency safe. 341 // Returns: 342 // * exception in case of unexpected error 343 // * nil - successfully processed result approval 344 func (c *Core) ProcessApproval(approval *flow.ResultApproval) error { 345 c.log.Debug(). 346 Str("result_id", approval.Body.ExecutionResultID.String()). 347 Str("verifier_id", approval.Body.ApproverID.String()). 348 Msg("processing result approval") 349 350 span, _ := c.tracer.StartBlockSpan(context.Background(), approval.Body.BlockID, trace.CONSealingProcessApproval) 351 span.SetAttributes( 352 attribute.String("approverId", approval.Body.ApproverID.String()), 353 attribute.Int64("chunkIndex", int64(approval.Body.ChunkIndex)), 354 ) 355 defer span.End() 356 357 startTime := time.Now() 358 err := c.processApproval(approval) 359 c.metrics.OnApprovalProcessingDuration(time.Since(startTime)) 360 361 if err != nil { 362 if engine.IsOutdatedInputError(err) { 363 return nil // potentially delayed input 364 } 365 366 lg := c.log.With(). 367 Err(err). 368 Str("approver_id", approval.Body.ApproverID.String()). 369 Str("executed_block_id", approval.Body.BlockID.String()). 370 Str("result_id", approval.Body.ExecutionResultID.String()). 371 Str("approval_id", approval.ID().String()). 372 Logger() 373 if engine.IsUnverifiableInputError(err) { 374 lg.Warn().Msg("received approval for unknown block (this node is potentially behind)") 375 return nil 376 } 377 if engine.IsInvalidInputError(err) { 378 lg.Error().Msg("received invalid approval") 379 return nil 380 } 381 lg.Error().Msg("unexpected error processing result approval") 382 383 return fmt.Errorf("internal error processing result approval %x: %w", approval.ID(), err) 384 } 385 386 return nil 387 } 388 389 // processApproval implements business logic for processing single approval 390 // Returns: 391 // * engine.InvalidInputError - result approval is invalid 392 // * engine.UnverifiableInputError - result approval is unverifiable since referenced block cannot be found 393 // * engine.OutdatedInputError - result approval is outdated for instance block was already sealed 394 // * exception in case of any other error, usually this is not expected 395 // * nil - successfully processed result approval 396 func (c *Core) processApproval(approval *flow.ResultApproval) error { 397 err := c.checkBlockOutdated(approval.Body.BlockID) 398 if err != nil { 399 return fmt.Errorf("won't process approval for oudated block (%x): %w", approval.Body.BlockID, err) 400 } 401 402 if collector := c.collectorTree.GetCollector(approval.Body.ExecutionResultID); collector != nil { 403 // if there is a collector it means that we have received execution result and we are ready 404 // to process approvals 405 err = collector.ProcessApproval(approval) 406 if err != nil { 407 return fmt.Errorf("could not process assignment: %w", err) 408 } 409 } else { 410 c.log.Debug(). 411 Str("result_id", approval.Body.ExecutionResultID.String()). 412 Msg("haven't yet received execution result, caching for later") 413 414 // in case we haven't received execution result, cache it and process later. 415 c.approvalsCache.Put(approval) 416 } 417 418 return nil 419 } 420 421 // checkEmergencySealing triggers the AssignmentCollectors to check whether satisfy the conditions to 422 // generate an emergency seal. To limit performance impact of these checks, we limit emergency sealing 423 // to the 100 lowest finalized blocks that are still unsealed. 424 // Inputs: 425 // - `observer` for tracking and reporting the current internal state of the local sealing logic 426 // - `lastFinalizedHeight` is the height of the latest block that is finalized 427 // - `lastHeightWithFinalizedSeal` is the height of the latest block that is finalized and in addition 428 // 429 // No errors are expected during normal operations. 430 func (c *Core) checkEmergencySealing(observer consensus.SealingObservation, lastHeightWithFinalizedSeal, lastFinalizedHeight uint64) error { 431 // if emergency sealing is not activated, then exit 432 if !c.sealingConfigsGetter.EmergencySealingActiveConst() { 433 return nil 434 } 435 436 // calculate total number of finalized blocks that are still unsealed 437 if lastHeightWithFinalizedSeal > lastFinalizedHeight { // sanity check; protects calculation of `unsealedFinalizedCount` from underflow 438 return fmt.Errorf( 439 "latest finalized block must have height (%d) ≥ latest finalized _and_ sealed block (%d)", lastFinalizedHeight, lastHeightWithFinalizedSeal) 440 } 441 unsealedFinalizedCount := lastFinalizedHeight - lastHeightWithFinalizedSeal 442 443 // We are checking emergency sealing only if there are more than approvals.DefaultEmergencySealingThresholdForFinalization 444 // number of unsealed finalized blocks. 445 if unsealedFinalizedCount <= approvals.DefaultEmergencySealingThresholdForFinalization { 446 return nil 447 } 448 449 // we will check all the unsealed finalized height except the last approvals.DefaultEmergencySealingThresholdForFinalization 450 // number of finalized heights 451 heightCountForCheckingEmergencySealing := unsealedFinalizedCount - approvals.DefaultEmergencySealingThresholdForFinalization 452 453 // If there are too many unsealed and finalized blocks, we don't have to check emergency sealing for all of them, 454 // instead, only check for at most 100 blocks. This limits computation cost. 455 // Note: the block builder also limits the max number of seals that can be included in a new block to `maxSealCount`. 456 // While `maxSealCount` doesn't have to be the same value as the limit below, there is little benefit of our limit 457 // exceeding `maxSealCount`. 458 if heightCountForCheckingEmergencySealing > 100 { 459 heightCountForCheckingEmergencySealing = 100 460 } 461 // if block is emergency sealable depends on it's incorporated block height 462 // collectors tree stores collector by executed block height 463 // we need to select multiple levels to find eligible collectors for emergency sealing 464 for _, collector := range c.collectorTree.GetCollectorsByInterval(lastHeightWithFinalizedSeal, lastHeightWithFinalizedSeal+heightCountForCheckingEmergencySealing) { 465 err := collector.CheckEmergencySealing(observer, lastFinalizedHeight) 466 if err != nil { 467 return err 468 } 469 } 470 return nil 471 } 472 473 func (c *Core) processPendingApprovals(collector approvals.AssignmentCollectorState) error { 474 resultID := collector.ResultID() 475 // filter cached approvals for concrete execution result 476 for _, approval := range c.approvalsCache.TakeByResultID(resultID) { 477 err := collector.ProcessApproval(approval) 478 if err != nil { 479 if engine.IsInvalidInputError(err) { 480 c.log.Debug(). 481 Hex("result_id", resultID[:]). 482 Err(err). 483 Msgf("invalid approval with id %s", approval.ID()) 484 } else { 485 return fmt.Errorf("could not process assignment: %w", err) 486 } 487 } 488 } 489 490 return nil 491 } 492 493 // ProcessFinalizedBlock processes finalization events in blocking way. The entire business 494 // logic in this function can be executed completely concurrently. We only waste some work 495 // if multiple goroutines enter the following block. 496 // Returns: 497 // * exception in case of unexpected error 498 // * nil - successfully processed finalized block 499 func (c *Core) ProcessFinalizedBlock(finalizedBlockID flow.Identifier) error { 500 501 processFinalizedBlockSpan, _ := c.tracer.StartBlockSpan(context.Background(), finalizedBlockID, trace.CONSealingProcessFinalizedBlock) 502 defer processFinalizedBlockSpan.End() 503 504 // STEP 0: Collect auxiliary information 505 // ------------------------------------------------------------------------ 506 // retrieve finalized block's header; update last finalized height and bail 507 // if another goroutine is already ahead with a higher finalized block 508 finalized, err := c.headers.ByBlockID(finalizedBlockID) 509 if err != nil { 510 return fmt.Errorf("could not retrieve header for finalized block %s", finalizedBlockID) 511 } 512 if !c.counterLastFinalizedHeight.Set(finalized.Height) { 513 return nil 514 } 515 516 // retrieve latest _finalized_ seal in the fork with head finalizedBlock and update last 517 // sealed height; we do _not_ bail, because we want to re-request approvals 518 // especially, when sealing is stuck, i.e. last sealed height does not increase 519 finalizedSeal, err := c.seals.HighestInFork(finalizedBlockID) 520 if err != nil { 521 return fmt.Errorf("could not retrieve finalizedSeal for finalized block %s", finalizedBlockID) 522 } 523 lastBlockWithFinalizedSeal, err := c.headers.ByBlockID(finalizedSeal.BlockID) 524 if err != nil { 525 return fmt.Errorf("could not retrieve last sealed block %v: %w", finalizedSeal.BlockID, err) 526 } 527 c.counterLastSealedHeight.Set(lastBlockWithFinalizedSeal.Height) 528 529 // STEP 1: Pruning 530 // ------------------------------------------------------------------------ 531 c.log.Info().Msgf("processing finalized block %v at height %d, lastSealedHeight %d", finalizedBlockID, finalized.Height, lastBlockWithFinalizedSeal.Height) 532 err = c.prune(processFinalizedBlockSpan, finalized, lastBlockWithFinalizedSeal) 533 if err != nil { 534 return fmt.Errorf("updating to finalized block %v and sealed block %v failed: %w", finalizedBlockID, lastBlockWithFinalizedSeal.ID(), err) 535 } 536 537 // STEP 2: Check emergency sealing and re-request missing approvals 538 // ------------------------------------------------------------------------ 539 sealingObservation := c.sealingTracker.NewSealingObservation(finalized, finalizedSeal, lastBlockWithFinalizedSeal) 540 541 checkEmergencySealingSpan := c.tracer.StartSpanFromParent(processFinalizedBlockSpan, trace.CONSealingCheckForEmergencySealableBlocks) 542 // check if there are stale results qualified for emergency sealing 543 err = c.checkEmergencySealing(sealingObservation, lastBlockWithFinalizedSeal.Height, finalized.Height) 544 checkEmergencySealingSpan.End() 545 if err != nil { 546 return fmt.Errorf("could not check emergency sealing at block %v", finalizedBlockID) 547 } 548 549 requestPendingApprovalsSpan := c.tracer.StartSpanFromParent(processFinalizedBlockSpan, trace.CONSealingRequestingPendingApproval) 550 err = c.requestPendingApprovals(sealingObservation, lastBlockWithFinalizedSeal.Height, finalized.Height) 551 requestPendingApprovalsSpan.End() 552 if err != nil { 553 return fmt.Errorf("internal error while requesting pending approvals: %w", err) 554 } 555 556 // While SealingObservation is not intrinsically concurrency safe, running the following operation 557 // asynchronously is still safe for the following reason: 558 // * The `sealingObservation` is thread-local: created and mutated only by this goroutine. 559 // * According to the go spec: the statement that starts a new goroutine happens before the 560 // goroutine's execution begins. Hence, the goroutine executing the Complete() call 561 // observes the latest state of `sealingObservation`. 562 // * The `sealingObservation` lives in the scope of this function. Hence, when this goroutine exits 563 // this function, `sealingObservation` lives solely in the scope of the newly-created goroutine. 564 c.unit.Launch(sealingObservation.Complete) 565 566 return nil 567 } 568 569 // prune updates the AssignmentCollectorTree's knowledge about sealed and finalized blocks. 570 // Furthermore, it removes obsolete entries from AssignmentCollectorTree, RequestTracker 571 // and IncorporatedResultSeals mempool. 572 // We do _not_ expect any errors during normal operations. 573 func (c *Core) prune(parentSpan otelTrace.Span, finalized, lastSealed *flow.Header) error { 574 pruningSpan := c.tracer.StartSpanFromParent(parentSpan, trace.CONSealingPruning) 575 defer pruningSpan.End() 576 577 err := c.collectorTree.FinalizeForkAtLevel(finalized, lastSealed) // stop collecting approvals for orphan collectors 578 if err != nil { 579 return fmt.Errorf("AssignmentCollectorTree failed to update its finalization state: %w", err) 580 } 581 582 err = c.requestTracker.PruneUpToHeight(lastSealed.Height) 583 if err != nil && !mempool.IsBelowPrunedThresholdError(err) { 584 return fmt.Errorf("could not request tracker at block up to height %d: %w", lastSealed.Height, err) 585 } 586 587 err = c.sealsMempool.PruneUpToHeight(lastSealed.Height) // prune candidate seals mempool 588 if err != nil && !mempool.IsBelowPrunedThresholdError(err) { 589 return fmt.Errorf("could not prune seals mempool at block up to height %d: %w", lastSealed.Height, err) 590 } 591 592 return nil 593 } 594 595 // requestPendingApprovals requests approvals for chunks that haven't collected 596 // enough approvals. When the number of unsealed finalized blocks exceeds the 597 // threshold, we go through the entire mempool of incorporated-results, which 598 // haven't yet been sealed, and check which chunks need more approvals. We only 599 // request approvals if the block incorporating the result is below the 600 // threshold. 601 // 602 // threshold 603 // | | 604 // ... <-- A <-- A+1 <- ... <-- D <-- D+1 <- ... -- F 605 // sealed maxHeightForRequesting final 606 func (c *Core) requestPendingApprovals(observation consensus.SealingObservation, lastSealedHeight, lastFinalizedHeight uint64) error { 607 if lastSealedHeight+c.sealingConfigsGetter.ApprovalRequestsThresholdConst() >= lastFinalizedHeight { 608 return nil 609 } 610 611 // Reaching the following code implies: 612 // 0 <= sealed.Height < final.Height - ApprovalRequestsThreshold 613 // Hence, the following operation cannot underflow 614 maxHeightForRequesting := lastFinalizedHeight - c.sealingConfigsGetter.ApprovalRequestsThresholdConst() 615 616 pendingApprovalRequests := uint(0) 617 collectors := c.collectorTree.GetCollectorsByInterval(lastSealedHeight, maxHeightForRequesting) 618 for _, collector := range collectors { 619 // Note: 620 // * The `AssignmentCollectorTree` works with the height of the _executed_ block. However, 621 // the `maxHeightForRequesting` should use the height of the block _incorporating the result_ 622 // as reference. 623 // * There might be blocks whose height is below `maxHeightForRequesting`, while their result 624 // is incorporated into blocks with _larger_ height than `maxHeightForRequesting`. Therefore, 625 // filtering based on the executed block height is a useful pre-filter, but not quite 626 // precise enough. 627 // * The `AssignmentCollector` will apply the precise filter to avoid unnecessary overhead. 628 requestCount, err := collector.RequestMissingApprovals(observation, maxHeightForRequesting) 629 if err != nil { 630 return err 631 } 632 pendingApprovalRequests += requestCount 633 } 634 635 return nil 636 } 637 638 // getOutdatedBlockIDsFromRootSealingSegment finds all references to unknown blocks 639 // by execution results within the sealing segment. In general we disallow references 640 // to unknown blocks, but execution results incorporated within the sealing segment 641 // are an exception to this rule. 642 // 643 // For example, given the sealing segment A...E, B contains an ER referencing Z, but 644 // since Z is prior to sealing segment, the node cannot valid the ER. Therefore, we 645 // ignore these block references. 646 // 647 // [ sealing segment ] 648 // Z <- A <- B(RZ) <- C <- D <- E 649 func (c *Core) getOutdatedBlockIDsFromRootSealingSegment(rootHeader *flow.Header) (map[flow.Identifier]struct{}, error) { 650 651 rootSealingSegment, err := c.state.AtBlockID(rootHeader.ID()).SealingSegment() 652 if err != nil { 653 return nil, fmt.Errorf("could not get root sealing segment: %w", err) 654 } 655 656 knownBlockIDs := make(map[flow.Identifier]struct{}) // track block IDs in the sealing segment 657 var outdatedBlockIDs flow.IdentifierList 658 for _, block := range rootSealingSegment.Blocks { 659 knownBlockIDs[block.ID()] = struct{}{} 660 for _, result := range block.Payload.Results { 661 _, known := knownBlockIDs[result.BlockID] 662 if !known { 663 outdatedBlockIDs = append(outdatedBlockIDs, result.BlockID) 664 } 665 } 666 } 667 return outdatedBlockIDs.Lookup(), nil 668 }