github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/engine/execution/computation/computer/computer.go (about)

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