-
Notifications
You must be signed in to change notification settings - Fork 887
fix(giga): route EVM validation failures to v2 fallback (CON-368) #3753
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
05386d3
d06795b
151a4ed
75d0509
7215c0a
ed8c3b4
4ebc025
4496581
5359732
ee36fc2
918d980
4f2f736
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1610,16 +1610,25 @@ func (app *App) ProcessTxsSynchronousGiga(ctx sdk.Context, txs [][]byte, typedTx | |
| // Store-iteration panic: re-run via v2 so the result matches v2 exactly. | ||
| if fallbackToV2 { | ||
| utilmetrics.IncrGigaFallbackToV2Counter() // TODO(PLT-327): remove once app_giga_fallback_to_v2_total verified | ||
| appMetrics.gigaFallback.Add(ctx.Context(), 1) | ||
| appMetrics.gigaFallback.Add(ctx.Context(), 1, | ||
| otelmetric.WithAttributes( | ||
| attribute.String("reason", gigaFallbackStoreIterator), | ||
| attribute.String("scope", "tx"))) | ||
| res := app.DeliverTxWithResult(ctx, tx, typedTxs[i]) | ||
| txResults[i] = res | ||
| ms.Write() | ||
| continue | ||
| } | ||
|
|
||
| if execErr != nil { | ||
| // Check if this is a fail-fast error (Cosmos precompile interop detected) | ||
| // Abort errors (validation failure, balance migration, self-destruct, | ||
| // cosmos-precompile interop) re-run this tx via v2. | ||
| if gigautils.ShouldExecutionAbort(execErr) { | ||
| utilmetrics.IncrGigaFallbackToV2Counter() // TODO(PLT-327): remove once app_giga_fallback_to_v2_total verified | ||
| appMetrics.gigaFallback.Add(ctx.Context(), 1, | ||
| otelmetric.WithAttributes( | ||
| attribute.String("reason", gigaFallbackReason(execErr)), | ||
| attribute.String("scope", "tx"))) | ||
| res := app.DeliverTxWithResult(ctx, tx, typedTxs[i]) | ||
| txResults[i] = res | ||
| ms.Write() | ||
|
|
@@ -1837,16 +1846,23 @@ func (app *App) ProcessTXsWithOCCGiga(ctx sdk.Context, txs [][]byte, typedTxs [] | |
| return nil, ctx | ||
| } | ||
|
|
||
| fallbackReason := gigaFallbackOther | ||
| for _, r := range evmBatchResult { | ||
| if r.Code == gigautils.GigaAbortCode && r.Codespace == gigautils.GigaAbortCodespace { | ||
| fallbackToV2 = true | ||
| if r.Info != "" { | ||
| fallbackReason = r.Info | ||
| } | ||
| break | ||
| } | ||
| } | ||
|
|
||
| if fallbackToV2 { | ||
| utilmetrics.IncrGigaFallbackToV2Counter() // TODO(PLT-327): remove once app_giga_fallback_to_v2_total verified | ||
| appMetrics.gigaFallback.Add(ctx.Context(), 1) | ||
| appMetrics.gigaFallback.Add(ctx.Context(), 1, | ||
| otelmetric.WithAttributes( | ||
| attribute.String("reason", fallbackReason), | ||
| attribute.String("scope", "batch"))) | ||
| // Discard all EVM changes by skipping cache writes, then re-run all txs via DeliverTx. | ||
| evmBatchResult = nil | ||
| v2Entries = make([]*sdk.DeliverTxEntry, len(txs)) | ||
|
|
@@ -1996,6 +2012,34 @@ func (app *App) ProcessBlock(ctx sdk.Context, txs [][]byte, req *BlockProcessReq | |
| return events, txResults, endBlockResp, nil | ||
| } | ||
|
|
||
| // Fallback-cause labels for the app_giga_fallback_to_v2 metric. | ||
| const ( | ||
| gigaFallbackValidationFailed = "validation_failed" | ||
| gigaFallbackBalanceMigration = "balance_migration" | ||
| gigaFallbackSelfDestruct = "self_destruct" | ||
| gigaFallbackInvalidPrecompile = "invalid_precompile" | ||
| gigaFallbackStoreIterator = "store_iterator" | ||
| gigaFallbackOther = "other" | ||
| ) | ||
|
|
||
| // gigaFallbackReason labels the app_giga_fallback_to_v2 metric with which | ||
|
bdchatham marked this conversation as resolved.
bdchatham marked this conversation as resolved.
|
||
| // abort sentinel routed a tx to v2, so operators can tell a validation-failure | ||
| // wave from balance-migration churn. | ||
| func gigaFallbackReason(err error) string { | ||
| switch err.(type) { | ||
| case *gigautils.ValidationFailedAbortError: | ||
| return gigaFallbackValidationFailed | ||
| case *gigaprecompiles.BalanceMigrationAbortError: | ||
| return gigaFallbackBalanceMigration | ||
| case *gigaprecompiles.SelfDestructAbortError: | ||
| return gigaFallbackSelfDestruct | ||
| case *gigaprecompiles.InvalidPrecompileCallError: | ||
| return gigaFallbackInvalidPrecompile | ||
| default: | ||
| return gigaFallbackOther | ||
| } | ||
| } | ||
|
|
||
| // executeEVMTxWithGigaExecutor executes a single EVM transaction using the giga executor. | ||
| // The sender address is recovered directly from the transaction signature - no Cosmos SDK ante handlers needed. | ||
| func (app *App) executeEVMTxWithGigaExecutor(ctx sdk.Context, msg *evmtypes.MsgEVMTransaction, cache *gigaBlockCache) (*abci.ExecTxResult, error) { | ||
|
|
@@ -2025,19 +2069,11 @@ func (app *App) executeEVMTxWithGigaExecutor(ctx sdk.Context, msg *evmtypes.MsgE | |
| ctx = ctx.WithGasMeter(sdk.NewInfiniteGasMeterWithMultiplier(ctx)) | ||
|
|
||
| if validation.err != nil { | ||
| // Validation failed - bump nonce via keeper if it was valid (matches V2's DeliverTxCallback | ||
| // behavior where nonce is incremented even on fee validation failures). | ||
| // For successful txs, the nonce is bumped by the EVM during execution. | ||
| if validation.bumpNonce { | ||
| app.GigaEvmKeeper.SetNonce(ctx, sender, validation.currentNonce+1) | ||
| app.EvmKeeper.SetNonceBumped(ctx) | ||
| } | ||
| // V2 reports intrinsic gas as gasUsed even on validation failure (for metrics), | ||
| // but no actual balance is deducted | ||
| intrinsicGas, _ := core.IntrinsicGas(ethTx.Data(), ethTx.AccessList(), ethTx.SetCodeAuthorizations(), ethTx.To() == nil, true, true, true) | ||
| validation.err.GasUsed = int64(intrinsicGas) //nolint:gosec | ||
| validation.err.GasWanted = int64(ethTx.Gas()) //nolint:gosec | ||
| return validation.err, nil | ||
| // v2 rejects fee/nonce/balance failures in its ante chain, whose | ||
| // receipt gas fields giga cannot reconstruct; a giga-side receipt here | ||
| // diverges LastResultsHash on mixed fleets (CON-368). Fall back to v2, | ||
| // which also owns the nonce bump. | ||
| return nil, gigautils.ErrValidationFailed | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [suggestion] Behavior change worth calling out: before this PR a delivery-time validation failure (nonce-too-high, insufficient-fee, insufficient-funds) produced an inline giga receipt. Now it returns an abort error, and under OCC (ProcessTXsWithOCCGiga) a single aborting tx forces the entire giga batch to be discarded and re-run through the v2 OCC scheduler. Validation failures like nonce-too-high can occur on otherwise-valid blocks, so any block containing one now pays a full-batch re-execution. This is the correct trade for consensus safety, and the new reason-labeled app_giga_fallback_to_v2 metric makes it observable — but the frequency of batch fallback should be watched after rollout in case a cheaper per-tx fallback is warranted for the OCC path later. |
||
| } | ||
|
|
||
| if !isAssociated { | ||
|
|
@@ -2277,7 +2313,7 @@ func (app *App) makeGigaDeliverTx(cache *gigaBlockCache) func(sdk.Context, abci. | |
| resp = abci.ResponseDeliverTx{ | ||
| Code: gigautils.GigaAbortCode, | ||
| Codespace: gigautils.GigaAbortCodespace, | ||
| Info: gigautils.GigaAbortInfo, | ||
| Info: gigaFallbackStoreIterator, | ||
| Log: "giga executor abort: store iteration unsupported, fall back to v2", | ||
| } | ||
| return | ||
|
|
@@ -2316,7 +2352,7 @@ func (app *App) makeGigaDeliverTx(cache *gigaBlockCache) func(sdk.Context, abci. | |
| return abci.ResponseDeliverTx{ | ||
| Code: gigautils.GigaAbortCode, | ||
| Codespace: gigautils.GigaAbortCodespace, | ||
| Info: gigautils.GigaAbortInfo, | ||
| Info: gigaFallbackReason(err), | ||
| Log: "giga executor abort: fall back to v2", | ||
| } | ||
| } | ||
|
|
@@ -3006,10 +3042,8 @@ func (app *App) inplacetestnetInitializer(pk cryptotypes.PubKey) error { | |
|
|
||
| // gigaValidationResult holds the result of EVM transaction validation. | ||
| type gigaValidationResult struct { | ||
| err *abci.ExecTxResult // nil if validation passed | ||
| bumpNonce bool // true if tx nonce matches expected nonce | ||
| currentNonce uint64 // the expected nonce at time of validation | ||
| baseFee *big.Int // the base fee used for validation | ||
| err *abci.ExecTxResult // nil if validation passed | ||
| baseFee *big.Int // the base fee used for validation | ||
| } | ||
|
|
||
| // validateGigaEVMTx validates an EVM tx for fee, nonce, and stateless checks. | ||
|
|
@@ -3035,10 +3069,8 @@ func (app *App) validateGigaEVMTx( | |
| baseFee = new(big.Int) | ||
| } | ||
|
|
||
| // Check nonce validity - determines if we bump nonce on fee/balance failures | ||
| currentNonce := app.GigaEvmKeeper.GetNonce(ctx, sender) | ||
| txNonce := ethTx.Nonce() | ||
| bumpNonce := txNonce == currentNonce | ||
|
|
||
| // Fee cap below base fee | ||
| if ethTx.GasFeeCap().Cmp(baseFee) < 0 { | ||
|
|
@@ -3047,9 +3079,7 @@ func (app *App) validateGigaEVMTx( | |
| Code: sdkerrors.ErrInsufficientFee.ABCICode(), | ||
| Log: "max fee per gas less than block base fee", | ||
| }, | ||
| bumpNonce: bumpNonce, | ||
| currentNonce: currentNonce, | ||
| baseFee: baseFee, | ||
| baseFee: baseFee, | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -3061,9 +3091,7 @@ func (app *App) validateGigaEVMTx( | |
| Code: sdkerrors.ErrInsufficientFee.ABCICode(), | ||
| Log: "max fee per gas less than minimum fee", | ||
| }, | ||
| bumpNonce: bumpNonce, | ||
| currentNonce: currentNonce, | ||
| baseFee: baseFee, | ||
| baseFee: baseFee, | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -3078,9 +3106,7 @@ func (app *App) validateGigaEVMTx( | |
| Code: sdkerrors.ErrWrongSequence.ABCICode(), | ||
| Log: fmt.Sprintf("nonce too high: address %s, tx: %d state: %d", sender.Hex(), txNonce, currentNonce), | ||
| }, | ||
| bumpNonce: bumpNonce, | ||
| currentNonce: currentNonce, | ||
| baseFee: baseFee, | ||
| baseFee: baseFee, | ||
| } | ||
| } | ||
| if txNonce < currentNonce { | ||
|
|
@@ -3089,9 +3115,7 @@ func (app *App) validateGigaEVMTx( | |
| Code: sdkerrors.ErrWrongSequence.ABCICode(), | ||
| Log: fmt.Sprintf("nonce too low: address %s, tx: %d state: %d", sender.Hex(), txNonce, currentNonce), | ||
| }, | ||
| bumpNonce: bumpNonce, | ||
| currentNonce: currentNonce, | ||
| baseFee: baseFee, | ||
| baseFee: baseFee, | ||
| } | ||
| } | ||
| // Nonce overflow guard (currentNonce + 1 would overflow) | ||
|
|
@@ -3101,9 +3125,7 @@ func (app *App) validateGigaEVMTx( | |
| Code: sdkerrors.ErrWrongSequence.ABCICode(), | ||
| Log: fmt.Sprintf("nonce max: address %s, nonce: %d", sender.Hex(), currentNonce), | ||
| }, | ||
| bumpNonce: bumpNonce, | ||
| currentNonce: currentNonce, | ||
| baseFee: baseFee, | ||
| baseFee: baseFee, | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -3117,9 +3139,7 @@ func (app *App) validateGigaEVMTx( | |
| Code: sdkerrors.ErrWrongSequence.ABCICode(), | ||
| Log: fmt.Sprintf("sender not an eoa: address %s, len(code): %d", sender.Hex(), len(senderCode)), | ||
| }, | ||
| bumpNonce: bumpNonce, | ||
| currentNonce: currentNonce, | ||
| baseFee: baseFee, | ||
| baseFee: baseFee, | ||
| } | ||
| } | ||
| } | ||
|
|
@@ -3131,9 +3151,7 @@ func (app *App) validateGigaEVMTx( | |
| Code: sdkerrors.ErrWrongSequence.ABCICode(), | ||
| Log: fmt.Sprintf("max fee per gas higher than 2^256-1: address %s, maxFeePerGas bit length: %d", sender.Hex(), l), | ||
| }, | ||
| bumpNonce: bumpNonce, | ||
| currentNonce: currentNonce, | ||
| baseFee: baseFee, | ||
| baseFee: baseFee, | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -3144,9 +3162,7 @@ func (app *App) validateGigaEVMTx( | |
| Code: sdkerrors.ErrWrongSequence.ABCICode(), | ||
| Log: fmt.Sprintf("max priority fee per gas higher than 2^256-1: address %s, maxPriorityFeePerGas bit length: %d", sender.Hex(), l), | ||
| }, | ||
| bumpNonce: bumpNonce, | ||
| currentNonce: currentNonce, | ||
| baseFee: baseFee, | ||
| baseFee: baseFee, | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -3157,9 +3173,7 @@ func (app *App) validateGigaEVMTx( | |
| Code: sdkerrors.ErrWrongSequence.ABCICode(), | ||
| Log: fmt.Sprintf("max priority fee per gas higher than max fee per gas: address %s, maxPriorityFeePerGas: %s, maxFeePerGas: %s", sender.Hex(), ethTx.GasTipCap(), ethTx.GasFeeCap()), | ||
| }, | ||
| bumpNonce: bumpNonce, | ||
| currentNonce: currentNonce, | ||
| baseFee: baseFee, | ||
| baseFee: baseFee, | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -3172,9 +3186,7 @@ func (app *App) validateGigaEVMTx( | |
| Code: sdkerrors.ErrWrongSequence.ABCICode(), | ||
| Log: fmt.Sprintf("set-code transaction must not be a create transaction: sender %s", sender.Hex()), | ||
| }, | ||
| bumpNonce: bumpNonce, | ||
| currentNonce: currentNonce, | ||
| baseFee: baseFee, | ||
| baseFee: baseFee, | ||
| } | ||
| } | ||
| // Set-code tx auth list must be non-empty | ||
|
|
@@ -3184,9 +3196,7 @@ func (app *App) validateGigaEVMTx( | |
| Code: sdkerrors.ErrWrongSequence.ABCICode(), | ||
| Log: fmt.Sprintf("set-code transaction with empty auth list: sender %s", sender.Hex()), | ||
| }, | ||
| bumpNonce: bumpNonce, | ||
| currentNonce: currentNonce, | ||
| baseFee: baseFee, | ||
| baseFee: baseFee, | ||
| } | ||
| } | ||
| } | ||
|
|
@@ -3214,18 +3224,14 @@ func (app *App) validateGigaEVMTx( | |
| Code: sdkerrors.ErrInsufficientFunds.ABCICode(), | ||
| Log: fmt.Sprintf("insufficient funds for gas * price + value: address %s have %v want %v: insufficient funds", sender.Hex(), senderBalance, balanceCheck), | ||
| }, | ||
| bumpNonce: bumpNonce, | ||
| currentNonce: currentNonce, | ||
| baseFee: baseFee, | ||
| baseFee: baseFee, | ||
| } | ||
| } | ||
|
|
||
| // All checks passed | ||
| return gigaValidationResult{ | ||
|
bdchatham marked this conversation as resolved.
|
||
| err: nil, | ||
| bumpNonce: bumpNonce, | ||
| currentNonce: currentNonce, | ||
| baseFee: baseFee, | ||
| err: nil, | ||
| baseFee: baseFee, | ||
| } | ||
| } | ||
|
|
||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.