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 }