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 }