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  }