Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
134 changes: 70 additions & 64 deletions app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Comment thread
bdchatham marked this conversation as resolved.
Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -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
Comment thread
bdchatham marked this conversation as resolved.
Comment thread
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) {
Expand Down Expand Up @@ -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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The 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 {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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",
}
}
Expand Down Expand Up @@ -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.
Expand All @@ -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 {
Expand All @@ -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,
}
}

Expand All @@ -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,
}
}

Expand All @@ -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 {
Expand All @@ -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)
Expand All @@ -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,
}
}

Expand All @@ -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,
}
}
}
Expand All @@ -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,
}
}

Expand All @@ -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,
}
}

Expand All @@ -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,
}
}

Expand All @@ -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
Expand All @@ -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,
}
}
}
Expand Down Expand Up @@ -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{
Comment thread
bdchatham marked this conversation as resolved.
err: nil,
bumpNonce: bumpNonce,
currentNonce: currentNonce,
baseFee: baseFee,
err: nil,
baseFee: baseFee,
}
}

Expand Down
9 changes: 1 addition & 8 deletions app/test_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,10 +141,7 @@ func NewTestWrapperWithSc(t *testing.T, tm time.Time, valPub cryptotypes.PubKey,
}

func NewGigaTestWrapper(t *testing.T, tm time.Time, valPub cryptotypes.PubKey, enableEVMCustomPrecompiles bool, useOcc bool, baseAppOptions ...func(*bam.BaseApp)) *TestWrapper {
wrapper := newTestWrapper(t, tm, valPub, enableEVMCustomPrecompiles, true, TestAppOpts{UseSc: true, EnableGiga: true, EnableGigaOCC: useOcc}, baseAppOptions...)
genState := evmtypes.DefaultGenesis()
wrapper.App.EvmKeeper.InitGenesis(wrapper.Ctx, *genState)
return wrapper
return newTestWrapper(t, tm, valPub, enableEVMCustomPrecompiles, true, TestAppOpts{UseSc: true, EnableGiga: true, EnableGigaOCC: useOcc}, baseAppOptions...)
}
Comment thread
bdchatham marked this conversation as resolved.

// NewGigaTestWrapperWithRegularStore creates a test wrapper that runs Giga executor
Expand Down Expand Up @@ -177,10 +174,6 @@ func NewGigaTestWrapperWithRegularStore(t *testing.T, tm time.Time, valPub crypt
}
}

// Init genesis for GigaEvmKeeper (now uses regular KVStore)
genState := evmtypes.DefaultGenesis()
wrapper.App.EvmKeeper.InitGenesis(wrapper.Ctx, *genState)

return wrapper
}

Expand Down
19 changes: 17 additions & 2 deletions giga/executor/utils/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,27 @@ import (

const (
// GigaAbortCodespace and GigaAbortCode identify a sentinel ResponseDeliverTx
// that signals the caller to fall back to the v2 execution path.
// that signals the caller to fall back to the v2 execution path. The
// response's Info field carries the abort cause for the fallback metric.
GigaAbortCodespace = "giga"
GigaAbortCode = uint32(1)
GigaAbortInfo = "giga_fallback_to_v2"
)

// ValidationFailedAbortError signals an EVM validation failure (fee/nonce/
// balance), whose canonical failure receipt only v2's ante chain can produce;
// the caller should fall back to v2.
type ValidationFailedAbortError struct{}

func (e *ValidationFailedAbortError) Error() string {
return "EVM validation failed; v2 produces the canonical failure receipt"
}

func (e *ValidationFailedAbortError) IsAbortError() bool {
return true
}

var ErrValidationFailed error = &ValidationFailedAbortError{}

// ShouldExecutionAbort checks if the given error is an AbortError that should
// cause Giga execution to abort and fall back to standard execution.
func ShouldExecutionAbort(err error) bool {
Expand Down
Loading
Loading