github.com/onflow/flow-go@v0.33.17/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  			chain.ServiceAddress(),
   194  			sc.FlowToken.Address,
   195  		)
   196  		if err != nil {
   197  			return err
   198  		}
   199  	}
   200  
   201  	meterParams, err := getBodyMeterParameters(
   202  		executor.ctx,
   203  		executor.proc,
   204  		executor.txnState)
   205  	if err != nil {
   206  		return fmt.Errorf("error gettng meter parameters: %w", err)
   207  	}
   208  
   209  	txnId, err := executor.txnState.BeginNestedTransactionWithMeterParams(
   210  		meterParams)
   211  	if err != nil {
   212  		return err
   213  	}
   214  	executor.startedTransactionBodyExecution = true
   215  	executor.nestedTxnId = txnId
   216  
   217  	executor.txnBodyExecutor = executor.cadenceRuntime.NewTransactionExecutor(
   218  		runtime.Script{
   219  			Source:    executor.proc.Transaction.Script,
   220  			Arguments: executor.proc.Transaction.Arguments,
   221  		},
   222  		common.TransactionLocation(executor.proc.ID))
   223  
   224  	// This initializes various cadence variables and parses the programs used
   225  	// by the transaction body.
   226  	err = executor.txnBodyExecutor.Preprocess()
   227  	if err != nil {
   228  		return fmt.Errorf(
   229  			"transaction preprocess failed: %w",
   230  			err)
   231  	}
   232  
   233  	return nil
   234  }
   235  
   236  func (executor *transactionExecutor) execute() error {
   237  	if !executor.startedTransactionBodyExecution {
   238  		return executor.errs.ErrorOrNil()
   239  	}
   240  
   241  	return executor.ExecuteTransactionBody()
   242  }
   243  
   244  func (executor *transactionExecutor) ExecuteTransactionBody() error {
   245  	// setup evm
   246  	if executor.ctx.EVMEnabled {
   247  		chain := executor.ctx.Chain
   248  		sc := systemcontracts.SystemContractsForChain(chain.ChainID())
   249  		err := evm.SetupEnvironment(
   250  			chain.ChainID(),
   251  			executor.env,
   252  			executor.cadenceRuntime.TxRuntimeEnv,
   253  			chain.ServiceAddress(),
   254  			sc.FlowToken.Address,
   255  		)
   256  		if err != nil {
   257  			return err
   258  		}
   259  	}
   260  
   261  	var invalidator derived.TransactionInvalidator
   262  	if !executor.errs.CollectedError() {
   263  
   264  		var txError error
   265  		invalidator, txError = executor.normalExecution()
   266  		if executor.errs.Collect(txError).CollectedFailure() {
   267  			return executor.errs.ErrorOrNil()
   268  		}
   269  	}
   270  
   271  	if executor.errs.CollectedError() {
   272  		invalidator = nil
   273  		executor.txnState.RunWithAllLimitsDisabled(executor.errorExecution)
   274  		if executor.errs.CollectedFailure() {
   275  			return executor.errs.ErrorOrNil()
   276  		}
   277  	}
   278  
   279  	// log the execution intensities here, so that they do not contain data
   280  	// from transaction fee deduction, because the payer is not charged for that.
   281  	executor.logExecutionIntensities()
   282  
   283  	executor.errs.Collect(executor.commit(invalidator))
   284  
   285  	return executor.errs.ErrorOrNil()
   286  }
   287  
   288  func (executor *transactionExecutor) deductTransactionFees() (err error) {
   289  	if !executor.env.TransactionFeesEnabled() {
   290  		return nil
   291  	}
   292  
   293  	computationLimit := uint64(executor.txnState.TotalComputationLimit())
   294  
   295  	computationUsed, err := executor.env.ComputationUsed()
   296  	if err != nil {
   297  		return errors.NewTransactionFeeDeductionFailedError(
   298  			executor.proc.Transaction.Payer,
   299  			computationLimit,
   300  			err)
   301  	}
   302  
   303  	if computationUsed > computationLimit {
   304  		computationUsed = computationLimit
   305  	}
   306  
   307  	_, err = executor.env.DeductTransactionFees(
   308  		executor.proc.Transaction.Payer,
   309  		executor.proc.Transaction.InclusionEffort(),
   310  		computationUsed)
   311  
   312  	if err != nil {
   313  		return errors.NewTransactionFeeDeductionFailedError(
   314  			executor.proc.Transaction.Payer,
   315  			computationUsed,
   316  			err)
   317  	}
   318  	return nil
   319  }
   320  
   321  // logExecutionIntensities logs execution intensities of the transaction
   322  func (executor *transactionExecutor) logExecutionIntensities() {
   323  	log := executor.env.Logger()
   324  	if !log.Debug().Enabled() {
   325  		return
   326  	}
   327  
   328  	computation := zerolog.Dict()
   329  	for s, u := range executor.txnState.ComputationIntensities() {
   330  		computation.Uint(strconv.FormatUint(uint64(s), 10), u)
   331  	}
   332  	memory := zerolog.Dict()
   333  	for s, u := range executor.txnState.MemoryIntensities() {
   334  		memory.Uint(strconv.FormatUint(uint64(s), 10), u)
   335  	}
   336  	log.Debug().
   337  		Uint64("ledgerInteractionUsed", executor.txnState.InteractionUsed()).
   338  		Uint64("computationUsed", executor.txnState.TotalComputationUsed()).
   339  		Uint64("memoryEstimate", executor.txnState.TotalMemoryEstimate()).
   340  		Dict("computationIntensities", computation).
   341  		Dict("memoryIntensities", memory).
   342  		Msg("transaction execution data")
   343  }
   344  
   345  func (executor *transactionExecutor) normalExecution() (
   346  	invalidator derived.TransactionInvalidator,
   347  	err error,
   348  ) {
   349  	var maxTxFees uint64
   350  	// run with limits disabled since this is a static cost check
   351  	// and should be accounted for in the inclusion cost.
   352  	executor.txnState.RunWithAllLimitsDisabled(func() {
   353  		maxTxFees, err = executor.CheckPayerBalanceAndReturnMaxFees(
   354  			executor.proc,
   355  			executor.txnState,
   356  			executor.env)
   357  	})
   358  
   359  	if err != nil {
   360  		return
   361  	}
   362  
   363  	var bodyTxnId state.NestedTransactionId
   364  	bodyTxnId, err = executor.txnState.BeginNestedTransaction()
   365  	if err != nil {
   366  		return
   367  	}
   368  
   369  	err = executor.txnBodyExecutor.Execute()
   370  	if err != nil {
   371  		err = fmt.Errorf("transaction execute failed: %w", err)
   372  		return
   373  	}
   374  
   375  	// Before checking storage limits, we must apply all pending changes
   376  	// that may modify storage usage.
   377  	var contractUpdates environment.ContractUpdates
   378  	contractUpdates, err = executor.env.FlushPendingUpdates()
   379  	if err != nil {
   380  		err = fmt.Errorf(
   381  			"transaction invocation failed to flush pending changes from "+
   382  				"environment: %w",
   383  			err)
   384  		return
   385  	}
   386  
   387  	var bodySnapshot *snapshot.ExecutionSnapshot
   388  	bodySnapshot, err = executor.txnState.CommitNestedTransaction(bodyTxnId)
   389  	if err != nil {
   390  		return
   391  	}
   392  
   393  	invalidator = environment.NewDerivedDataInvalidator(
   394  		contractUpdates,
   395  		executor.ctx.Chain.ServiceAddress(),
   396  		bodySnapshot)
   397  
   398  	// Check if all account storage limits are ok
   399  	//
   400  	// disable the computation/memory limit checks on storage checks,
   401  	// so we don't error from computation/memory limits on this part.
   402  	//
   403  	// The storage limit check is performed for all accounts that were touched during the transaction.
   404  	// The storage capacity of an account depends on its balance and should be higher than the accounts storage used.
   405  	// The payer account is special cased in this check and its balance is considered max_fees lower than its
   406  	// actual balance, for the purpose of calculating storage capacity, because the payer will have to pay for this tx.
   407  	executor.txnState.RunWithAllLimitsDisabled(func() {
   408  		err = executor.CheckStorageLimits(
   409  			executor.ctx,
   410  			executor.env,
   411  			bodySnapshot,
   412  			executor.proc.Transaction.Payer,
   413  			maxTxFees)
   414  	})
   415  
   416  	if err != nil {
   417  		return
   418  	}
   419  
   420  	executor.txnState.RunWithAllLimitsDisabled(func() {
   421  		err = executor.deductTransactionFees()
   422  	})
   423  
   424  	return
   425  }
   426  
   427  // Clear changes and try to deduct fees again.
   428  func (executor *transactionExecutor) errorExecution() {
   429  	// log transaction as failed
   430  	log := executor.env.Logger()
   431  	log.Info().
   432  		Err(executor.errs.ErrorOrNil()).
   433  		Msg("transaction executed with error")
   434  
   435  	executor.env.Reset()
   436  
   437  	// drop delta since transaction failed
   438  	restartErr := executor.txnState.RestartNestedTransaction(executor.nestedTxnId)
   439  	if executor.errs.Collect(restartErr).CollectedFailure() {
   440  		return
   441  	}
   442  
   443  	// try to deduct fees again, to get the fee deduction events
   444  	feesError := executor.deductTransactionFees()
   445  
   446  	// if fee deduction fails just do clean up and exit
   447  	if feesError != nil {
   448  		log.Info().
   449  			Err(feesError).
   450  			Msg("transaction fee deduction executed with error")
   451  
   452  		if executor.errs.Collect(feesError).CollectedFailure() {
   453  			return
   454  		}
   455  
   456  		// drop delta
   457  		executor.errs.Collect(
   458  			executor.txnState.RestartNestedTransaction(executor.nestedTxnId))
   459  	}
   460  }
   461  
   462  func (executor *transactionExecutor) commit(
   463  	invalidator derived.TransactionInvalidator,
   464  ) error {
   465  	if executor.txnState.NumNestedTransactions() > 1 {
   466  		// This is a fvm internal programming error.  We forgot to call Commit
   467  		// somewhere in the control flow.  We should halt.
   468  		return fmt.Errorf(
   469  			"successfully executed transaction has unexpected " +
   470  				"nested transactions.")
   471  	}
   472  
   473  	err := executor.output.PopulateEnvironmentValues(executor.env)
   474  	if err != nil {
   475  		return err
   476  	}
   477  
   478  	// Based on various (e.g., contract) updates, we decide
   479  	// how to clean up the derived data.  For failed transactions we also do
   480  	// the same as a successful transaction without any updates.
   481  	executor.txnState.AddInvalidator(invalidator)
   482  
   483  	_, commitErr := executor.txnState.CommitNestedTransaction(
   484  		executor.nestedTxnId)
   485  	if commitErr != nil {
   486  		return fmt.Errorf(
   487  			"transaction invocation failed when merging state: %w",
   488  			commitErr)
   489  	}
   490  
   491  	return nil
   492  }