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