Skip to content
111 changes: 92 additions & 19 deletions ante/evm/09_increment_sequence.go
Original file line number Diff line number Diff line change
@@ -1,38 +1,57 @@
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"
"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 {
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 := md.accountKeeper.UnorderedTransactionsEnabled()

fmt.Printf("\n\n[DEBUG] EVM IncrementNonce handler")
fmt.Printf("[DEBUG] nonce: %v, isUnordered: %v, unorderedEnabled: %v\n\n\n", txNonce, isUnordered, unorderedEnabled)
Comment on lines +26 to +27

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: remove debug


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 := md.verifyUnorderedNonce(ctx, account, 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.
Expand All @@ -49,6 +68,60 @@ func IncrementNonce(
return errorsmod.Wrapf(err, "failed to set sequence to %d", accountNonce)
}

accountKeeper.SetAccount(ctx, account)
md.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 (md MonoDecorator) verifyUnorderedNonce(ctx sdk.Context, account sdk.AccountI, 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(md.maxTxTimeoutDuration)) {
return errorsmod.Wrapf(
sdkerrors.ErrInvalidRequest,
"unordered tx ttl exceeds %s",
md.maxTxTimeoutDuration.String(),
)
}

ctx.GasMeter().ConsumeGas(md.unorderedTxGasCost, "unordered tx")

execMode := ctx.ExecMode()
if execMode == sdk.ExecModeSimulate {
return nil
}

err := md.accountKeeper.TryAddUnorderedNonce(
ctx,
account.GetAddress().Bytes(),
unorderedTx.GetTimeoutTimeStamp(),
)
if err != nil {
return errorsmod.Wrapf(sdkerrors.ErrInvalidRequest, "failed to add unordered nonce: %s", err)
}

return nil
}
73 changes: 52 additions & 21 deletions ante/evm/mono_decorator.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Expand All @@ -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
Expand All @@ -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.
Expand Down Expand Up @@ -258,7 +289,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 := md.IncrementNonce(ctx, acc, tx, ethTx.Nonce()); err != nil {
return ctx, err
}

Expand Down