From 4aad38c1efc822276434be692aeab3562838b912 Mon Sep 17 00:00:00 2001 From: Aleksandr Bezobchuk Date: Tue, 30 Sep 2025 17:06:30 -0700 Subject: [PATCH 1/8] fix: update IncrementNonce function signature in MonoDecorator --- ante/evm/09_increment_sequence.go | 112 +++++++++++++++++++++++++----- ante/evm/mono_decorator.go | 2 +- 2 files changed, 97 insertions(+), 17 deletions(-) diff --git a/ante/evm/09_increment_sequence.go b/ante/evm/09_increment_sequence.go index fe28cf896..bbe8f2117 100644 --- a/ante/evm/09_increment_sequence.go +++ b/ante/evm/09_increment_sequence.go @@ -1,15 +1,16 @@ package evm import ( + "fmt" "math" - anteinterfaces "github.com/cosmos/evm/ante/interfaces" - "github.com/cosmos/evm/mempool" - errorsmod "cosmossdk.io/errors" sdk "github.com/cosmos/cosmos-sdk/types" errortypes "github.com/cosmos/cosmos-sdk/types/errors" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + anteinterfaces "github.com/cosmos/evm/ante/interfaces" + "github.com/cosmos/evm/mempool" ) // IncrementNonce increments the sequence of the account. @@ -17,22 +18,42 @@ func IncrementNonce( ctx sdk.Context, accountKeeper anteinterfaces.AccountKeeper, account sdk.AccountI, + tx sdk.Tx, txNonce uint64, ) error { - accountNonce := account.GetSequence() - // we merged the accountNonce verification to accountNonce increment, so when tx includes multiple messages - // with same sender, they'll be accepted. - if txNonce > accountNonce { - return errorsmod.Wrapf( - mempool.ErrNonceGap, - "tx nonce: %d, account accountNonce: %d", txNonce, accountNonce, - ) + utx, ok := tx.(sdk.TxWithUnordered) + isUnordered := ok && utx.GetUnordered() + unorderedEnabled := accountKeeper.UnorderedTransactionsEnabled() + + fmt.Println("[DEBUG] EVM IncrementNonce handler") + fmt.Printf("[DEBUG] nonce: %v, isUnordered: %v, unorderedEnabled: %v\n", txNonce, isUnordered, unorderedEnabled) + + if isUnordered && !unorderedEnabled { + return errorsmod.Wrap(sdkerrors.ErrNotSupported, "unordered transactions are not enabled") } - if txNonce < accountNonce { - return errorsmod.Wrapf( - mempool.ErrNonceLow, - "invalid nonce; got %d, expected %d", txNonce, accountNonce, - ) + + accountNonce := account.GetSequence() + + if isUnordered { + if err := verifyUnorderedNonce(ctx, utx); err != nil { + return err + } + } else { + // We've merged the accountNonce verification to accountNonce increment, so + // when tx includes multiple messages with same sender, they'll be accepted. + if txNonce > accountNonce { + return errorsmod.Wrapf( + mempool.ErrNonceGap, + "tx nonce: %d, account accountNonce: %d", txNonce, accountNonce, + ) + } + + if txNonce < accountNonce { + return errorsmod.Wrapf( + mempool.ErrNonceLow, + "invalid nonce; got %d, expected %d", txNonce, accountNonce, + ) + } } // EIP-2681 / state safety: refuse to overflow beyond 2^64-1. @@ -52,3 +73,62 @@ func IncrementNonce( accountKeeper.SetAccount(ctx, account) return nil } + +// verifyUnorderedNonce verifies the unordered nonce of an unordered transaction. +// This checks that: +// 1. The unordered transaction's timeout timestamp is set. +// 2. The unordered transaction's timeout timestamp is not in the past. +// 3. The unordered transaction's timeout timestamp is not more than the max TTL. +// 4. The unordered transaction's nonce has not been used previously. +// +// If all the checks above pass, the nonce is marked as used for each signer of +// the transaction. +func verifyUnorderedNonce(ctx sdk.Context, unorderedTx sdk.TxWithUnordered) error { + blockTime := ctx.BlockTime() + timeoutTimestamp := unorderedTx.GetTimeoutTimeStamp() + + if timeoutTimestamp.IsZero() || timeoutTimestamp.Unix() == 0 { + return errorsmod.Wrap( + sdkerrors.ErrInvalidRequest, + "unordered transaction must have timeout_timestamp set", + ) + } + + if timeoutTimestamp.Before(blockTime) { + return errorsmod.Wrap( + sdkerrors.ErrInvalidRequest, + "unordered transaction has a timeout_timestamp that has already passed", + ) + } + + if timeoutTimestamp.After(blockTime.Add(svd.maxTxTimeoutDuration)) { + return errorsmod.Wrapf( + sdkerrors.ErrInvalidRequest, + "unordered tx ttl exceeds %s", + svd.maxTxTimeoutDuration.String(), + ) + } + + ctx.GasMeter().ConsumeGas(svd.unorderedTxGasCost, "unordered tx") + + execMode := ctx.ExecMode() + if execMode == sdk.ExecModeSimulate { + return nil + } + + signerAddrs, err := extractSignersBytes(unorderedTx) + if err != nil { + return err + } + + for _, signerAddr := range signerAddrs { + if err := svd.ak.TryAddUnorderedNonce(ctx, signerAddr, unorderedTx.GetTimeoutTimeStamp()); err != nil { + return errorsmod.Wrapf( + sdkerrors.ErrInvalidRequest, + "failed to add unordered nonce: %s", err, + ) + } + } + + return nil +} diff --git a/ante/evm/mono_decorator.go b/ante/evm/mono_decorator.go index 8c632ebdc..41b8d3dac 100644 --- a/ante/evm/mono_decorator.go +++ b/ante/evm/mono_decorator.go @@ -258,7 +258,7 @@ func (md MonoDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, ne ) } - if err := IncrementNonce(ctx, md.accountKeeper, acc, ethTx.Nonce()); err != nil { + if err := IncrementNonce(ctx, md.accountKeeper, acc, tx, ethTx.Nonce()); err != nil { return ctx, err } From f72bd2f4ed9ac3964c37b8bb0ac2b62c6f7d3f95 Mon Sep 17 00:00:00 2001 From: Aleksandr Bezobchuk Date: Tue, 30 Sep 2025 18:10:14 -0700 Subject: [PATCH 2/8] fix: refactor IncrementNonce to use MonoDecorator's accountKeeper --- ante/evm/09_increment_sequence.go | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/ante/evm/09_increment_sequence.go b/ante/evm/09_increment_sequence.go index bbe8f2117..845f2237f 100644 --- a/ante/evm/09_increment_sequence.go +++ b/ante/evm/09_increment_sequence.go @@ -9,21 +9,19 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" errortypes "github.com/cosmos/cosmos-sdk/types/errors" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" - anteinterfaces "github.com/cosmos/evm/ante/interfaces" "github.com/cosmos/evm/mempool" ) // IncrementNonce increments the sequence of the account. -func IncrementNonce( +func (md MonoDecorator) IncrementNonce( ctx sdk.Context, - accountKeeper anteinterfaces.AccountKeeper, account sdk.AccountI, tx sdk.Tx, txNonce uint64, ) error { utx, ok := tx.(sdk.TxWithUnordered) isUnordered := ok && utx.GetUnordered() - unorderedEnabled := accountKeeper.UnorderedTransactionsEnabled() + unorderedEnabled := md.accountKeeper.UnorderedTransactionsEnabled() fmt.Println("[DEBUG] EVM IncrementNonce handler") fmt.Printf("[DEBUG] nonce: %v, isUnordered: %v, unorderedEnabled: %v\n", txNonce, isUnordered, unorderedEnabled) @@ -70,7 +68,7 @@ func IncrementNonce( return errorsmod.Wrapf(err, "failed to set sequence to %d", accountNonce) } - accountKeeper.SetAccount(ctx, account) + md.accountKeeper.SetAccount(ctx, account) return nil } From 84d64feb7db3c7a03ffb38d3345dcf692ad43c9e Mon Sep 17 00:00:00 2001 From: Aleksandr Bezobchuk Date: Tue, 30 Sep 2025 18:18:50 -0700 Subject: [PATCH 3/8] fix: update verifyUnorderedNonce method to use receiver for MonoDecorator --- ante/evm/09_increment_sequence.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ante/evm/09_increment_sequence.go b/ante/evm/09_increment_sequence.go index 845f2237f..989a7fccd 100644 --- a/ante/evm/09_increment_sequence.go +++ b/ante/evm/09_increment_sequence.go @@ -33,7 +33,7 @@ func (md MonoDecorator) IncrementNonce( accountNonce := account.GetSequence() if isUnordered { - if err := verifyUnorderedNonce(ctx, utx); err != nil { + if err := md.verifyUnorderedNonce(ctx, utx); err != nil { return err } } else { @@ -81,7 +81,7 @@ func (md MonoDecorator) IncrementNonce( // // If all the checks above pass, the nonce is marked as used for each signer of // the transaction. -func verifyUnorderedNonce(ctx sdk.Context, unorderedTx sdk.TxWithUnordered) error { +func (md MonoDecorator) verifyUnorderedNonce(ctx sdk.Context, unorderedTx sdk.TxWithUnordered) error { blockTime := ctx.BlockTime() timeoutTimestamp := unorderedTx.GetTimeoutTimeStamp() @@ -99,15 +99,15 @@ func verifyUnorderedNonce(ctx sdk.Context, unorderedTx sdk.TxWithUnordered) erro ) } - if timeoutTimestamp.After(blockTime.Add(svd.maxTxTimeoutDuration)) { + if timeoutTimestamp.After(blockTime.Add(md.maxTxTimeoutDuration)) { return errorsmod.Wrapf( sdkerrors.ErrInvalidRequest, "unordered tx ttl exceeds %s", - svd.maxTxTimeoutDuration.String(), + md.maxTxTimeoutDuration.String(), ) } - ctx.GasMeter().ConsumeGas(svd.unorderedTxGasCost, "unordered tx") + ctx.GasMeter().ConsumeGas(md.unorderedTxGasCost, "unordered tx") execMode := ctx.ExecMode() if execMode == sdk.ExecModeSimulate { From 317a4a166f89be0045b1c426920525b147ea74be Mon Sep 17 00:00:00 2001 From: Aleksandr Bezobchuk Date: Tue, 30 Sep 2025 18:19:33 -0700 Subject: [PATCH 4/8] fix: update verifyUnorderedNonce to use accountKeeper for nonce management --- ante/evm/09_increment_sequence.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ante/evm/09_increment_sequence.go b/ante/evm/09_increment_sequence.go index 989a7fccd..e2a6f403f 100644 --- a/ante/evm/09_increment_sequence.go +++ b/ante/evm/09_increment_sequence.go @@ -120,7 +120,7 @@ func (md MonoDecorator) verifyUnorderedNonce(ctx sdk.Context, unorderedTx sdk.Tx } for _, signerAddr := range signerAddrs { - if err := svd.ak.TryAddUnorderedNonce(ctx, signerAddr, unorderedTx.GetTimeoutTimeStamp()); err != nil { + if err := md.accountKeeper.TryAddUnorderedNonce(ctx, signerAddr, unorderedTx.GetTimeoutTimeStamp()); err != nil { return errorsmod.Wrapf( sdkerrors.ErrInvalidRequest, "failed to add unordered nonce: %s", err, From a009cc8587ff8320ee7318376a3626ac6ffc9032 Mon Sep 17 00:00:00 2001 From: Aleksandr Bezobchuk Date: Tue, 30 Sep 2025 18:19:51 -0700 Subject: [PATCH 5/8] fix: update AnteHandle to use IncrementNonce method from MonoDecorator --- ante/evm/mono_decorator.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ante/evm/mono_decorator.go b/ante/evm/mono_decorator.go index 41b8d3dac..40f1d35e8 100644 --- a/ante/evm/mono_decorator.go +++ b/ante/evm/mono_decorator.go @@ -258,7 +258,7 @@ func (md MonoDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, ne ) } - if err := IncrementNonce(ctx, md.accountKeeper, acc, tx, ethTx.Nonce()); err != nil { + if err := md.IncrementNonce(ctx, acc, tx, ethTx.Nonce()); err != nil { return ctx, err } From 20404c42cd6df7265bb0316c55db23ef3ccb5265 Mon Sep 17 00:00:00 2001 From: Aleksandr Bezobchuk Date: Wed, 1 Oct 2025 14:33:10 -0700 Subject: [PATCH 6/8] fix: reorganize imports and enhance MonoDecorator with timeout and gas cost options --- ante/evm/mono_decorator.go | 71 +++++++++++++++++++++++++++----------- 1 file changed, 51 insertions(+), 20 deletions(-) diff --git a/ante/evm/mono_decorator.go b/ante/evm/mono_decorator.go index 40f1d35e8..5b93ac25f 100644 --- a/ante/evm/mono_decorator.go +++ b/ante/evm/mono_decorator.go @@ -3,22 +3,22 @@ package evm import ( "math" "math/big" + "time" + errorsmod "cosmossdk.io/errors" + sdkmath "cosmossdk.io/math" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/txpool" ethtypes "github.com/ethereum/go-ethereum/core/types" + sdk "github.com/cosmos/cosmos-sdk/types" + errortypes "github.com/cosmos/cosmos-sdk/types/errors" + txtypes "github.com/cosmos/cosmos-sdk/types/tx" + authante "github.com/cosmos/cosmos-sdk/x/auth/ante" anteinterfaces "github.com/cosmos/evm/ante/interfaces" feemarkettypes "github.com/cosmos/evm/x/feemarket/types" evmkeeper "github.com/cosmos/evm/x/vm/keeper" evmtypes "github.com/cosmos/evm/x/vm/types" - - errorsmod "cosmossdk.io/errors" - sdkmath "cosmossdk.io/math" - - sdk "github.com/cosmos/cosmos-sdk/types" - errortypes "github.com/cosmos/cosmos-sdk/types/errors" - txtypes "github.com/cosmos/cosmos-sdk/types/tx" ) const AcceptedTxType = 0 | @@ -30,12 +30,34 @@ const AcceptedTxType = 0 | // MonoDecorator is a single decorator that handles all the prechecks for // ethereum transactions. type MonoDecorator struct { - accountKeeper anteinterfaces.AccountKeeper - feeMarketKeeper anteinterfaces.FeeMarketKeeper - evmKeeper anteinterfaces.EVMKeeper - maxGasWanted uint64 - evmParams *evmtypes.Params - feemarketParams *feemarkettypes.Params + accountKeeper anteinterfaces.AccountKeeper + feeMarketKeeper anteinterfaces.FeeMarketKeeper + evmKeeper anteinterfaces.EVMKeeper + maxGasWanted uint64 + evmParams *evmtypes.Params + feemarketParams *feemarkettypes.Params + maxTxTimeoutDuration time.Duration + unorderedTxGasCost uint64 +} + +type MonoDecoratorOption func(*MonoDecorator) + +// WithMaxUnorderedTxTimeoutDuration sets the maximum TTL a transaction can define +// for unordered transactions. +func WithMaxUnorderedTxTimeoutDuration(duration time.Duration) MonoDecoratorOption { + return func(md *MonoDecorator) { + md.maxTxTimeoutDuration = duration + } +} + +// WithUnorderedTxGasCost sets the gas cost for unordered transactions. +// We must charge extra gas for unordered transactions +// as they incur extra processing time for cleaning up the expired txs in x/auth PreBlocker. +// Note: this value was chosen by 2x-ing the cost of fetching and removing an unordered nonce entry. +func WithUnorderedTxGasCost(gasCost uint64) MonoDecoratorOption { + return func(md *MonoDecorator) { + md.unorderedTxGasCost = gasCost + } } // NewEVMMonoDecorator creates the 'mono' decorator, that is used to run the ante handle logic @@ -51,15 +73,24 @@ func NewEVMMonoDecorator( maxGasWanted uint64, evmParams *evmtypes.Params, feemarketParams *feemarkettypes.Params, + opts ...MonoDecoratorOption, ) MonoDecorator { - return MonoDecorator{ - accountKeeper: accountKeeper, - feeMarketKeeper: feeMarketKeeper, - evmKeeper: evmKeeper, - maxGasWanted: maxGasWanted, - evmParams: evmParams, - feemarketParams: feemarketParams, + md := MonoDecorator{ + accountKeeper: accountKeeper, + feeMarketKeeper: feeMarketKeeper, + evmKeeper: evmKeeper, + maxGasWanted: maxGasWanted, + evmParams: evmParams, + feemarketParams: feemarketParams, + maxTxTimeoutDuration: authante.DefaultMaxTimeoutDuration, + unorderedTxGasCost: authante.DefaultUnorderedTxGasCost, } + + for _, opt := range opts { + opt(&md) + } + + return md } // AnteHandle handles the entire decorator chain using a mono decorator. From c1d3f8243d2b755860d825550b1c1a9e973c9f33 Mon Sep 17 00:00:00 2001 From: Aleksandr Bezobchuk Date: Thu, 2 Oct 2025 09:27:41 -0700 Subject: [PATCH 7/8] fix: update verifyUnorderedNonce to include account parameter for nonce management --- ante/evm/09_increment_sequence.go | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/ante/evm/09_increment_sequence.go b/ante/evm/09_increment_sequence.go index e2a6f403f..947b7040b 100644 --- a/ante/evm/09_increment_sequence.go +++ b/ante/evm/09_increment_sequence.go @@ -33,7 +33,7 @@ func (md MonoDecorator) IncrementNonce( accountNonce := account.GetSequence() if isUnordered { - if err := md.verifyUnorderedNonce(ctx, utx); err != nil { + if err := md.verifyUnorderedNonce(ctx, account, utx); err != nil { return err } } else { @@ -81,7 +81,7 @@ func (md MonoDecorator) IncrementNonce( // // If all the checks above pass, the nonce is marked as used for each signer of // the transaction. -func (md MonoDecorator) verifyUnorderedNonce(ctx sdk.Context, unorderedTx sdk.TxWithUnordered) error { +func (md MonoDecorator) verifyUnorderedNonce(ctx sdk.Context, account sdk.AccountI, unorderedTx sdk.TxWithUnordered) error { blockTime := ctx.BlockTime() timeoutTimestamp := unorderedTx.GetTimeoutTimeStamp() @@ -114,18 +114,13 @@ func (md MonoDecorator) verifyUnorderedNonce(ctx sdk.Context, unorderedTx sdk.Tx return nil } - signerAddrs, err := extractSignersBytes(unorderedTx) + err := md.accountKeeper.TryAddUnorderedNonce( + ctx, + account.GetAddress().Bytes(), + unorderedTx.GetTimeoutTimeStamp(), + ) if err != nil { - return err - } - - for _, signerAddr := range signerAddrs { - if err := md.accountKeeper.TryAddUnorderedNonce(ctx, signerAddr, unorderedTx.GetTimeoutTimeStamp()); err != nil { - return errorsmod.Wrapf( - sdkerrors.ErrInvalidRequest, - "failed to add unordered nonce: %s", err, - ) - } + return errorsmod.Wrapf(sdkerrors.ErrInvalidRequest, "failed to add unordered nonce: %s", err) } return nil From c1a0a5ddbdde20c35a28d1f77529205361a3cc10 Mon Sep 17 00:00:00 2001 From: Aleksandr Bezobchuk Date: Thu, 2 Oct 2025 10:05:55 -0700 Subject: [PATCH 8/8] fix: improve debug output formatting in IncrementNonce method --- ante/evm/09_increment_sequence.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ante/evm/09_increment_sequence.go b/ante/evm/09_increment_sequence.go index 947b7040b..f4ae257d3 100644 --- a/ante/evm/09_increment_sequence.go +++ b/ante/evm/09_increment_sequence.go @@ -23,8 +23,8 @@ func (md MonoDecorator) IncrementNonce( isUnordered := ok && utx.GetUnordered() unorderedEnabled := md.accountKeeper.UnorderedTransactionsEnabled() - fmt.Println("[DEBUG] EVM IncrementNonce handler") - fmt.Printf("[DEBUG] nonce: %v, isUnordered: %v, unorderedEnabled: %v\n", txNonce, isUnordered, unorderedEnabled) + fmt.Printf("\n\n[DEBUG] EVM IncrementNonce handler") + fmt.Printf("[DEBUG] nonce: %v, isUnordered: %v, unorderedEnabled: %v\n\n\n", txNonce, isUnordered, unorderedEnabled) if isUnordered && !unorderedEnabled { return errorsmod.Wrap(sdkerrors.ErrNotSupported, "unordered transactions are not enabled")