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