From 47bafe46190a253947c83678810f5bd8093f3394 Mon Sep 17 00:00:00 2001 From: i-norden Date: Tue, 1 Mar 2022 08:20:37 -0600 Subject: [PATCH] port auth module here for old version of ante module --- x/auth/ante/ante.go | 58 ++ x/auth/ante/ante_test.go | 1151 +++++++++++++++++++++++ x/auth/ante/basic.go | 206 ++++ x/auth/ante/basic_test.go | 224 +++++ x/auth/ante/expected_keepers.go | 20 + x/auth/ante/ext.go | 36 + x/auth/ante/ext_test.go | 36 + x/auth/ante/fee.go | 141 +++ x/auth/ante/fee_test.go | 104 ++ x/auth/ante/feegrant_test.go | 233 +++++ x/auth/ante/setup.go | 76 ++ x/auth/ante/setup_test.go | 99 ++ x/auth/ante/sigverify.go | 510 ++++++++++ x/auth/ante/sigverify_benchmark_test.go | 44 + x/auth/ante/sigverify_test.go | 390 ++++++++ x/auth/ante/testutil_test.go | 200 ++++ 16 files changed, 3528 insertions(+) create mode 100644 x/auth/ante/ante.go create mode 100644 x/auth/ante/ante_test.go create mode 100644 x/auth/ante/basic.go create mode 100644 x/auth/ante/basic_test.go create mode 100644 x/auth/ante/expected_keepers.go create mode 100644 x/auth/ante/ext.go create mode 100644 x/auth/ante/ext_test.go create mode 100644 x/auth/ante/fee.go create mode 100644 x/auth/ante/fee_test.go create mode 100644 x/auth/ante/feegrant_test.go create mode 100644 x/auth/ante/setup.go create mode 100644 x/auth/ante/setup_test.go create mode 100644 x/auth/ante/sigverify.go create mode 100644 x/auth/ante/sigverify_benchmark_test.go create mode 100644 x/auth/ante/sigverify_test.go create mode 100644 x/auth/ante/testutil_test.go diff --git a/x/auth/ante/ante.go b/x/auth/ante/ante.go new file mode 100644 index 00000000..3b4aa6a5 --- /dev/null +++ b/x/auth/ante/ante.go @@ -0,0 +1,58 @@ +package ante + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + "github.com/cosmos/cosmos-sdk/types/tx/signing" + authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing" + "github.com/cosmos/cosmos-sdk/x/auth/types" +) + +// HandlerOptions are the options required for constructing a default SDK AnteHandler. +type HandlerOptions struct { + AccountKeeper AccountKeeper + BankKeeper types.BankKeeper + FeegrantKeeper FeegrantKeeper + SignModeHandler authsigning.SignModeHandler + SigGasConsumer func(meter sdk.GasMeter, sig signing.SignatureV2, params types.Params) error +} + +// NewAnteHandler returns an AnteHandler that checks and increments sequence +// numbers, checks signatures & account numbers, and deducts fees from the first +// signer. +func NewAnteHandler(options HandlerOptions) (sdk.AnteHandler, error) { + if options.AccountKeeper == nil { + return nil, sdkerrors.Wrap(sdkerrors.ErrLogic, "account keeper is required for ante builder") + } + + if options.BankKeeper == nil { + return nil, sdkerrors.Wrap(sdkerrors.ErrLogic, "bank keeper is required for ante builder") + } + + if options.SignModeHandler == nil { + return nil, sdkerrors.Wrap(sdkerrors.ErrLogic, "sign mode handler is required for ante builder") + } + + var sigGasConsumer = options.SigGasConsumer + if sigGasConsumer == nil { + sigGasConsumer = DefaultSigVerificationGasConsumer + } + + anteDecorators := []sdk.AnteDecorator{ + NewSetUpContextDecorator(), // outermost AnteDecorator. SetUpContext must be called first + NewRejectExtensionOptionsDecorator(), + NewMempoolFeeDecorator(), + NewValidateBasicDecorator(), + NewTxTimeoutHeightDecorator(), + NewValidateMemoDecorator(options.AccountKeeper), + NewConsumeGasForTxSizeDecorator(options.AccountKeeper), + NewDeductFeeDecorator(options.AccountKeeper, options.BankKeeper, options.FeegrantKeeper), + NewSetPubKeyDecorator(options.AccountKeeper), // SetPubKeyDecorator must be called before all signature verification decorators + NewValidateSigCountDecorator(options.AccountKeeper), + NewSigGasConsumeDecorator(options.AccountKeeper, sigGasConsumer), + NewSigVerificationDecorator(options.AccountKeeper, options.SignModeHandler), + NewIncrementSequenceDecorator(options.AccountKeeper), + } + + return sdk.ChainAnteDecorators(anteDecorators...), nil +} diff --git a/x/auth/ante/ante_test.go b/x/auth/ante/ante_test.go new file mode 100644 index 00000000..74a10d7d --- /dev/null +++ b/x/auth/ante/ante_test.go @@ -0,0 +1,1151 @@ +package ante_test +/* TODO: fix these tests +import ( + "encoding/json" + "errors" + "fmt" + "strings" + "testing" + + "github.com/cosmos/cosmos-sdk/simapp" + + minttypes "github.com/cosmos/cosmos-sdk/x/mint/types" + + "github.com/stretchr/testify/require" + + "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" + kmultisig "github.com/cosmos/cosmos-sdk/crypto/keys/multisig" + "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + "github.com/cosmos/cosmos-sdk/testutil/testdata" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + "github.com/cosmos/cosmos-sdk/types/tx/signing" + ante "github.com/tharsis/ethermint/x/auth/ante" + "github.com/cosmos/cosmos-sdk/x/auth/types" +) + +// Test that simulate transaction accurately estimates gas cost +func (suite *AnteTestSuite) TestSimulateGasCost() { + suite.SetupTest(false) // reset + + // Same data for every test cases + accounts := suite.CreateTestAccounts(3) + msgs := []sdk.Msg{ + testdata.NewTestMsg(accounts[0].acc.GetAddress(), accounts[1].acc.GetAddress()), + testdata.NewTestMsg(accounts[2].acc.GetAddress(), accounts[0].acc.GetAddress()), + testdata.NewTestMsg(accounts[1].acc.GetAddress(), accounts[2].acc.GetAddress()), + } + feeAmount := testdata.NewTestFeeAmount() + gasLimit := testdata.NewTestGasLimit() + accSeqs := []uint64{0, 0, 0} + privs := []cryptotypes.PrivKey{accounts[0].priv, accounts[1].priv, accounts[2].priv} + accNums := []uint64{0, 1, 2} + + testCases := []TestCase{ + { + "tx with 150atom fee", + func() { + suite.txBuilder.SetFeeAmount(feeAmount) + suite.txBuilder.SetGasLimit(gasLimit) + }, + true, + true, + nil, + }, + { + "with previously estimated gas", + func() { + simulatedGas := suite.ctx.GasMeter().GasConsumed() + + accSeqs = []uint64{1, 1, 1} + suite.txBuilder.SetFeeAmount(feeAmount) + suite.txBuilder.SetGasLimit(simulatedGas) + }, + false, + true, + nil, + }, + } + + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.desc), func() { + suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() + tc.malleate() + + suite.RunTestCase(privs, msgs, feeAmount, gasLimit, accNums, accSeqs, suite.ctx.ChainID(), tc) + }) + } +} + +// Test various error cases in the AnteHandler control flow. +func (suite *AnteTestSuite) TestAnteHandlerSigErrors() { + suite.SetupTest(false) // reset + + // Same data for every test cases + priv0, _, addr0 := testdata.KeyTestPubAddr() + priv1, _, addr1 := testdata.KeyTestPubAddr() + priv2, _, addr2 := testdata.KeyTestPubAddr() + msgs := []sdk.Msg{ + testdata.NewTestMsg(addr0, addr1), + testdata.NewTestMsg(addr0, addr2), + } + feeAmount := testdata.NewTestFeeAmount() + gasLimit := testdata.NewTestGasLimit() + + // Variable data per test case + var ( + privs []cryptotypes.PrivKey + accNums []uint64 + accSeqs []uint64 + ) + + testCases := []TestCase{ + { + "check no signatures fails", + func() { + privs, accNums, accSeqs = []cryptotypes.PrivKey{}, []uint64{}, []uint64{} + + // Create tx manually to test the tx's signers + suite.Require().NoError(suite.txBuilder.SetMsgs(msgs...)) + tx, err := suite.CreateTestTx(privs, accNums, accSeqs, suite.ctx.ChainID()) + suite.Require().NoError(err) + // tx.GetSigners returns addresses in correct order: addr1, addr2, addr3 + expectedSigners := []sdk.AccAddress{addr0, addr1, addr2} + suite.Require().Equal(expectedSigners, tx.GetSigners()) + }, + false, + false, + sdkerrors.ErrNoSignatures, + }, + { + "num sigs dont match GetSigners", + func() { + privs, accNums, accSeqs = []cryptotypes.PrivKey{priv0}, []uint64{0}, []uint64{0} + }, + false, + false, + sdkerrors.ErrUnauthorized, + }, + { + "unrecognized account", + func() { + privs, accNums, accSeqs = []cryptotypes.PrivKey{priv0, priv1, priv2}, []uint64{0, 1, 2}, []uint64{0, 0, 0} + }, + false, + false, + sdkerrors.ErrUnknownAddress, + }, + { + "save the first account, but second is still unrecognized", + func() { + acc1 := suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, addr0) + suite.app.AccountKeeper.SetAccount(suite.ctx, acc1) + err := suite.app.BankKeeper.MintCoins(suite.ctx, minttypes.ModuleName, feeAmount) + suite.Require().NoError(err) + err = suite.app.BankKeeper.SendCoinsFromModuleToAccount(suite.ctx, minttypes.ModuleName, addr0, feeAmount) + suite.Require().NoError(err) + }, + false, + false, + sdkerrors.ErrUnknownAddress, + }, + } + + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.desc), func() { + suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() + tc.malleate() + + suite.RunTestCase(privs, msgs, feeAmount, gasLimit, accNums, accSeqs, suite.ctx.ChainID(), tc) + }) + } +} + +// Test logic around account number checking with one signer and many signers. +func (suite *AnteTestSuite) TestAnteHandlerAccountNumbers() { + suite.SetupTest(false) // reset + + // Same data for every test cases + accounts := suite.CreateTestAccounts(2) + feeAmount := testdata.NewTestFeeAmount() + gasLimit := testdata.NewTestGasLimit() + + // Variable data per test case + var ( + accNums []uint64 + msgs []sdk.Msg + privs []cryptotypes.PrivKey + accSeqs []uint64 + ) + + testCases := []TestCase{ + { + "good tx from one signer", + func() { + msg := testdata.NewTestMsg(accounts[0].acc.GetAddress()) + msgs = []sdk.Msg{msg} + + privs, accNums, accSeqs = []cryptotypes.PrivKey{accounts[0].priv}, []uint64{0}, []uint64{0} + }, + false, + true, + nil, + }, + { + "new tx from wrong account number", + func() { + privs, accNums, accSeqs = []cryptotypes.PrivKey{accounts[0].priv}, []uint64{1}, []uint64{1} + }, + false, + false, + sdkerrors.ErrUnauthorized, + }, + { + "new tx from correct account number", + func() { + privs, accNums, accSeqs = []cryptotypes.PrivKey{accounts[0].priv}, []uint64{0}, []uint64{1} + }, + false, + true, + nil, + }, + { + "new tx with another signer and incorrect account numbers", + func() { + msg1 := testdata.NewTestMsg(accounts[0].acc.GetAddress(), accounts[1].acc.GetAddress()) + msg2 := testdata.NewTestMsg(accounts[1].acc.GetAddress(), accounts[0].acc.GetAddress()) + msgs = []sdk.Msg{msg1, msg2} + privs, accNums, accSeqs = []cryptotypes.PrivKey{accounts[0].priv, accounts[1].priv}, []uint64{1, 0}, []uint64{2, 0} + }, + false, + false, + sdkerrors.ErrUnauthorized, + }, + { + "new tx with correct account numbers", + func() { + privs, accNums, accSeqs = []cryptotypes.PrivKey{accounts[0].priv, accounts[1].priv}, []uint64{0, 1}, []uint64{2, 0} + }, + false, + true, + nil, + }, + } + + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.desc), func() { + suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() + tc.malleate() + + suite.RunTestCase(privs, msgs, feeAmount, gasLimit, accNums, accSeqs, suite.ctx.ChainID(), tc) + }) + } +} + +// Test logic around account number checking with many signers when BlockHeight is 0. +func (suite *AnteTestSuite) TestAnteHandlerAccountNumbersAtBlockHeightZero() { + suite.SetupTest(false) // setup + suite.ctx = suite.ctx.WithBlockHeight(0) + + // Same data for every test cases + accounts := suite.CreateTestAccounts(2) + feeAmount := testdata.NewTestFeeAmount() + gasLimit := testdata.NewTestGasLimit() + + // Variable data per test case + var ( + accNums []uint64 + msgs []sdk.Msg + privs []cryptotypes.PrivKey + accSeqs []uint64 + ) + + testCases := []TestCase{ + { + "good tx from one signer", + func() { + msg := testdata.NewTestMsg(accounts[0].acc.GetAddress()) + msgs = []sdk.Msg{msg} + + privs, accNums, accSeqs = []cryptotypes.PrivKey{accounts[0].priv}, []uint64{0}, []uint64{0} + }, + false, + true, + nil, + }, + { + "new tx from wrong account number", + func() { + privs, accNums, accSeqs = []cryptotypes.PrivKey{accounts[0].priv}, []uint64{1}, []uint64{1} + }, + false, + false, + sdkerrors.ErrUnauthorized, + }, + { + "new tx from correct account number", + func() { + privs, accNums, accSeqs = []cryptotypes.PrivKey{accounts[0].priv}, []uint64{0}, []uint64{1} + }, + false, + true, + nil, + }, + { + "new tx with another signer and incorrect account numbers", + func() { + msg1 := testdata.NewTestMsg(accounts[0].acc.GetAddress(), accounts[1].acc.GetAddress()) + msg2 := testdata.NewTestMsg(accounts[1].acc.GetAddress(), accounts[0].acc.GetAddress()) + msgs = []sdk.Msg{msg1, msg2} + + privs, accNums, accSeqs = []cryptotypes.PrivKey{accounts[0].priv, accounts[1].priv}, []uint64{1, 0}, []uint64{2, 0} + }, + false, + false, + sdkerrors.ErrUnauthorized, + }, + { + "new tx with another signer and correct account numbers", + func() { + // Note that accNums is [0,0] at block 0. + privs, accNums, accSeqs = []cryptotypes.PrivKey{accounts[0].priv, accounts[1].priv}, []uint64{0, 0}, []uint64{2, 0} + }, + false, + true, + nil, + }, + } + + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.desc), func() { + suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() + tc.malleate() + + suite.RunTestCase(privs, msgs, feeAmount, gasLimit, accNums, accSeqs, suite.ctx.ChainID(), tc) + }) + } +} + +// Test logic around sequence checking with one signer and many signers. +func (suite *AnteTestSuite) TestAnteHandlerSequences() { + suite.SetupTest(false) // setup + + // Same data for every test cases + accounts := suite.CreateTestAccounts(3) + feeAmount := testdata.NewTestFeeAmount() + gasLimit := testdata.NewTestGasLimit() + + // Variable data per test case + var ( + accNums []uint64 + msgs []sdk.Msg + privs []cryptotypes.PrivKey + accSeqs []uint64 + ) + + testCases := []TestCase{ + { + "good tx from one signer", + func() { + msg := testdata.NewTestMsg(accounts[0].acc.GetAddress()) + msgs = []sdk.Msg{msg} + + privs, accNums, accSeqs = []cryptotypes.PrivKey{accounts[0].priv}, []uint64{0}, []uint64{0} + }, + false, + true, + nil, + }, + { + "test sending it again fails (replay protection)", + func() { + privs, accNums, accSeqs = []cryptotypes.PrivKey{accounts[0].priv}, []uint64{0}, []uint64{0} + }, + false, + false, + sdkerrors.ErrWrongSequence, + }, + { + "fix sequence, should pass", + func() { + privs, accNums, accSeqs = []cryptotypes.PrivKey{accounts[0].priv}, []uint64{0}, []uint64{1} + }, + false, + true, + nil, + }, + { + "new tx with another signer and correct sequences", + func() { + msg1 := testdata.NewTestMsg(accounts[0].acc.GetAddress(), accounts[1].acc.GetAddress()) + msg2 := testdata.NewTestMsg(accounts[2].acc.GetAddress(), accounts[0].acc.GetAddress()) + msgs = []sdk.Msg{msg1, msg2} + + privs, accNums, accSeqs = []cryptotypes.PrivKey{accounts[0].priv, accounts[1].priv, accounts[2].priv}, []uint64{0, 1, 2}, []uint64{2, 0, 0} + }, + false, + true, + nil, + }, + { + "replay fails", + func() {}, + false, + false, + sdkerrors.ErrWrongSequence, + }, + { + "tx from just second signer with incorrect sequence fails", + func() { + msg := testdata.NewTestMsg(accounts[1].acc.GetAddress()) + msgs = []sdk.Msg{msg} + privs, accNums, accSeqs = []cryptotypes.PrivKey{accounts[1].priv}, []uint64{1}, []uint64{0} + }, + false, + false, + sdkerrors.ErrWrongSequence, + }, + { + "fix the sequence and it passes", + func() { + accSeqs = []uint64{1} + }, + false, + true, + nil, + }, + { + "fix the sequence and it passes", + func() { + msg := testdata.NewTestMsg(accounts[0].acc.GetAddress(), accounts[1].acc.GetAddress()) + msgs = []sdk.Msg{msg} + + privs, accNums, accSeqs = []cryptotypes.PrivKey{accounts[0].priv, accounts[1].priv}, []uint64{0, 1}, []uint64{3, 2} + }, + false, + true, + nil, + }, + } + + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.desc), func() { + suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() + tc.malleate() + + suite.RunTestCase(privs, msgs, feeAmount, gasLimit, accNums, accSeqs, suite.ctx.ChainID(), tc) + }) + } +} + +// Test logic around fee deduction. +func (suite *AnteTestSuite) TestAnteHandlerFees() { + suite.SetupTest(false) // setup + + // Same data for every test cases + priv0, _, addr0 := testdata.KeyTestPubAddr() + + acc1 := suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, addr0) + suite.app.AccountKeeper.SetAccount(suite.ctx, acc1) + msgs := []sdk.Msg{testdata.NewTestMsg(addr0)} + feeAmount := testdata.NewTestFeeAmount() + gasLimit := testdata.NewTestGasLimit() + privs, accNums, accSeqs := []cryptotypes.PrivKey{priv0}, []uint64{0}, []uint64{0} + + testCases := []struct { + desc string + malleate func() + simulate bool + expPass bool + expErr error + }{ + { + "signer has no funds", + func() { + accSeqs = []uint64{0} + }, + false, + false, + sdkerrors.ErrInsufficientFunds, + }, + { + "signer does not have enough funds to pay the fee", + func() { + err := simapp.FundAccount(suite.app.BankKeeper, suite.ctx, addr0, sdk.NewCoins(sdk.NewInt64Coin("atom", 149))) + suite.Require().NoError(err) + }, + false, + false, + sdkerrors.ErrInsufficientFunds, + }, + { + "signer as enough funds, should pass", + func() { + accNums = []uint64{acc1.GetAccountNumber()} + + modAcc := suite.app.AccountKeeper.GetModuleAccount(suite.ctx, types.FeeCollectorName) + + suite.Require().True(suite.app.BankKeeper.GetAllBalances(suite.ctx, modAcc.GetAddress()).Empty()) + require.True(sdk.IntEq(suite.T(), suite.app.BankKeeper.GetAllBalances(suite.ctx, addr0).AmountOf("atom"), sdk.NewInt(149))) + + err := simapp.FundAccount(suite.app.BankKeeper, suite.ctx, addr0, sdk.NewCoins(sdk.NewInt64Coin("atom", 1))) + suite.Require().NoError(err) + }, + false, + true, + nil, + }, + { + "signer doesn't have any more funds", + func() { + modAcc := suite.app.AccountKeeper.GetModuleAccount(suite.ctx, types.FeeCollectorName) + + require.True(sdk.IntEq(suite.T(), suite.app.BankKeeper.GetAllBalances(suite.ctx, modAcc.GetAddress()).AmountOf("atom"), sdk.NewInt(150))) + require.True(sdk.IntEq(suite.T(), suite.app.BankKeeper.GetAllBalances(suite.ctx, addr0).AmountOf("atom"), sdk.NewInt(0))) + }, + false, + false, + sdkerrors.ErrInsufficientFunds, + }, + } + + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.desc), func() { + + suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() + tc.malleate() + + suite.RunTestCase(privs, msgs, feeAmount, gasLimit, accNums, accSeqs, suite.ctx.ChainID(), tc) + }) + } +} + +// Test logic around memo gas consumption. +func (suite *AnteTestSuite) TestAnteHandlerMemoGas() { + suite.SetupTest(false) // setup + + // Same data for every test cases + accounts := suite.CreateTestAccounts(1) + msgs := []sdk.Msg{testdata.NewTestMsg(accounts[0].acc.GetAddress())} + privs, accNums, accSeqs := []cryptotypes.PrivKey{accounts[0].priv}, []uint64{0}, []uint64{0} + + // Variable data per test case + var ( + feeAmount sdk.Coins + gasLimit uint64 + ) + + testCases := []TestCase{ + { + "tx does not have enough gas", + func() { + feeAmount = sdk.NewCoins(sdk.NewInt64Coin("atom", 0)) + gasLimit = 0 + }, + false, + false, + sdkerrors.ErrOutOfGas, + }, + { + "tx with memo doesn't have enough gas", + func() { + feeAmount = sdk.NewCoins(sdk.NewInt64Coin("atom", 0)) + gasLimit = 801 + suite.txBuilder.SetMemo("abcininasidniandsinasindiansdiansdinaisndiasndiadninsd") + }, + false, + false, + sdkerrors.ErrOutOfGas, + }, + { + "memo too large", + func() { + feeAmount = sdk.NewCoins(sdk.NewInt64Coin("atom", 0)) + gasLimit = 50000 + suite.txBuilder.SetMemo(strings.Repeat("01234567890", 500)) + }, + false, + false, + sdkerrors.ErrMemoTooLarge, + }, + { + "tx with memo has enough gas", + func() { + feeAmount = sdk.NewCoins(sdk.NewInt64Coin("atom", 0)) + gasLimit = 50000 + suite.txBuilder.SetMemo(strings.Repeat("0123456789", 10)) + }, + false, + true, + nil, + }, + } + + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.desc), func() { + suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() + tc.malleate() + + suite.RunTestCase(privs, msgs, feeAmount, gasLimit, accNums, accSeqs, suite.ctx.ChainID(), tc) + }) + } +} + +func (suite *AnteTestSuite) TestAnteHandlerMultiSigner() { + suite.SetupTest(false) // setup + + // Same data for every test cases + accounts := suite.CreateTestAccounts(3) + msg1 := testdata.NewTestMsg(accounts[0].acc.GetAddress(), accounts[1].acc.GetAddress()) + msg2 := testdata.NewTestMsg(accounts[2].acc.GetAddress(), accounts[0].acc.GetAddress()) + msg3 := testdata.NewTestMsg(accounts[1].acc.GetAddress(), accounts[2].acc.GetAddress()) + feeAmount := testdata.NewTestFeeAmount() + gasLimit := testdata.NewTestGasLimit() + + // Variable data per test case + var ( + accNums []uint64 + msgs []sdk.Msg + privs []cryptotypes.PrivKey + accSeqs []uint64 + ) + + testCases := []TestCase{ + { + "signers in order", + func() { + msgs = []sdk.Msg{msg1, msg2, msg3} + privs, accNums, accSeqs = []cryptotypes.PrivKey{accounts[0].priv, accounts[1].priv, accounts[2].priv}, []uint64{0, 1, 2}, []uint64{0, 0, 0} + suite.txBuilder.SetMemo("Check signers are in expected order and different account numbers works") + }, + false, + true, + nil, + }, + { + "change sequence numbers (only accounts 0 and 1 sign)", + func() { + msgs = []sdk.Msg{msg1} + privs, accNums, accSeqs = []cryptotypes.PrivKey{accounts[0].priv, accounts[1].priv}, []uint64{0, 1}, []uint64{1, 1} + }, + false, + true, + nil, + }, + { + "change sequence numbers (only accounts 1 and 2 sign)", + func() { + msgs = []sdk.Msg{msg2} + privs, accNums, accSeqs = []cryptotypes.PrivKey{accounts[2].priv, accounts[0].priv}, []uint64{2, 0}, []uint64{1, 2} + }, + false, + true, + nil, + }, + { + "everyone signs again", + func() { + msgs = []sdk.Msg{msg1, msg2, msg3} + privs, accNums, accSeqs = []cryptotypes.PrivKey{accounts[0].priv, accounts[1].priv, accounts[2].priv}, []uint64{0, 1, 2}, []uint64{3, 2, 2} + }, + false, + true, + nil, + }, + } + + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.desc), func() { + suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() + tc.malleate() + + suite.RunTestCase(privs, msgs, feeAmount, gasLimit, accNums, accSeqs, suite.ctx.ChainID(), tc) + }) + } +} + +func (suite *AnteTestSuite) TestAnteHandlerBadSignBytes() { + suite.SetupTest(false) // setup + + // Same data for every test cases + accounts := suite.CreateTestAccounts(2) + msg0 := testdata.NewTestMsg(accounts[0].acc.GetAddress()) + + // Variable data per test case + var ( + accNums []uint64 + chainID string + feeAmount sdk.Coins + gasLimit uint64 + msgs []sdk.Msg + privs []cryptotypes.PrivKey + accSeqs []uint64 + ) + + testCases := []TestCase{ + { + "test good tx and signBytes", + func() { + chainID = suite.ctx.ChainID() + feeAmount = testdata.NewTestFeeAmount() + gasLimit = testdata.NewTestGasLimit() + msgs = []sdk.Msg{msg0} + privs, accNums, accSeqs = []cryptotypes.PrivKey{accounts[0].priv}, []uint64{0}, []uint64{0} + }, + false, + true, + nil, + }, + { + "test wrong chainID", + func() { + accSeqs = []uint64{1} // Back to correct accSeqs + chainID = "chain-foo" + }, + false, + false, + sdkerrors.ErrUnauthorized, + }, + { + "test wrong accSeqs", + func() { + chainID = suite.ctx.ChainID() // Back to correct chainID + accSeqs = []uint64{2} + }, + false, + false, + sdkerrors.ErrWrongSequence, + }, + { + "test wrong accNums", + func() { + accSeqs = []uint64{1} // Back to correct accSeqs + accNums = []uint64{1} + }, + false, + false, + sdkerrors.ErrUnauthorized, + }, + { + "test wrong msg", + func() { + msgs = []sdk.Msg{testdata.NewTestMsg(accounts[1].acc.GetAddress())} + }, + false, + false, + sdkerrors.ErrInvalidPubKey, + }, + { + "test wrong fee gas", + func() { + msgs = []sdk.Msg{msg0} // Back to correct msgs + feeAmount = testdata.NewTestFeeAmount() + gasLimit = testdata.NewTestGasLimit() + 100 + }, + false, + false, + sdkerrors.ErrUnauthorized, + }, + { + "test wrong fee amount", + func() { + feeAmount = testdata.NewTestFeeAmount() + feeAmount[0].Amount = feeAmount[0].Amount.AddRaw(100) + gasLimit = testdata.NewTestGasLimit() + }, + false, + false, + sdkerrors.ErrUnauthorized, + }, + { + "test wrong signer if public key exist", + func() { + feeAmount = testdata.NewTestFeeAmount() + gasLimit = testdata.NewTestGasLimit() + privs, accNums, accSeqs = []cryptotypes.PrivKey{accounts[1].priv}, []uint64{0}, []uint64{1} + }, + false, + false, + sdkerrors.ErrInvalidPubKey, + }, + { + "test wrong signer if public doesn't exist", + func() { + msgs = []sdk.Msg{testdata.NewTestMsg(accounts[1].acc.GetAddress())} + privs, accNums, accSeqs = []cryptotypes.PrivKey{accounts[0].priv}, []uint64{1}, []uint64{0} + }, + false, + false, + sdkerrors.ErrInvalidPubKey, + }, + } + + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.desc), func() { + suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() + tc.malleate() + + suite.RunTestCase(privs, msgs, feeAmount, gasLimit, accNums, accSeqs, chainID, tc) + }) + } +} + +func (suite *AnteTestSuite) TestAnteHandlerSetPubKey() { + suite.SetupTest(false) // setup + + // Same data for every test cases + accounts := suite.CreateTestAccounts(2) + feeAmount := testdata.NewTestFeeAmount() + gasLimit := testdata.NewTestGasLimit() + + // Variable data per test case + var ( + accNums []uint64 + msgs []sdk.Msg + privs []cryptotypes.PrivKey + accSeqs []uint64 + ) + + testCases := []TestCase{ + { + "test good tx", + func() { + privs, accNums, accSeqs = []cryptotypes.PrivKey{accounts[0].priv}, []uint64{0}, []uint64{0} + msgs = []sdk.Msg{testdata.NewTestMsg(accounts[0].acc.GetAddress())} + }, + false, + true, + nil, + }, + { + "make sure public key has been set (tx itself should fail because of replay protection)", + func() { + // Make sure public key has been set from previous test. + acc0 := suite.app.AccountKeeper.GetAccount(suite.ctx, accounts[0].acc.GetAddress()) + suite.Require().Equal(acc0.GetPubKey(), accounts[0].priv.PubKey()) + }, + false, + false, + sdkerrors.ErrWrongSequence, + }, + { + "test public key not found", + func() { + // See above, `privs` still holds the private key of accounts[0]. + msgs = []sdk.Msg{testdata.NewTestMsg(accounts[1].acc.GetAddress())} + }, + false, + false, + sdkerrors.ErrInvalidPubKey, + }, + { + "make sure public key is not set, when tx has no pubkey or signature", + func() { + // Make sure public key has not been set from previous test. + acc1 := suite.app.AccountKeeper.GetAccount(suite.ctx, accounts[1].acc.GetAddress()) + suite.Require().Nil(acc1.GetPubKey()) + + privs, accNums, accSeqs = []cryptotypes.PrivKey{accounts[1].priv}, []uint64{1}, []uint64{0} + msgs = []sdk.Msg{testdata.NewTestMsg(accounts[1].acc.GetAddress())} + suite.txBuilder.SetMsgs(msgs...) + suite.txBuilder.SetFeeAmount(feeAmount) + suite.txBuilder.SetGasLimit(gasLimit) + + // Manually create tx, and remove signature. + tx, err := suite.CreateTestTx(privs, accNums, accSeqs, suite.ctx.ChainID()) + suite.Require().NoError(err) + txBuilder, err := suite.clientCtx.TxConfig.WrapTxBuilder(tx) + suite.Require().NoError(err) + suite.Require().NoError(txBuilder.SetSignatures()) + + // Run anteHandler manually, expect ErrNoSignatures. + _, err = suite.anteHandler(suite.ctx, txBuilder.GetTx(), false) + suite.Require().Error(err) + suite.Require().True(errors.Is(err, sdkerrors.ErrNoSignatures)) + + // Make sure public key has not been set. + acc1 = suite.app.AccountKeeper.GetAccount(suite.ctx, accounts[1].acc.GetAddress()) + suite.Require().Nil(acc1.GetPubKey()) + + // Set incorrect accSeq, to generate incorrect signature. + privs, accNums, accSeqs = []cryptotypes.PrivKey{accounts[1].priv}, []uint64{1}, []uint64{1} + }, + false, + false, + sdkerrors.ErrWrongSequence, + }, + { + "make sure previous public key has been set after wrong signature", + func() { + // Make sure public key has been set, as SetPubKeyDecorator + // is called before all signature verification decorators. + acc1 := suite.app.AccountKeeper.GetAccount(suite.ctx, accounts[1].acc.GetAddress()) + suite.Require().Equal(acc1.GetPubKey(), accounts[1].priv.PubKey()) + }, + false, + false, + sdkerrors.ErrWrongSequence, + }, + } + + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.desc), func() { + suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() + tc.malleate() + + suite.RunTestCase(privs, msgs, feeAmount, gasLimit, accNums, accSeqs, suite.ctx.ChainID(), tc) + }) + } +} + +func generatePubKeysAndSignatures(n int, msg []byte, _ bool) (pubkeys []cryptotypes.PubKey, signatures [][]byte) { + pubkeys = make([]cryptotypes.PubKey, n) + signatures = make([][]byte, n) + for i := 0; i < n; i++ { + var privkey cryptotypes.PrivKey = secp256k1.GenPrivKey() + + // TODO: also generate ed25519 keys as below when ed25519 keys are + // actually supported, https://github.com/cosmos/cosmos-sdk/issues/4789 + // for now this fails: + //if rand.Int63()%2 == 0 { + // privkey = ed25519.GenPrivKey() + //} else { + // privkey = secp256k1.GenPrivKey() + //} + + pubkeys[i] = privkey.PubKey() + signatures[i], _ = privkey.Sign(msg) + } + return +} + +func expectedGasCostByKeys(pubkeys []cryptotypes.PubKey) uint64 { + cost := uint64(0) + for _, pubkey := range pubkeys { + pubkeyType := strings.ToLower(fmt.Sprintf("%T", pubkey)) + switch { + case strings.Contains(pubkeyType, "ed25519"): + cost += types.DefaultParams().SigVerifyCostED25519 + case strings.Contains(pubkeyType, "secp256k1"): + cost += types.DefaultParams().SigVerifyCostSecp256k1 + default: + panic("unexpected key type") + } + } + return cost +} + +func TestCountSubkeys(t *testing.T) { + genPubKeys := func(n int) []cryptotypes.PubKey { + var ret []cryptotypes.PubKey + for i := 0; i < n; i++ { + ret = append(ret, secp256k1.GenPrivKey().PubKey()) + } + return ret + } + singleKey := secp256k1.GenPrivKey().PubKey() + singleLevelMultiKey := kmultisig.NewLegacyAminoPubKey(4, genPubKeys(5)) + multiLevelSubKey1 := kmultisig.NewLegacyAminoPubKey(4, genPubKeys(5)) + multiLevelSubKey2 := kmultisig.NewLegacyAminoPubKey(4, genPubKeys(5)) + multiLevelMultiKey := kmultisig.NewLegacyAminoPubKey(2, []cryptotypes.PubKey{ + multiLevelSubKey1, multiLevelSubKey2, secp256k1.GenPrivKey().PubKey()}) + type args struct { + pub cryptotypes.PubKey + } + testCases := []struct { + name string + args args + want int + }{ + {"single key", args{singleKey}, 1}, + {"single level multikey", args{singleLevelMultiKey}, 5}, + {"multi level multikey", args{multiLevelMultiKey}, 11}, + } + for _, tc := range testCases { + t.Run(tc.name, func(T *testing.T) { + require.Equal(t, tc.want, ante.CountSubKeys(tc.args.pub)) + }) + } +} + +func (suite *AnteTestSuite) TestAnteHandlerSigLimitExceeded() { + suite.SetupTest(false) // setup + + // Same data for every test cases + accounts := suite.CreateTestAccounts(8) + var addrs []sdk.AccAddress + var privs []cryptotypes.PrivKey + for i := 0; i < 8; i++ { + addrs = append(addrs, accounts[i].acc.GetAddress()) + privs = append(privs, accounts[i].priv) + } + msgs := []sdk.Msg{testdata.NewTestMsg(addrs...)} + accNums, accSeqs := []uint64{0, 1, 2, 3, 4, 5, 6, 7}, []uint64{0, 0, 0, 0, 0, 0, 0, 0} + feeAmount := testdata.NewTestFeeAmount() + gasLimit := testdata.NewTestGasLimit() + + testCases := []TestCase{ + { + "test rejection logic", + func() {}, + false, + false, + sdkerrors.ErrTooManySignatures, + }, + } + + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.desc), func() { + suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() + tc.malleate() + + suite.RunTestCase(privs, msgs, feeAmount, gasLimit, accNums, accSeqs, suite.ctx.ChainID(), tc) + }) + } +} + +// Test custom SignatureVerificationGasConsumer +func (suite *AnteTestSuite) TestCustomSignatureVerificationGasConsumer() { + suite.SetupTest(false) // setup + + // setup an ante handler that only accepts PubKeyEd25519 + anteHandler, err := ante.NewAnteHandler( + ante.HandlerOptions{ + AccountKeeper: suite.app.AccountKeeper, + BankKeeper: suite.app.BankKeeper, + FeegrantKeeper: suite.app.FeeGrantKeeper, + SignModeHandler: suite.clientCtx.TxConfig.SignModeHandler(), + SigGasConsumer: func(meter sdk.GasMeter, sig signing.SignatureV2, params types.Params) error { + switch pubkey := sig.PubKey.(type) { + case *ed25519.PubKey: + meter.ConsumeGas(params.SigVerifyCostED25519, "ante verify: ed25519") + return nil + default: + return sdkerrors.Wrapf(sdkerrors.ErrInvalidPubKey, "unrecognized public key type: %T", pubkey) + } + }, + }, + ) + + suite.Require().NoError(err) + suite.anteHandler = anteHandler + + // Same data for every test cases + accounts := suite.CreateTestAccounts(1) + feeAmount := testdata.NewTestFeeAmount() + gasLimit := testdata.NewTestGasLimit() + + // Variable data per test case + var ( + accNums []uint64 + msgs []sdk.Msg + privs []cryptotypes.PrivKey + accSeqs []uint64 + ) + + testCases := []TestCase{ + { + "verify that an secp256k1 account gets rejected", + func() { + msgs = []sdk.Msg{testdata.NewTestMsg(accounts[0].acc.GetAddress())} + privs, accNums, accSeqs = []cryptotypes.PrivKey{accounts[0].priv}, []uint64{0}, []uint64{0} + }, + false, + false, + sdkerrors.ErrInvalidPubKey, + }, + } + + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.desc), func() { + suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() + tc.malleate() + + suite.RunTestCase(privs, msgs, feeAmount, gasLimit, accNums, accSeqs, suite.ctx.ChainID(), tc) + }) + } +} + +func (suite *AnteTestSuite) TestAnteHandlerReCheck() { + suite.SetupTest(false) // setup + // Set recheck=true + suite.ctx = suite.ctx.WithIsReCheckTx(true) + suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() + + // Same data for every test cases + accounts := suite.CreateTestAccounts(1) + + feeAmount := testdata.NewTestFeeAmount() + gasLimit := testdata.NewTestGasLimit() + suite.txBuilder.SetFeeAmount(feeAmount) + suite.txBuilder.SetGasLimit(gasLimit) + + msg := testdata.NewTestMsg(accounts[0].acc.GetAddress()) + msgs := []sdk.Msg{msg} + suite.Require().NoError(suite.txBuilder.SetMsgs(msgs...)) + + suite.txBuilder.SetMemo("thisisatestmemo") + + // test that operations skipped on recheck do not run + privs, accNums, accSeqs := []cryptotypes.PrivKey{accounts[0].priv}, []uint64{0}, []uint64{0} + tx, err := suite.CreateTestTx(privs, accNums, accSeqs, suite.ctx.ChainID()) + suite.Require().NoError(err) + + // make signature array empty which would normally cause ValidateBasicDecorator and SigVerificationDecorator fail + // since these decorators don't run on recheck, the tx should pass the antehandler + txBuilder, err := suite.clientCtx.TxConfig.WrapTxBuilder(tx) + suite.Require().NoError(err) + suite.Require().NoError(txBuilder.SetSignatures()) + + _, err = suite.anteHandler(suite.ctx, txBuilder.GetTx(), false) + suite.Require().Nil(err, "AnteHandler errored on recheck unexpectedly: %v", err) + + tx, err = suite.CreateTestTx(privs, accNums, accSeqs, suite.ctx.ChainID()) + suite.Require().NoError(err) + txBytes, err := json.Marshal(tx) + suite.Require().Nil(err, "Error marshalling tx: %v", err) + suite.ctx = suite.ctx.WithTxBytes(txBytes) + + // require that state machine param-dependent checking is still run on recheck since parameters can change between check and recheck + testCases := []struct { + name string + params types.Params + }{ + {"memo size check", types.NewParams(1, types.DefaultTxSigLimit, types.DefaultTxSizeCostPerByte, types.DefaultSigVerifyCostED25519, types.DefaultSigVerifyCostSecp256k1)}, + {"txsize check", types.NewParams(types.DefaultMaxMemoCharacters, types.DefaultTxSigLimit, 10000000, types.DefaultSigVerifyCostED25519, types.DefaultSigVerifyCostSecp256k1)}, + {"sig verify cost check", types.NewParams(types.DefaultMaxMemoCharacters, types.DefaultTxSigLimit, types.DefaultTxSizeCostPerByte, types.DefaultSigVerifyCostED25519, 100000000)}, + } + for _, tc := range testCases { + // set testcase parameters + suite.app.AccountKeeper.SetParams(suite.ctx, tc.params) + + _, err := suite.anteHandler(suite.ctx, tx, false) + + suite.Require().NotNil(err, "tx does not fail on recheck with updated params in test case: %s", tc.name) + + // reset parameters to default values + suite.app.AccountKeeper.SetParams(suite.ctx, types.DefaultParams()) + } + + // require that local mempool fee check is still run on recheck since validator may change minFee between check and recheck + // create new minimum gas price so antehandler fails on recheck + suite.ctx = suite.ctx.WithMinGasPrices([]sdk.DecCoin{{ + Denom: "dnecoin", // fee does not have this denom + Amount: sdk.NewDec(5), + }}) + _, err = suite.anteHandler(suite.ctx, tx, false) + suite.Require().NotNil(err, "antehandler on recheck did not fail when mingasPrice was changed") + // reset min gasprice + suite.ctx = suite.ctx.WithMinGasPrices(sdk.DecCoins{}) + + // remove funds for account so antehandler fails on recheck + suite.app.AccountKeeper.SetAccount(suite.ctx, accounts[0].acc) + balances := suite.app.BankKeeper.GetAllBalances(suite.ctx, accounts[0].acc.GetAddress()) + err = suite.app.BankKeeper.SendCoinsFromAccountToModule(suite.ctx, accounts[0].acc.GetAddress(), minttypes.ModuleName, balances) + suite.Require().NoError(err) + + _, err = suite.anteHandler(suite.ctx, tx, false) + suite.Require().NotNil(err, "antehandler on recheck did not fail once feePayer no longer has sufficient funds") +} +*/ diff --git a/x/auth/ante/basic.go b/x/auth/ante/basic.go new file mode 100644 index 00000000..8ec0d68e --- /dev/null +++ b/x/auth/ante/basic.go @@ -0,0 +1,206 @@ +package ante + +import ( + "github.com/cosmos/cosmos-sdk/codec/legacy" + "github.com/cosmos/cosmos-sdk/crypto/keys/multisig" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + "github.com/cosmos/cosmos-sdk/types/tx/signing" + "github.com/cosmos/cosmos-sdk/x/auth/legacy/legacytx" + authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing" +) + +// ValidateBasicDecorator will call tx.ValidateBasic and return any non-nil error. +// If ValidateBasic passes, decorator calls next AnteHandler in chain. Note, +// ValidateBasicDecorator decorator will not get executed on ReCheckTx since it +// is not dependent on application state. +type ValidateBasicDecorator struct{} + +func NewValidateBasicDecorator() ValidateBasicDecorator { + return ValidateBasicDecorator{} +} + +func (vbd ValidateBasicDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) { + // no need to validate basic on recheck tx, call next antehandler + if ctx.IsReCheckTx() { + return next(ctx, tx, simulate) + } + + if err := tx.ValidateBasic(); err != nil { + return ctx, err + } + + return next(ctx, tx, simulate) +} + +// ValidateMemoDecorator will validate memo given the parameters passed in +// If memo is too large decorator returns with error, otherwise call next AnteHandler +// CONTRACT: Tx must implement TxWithMemo interface +type ValidateMemoDecorator struct { + ak AccountKeeper +} + +func NewValidateMemoDecorator(ak AccountKeeper) ValidateMemoDecorator { + return ValidateMemoDecorator{ + ak: ak, + } +} + +func (vmd ValidateMemoDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) { + memoTx, ok := tx.(sdk.TxWithMemo) + if !ok { + return ctx, sdkerrors.Wrap(sdkerrors.ErrTxDecode, "invalid transaction type") + } + + params := vmd.ak.GetParams(ctx) + + memoLength := len(memoTx.GetMemo()) + if uint64(memoLength) > params.MaxMemoCharacters { + return ctx, sdkerrors.Wrapf(sdkerrors.ErrMemoTooLarge, + "maximum number of characters is %d but received %d characters", + params.MaxMemoCharacters, memoLength, + ) + } + + return next(ctx, tx, simulate) +} + +// ConsumeTxSizeGasDecorator will take in parameters and consume gas proportional +// to the size of tx before calling next AnteHandler. Note, the gas costs will be +// slightly over estimated due to the fact that any given signing account may need +// to be retrieved from state. +// +// CONTRACT: If simulate=true, then signatures must either be completely filled +// in or empty. +// CONTRACT: To use this decorator, signatures of transaction must be represented +// as legacytx.StdSignature otherwise simulate mode will incorrectly estimate gas cost. +type ConsumeTxSizeGasDecorator struct { + ak AccountKeeper +} + +func NewConsumeGasForTxSizeDecorator(ak AccountKeeper) ConsumeTxSizeGasDecorator { + return ConsumeTxSizeGasDecorator{ + ak: ak, + } +} + +func (cgts ConsumeTxSizeGasDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) { + sigTx, ok := tx.(authsigning.SigVerifiableTx) + if !ok { + return ctx, sdkerrors.Wrap(sdkerrors.ErrTxDecode, "invalid tx type") + } + params := cgts.ak.GetParams(ctx) + + ctx.GasMeter().ConsumeGas(params.TxSizeCostPerByte*sdk.Gas(len(ctx.TxBytes())), "txSize") + + // simulate gas cost for signatures in simulate mode + if simulate { + // in simulate mode, each element should be a nil signature + sigs, err := sigTx.GetSignaturesV2() + if err != nil { + return ctx, err + } + n := len(sigs) + + for i, signer := range sigTx.GetSigners() { + // if signature is already filled in, no need to simulate gas cost + if i < n && !isIncompleteSignature(sigs[i].Data) { + continue + } + + var pubkey cryptotypes.PubKey + + acc := cgts.ak.GetAccount(ctx, signer) + + // use placeholder simSecp256k1Pubkey if sig is nil + if acc == nil || acc.GetPubKey() == nil { + pubkey = simSecp256k1Pubkey + } else { + pubkey = acc.GetPubKey() + } + + // use stdsignature to mock the size of a full signature + simSig := legacytx.StdSignature{ //nolint:staticcheck // this will be removed when proto is ready + Signature: simSecp256k1Sig[:], + PubKey: pubkey, + } + + sigBz := legacy.Cdc.MustMarshal(simSig) + cost := sdk.Gas(len(sigBz) + 6) + + // If the pubkey is a multi-signature pubkey, then we estimate for the maximum + // number of signers. + if _, ok := pubkey.(*multisig.LegacyAminoPubKey); ok { + cost *= params.TxSigLimit + } + + ctx.GasMeter().ConsumeGas(params.TxSizeCostPerByte*cost, "txSize") + } + } + + return next(ctx, tx, simulate) +} + +// isIncompleteSignature tests whether SignatureData is fully filled in for simulation purposes +func isIncompleteSignature(data signing.SignatureData) bool { + if data == nil { + return true + } + + switch data := data.(type) { + case *signing.SingleSignatureData: + return len(data.Signature) == 0 + case *signing.MultiSignatureData: + if len(data.Signatures) == 0 { + return true + } + for _, s := range data.Signatures { + if isIncompleteSignature(s) { + return true + } + } + } + + return false +} + +type ( + // TxTimeoutHeightDecorator defines an AnteHandler decorator that checks for a + // tx height timeout. + TxTimeoutHeightDecorator struct{} + + // TxWithTimeoutHeight defines the interface a tx must implement in order for + // TxHeightTimeoutDecorator to process the tx. + TxWithTimeoutHeight interface { + sdk.Tx + + GetTimeoutHeight() uint64 + } +) + +// TxTimeoutHeightDecorator defines an AnteHandler decorator that checks for a +// tx height timeout. +func NewTxTimeoutHeightDecorator() TxTimeoutHeightDecorator { + return TxTimeoutHeightDecorator{} +} + +// AnteHandle implements an AnteHandler decorator for the TxHeightTimeoutDecorator +// type where the current block height is checked against the tx's height timeout. +// If a height timeout is provided (non-zero) and is less than the current block +// height, then an error is returned. +func (txh TxTimeoutHeightDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) { + timeoutTx, ok := tx.(TxWithTimeoutHeight) + if !ok { + return ctx, sdkerrors.Wrap(sdkerrors.ErrTxDecode, "expected tx to implement TxWithTimeoutHeight") + } + + timeoutHeight := timeoutTx.GetTimeoutHeight() + if timeoutHeight > 0 && uint64(ctx.BlockHeight()) > timeoutHeight { + return ctx, sdkerrors.Wrapf( + sdkerrors.ErrTxTimeoutHeight, "block height: %d, timeout height: %d", ctx.BlockHeight(), timeoutHeight, + ) + } + + return next(ctx, tx, simulate) +} diff --git a/x/auth/ante/basic_test.go b/x/auth/ante/basic_test.go new file mode 100644 index 00000000..4a8cb830 --- /dev/null +++ b/x/auth/ante/basic_test.go @@ -0,0 +1,224 @@ +package ante_test + +import ( + "strings" + + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + "github.com/cosmos/cosmos-sdk/crypto/types/multisig" + "github.com/cosmos/cosmos-sdk/testutil/testdata" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/tx/signing" + "github.com/cosmos/cosmos-sdk/x/auth/ante" +) + +func (suite *AnteTestSuite) TestValidateBasic() { + suite.SetupTest(true) // setup + suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() + + // keys and addresses + priv1, _, addr1 := testdata.KeyTestPubAddr() + + // msg and signatures + msg := testdata.NewTestMsg(addr1) + feeAmount := testdata.NewTestFeeAmount() + gasLimit := testdata.NewTestGasLimit() + suite.Require().NoError(suite.txBuilder.SetMsgs(msg)) + suite.txBuilder.SetFeeAmount(feeAmount) + suite.txBuilder.SetGasLimit(gasLimit) + + privs, accNums, accSeqs := []cryptotypes.PrivKey{}, []uint64{}, []uint64{} + invalidTx, err := suite.CreateTestTx(privs, accNums, accSeqs, suite.ctx.ChainID()) + suite.Require().NoError(err) + + vbd := ante.NewValidateBasicDecorator() + antehandler := sdk.ChainAnteDecorators(vbd) + _, err = antehandler(suite.ctx, invalidTx, false) + + suite.Require().NotNil(err, "Did not error on invalid tx") + + privs, accNums, accSeqs = []cryptotypes.PrivKey{priv1}, []uint64{0}, []uint64{0} + validTx, err := suite.CreateTestTx(privs, accNums, accSeqs, suite.ctx.ChainID()) + suite.Require().NoError(err) + + _, err = antehandler(suite.ctx, validTx, false) + suite.Require().Nil(err, "ValidateBasicDecorator returned error on valid tx. err: %v", err) + + // test decorator skips on recheck + suite.ctx = suite.ctx.WithIsReCheckTx(true) + + // decorator should skip processing invalidTx on recheck and thus return nil-error + _, err = antehandler(suite.ctx, invalidTx, false) + + suite.Require().Nil(err, "ValidateBasicDecorator ran on ReCheck") +} + +func (suite *AnteTestSuite) TestValidateMemo() { + suite.SetupTest(true) // setup + suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() + + // keys and addresses + priv1, _, addr1 := testdata.KeyTestPubAddr() + + // msg and signatures + msg := testdata.NewTestMsg(addr1) + feeAmount := testdata.NewTestFeeAmount() + gasLimit := testdata.NewTestGasLimit() + suite.Require().NoError(suite.txBuilder.SetMsgs(msg)) + suite.txBuilder.SetFeeAmount(feeAmount) + suite.txBuilder.SetGasLimit(gasLimit) + + privs, accNums, accSeqs := []cryptotypes.PrivKey{priv1}, []uint64{0}, []uint64{0} + suite.txBuilder.SetMemo(strings.Repeat("01234567890", 500)) + invalidTx, err := suite.CreateTestTx(privs, accNums, accSeqs, suite.ctx.ChainID()) + suite.Require().NoError(err) + + // require that long memos get rejected + vmd := ante.NewValidateMemoDecorator(suite.app.AccountKeeper) + antehandler := sdk.ChainAnteDecorators(vmd) + _, err = antehandler(suite.ctx, invalidTx, false) + + suite.Require().NotNil(err, "Did not error on tx with high memo") + + suite.txBuilder.SetMemo(strings.Repeat("01234567890", 10)) + validTx, err := suite.CreateTestTx(privs, accNums, accSeqs, suite.ctx.ChainID()) + suite.Require().NoError(err) + + // require small memos pass ValidateMemo Decorator + _, err = antehandler(suite.ctx, validTx, false) + suite.Require().Nil(err, "ValidateBasicDecorator returned error on valid tx. err: %v", err) +} + +func (suite *AnteTestSuite) TestConsumeGasForTxSize() { + suite.SetupTest(true) // setup + + // keys and addresses + priv1, _, addr1 := testdata.KeyTestPubAddr() + + // msg and signatures + msg := testdata.NewTestMsg(addr1) + feeAmount := testdata.NewTestFeeAmount() + gasLimit := testdata.NewTestGasLimit() + + cgtsd := ante.NewConsumeGasForTxSizeDecorator(suite.app.AccountKeeper) + antehandler := sdk.ChainAnteDecorators(cgtsd) + + testCases := []struct { + name string + sigV2 signing.SignatureV2 + }{ + {"SingleSignatureData", signing.SignatureV2{PubKey: priv1.PubKey()}}, + {"MultiSignatureData", signing.SignatureV2{PubKey: priv1.PubKey(), Data: multisig.NewMultisig(2)}}, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() + suite.Require().NoError(suite.txBuilder.SetMsgs(msg)) + suite.txBuilder.SetFeeAmount(feeAmount) + suite.txBuilder.SetGasLimit(gasLimit) + suite.txBuilder.SetMemo(strings.Repeat("01234567890", 10)) + + privs, accNums, accSeqs := []cryptotypes.PrivKey{priv1}, []uint64{0}, []uint64{0} + tx, err := suite.CreateTestTx(privs, accNums, accSeqs, suite.ctx.ChainID()) + suite.Require().NoError(err) + + txBytes, err := suite.clientCtx.TxConfig.TxJSONEncoder()(tx) + suite.Require().Nil(err, "Cannot marshal tx: %v", err) + + params := suite.app.AccountKeeper.GetParams(suite.ctx) + expectedGas := sdk.Gas(len(txBytes)) * params.TxSizeCostPerByte + + // Set suite.ctx with TxBytes manually + suite.ctx = suite.ctx.WithTxBytes(txBytes) + + // track how much gas is necessary to retrieve parameters + beforeGas := suite.ctx.GasMeter().GasConsumed() + suite.app.AccountKeeper.GetParams(suite.ctx) + afterGas := suite.ctx.GasMeter().GasConsumed() + expectedGas += afterGas - beforeGas + + beforeGas = suite.ctx.GasMeter().GasConsumed() + suite.ctx, err = antehandler(suite.ctx, tx, false) + suite.Require().Nil(err, "ConsumeTxSizeGasDecorator returned error: %v", err) + + // require that decorator consumes expected amount of gas + consumedGas := suite.ctx.GasMeter().GasConsumed() - beforeGas + suite.Require().Equal(expectedGas, consumedGas, "Decorator did not consume the correct amount of gas") + + // simulation must not underestimate gas of this decorator even with nil signatures + txBuilder, err := suite.clientCtx.TxConfig.WrapTxBuilder(tx) + suite.Require().NoError(err) + suite.Require().NoError(txBuilder.SetSignatures(tc.sigV2)) + tx = txBuilder.GetTx() + + simTxBytes, err := suite.clientCtx.TxConfig.TxJSONEncoder()(tx) + suite.Require().Nil(err, "Cannot marshal tx: %v", err) + // require that simulated tx is smaller than tx with signatures + suite.Require().True(len(simTxBytes) < len(txBytes), "simulated tx still has signatures") + + // Set suite.ctx with smaller simulated TxBytes manually + suite.ctx = suite.ctx.WithTxBytes(simTxBytes) + + beforeSimGas := suite.ctx.GasMeter().GasConsumed() + + // run antehandler with simulate=true + suite.ctx, err = antehandler(suite.ctx, tx, true) + consumedSimGas := suite.ctx.GasMeter().GasConsumed() - beforeSimGas + + // require that antehandler passes and does not underestimate decorator cost + suite.Require().Nil(err, "ConsumeTxSizeGasDecorator returned error: %v", err) + suite.Require().True(consumedSimGas >= expectedGas, "Simulate mode underestimates gas on AnteDecorator. Simulated cost: %d, expected cost: %d", consumedSimGas, expectedGas) + + }) + } + +} + +func (suite *AnteTestSuite) TestTxHeightTimeoutDecorator() { + suite.SetupTest(true) + + antehandler := sdk.ChainAnteDecorators(ante.NewTxTimeoutHeightDecorator()) + + // keys and addresses + priv1, _, addr1 := testdata.KeyTestPubAddr() + + // msg and signatures + msg := testdata.NewTestMsg(addr1) + feeAmount := testdata.NewTestFeeAmount() + gasLimit := testdata.NewTestGasLimit() + + testCases := []struct { + name string + timeout uint64 + height int64 + expectErr bool + }{ + {"default value", 0, 10, false}, + {"no timeout (greater height)", 15, 10, false}, + {"no timeout (same height)", 10, 10, false}, + {"timeout (smaller height)", 9, 10, true}, + } + + for _, tc := range testCases { + tc := tc + + suite.Run(tc.name, func() { + suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() + + suite.Require().NoError(suite.txBuilder.SetMsgs(msg)) + + suite.txBuilder.SetFeeAmount(feeAmount) + suite.txBuilder.SetGasLimit(gasLimit) + suite.txBuilder.SetMemo(strings.Repeat("01234567890", 10)) + suite.txBuilder.SetTimeoutHeight(tc.timeout) + + privs, accNums, accSeqs := []cryptotypes.PrivKey{priv1}, []uint64{0}, []uint64{0} + tx, err := suite.CreateTestTx(privs, accNums, accSeqs, suite.ctx.ChainID()) + suite.Require().NoError(err) + + ctx := suite.ctx.WithBlockHeight(tc.height) + _, err = antehandler(ctx, tx, true) + suite.Require().Equal(tc.expectErr, err != nil, err) + }) + } +} diff --git a/x/auth/ante/expected_keepers.go b/x/auth/ante/expected_keepers.go new file mode 100644 index 00000000..4dbbbd21 --- /dev/null +++ b/x/auth/ante/expected_keepers.go @@ -0,0 +1,20 @@ +package ante + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth/types" +) + +// AccountKeeper defines the contract needed for AccountKeeper related APIs. +// Interface provides support to use non-sdk AccountKeeper for AnteHandler's decorators. +type AccountKeeper interface { + GetParams(ctx sdk.Context) (params types.Params) + GetAccount(ctx sdk.Context, addr sdk.AccAddress) types.AccountI + SetAccount(ctx sdk.Context, acc types.AccountI) + GetModuleAddress(moduleName string) sdk.AccAddress +} + +// FeegrantKeeper defines the expected feegrant keeper. +type FeegrantKeeper interface { + UseGrantedFees(ctx sdk.Context, granter, grantee sdk.AccAddress, fee sdk.Coins, msgs []sdk.Msg) error +} diff --git a/x/auth/ante/ext.go b/x/auth/ante/ext.go new file mode 100644 index 00000000..362b8d32 --- /dev/null +++ b/x/auth/ante/ext.go @@ -0,0 +1,36 @@ +package ante + +import ( + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" +) + +type HasExtensionOptionsTx interface { + GetExtensionOptions() []*codectypes.Any + GetNonCriticalExtensionOptions() []*codectypes.Any +} + +// RejectExtensionOptionsDecorator is an AnteDecorator that rejects all extension +// options which can optionally be included in protobuf transactions. Users that +// need extension options should create a custom AnteHandler chain that handles +// needed extension options properly and rejects unknown ones. +type RejectExtensionOptionsDecorator struct{} + +// NewRejectExtensionOptionsDecorator creates a new RejectExtensionOptionsDecorator +func NewRejectExtensionOptionsDecorator() RejectExtensionOptionsDecorator { + return RejectExtensionOptionsDecorator{} +} + +var _ types.AnteDecorator = RejectExtensionOptionsDecorator{} + +// AnteHandle implements the AnteDecorator.AnteHandle method +func (r RejectExtensionOptionsDecorator) AnteHandle(ctx types.Context, tx types.Tx, simulate bool, next types.AnteHandler) (newCtx types.Context, err error) { + if hasExtOptsTx, ok := tx.(HasExtensionOptionsTx); ok { + if len(hasExtOptsTx.GetExtensionOptions()) != 0 { + return ctx, sdkerrors.ErrUnknownExtensionOptions + } + } + + return next(ctx, tx, simulate) +} diff --git a/x/auth/ante/ext_test.go b/x/auth/ante/ext_test.go new file mode 100644 index 00000000..89ce6a7d --- /dev/null +++ b/x/auth/ante/ext_test.go @@ -0,0 +1,36 @@ +package ante_test + +import ( + "github.com/cosmos/cosmos-sdk/codec/types" + "github.com/cosmos/cosmos-sdk/testutil/testdata" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth/ante" + "github.com/cosmos/cosmos-sdk/x/auth/tx" +) + +func (suite *AnteTestSuite) TestRejectExtensionOptionsDecorator() { + suite.SetupTest(true) // setup + suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() + + reod := ante.NewRejectExtensionOptionsDecorator() + antehandler := sdk.ChainAnteDecorators(reod) + + // no extension options should not trigger an error + theTx := suite.txBuilder.GetTx() + _, err := antehandler(suite.ctx, theTx, false) + suite.Require().NoError(err) + + extOptsTxBldr, ok := suite.txBuilder.(tx.ExtensionOptionsTxBuilder) + if !ok { + // if we can't set extension options, this decorator doesn't apply and we're done + return + } + + // setting any extension option should cause an error + any, err := types.NewAnyWithValue(testdata.NewTestMsg()) + suite.Require().NoError(err) + extOptsTxBldr.SetExtensionOptions(any) + theTx = suite.txBuilder.GetTx() + _, err = antehandler(suite.ctx, theTx, false) + suite.Require().EqualError(err, "unknown extension options") +} diff --git a/x/auth/ante/fee.go b/x/auth/ante/fee.go new file mode 100644 index 00000000..936fd309 --- /dev/null +++ b/x/auth/ante/fee.go @@ -0,0 +1,141 @@ +package ante + +import ( + "fmt" + evmtypes "github.com/tharsis/ethermint/x/evm/types" + + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + "github.com/cosmos/cosmos-sdk/x/auth/types" +) + +// MempoolFeeDecorator will check if the transaction's fee is at least as large +// as the local validator's minimum gasFee (defined in validator config). +// If fee is too low, decorator returns error and tx is rejected from mempool. +// Note this only applies when ctx.CheckTx = true +// If fee is high enough or not CheckTx, then call next AnteHandler +// CONTRACT: Tx must implement FeeTx to use MempoolFeeDecorator +type MempoolFeeDecorator struct{} + +func NewMempoolFeeDecorator() MempoolFeeDecorator { + return MempoolFeeDecorator{} +} + +func (mfd MempoolFeeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) { + feeTx, ok := tx.(sdk.FeeTx) + if !ok { + return ctx, sdkerrors.Wrap(sdkerrors.ErrTxDecode, "Tx must be a FeeTx") + } + + feeCoins := feeTx.GetFee() + gas := feeTx.GetGas() + + // Ensure that the provided fees meet a minimum threshold for the validator, + // if this is a CheckTx. This is only for local mempool purposes, and thus + // is only ran on check tx. + if ctx.IsCheckTx() && !simulate { + minGasPrices := ctx.MinGasPrices() + if !minGasPrices.IsZero() { + requiredFees := make(sdk.Coins, len(minGasPrices)) + + // Determine the required fees by multiplying each required minimum gas + // price by the gas limit, where fee = ceil(minGasPrice * gasLimit). + glDec := sdk.NewDec(int64(gas)) + for i, gp := range minGasPrices { + fee := gp.Amount.Mul(glDec) + requiredFees[i] = sdk.NewCoin(gp.Denom, fee.Ceil().RoundInt()) + } + + if !feeCoins.IsAnyGTE(requiredFees) { + return ctx, sdkerrors.Wrapf(sdkerrors.ErrInsufficientFee, "insufficient fees; got: %s required: %s", feeCoins, requiredFees) + } + } + } + + return next(ctx, tx, simulate) +} + +// DeductFeeDecorator deducts fees from the first signer of the tx +// If the first signer does not have the funds to pay for the fees, return with InsufficientFunds error +// Call next AnteHandler if fees successfully deducted +// CONTRACT: Tx must implement FeeTx interface to use DeductFeeDecorator +type DeductFeeDecorator struct { + ak AccountKeeper + bankKeeper evmtypes.BankKeeper + feegrantKeeper FeegrantKeeper +} + +func NewDeductFeeDecorator(ak AccountKeeper, bk evmtypes.BankKeeper, fk FeegrantKeeper) DeductFeeDecorator { + return DeductFeeDecorator{ + ak: ak, + bankKeeper: bk, + feegrantKeeper: fk, + } +} + +func (dfd DeductFeeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) { + feeTx, ok := tx.(sdk.FeeTx) + if !ok { + return ctx, sdkerrors.Wrap(sdkerrors.ErrTxDecode, "Tx must be a FeeTx") + } + + if addr := dfd.ak.GetModuleAddress(types.FeeCollectorName); addr == nil { + panic(fmt.Sprintf("%s module account has not been set", types.FeeCollectorName)) + } + + fee := feeTx.GetFee() + feePayer := feeTx.FeePayer() + feeGranter := feeTx.FeeGranter() + + deductFeesFrom := feePayer + + // if feegranter set deduct fee from feegranter account. + // this works with only when feegrant enabled. + if feeGranter != nil { + if dfd.feegrantKeeper == nil { + return ctx, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "fee grants are not enabled") + } else if !feeGranter.Equals(feePayer) { + err := dfd.feegrantKeeper.UseGrantedFees(ctx, feeGranter, feePayer, fee, tx.GetMsgs()) + + if err != nil { + return ctx, sdkerrors.Wrapf(err, "%s not allowed to pay fees from %s", feeGranter, feePayer) + } + } + + deductFeesFrom = feeGranter + } + + deductFeesFromAcc := dfd.ak.GetAccount(ctx, deductFeesFrom) + if deductFeesFromAcc == nil { + return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnknownAddress, "fee payer address: %s does not exist", deductFeesFrom) + } + + // deduct the fees + if !feeTx.GetFee().IsZero() { + err = DeductFees(dfd.bankKeeper, ctx, deductFeesFromAcc, feeTx.GetFee()) + if err != nil { + return ctx, err + } + } + + events := sdk.Events{sdk.NewEvent(sdk.EventTypeTx, + sdk.NewAttribute(sdk.AttributeKeyFee, feeTx.GetFee().String()), + )} + ctx.EventManager().EmitEvents(events) + + return next(ctx, tx, simulate) +} + +// DeductFees deducts fees from the given account. +func DeductFees(bankKeeper evmtypes.BankKeeper, ctx sdk.Context, acc types.AccountI, fees sdk.Coins) error { + if !fees.IsValid() { + return sdkerrors.Wrapf(sdkerrors.ErrInsufficientFee, "invalid fee amount: %s", fees) + } + + err := bankKeeper.SendCoinsFromAccountToModule(ctx, acc.GetAddress(), types.FeeCollectorName, fees) + if err != nil { + return sdkerrors.Wrapf(sdkerrors.ErrInsufficientFunds, err.Error()) + } + + return nil +} diff --git a/x/auth/ante/fee_test.go b/x/auth/ante/fee_test.go new file mode 100644 index 00000000..7edd8fad --- /dev/null +++ b/x/auth/ante/fee_test.go @@ -0,0 +1,104 @@ +package ante_test + +import ( + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + "github.com/cosmos/cosmos-sdk/simapp" + "github.com/cosmos/cosmos-sdk/testutil/testdata" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth/ante" +) + +func (suite *AnteTestSuite) TestEnsureMempoolFees() { + suite.SetupTest(true) // setup + suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() + + mfd := ante.NewMempoolFeeDecorator() + antehandler := sdk.ChainAnteDecorators(mfd) + + // keys and addresses + priv1, _, addr1 := testdata.KeyTestPubAddr() + + // msg and signatures + msg := testdata.NewTestMsg(addr1) + feeAmount := testdata.NewTestFeeAmount() + gasLimit := testdata.NewTestGasLimit() + suite.Require().NoError(suite.txBuilder.SetMsgs(msg)) + suite.txBuilder.SetFeeAmount(feeAmount) + suite.txBuilder.SetGasLimit(gasLimit) + + privs, accNums, accSeqs := []cryptotypes.PrivKey{priv1}, []uint64{0}, []uint64{0} + tx, err := suite.CreateTestTx(privs, accNums, accSeqs, suite.ctx.ChainID()) + suite.Require().NoError(err) + + // Set high gas price so standard test fee fails + atomPrice := sdk.NewDecCoinFromDec("atom", sdk.NewDec(200).Quo(sdk.NewDec(100000))) + highGasPrice := []sdk.DecCoin{atomPrice} + suite.ctx = suite.ctx.WithMinGasPrices(highGasPrice) + + // Set IsCheckTx to true + suite.ctx = suite.ctx.WithIsCheckTx(true) + + // antehandler errors with insufficient fees + _, err = antehandler(suite.ctx, tx, false) + suite.Require().NotNil(err, "Decorator should have errored on too low fee for local gasPrice") + + // Set IsCheckTx to false + suite.ctx = suite.ctx.WithIsCheckTx(false) + + // antehandler should not error since we do not check minGasPrice in DeliverTx + _, err = antehandler(suite.ctx, tx, false) + suite.Require().Nil(err, "MempoolFeeDecorator returned error in DeliverTx") + + // Set IsCheckTx back to true for testing sufficient mempool fee + suite.ctx = suite.ctx.WithIsCheckTx(true) + + atomPrice = sdk.NewDecCoinFromDec("atom", sdk.NewDec(0).Quo(sdk.NewDec(100000))) + lowGasPrice := []sdk.DecCoin{atomPrice} + suite.ctx = suite.ctx.WithMinGasPrices(lowGasPrice) + + _, err = antehandler(suite.ctx, tx, false) + suite.Require().Nil(err, "Decorator should not have errored on fee higher than local gasPrice") +} + +func (suite *AnteTestSuite) TestDeductFees() { + suite.SetupTest(false) // setup + suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() + + // keys and addresses + priv1, _, addr1 := testdata.KeyTestPubAddr() + + // msg and signatures + msg := testdata.NewTestMsg(addr1) + feeAmount := testdata.NewTestFeeAmount() + gasLimit := testdata.NewTestGasLimit() + suite.Require().NoError(suite.txBuilder.SetMsgs(msg)) + suite.txBuilder.SetFeeAmount(feeAmount) + suite.txBuilder.SetGasLimit(gasLimit) + + privs, accNums, accSeqs := []cryptotypes.PrivKey{priv1}, []uint64{0}, []uint64{0} + tx, err := suite.CreateTestTx(privs, accNums, accSeqs, suite.ctx.ChainID()) + suite.Require().NoError(err) + + // Set account with insufficient funds + acc := suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, addr1) + suite.app.AccountKeeper.SetAccount(suite.ctx, acc) + coins := sdk.NewCoins(sdk.NewCoin("atom", sdk.NewInt(10))) + err = simapp.FundAccount(suite.app.BankKeeper, suite.ctx, addr1, coins) + suite.Require().NoError(err) + + dfd := ante.NewDeductFeeDecorator(suite.app.AccountKeeper, suite.app.BankKeeper, nil) + antehandler := sdk.ChainAnteDecorators(dfd) + + _, err = antehandler(suite.ctx, tx, false) + + suite.Require().NotNil(err, "Tx did not error when fee payer had insufficient funds") + + // Set account with sufficient funds + suite.app.AccountKeeper.SetAccount(suite.ctx, acc) + err = simapp.FundAccount(suite.app.BankKeeper, suite.ctx, addr1, sdk.NewCoins(sdk.NewCoin("atom", sdk.NewInt(200)))) + suite.Require().NoError(err) + + _, err = antehandler(suite.ctx, tx, false) + + suite.Require().Nil(err, "Tx errored after account has been set with sufficient funds") +} diff --git a/x/auth/ante/feegrant_test.go b/x/auth/ante/feegrant_test.go new file mode 100644 index 00000000..02207801 --- /dev/null +++ b/x/auth/ante/feegrant_test.go @@ -0,0 +1,233 @@ +package ante_test + +/* TODO: fix these tests +import ( + "math/rand" + "testing" + "time" + + "github.com/tendermint/tendermint/crypto" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/codec" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + + "github.com/cosmos/cosmos-sdk/simapp" + "github.com/cosmos/cosmos-sdk/simapp/helpers" + "github.com/cosmos/cosmos-sdk/testutil/testdata" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/simulation" + "github.com/cosmos/cosmos-sdk/types/tx/signing" + ante "github.com/tharsis/ethermint/x/auth/ante" + authsign "github.com/cosmos/cosmos-sdk/x/auth/signing" + "github.com/cosmos/cosmos-sdk/x/auth/tx" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + "github.com/cosmos/cosmos-sdk/x/feegrant" +) + +func (suite *AnteTestSuite) TestDeductFeesNoDelegation() { + suite.SetupTest(false) + // setup + app, ctx := suite.app, suite.ctx + + protoTxCfg := tx.NewTxConfig(codec.NewProtoCodec(app.InterfaceRegistry()), tx.DefaultSignModes) + + // this just tests our handler + dfd := ante.NewDeductFeeDecorator(app.AccountKeeper, app.BankKeeper, app.FeeGrantKeeper) + feeAnteHandler := sdk.ChainAnteDecorators(dfd) + + // this tests the whole stack + anteHandlerStack := suite.anteHandler + + // keys and addresses + priv1, _, addr1 := testdata.KeyTestPubAddr() + priv2, _, addr2 := testdata.KeyTestPubAddr() + priv3, _, addr3 := testdata.KeyTestPubAddr() + priv4, _, addr4 := testdata.KeyTestPubAddr() + priv5, _, addr5 := testdata.KeyTestPubAddr() + + // Set addr1 with insufficient funds + err := simapp.FundAccount(suite.app.BankKeeper, suite.ctx, addr1, []sdk.Coin{sdk.NewCoin("atom", sdk.NewInt(10))}) + suite.Require().NoError(err) + + // Set addr2 with more funds + err = simapp.FundAccount(suite.app.BankKeeper, suite.ctx, addr2, []sdk.Coin{sdk.NewCoin("atom", sdk.NewInt(99999))}) + suite.Require().NoError(err) + + // grant fee allowance from `addr2` to `addr3` (plenty to pay) + err = app.FeeGrantKeeper.GrantAllowance(ctx, addr2, addr3, &feegrant.BasicAllowance{ + SpendLimit: sdk.NewCoins(sdk.NewInt64Coin("atom", 500)), + }) + suite.Require().NoError(err) + + // grant low fee allowance (20atom), to check the tx requesting more than allowed. + err = app.FeeGrantKeeper.GrantAllowance(ctx, addr2, addr4, &feegrant.BasicAllowance{ + SpendLimit: sdk.NewCoins(sdk.NewInt64Coin("atom", 20)), + }) + suite.Require().NoError(err) + + cases := map[string]struct { + signerKey cryptotypes.PrivKey + signer sdk.AccAddress + feeAccount sdk.AccAddress + fee int64 + valid bool + }{ + "paying with low funds": { + signerKey: priv1, + signer: addr1, + fee: 50, + valid: false, + }, + "paying with good funds": { + signerKey: priv2, + signer: addr2, + fee: 50, + valid: true, + }, + "paying with no account": { + signerKey: priv3, + signer: addr3, + fee: 1, + valid: false, + }, + "no fee with real account": { + signerKey: priv1, + signer: addr1, + fee: 0, + valid: true, + }, + "no fee with no account": { + signerKey: priv5, + signer: addr5, + fee: 0, + valid: false, + }, + "valid fee grant without account": { + signerKey: priv3, + signer: addr3, + feeAccount: addr2, + fee: 50, + valid: true, + }, + "no fee grant": { + signerKey: priv3, + signer: addr3, + feeAccount: addr1, + fee: 2, + valid: false, + }, + "allowance smaller than requested fee": { + signerKey: priv4, + signer: addr4, + feeAccount: addr2, + fee: 50, + valid: false, + }, + "granter cannot cover allowed fee grant": { + signerKey: priv4, + signer: addr4, + feeAccount: addr1, + fee: 50, + valid: false, + }, + } + + for name, stc := range cases { + tc := stc // to make scopelint happy + suite.T().Run(name, func(t *testing.T) { + fee := sdk.NewCoins(sdk.NewInt64Coin("atom", tc.fee)) + msgs := []sdk.Msg{testdata.NewTestMsg(tc.signer)} + + acc := app.AccountKeeper.GetAccount(ctx, tc.signer) + privs, accNums, seqs := []cryptotypes.PrivKey{tc.signerKey}, []uint64{0}, []uint64{0} + if acc != nil { + accNums, seqs = []uint64{acc.GetAccountNumber()}, []uint64{acc.GetSequence()} + } + + tx, err := genTxWithFeeGranter(protoTxCfg, msgs, fee, helpers.DefaultGenTxGas, ctx.ChainID(), accNums, seqs, tc.feeAccount, privs...) + suite.Require().NoError(err) + _, err = feeAnteHandler(ctx, tx, false) // tests only feegrant ante + if tc.valid { + suite.Require().NoError(err) + } else { + suite.Require().Error(err) + } + + _, err = anteHandlerStack(ctx, tx, false) // tests while stack + if tc.valid { + suite.Require().NoError(err) + } else { + suite.Require().Error(err) + } + }) + } +} + +// don't consume any gas +func SigGasNoConsumer(meter sdk.GasMeter, sig []byte, pubkey crypto.PubKey, params authtypes.Params) error { + return nil +} + +func genTxWithFeeGranter(gen client.TxConfig, msgs []sdk.Msg, feeAmt sdk.Coins, gas uint64, chainID string, accNums, + accSeqs []uint64, feeGranter sdk.AccAddress, priv ...cryptotypes.PrivKey) (sdk.Tx, error) { + sigs := make([]signing.SignatureV2, len(priv)) + + // create a random length memo + r := rand.New(rand.NewSource(time.Now().UnixNano())) + + memo := simulation.RandStringOfLength(r, simulation.RandIntBetween(r, 0, 100)) + + signMode := gen.SignModeHandler().DefaultMode() + + // 1st round: set SignatureV2 with empty signatures, to set correct + // signer infos. + for i, p := range priv { + sigs[i] = signing.SignatureV2{ + PubKey: p.PubKey(), + Data: &signing.SingleSignatureData{ + SignMode: signMode, + }, + Sequence: accSeqs[i], + } + } + + tx := gen.NewTxBuilder() + err := tx.SetMsgs(msgs...) + if err != nil { + return nil, err + } + err = tx.SetSignatures(sigs...) + if err != nil { + return nil, err + } + tx.SetMemo(memo) + tx.SetFeeAmount(feeAmt) + tx.SetGasLimit(gas) + tx.SetFeeGranter(feeGranter) + + // 2nd round: once all signer infos are set, every signer can sign. + for i, p := range priv { + signerData := authsign.SignerData{ + ChainID: chainID, + AccountNumber: accNums[i], + Sequence: accSeqs[i], + } + signBytes, err := gen.SignModeHandler().GetSignBytes(signMode, signerData, tx.GetTx()) + if err != nil { + panic(err) + } + sig, err := p.Sign(signBytes) + if err != nil { + panic(err) + } + sigs[i].Data.(*signing.SingleSignatureData).Signature = sig + err = tx.SetSignatures(sigs...) + if err != nil { + panic(err) + } + } + + return tx.GetTx(), nil +} +*/ diff --git a/x/auth/ante/setup.go b/x/auth/ante/setup.go new file mode 100644 index 00000000..5f21aba8 --- /dev/null +++ b/x/auth/ante/setup.go @@ -0,0 +1,76 @@ +package ante + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + "github.com/cosmos/cosmos-sdk/x/auth/legacy/legacytx" +) + +var ( + _ GasTx = (*legacytx.StdTx)(nil) // assert StdTx implements GasTx +) + +// GasTx defines a Tx with a GetGas() method which is needed to use SetUpContextDecorator +type GasTx interface { + sdk.Tx + GetGas() uint64 +} + +// SetUpContextDecorator sets the GasMeter in the Context and wraps the next AnteHandler with a defer clause +// to recover from any downstream OutOfGas panics in the AnteHandler chain to return an error with information +// on gas provided and gas used. +// CONTRACT: Must be first decorator in the chain +// CONTRACT: Tx must implement GasTx interface +type SetUpContextDecorator struct{} + +func NewSetUpContextDecorator() SetUpContextDecorator { + return SetUpContextDecorator{} +} + +func (sud SetUpContextDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) { + // all transactions must implement GasTx + gasTx, ok := tx.(GasTx) + if !ok { + // Set a gas meter with limit 0 as to prevent an infinite gas meter attack + // during runTx. + newCtx = SetGasMeter(simulate, ctx, 0) + return newCtx, sdkerrors.Wrap(sdkerrors.ErrTxDecode, "Tx must be GasTx") + } + + newCtx = SetGasMeter(simulate, ctx, gasTx.GetGas()) + + // Decorator will catch an OutOfGasPanic caused in the next antehandler + // AnteHandlers must have their own defer/recover in order for the BaseApp + // to know how much gas was used! This is because the GasMeter is created in + // the AnteHandler, but if it panics the context won't be set properly in + // runTx's recover call. + defer func() { + if r := recover(); r != nil { + switch rType := r.(type) { + case sdk.ErrorOutOfGas: + log := fmt.Sprintf( + "out of gas in location: %v; gasWanted: %d, gasUsed: %d", + rType.Descriptor, gasTx.GetGas(), newCtx.GasMeter().GasConsumed()) + + err = sdkerrors.Wrap(sdkerrors.ErrOutOfGas, log) + default: + panic(r) + } + } + }() + + return next(newCtx, tx, simulate) +} + +// SetGasMeter returns a new context with a gas meter set from a given context. +func SetGasMeter(simulate bool, ctx sdk.Context, gasLimit uint64) sdk.Context { + // In various cases such as simulation and during the genesis block, we do not + // meter any gas utilization. + if simulate || ctx.BlockHeight() == 0 { + return ctx.WithGasMeter(sdk.NewInfiniteGasMeter()) + } + + return ctx.WithGasMeter(sdk.NewGasMeter(gasLimit)) +} diff --git a/x/auth/ante/setup_test.go b/x/auth/ante/setup_test.go new file mode 100644 index 00000000..4942665c --- /dev/null +++ b/x/auth/ante/setup_test.go @@ -0,0 +1,99 @@ +package ante_test + +import ( + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + "github.com/cosmos/cosmos-sdk/testutil/testdata" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + "github.com/cosmos/cosmos-sdk/x/auth/ante" +) + +func (suite *AnteTestSuite) TestSetup() { + suite.SetupTest(true) // setup + suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() + + // keys and addresses + priv1, _, addr1 := testdata.KeyTestPubAddr() + + // msg and signatures + msg := testdata.NewTestMsg(addr1) + feeAmount := testdata.NewTestFeeAmount() + gasLimit := testdata.NewTestGasLimit() + suite.Require().NoError(suite.txBuilder.SetMsgs(msg)) + suite.txBuilder.SetFeeAmount(feeAmount) + suite.txBuilder.SetGasLimit(gasLimit) + + privs, accNums, accSeqs := []cryptotypes.PrivKey{priv1}, []uint64{0}, []uint64{0} + tx, err := suite.CreateTestTx(privs, accNums, accSeqs, suite.ctx.ChainID()) + suite.Require().NoError(err) + + sud := ante.NewSetUpContextDecorator() + antehandler := sdk.ChainAnteDecorators(sud) + + // Set height to non-zero value for GasMeter to be set + suite.ctx = suite.ctx.WithBlockHeight(1) + + // Context GasMeter Limit not set + suite.Require().Equal(uint64(0), suite.ctx.GasMeter().Limit(), "GasMeter set with limit before setup") + + newCtx, err := antehandler(suite.ctx, tx, false) + suite.Require().Nil(err, "SetUpContextDecorator returned error") + + // Context GasMeter Limit should be set after SetUpContextDecorator runs + suite.Require().Equal(gasLimit, newCtx.GasMeter().Limit(), "GasMeter not set correctly") +} + +func (suite *AnteTestSuite) TestRecoverPanic() { + suite.SetupTest(true) // setup + suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() + + // keys and addresses + priv1, _, addr1 := testdata.KeyTestPubAddr() + + // msg and signatures + msg := testdata.NewTestMsg(addr1) + feeAmount := testdata.NewTestFeeAmount() + gasLimit := testdata.NewTestGasLimit() + suite.Require().NoError(suite.txBuilder.SetMsgs(msg)) + suite.txBuilder.SetFeeAmount(feeAmount) + suite.txBuilder.SetGasLimit(gasLimit) + + privs, accNums, accSeqs := []cryptotypes.PrivKey{priv1}, []uint64{0}, []uint64{0} + tx, err := suite.CreateTestTx(privs, accNums, accSeqs, suite.ctx.ChainID()) + suite.Require().NoError(err) + + sud := ante.NewSetUpContextDecorator() + antehandler := sdk.ChainAnteDecorators(sud, OutOfGasDecorator{}) + + // Set height to non-zero value for GasMeter to be set + suite.ctx = suite.ctx.WithBlockHeight(1) + + newCtx, err := antehandler(suite.ctx, tx, false) + + suite.Require().NotNil(err, "Did not return error on OutOfGas panic") + + suite.Require().True(sdkerrors.ErrOutOfGas.Is(err), "Returned error is not an out of gas error") + suite.Require().Equal(gasLimit, newCtx.GasMeter().Limit()) + + antehandler = sdk.ChainAnteDecorators(sud, PanicDecorator{}) + suite.Require().Panics(func() { antehandler(suite.ctx, tx, false) }, "Recovered from non-Out-of-Gas panic") // nolint:errcheck +} + +type OutOfGasDecorator struct{} + +// AnteDecorator that will throw OutOfGas panic +func (ogd OutOfGasDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) { + overLimit := ctx.GasMeter().Limit() + 1 + + // Should panic with outofgas error + ctx.GasMeter().ConsumeGas(overLimit, "test panic") + + // not reached + return next(ctx, tx, simulate) +} + +type PanicDecorator struct{} + +func (pd PanicDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) { + panic("random error") +} diff --git a/x/auth/ante/sigverify.go b/x/auth/ante/sigverify.go new file mode 100644 index 00000000..8ff8ee8d --- /dev/null +++ b/x/auth/ante/sigverify.go @@ -0,0 +1,510 @@ +package ante + +import ( + "bytes" + "encoding/base64" + "encoding/hex" + "fmt" + + "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" + kmultisig "github.com/cosmos/cosmos-sdk/crypto/keys/multisig" + "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + "github.com/cosmos/cosmos-sdk/crypto/keys/secp256r1" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + "github.com/cosmos/cosmos-sdk/crypto/types/multisig" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + "github.com/cosmos/cosmos-sdk/types/tx/signing" + "github.com/cosmos/cosmos-sdk/x/auth/legacy/legacytx" + authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing" + "github.com/cosmos/cosmos-sdk/x/auth/types" +) + +var ( + // simulation signature values used to estimate gas consumption + key = make([]byte, secp256k1.PubKeySize) + simSecp256k1Pubkey = &secp256k1.PubKey{Key: key} + simSecp256k1Sig [64]byte + + _ authsigning.SigVerifiableTx = (*legacytx.StdTx)(nil) // assert StdTx implements SigVerifiableTx +) + +func init() { + // This decodes a valid hex string into a sepc256k1Pubkey for use in transaction simulation + bz, _ := hex.DecodeString("035AD6810A47F073553FF30D2FCC7E0D3B1C0B74B61A1AAA2582344037151E143A") + copy(key, bz) + simSecp256k1Pubkey.Key = key +} + +// SignatureVerificationGasConsumer is the type of function that is used to both +// consume gas when verifying signatures and also to accept or reject different types of pubkeys +// This is where apps can define their own PubKey +type SignatureVerificationGasConsumer = func(meter sdk.GasMeter, sig signing.SignatureV2, params types.Params) error + +// SetPubKeyDecorator sets PubKeys in context for any signer which does not already have pubkey set +// PubKeys must be set in context for all signers before any other sigverify decorators run +// CONTRACT: Tx must implement SigVerifiableTx interface +type SetPubKeyDecorator struct { + ak AccountKeeper +} + +func NewSetPubKeyDecorator(ak AccountKeeper) SetPubKeyDecorator { + return SetPubKeyDecorator{ + ak: ak, + } +} + +func (spkd SetPubKeyDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) { + sigTx, ok := tx.(authsigning.SigVerifiableTx) + if !ok { + return ctx, sdkerrors.Wrap(sdkerrors.ErrTxDecode, "invalid tx type") + } + + pubkeys, err := sigTx.GetPubKeys() + if err != nil { + return ctx, err + } + signers := sigTx.GetSigners() + + for i, pk := range pubkeys { + // PublicKey was omitted from slice since it has already been set in context + if pk == nil { + if !simulate { + continue + } + pk = simSecp256k1Pubkey + } + // Only make check if simulate=false + if !simulate && !bytes.Equal(pk.Address(), signers[i]) { + return ctx, sdkerrors.Wrapf(sdkerrors.ErrInvalidPubKey, + "pubKey does not match signer address %s with signer index: %d", signers[i], i) + } + + acc, err := GetSignerAcc(ctx, spkd.ak, signers[i]) + if err != nil { + return ctx, err + } + // account already has pubkey set,no need to reset + if acc.GetPubKey() != nil { + continue + } + err = acc.SetPubKey(pk) + if err != nil { + return ctx, sdkerrors.Wrap(sdkerrors.ErrInvalidPubKey, err.Error()) + } + spkd.ak.SetAccount(ctx, acc) + } + + // Also emit the following events, so that txs can be indexed by these + // indices: + // - signature (via `tx.signature=''`), + // - concat(address,"/",sequence) (via `tx.acc_seq='cosmos1abc...def/42'`). + sigs, err := sigTx.GetSignaturesV2() + if err != nil { + return ctx, err + } + + var events sdk.Events + for i, sig := range sigs { + events = append(events, sdk.NewEvent(sdk.EventTypeTx, + sdk.NewAttribute(sdk.AttributeKeyAccountSequence, fmt.Sprintf("%s/%d", signers[i], sig.Sequence)), + )) + + sigBzs, err := signatureDataToBz(sig.Data) + if err != nil { + return ctx, err + } + for _, sigBz := range sigBzs { + events = append(events, sdk.NewEvent(sdk.EventTypeTx, + sdk.NewAttribute(sdk.AttributeKeySignature, base64.StdEncoding.EncodeToString(sigBz)), + )) + } + } + + ctx.EventManager().EmitEvents(events) + + return next(ctx, tx, simulate) +} + +// Consume parameter-defined amount of gas for each signature according to the passed-in SignatureVerificationGasConsumer function +// before calling the next AnteHandler +// CONTRACT: Pubkeys are set in context for all signers before this decorator runs +// CONTRACT: Tx must implement SigVerifiableTx interface +type SigGasConsumeDecorator struct { + ak AccountKeeper + sigGasConsumer SignatureVerificationGasConsumer +} + +func NewSigGasConsumeDecorator(ak AccountKeeper, sigGasConsumer SignatureVerificationGasConsumer) SigGasConsumeDecorator { + return SigGasConsumeDecorator{ + ak: ak, + sigGasConsumer: sigGasConsumer, + } +} + +func (sgcd SigGasConsumeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) { + sigTx, ok := tx.(authsigning.SigVerifiableTx) + if !ok { + return ctx, sdkerrors.Wrap(sdkerrors.ErrTxDecode, "invalid transaction type") + } + + params := sgcd.ak.GetParams(ctx) + sigs, err := sigTx.GetSignaturesV2() + if err != nil { + return ctx, err + } + + // stdSigs contains the sequence number, account number, and signatures. + // When simulating, this would just be a 0-length slice. + signerAddrs := sigTx.GetSigners() + + for i, sig := range sigs { + signerAcc, err := GetSignerAcc(ctx, sgcd.ak, signerAddrs[i]) + if err != nil { + return ctx, err + } + + pubKey := signerAcc.GetPubKey() + + // In simulate mode the transaction comes with no signatures, thus if the + // account's pubkey is nil, both signature verification and gasKVStore.Set() + // shall consume the largest amount, i.e. it takes more gas to verify + // secp256k1 keys than ed25519 ones. + if simulate && pubKey == nil { + pubKey = simSecp256k1Pubkey + } + + // make a SignatureV2 with PubKey filled in from above + sig = signing.SignatureV2{ + PubKey: pubKey, + Data: sig.Data, + Sequence: sig.Sequence, + } + + err = sgcd.sigGasConsumer(ctx.GasMeter(), sig, params) + if err != nil { + return ctx, err + } + } + + return next(ctx, tx, simulate) +} + +// Verify all signatures for a tx and return an error if any are invalid. Note, +// the SigVerificationDecorator decorator will not get executed on ReCheck. +// +// CONTRACT: Pubkeys are set in context for all signers before this decorator runs +// CONTRACT: Tx must implement SigVerifiableTx interface +type SigVerificationDecorator struct { + ak AccountKeeper + signModeHandler authsigning.SignModeHandler +} + +func NewSigVerificationDecorator(ak AccountKeeper, signModeHandler authsigning.SignModeHandler) SigVerificationDecorator { + return SigVerificationDecorator{ + ak: ak, + signModeHandler: signModeHandler, + } +} + +// OnlyLegacyAminoSigners checks SignatureData to see if all +// signers are using SIGN_MODE_LEGACY_AMINO_JSON. If this is the case +// then the corresponding SignatureV2 struct will not have account sequence +// explicitly set, and we should skip the explicit verification of sig.Sequence +// in the SigVerificationDecorator's AnteHandler function. +func OnlyLegacyAminoSigners(sigData signing.SignatureData) bool { + switch v := sigData.(type) { + case *signing.SingleSignatureData: + return v.SignMode == signing.SignMode_SIGN_MODE_LEGACY_AMINO_JSON + case *signing.MultiSignatureData: + for _, s := range v.Signatures { + if !OnlyLegacyAminoSigners(s) { + return false + } + } + return true + default: + return false + } +} + +func (svd SigVerificationDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) { + // no need to verify signatures on recheck tx + if ctx.IsReCheckTx() { + return next(ctx, tx, simulate) + } + sigTx, ok := tx.(authsigning.SigVerifiableTx) + if !ok { + return ctx, sdkerrors.Wrap(sdkerrors.ErrTxDecode, "invalid transaction type") + } + + // stdSigs contains the sequence number, account number, and signatures. + // When simulating, this would just be a 0-length slice. + sigs, err := sigTx.GetSignaturesV2() + if err != nil { + return ctx, err + } + + signerAddrs := sigTx.GetSigners() + + // check that signer length and signature length are the same + if len(sigs) != len(signerAddrs) { + return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnauthorized, "invalid number of signer; expected: %d, got %d", len(signerAddrs), len(sigs)) + } + + for i, sig := range sigs { + acc, err := GetSignerAcc(ctx, svd.ak, signerAddrs[i]) + if err != nil { + return ctx, err + } + + // retrieve pubkey + pubKey := acc.GetPubKey() + if !simulate && pubKey == nil { + return ctx, sdkerrors.Wrap(sdkerrors.ErrInvalidPubKey, "pubkey on account is not set") + } + + // Check account sequence number. + if sig.Sequence != acc.GetSequence() { + return ctx, sdkerrors.Wrapf( + sdkerrors.ErrWrongSequence, + "account sequence mismatch, expected %d, got %d", acc.GetSequence(), sig.Sequence, + ) + } + + // retrieve signer data + genesis := ctx.BlockHeight() == 0 + chainID := ctx.ChainID() + var accNum uint64 + if !genesis { + accNum = acc.GetAccountNumber() + } + signerData := authsigning.SignerData{ + ChainID: chainID, + AccountNumber: accNum, + Sequence: acc.GetSequence(), + } + + if !simulate { + err := authsigning.VerifySignature(pubKey, signerData, sig.Data, svd.signModeHandler, tx) + if err != nil { + var errMsg string + if OnlyLegacyAminoSigners(sig.Data) { + // If all signers are using SIGN_MODE_LEGACY_AMINO, we rely on VerifySignature to check account sequence number, + // and therefore communicate sequence number as a potential cause of error. + errMsg = fmt.Sprintf("signature verification failed; please verify account number (%d), sequence (%d) and chain-id (%s)", accNum, acc.GetSequence(), chainID) + } else { + errMsg = fmt.Sprintf("signature verification failed; please verify account number (%d) and chain-id (%s)", accNum, chainID) + } + return ctx, sdkerrors.Wrap(sdkerrors.ErrUnauthorized, errMsg) + + } + } + } + + return next(ctx, tx, simulate) +} + +// IncrementSequenceDecorator handles incrementing sequences of all signers. +// Use the IncrementSequenceDecorator decorator to prevent replay attacks. Note, +// there is no need to execute IncrementSequenceDecorator on RecheckTX since +// CheckTx would already bump the sequence number. +// +// NOTE: Since CheckTx and DeliverTx state are managed separately, subsequent and +// sequential txs orginating from the same account cannot be handled correctly in +// a reliable way unless sequence numbers are managed and tracked manually by a +// client. It is recommended to instead use multiple messages in a tx. +type IncrementSequenceDecorator struct { + ak AccountKeeper +} + +func NewIncrementSequenceDecorator(ak 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, sdkerrors.Wrap(sdkerrors.ErrTxDecode, "invalid transaction type") + } + + // increment sequence of all signers + for _, addr := range sigTx.GetSigners() { + acc := isd.ak.GetAccount(ctx, addr) + if err := acc.SetSequence(acc.GetSequence() + 1); err != nil { + panic(err) + } + + isd.ak.SetAccount(ctx, acc) + } + + return next(ctx, tx, simulate) +} + +// ValidateSigCountDecorator takes in Params and returns errors if there are too many signatures in the tx for the given params +// otherwise it calls next AnteHandler +// Use this decorator to set parameterized limit on number of signatures in tx +// CONTRACT: Tx must implement SigVerifiableTx interface +type ValidateSigCountDecorator struct { + ak AccountKeeper +} + +func NewValidateSigCountDecorator(ak AccountKeeper) ValidateSigCountDecorator { + return ValidateSigCountDecorator{ + ak: ak, + } +} + +func (vscd ValidateSigCountDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) { + sigTx, ok := tx.(authsigning.SigVerifiableTx) + if !ok { + return ctx, sdkerrors.Wrap(sdkerrors.ErrTxDecode, "Tx must be a sigTx") + } + + params := vscd.ak.GetParams(ctx) + pubKeys, err := sigTx.GetPubKeys() + if err != nil { + return ctx, err + } + + sigCount := 0 + for _, pk := range pubKeys { + sigCount += CountSubKeys(pk) + if uint64(sigCount) > params.TxSigLimit { + return ctx, sdkerrors.Wrapf(sdkerrors.ErrTooManySignatures, + "signatures: %d, limit: %d", sigCount, params.TxSigLimit) + } + } + + return next(ctx, tx, simulate) +} + +// DefaultSigVerificationGasConsumer is the default implementation of SignatureVerificationGasConsumer. It consumes gas +// for signature verification based upon the public key type. The cost is fetched from the given params and is matched +// by the concrete type. +func DefaultSigVerificationGasConsumer( + meter sdk.GasMeter, sig signing.SignatureV2, params types.Params, +) error { + pubkey := sig.PubKey + switch pubkey := pubkey.(type) { + case *ed25519.PubKey: + meter.ConsumeGas(params.SigVerifyCostED25519, "ante verify: ed25519") + return sdkerrors.Wrap(sdkerrors.ErrInvalidPubKey, "ED25519 public keys are unsupported") + + case *secp256k1.PubKey: + meter.ConsumeGas(params.SigVerifyCostSecp256k1, "ante verify: secp256k1") + return nil + + case *secp256r1.PubKey: + meter.ConsumeGas(params.SigVerifyCostSecp256r1(), "ante verify: secp256r1") + return nil + + case multisig.PubKey: + multisignature, ok := sig.Data.(*signing.MultiSignatureData) + if !ok { + return fmt.Errorf("expected %T, got, %T", &signing.MultiSignatureData{}, sig.Data) + } + err := ConsumeMultisignatureVerificationGas(meter, multisignature, pubkey, params, sig.Sequence) + if err != nil { + return err + } + return nil + + default: + return sdkerrors.Wrapf(sdkerrors.ErrInvalidPubKey, "unrecognized public key type: %T", pubkey) + } +} + +// ConsumeMultisignatureVerificationGas consumes gas from a GasMeter for verifying a multisig pubkey signature +func ConsumeMultisignatureVerificationGas( + meter sdk.GasMeter, sig *signing.MultiSignatureData, pubkey multisig.PubKey, + params types.Params, accSeq uint64, +) error { + + size := sig.BitArray.Count() + sigIndex := 0 + + for i := 0; i < size; i++ { + if !sig.BitArray.GetIndex(i) { + continue + } + sigV2 := signing.SignatureV2{ + PubKey: pubkey.GetPubKeys()[i], + Data: sig.Signatures[sigIndex], + Sequence: accSeq, + } + err := DefaultSigVerificationGasConsumer(meter, sigV2, params) + if err != nil { + return err + } + sigIndex++ + } + + return nil +} + +// GetSignerAcc returns an account for a given address that is expected to sign +// a transaction. +func GetSignerAcc(ctx sdk.Context, ak AccountKeeper, addr sdk.AccAddress) (types.AccountI, error) { + if acc := ak.GetAccount(ctx, addr); acc != nil { + return acc, nil + } + + return nil, sdkerrors.Wrapf(sdkerrors.ErrUnknownAddress, "account %s does not exist", addr) +} + +// CountSubKeys counts the total number of keys for a multi-sig public key. +func CountSubKeys(pub cryptotypes.PubKey) int { + v, ok := pub.(*kmultisig.LegacyAminoPubKey) + if !ok { + return 1 + } + + numKeys := 0 + for _, subkey := range v.GetPubKeys() { + numKeys += CountSubKeys(subkey) + } + + return numKeys +} + +// signatureDataToBz converts a SignatureData into raw bytes signature. +// For SingleSignatureData, it returns the signature raw bytes. +// For MultiSignatureData, it returns an array of all individual signatures, +// as well as the aggregated signature. +func signatureDataToBz(data signing.SignatureData) ([][]byte, error) { + if data == nil { + return nil, fmt.Errorf("got empty SignatureData") + } + + switch data := data.(type) { + case *signing.SingleSignatureData: + return [][]byte{data.Signature}, nil + case *signing.MultiSignatureData: + sigs := [][]byte{} + var err error + + for _, d := range data.Signatures { + nestedSigs, err := signatureDataToBz(d) + if err != nil { + return nil, err + } + sigs = append(sigs, nestedSigs...) + } + + multisig := cryptotypes.MultiSignature{ + Signatures: sigs, + } + aggregatedSig, err := multisig.Marshal() + if err != nil { + return nil, err + } + sigs = append(sigs, aggregatedSig) + + return sigs, nil + default: + return nil, sdkerrors.ErrInvalidType.Wrapf("unexpected signature data type %T", data) + } +} diff --git a/x/auth/ante/sigverify_benchmark_test.go b/x/auth/ante/sigverify_benchmark_test.go new file mode 100644 index 00000000..56e596fa --- /dev/null +++ b/x/auth/ante/sigverify_benchmark_test.go @@ -0,0 +1,44 @@ +package ante_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + tmcrypto "github.com/tendermint/tendermint/crypto" + + "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + "github.com/cosmos/cosmos-sdk/crypto/keys/secp256r1" +) + +// This benchmark is used to asses the ante.Secp256k1ToR1GasFactor value +func BenchmarkSig(b *testing.B) { + require := require.New(b) + msg := tmcrypto.CRandBytes(1000) + + skK := secp256k1.GenPrivKey() + pkK := skK.PubKey() + skR, _ := secp256r1.GenPrivKey() + pkR := skR.PubKey() + + sigK, err := skK.Sign(msg) + require.NoError(err) + sigR, err := skR.Sign(msg) + require.NoError(err) + b.ResetTimer() + + b.Run("secp256k1", func(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + ok := pkK.VerifySignature(msg, sigK) + require.True(ok) + } + }) + + b.Run("secp256r1", func(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + ok := pkR.VerifySignature(msg, sigR) + require.True(ok) + } + }) +} diff --git a/x/auth/ante/sigverify_test.go b/x/auth/ante/sigverify_test.go new file mode 100644 index 00000000..6e720ac2 --- /dev/null +++ b/x/auth/ante/sigverify_test.go @@ -0,0 +1,390 @@ +package ante_test + +import ( + "fmt" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" + kmultisig "github.com/cosmos/cosmos-sdk/crypto/keys/multisig" + "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + "github.com/cosmos/cosmos-sdk/crypto/keys/secp256r1" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + "github.com/cosmos/cosmos-sdk/crypto/types/multisig" + "github.com/cosmos/cosmos-sdk/simapp" + "github.com/cosmos/cosmos-sdk/testutil/testdata" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/tx/signing" + "github.com/cosmos/cosmos-sdk/x/auth/ante" + "github.com/cosmos/cosmos-sdk/x/auth/legacy/legacytx" + "github.com/cosmos/cosmos-sdk/x/auth/types" +) + +func (suite *AnteTestSuite) TestSetPubKey() { + suite.SetupTest(true) // setup + require := suite.Require() + suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() + + // keys and addresses + priv1, pub1, addr1 := testdata.KeyTestPubAddr() + priv2, pub2, addr2 := testdata.KeyTestPubAddr() + priv3, pub3, addr3 := testdata.KeyTestPubAddrSecp256R1(require) + + addrs := []sdk.AccAddress{addr1, addr2, addr3} + pubs := []cryptotypes.PubKey{pub1, pub2, pub3} + + msgs := make([]sdk.Msg, len(addrs)) + // set accounts and create msg for each address + for i, addr := range addrs { + acc := suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, addr) + require.NoError(acc.SetAccountNumber(uint64(i))) + suite.app.AccountKeeper.SetAccount(suite.ctx, acc) + msgs[i] = testdata.NewTestMsg(addr) + } + require.NoError(suite.txBuilder.SetMsgs(msgs...)) + suite.txBuilder.SetFeeAmount(testdata.NewTestFeeAmount()) + suite.txBuilder.SetGasLimit(testdata.NewTestGasLimit()) + + privs, accNums, accSeqs := []cryptotypes.PrivKey{priv1, priv2, priv3}, []uint64{0, 1, 2}, []uint64{0, 0, 0} + tx, err := suite.CreateTestTx(privs, accNums, accSeqs, suite.ctx.ChainID()) + require.NoError(err) + + spkd := ante.NewSetPubKeyDecorator(suite.app.AccountKeeper) + antehandler := sdk.ChainAnteDecorators(spkd) + + ctx, err := antehandler(suite.ctx, tx, false) + require.NoError(err) + + // Require that all accounts have pubkey set after Decorator runs + for i, addr := range addrs { + pk, err := suite.app.AccountKeeper.GetPubKey(ctx, addr) + require.NoError(err, "Error on retrieving pubkey from account") + require.True(pubs[i].Equals(pk), + "Wrong Pubkey retrieved from AccountKeeper, idx=%d\nexpected=%s\n got=%s", i, pubs[i], pk) + } +} + +func (suite *AnteTestSuite) TestConsumeSignatureVerificationGas() { + params := types.DefaultParams() + msg := []byte{1, 2, 3, 4} + cdc := simapp.MakeTestEncodingConfig().Amino + + p := types.DefaultParams() + skR1, _ := secp256r1.GenPrivKey() + pkSet1, sigSet1 := generatePubKeysAndSignatures(5, msg, false) + multisigKey1 := kmultisig.NewLegacyAminoPubKey(2, pkSet1) + multisignature1 := multisig.NewMultisig(len(pkSet1)) + expectedCost1 := expectedGasCostByKeys(pkSet1) + for i := 0; i < len(pkSet1); i++ { + stdSig := legacytx.StdSignature{PubKey: pkSet1[i], Signature: sigSet1[i]} + sigV2, err := legacytx.StdSignatureToSignatureV2(cdc, stdSig) + suite.Require().NoError(err) + err = multisig.AddSignatureV2(multisignature1, sigV2, pkSet1) + suite.Require().NoError(err) + } + + type args struct { + meter sdk.GasMeter + sig signing.SignatureData + pubkey cryptotypes.PubKey + params types.Params + } + tests := []struct { + name string + args args + gasConsumed uint64 + shouldErr bool + }{ + {"PubKeyEd25519", args{sdk.NewInfiniteGasMeter(), nil, ed25519.GenPrivKey().PubKey(), params}, p.SigVerifyCostED25519, true}, + {"PubKeySecp256k1", args{sdk.NewInfiniteGasMeter(), nil, secp256k1.GenPrivKey().PubKey(), params}, p.SigVerifyCostSecp256k1, false}, + {"PubKeySecp256r1", args{sdk.NewInfiniteGasMeter(), nil, skR1.PubKey(), params}, p.SigVerifyCostSecp256r1(), false}, + {"Multisig", args{sdk.NewInfiniteGasMeter(), multisignature1, multisigKey1, params}, expectedCost1, false}, + {"unknown key", args{sdk.NewInfiniteGasMeter(), nil, nil, params}, 0, true}, + } + for _, tt := range tests { + sigV2 := signing.SignatureV2{ + PubKey: tt.args.pubkey, + Data: tt.args.sig, + Sequence: 0, // Arbitrary account sequence + } + err := ante.DefaultSigVerificationGasConsumer(tt.args.meter, sigV2, tt.args.params) + + if tt.shouldErr { + suite.Require().NotNil(err) + } else { + suite.Require().Nil(err) + suite.Require().Equal(tt.gasConsumed, tt.args.meter.GasConsumed(), fmt.Sprintf("%d != %d", tt.gasConsumed, tt.args.meter.GasConsumed())) + } + } +} + +func (suite *AnteTestSuite) TestSigVerification() { + suite.SetupTest(true) // setup + suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() + + // make block height non-zero to ensure account numbers part of signBytes + suite.ctx = suite.ctx.WithBlockHeight(1) + + // keys and addresses + priv1, _, addr1 := testdata.KeyTestPubAddr() + priv2, _, addr2 := testdata.KeyTestPubAddr() + priv3, _, addr3 := testdata.KeyTestPubAddr() + + addrs := []sdk.AccAddress{addr1, addr2, addr3} + + msgs := make([]sdk.Msg, len(addrs)) + // set accounts and create msg for each address + for i, addr := range addrs { + acc := suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, addr) + suite.Require().NoError(acc.SetAccountNumber(uint64(i))) + suite.app.AccountKeeper.SetAccount(suite.ctx, acc) + msgs[i] = testdata.NewTestMsg(addr) + } + + feeAmount := testdata.NewTestFeeAmount() + gasLimit := testdata.NewTestGasLimit() + + spkd := ante.NewSetPubKeyDecorator(suite.app.AccountKeeper) + svd := ante.NewSigVerificationDecorator(suite.app.AccountKeeper, suite.clientCtx.TxConfig.SignModeHandler()) + antehandler := sdk.ChainAnteDecorators(spkd, svd) + + type testCase struct { + name string + privs []cryptotypes.PrivKey + accNums []uint64 + accSeqs []uint64 + recheck bool + shouldErr bool + } + testCases := []testCase{ + {"no signers", []cryptotypes.PrivKey{}, []uint64{}, []uint64{}, false, true}, + {"not enough signers", []cryptotypes.PrivKey{priv1, priv2}, []uint64{0, 1}, []uint64{0, 0}, false, true}, + {"wrong order signers", []cryptotypes.PrivKey{priv3, priv2, priv1}, []uint64{2, 1, 0}, []uint64{0, 0, 0}, false, true}, + {"wrong accnums", []cryptotypes.PrivKey{priv1, priv2, priv3}, []uint64{7, 8, 9}, []uint64{0, 0, 0}, false, true}, + {"wrong sequences", []cryptotypes.PrivKey{priv1, priv2, priv3}, []uint64{0, 1, 2}, []uint64{3, 4, 5}, false, true}, + {"valid tx", []cryptotypes.PrivKey{priv1, priv2, priv3}, []uint64{0, 1, 2}, []uint64{0, 0, 0}, false, false}, + {"no err on recheck", []cryptotypes.PrivKey{}, []uint64{}, []uint64{}, true, false}, + } + for i, tc := range testCases { + suite.ctx = suite.ctx.WithIsReCheckTx(tc.recheck) + suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() // Create new txBuilder for each test + + suite.Require().NoError(suite.txBuilder.SetMsgs(msgs...)) + suite.txBuilder.SetFeeAmount(feeAmount) + suite.txBuilder.SetGasLimit(gasLimit) + + tx, err := suite.CreateTestTx(tc.privs, tc.accNums, tc.accSeqs, suite.ctx.ChainID()) + suite.Require().NoError(err) + + _, err = antehandler(suite.ctx, tx, false) + if tc.shouldErr { + suite.Require().NotNil(err, "TestCase %d: %s did not error as expected", i, tc.name) + } else { + suite.Require().Nil(err, "TestCase %d: %s errored unexpectedly. Err: %v", i, tc.name, err) + } + } +} + +// This test is exactly like the one above, but we set the codec explicitly to +// Amino. +// Once https://github.com/cosmos/cosmos-sdk/issues/6190 is in, we can remove +// this, since it'll be handled by the test matrix. +// In the meantime, we want to make double-sure amino compatibility works. +// ref: https://github.com/cosmos/cosmos-sdk/issues/7229 +func (suite *AnteTestSuite) TestSigVerification_ExplicitAmino() { + suite.app, suite.ctx = createTestApp(true) + suite.ctx = suite.ctx.WithBlockHeight(1) + + // Set up TxConfig. + aminoCdc := codec.NewLegacyAmino() + // We're using TestMsg amino encoding in some tests, so register it here. + txConfig := legacytx.StdTxConfig{Cdc: aminoCdc} + + suite.clientCtx = client.Context{}. + WithTxConfig(txConfig) + + anteHandler, err := ante.NewAnteHandler( + ante.HandlerOptions{ + AccountKeeper: suite.app.AccountKeeper, + BankKeeper: suite.app.BankKeeper, + FeegrantKeeper: suite.app.FeeGrantKeeper, + SignModeHandler: txConfig.SignModeHandler(), + SigGasConsumer: ante.DefaultSigVerificationGasConsumer, + }, + ) + + suite.Require().NoError(err) + suite.anteHandler = anteHandler + + suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() + + // make block height non-zero to ensure account numbers part of signBytes + suite.ctx = suite.ctx.WithBlockHeight(1) + + // keys and addresses + priv1, _, addr1 := testdata.KeyTestPubAddr() + priv2, _, addr2 := testdata.KeyTestPubAddr() + priv3, _, addr3 := testdata.KeyTestPubAddr() + + addrs := []sdk.AccAddress{addr1, addr2, addr3} + + msgs := make([]sdk.Msg, len(addrs)) + // set accounts and create msg for each address + for i, addr := range addrs { + acc := suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, addr) + suite.Require().NoError(acc.SetAccountNumber(uint64(i))) + suite.app.AccountKeeper.SetAccount(suite.ctx, acc) + msgs[i] = testdata.NewTestMsg(addr) + } + + feeAmount := testdata.NewTestFeeAmount() + gasLimit := testdata.NewTestGasLimit() + + spkd := ante.NewSetPubKeyDecorator(suite.app.AccountKeeper) + svd := ante.NewSigVerificationDecorator(suite.app.AccountKeeper, suite.clientCtx.TxConfig.SignModeHandler()) + antehandler := sdk.ChainAnteDecorators(spkd, svd) + + type testCase struct { + name string + privs []cryptotypes.PrivKey + accNums []uint64 + accSeqs []uint64 + recheck bool + shouldErr bool + } + testCases := []testCase{ + {"no signers", []cryptotypes.PrivKey{}, []uint64{}, []uint64{}, false, true}, + {"not enough signers", []cryptotypes.PrivKey{priv1, priv2}, []uint64{0, 1}, []uint64{0, 0}, false, true}, + {"wrong order signers", []cryptotypes.PrivKey{priv3, priv2, priv1}, []uint64{2, 1, 0}, []uint64{0, 0, 0}, false, true}, + {"wrong accnums", []cryptotypes.PrivKey{priv1, priv2, priv3}, []uint64{7, 8, 9}, []uint64{0, 0, 0}, false, true}, + {"wrong sequences", []cryptotypes.PrivKey{priv1, priv2, priv3}, []uint64{0, 1, 2}, []uint64{3, 4, 5}, false, true}, + {"valid tx", []cryptotypes.PrivKey{priv1, priv2, priv3}, []uint64{0, 1, 2}, []uint64{0, 0, 0}, false, false}, + {"no err on recheck", []cryptotypes.PrivKey{}, []uint64{}, []uint64{}, true, false}, + } + for i, tc := range testCases { + suite.ctx = suite.ctx.WithIsReCheckTx(tc.recheck) + suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() // Create new txBuilder for each test + + suite.Require().NoError(suite.txBuilder.SetMsgs(msgs...)) + suite.txBuilder.SetFeeAmount(feeAmount) + suite.txBuilder.SetGasLimit(gasLimit) + + tx, err := suite.CreateTestTx(tc.privs, tc.accNums, tc.accSeqs, suite.ctx.ChainID()) + suite.Require().NoError(err) + + _, err = antehandler(suite.ctx, tx, false) + if tc.shouldErr { + suite.Require().NotNil(err, "TestCase %d: %s did not error as expected", i, tc.name) + } else { + suite.Require().Nil(err, "TestCase %d: %s errored unexpectedly. Err: %v", i, tc.name, err) + } + } +} + +func (suite *AnteTestSuite) TestSigIntegration() { + // generate private keys + privs := []cryptotypes.PrivKey{ + secp256k1.GenPrivKey(), + secp256k1.GenPrivKey(), + secp256k1.GenPrivKey(), + } + + params := types.DefaultParams() + initialSigCost := params.SigVerifyCostSecp256k1 + initialCost, err := suite.runSigDecorators(params, false, privs...) + suite.Require().Nil(err) + + params.SigVerifyCostSecp256k1 *= 2 + doubleCost, err := suite.runSigDecorators(params, false, privs...) + suite.Require().Nil(err) + + suite.Require().Equal(initialSigCost*uint64(len(privs)), doubleCost-initialCost) +} + +func (suite *AnteTestSuite) runSigDecorators(params types.Params, _ bool, privs ...cryptotypes.PrivKey) (sdk.Gas, error) { + suite.SetupTest(true) // setup + suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() + + // Make block-height non-zero to include accNum in SignBytes + suite.ctx = suite.ctx.WithBlockHeight(1) + suite.app.AccountKeeper.SetParams(suite.ctx, params) + + msgs := make([]sdk.Msg, len(privs)) + accNums := make([]uint64, len(privs)) + accSeqs := make([]uint64, len(privs)) + // set accounts and create msg for each address + for i, priv := range privs { + addr := sdk.AccAddress(priv.PubKey().Address()) + acc := suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, addr) + suite.Require().NoError(acc.SetAccountNumber(uint64(i))) + suite.app.AccountKeeper.SetAccount(suite.ctx, acc) + msgs[i] = testdata.NewTestMsg(addr) + accNums[i] = uint64(i) + accSeqs[i] = uint64(0) + } + suite.Require().NoError(suite.txBuilder.SetMsgs(msgs...)) + + feeAmount := testdata.NewTestFeeAmount() + gasLimit := testdata.NewTestGasLimit() + suite.txBuilder.SetFeeAmount(feeAmount) + suite.txBuilder.SetGasLimit(gasLimit) + + tx, err := suite.CreateTestTx(privs, accNums, accSeqs, suite.ctx.ChainID()) + suite.Require().NoError(err) + + spkd := ante.NewSetPubKeyDecorator(suite.app.AccountKeeper) + svgc := ante.NewSigGasConsumeDecorator(suite.app.AccountKeeper, ante.DefaultSigVerificationGasConsumer) + svd := ante.NewSigVerificationDecorator(suite.app.AccountKeeper, suite.clientCtx.TxConfig.SignModeHandler()) + antehandler := sdk.ChainAnteDecorators(spkd, svgc, svd) + + // Determine gas consumption of antehandler with default params + before := suite.ctx.GasMeter().GasConsumed() + ctx, err := antehandler(suite.ctx, tx, false) + after := ctx.GasMeter().GasConsumed() + + return after - before, err +} + +func (suite *AnteTestSuite) TestIncrementSequenceDecorator() { + suite.SetupTest(true) // setup + suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() + + priv, _, addr := testdata.KeyTestPubAddr() + acc := suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, addr) + suite.Require().NoError(acc.SetAccountNumber(uint64(50))) + suite.app.AccountKeeper.SetAccount(suite.ctx, acc) + + msgs := []sdk.Msg{testdata.NewTestMsg(addr)} + suite.Require().NoError(suite.txBuilder.SetMsgs(msgs...)) + privs := []cryptotypes.PrivKey{priv} + accNums := []uint64{suite.app.AccountKeeper.GetAccount(suite.ctx, addr).GetAccountNumber()} + accSeqs := []uint64{suite.app.AccountKeeper.GetAccount(suite.ctx, addr).GetSequence()} + feeAmount := testdata.NewTestFeeAmount() + gasLimit := testdata.NewTestGasLimit() + suite.txBuilder.SetFeeAmount(feeAmount) + suite.txBuilder.SetGasLimit(gasLimit) + + tx, err := suite.CreateTestTx(privs, accNums, accSeqs, suite.ctx.ChainID()) + suite.Require().NoError(err) + + isd := ante.NewIncrementSequenceDecorator(suite.app.AccountKeeper) + antehandler := sdk.ChainAnteDecorators(isd) + + testCases := []struct { + ctx sdk.Context + simulate bool + expectedSeq uint64 + }{ + {suite.ctx.WithIsReCheckTx(true), false, 1}, + {suite.ctx.WithIsCheckTx(true).WithIsReCheckTx(false), false, 2}, + {suite.ctx.WithIsReCheckTx(true), false, 3}, + {suite.ctx.WithIsReCheckTx(true), false, 4}, + {suite.ctx.WithIsReCheckTx(true), true, 5}, + } + + for i, tc := range testCases { + _, err := antehandler(tc.ctx, tx, tc.simulate) + suite.Require().NoError(err, "unexpected error; tc #%d, %v", i, tc) + suite.Require().Equal(tc.expectedSeq, suite.app.AccountKeeper.GetAccount(suite.ctx, addr).GetSequence()) + } +} diff --git a/x/auth/ante/testutil_test.go b/x/auth/ante/testutil_test.go new file mode 100644 index 00000000..74216420 --- /dev/null +++ b/x/auth/ante/testutil_test.go @@ -0,0 +1,200 @@ +package ante_test + +import ( + "errors" + "fmt" + "testing" + + minttypes "github.com/cosmos/cosmos-sdk/x/mint/types" + + "github.com/stretchr/testify/suite" + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/tx" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + "github.com/cosmos/cosmos-sdk/simapp" + "github.com/cosmos/cosmos-sdk/testutil/testdata" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/tx/signing" + "github.com/cosmos/cosmos-sdk/x/auth/ante" + xauthsigning "github.com/cosmos/cosmos-sdk/x/auth/signing" + "github.com/cosmos/cosmos-sdk/x/auth/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" +) + +// TestAccount represents an account used in the tests in x/auth/ante. +type TestAccount struct { + acc types.AccountI + priv cryptotypes.PrivKey +} + +// AnteTestSuite is a test suite to be used with ante handler tests. +type AnteTestSuite struct { + suite.Suite + + app *simapp.SimApp + anteHandler sdk.AnteHandler + ctx sdk.Context + clientCtx client.Context + txBuilder client.TxBuilder +} + +// returns context and app with params set on account keeper +func createTestApp(isCheckTx bool) (*simapp.SimApp, sdk.Context) { + app := simapp.Setup(isCheckTx) + ctx := app.BaseApp.NewContext(isCheckTx, tmproto.Header{}) + app.AccountKeeper.SetParams(ctx, authtypes.DefaultParams()) + + return app, ctx +} + +// SetupTest setups a new test, with new app, context, and anteHandler. +func (suite *AnteTestSuite) SetupTest(isCheckTx bool) { + suite.app, suite.ctx = createTestApp(isCheckTx) + suite.ctx = suite.ctx.WithBlockHeight(1) + + // Set up TxConfig. + encodingConfig := simapp.MakeTestEncodingConfig() + // We're using TestMsg encoding in some tests, so register it here. + encodingConfig.Amino.RegisterConcrete(&testdata.TestMsg{}, "testdata.TestMsg", nil) + testdata.RegisterInterfaces(encodingConfig.InterfaceRegistry) + + suite.clientCtx = client.Context{}. + WithTxConfig(encodingConfig.TxConfig) + + anteHandler, err := ante.NewAnteHandler( + ante.HandlerOptions{ + AccountKeeper: suite.app.AccountKeeper, + BankKeeper: suite.app.BankKeeper, + FeegrantKeeper: suite.app.FeeGrantKeeper, + SignModeHandler: encodingConfig.TxConfig.SignModeHandler(), + SigGasConsumer: ante.DefaultSigVerificationGasConsumer, + }, + ) + + suite.Require().NoError(err) + suite.anteHandler = anteHandler +} + +// CreateTestAccounts creates `numAccs` accounts, and return all relevant +// information about them including their private keys. +func (suite *AnteTestSuite) CreateTestAccounts(numAccs int) []TestAccount { + var accounts []TestAccount + + for i := 0; i < numAccs; i++ { + priv, _, addr := testdata.KeyTestPubAddr() + acc := suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, addr) + err := acc.SetAccountNumber(uint64(i)) + suite.Require().NoError(err) + suite.app.AccountKeeper.SetAccount(suite.ctx, acc) + someCoins := sdk.Coins{ + sdk.NewInt64Coin("atom", 10000000), + } + err = suite.app.BankKeeper.MintCoins(suite.ctx, minttypes.ModuleName, someCoins) + suite.Require().NoError(err) + + err = suite.app.BankKeeper.SendCoinsFromModuleToAccount(suite.ctx, minttypes.ModuleName, addr, someCoins) + suite.Require().NoError(err) + + accounts = append(accounts, TestAccount{acc, priv}) + } + + return accounts +} + +// CreateTestTx is a helper function to create a tx given multiple inputs. +func (suite *AnteTestSuite) CreateTestTx(privs []cryptotypes.PrivKey, accNums []uint64, accSeqs []uint64, chainID string) (xauthsigning.Tx, error) { + // First round: we gather all the signer infos. We use the "set empty + // signature" hack to do that. + var sigsV2 []signing.SignatureV2 + for i, priv := range privs { + sigV2 := signing.SignatureV2{ + PubKey: priv.PubKey(), + Data: &signing.SingleSignatureData{ + SignMode: suite.clientCtx.TxConfig.SignModeHandler().DefaultMode(), + Signature: nil, + }, + Sequence: accSeqs[i], + } + + sigsV2 = append(sigsV2, sigV2) + } + err := suite.txBuilder.SetSignatures(sigsV2...) + if err != nil { + return nil, err + } + + // Second round: all signer infos are set, so each signer can sign. + sigsV2 = []signing.SignatureV2{} + for i, priv := range privs { + signerData := xauthsigning.SignerData{ + ChainID: chainID, + AccountNumber: accNums[i], + Sequence: accSeqs[i], + } + sigV2, err := tx.SignWithPrivKey( + suite.clientCtx.TxConfig.SignModeHandler().DefaultMode(), signerData, + suite.txBuilder, priv, suite.clientCtx.TxConfig, accSeqs[i]) + if err != nil { + return nil, err + } + + sigsV2 = append(sigsV2, sigV2) + } + err = suite.txBuilder.SetSignatures(sigsV2...) + if err != nil { + return nil, err + } + + return suite.txBuilder.GetTx(), nil +} + +// TestCase represents a test case used in test tables. +type TestCase struct { + desc string + malleate func() + simulate bool + expPass bool + expErr error +} + +// CreateTestTx is a helper function to create a tx given multiple inputs. +func (suite *AnteTestSuite) RunTestCase(privs []cryptotypes.PrivKey, msgs []sdk.Msg, feeAmount sdk.Coins, gasLimit uint64, accNums, accSeqs []uint64, chainID string, tc TestCase) { + suite.Run(fmt.Sprintf("Case %s", tc.desc), func() { + suite.Require().NoError(suite.txBuilder.SetMsgs(msgs...)) + suite.txBuilder.SetFeeAmount(feeAmount) + suite.txBuilder.SetGasLimit(gasLimit) + + // Theoretically speaking, ante handler unit tests should only test + // ante handlers, but here we sometimes also test the tx creation + // process. + tx, txErr := suite.CreateTestTx(privs, accNums, accSeqs, chainID) + newCtx, anteErr := suite.anteHandler(suite.ctx, tx, tc.simulate) + + if tc.expPass { + suite.Require().NoError(txErr) + suite.Require().NoError(anteErr) + suite.Require().NotNil(newCtx) + + suite.ctx = newCtx + } else { + switch { + case txErr != nil: + suite.Require().Error(txErr) + suite.Require().True(errors.Is(txErr, tc.expErr)) + + case anteErr != nil: + suite.Require().Error(anteErr) + suite.Require().True(errors.Is(anteErr, tc.expErr)) + + default: + suite.Fail("expected one of txErr,anteErr to be an error") + } + } + }) +} + +func TestAnteTestSuite(t *testing.T) { + suite.Run(t, new(AnteTestSuite)) +}