github.com/onflow/flow-go@v0.33.17/engine/execution/computation/computer/computer.go (about)

     1  package computer
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"sync"
     7  
     8  	"github.com/rs/zerolog"
     9  	"go.opentelemetry.io/otel/attribute"
    10  	otelTrace "go.opentelemetry.io/otel/trace"
    11  
    12  	"github.com/onflow/flow-go/crypto/hash"
    13  	"github.com/onflow/flow-go/engine/execution"
    14  	"github.com/onflow/flow-go/engine/execution/computation/result"
    15  	"github.com/onflow/flow-go/engine/execution/utils"
    16  	"github.com/onflow/flow-go/fvm"
    17  	"github.com/onflow/flow-go/fvm/blueprints"
    18  	"github.com/onflow/flow-go/fvm/storage/derived"
    19  	"github.com/onflow/flow-go/fvm/storage/errors"
    20  	"github.com/onflow/flow-go/fvm/storage/logical"
    21  	"github.com/onflow/flow-go/fvm/storage/snapshot"
    22  	"github.com/onflow/flow-go/model/flow"
    23  	"github.com/onflow/flow-go/module"
    24  	"github.com/onflow/flow-go/module/executiondatasync/provider"
    25  	"github.com/onflow/flow-go/module/mempool/entity"
    26  	"github.com/onflow/flow-go/module/trace"
    27  	"github.com/onflow/flow-go/state/protocol"
    28  	"github.com/onflow/flow-go/utils/logging"
    29  )
    30  
    31  const (
    32  	SystemChunkEventCollectionMaxSize = 256_000_000 // ~256MB
    33  )
    34  
    35  type collectionInfo struct {
    36  	blockId    flow.Identifier
    37  	blockIdStr string
    38  
    39  	collectionIndex int
    40  	*entity.CompleteCollection
    41  
    42  	isSystemTransaction bool
    43  }
    44  
    45  type TransactionRequest struct {
    46  	collectionInfo
    47  
    48  	txnId    flow.Identifier
    49  	txnIdStr string
    50  
    51  	txnIndex uint32
    52  
    53  	lastTransactionInCollection bool
    54  
    55  	ctx fvm.Context
    56  	*fvm.TransactionProcedure
    57  }
    58  
    59  func newTransactionRequest(
    60  	collection collectionInfo,
    61  	collectionCtx fvm.Context,
    62  	collectionLogger zerolog.Logger,
    63  	txnIndex uint32,
    64  	txnBody *flow.TransactionBody,
    65  	lastTransactionInCollection bool,
    66  ) TransactionRequest {
    67  	txnId := txnBody.ID()
    68  	txnIdStr := txnId.String()
    69  
    70  	return TransactionRequest{
    71  		collectionInfo: collection,
    72  		txnId:          txnId,
    73  		txnIdStr:       txnIdStr,
    74  		txnIndex:       txnIndex,
    75  		ctx: fvm.NewContextFromParent(
    76  			collectionCtx,
    77  			fvm.WithLogger(
    78  				collectionLogger.With().
    79  					Str("tx_id", txnIdStr).
    80  					Uint32("tx_index", txnIndex).
    81  					Logger())),
    82  		TransactionProcedure: fvm.NewTransaction(
    83  			txnId,
    84  			txnIndex,
    85  			txnBody),
    86  		lastTransactionInCollection: lastTransactionInCollection,
    87  	}
    88  }
    89  
    90  // A BlockComputer executes the transactions in a block.
    91  type BlockComputer interface {
    92  	ExecuteBlock(
    93  		ctx context.Context,
    94  		parentBlockExecutionResultID flow.Identifier,
    95  		block *entity.ExecutableBlock,
    96  		snapshot snapshot.StorageSnapshot,
    97  		derivedBlockData *derived.DerivedBlockData,
    98  	) (
    99  		*execution.ComputationResult,
   100  		error,
   101  	)
   102  }
   103  
   104  type blockComputer struct {
   105  	vm                    fvm.VM
   106  	vmCtx                 fvm.Context
   107  	metrics               module.ExecutionMetrics
   108  	tracer                module.Tracer
   109  	log                   zerolog.Logger
   110  	systemChunkCtx        fvm.Context
   111  	committer             ViewCommitter
   112  	executionDataProvider provider.Provider
   113  	signer                module.Local
   114  	spockHasher           hash.Hasher
   115  	receiptHasher         hash.Hasher
   116  	colResCons            []result.ExecutedCollectionConsumer
   117  	protocolState         protocol.State
   118  	maxConcurrency        int
   119  }
   120  
   121  func SystemChunkContext(vmCtx fvm.Context) fvm.Context {
   122  	return fvm.NewContextFromParent(
   123  		vmCtx,
   124  		fvm.WithContractDeploymentRestricted(false),
   125  		fvm.WithContractRemovalRestricted(false),
   126  		fvm.WithAuthorizationChecksEnabled(false),
   127  		fvm.WithSequenceNumberCheckAndIncrementEnabled(false),
   128  		fvm.WithTransactionFeesEnabled(false),
   129  		fvm.WithServiceEventCollectionEnabled(),
   130  		fvm.WithEventCollectionSizeLimit(SystemChunkEventCollectionMaxSize),
   131  		fvm.WithMemoryAndInteractionLimitsDisabled(),
   132  		// only the system transaction is allowed to call the block entropy provider
   133  		fvm.WithRandomSourceHistoryCallAllowed(true),
   134  	)
   135  }
   136  
   137  // NewBlockComputer creates a new block executor.
   138  func NewBlockComputer(
   139  	vm fvm.VM,
   140  	vmCtx fvm.Context,
   141  	metrics module.ExecutionMetrics,
   142  	tracer module.Tracer,
   143  	logger zerolog.Logger,
   144  	committer ViewCommitter,
   145  	signer module.Local,
   146  	executionDataProvider provider.Provider,
   147  	colResCons []result.ExecutedCollectionConsumer,
   148  	state protocol.State,
   149  	maxConcurrency int,
   150  ) (BlockComputer, error) {
   151  	if maxConcurrency < 1 {
   152  		return nil, fmt.Errorf("invalid maxConcurrency: %d", maxConcurrency)
   153  	}
   154  	systemChunkCtx := SystemChunkContext(vmCtx)
   155  	vmCtx = fvm.NewContextFromParent(
   156  		vmCtx,
   157  		fvm.WithMetricsReporter(metrics),
   158  		fvm.WithTracer(tracer))
   159  	return &blockComputer{
   160  		vm:                    vm,
   161  		vmCtx:                 vmCtx,
   162  		metrics:               metrics,
   163  		tracer:                tracer,
   164  		log:                   logger,
   165  		systemChunkCtx:        systemChunkCtx,
   166  		committer:             committer,
   167  		executionDataProvider: executionDataProvider,
   168  		signer:                signer,
   169  		spockHasher:           utils.NewSPOCKHasher(),
   170  		receiptHasher:         utils.NewExecutionReceiptHasher(),
   171  		colResCons:            colResCons,
   172  		protocolState:         state,
   173  		maxConcurrency:        maxConcurrency,
   174  	}, nil
   175  }
   176  
   177  // ExecuteBlock executes a block and returns the resulting chunks.
   178  func (e *blockComputer) ExecuteBlock(
   179  	ctx context.Context,
   180  	parentBlockExecutionResultID flow.Identifier,
   181  	block *entity.ExecutableBlock,
   182  	snapshot snapshot.StorageSnapshot,
   183  	derivedBlockData *derived.DerivedBlockData,
   184  ) (
   185  	*execution.ComputationResult,
   186  	error,
   187  ) {
   188  	results, err := e.executeBlock(
   189  		ctx,
   190  		parentBlockExecutionResultID,
   191  		block,
   192  		snapshot,
   193  		derivedBlockData)
   194  	if err != nil {
   195  		return nil, fmt.Errorf("failed to execute transactions: %w", err)
   196  	}
   197  
   198  	return results, nil
   199  }
   200  
   201  func (e *blockComputer) queueTransactionRequests(
   202  	blockId flow.Identifier,
   203  	blockIdStr string,
   204  	blockHeader *flow.Header,
   205  	rawCollections []*entity.CompleteCollection,
   206  	systemTxnBody *flow.TransactionBody,
   207  	requestQueue chan TransactionRequest,
   208  	numTxns int,
   209  ) {
   210  	txnIndex := uint32(0)
   211  
   212  	collectionCtx := fvm.NewContextFromParent(
   213  		e.vmCtx,
   214  		fvm.WithBlockHeader(blockHeader),
   215  		// `protocol.Snapshot` implements `EntropyProvider` interface
   216  		// Note that `Snapshot` possible errors for RandomSource() are:
   217  		// - storage.ErrNotFound if the QC is unknown.
   218  		// - state.ErrUnknownSnapshotReference if the snapshot reference block is unknown
   219  		// However, at this stage, snapshot reference block should be known and the QC should also be known,
   220  		// so no error is expected in normal operations, as required by `EntropyProvider`.
   221  		fvm.WithEntropyProvider(e.protocolState.AtBlockID(blockId)),
   222  	)
   223  
   224  	for idx, collection := range rawCollections {
   225  		collectionLogger := collectionCtx.Logger.With().
   226  			Str("block_id", blockIdStr).
   227  			Uint64("height", blockHeader.Height).
   228  			Bool("system_chunk", false).
   229  			Bool("system_transaction", false).
   230  			Logger()
   231  
   232  		collectionInfo := collectionInfo{
   233  			blockId:             blockId,
   234  			blockIdStr:          blockIdStr,
   235  			collectionIndex:     idx,
   236  			CompleteCollection:  collection,
   237  			isSystemTransaction: false,
   238  		}
   239  
   240  		for i, txnBody := range collection.Transactions {
   241  			requestQueue <- newTransactionRequest(
   242  				collectionInfo,
   243  				collectionCtx,
   244  				collectionLogger,
   245  				txnIndex,
   246  				txnBody,
   247  				i == len(collection.Transactions)-1)
   248  			txnIndex += 1
   249  		}
   250  
   251  	}
   252  
   253  	systemCtx := fvm.NewContextFromParent(
   254  		e.systemChunkCtx,
   255  		fvm.WithBlockHeader(blockHeader),
   256  		// `protocol.Snapshot` implements `EntropyProvider` interface
   257  		// Note that `Snapshot` possible errors for RandomSource() are:
   258  		// - storage.ErrNotFound if the QC is unknown.
   259  		// - state.ErrUnknownSnapshotReference if the snapshot reference block is unknown
   260  		// However, at this stage, snapshot reference block should be known and the QC should also be known,
   261  		// so no error is expected in normal operations, as required by `EntropyProvider`.
   262  		fvm.WithEntropyProvider(e.protocolState.AtBlockID(blockId)),
   263  	)
   264  	systemCollectionLogger := systemCtx.Logger.With().
   265  		Str("block_id", blockIdStr).
   266  		Uint64("height", blockHeader.Height).
   267  		Bool("system_chunk", true).
   268  		Bool("system_transaction", true).
   269  		Int("num_collections", len(rawCollections)).
   270  		Int("num_txs", numTxns).
   271  		Logger()
   272  	systemCollectionInfo := collectionInfo{
   273  		blockId:         blockId,
   274  		blockIdStr:      blockIdStr,
   275  		collectionIndex: len(rawCollections),
   276  		CompleteCollection: &entity.CompleteCollection{
   277  			Transactions: []*flow.TransactionBody{systemTxnBody},
   278  		},
   279  		isSystemTransaction: true,
   280  	}
   281  
   282  	requestQueue <- newTransactionRequest(
   283  		systemCollectionInfo,
   284  		systemCtx,
   285  		systemCollectionLogger,
   286  		txnIndex,
   287  		systemTxnBody,
   288  		true)
   289  }
   290  
   291  func numberOfTransactionsInBlock(collections []*entity.CompleteCollection) int {
   292  	numTxns := 1 // there's one system transaction per block
   293  	for _, collection := range collections {
   294  		numTxns += len(collection.Transactions)
   295  	}
   296  
   297  	return numTxns
   298  }
   299  
   300  func (e *blockComputer) executeBlock(
   301  	ctx context.Context,
   302  	parentBlockExecutionResultID flow.Identifier,
   303  	block *entity.ExecutableBlock,
   304  	baseSnapshot snapshot.StorageSnapshot,
   305  	derivedBlockData *derived.DerivedBlockData,
   306  ) (
   307  	*execution.ComputationResult,
   308  	error,
   309  ) {
   310  	// check the start state is set
   311  	if !block.HasStartState() {
   312  		return nil, fmt.Errorf("executable block start state is not set")
   313  	}
   314  
   315  	blockId := block.ID()
   316  	blockIdStr := blockId.String()
   317  
   318  	rawCollections := block.Collections()
   319  
   320  	blockSpan := e.tracer.StartSpanFromParent(
   321  		e.tracer.BlockRootSpan(blockId),
   322  		trace.EXEComputeBlock)
   323  	blockSpan.SetAttributes(
   324  		attribute.String("block_id", blockIdStr),
   325  		attribute.Int("collection_counts", len(rawCollections)))
   326  	defer blockSpan.End()
   327  
   328  	systemTxn, err := blueprints.SystemChunkTransaction(e.vmCtx.Chain)
   329  	if err != nil {
   330  		return nil, fmt.Errorf(
   331  			"could not get system chunk transaction: %w",
   332  			err)
   333  	}
   334  
   335  	numTxns := numberOfTransactionsInBlock(rawCollections)
   336  
   337  	collector := newResultCollector(
   338  		e.tracer,
   339  		blockSpan,
   340  		e.metrics,
   341  		e.committer,
   342  		e.signer,
   343  		e.executionDataProvider,
   344  		e.spockHasher,
   345  		e.receiptHasher,
   346  		parentBlockExecutionResultID,
   347  		block,
   348  		numTxns,
   349  		e.colResCons,
   350  		baseSnapshot,
   351  	)
   352  	defer collector.Stop()
   353  
   354  	requestQueue := make(chan TransactionRequest, numTxns)
   355  
   356  	database := newTransactionCoordinator(
   357  		e.vm,
   358  		baseSnapshot,
   359  		derivedBlockData,
   360  		collector)
   361  
   362  	e.queueTransactionRequests(
   363  		blockId,
   364  		blockIdStr,
   365  		block.Block.Header,
   366  		rawCollections,
   367  		systemTxn,
   368  		requestQueue,
   369  		numTxns,
   370  	)
   371  	close(requestQueue)
   372  
   373  	wg := &sync.WaitGroup{}
   374  	wg.Add(e.maxConcurrency)
   375  
   376  	for i := 0; i < e.maxConcurrency; i++ {
   377  		go e.executeTransactions(
   378  			blockSpan,
   379  			database,
   380  			requestQueue,
   381  			wg)
   382  	}
   383  
   384  	wg.Wait()
   385  
   386  	err = database.Error()
   387  	if err != nil {
   388  		return nil, err
   389  	}
   390  
   391  	res, err := collector.Finalize(ctx)
   392  	if err != nil {
   393  		return nil, fmt.Errorf("cannot finalize computation result: %w", err)
   394  	}
   395  
   396  	e.log.Debug().
   397  		Hex("block_id", logging.Entity(block)).
   398  		Msg("all views committed")
   399  
   400  	e.metrics.ExecutionBlockCachedPrograms(derivedBlockData.CachedPrograms())
   401  
   402  	return res, nil
   403  }
   404  
   405  func (e *blockComputer) executeTransactions(
   406  	blockSpan otelTrace.Span,
   407  	database *transactionCoordinator,
   408  	requestQueue chan TransactionRequest,
   409  	wg *sync.WaitGroup,
   410  ) {
   411  	defer wg.Done()
   412  
   413  	for request := range requestQueue {
   414  		attempt := 0
   415  		for {
   416  			request.ctx.Logger.Info().
   417  				Int("attempt", attempt).
   418  				Msg("executing transaction")
   419  
   420  			attempt += 1
   421  			err := e.executeTransaction(blockSpan, database, request, attempt)
   422  
   423  			if errors.IsRetryableConflictError(err) {
   424  				request.ctx.Logger.Info().
   425  					Int("attempt", attempt).
   426  					Str("conflict_error", err.Error()).
   427  					Msg("conflict detected. retrying transaction")
   428  				continue
   429  			}
   430  
   431  			if err != nil {
   432  				database.AbortAllOutstandingTransactions(err)
   433  				return
   434  			}
   435  
   436  			break // process next transaction
   437  		}
   438  	}
   439  }
   440  
   441  func (e *blockComputer) executeTransaction(
   442  	blockSpan otelTrace.Span,
   443  	database *transactionCoordinator,
   444  	request TransactionRequest,
   445  	attempt int,
   446  ) error {
   447  	txn, err := e.executeTransactionInternal(
   448  		blockSpan,
   449  		database,
   450  		request,
   451  		attempt)
   452  	if err != nil {
   453  		prefix := ""
   454  		if request.isSystemTransaction {
   455  			prefix = "system "
   456  		}
   457  
   458  		snapshotTime := logical.Time(0)
   459  		if txn != nil {
   460  			snapshotTime = txn.SnapshotTime()
   461  		}
   462  
   463  		return fmt.Errorf(
   464  			"failed to execute %stransaction %v (%d@%d) for block %s "+
   465  				"at height %v: %w",
   466  			prefix,
   467  			request.txnIdStr,
   468  			request.txnIndex,
   469  			snapshotTime,
   470  			request.blockIdStr,
   471  			request.ctx.BlockHeader.Height,
   472  			err)
   473  	}
   474  
   475  	return nil
   476  }
   477  
   478  func (e *blockComputer) executeTransactionInternal(
   479  	blockSpan otelTrace.Span,
   480  	database *transactionCoordinator,
   481  	request TransactionRequest,
   482  	attempt int,
   483  ) (
   484  	*transaction,
   485  	error,
   486  ) {
   487  	txSpan := e.tracer.StartSampledSpanFromParent(
   488  		blockSpan,
   489  		request.txnId,
   490  		trace.EXEComputeTransaction)
   491  	txSpan.SetAttributes(
   492  		attribute.String("tx_id", request.txnIdStr),
   493  		attribute.Int64("tx_index", int64(request.txnIndex)),
   494  		attribute.Int("col_index", request.collectionIndex),
   495  	)
   496  	defer txSpan.End()
   497  
   498  	request.ctx = fvm.NewContextFromParent(request.ctx, fvm.WithSpan(txSpan))
   499  
   500  	txn, err := database.NewTransaction(request, attempt)
   501  	if err != nil {
   502  		return nil, err
   503  	}
   504  	defer txn.Cleanup()
   505  
   506  	err = txn.Preprocess()
   507  	if err != nil {
   508  		return txn, err
   509  	}
   510  
   511  	// Validating here gives us an opportunity to early abort/retry the
   512  	// transaction in case the conflict is detectable after preprocessing.
   513  	// This is strictly an optimization and hence we don't need to wait for
   514  	// updates (removing this validate call won't impact correctness).
   515  	err = txn.Validate()
   516  	if err != nil {
   517  		return txn, err
   518  	}
   519  
   520  	err = txn.Execute()
   521  	if err != nil {
   522  		return txn, err
   523  	}
   524  
   525  	err = txn.Finalize()
   526  	if err != nil {
   527  		return txn, err
   528  	}
   529  
   530  	// Snapshot time smaller than execution time indicates there are outstanding
   531  	// transaction(s) that must be committed before this transaction can be
   532  	// committed.
   533  	for txn.SnapshotTime() < request.ExecutionTime() {
   534  		err = txn.WaitForUpdates()
   535  		if err != nil {
   536  			return txn, err
   537  		}
   538  
   539  		err = txn.Validate()
   540  		if err != nil {
   541  			return txn, err
   542  		}
   543  	}
   544  
   545  	return txn, txn.Commit()
   546  }