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  }