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 }