Skip to content
Open
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
95 changes: 82 additions & 13 deletions ante/evm/09_increment_sequence.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,36 +3,51 @@ package evm
import (
"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 {
utx, ok := tx.(sdk.TxWithUnordered)
isUnordered := ok && utx.GetUnordered()
unorderedEnabled := md.accountKeeper.UnorderedTransactionsEnabled()

if isUnordered && !unorderedEnabled {
return errorsmod.Wrap(sdkerrors.ErrNotSupported, "unordered transactions are not enabled")
}

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 {

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,
)
}
return errorsmod.Wrapf(
errortypes.ErrInvalidSequence,
"invalid nonce; got %d, expected %d", txNonce, accountNonce,
)

if txNonce < accountNonce {
return errorsmod.Wrapf(
errortypes.ErrInvalidSequence,
"invalid nonce; got %d, expected %d", txNonce, accountNonce,
)
}
}

// EIP-2681 / state safety: refuse to overflow beyond 2^64-1.
Expand All @@ -49,6 +64,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
}
65 changes: 48 additions & 17 deletions ante/evm/mono_decorator.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,51 @@ package evm

import (
"math/big"

"github.com/ethereum/go-ethereum/common"
ethtypes "github.com/ethereum/go-ethereum/core/types"

anteinterfaces "github.com/cosmos/evm/ante/interfaces"
evmkeeper "github.com/cosmos/evm/x/vm/keeper"
evmtypes "github.com/cosmos/evm/x/vm/types"
"time"

errorsmod "cosmossdk.io/errors"
sdkmath "cosmossdk.io/math"
"github.com/ethereum/go-ethereum/common"
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"
evmkeeper "github.com/cosmos/evm/x/vm/keeper"
evmtypes "github.com/cosmos/evm/x/vm/types"
)

// 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
accountKeeper anteinterfaces.AccountKeeper
feeMarketKeeper anteinterfaces.FeeMarketKeeper
evmKeeper anteinterfaces.EVMKeeper
maxGasWanted uint64
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 @@ -38,13 +60,22 @@ func NewEVMMonoDecorator(
feeMarketKeeper anteinterfaces.FeeMarketKeeper,
evmKeeper anteinterfaces.EVMKeeper,
maxGasWanted uint64,
opts ...MonoDecoratorOption,
) MonoDecorator {
return MonoDecorator{
accountKeeper: accountKeeper,
feeMarketKeeper: feeMarketKeeper,
evmKeeper: evmKeeper,
maxGasWanted: maxGasWanted,
md := MonoDecorator{
accountKeeper: accountKeeper,
feeMarketKeeper: feeMarketKeeper,
evmKeeper: evmKeeper,
maxGasWanted: maxGasWanted,
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 @@ -226,7 +257,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
15 changes: 8 additions & 7 deletions ante/evm/nonce_limit_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,12 @@ import (
"testing"
"time"

"github.com/stretchr/testify/require"

evmante "github.com/cosmos/evm/ante/evm"

addresscodec "cosmossdk.io/core/address"
"github.com/stretchr/testify/require"

sdk "github.com/cosmos/cosmos-sdk/types"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
evmante "github.com/cosmos/evm/ante/evm"
)

// --- minimal codec to satisfy addresscodec.Codec (not used by these tests) ---
Expand Down Expand Up @@ -54,8 +52,9 @@ func TestIncrementNonce_HappyPath(t *testing.T) {
var ctx sdk.Context
ak := &mockAccountKeeper{}
acc := baseAcc(7)
md := evmante.NewEVMMonoDecorator(ak, nil, nil, 0)

err := evmante.IncrementNonce(ctx, ak, acc, 7)
err := md.IncrementNonce(ctx, acc, nil, 7)
require.NoError(t, err)
require.Equal(t, uint64(8), acc.GetSequence())
require.Equal(t, acc, ak.last) // SetAccount called
Expand All @@ -65,8 +64,9 @@ func TestIncrementNonce_NonceMismatch(t *testing.T) {
var ctx sdk.Context
ak := &mockAccountKeeper{}
acc := baseAcc(10)
md := evmante.NewEVMMonoDecorator(ak, nil, nil, 0)

err := evmante.IncrementNonce(ctx, ak, acc, 9)
err := md.IncrementNonce(ctx, acc, nil, 9)
require.Error(t, err)
require.Contains(t, err.Error(), "invalid nonce")
require.Equal(t, uint64(10), acc.GetSequence()) // unchanged
Expand All @@ -76,8 +76,9 @@ func TestIncrementNonce_OverflowGuard(t *testing.T) {
var ctx sdk.Context
ak := &mockAccountKeeper{}
acc := baseAcc(math.MaxUint64)
md := evmante.NewEVMMonoDecorator(ak, nil, nil, 0)

err := evmante.IncrementNonce(ctx, ak, acc, math.MaxUint64)
err := md.IncrementNonce(ctx, acc, nil, math.MaxUint64)
require.Error(t, err)
require.Contains(t, err.Error(), "overflow")
require.Equal(t, uint64(math.MaxUint64), acc.GetSequence()) // unchanged
Expand Down
11 changes: 9 additions & 2 deletions tests/integration/ante/test_evm_unit_09_increment_sequence.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,13 @@ func (s *EvmUnitAnteTestSuite) TestIncrementSequence() {
},
}

md := evm.NewEVMMonoDecorator(
unitNetwork.App.GetAccountKeeper(),
unitNetwork.App.GetFeeMarketKeeper(),
unitNetwork.App.GetEVMKeeper(),
0,
)

for _, tc := range testCases {
s.Run(tc.name, func() {
account, err := grpcHandler.GetAccount(accAddr.String())
Expand All @@ -65,10 +72,10 @@ func (s *EvmUnitAnteTestSuite) TestIncrementSequence() {
nonce := tc.malleate(account)

// Function under test
err = evm.IncrementNonce(
err = md.IncrementNonce(
unitNetwork.GetContext(),
unitNetwork.App.GetAccountKeeper(),
account,
nil,
nonce,
)

Expand Down
21 changes: 11 additions & 10 deletions x/vm/client/cli/tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,21 @@ import (
"os"
"strings"

"cosmossdk.io/core/address"
"github.com/ethereum/go-ethereum/common/hexutil"
ethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/pkg/errors"
"github.com/spf13/cobra"

"github.com/cosmos/evm/utils"
"github.com/cosmos/evm/x/vm/types"

"cosmossdk.io/core/address"

"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/flags"
"github.com/cosmos/cosmos-sdk/client/input"
"github.com/cosmos/cosmos-sdk/client/tx"
sdk "github.com/cosmos/cosmos-sdk/types"
errortypes "github.com/cosmos/cosmos-sdk/types/errors"
types2 "github.com/cosmos/cosmos-sdk/x/bank/types"
"github.com/cosmos/evm/utils"
"github.com/cosmos/evm/x/vm/types"
)

// NewTxCmd returns a root CLI command handler for evm module transaction commands
Expand Down Expand Up @@ -74,15 +72,18 @@ func NewRawTxCmd() *cobra.Command {
return err
}

baseDenom := types.GetEVMCoinDenom()
txf, err := tx.NewFactoryCLI(clientCtx, cmd.Flags())
if err != nil {
return err
}

tx, err := msg.BuildTx(clientCtx.TxConfig.NewTxBuilder(), baseDenom)
signingTx, err := msg.BuildTxWithFactory(clientCtx.TxConfig.NewTxBuilder(), txf, types.GetEVMCoinDenom())
if err != nil {
return err
}

if clientCtx.GenerateOnly {
json, err := clientCtx.TxConfig.TxJSONEncoder()(tx)
json, err := clientCtx.TxConfig.TxJSONEncoder()(signingTx)
if err != nil {
return err
}
Expand All @@ -91,7 +92,7 @@ func NewRawTxCmd() *cobra.Command {
}

if !clientCtx.SkipConfirm {
out, err := clientCtx.TxConfig.TxJSONEncoder()(tx)
out, err := clientCtx.TxConfig.TxJSONEncoder()(signingTx)
if err != nil {
return err
}
Expand All @@ -107,7 +108,7 @@ func NewRawTxCmd() *cobra.Command {
}
}

txBytes, err := clientCtx.TxConfig.TxEncoder()(tx)
txBytes, err := clientCtx.TxConfig.TxEncoder()(signingTx)
if err != nil {
return err
}
Expand Down
Loading