github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/engine/consensus/matching/core.go (about)

     1  package matching
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"errors"
     7  	"fmt"
     8  	"math"
     9  	"time"
    10  
    11  	"github.com/rs/zerolog"
    12  	"go.opentelemetry.io/otel/attribute"
    13  
    14  	"github.com/onflow/flow-go/engine"
    15  	"github.com/onflow/flow-go/model/flow"
    16  	"github.com/onflow/flow-go/model/flow/filter"
    17  	"github.com/onflow/flow-go/module"
    18  	"github.com/onflow/flow-go/module/mempool"
    19  	"github.com/onflow/flow-go/module/metrics"
    20  	"github.com/onflow/flow-go/module/trace"
    21  	"github.com/onflow/flow-go/state/protocol"
    22  	"github.com/onflow/flow-go/storage"
    23  	"github.com/onflow/flow-go/utils/logging"
    24  )
    25  
    26  // Config is a structure of values that configure behavior of matching engine
    27  type Config struct {
    28  	SealingThreshold    uint // threshold between sealed and finalized blocks
    29  	MaxResultsToRequest uint // maximum number of receipts to request
    30  }
    31  
    32  func DefaultConfig() Config {
    33  	return Config{
    34  		SealingThreshold:    10,
    35  		MaxResultsToRequest: 20,
    36  	}
    37  }
    38  
    39  // Core represents the matching business logic, used to process receipts received from
    40  // p2p network. Performs processing of pending receipts, storing of receipts and re-requesting
    41  // missing execution receipts.
    42  type Core struct {
    43  	log              zerolog.Logger                  // used to log relevant actions with context
    44  	tracer           module.Tracer                   // used to trace execution
    45  	metrics          module.ConsensusMetrics         // used to track consensus metrics
    46  	mempool          module.MempoolMetrics           // used to track mempool size
    47  	state            protocol.State                  // used to access the  protocol state
    48  	headersDB        storage.Headers                 // used to check sealed headers
    49  	receiptsDB       storage.ExecutionReceipts       // to persist received execution receipts
    50  	receipts         mempool.ExecutionTree           // holds execution receipts; indexes them by height; can search all receipts derived from a given parent result
    51  	pendingReceipts  mempool.PendingReceipts         // buffer for receipts where an ancestor result is missing, so they can't be connected to the sealed results
    52  	seals            mempool.IncorporatedResultSeals // holds candidate seals for incorporated results that have acquired sufficient approvals; candidate seals are constructed  without consideration of the sealability of parent results
    53  	receiptValidator module.ReceiptValidator         // used to validate receipts
    54  	receiptRequester module.Requester                // used to request missing execution receipts by block ID
    55  	config           Config                          // config for matching core
    56  }
    57  
    58  func NewCore(
    59  	log zerolog.Logger,
    60  	tracer module.Tracer,
    61  	metrics module.ConsensusMetrics,
    62  	mempool module.MempoolMetrics,
    63  	state protocol.State,
    64  	headersDB storage.Headers,
    65  	receiptsDB storage.ExecutionReceipts,
    66  	receipts mempool.ExecutionTree,
    67  	pendingReceipts mempool.PendingReceipts,
    68  	seals mempool.IncorporatedResultSeals,
    69  	receiptValidator module.ReceiptValidator,
    70  	receiptRequester module.Requester,
    71  	config Config,
    72  ) *Core {
    73  	return &Core{
    74  		log:              log.With().Str("engine", "matching.Core").Logger(),
    75  		tracer:           tracer,
    76  		metrics:          metrics,
    77  		mempool:          mempool,
    78  		state:            state,
    79  		headersDB:        headersDB,
    80  		receiptsDB:       receiptsDB,
    81  		receipts:         receipts,
    82  		pendingReceipts:  pendingReceipts,
    83  		seals:            seals,
    84  		receiptValidator: receiptValidator,
    85  		receiptRequester: receiptRequester,
    86  		config:           config,
    87  	}
    88  }
    89  
    90  // ProcessReceipt processes a new execution receipt.
    91  // Any error indicates an unexpected problem in the protocol logic. The node's
    92  // internal state might be corrupted. Hence, returned errors should be treated as fatal.
    93  func (c *Core) ProcessReceipt(receipt *flow.ExecutionReceipt) error {
    94  	// When receiving a receipt, we might not be able to verify it if its previous result
    95  	// is unknown.  In this case, instead of dropping it, we store it in the pending receipts
    96  	// mempool, and process it later when its parent result has been received and processed.
    97  	// Therefore, if a receipt is processed, we will check if it is the previous results of
    98  	// some pending receipts and process them one after another.
    99  	receiptID := receipt.ID()
   100  	resultID := receipt.ExecutionResult.ID()
   101  
   102  	processed, err := c.processReceipt(receipt)
   103  	if err != nil {
   104  		marshalled, encErr := json.Marshal(receipt)
   105  		if encErr != nil {
   106  			marshalled = []byte("json_marshalling_failed")
   107  		}
   108  		c.log.Error().Err(err).
   109  			Hex("origin", logging.ID(receipt.ExecutorID)).
   110  			Hex("receipt_id", receiptID[:]).
   111  			Hex("result_id", resultID[:]).
   112  			Str("receipt", string(marshalled)).
   113  			Msg("internal error processing execution receipt")
   114  
   115  		return fmt.Errorf("internal error processing execution receipt %x: %w", receipt.ID(), err)
   116  	}
   117  
   118  	if !processed {
   119  		return nil
   120  	}
   121  
   122  	childReceipts := c.pendingReceipts.ByPreviousResultID(resultID)
   123  	c.pendingReceipts.Remove(receipt.ID())
   124  
   125  	for _, childReceipt := range childReceipts {
   126  		// recursively processing the child receipts
   127  		err := c.ProcessReceipt(childReceipt)
   128  		if err != nil {
   129  			// we don't want to wrap the error with any info from its parent receipt,
   130  			// because the error has nothing to do with its parent receipt.
   131  			return err
   132  		}
   133  	}
   134  
   135  	return nil
   136  }
   137  
   138  // processReceipt checks validity of the given receipt and adds it to the node's validated information.
   139  // Returns:
   140  //   - bool: true iff receipt is new (previously unknown), and its validity can be confirmed
   141  //   - error: any error indicates an unexpected problem in the protocol logic. The node's
   142  //     internal state might be corrupted. Hence, returned errors should be treated as fatal.
   143  func (c *Core) processReceipt(receipt *flow.ExecutionReceipt) (bool, error) {
   144  	// setup logger to capture basic information about the receipt
   145  	log := c.log.With().
   146  		Hex("receipt_id", logging.Entity(receipt)).
   147  		Hex("result_id", logging.Entity(receipt.ExecutionResult)).
   148  		Hex("execution_data_id", receipt.ExecutionResult.ExecutionDataID[:]).
   149  		Hex("previous_result", receipt.ExecutionResult.PreviousResultID[:]).
   150  		Hex("block_id", receipt.ExecutionResult.BlockID[:]).
   151  		Hex("executor_id", receipt.ExecutorID[:]).
   152  		Logger()
   153  
   154  	if c.receipts.HasReceipt(receipt) {
   155  		log.Debug().Msg("skipping processing of already known receipt")
   156  		return false, nil
   157  	}
   158  
   159  	startTime := time.Now()
   160  	defer func() {
   161  		c.metrics.OnReceiptProcessingDuration(time.Since(startTime))
   162  	}()
   163  
   164  	receiptSpan, _ := c.tracer.StartBlockSpan(context.Background(), receipt.ExecutionResult.BlockID, trace.CONMatchProcessReceipt)
   165  	receiptSpan.SetAttributes(
   166  		attribute.String("result_id", receipt.ExecutionResult.ID().String()),
   167  		attribute.String("executor", receipt.ExecutorID.String()),
   168  	)
   169  	defer receiptSpan.End()
   170  
   171  	initialState, finalState, err := getStartAndEndStates(receipt)
   172  	if err != nil {
   173  		if errors.Is(err, flow.ErrNoChunks) {
   174  			log.Error().Err(err).Msg("discarding malformed receipt")
   175  			return false, nil
   176  		}
   177  		return false, fmt.Errorf("internal problem retrieving start- and end-state commitment from receipt: %w", err)
   178  	}
   179  	log = log.With().
   180  		Hex("initial_state", initialState[:]).
   181  		Hex("final_state", finalState[:]).Logger()
   182  
   183  	// if the receipt is for an unknown block, skip it. It will be re-requested
   184  	// later by `requestPending` function.
   185  	executedBlock, err := c.headersDB.ByBlockID(receipt.ExecutionResult.BlockID)
   186  	if err != nil {
   187  		log.Debug().Msg("discarding receipt for unknown block")
   188  		return false, nil
   189  	}
   190  
   191  	log = log.With().
   192  		Uint64("block_view", executedBlock.View).
   193  		Uint64("block_height", executedBlock.Height).
   194  		Logger()
   195  	log.Info().Msg("execution receipt received")
   196  
   197  	// if Execution Receipt is for block whose height is lower or equal to already sealed height
   198  	//  => drop Receipt
   199  	sealed, err := c.state.Sealed().Head()
   200  	if err != nil {
   201  		return false, fmt.Errorf("could not find sealed block: %w", err)
   202  	}
   203  	if executedBlock.Height <= sealed.Height {
   204  		log.Debug().Msg("discarding receipt for already sealed and finalized block height")
   205  		return false, nil
   206  	}
   207  
   208  	childSpan := c.tracer.StartSpanFromParent(receiptSpan, trace.CONMatchProcessReceiptVal)
   209  	err = c.receiptValidator.Validate(receipt)
   210  	childSpan.End()
   211  
   212  	if engine.IsUnverifiableInputError(err) {
   213  		// If previous result is missing, we can't validate this receipt.
   214  		// Although we will request its previous receipt(s),
   215  		// we don't want to drop it now, because when the missing previous arrive
   216  		// in a wrong order, they will still be dropped, and causing the catch up
   217  		// to be inefficient.
   218  		// Instead, we cache the receipt in case it arrives earlier than its
   219  		// previous receipt.
   220  		// For instance, given blocks A <- B <- C <- D <- E, if we receive their receipts
   221  		// in the order of [E,C,D,B,A], then:
   222  		// if we drop the missing previous receipts, then only A will be processed;
   223  		// if we cache the missing previous receipts, then all of them will be processed, because
   224  		// once A is processed, we will check if there is a child receipt pending,
   225  		// if yes, then process it.
   226  		c.pendingReceipts.Add(receipt)
   227  		log.Info().Msg("receipt is cached because its previous result is missing")
   228  		return false, nil
   229  	}
   230  
   231  	if err != nil {
   232  		if engine.IsInvalidInputError(err) {
   233  			log.Err(err).Msg("invalid execution receipt")
   234  			return false, nil
   235  		}
   236  		return false, fmt.Errorf("failed to validate execution receipt: %w", err)
   237  	}
   238  
   239  	added, err := c.storeReceipt(receipt, executedBlock)
   240  	if err != nil {
   241  		return false, fmt.Errorf("failed to store receipt: %w", err)
   242  	}
   243  	if added {
   244  		log.Info().Msg("execution result processed and stored")
   245  	}
   246  
   247  	return added, nil
   248  }
   249  
   250  // storeReceipt adds the receipt to the receipts mempool as well as to the persistent storage layer.
   251  // Return values:
   252  //   - bool to indicate whether the receipt is stored.
   253  //   - exception in case something (unexpected) went wrong
   254  func (c *Core) storeReceipt(receipt *flow.ExecutionReceipt, head *flow.Header) (bool, error) {
   255  	added, err := c.receipts.AddReceipt(receipt, head)
   256  	if err != nil {
   257  		return false, fmt.Errorf("adding receipt (%x) to mempool failed: %w", receipt.ID(), err)
   258  	}
   259  	if !added {
   260  		return false, nil
   261  	}
   262  	// TODO: we'd better wrap the `receipts` with the metrics method to avoid the metrics
   263  	// getting out of sync
   264  	c.mempool.MempoolEntries(metrics.ResourceReceipt, c.receipts.Size())
   265  
   266  	// persist receipt in database. Even if the receipt is already in persistent storage,
   267  	// we still need to process it, as it is not in the mempool. This can happen if the
   268  	// mempool was wiped during a node crash.
   269  	err = c.receiptsDB.Store(receipt) // internally de-duplicates
   270  	if err != nil && !errors.Is(err, storage.ErrAlreadyExists) {
   271  		return false, fmt.Errorf("could not persist receipt: %w", err)
   272  	}
   273  	return true, nil
   274  }
   275  
   276  // requestPendingReceipts requests the execution receipts of unsealed finalized
   277  // blocks.
   278  // it returns the number of pending receipts requests being created, and
   279  // the first finalized height at which there is no receipt for the block
   280  func (c *Core) requestPendingReceipts() (int, uint64, error) {
   281  	finalSnapshot := c.state.Final()
   282  	final, err := finalSnapshot.Head() // last finalized block
   283  	if err != nil {
   284  		return 0, 0, fmt.Errorf("could not get finalized height: %w", err)
   285  	}
   286  	_, seal, err := finalSnapshot.SealedResult() // last finalized seal
   287  	if err != nil {
   288  		return 0, 0, fmt.Errorf("could not retrieve latest finalized seal: %w", err)
   289  	}
   290  	sealed, err := c.headersDB.ByBlockID(seal.BlockID) // last sealed block
   291  	if err != nil {
   292  		return 0, 0, fmt.Errorf("could not get sealed height: %w", err)
   293  	}
   294  
   295  	// only request if number of unsealed finalized blocks exceeds the threshold
   296  	if uint(final.Height-sealed.Height) < c.config.SealingThreshold {
   297  		return 0, 0, nil
   298  	}
   299  
   300  	// order the missing blocks by height from low to high such that when
   301  	// passing them to the missing block requester, they can be requested in the
   302  	// right order. The right order gives the priority to the execution result
   303  	// of lower height blocks to be requested first, since a gap in the sealing
   304  	// heights would stop the sealing.
   305  	missingBlocksOrderedByHeight := make([]flow.Identifier, 0, c.config.MaxResultsToRequest)
   306  
   307  	var firstMissingHeight uint64 = math.MaxUint64
   308  	// traverse each unsealed and finalized block with height from low to high,
   309  	// if the result is missing, then add the blockID to a missing block list in
   310  	// order to request them.
   311  HEIGHT_LOOP:
   312  	for height := sealed.Height + 1; height <= final.Height; height++ {
   313  		// add at most <maxUnsealedResults> number of results
   314  		if len(missingBlocksOrderedByHeight) >= int(c.config.MaxResultsToRequest) {
   315  			break
   316  		}
   317  
   318  		// get the block header at this height (should not error as heights are finalized)
   319  		header, err := c.headersDB.ByHeight(height)
   320  		if err != nil {
   321  			return 0, 0, fmt.Errorf("could not get header (height=%d): %w", height, err)
   322  		}
   323  		blockID := header.ID()
   324  
   325  		receipts, err := c.receiptsDB.ByBlockID(blockID)
   326  		if err != nil && !errors.Is(err, storage.ErrNotFound) {
   327  			return 0, 0, fmt.Errorf("could not get receipts by block ID: %v, %w", blockID, err)
   328  		}
   329  
   330  		// We require at least 2 consistent receipts from different ENs to seal a block. If don't need to fetching receipts.
   331  		// CAUTION: This is a temporary shortcut incompatible with the mature BFT protocol!
   332  		// There might be multiple consistent receipts that commit to a wrong result. To guarantee
   333  		// sealing liveness, we need to fetch receipts from those ENs, whose receipts we don't have yet.
   334  		// TODO: update for full BFT
   335  		for _, receiptsForResult := range receipts.GroupByResultID() {
   336  			if receiptsForResult.GroupByExecutorID().NumberGroups() >= 2 {
   337  				continue HEIGHT_LOOP
   338  			}
   339  		}
   340  
   341  		missingBlocksOrderedByHeight = append(missingBlocksOrderedByHeight, blockID)
   342  		if height < firstMissingHeight {
   343  			firstMissingHeight = height
   344  		}
   345  	}
   346  
   347  	// request missing execution results, if sealed height is low enough
   348  	for _, blockID := range missingBlocksOrderedByHeight {
   349  		c.receiptRequester.Query(blockID, filter.Any)
   350  	}
   351  
   352  	return len(missingBlocksOrderedByHeight), firstMissingHeight, nil
   353  }
   354  
   355  func (c *Core) OnBlockFinalization() error {
   356  	startTime := time.Now()
   357  
   358  	// request execution receipts for unsealed finalized blocks
   359  	pendingReceiptRequests, firstMissingHeight, err := c.requestPendingReceipts()
   360  	if err != nil {
   361  		return fmt.Errorf("could not request pending block results: %w", err)
   362  	}
   363  
   364  	// Prune Execution Tree
   365  	lastSealed, err := c.state.Sealed().Head()
   366  	if err != nil {
   367  		return fmt.Errorf("could not retrieve last sealed block : %w", err)
   368  	}
   369  	err = c.receipts.PruneUpToHeight(lastSealed.Height)
   370  	if err != nil {
   371  		return fmt.Errorf("failed to prune execution tree up to latest sealed and finalized block %v, height: %v: %w",
   372  			lastSealed.ID(), lastSealed.Height, err)
   373  	}
   374  
   375  	err = c.pendingReceipts.PruneUpToHeight(lastSealed.Height)
   376  	if err != nil {
   377  		return fmt.Errorf("failed to prune pending receipts mempool up to latest sealed and finalized block %v, height: %v: %w",
   378  			lastSealed.ID(), lastSealed.Height, err)
   379  	}
   380  
   381  	c.log.Info().
   382  		Uint64("first_height_missing_result", firstMissingHeight).
   383  		Uint("seals_size", c.seals.Size()).
   384  		Uint("receipts_size", c.receipts.Size()).
   385  		Int("pending_receipt_requests", pendingReceiptRequests).
   386  		Int64("duration_ms", time.Since(startTime).Milliseconds()).
   387  		Msg("finalized block processed successfully")
   388  
   389  	return nil
   390  }
   391  
   392  // getStartAndEndStates returns the pair: (start state commitment; final state commitment)
   393  // Error returns:
   394  //   - ErrNoChunks: if there are no chunks, i.e. the ExecutionResult is malformed
   395  //   - all other errors are unexpected and symptoms of node-internal problems
   396  func getStartAndEndStates(receipt *flow.ExecutionReceipt) (initialState flow.StateCommitment, finalState flow.StateCommitment, err error) {
   397  	initialState, err = receipt.ExecutionResult.InitialStateCommit()
   398  	if err != nil {
   399  		return initialState, finalState, fmt.Errorf("could not get commitment for initial state from receipt: %w", err)
   400  	}
   401  	finalState, err = receipt.ExecutionResult.FinalStateCommitment()
   402  	if err != nil {
   403  		return initialState, finalState, fmt.Errorf("could not get commitment for final state from receipt: %w", err)
   404  	}
   405  	return initialState, finalState, nil
   406  }