github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/fvm/transactionInvoker.go (about)

     1  package fvm
     2  
     3  import (
     4  	"fmt"
     5  	"strconv"
     6  
     7  	"github.com/onflow/cadence/runtime"
     8  	"github.com/onflow/cadence/runtime/common"
     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/fvm/environment"
    14  	"github.com/onflow/flow-go/fvm/errors"
    15  	"github.com/onflow/flow-go/fvm/evm"
    16  	reusableRuntime "github.com/onflow/flow-go/fvm/runtime"
    17  	"github.com/onflow/flow-go/fvm/storage"
    18  	"github.com/onflow/flow-go/fvm/storage/derived"
    19  	"github.com/onflow/flow-go/fvm/storage/snapshot"
    20  	"github.com/onflow/flow-go/fvm/storage/state"
    21  	"github.com/onflow/flow-go/fvm/systemcontracts"
    22  	"github.com/onflow/flow-go/module/trace"
    23  )
    24  
    25  type TransactionExecutorParams struct {
    26  	AuthorizationChecksEnabled bool
    27  
    28  	SequenceNumberCheckAndIncrementEnabled bool
    29  
    30  	// If AccountKeyWeightThreshold is set to a negative number, signature
    31  	// verification is skipped during authorization checks.
    32  	//
    33  	// Note: This is set only by tests
    34  	AccountKeyWeightThreshold int
    35  
    36  	// Note: This is disabled only by tests
    37  	TransactionBodyExecutionEnabled bool
    38  }
    39  
    40  func DefaultTransactionExecutorParams() TransactionExecutorParams {
    41  	return TransactionExecutorParams{
    42  		AuthorizationChecksEnabled:             true,
    43  		SequenceNumberCheckAndIncrementEnabled: true,
    44  		AccountKeyWeightThreshold:              AccountKeyWeightThreshold,
    45  		TransactionBodyExecutionEnabled:        true,
    46  	}
    47  }
    48  
    49  type transactionExecutor struct {
    50  	TransactionExecutorParams
    51  
    52  	TransactionVerifier
    53  	TransactionSequenceNumberChecker
    54  	TransactionStorageLimiter
    55  	TransactionPayerBalanceChecker
    56  
    57  	ctx      Context
    58  	proc     *TransactionProcedure
    59  	txnState storage.TransactionPreparer
    60  
    61  	span otelTrace.Span
    62  	env  environment.Environment
    63  
    64  	errs *errors.ErrorsCollector
    65  
    66  	startedTransactionBodyExecution bool
    67  	nestedTxnId                     state.NestedTransactionId
    68  
    69  	cadenceRuntime  *reusableRuntime.ReusableCadenceRuntime
    70  	txnBodyExecutor runtime.Executor
    71  
    72  	output ProcedureOutput
    73  }
    74  
    75  func newTransactionExecutor(
    76  	ctx Context,
    77  	proc *TransactionProcedure,
    78  	txnState storage.TransactionPreparer,
    79  ) *transactionExecutor {
    80  	span := ctx.StartChildSpan(trace.FVMExecuteTransaction)
    81  	span.SetAttributes(attribute.String("transaction_id", proc.ID.String()))
    82  
    83  	ctx.TxIndex = proc.TxIndex
    84  	ctx.TxId = proc.ID
    85  	ctx.TxBody = proc.Transaction
    86  
    87  	env := environment.NewTransactionEnvironment(
    88  		span,
    89  		ctx.EnvironmentParams,
    90  		txnState)
    91  
    92  	return &transactionExecutor{
    93  		TransactionExecutorParams: ctx.TransactionExecutorParams,
    94  		TransactionVerifier: TransactionVerifier{
    95  			VerificationConcurrency: 4,
    96  		},
    97  		ctx:                             ctx,
    98  		proc:                            proc,
    99  		txnState:                        txnState,
   100  		span:                            span,
   101  		env:                             env,
   102  		errs:                            errors.NewErrorsCollector(),
   103  		startedTransactionBodyExecution: false,
   104  		cadenceRuntime:                  env.BorrowCadenceRuntime(),
   105  	}
   106  }
   107  
   108  func (executor *transactionExecutor) Cleanup() {
   109  	executor.env.ReturnCadenceRuntime(executor.cadenceRuntime)
   110  	executor.span.End()
   111  }
   112  
   113  func (executor *transactionExecutor) Output() ProcedureOutput {
   114  	return executor.output
   115  }
   116  
   117  func (executor *transactionExecutor) handleError(
   118  	err error,
   119  	step string,
   120  ) error {
   121  	txErr, failure := errors.SplitErrorTypes(err)
   122  	if failure != nil {
   123  		// log the full error path
   124  		executor.ctx.Logger.Err(err).
   125  			Str("step", step).
   126  			Msg("fatal error when handling a transaction")
   127  		return failure
   128  	}
   129  
   130  	if txErr != nil {
   131  		executor.output.Err = txErr
   132  	}
   133  
   134  	return nil
   135  }
   136  
   137  func (executor *transactionExecutor) Preprocess() error {
   138  	return executor.handleError(executor.preprocess(), "preprocess")
   139  }
   140  
   141  func (executor *transactionExecutor) Execute() error {
   142  	return executor.handleError(executor.execute(), "executing")
   143  }
   144  
   145  func (executor *transactionExecutor) preprocess() error {
   146  	if executor.AuthorizationChecksEnabled {
   147  		err := executor.CheckAuthorization(
   148  			executor.ctx.TracerSpan,
   149  			executor.proc,
   150  			executor.txnState,
   151  			executor.AccountKeyWeightThreshold)
   152  		if err != nil {
   153  			executor.errs.Collect(err)
   154  			return executor.errs.ErrorOrNil()
   155  		}
   156  	}
   157  
   158  	if executor.SequenceNumberCheckAndIncrementEnabled {
   159  		err := executor.CheckAndIncrementSequenceNumber(
   160  			executor.ctx.TracerSpan,
   161  			executor.proc,
   162  			executor.txnState)
   163  		if err != nil {
   164  			executor.errs.Collect(err)
   165  			return executor.errs.ErrorOrNil()
   166  		}
   167  	}
   168  
   169  	if !executor.TransactionBodyExecutionEnabled {
   170  		return nil
   171  	}
   172  
   173  	executor.errs.Collect(executor.preprocessTransactionBody())
   174  	if executor.errs.CollectedFailure() {
   175  		return executor.errs.ErrorOrNil()
   176  	}
   177  
   178  	return nil
   179  }
   180  
   181  // preprocessTransactionBody preprocess parts of a transaction body that are
   182  // infrequently modified and are expensive to compute.  For now this includes
   183  // reading meter parameter overrides and parsing programs.
   184  func (executor *transactionExecutor) preprocessTransactionBody() error {
   185  	// setup evm
   186  	if executor.ctx.EVMEnabled {
   187  		chain := executor.ctx.Chain
   188  		sc := systemcontracts.SystemContractsForChain(chain.ChainID())
   189  		err := evm.SetupEnvironment(
   190  			chain.ChainID(),
   191  			executor.env,
   192  			executor.cadenceRuntime.TxRuntimeEnv,
   193  			sc.FlowToken.Address,
   194  		)
   195  		if err != nil {
   196  			return err
   197  		}
   198  	}
   199  
   200  	meterParams, err := getBodyMeterParameters(
   201  		executor.ctx,
   202  		executor.proc,
   203  		executor.txnState)
   204  	if err != nil {
   205  		return fmt.Errorf("error gettng meter parameters: %w", err)
   206  	}
   207  
   208  	txnId, err := executor.txnState.BeginNestedTransactionWithMeterParams(
   209  		meterParams)
   210  	if err != nil {
   211  		return err
   212  	}
   213  	executor.startedTransactionBodyExecution = true
   214  	executor.nestedTxnId = txnId
   215  
   216  	executor.txnBodyExecutor = executor.cadenceRuntime.NewTransactionExecutor(
   217  		runtime.Script{
   218  			Source:    executor.proc.Transaction.Script,
   219  			Arguments: executor.proc.Transaction.Arguments,
   220  		},
   221  		common.TransactionLocation(executor.proc.ID))
   222  
   223  	// This initializes various cadence variables and parses the programs used
   224  	// by the transaction body.
   225  	err = executor.txnBodyExecutor.Preprocess()
   226  	if err != nil {
   227  		return fmt.Errorf(
   228  			"transaction preprocess failed: %w",
   229  			err)
   230  	}
   231  
   232  	return nil
   233  }
   234  
   235  func (executor *transactionExecutor) execute() error {
   236  	if !executor.startedTransactionBodyExecution {
   237  		return executor.errs.ErrorOrNil()
   238  	}
   239  
   240  	return executor.ExecuteTransactionBody()
   241  }
   242  
   243  func (executor *transactionExecutor) ExecuteTransactionBody() error {
   244  	// setup evm
   245  	if executor.ctx.EVMEnabled {
   246  		chain := executor.ctx.Chain
   247  		sc := systemcontracts.SystemContractsForChain(chain.ChainID())
   248  		err := evm.SetupEnvironment(
   249  			chain.ChainID(),
   250  			executor.env,
   251  			executor.cadenceRuntime.TxRuntimeEnv,
   252  			sc.FlowToken.Address,
   253  		)
   254  		if err != nil {
   255  			return err
   256  		}
   257  	}
   258  
   259  	var invalidator derived.TransactionInvalidator
   260  	if !executor.errs.CollectedError() {
   261  
   262  		var txError error
   263  		invalidator, txError = executor.normalExecution()
   264  		if executor.errs.Collect(txError).CollectedFailure() {
   265  			return executor.errs.ErrorOrNil()
   266  		}
   267  	}
   268  
   269  	if executor.errs.CollectedError() {
   270  		invalidator = nil
   271  		executor.txnState.RunWithAllLimitsDisabled(executor.errorExecution)
   272  		if executor.errs.CollectedFailure() {
   273  			return executor.errs.ErrorOrNil()
   274  		}
   275  	}
   276  
   277  	// log the execution intensities here, so that they do not contain data
   278  	// from transaction fee deduction, because the payer is not charged for that.
   279  	executor.logExecutionIntensities()
   280  
   281  	executor.errs.Collect(executor.commit(invalidator))
   282  
   283  	return executor.errs.ErrorOrNil()
   284  }
   285  
   286  func (executor *transactionExecutor) deductTransactionFees() (err error) {
   287  	if !executor.env.TransactionFeesEnabled() {
   288  		return nil
   289  	}
   290  
   291  	computationLimit := uint64(executor.txnState.TotalComputationLimit())
   292  
   293  	computationUsed, err := executor.env.ComputationUsed()
   294  	if err != nil {
   295  		return errors.NewTransactionFeeDeductionFailedError(
   296  			executor.proc.Transaction.Payer,
   297  			computationLimit,
   298  			err)
   299  	}
   300  
   301  	if computationUsed > computationLimit {
   302  		computationUsed = computationLimit
   303  	}
   304  
   305  	_, err = executor.env.DeductTransactionFees(
   306  		executor.proc.Transaction.Payer,
   307  		executor.proc.Transaction.InclusionEffort(),
   308  		computationUsed)
   309  
   310  	if err != nil {
   311  		return errors.NewTransactionFeeDeductionFailedError(
   312  			executor.proc.Transaction.Payer,
   313  			computationUsed,
   314  			err)
   315  	}
   316  	return nil
   317  }
   318  
   319  // logExecutionIntensities logs execution intensities of the transaction
   320  func (executor *transactionExecutor) logExecutionIntensities() {
   321  	log := executor.env.Logger()
   322  	if !log.Debug().Enabled() {
   323  		return
   324  	}
   325  
   326  	computation := zerolog.Dict()
   327  	for s, u := range executor.txnState.ComputationIntensities() {
   328  		computation.Uint(strconv.FormatUint(uint64(s), 10), u)
   329  	}
   330  	memory := zerolog.Dict()
   331  	for s, u := range executor.txnState.MemoryIntensities() {
   332  		memory.Uint(strconv.FormatUint(uint64(s), 10), u)
   333  	}
   334  	log.Debug().
   335  		Uint64("ledgerInteractionUsed", executor.txnState.InteractionUsed()).
   336  		Uint64("computationUsed", executor.txnState.TotalComputationUsed()).
   337  		Uint64("memoryEstimate", executor.txnState.TotalMemoryEstimate()).
   338  		Dict("computationIntensities", computation).
   339  		Dict("memoryIntensities", memory).
   340  		Msg("transaction execution data")
   341  }
   342  
   343  func (executor *transactionExecutor) normalExecution() (
   344  	invalidator derived.TransactionInvalidator,
   345  	err error,
   346  ) {
   347  	var maxTxFees uint64
   348  	// run with limits disabled since this is a static cost check
   349  	// and should be accounted for in the inclusion cost.
   350  	executor.txnState.RunWithAllLimitsDisabled(func() {
   351  		maxTxFees, err = executor.CheckPayerBalanceAndReturnMaxFees(
   352  			executor.proc,
   353  			executor.txnState,
   354  			executor.env)
   355  	})
   356  
   357  	if err != nil {
   358  		return
   359  	}
   360  
   361  	var bodyTxnId state.NestedTransactionId
   362  	bodyTxnId, err = executor.txnState.BeginNestedTransaction()
   363  	if err != nil {
   364  		return
   365  	}
   366  
   367  	err = executor.txnBodyExecutor.Execute()
   368  	if err != nil {
   369  		err = fmt.Errorf("transaction execute failed: %w", err)
   370  		return
   371  	}
   372  
   373  	// Before checking storage limits, we must apply all pending changes
   374  	// that may modify storage usage.
   375  	var contractUpdates environment.ContractUpdates
   376  	contractUpdates, err = executor.env.FlushPendingUpdates()
   377  	if err != nil {
   378  		err = fmt.Errorf(
   379  			"transaction invocation failed to flush pending changes from "+
   380  				"environment: %w",
   381  			err)
   382  		return
   383  	}
   384  
   385  	var bodySnapshot *snapshot.ExecutionSnapshot
   386  	bodySnapshot, err = executor.txnState.CommitNestedTransaction(bodyTxnId)
   387  	if err != nil {
   388  		return
   389  	}
   390  
   391  	invalidator = environment.NewDerivedDataInvalidator(
   392  		contractUpdates,
   393  		executor.ctx.Chain.ServiceAddress(),
   394  		bodySnapshot)
   395  
   396  	// Check if all account storage limits are ok
   397  	//
   398  	// The storage limit check is performed for all accounts that were touched during the transaction.
   399  	// The storage capacity of an account depends on its balance and should be higher than the accounts storage used.
   400  	// The payer account is special cased in this check and its balance is considered max_fees lower than its
   401  	// actual balance, for the purpose of calculating storage capacity, because the payer will have to pay for this tx.
   402  	err = executor.CheckStorageLimits(
   403  		executor.ctx,
   404  		executor.env,
   405  		bodySnapshot,
   406  		executor.proc.Transaction.Payer,
   407  		maxTxFees)
   408  
   409  	if err != nil {
   410  		return
   411  	}
   412  
   413  	executor.txnState.RunWithAllLimitsDisabled(func() {
   414  		err = executor.deductTransactionFees()
   415  	})
   416  
   417  	return
   418  }
   419  
   420  // Clear changes and try to deduct fees again.
   421  func (executor *transactionExecutor) errorExecution() {
   422  	// log transaction as failed
   423  	log := executor.env.Logger()
   424  	log.Info().
   425  		Err(executor.errs.ErrorOrNil()).
   426  		Msg("transaction executed with error")
   427  
   428  	executor.env.Reset()
   429  
   430  	// drop delta since transaction failed
   431  	restartErr := executor.txnState.RestartNestedTransaction(executor.nestedTxnId)
   432  	if executor.errs.Collect(restartErr).CollectedFailure() {
   433  		return
   434  	}
   435  
   436  	// try to deduct fees again, to get the fee deduction events
   437  	feesError := executor.deductTransactionFees()
   438  
   439  	// if fee deduction fails just do clean up and exit
   440  	if feesError != nil {
   441  		log.Info().
   442  			Err(feesError).
   443  			Msg("transaction fee deduction executed with error")
   444  
   445  		if executor.errs.Collect(feesError).CollectedFailure() {
   446  			return
   447  		}
   448  
   449  		// drop delta
   450  		executor.errs.Collect(
   451  			executor.txnState.RestartNestedTransaction(executor.nestedTxnId))
   452  	}
   453  }
   454  
   455  func (executor *transactionExecutor) commit(
   456  	invalidator derived.TransactionInvalidator,
   457  ) error {
   458  	if executor.txnState.NumNestedTransactions() > 1 {
   459  		// This is a fvm internal programming error.  We forgot to call Commit
   460  		// somewhere in the control flow.  We should halt.
   461  		return fmt.Errorf(
   462  			"successfully executed transaction has unexpected " +
   463  				"nested transactions.")
   464  	}
   465  
   466  	err := executor.output.PopulateEnvironmentValues(executor.env)
   467  	if err != nil {
   468  		return err
   469  	}
   470  
   471  	// Based on various (e.g., contract) updates, we decide
   472  	// how to clean up the derived data.  For failed transactions we also do
   473  	// the same as a successful transaction without any updates.
   474  	executor.txnState.AddInvalidator(invalidator)
   475  
   476  	_, commitErr := executor.txnState.CommitNestedTransaction(
   477  		executor.nestedTxnId)
   478  	if commitErr != nil {
   479  		return fmt.Errorf(
   480  			"transaction invocation failed when merging state: %w",
   481  			commitErr)
   482  	}
   483  
   484  	return nil
   485  }