diff --git a/protocol/app/ante.go b/protocol/app/ante.go index 2319b2bf4c..1fd4210234 100644 --- a/protocol/app/ante.go +++ b/protocol/app/ante.go @@ -105,7 +105,7 @@ func NewAnteHandler(options HandlerOptions) (sdk.AnteHandler, error) { validateMemo: ante.NewValidateMemoDecorator(options.AccountKeeper), validateBasic: ante.NewValidateBasicDecorator(), validateSigCount: ante.NewValidateSigCountDecorator(options.AccountKeeper), - incrementSequence: ante.NewIncrementSequenceDecorator(options.AccountKeeper), + incrementSequence: customante.NewIncrementSequenceDecorator(options.AccountKeeper), sigVerification: customante.NewSigVerificationDecorator(options.AccountKeeper, options.SignModeHandler), consumeTxSizeGas: ante.NewConsumeGasForTxSizeDecorator(options.AccountKeeper), deductFee: ante.NewDeductFeeDecorator( @@ -140,7 +140,7 @@ type lockingAnteHandler struct { validateMemo ante.ValidateMemoDecorator validateBasic ante.ValidateBasicDecorator validateSigCount ante.ValidateSigCountDecorator - incrementSequence ante.IncrementSequenceDecorator + incrementSequence customante.IncrementSequenceDecorator sigVerification customante.SigVerificationDecorator consumeTxSizeGas ante.ConsumeTxSizeGasDecorator deductFee ante.DeductFeeDecorator diff --git a/protocol/app/ante/timestampnonce.go b/protocol/app/ante/timestampnonce.go new file mode 100644 index 0000000000..bd516324bb --- /dev/null +++ b/protocol/app/ante/timestampnonce.go @@ -0,0 +1,55 @@ +package ante + +import ( + errorsmod "cosmossdk.io/errors" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + ante "github.com/cosmos/cosmos-sdk/x/auth/ante" + authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing" + accountpluskeeper "github.com/dydxprotocol/v4-chain/protocol/x/accountplus/keeper" +) + +// TODO: combine increment sequence and sequence verification into one decorator +// https://github.com/cosmos/cosmos-sdk/pull/18817 +type IncrementSequenceDecorator struct { + ak ante.AccountKeeper +} + +func NewIncrementSequenceDecorator(ak ante.AccountKeeper) IncrementSequenceDecorator { + return IncrementSequenceDecorator{ + ak: ak, + } +} + +func (isd IncrementSequenceDecorator) AnteHandle( + ctx sdk.Context, + tx sdk.Tx, + simulate bool, + next sdk.AnteHandler, +) (sdk.Context, error) { + sigTx, ok := tx.(authsigning.SigVerifiableTx) + if !ok { + return ctx, errorsmod.Wrap(sdkerrors.ErrTxDecode, "invalid transaction type") + } + + signatures, err := sigTx.GetSignaturesV2() + if err != nil { + return sdk.Context{}, err + } + + for _, signature := range signatures { + if accountpluskeeper.IsTimestampNonce(signature.Sequence) { + // Skip increment for this signature + continue + } + + acc := isd.ak.GetAccount(ctx, signature.PubKey.Address().Bytes()) + if err := acc.SetSequence(acc.GetSequence() + 1); err != nil { + panic(err) + } + + isd.ak.SetAccount(ctx, acc) + } + + return next(ctx, tx, simulate) +} diff --git a/protocol/app/ante/timestampnonce_test.go b/protocol/app/ante/timestampnonce_test.go new file mode 100644 index 0000000000..7c09b67703 --- /dev/null +++ b/protocol/app/ante/timestampnonce_test.go @@ -0,0 +1,75 @@ +package ante_test + +import ( + "testing" + + testante "github.com/dydxprotocol/v4-chain/protocol/testutil/ante" + "github.com/stretchr/testify/require" + + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + "github.com/cosmos/cosmos-sdk/testutil/testdata" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/tx/signing" + + customante "github.com/dydxprotocol/v4-chain/protocol/app/ante" + accountpluskeeper "github.com/dydxprotocol/v4-chain/protocol/x/accountplus/keeper" +) + +// Modified from cosmossdk test for IncrementSequenceDecorator +func TestDydxIncrementSequenceDecorator(t *testing.T) { + suite := testante.SetupTestSuite(t, true) + suite.TxBuilder = suite.ClientCtx.TxConfig.NewTxBuilder() + + priv, _, addr := testdata.KeyTestPubAddr() + acc := suite.AccountKeeper.NewAccountWithAddress(suite.Ctx, addr) + require.NoError(t, acc.SetAccountNumber(uint64(50))) + suite.AccountKeeper.SetAccount(suite.Ctx, acc) + + msgs := []sdk.Msg{testdata.NewTestMsg(addr)} + require.NoError(t, suite.TxBuilder.SetMsgs(msgs...)) + privs := []cryptotypes.PrivKey{priv} + accNums := []uint64{suite.AccountKeeper.GetAccount(suite.Ctx, addr).GetAccountNumber()} + feeAmount := testdata.NewTestFeeAmount() + gasLimit := testdata.NewTestGasLimit() + suite.TxBuilder.SetFeeAmount(feeAmount) + suite.TxBuilder.SetGasLimit(gasLimit) + + isd := customante.NewIncrementSequenceDecorator(suite.AccountKeeper) + antehandler := sdk.ChainAnteDecorators(isd) + + testCases := []struct { + ctx sdk.Context + simulate bool + // This value need not be valid (accountSeq + 1). Validity is handed in customante.NewSigVerificationDecorator + signatureSeq uint64 + expectedSeq uint64 + }{ + // tests from cosmossdk checking incrementing seqence + {suite.Ctx.WithIsReCheckTx(true), false, 0, 1}, + {suite.Ctx.WithIsCheckTx(true).WithIsReCheckTx(false), false, 0, 2}, + {suite.Ctx.WithIsReCheckTx(true), false, 0, 3}, + {suite.Ctx.WithIsReCheckTx(true), false, 0, 4}, + {suite.Ctx.WithIsReCheckTx(true), true, 0, 5}, + + // tests checking that tx with timestamp nonces will not increment sequence + {suite.Ctx.WithIsReCheckTx(true), true, accountpluskeeper.TimestampNonceSequenceCutoff, 5}, + {suite.Ctx.WithIsReCheckTx(true), true, accountpluskeeper.TimestampNonceSequenceCutoff + 100000, 5}, + } + + for i, tc := range testCases { + accSeqs := []uint64{tc.signatureSeq} + tx, err := suite.CreateTestTx( + suite.Ctx, + privs, + accNums, + accSeqs, + suite.Ctx.ChainID(), + signing.SignMode_SIGN_MODE_DIRECT, + ) + require.NoError(t, err) + + _, err = antehandler(tc.ctx, tx, tc.simulate) + require.NoError(t, err, "unexpected error; tc #%d, %v", i, tc) + require.Equal(t, tc.expectedSeq, suite.AccountKeeper.GetAccount(suite.Ctx, addr).GetSequence()) + } +} diff --git a/protocol/x/accountplus/keeper/keeper.go b/protocol/x/accountplus/keeper/keeper.go index 9f3ef67c43..3a9361b21b 100644 --- a/protocol/x/accountplus/keeper/keeper.go +++ b/protocol/x/accountplus/keeper/keeper.go @@ -73,6 +73,8 @@ func (k Keeper) SetGenesisState(ctx sdk.Context, data types.GenesisState) error return nil } +// TODO: refactor this function -> InitializeWithTimestampNonceDetails +// Writing to store is expensive so directly write with ts-nonce instead of initializing an empty account then setting. func (k Keeper) InitializeAccount(ctx sdk.Context, address sdk.AccAddress) error { if _, found := k.GetAccountState(ctx, address); found { return errors.New( diff --git a/protocol/x/accountplus/keeper/timestampnonce.go b/protocol/x/accountplus/keeper/timestampnonce.go index 957b854096..ececa48741 100644 --- a/protocol/x/accountplus/keeper/timestampnonce.go +++ b/protocol/x/accountplus/keeper/timestampnonce.go @@ -1,3 +1,7 @@ package keeper -func Placeholder() {} +const TimestampNonceSequenceCutoff uint64 = 1 << 40 // 2^40 + +func IsTimestampNonce(ts uint64) bool { + return ts >= TimestampNonceSequenceCutoff +} diff --git a/protocol/x/accountplus/keeper/timestampnonce_test.go b/protocol/x/accountplus/keeper/timestampnonce_test.go index e2d6fabe21..91a35c3bff 100644 --- a/protocol/x/accountplus/keeper/timestampnonce_test.go +++ b/protocol/x/accountplus/keeper/timestampnonce_test.go @@ -2,6 +2,13 @@ package keeper_test import ( "testing" + + "github.com/dydxprotocol/v4-chain/protocol/x/accountplus/keeper" + "github.com/stretchr/testify/require" ) -func Placeholder(t *testing.T) {} +func TestIsTimestampNonce(t *testing.T) { + require.True(t, keeper.IsTimestampNonce(keeper.TimestampNonceSequenceCutoff)) + require.True(t, keeper.IsTimestampNonce(keeper.TimestampNonceSequenceCutoff+uint64(1))) + require.False(t, keeper.IsTimestampNonce(keeper.TimestampNonceSequenceCutoff-uint64(1))) +}