From a0dde4a2dcfa1551613a94dcedad05208cd00dc9 Mon Sep 17 00:00:00 2001 From: DedicatedDev Date: Fri, 8 Mar 2024 17:20:18 -0600 Subject: [PATCH] feat: enable convert in Encode --- ante/ante.go | 54 + ante/ante_test.go | 1497 +++++++++++++++++++++++ ante/basic.go | 207 ++++ ante/basic_test.go | 225 ++++ ante/expected_keepers.go | 20 + ante/ext.go | 65 + ante/ext_test.go | 57 + ante/fee.go | 136 ++ ante/fee_test.go | 142 +++ ante/feegrant_test.go | 251 ++++ ante/setup.go | 82 ++ ante/setup_test.go | 139 +++ ante/sigverify.go | 515 ++++++++ ante/sigverify_benchmark_test.go | 44 + ante/sigverify_test.go | 405 ++++++ ante/testutil/expected_keepers_mocks.go | 127 ++ ante/testutil_test.go | 222 ++++ ante/validator_tx_fee.go | 66 + app/app.go | 50 +- app/encoding.go | 11 + app/keepers/keepers.go | 119 +- app/keepers/modules.go | 4 +- app/modules.go | 2 +- app/test_setup.go | 6 +- bitcoin/codec/amino.go | 3 - bitcoin/codec/proto.go | 7 - bitcoin/keys/segwit/segwit_cgo.go | 24 +- bitcoin/keys/segwit/segwit_test.go | 20 +- cmd/sided/cmd/config.go | 1 + cmd/sided/cmd/genaccounts.go | 15 +- go.mod | 12 +- go.sum | 10 +- local_node.sh | 8 +- local_node_test.sh | 121 ++ 34 files changed, 4540 insertions(+), 127 deletions(-) create mode 100644 ante/ante.go create mode 100644 ante/ante_test.go create mode 100644 ante/basic.go create mode 100644 ante/basic_test.go create mode 100644 ante/expected_keepers.go create mode 100644 ante/ext.go create mode 100644 ante/ext_test.go create mode 100644 ante/fee.go create mode 100644 ante/fee_test.go create mode 100644 ante/feegrant_test.go create mode 100644 ante/setup.go create mode 100644 ante/setup_test.go create mode 100644 ante/sigverify.go create mode 100644 ante/sigverify_benchmark_test.go create mode 100644 ante/sigverify_test.go create mode 100644 ante/testutil/expected_keepers_mocks.go create mode 100644 ante/testutil_test.go create mode 100644 ante/validator_tx_fee.go create mode 100644 local_node_test.sh diff --git a/ante/ante.go b/ante/ante.go new file mode 100644 index 00000000..a562e67e --- /dev/null +++ b/ante/ante.go @@ -0,0 +1,54 @@ +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 + ExtensionOptionChecker ExtensionOptionChecker + FeegrantKeeper FeegrantKeeper + SignModeHandler authsigning.SignModeHandler + SigGasConsumer func(meter sdk.GasMeter, sig signing.SignatureV2, params types.Params) error + TxFeeChecker TxFeeChecker +} + +// 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") + } + + anteDecorators := []sdk.AnteDecorator{ + NewSetUpContextDecorator(), // outermost AnteDecorator. SetUpContext must be called first + NewExtensionOptionsDecorator(options.ExtensionOptionChecker), + NewValidateBasicDecorator(), + NewTxTimeoutHeightDecorator(), + NewValidateMemoDecorator(options.AccountKeeper), + NewConsumeGasForTxSizeDecorator(options.AccountKeeper), + NewDeductFeeDecorator(options.AccountKeeper, options.BankKeeper, options.FeegrantKeeper, options.TxFeeChecker), + NewSetPubKeyDecorator(options.AccountKeeper), // SetPubKeyDecorator must be called before all signature verification decorators + NewValidateSigCountDecorator(options.AccountKeeper), + NewSigGasConsumeDecorator(options.AccountKeeper, options.SigGasConsumer), + NewSigVerificationDecorator(options.AccountKeeper, options.SignModeHandler), + NewIncrementSequenceDecorator(options.AccountKeeper), + } + + return sdk.ChainAnteDecorators(anteDecorators...), nil +} diff --git a/ante/ante_test.go b/ante/ante_test.go new file mode 100644 index 00000000..0ff3cdae --- /dev/null +++ b/ante/ante_test.go @@ -0,0 +1,1497 @@ +package ante_test + +import ( + "encoding/json" + "errors" + "fmt" + "strings" + "testing" + + "cosmossdk.io/math" + "github.com/golang/mock/gomock" + "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" + "github.com/cosmos/cosmos-sdk/x/auth/ante" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" +) + +// Test that simulate transaction accurately estimates gas cost +func TestSimulateGasCost(t *testing.T) { + // This test has a test case that uses another's output. + var simulatedGas uint64 + + // Same data for every test case + feeAmount := testdata.NewTestFeeAmount() + + testCases := []TestCase{ + { + "tx with 150atom fee", + func(suite *AnteTestSuite) TestCaseArgs { + accs := suite.CreateTestAccounts(3) + suite.bankKeeper.EXPECT().SendCoinsFromAccountToModule(gomock.Any(), accs[0].acc.GetAddress(), authtypes.FeeCollectorName, feeAmount).Return(nil) + + msgs := []sdk.Msg{ + testdata.NewTestMsg(accs[0].acc.GetAddress(), accs[1].acc.GetAddress()), + testdata.NewTestMsg(accs[2].acc.GetAddress(), accs[0].acc.GetAddress()), + testdata.NewTestMsg(accs[1].acc.GetAddress(), accs[2].acc.GetAddress()), + } + + return TestCaseArgs{ + accNums: []uint64{0, 1, 2}, + accSeqs: []uint64{0, 0, 0}, + feeAmount: feeAmount, + gasLimit: testdata.NewTestGasLimit(), + msgs: msgs, + privs: []cryptotypes.PrivKey{accs[0].priv, accs[1].priv, accs[2].priv}, + } + }, + true, + true, + nil, + }, + { + "with previously estimated gas", + func(suite *AnteTestSuite) TestCaseArgs { + accs := suite.CreateTestAccounts(3) + suite.bankKeeper.EXPECT().SendCoinsFromAccountToModule(gomock.Any(), accs[0].acc.GetAddress(), authtypes.FeeCollectorName, feeAmount).Return(nil) + + msgs := []sdk.Msg{ + testdata.NewTestMsg(accs[0].acc.GetAddress(), accs[1].acc.GetAddress()), + testdata.NewTestMsg(accs[2].acc.GetAddress(), accs[0].acc.GetAddress()), + testdata.NewTestMsg(accs[1].acc.GetAddress(), accs[2].acc.GetAddress()), + } + + return TestCaseArgs{ + accNums: []uint64{0, 1, 2}, + accSeqs: []uint64{0, 0, 0}, + feeAmount: feeAmount, + gasLimit: simulatedGas, + msgs: msgs, + privs: []cryptotypes.PrivKey{accs[0].priv, accs[1].priv, accs[2].priv}, + } + }, + false, + true, + nil, + }, + } + + for _, tc := range testCases { + t.Run(fmt.Sprintf("Case %s", tc.desc), func(t *testing.T) { + suite := SetupTestSuite(t, false) + suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() + args := tc.malleate(suite) + args.chainID = suite.ctx.ChainID() + suite.RunTestCase(t, tc, args) + + // Gather info for the next test case + simulatedGas = suite.ctx.GasMeter().GasConsumed() + }) + } +} + +// Test various error cases in the AnteHandler control flow. +func TestAnteHandlerSigErrors(t *testing.T) { + // This test requires the accounts to not be set, so we create them here + priv0, _, addr0 := testdata.KeyTestPubAddr() + priv1, _, addr1 := testdata.KeyTestPubAddr() + priv2, _, addr2 := testdata.KeyTestPubAddr() + msgs := []sdk.Msg{ + testdata.NewTestMsg(addr0, addr1), + testdata.NewTestMsg(addr0, addr2), + } + + testCases := []TestCase{ + { + "check no signatures fails", + func(suite *AnteTestSuite) TestCaseArgs { + privs, accNums, accSeqs := []cryptotypes.PrivKey{}, []uint64{}, []uint64{} + + // Create tx manually to test the tx's signers + require.NoError(t, suite.txBuilder.SetMsgs(msgs...)) + tx, err := suite.CreateTestTx(privs, accNums, accSeqs, suite.ctx.ChainID()) + require.NoError(t, err) + + // tx.GetSigners returns addresses in correct order: addr1, addr2, addr3 + expectedSigners := []sdk.AccAddress{addr0, addr1, addr2} + require.Equal(t, expectedSigners, tx.GetSigners()) + + return TestCaseArgs{ + accNums: accNums, + accSeqs: accSeqs, + msgs: msgs, + privs: privs, + } + }, + false, + false, + sdkerrors.ErrNoSignatures, + }, + { + "num sigs dont match GetSigners", + func(suite *AnteTestSuite) TestCaseArgs { + privs, accNums, accSeqs := []cryptotypes.PrivKey{priv0}, []uint64{0}, []uint64{0} + + return TestCaseArgs{ + accNums: accNums, + accSeqs: accSeqs, + msgs: msgs, + privs: privs, + } + }, + false, + false, + sdkerrors.ErrUnauthorized, + }, + { + "unrecognized account", + func(suite *AnteTestSuite) TestCaseArgs { + privs, accNums, accSeqs := []cryptotypes.PrivKey{priv0, priv1, priv2}, []uint64{0, 1, 2}, []uint64{0, 0, 0} + + return TestCaseArgs{ + accNums: accNums, + accSeqs: accSeqs, + msgs: msgs, + privs: privs, + } + }, + false, + false, + sdkerrors.ErrUnknownAddress, + }, + { + "save the first account, but second is still unrecognized", + func(suite *AnteTestSuite) TestCaseArgs { + suite.accountKeeper.SetAccount(suite.ctx, suite.accountKeeper.NewAccountWithAddress(suite.ctx, addr0)) + suite.bankKeeper.EXPECT().SendCoinsFromAccountToModule(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil) + + privs, accNums, accSeqs := []cryptotypes.PrivKey{priv0, priv1, priv2}, []uint64{0, 1, 2}, []uint64{0, 0, 0} + + return TestCaseArgs{ + accNums: accNums, + accSeqs: accSeqs, + msgs: msgs, + privs: privs, + } + }, + false, + false, + sdkerrors.ErrUnknownAddress, + }, + { + "save all the accounts, should pass", + func(suite *AnteTestSuite) TestCaseArgs { + suite.accountKeeper.SetAccount(suite.ctx, suite.accountKeeper.NewAccountWithAddress(suite.ctx, addr0)) + suite.accountKeeper.SetAccount(suite.ctx, suite.accountKeeper.NewAccountWithAddress(suite.ctx, addr1)) + suite.accountKeeper.SetAccount(suite.ctx, suite.accountKeeper.NewAccountWithAddress(suite.ctx, addr2)) + suite.bankKeeper.EXPECT().SendCoinsFromAccountToModule(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil) + + privs, accNums, accSeqs := []cryptotypes.PrivKey{priv0, priv1, priv2}, []uint64{1, 2, 3}, []uint64{0, 0, 0} + + return TestCaseArgs{ + accNums: accNums, + accSeqs: accSeqs, + msgs: msgs, + privs: privs, + } + }, + false, + true, + nil, + }, + } + + for _, tc := range testCases { + t.Run(fmt.Sprintf("Case %s", tc.desc), func(t *testing.T) { + suite := SetupTestSuite(t, false) + suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() + args := tc.malleate(suite) + args.chainID = suite.ctx.ChainID() + args.feeAmount = testdata.NewTestFeeAmount() + args.gasLimit = testdata.NewTestGasLimit() + + suite.RunTestCase(t, tc, args) + }) + } +} + +// Test logic around account number checking with one signer and many signers. +func TestAnteHandlerAccountNumbers(t *testing.T) { + testCases := []TestCase{ + { + "good tx from one signer", + func(suite *AnteTestSuite) TestCaseArgs { + accs := suite.CreateTestAccounts(1) + msg := testdata.NewTestMsg(accs[0].acc.GetAddress()) + suite.bankKeeper.EXPECT().SendCoinsFromAccountToModule(gomock.Any(), accs[0].acc.GetAddress(), gomock.Any(), gomock.Any()).Return(nil) + + return TestCaseArgs{ + accNums: []uint64{0}, + accSeqs: []uint64{0}, + msgs: []sdk.Msg{msg}, + privs: []cryptotypes.PrivKey{accs[0].priv}, + } + }, + false, + true, + nil, + }, + { + "new tx from wrong account number", + func(suite *AnteTestSuite) TestCaseArgs { + accs := suite.CreateTestAccounts(1) + msg := testdata.NewTestMsg(accs[0].acc.GetAddress()) + suite.bankKeeper.EXPECT().SendCoinsFromAccountToModule(gomock.Any(), accs[0].acc.GetAddress(), gomock.Any(), gomock.Any()).Return(nil) + + return TestCaseArgs{ + accNums: []uint64{1}, + accSeqs: []uint64{0}, + msgs: []sdk.Msg{msg}, + privs: []cryptotypes.PrivKey{accs[0].priv}, + } + }, + false, + false, + sdkerrors.ErrUnauthorized, + }, + { + "new tx with another signer and incorrect account numbers", + func(suite *AnteTestSuite) TestCaseArgs { + accs := suite.CreateTestAccounts(2) + msg1 := testdata.NewTestMsg(accs[0].acc.GetAddress(), accs[1].acc.GetAddress()) + msg2 := testdata.NewTestMsg(accs[1].acc.GetAddress(), accs[0].acc.GetAddress()) + suite.bankKeeper.EXPECT().SendCoinsFromAccountToModule(gomock.Any(), accs[0].acc.GetAddress(), gomock.Any(), gomock.Any()).Return(nil) + + return TestCaseArgs{ + accNums: []uint64{2, 0}, + accSeqs: []uint64{0, 0}, + msgs: []sdk.Msg{msg1, msg2}, + privs: []cryptotypes.PrivKey{accs[0].priv, accs[1].priv}, + } + }, + false, + false, + sdkerrors.ErrUnauthorized, + }, + { + "new tx with correct account numbers", + func(suite *AnteTestSuite) TestCaseArgs { + accs := suite.CreateTestAccounts(2) + msg1 := testdata.NewTestMsg(accs[0].acc.GetAddress(), accs[1].acc.GetAddress()) + msg2 := testdata.NewTestMsg(accs[1].acc.GetAddress(), accs[0].acc.GetAddress()) + suite.bankKeeper.EXPECT().SendCoinsFromAccountToModule(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil) + + return TestCaseArgs{ + accNums: []uint64{0, 1}, + accSeqs: []uint64{0, 0}, + msgs: []sdk.Msg{msg1, msg2}, + privs: []cryptotypes.PrivKey{accs[0].priv, accs[1].priv}, + } + }, + false, + true, + nil, + }, + } + + for _, tc := range testCases { + t.Run(fmt.Sprintf("Case %s", tc.desc), func(t *testing.T) { + suite := SetupTestSuite(t, false) + args := tc.malleate(suite) + args.feeAmount = testdata.NewTestFeeAmount() + args.gasLimit = testdata.NewTestGasLimit() + args.chainID = suite.ctx.ChainID() + + suite.RunTestCase(t, tc, args) + }) + } +} + +// Test logic around account number checking with many signers when BlockHeight is 0. +func TestAnteHandlerAccountNumbersAtBlockHeightZero(t *testing.T) { + testCases := []TestCase{ + { + "good tx from one signer", + func(suite *AnteTestSuite) TestCaseArgs { + accs := suite.CreateTestAccounts(1) + msg := testdata.NewTestMsg(accs[0].acc.GetAddress()) + suite.bankKeeper.EXPECT().SendCoinsFromAccountToModule(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil) + + return TestCaseArgs{ + accNums: []uint64{0}, + accSeqs: []uint64{0}, + msgs: []sdk.Msg{msg}, + privs: []cryptotypes.PrivKey{accs[0].priv}, + } + }, + false, + true, + nil, + }, + { + "new tx from wrong account number", + func(suite *AnteTestSuite) TestCaseArgs { + accs := suite.CreateTestAccounts(1) + msg := testdata.NewTestMsg(accs[0].acc.GetAddress()) + suite.bankKeeper.EXPECT().SendCoinsFromAccountToModule(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil) + + return TestCaseArgs{ + accNums: []uint64{1}, // wrong account number + accSeqs: []uint64{0}, + msgs: []sdk.Msg{msg}, + privs: []cryptotypes.PrivKey{accs[0].priv}, + } + }, + false, + false, + sdkerrors.ErrUnauthorized, + }, + { + "new tx with another signer and incorrect account numbers", + func(suite *AnteTestSuite) TestCaseArgs { + accs := suite.CreateTestAccounts(2) + msg1 := testdata.NewTestMsg(accs[0].acc.GetAddress(), accs[1].acc.GetAddress()) + msg2 := testdata.NewTestMsg(accs[1].acc.GetAddress(), accs[0].acc.GetAddress()) + + suite.bankKeeper.EXPECT().SendCoinsFromAccountToModule(gomock.Any(), accs[0].acc.GetAddress(), gomock.Any(), gomock.Any()).Return(nil) + + return TestCaseArgs{ + accNums: []uint64{1, 0}, // wrong account numbers + accSeqs: []uint64{0, 0}, + msgs: []sdk.Msg{msg1, msg2}, + privs: []cryptotypes.PrivKey{accs[0].priv, accs[1].priv}, + } + }, + false, + false, + sdkerrors.ErrUnauthorized, + }, + { + "new tx with another signer and correct account numbers", + func(suite *AnteTestSuite) TestCaseArgs { + accs := suite.CreateTestAccounts(2) + msg1 := testdata.NewTestMsg(accs[0].acc.GetAddress(), accs[1].acc.GetAddress()) + msg2 := testdata.NewTestMsg(accs[1].acc.GetAddress(), accs[0].acc.GetAddress()) + + suite.bankKeeper.EXPECT().SendCoinsFromAccountToModule(gomock.Any(), accs[0].acc.GetAddress(), gomock.Any(), gomock.Any()).Return(nil) + + return TestCaseArgs{ + accNums: []uint64{0, 0}, // correct account numbers + accSeqs: []uint64{0, 0}, + msgs: []sdk.Msg{msg1, msg2}, + privs: []cryptotypes.PrivKey{accs[0].priv, accs[1].priv}, + } + }, + false, + true, + nil, + }, + } + + for _, tc := range testCases { + t.Run(fmt.Sprintf("Case %s", tc.desc), func(t *testing.T) { + suite := SetupTestSuite(t, false) + suite.ctx = suite.ctx.WithBlockHeight(0) + suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() + + args := tc.malleate(suite) + args.feeAmount = testdata.NewTestFeeAmount() + args.gasLimit = testdata.NewTestGasLimit() + + suite.RunTestCase(t, tc, args) + }) + } +} + +// Test logic around sequence checking with one signer and many signers. +func TestAnteHandlerSequences(t *testing.T) { + // Same data for every test cases + feeAmount := testdata.NewTestFeeAmount() + gasLimit := testdata.NewTestGasLimit() + + testCases := []TestCase{ + { + "good tx from one signer", + func(suite *AnteTestSuite) TestCaseArgs { + accs := suite.CreateTestAccounts(1) + msg := testdata.NewTestMsg(accs[0].acc.GetAddress()) + suite.bankKeeper.EXPECT().SendCoinsFromAccountToModule(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil) + + return TestCaseArgs{ + accNums: []uint64{0}, + accSeqs: []uint64{0}, + msgs: []sdk.Msg{msg}, + privs: []cryptotypes.PrivKey{accs[0].priv}, + } + }, + false, + true, + nil, + }, + { + "test sending it again fails (replay protection)", + func(suite *AnteTestSuite) TestCaseArgs { + accs := suite.CreateTestAccounts(1) + msg := testdata.NewTestMsg(accs[0].acc.GetAddress()) + msgs := []sdk.Msg{msg} + privs, accNums, accSeqs := []cryptotypes.PrivKey{accs[0].priv}, []uint64{0}, []uint64{0} + + // This will be called only once given that the second tx will fail + suite.bankKeeper.EXPECT().SendCoinsFromAccountToModule(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).Times(2) + + // Send the same tx before running the test case, to trigger replay protection. + var err error + suite.ctx, err = suite.DeliverMsgs(t, privs, msgs, feeAmount, gasLimit, accNums, accSeqs, suite.ctx.ChainID(), false) + require.NoError(t, err) + + return TestCaseArgs{ + accNums: accNums, + accSeqs: accSeqs, + msgs: msgs, + privs: privs, + } + }, + false, + false, + sdkerrors.ErrWrongSequence, + }, + { + "fix sequence, should pass", + func(suite *AnteTestSuite) TestCaseArgs { + accs := suite.CreateTestAccounts(1) + msg := testdata.NewTestMsg(accs[0].acc.GetAddress()) + msgs := []sdk.Msg{msg} + + privs, accNums, accSeqs := []cryptotypes.PrivKey{accs[0].priv}, []uint64{0}, []uint64{0} + suite.bankKeeper.EXPECT().SendCoinsFromAccountToModule(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).Times(2) + + // Send the same tx before running the test case, then change the sequence to a valid one. + var err error + suite.ctx, err = suite.DeliverMsgs(t, privs, msgs, feeAmount, gasLimit, accNums, accSeqs, suite.ctx.ChainID(), false) + require.NoError(t, err) + + // +1 the account sequence + accSeqs = []uint64{1} + + return TestCaseArgs{ + accNums: accNums, + accSeqs: accSeqs, + msgs: msgs, + privs: privs, + } + }, + false, + true, + nil, + }, + { + "new tx with another signer and correct sequences", + func(suite *AnteTestSuite) TestCaseArgs { + accs := suite.CreateTestAccounts(3) + msg1 := testdata.NewTestMsg(accs[0].acc.GetAddress(), accs[1].acc.GetAddress()) + msg2 := testdata.NewTestMsg(accs[2].acc.GetAddress(), accs[0].acc.GetAddress()) + + suite.bankKeeper.EXPECT().SendCoinsFromAccountToModule(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil) + + return TestCaseArgs{ + accNums: []uint64{0, 1, 2}, + accSeqs: []uint64{0, 0, 0}, + msgs: []sdk.Msg{msg1, msg2}, + privs: []cryptotypes.PrivKey{accs[0].priv, accs[1].priv, accs[2].priv}, + } + }, + false, + true, + nil, + }, + { + "replay fails", + func(suite *AnteTestSuite) TestCaseArgs { + accs := suite.CreateTestAccounts(3) + msg1 := testdata.NewTestMsg(accs[0].acc.GetAddress(), accs[1].acc.GetAddress()) + msg2 := testdata.NewTestMsg(accs[2].acc.GetAddress(), accs[0].acc.GetAddress()) + msgs := []sdk.Msg{msg1, msg2} + + privs, accNums, accSeqs := []cryptotypes.PrivKey{accs[0].priv, accs[1].priv, accs[2].priv}, []uint64{0, 1, 2}, []uint64{0, 0, 0} + suite.bankKeeper.EXPECT().SendCoinsFromAccountToModule(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).Times(2) + + // Send the same tx before running the test case, to trigger replay protection. + var err error + suite.ctx, err = suite.DeliverMsgs(t, privs, msgs, feeAmount, gasLimit, accNums, accSeqs, suite.ctx.ChainID(), false) + require.NoError(t, err) + + return TestCaseArgs{ + accNums: accNums, + accSeqs: accSeqs, + msgs: []sdk.Msg{msg1, msg2}, + privs: privs, + } + }, + false, + false, + sdkerrors.ErrWrongSequence, + }, + { + "tx from just second signer with incorrect sequence fails", + func(suite *AnteTestSuite) TestCaseArgs { + accs := suite.CreateTestAccounts(3) + msg1 := testdata.NewTestMsg(accs[0].acc.GetAddress(), accs[1].acc.GetAddress()) + msg2 := testdata.NewTestMsg(accs[2].acc.GetAddress(), accs[0].acc.GetAddress()) + msgs := []sdk.Msg{msg1, msg2} + + privs, accNums, accSeqs := []cryptotypes.PrivKey{accs[0].priv, accs[1].priv, accs[2].priv}, []uint64{0, 1, 2}, []uint64{0, 0, 0} + suite.bankKeeper.EXPECT().SendCoinsFromAccountToModule(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).Times(2) + + // Send the same tx before running the test case, to trigger replay protection. + var err error + suite.ctx, err = suite.DeliverMsgs(t, privs, msgs, feeAmount, gasLimit, accNums, accSeqs, suite.ctx.ChainID(), false) + require.NoError(t, err) + + // Send a message using the second signer, this will fail given that the second signer already sent a TX, + // thus the sequence (0) is incorrect. + msg1 = testdata.NewTestMsg(accs[1].acc.GetAddress()) + msgs = []sdk.Msg{msg1} + privs, accNums, accSeqs = []cryptotypes.PrivKey{accs[1].priv}, []uint64{1}, []uint64{0} + + return TestCaseArgs{ + accNums: accNums, + accSeqs: accSeqs, + msgs: msgs, + privs: privs, + } + }, + false, + false, + sdkerrors.ErrWrongSequence, + }, + { + "fix the sequence and it passes", + func(suite *AnteTestSuite) TestCaseArgs { + accs := suite.CreateTestAccounts(3) + msg1 := testdata.NewTestMsg(accs[0].acc.GetAddress(), accs[1].acc.GetAddress()) + msg2 := testdata.NewTestMsg(accs[2].acc.GetAddress(), accs[0].acc.GetAddress()) + msgs := []sdk.Msg{msg1, msg2} + + privs, accNums, accSeqs := []cryptotypes.PrivKey{accs[0].priv, accs[1].priv, accs[2].priv}, []uint64{0, 1, 2}, []uint64{0, 0, 0} + suite.bankKeeper.EXPECT().SendCoinsFromAccountToModule(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).Times(2) + + // Send the same tx before running the test case, to trigger replay protection. + var err error + suite.ctx, err = suite.DeliverMsgs(t, privs, msgs, feeAmount, gasLimit, accNums, accSeqs, suite.ctx.ChainID(), false) + require.NoError(t, err) + + // Send a message using the second signer, this will now pass given that the second signer already sent a TX + // and the sequence was fixed (1). + msg1 = testdata.NewTestMsg(accs[1].acc.GetAddress()) + msgs = []sdk.Msg{msg1} + privs, accNums, accSeqs = []cryptotypes.PrivKey{accs[1].priv}, []uint64{1}, []uint64{1} + + return TestCaseArgs{ + accNums: accNums, + accSeqs: accSeqs, + msgs: msgs, + privs: privs, + } + }, + false, + true, + nil, + }, + } + + for _, tc := range testCases { + t.Run(fmt.Sprintf("Case %s", tc.desc), func(t *testing.T) { + suite := SetupTestSuite(t, false) + suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() + args := tc.malleate(suite) + args.feeAmount = feeAmount + args.gasLimit = gasLimit + + suite.RunTestCase(t, tc, args) + }) + } +} + +// Test logic around fee deduction. +func TestAnteHandlerFees(t *testing.T) { + // Same data for every test cases + feeAmount := testdata.NewTestFeeAmount() + gasLimit := testdata.NewTestGasLimit() + + testCases := []TestCase{ + { + "signer has no funds", + func(suite *AnteTestSuite) TestCaseArgs { + accs := suite.CreateTestAccounts(1) + suite.bankKeeper.EXPECT().SendCoinsFromAccountToModule(gomock.Any(), accs[0].acc.GetAddress(), gomock.Any(), feeAmount).Return(sdkerrors.ErrInsufficientFunds) + + return TestCaseArgs{ + accNums: []uint64{0}, + accSeqs: []uint64{0}, + msgs: []sdk.Msg{testdata.NewTestMsg(accs[0].acc.GetAddress())}, + privs: []cryptotypes.PrivKey{accs[0].priv}, + } + }, + false, + false, + sdkerrors.ErrInsufficientFunds, + }, + { + "signer has enough funds, should pass", + func(suite *AnteTestSuite) TestCaseArgs { + accs := suite.CreateTestAccounts(1) + suite.bankKeeper.EXPECT().SendCoinsFromAccountToModule(gomock.Any(), accs[0].acc.GetAddress(), gomock.Any(), feeAmount).Return(nil) + + return TestCaseArgs{ + accNums: []uint64{0}, + accSeqs: []uint64{0}, + msgs: []sdk.Msg{testdata.NewTestMsg(accs[0].acc.GetAddress())}, + privs: []cryptotypes.PrivKey{accs[0].priv}, + } + }, + false, + true, + nil, + }, + } + + for _, tc := range testCases { + t.Run(fmt.Sprintf("Case %s", tc.desc), func(t *testing.T) { + suite := SetupTestSuite(t, false) + suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() + args := tc.malleate(suite) + args.feeAmount = feeAmount + args.gasLimit = gasLimit + + suite.RunTestCase(t, tc, args) + }) + } +} + +// Test logic around memo gas consumption. +func TestAnteHandlerMemoGas(t *testing.T) { + testCases := []TestCase{ + { + "tx does not have enough gas", + func(suite *AnteTestSuite) TestCaseArgs { + accs := suite.CreateTestAccounts(1) + return TestCaseArgs{ + accNums: []uint64{0}, + accSeqs: []uint64{0}, + feeAmount: sdk.NewCoins(sdk.NewInt64Coin("atom", 0)), + gasLimit: 0, + msgs: []sdk.Msg{testdata.NewTestMsg(accs[0].acc.GetAddress())}, + privs: []cryptotypes.PrivKey{accs[0].priv}, + } + }, + false, + false, + sdkerrors.ErrOutOfGas, + }, + { + "tx with memo doesn't have enough gas", + func(suite *AnteTestSuite) TestCaseArgs { + accs := suite.CreateTestAccounts(1) + suite.txBuilder.SetMemo("abcininasidniandsinasindiansdiansdinaisndiasndiadninsd") + + return TestCaseArgs{ + accNums: []uint64{0}, + accSeqs: []uint64{0}, + feeAmount: sdk.NewCoins(sdk.NewInt64Coin("atom", 0)), + gasLimit: 801, + msgs: []sdk.Msg{testdata.NewTestMsg(accs[0].acc.GetAddress())}, + privs: []cryptotypes.PrivKey{accs[0].priv}, + } + }, + false, + false, + sdkerrors.ErrOutOfGas, + }, + { + "memo too large", + func(suite *AnteTestSuite) TestCaseArgs { + accs := suite.CreateTestAccounts(1) + suite.txBuilder.SetMemo(strings.Repeat("01234567890", 500)) + + return TestCaseArgs{ + accNums: []uint64{0}, + accSeqs: []uint64{0}, + feeAmount: sdk.NewCoins(sdk.NewInt64Coin("atom", 0)), + gasLimit: 50000, + msgs: []sdk.Msg{testdata.NewTestMsg(accs[0].acc.GetAddress())}, + privs: []cryptotypes.PrivKey{accs[0].priv}, + } + }, + false, + false, + sdkerrors.ErrMemoTooLarge, + }, + { + "tx with memo has enough gas", + func(suite *AnteTestSuite) TestCaseArgs { + accs := suite.CreateTestAccounts(1) + suite.txBuilder.SetMemo(strings.Repeat("0123456789", 10)) + return TestCaseArgs{ + accNums: []uint64{0}, + accSeqs: []uint64{0}, + feeAmount: sdk.NewCoins(sdk.NewInt64Coin("atom", 0)), + gasLimit: 60000, + msgs: []sdk.Msg{testdata.NewTestMsg(accs[0].acc.GetAddress())}, + privs: []cryptotypes.PrivKey{accs[0].priv}, + } + }, + false, + true, + nil, + }, + } + + for _, tc := range testCases { + t.Run(fmt.Sprintf("Case %s", tc.desc), func(t *testing.T) { + suite := SetupTestSuite(t, false) + suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() + args := tc.malleate(suite) + + suite.RunTestCase(t, tc, args) + }) + } +} + +func TestAnteHandlerMultiSigner(t *testing.T) { + feeAmount := testdata.NewTestFeeAmount() + gasLimit := testdata.NewTestGasLimit() + + testCases := []TestCase{ + { + "signers in order", + func(suite *AnteTestSuite) TestCaseArgs { + accs := suite.CreateTestAccounts(3) + msg1 := testdata.NewTestMsg(accs[0].acc.GetAddress(), accs[1].acc.GetAddress()) + msg2 := testdata.NewTestMsg(accs[2].acc.GetAddress(), accs[0].acc.GetAddress()) + msg3 := testdata.NewTestMsg(accs[1].acc.GetAddress(), accs[2].acc.GetAddress()) + suite.txBuilder.SetMemo("Check signers are in expected order and different account numbers works") + suite.bankKeeper.EXPECT().SendCoinsFromAccountToModule(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil) + + return TestCaseArgs{ + accNums: []uint64{0, 1, 2}, + accSeqs: []uint64{0, 0, 0}, + msgs: []sdk.Msg{msg1, msg2, msg3}, + privs: []cryptotypes.PrivKey{accs[0].priv, accs[1].priv, accs[2].priv}, + } + }, + false, + true, + nil, + }, + { + "change sequence numbers (only accounts 0 and 1 sign)", + func(suite *AnteTestSuite) TestCaseArgs { + accs := suite.CreateTestAccounts(3) + msg1 := testdata.NewTestMsg(accs[0].acc.GetAddress(), accs[1].acc.GetAddress()) + msg2 := testdata.NewTestMsg(accs[2].acc.GetAddress(), accs[0].acc.GetAddress()) + msg3 := testdata.NewTestMsg(accs[1].acc.GetAddress(), accs[2].acc.GetAddress()) + msgs := []sdk.Msg{msg1, msg2, msg3} + privs, accNums, accSeqs := []cryptotypes.PrivKey{accs[0].priv, accs[1].priv, accs[2].priv}, []uint64{0, 1, 2}, []uint64{0, 0, 0} + + suite.bankKeeper.EXPECT().SendCoinsFromAccountToModule(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).Times(2) + + var err error + suite.ctx, err = suite.DeliverMsgs(t, privs, msgs, feeAmount, gasLimit, accNums, accSeqs, suite.ctx.ChainID(), false) + require.NoError(t, err) + + msgs = []sdk.Msg{msg1} + privs, accNums, accSeqs = []cryptotypes.PrivKey{accs[0].priv, accs[1].priv}, []uint64{0, 1}, []uint64{1, 1} + + return TestCaseArgs{ + accNums: accNums, + accSeqs: accSeqs, + msgs: msgs, + privs: privs, + } + }, + false, + true, + nil, + }, + { + "change sequence numbers (only accounts 1 and 2 sign)", + func(suite *AnteTestSuite) TestCaseArgs { + accs := suite.CreateTestAccounts(3) + msg1 := testdata.NewTestMsg(accs[0].acc.GetAddress(), accs[1].acc.GetAddress()) + msg2 := testdata.NewTestMsg(accs[2].acc.GetAddress(), accs[0].acc.GetAddress()) + msg3 := testdata.NewTestMsg(accs[1].acc.GetAddress(), accs[2].acc.GetAddress()) + msgs := []sdk.Msg{msg1, msg2, msg3} + privs, accNums, accSeqs := []cryptotypes.PrivKey{accs[0].priv, accs[1].priv, accs[2].priv}, []uint64{0, 1, 2}, []uint64{0, 0, 0} + + suite.bankKeeper.EXPECT().SendCoinsFromAccountToModule(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).Times(2) + var err error + suite.ctx, err = suite.DeliverMsgs(t, privs, msgs, feeAmount, gasLimit, accNums, accSeqs, suite.ctx.ChainID(), false) + require.NoError(t, err) + + msgs = []sdk.Msg{msg3} + privs, accNums, accSeqs = []cryptotypes.PrivKey{accs[1].priv, accs[2].priv}, []uint64{1, 2}, []uint64{1, 1} + + return TestCaseArgs{ + accNums: accNums, + accSeqs: accSeqs, + msgs: msgs, + privs: privs, + } + }, + false, + true, + nil, + }, + { + "everyone signs again", + func(suite *AnteTestSuite) TestCaseArgs { + accs := suite.CreateTestAccounts(3) + msg1 := testdata.NewTestMsg(accs[0].acc.GetAddress(), accs[1].acc.GetAddress()) + msg2 := testdata.NewTestMsg(accs[2].acc.GetAddress(), accs[0].acc.GetAddress()) + msg3 := testdata.NewTestMsg(accs[1].acc.GetAddress(), accs[2].acc.GetAddress()) + msgs := []sdk.Msg{msg1, msg2, msg3} + + privs, accNums, accSeqs := []cryptotypes.PrivKey{accs[0].priv, accs[1].priv, accs[2].priv}, []uint64{0, 1, 2}, []uint64{0, 0, 0} + + suite.bankKeeper.EXPECT().SendCoinsFromAccountToModule(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).Times(2) + var err error + suite.ctx, err = suite.DeliverMsgs(t, privs, msgs, feeAmount, gasLimit, accNums, accSeqs, suite.ctx.ChainID(), false) + require.NoError(t, err) + + privs, accNums, accSeqs = []cryptotypes.PrivKey{accs[0].priv, accs[1].priv, accs[2].priv}, []uint64{0, 1, 2}, []uint64{1, 1, 1} + + return TestCaseArgs{ + accNums: accNums, + accSeqs: accSeqs, + msgs: msgs, + privs: privs, + } + }, + false, + true, + nil, + }, + } + + for _, tc := range testCases { + t.Run(fmt.Sprintf("Case %s", tc.desc), func(t *testing.T) { + suite := SetupTestSuite(t, false) + suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() + args := tc.malleate(suite) + args.feeAmount = feeAmount + args.gasLimit = gasLimit + args.chainID = suite.ctx.ChainID() + + suite.RunTestCase(t, tc, args) + }) + } +} + +func TestAnteHandlerBadSignBytes(t *testing.T) { + feeAmount := testdata.NewTestFeeAmount() + gasLimit := testdata.NewTestGasLimit() + + testCases := []TestCase{ + { + "test good tx and signBytes", + func(suite *AnteTestSuite) TestCaseArgs { + accs := suite.CreateTestAccounts(1) + msg0 := testdata.NewTestMsg(accs[0].acc.GetAddress()) + suite.bankKeeper.EXPECT().SendCoinsFromAccountToModule(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil) + + return TestCaseArgs{ + chainID: suite.ctx.ChainID(), + accNums: []uint64{0}, + accSeqs: []uint64{0}, + feeAmount: feeAmount, + gasLimit: gasLimit, + msgs: []sdk.Msg{msg0}, + privs: []cryptotypes.PrivKey{accs[0].priv}, + } + }, + false, + true, + nil, + }, + { + "test wrong chainID", + func(suite *AnteTestSuite) TestCaseArgs { + accs := suite.CreateTestAccounts(1) + msg0 := testdata.NewTestMsg(accs[0].acc.GetAddress()) + suite.bankKeeper.EXPECT().SendCoinsFromAccountToModule(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil) + + return TestCaseArgs{ + chainID: "wrong-chain-id", + accNums: []uint64{0}, + accSeqs: []uint64{0}, + feeAmount: feeAmount, + gasLimit: gasLimit, + msgs: []sdk.Msg{msg0}, + privs: []cryptotypes.PrivKey{accs[0].priv}, + } + }, + false, + false, + sdkerrors.ErrUnauthorized, + }, + { + "test wrong accSeqs", + func(suite *AnteTestSuite) TestCaseArgs { + accs := suite.CreateTestAccounts(1) + msg0 := testdata.NewTestMsg(accs[0].acc.GetAddress()) + suite.bankKeeper.EXPECT().SendCoinsFromAccountToModule(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil) + + return TestCaseArgs{ + chainID: suite.ctx.ChainID(), + accNums: []uint64{0}, + accSeqs: []uint64{2}, // wrong accSeq + feeAmount: feeAmount, + gasLimit: gasLimit, + msgs: []sdk.Msg{msg0}, + privs: []cryptotypes.PrivKey{accs[0].priv}, + } + }, + false, + false, + sdkerrors.ErrWrongSequence, + }, + { + "test wrong accNums", + func(suite *AnteTestSuite) TestCaseArgs { + accs := suite.CreateTestAccounts(1) + msg0 := testdata.NewTestMsg(accs[0].acc.GetAddress()) + suite.bankKeeper.EXPECT().SendCoinsFromAccountToModule(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil) + + return TestCaseArgs{ + chainID: suite.ctx.ChainID(), + accNums: []uint64{1}, // wrong accNum + accSeqs: []uint64{0}, + feeAmount: feeAmount, + gasLimit: gasLimit, + msgs: []sdk.Msg{msg0}, + privs: []cryptotypes.PrivKey{accs[0].priv}, + } + }, + false, + false, + sdkerrors.ErrUnauthorized, + }, + { + "test wrong msg", + func(suite *AnteTestSuite) TestCaseArgs { + accs := suite.CreateTestAccounts(2) + suite.bankKeeper.EXPECT().SendCoinsFromAccountToModule(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil) + + return TestCaseArgs{ + chainID: suite.ctx.ChainID(), + accNums: []uint64{0}, + accSeqs: []uint64{0}, + feeAmount: feeAmount, + gasLimit: gasLimit, + msgs: []sdk.Msg{testdata.NewTestMsg(accs[1].acc.GetAddress())}, // wrong account in the msg + privs: []cryptotypes.PrivKey{accs[0].priv}, + } + }, + false, + false, + sdkerrors.ErrInvalidPubKey, + }, + { + "test wrong signer if public key exist", + func(suite *AnteTestSuite) TestCaseArgs { + accs := suite.CreateTestAccounts(2) + msg0 := testdata.NewTestMsg(accs[0].acc.GetAddress()) + suite.bankKeeper.EXPECT().SendCoinsFromAccountToModule(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil) + + return TestCaseArgs{ + chainID: suite.ctx.ChainID(), + accNums: []uint64{0}, + accSeqs: []uint64{0}, + feeAmount: feeAmount, + gasLimit: gasLimit, + msgs: []sdk.Msg{msg0}, + privs: []cryptotypes.PrivKey{accs[1].priv}, + } + }, + false, + false, + sdkerrors.ErrInvalidPubKey, + }, + { + "test wrong signer if public doesn't exist", + func(suite *AnteTestSuite) TestCaseArgs { + accs := suite.CreateTestAccounts(2) + suite.bankKeeper.EXPECT().SendCoinsFromAccountToModule(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil) + + return TestCaseArgs{ + chainID: suite.ctx.ChainID(), + accNums: []uint64{1}, + accSeqs: []uint64{0}, + feeAmount: feeAmount, + gasLimit: gasLimit, + msgs: []sdk.Msg{testdata.NewTestMsg(accs[1].acc.GetAddress())}, + privs: []cryptotypes.PrivKey{accs[0].priv}, + } + }, + false, + false, + sdkerrors.ErrInvalidPubKey, + }, + } + + for _, tc := range testCases { + t.Run(fmt.Sprintf("Case %s", tc.desc), func(t *testing.T) { + suite := SetupTestSuite(t, false) + suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() + args := tc.malleate(suite) + + suite.RunTestCase(t, tc, args) + }) + } +} + +func TestAnteHandlerSetPubKey(t *testing.T) { + feeAmount := testdata.NewTestFeeAmount() + gasLimit := testdata.NewTestGasLimit() + + testCases := []TestCase{ + { + "test good tx", + func(suite *AnteTestSuite) TestCaseArgs { + accs := suite.CreateTestAccounts(1) + suite.bankKeeper.EXPECT().SendCoinsFromAccountToModule(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil) + + return TestCaseArgs{ + accNums: []uint64{0}, + accSeqs: []uint64{0}, + msgs: []sdk.Msg{testdata.NewTestMsg(accs[0].acc.GetAddress())}, + privs: []cryptotypes.PrivKey{accs[0].priv}, + } + }, + false, + true, + nil, + }, + { + "make sure public key has been set (tx itself should fail because of replay protection)", + func(suite *AnteTestSuite) TestCaseArgs { + accs := suite.CreateTestAccounts(1) + suite.bankKeeper.EXPECT().SendCoinsFromAccountToModule(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).Times(2) + + privs, accNums, accSeqs := []cryptotypes.PrivKey{accs[0].priv}, []uint64{0}, []uint64{0} + msgs := []sdk.Msg{testdata.NewTestMsg(accs[0].acc.GetAddress())} + var err error + suite.ctx, err = suite.DeliverMsgs(t, privs, msgs, feeAmount, gasLimit, accNums, accSeqs, suite.ctx.ChainID(), false) + require.NoError(t, err) + + // Make sure public key has been set from previous test. + acc0 := suite.accountKeeper.GetAccount(suite.ctx, accs[0].acc.GetAddress()) + require.Equal(t, acc0.GetPubKey(), accs[0].priv.PubKey()) + + return TestCaseArgs{ + accNums: accNums, + accSeqs: accSeqs, + msgs: msgs, + privs: privs, + } + }, + false, + false, + sdkerrors.ErrWrongSequence, + }, + { + "test public key not found", + func(suite *AnteTestSuite) TestCaseArgs { + accs := suite.CreateTestAccounts(2) + suite.bankKeeper.EXPECT().SendCoinsFromAccountToModule(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil) + return TestCaseArgs{ + accNums: []uint64{0}, + accSeqs: []uint64{0}, + msgs: []sdk.Msg{testdata.NewTestMsg(accs[1].acc.GetAddress())}, + privs: []cryptotypes.PrivKey{accs[0].priv}, // wrong signer + } + }, + false, + false, + sdkerrors.ErrInvalidPubKey, + }, + { + "make sure public key is not set, when tx has no pubkey or signature", + func(suite *AnteTestSuite) TestCaseArgs { + accs := suite.CreateTestAccounts(2) + suite.bankKeeper.EXPECT().SendCoinsFromAccountToModule(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil) + + // Make sure public key has not been set from previous test. + acc1 := suite.accountKeeper.GetAccount(suite.ctx, accs[1].acc.GetAddress()) + require.Nil(t, acc1.GetPubKey()) + + privs, accNums, accSeqs := []cryptotypes.PrivKey{accs[1].priv}, []uint64{1}, []uint64{0} + msgs := []sdk.Msg{testdata.NewTestMsg(accs[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()) + require.NoError(t, err) + txBuilder, err := suite.clientCtx.TxConfig.WrapTxBuilder(tx) + require.NoError(t, err) + require.NoError(t, txBuilder.SetSignatures()) + + // Run anteHandler manually, expect ErrNoSignatures. + _, err = suite.anteHandler(suite.ctx, txBuilder.GetTx(), false) + require.Error(t, err) + require.True(t, errors.Is(err, sdkerrors.ErrNoSignatures)) + + // Make sure public key has not been set. + acc1 = suite.accountKeeper.GetAccount(suite.ctx, accs[1].acc.GetAddress()) + require.Nil(t, acc1.GetPubKey()) + + // Set incorrect accSeq, to generate incorrect signature. + privs, accNums, accSeqs = []cryptotypes.PrivKey{accs[1].priv}, []uint64{1}, []uint64{1} + + return TestCaseArgs{ + accNums: accNums, + accSeqs: accSeqs, + msgs: msgs, + privs: privs, + } + }, + false, + false, + sdkerrors.ErrWrongSequence, + }, + { + "make sure previous public key has been set after wrong signature", + func(suite *AnteTestSuite) TestCaseArgs { + accs := suite.CreateTestAccounts(2) + suite.bankKeeper.EXPECT().SendCoinsFromAccountToModule(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil) + + // Make sure public key has not been set from previous test. + acc1 := suite.accountKeeper.GetAccount(suite.ctx, accs[1].acc.GetAddress()) + require.Nil(t, acc1.GetPubKey()) + + privs, accNums, accSeqs := []cryptotypes.PrivKey{accs[1].priv}, []uint64{1}, []uint64{0} + msgs := []sdk.Msg{testdata.NewTestMsg(accs[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()) + require.NoError(t, err) + txBuilder, err := suite.clientCtx.TxConfig.WrapTxBuilder(tx) + require.NoError(t, err) + require.NoError(t, txBuilder.SetSignatures()) + + // Run anteHandler manually, expect ErrNoSignatures. + _, err = suite.anteHandler(suite.ctx, txBuilder.GetTx(), false) + require.Error(t, err) + require.True(t, errors.Is(err, sdkerrors.ErrNoSignatures)) + + // Make sure public key has not been set. + acc1 = suite.accountKeeper.GetAccount(suite.ctx, accs[1].acc.GetAddress()) + require.Nil(t, acc1.GetPubKey()) + + // Set incorrect accSeq, to generate incorrect signature. + privs, accNums, accSeqs = []cryptotypes.PrivKey{accs[1].priv}, []uint64{1}, []uint64{1} + + suite.ctx, err = suite.DeliverMsgs(t, privs, msgs, feeAmount, gasLimit, accNums, accSeqs, suite.ctx.ChainID(), false) + require.Error(t, err) + + // Make sure public key has been set, as SetPubKeyDecorator + // is called before all signature verification decorators. + acc1 = suite.accountKeeper.GetAccount(suite.ctx, accs[1].acc.GetAddress()) + require.Equal(t, acc1.GetPubKey(), accs[1].priv.PubKey()) + suite.bankKeeper.EXPECT().SendCoinsFromAccountToModule(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil) + + return TestCaseArgs{ + accNums: accNums, + accSeqs: accSeqs, + msgs: msgs, + privs: privs, + } + }, + false, + false, + sdkerrors.ErrWrongSequence, + }, + } + + for _, tc := range testCases { + t.Run(fmt.Sprintf("Case %s", tc.desc), func(t *testing.T) { + suite := SetupTestSuite(t, false) + suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() + args := tc.malleate(suite) + args.chainID = suite.ctx.ChainID() + args.feeAmount = feeAmount + args.gasLimit = gasLimit + + suite.RunTestCase(t, tc, args) + }) + } +} + +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 += authtypes.DefaultParams().SigVerifyCostED25519 + case strings.Contains(pubkeyType, "secp256k1"): + cost += authtypes.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 TestAnteHandlerSigLimitExceeded(t *testing.T) { + testCases := []TestCase{ + { + "test rejection logic", + func(suite *AnteTestSuite) TestCaseArgs { + accs := suite.CreateTestAccounts(8) + var ( + addrs []sdk.AccAddress + privs []cryptotypes.PrivKey + ) + for i := 0; i < 8; i++ { + addrs = append(addrs, accs[i].acc.GetAddress()) + privs = append(privs, accs[i].priv) + } + + suite.bankKeeper.EXPECT().SendCoinsFromAccountToModule(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil) + + return TestCaseArgs{ + accNums: []uint64{0, 1, 2, 3, 4, 5, 6, 7}, + accSeqs: []uint64{0, 0, 0, 0, 0, 0, 0, 0}, + msgs: []sdk.Msg{testdata.NewTestMsg(addrs...)}, + privs: privs, + } + }, + false, + false, + sdkerrors.ErrTooManySignatures, + }, + } + + for _, tc := range testCases { + t.Run(fmt.Sprintf("Case %s", tc.desc), func(t *testing.T) { + suite := SetupTestSuite(t, false) + suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() + args := tc.malleate(suite) + args.chainID = suite.ctx.ChainID() + args.feeAmount = testdata.NewTestFeeAmount() + args.gasLimit = testdata.NewTestGasLimit() + + suite.RunTestCase(t, tc, args) + }) + } +} + +// Test custom SignatureVerificationGasConsumer +func TestCustomSignatureVerificationGasConsumer(t *testing.T) { + testCases := []TestCase{ + { + "verify that an secp256k1 account gets rejected", + func(suite *AnteTestSuite) TestCaseArgs { + // setup an ante handler that only accepts PubKeyEd25519 + anteHandler, err := ante.NewAnteHandler( + ante.HandlerOptions{ + AccountKeeper: suite.accountKeeper, + BankKeeper: suite.bankKeeper, + FeegrantKeeper: suite.feeGrantKeeper, + SignModeHandler: suite.clientCtx.TxConfig.SignModeHandler(), + SigGasConsumer: func(meter sdk.GasMeter, sig signing.SignatureV2, params authtypes.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) + } + }, + }, + ) + require.NoError(t, err) + suite.anteHandler = anteHandler + + accs := suite.CreateTestAccounts(1) + suite.bankKeeper.EXPECT().SendCoinsFromAccountToModule(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil) + + return TestCaseArgs{ + accNums: []uint64{0}, + accSeqs: []uint64{0}, + msgs: []sdk.Msg{testdata.NewTestMsg(accs[0].acc.GetAddress())}, + privs: []cryptotypes.PrivKey{accs[0].priv}, + } + }, + false, + false, + sdkerrors.ErrInvalidPubKey, + }, + } + + for _, tc := range testCases { + t.Run(fmt.Sprintf("Case %s", tc.desc), func(t *testing.T) { + suite := SetupTestSuite(t, false) + suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() + args := tc.malleate(suite) + args.chainID = suite.ctx.ChainID() + args.feeAmount = testdata.NewTestFeeAmount() + args.gasLimit = testdata.NewTestGasLimit() + + suite.RunTestCase(t, tc, args) + }) + } +} + +func TestAnteHandlerReCheck(t *testing.T) { + suite := SetupTestSuite(t, false) + // Set recheck=true + suite.ctx = suite.ctx.WithIsReCheckTx(true) + suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() + + // Same data for every test case + accs := suite.CreateTestAccounts(1) + + feeAmount := testdata.NewTestFeeAmount() + gasLimit := testdata.NewTestGasLimit() + suite.txBuilder.SetFeeAmount(feeAmount) + suite.txBuilder.SetGasLimit(gasLimit) + + msg := testdata.NewTestMsg(accs[0].acc.GetAddress()) + msgs := []sdk.Msg{msg} + require.NoError(t, suite.txBuilder.SetMsgs(msgs...)) + + suite.txBuilder.SetMemo("thisisatestmemo") + + // test that operations skipped on recheck do not run + privs, accNums, accSeqs := []cryptotypes.PrivKey{accs[0].priv}, []uint64{0}, []uint64{0} + tx, err := suite.CreateTestTx(privs, accNums, accSeqs, suite.ctx.ChainID()) + require.NoError(t, 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) + require.NoError(t, err) + + suite.bankKeeper.EXPECT().SendCoinsFromAccountToModule(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).Times(2) + _, err = suite.anteHandler(suite.ctx, txBuilder.GetTx(), false) + require.Nil(t, err, "AnteHandler errored on recheck unexpectedly: %v", err) + + tx, err = suite.CreateTestTx(privs, accNums, accSeqs, suite.ctx.ChainID()) + require.NoError(t, err) + txBytes, err := json.Marshal(tx) + require.Nil(t, 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 authtypes.Params + }{ + {"memo size check", authtypes.NewParams(1, authtypes.DefaultTxSigLimit, authtypes.DefaultTxSizeCostPerByte, authtypes.DefaultSigVerifyCostED25519, authtypes.DefaultSigVerifyCostSecp256k1)}, + {"txsize check", authtypes.NewParams(authtypes.DefaultMaxMemoCharacters, authtypes.DefaultTxSigLimit, 10000000, authtypes.DefaultSigVerifyCostED25519, authtypes.DefaultSigVerifyCostSecp256k1)}, + {"sig verify cost check", authtypes.NewParams(authtypes.DefaultMaxMemoCharacters, authtypes.DefaultTxSigLimit, authtypes.DefaultTxSizeCostPerByte, authtypes.DefaultSigVerifyCostED25519, 100000000)}, + } + + for _, tc := range testCases { + + // set testcase parameters + err := suite.accountKeeper.SetParams(suite.ctx, tc.params) + require.NoError(t, err) + + _, err = suite.anteHandler(suite.ctx, tx, false) + + require.NotNil(t, err, "tx does not fail on recheck with updated params in test case: %s", tc.name) + + // reset parameters to default values + err = suite.accountKeeper.SetParams(suite.ctx, authtypes.DefaultParams()) + require.NoError(t, err) + } + + suite.bankKeeper.EXPECT().SendCoinsFromAccountToModule(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(sdkerrors.ErrInsufficientFee) + // 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: math.LegacyNewDec(5), + }}) + _, err = suite.anteHandler(suite.ctx, tx, false) + require.NotNil(t, 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.accountKeeper.SetAccount(suite.ctx, accs[0].acc) + + _, err = suite.anteHandler(suite.ctx, tx, false) + require.NotNil(t, err, "antehandler on recheck did not fail once feePayer no longer has sufficient funds") +} diff --git a/ante/basic.go b/ante/basic.go new file mode 100644 index 00000000..c14d511e --- /dev/null +++ b/ante/basic.go @@ -0,0 +1,207 @@ +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/migrations/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") + } + + memoLength := len(memoTx.GetMemo()) + if memoLength > 0 { + params := vmd.ak.GetParams(ctx) + 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/ante/basic_test.go b/ante/basic_test.go new file mode 100644 index 00000000..8bf7b7ab --- /dev/null +++ b/ante/basic_test.go @@ -0,0 +1,225 @@ +package ante_test + +import ( + "strings" + "testing" + + 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" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + "github.com/cosmos/cosmos-sdk/types/tx/signing" + "github.com/cosmos/cosmos-sdk/x/auth/ante" + "github.com/stretchr/testify/require" +) + +func TestValidateBasic(t *testing.T) { + suite := SetupTestSuite(t, true) + 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() + require.NoError(t, 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()) + require.NoError(t, err) + + vbd := ante.NewValidateBasicDecorator() + antehandler := sdk.ChainAnteDecorators(vbd) + _, err = antehandler(suite.ctx, invalidTx, false) + + require.ErrorIs(t, err, sdkerrors.ErrNoSignatures, "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()) + require.NoError(t, err) + + _, err = antehandler(suite.ctx, validTx, false) + require.Nil(t, 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) + + require.Nil(t, err, "ValidateBasicDecorator ran on ReCheck") +} + +func TestValidateMemo(t *testing.T) { + suite := SetupTestSuite(t, true) + 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() + require.NoError(t, 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()) + require.NoError(t, err) + + // require that long memos get rejected + vmd := ante.NewValidateMemoDecorator(suite.accountKeeper) + antehandler := sdk.ChainAnteDecorators(vmd) + _, err = antehandler(suite.ctx, invalidTx, false) + + require.ErrorIs(t, err, sdkerrors.ErrMemoTooLarge, "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()) + require.NoError(t, err) + + // require small memos pass ValidateMemo Decorator + _, err = antehandler(suite.ctx, validTx, false) + require.Nil(t, err, "ValidateBasicDecorator returned error on valid tx. err: %v", err) +} + +func TestConsumeGasForTxSize(t *testing.T) { + suite := SetupTestSuite(t, true) + + // keys and addresses + priv1, _, addr1 := testdata.KeyTestPubAddr() + + // msg and signatures + msg := testdata.NewTestMsg(addr1) + feeAmount := testdata.NewTestFeeAmount() + gasLimit := testdata.NewTestGasLimit() + + cgtsd := ante.NewConsumeGasForTxSizeDecorator(suite.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 { + t.Run(tc.name, func(t *testing.T) { + suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() + require.NoError(t, 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()) + require.NoError(t, err) + + txBytes, err := suite.clientCtx.TxConfig.TxJSONEncoder()(tx) + require.Nil(t, err, "Cannot marshal tx: %v", err) + + params := suite.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.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) + require.Nil(t, err, "ConsumeTxSizeGasDecorator returned error: %v", err) + + // require that decorator consumes expected amount of gas + consumedGas := suite.ctx.GasMeter().GasConsumed() - beforeGas + require.Equal(t, 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) + require.NoError(t, err) + require.NoError(t, txBuilder.SetSignatures(tc.sigV2)) + tx = txBuilder.GetTx() + + simTxBytes, err := suite.clientCtx.TxConfig.TxJSONEncoder()(tx) + require.Nil(t, err, "Cannot marshal tx: %v", err) + // require that simulated tx is smaller than tx with signatures + require.True(t, 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 + require.Nil(t, err, "ConsumeTxSizeGasDecorator returned error: %v", err) + require.True(t, consumedSimGas >= expectedGas, "Simulate mode underestimates gas on AnteDecorator. Simulated cost: %d, expected cost: %d", consumedSimGas, expectedGas) + }) + } +} + +func TestTxHeightTimeoutDecorator(t *testing.T) { + suite := SetupTestSuite(t, 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 + expectedErr error + }{ + {"default value", 0, 10, nil}, + {"no timeout (greater height)", 15, 10, nil}, + {"no timeout (same height)", 10, 10, nil}, + {"timeout (smaller height)", 9, 10, sdkerrors.ErrTxTimeoutHeight}, + } + + for _, tc := range testCases { + tc := tc + + t.Run(tc.name, func(t *testing.T) { + suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() + + require.NoError(t, 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()) + require.NoError(t, err) + + ctx := suite.ctx.WithBlockHeight(tc.height) + _, err = antehandler(ctx, tx, true) + require.ErrorIs(t, err, tc.expectedErr) + }) + } +} diff --git a/ante/expected_keepers.go b/ante/expected_keepers.go new file mode 100644 index 00000000..4dbbbd21 --- /dev/null +++ b/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/ante/ext.go b/ante/ext.go new file mode 100644 index 00000000..6a072526 --- /dev/null +++ b/ante/ext.go @@ -0,0 +1,65 @@ +package ante + +import ( + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" +) + +type HasExtensionOptionsTx interface { + GetExtensionOptions() []*codectypes.Any + GetNonCriticalExtensionOptions() []*codectypes.Any +} + +// ExtensionOptionChecker is a function that returns true if the extension option is accepted. +type ExtensionOptionChecker func(*codectypes.Any) bool + +// rejectExtensionOption is the default extension check that reject all tx +// extensions. +func rejectExtensionOption(*codectypes.Any) bool { + return false +} + +// 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 { + checker ExtensionOptionChecker +} + +// NewExtensionOptionsDecorator creates a new antehandler that rejects all extension +// options which can optionally be included in protobuf transactions that don't pass the checker. +// Users that need extension options should pass a custom checker that returns true for the +// needed extension options. +func NewExtensionOptionsDecorator(checker ExtensionOptionChecker) sdk.AnteDecorator { + if checker == nil { + checker = rejectExtensionOption + } + + return RejectExtensionOptionsDecorator{checker: checker} +} + +var _ sdk.AnteDecorator = RejectExtensionOptionsDecorator{} + +// AnteHandle implements the AnteDecorator.AnteHandle method +func (r RejectExtensionOptionsDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) { + err = checkExtOpts(tx, r.checker) + if err != nil { + return ctx, err + } + + return next(ctx, tx, simulate) +} + +func checkExtOpts(tx sdk.Tx, checker ExtensionOptionChecker) error { + if hasExtOptsTx, ok := tx.(HasExtensionOptionsTx); ok { + for _, opt := range hasExtOptsTx.GetExtensionOptions() { + if !checker(opt) { + return sdkerrors.ErrUnknownExtensionOptions + } + } + } + + return nil +} diff --git a/ante/ext_test.go b/ante/ext_test.go new file mode 100644 index 00000000..8230b520 --- /dev/null +++ b/ante/ext_test.go @@ -0,0 +1,57 @@ +package ante_test + +import ( + "testing" + + codectypes "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" + "github.com/stretchr/testify/require" +) + +func TestRejectExtensionOptionsDecorator(t *testing.T) { + suite := SetupTestSuite(t, true) + + testCases := []struct { + msg string + allow bool + }{ + {"allow extension", true}, + {"reject extension", false}, + } + for _, tc := range testCases { + t.Run(tc.msg, func(t *testing.T) { + txBuilder := suite.clientCtx.TxConfig.NewTxBuilder() + + reod := ante.NewExtensionOptionsDecorator(func(_ *codectypes.Any) bool { + return tc.allow + }) + antehandler := sdk.ChainAnteDecorators(reod) + + // no extension options should not trigger an error + theTx := txBuilder.GetTx() + _, err := antehandler(suite.ctx, theTx, false) + require.NoError(t, err) + + extOptsTxBldr, ok := txBuilder.(tx.ExtensionOptionsTxBuilder) + if !ok { + // if we can't set extension options, this decorator doesn't apply and we're done + return + } + + // set an extension option and check + any, err := codectypes.NewAnyWithValue(testdata.NewTestMsg()) + require.NoError(t, err) + extOptsTxBldr.SetExtensionOptions(any) + theTx = txBuilder.GetTx() + _, err = antehandler(suite.ctx, theTx, false) + if tc.allow { + require.NoError(t, err) + } else { + require.EqualError(t, err, "unknown extension options") + } + }) + } +} diff --git a/ante/fee.go b/ante/fee.go new file mode 100644 index 00000000..66b2ba36 --- /dev/null +++ b/ante/fee.go @@ -0,0 +1,136 @@ +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/types" +) + +// TxFeeChecker check if the provided fee is enough and returns the effective fee and tx priority, +// the effective fee should be deducted later, and the priority should be returned in abci response. +type TxFeeChecker func(ctx sdk.Context, tx sdk.Tx) (sdk.Coins, int64, error) + +// DeductFeeDecorator deducts fees from the fee payer. The fee payer is the fee granter (if specified) or first signer of the tx. +// If the fee payer does not have the funds to pay for the fees, return an InsufficientFunds error. +// Call next AnteHandler if fees successfully deducted. +// CONTRACT: Tx must implement FeeTx interface to use DeductFeeDecorator +type DeductFeeDecorator struct { + accountKeeper AccountKeeper + bankKeeper types.BankKeeper + feegrantKeeper FeegrantKeeper + txFeeChecker TxFeeChecker +} + +func NewDeductFeeDecorator(ak AccountKeeper, bk types.BankKeeper, fk FeegrantKeeper, tfc TxFeeChecker) DeductFeeDecorator { + if tfc == nil { + tfc = checkTxFeeWithValidatorMinGasPrices + } + + return DeductFeeDecorator{ + accountKeeper: ak, + bankKeeper: bk, + feegrantKeeper: fk, + txFeeChecker: tfc, + } +} + +func (dfd DeductFeeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) { + feeTx, ok := tx.(sdk.FeeTx) + if !ok { + return ctx, sdkerrors.Wrap(sdkerrors.ErrTxDecode, "Tx must be a FeeTx") + } + + if !simulate && ctx.BlockHeight() > 0 && feeTx.GetGas() == 0 { + return ctx, sdkerrors.Wrap(sdkerrors.ErrInvalidGasLimit, "must provide positive gas") + } + + var ( + priority int64 + err error + ) + + fee := feeTx.GetFee() + if !simulate { + fee, priority, err = dfd.txFeeChecker(ctx, tx) + if err != nil { + return ctx, err + } + } + if err := dfd.checkDeductFee(ctx, tx, fee); err != nil { + return ctx, err + } + + newCtx := ctx.WithPriority(priority) + + return next(newCtx, tx, simulate) +} + +func (dfd DeductFeeDecorator) checkDeductFee(ctx sdk.Context, sdkTx sdk.Tx, fee sdk.Coins) error { + feeTx, ok := sdkTx.(sdk.FeeTx) + if !ok { + return sdkerrors.Wrap(sdkerrors.ErrTxDecode, "Tx must be a FeeTx") + } + + if addr := dfd.accountKeeper.GetModuleAddress(types.FeeCollectorName); addr == nil { + return fmt.Errorf("fee collector module account (%s) has not been set", types.FeeCollectorName) + } + + 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 sdkerrors.ErrInvalidRequest.Wrap("fee grants are not enabled") + } else if !feeGranter.Equals(feePayer) { + err := dfd.feegrantKeeper.UseGrantedFees(ctx, feeGranter, feePayer, fee, sdkTx.GetMsgs()) + if err != nil { + return sdkerrors.Wrapf(err, "%s does not allow to pay fees for %s", feeGranter, feePayer) + } + } + + deductFeesFrom = feeGranter + } + + deductFeesFromAcc := dfd.accountKeeper.GetAccount(ctx, deductFeesFrom) + if deductFeesFromAcc == nil { + return sdkerrors.ErrUnknownAddress.Wrapf("fee payer address: %s does not exist", deductFeesFrom) + } + + // deduct the fees + if !fee.IsZero() { + err := DeductFees(dfd.bankKeeper, ctx, deductFeesFromAcc, fee) + if err != nil { + return err + } + } + + events := sdk.Events{ + sdk.NewEvent( + sdk.EventTypeTx, + sdk.NewAttribute(sdk.AttributeKeyFee, fee.String()), + sdk.NewAttribute(sdk.AttributeKeyFeePayer, deductFeesFrom.String()), + ), + } + ctx.EventManager().EmitEvents(events) + + return nil +} + +// DeductFees deducts fees from the given account. +func DeductFees(bankKeeper types.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/ante/fee_test.go b/ante/fee_test.go new file mode 100644 index 00000000..9653cb1a --- /dev/null +++ b/ante/fee_test.go @@ -0,0 +1,142 @@ +package ante_test + +import ( + "testing" + + "cosmossdk.io/math" + 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" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/require" +) + +func TestDeductFeeDecorator_ZeroGas(t *testing.T) { + s := SetupTestSuite(t, true) + s.txBuilder = s.clientCtx.TxConfig.NewTxBuilder() + + mfd := ante.NewDeductFeeDecorator(s.accountKeeper, s.bankKeeper, s.feeGrantKeeper, nil) + antehandler := sdk.ChainAnteDecorators(mfd) + + // keys and addresses + accs := s.CreateTestAccounts(1) + + // msg and signatures + msg := testdata.NewTestMsg(accs[0].acc.GetAddress()) + require.NoError(t, s.txBuilder.SetMsgs(msg)) + + // set zero gas + s.txBuilder.SetGasLimit(0) + + privs, accNums, accSeqs := []cryptotypes.PrivKey{accs[0].priv}, []uint64{0}, []uint64{0} + tx, err := s.CreateTestTx(privs, accNums, accSeqs, s.ctx.ChainID()) + require.NoError(t, err) + + // Set IsCheckTx to true + s.ctx = s.ctx.WithIsCheckTx(true) + + _, err = antehandler(s.ctx, tx, false) + require.Error(t, err) + + // zero gas is accepted in simulation mode + _, err = antehandler(s.ctx, tx, true) + require.NoError(t, err) +} + +func TestEnsureMempoolFees(t *testing.T) { + s := SetupTestSuite(t, true) // setup + s.txBuilder = s.clientCtx.TxConfig.NewTxBuilder() + + mfd := ante.NewDeductFeeDecorator(s.accountKeeper, s.bankKeeper, s.feeGrantKeeper, nil) + antehandler := sdk.ChainAnteDecorators(mfd) + + // keys and addresses + accs := s.CreateTestAccounts(1) + + // msg and signatures + msg := testdata.NewTestMsg(accs[0].acc.GetAddress()) + feeAmount := testdata.NewTestFeeAmount() + gasLimit := uint64(15) + require.NoError(t, s.txBuilder.SetMsgs(msg)) + s.txBuilder.SetFeeAmount(feeAmount) + s.txBuilder.SetGasLimit(gasLimit) + + s.bankKeeper.EXPECT().SendCoinsFromAccountToModule(gomock.Any(), accs[0].acc.GetAddress(), authtypes.FeeCollectorName, feeAmount).Return(nil).Times(3) + + privs, accNums, accSeqs := []cryptotypes.PrivKey{accs[0].priv}, []uint64{0}, []uint64{0} + tx, err := s.CreateTestTx(privs, accNums, accSeqs, s.ctx.ChainID()) + require.NoError(t, err) + + // Set high gas price so standard test fee fails + atomPrice := sdk.NewDecCoinFromDec("atom", math.LegacyNewDec(20)) + highGasPrice := []sdk.DecCoin{atomPrice} + s.ctx = s.ctx.WithMinGasPrices(highGasPrice) + + // Set IsCheckTx to true + s.ctx = s.ctx.WithIsCheckTx(true) + + // antehandler errors with insufficient fees + _, err = antehandler(s.ctx, tx, false) + require.NotNil(t, err, "Decorator should have errored on too low fee for local gasPrice") + + // antehandler should not error since we do not check minGasPrice in simulation mode + cacheCtx, _ := s.ctx.CacheContext() + _, err = antehandler(cacheCtx, tx, true) + require.Nil(t, err, "Decorator should not have errored in simulation mode") + + // Set IsCheckTx to false + s.ctx = s.ctx.WithIsCheckTx(false) + + // antehandler should not error since we do not check minGasPrice in DeliverTx + _, err = antehandler(s.ctx, tx, false) + require.Nil(t, err, "MempoolFeeDecorator returned error in DeliverTx") + + // Set IsCheckTx back to true for testing sufficient mempool fee + s.ctx = s.ctx.WithIsCheckTx(true) + + atomPrice = sdk.NewDecCoinFromDec("atom", math.LegacyNewDec(0).Quo(math.LegacyNewDec(100000))) + lowGasPrice := []sdk.DecCoin{atomPrice} + s.ctx = s.ctx.WithMinGasPrices(lowGasPrice) + + newCtx, err := antehandler(s.ctx, tx, false) + require.Nil(t, err, "Decorator should not have errored on fee higher than local gasPrice") + // Priority is the smallest gas price amount in any denom. Since we have only 1 gas price + // of 10atom, the priority here is 10. + require.Equal(t, int64(10), newCtx.Priority()) +} + +func TestDeductFees(t *testing.T) { + s := SetupTestSuite(t, false) + s.txBuilder = s.clientCtx.TxConfig.NewTxBuilder() + + // keys and addresses + accs := s.CreateTestAccounts(1) + + // msg and signatures + msg := testdata.NewTestMsg(accs[0].acc.GetAddress()) + feeAmount := testdata.NewTestFeeAmount() + gasLimit := testdata.NewTestGasLimit() + require.NoError(t, s.txBuilder.SetMsgs(msg)) + s.txBuilder.SetFeeAmount(feeAmount) + s.txBuilder.SetGasLimit(gasLimit) + + privs, accNums, accSeqs := []cryptotypes.PrivKey{accs[0].priv}, []uint64{0}, []uint64{0} + tx, err := s.CreateTestTx(privs, accNums, accSeqs, s.ctx.ChainID()) + require.NoError(t, err) + + dfd := ante.NewDeductFeeDecorator(s.accountKeeper, s.bankKeeper, nil, nil) + antehandler := sdk.ChainAnteDecorators(dfd) + s.bankKeeper.EXPECT().SendCoinsFromAccountToModule(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(sdkerrors.ErrInsufficientFunds) + + _, err = antehandler(s.ctx, tx, false) + + require.NotNil(t, err, "Tx did not error when fee payer had insufficient funds") + + s.bankKeeper.EXPECT().SendCoinsFromAccountToModule(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil) + _, err = antehandler(s.ctx, tx, false) + + require.Nil(t, err, "Tx errored after account has been set with sufficient funds") +} diff --git a/ante/feegrant_test.go b/ante/feegrant_test.go new file mode 100644 index 00000000..130c66e6 --- /dev/null +++ b/ante/feegrant_test.go @@ -0,0 +1,251 @@ +package ante_test + +import ( + "math/rand" + "testing" + "time" + + "github.com/cometbft/cometbft/crypto" + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/require" + + "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/testutil/testdata" + + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + "github.com/cosmos/cosmos-sdk/types/simulation" + "github.com/cosmos/cosmos-sdk/types/tx/signing" + "github.com/cosmos/cosmos-sdk/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 TestDeductFeesNoDelegation(t *testing.T) { + cases := map[string]struct { + fee int64 + valid bool + err error + malleate func(*AnteTestSuite) (signer TestAccount, feeAcc sdk.AccAddress) + }{ + "paying with low funds": { + fee: 50, + valid: false, + err: sdkerrors.ErrInsufficientFunds, + malleate: func(suite *AnteTestSuite) (TestAccount, sdk.AccAddress) { + accs := suite.CreateTestAccounts(1) + // 2 calls are needed because we run the ante twice + suite.bankKeeper.EXPECT().SendCoinsFromAccountToModule(gomock.Any(), accs[0].acc.GetAddress(), authtypes.FeeCollectorName, gomock.Any()).Return(sdkerrors.ErrInsufficientFunds).Times(2) + return accs[0], nil + }, + }, + "paying with good funds": { + fee: 50, + valid: true, + malleate: func(suite *AnteTestSuite) (TestAccount, sdk.AccAddress) { + accs := suite.CreateTestAccounts(1) + suite.bankKeeper.EXPECT().SendCoinsFromAccountToModule(gomock.Any(), accs[0].acc.GetAddress(), authtypes.FeeCollectorName, gomock.Any()).Return(nil).Times(2) + return accs[0], nil + }, + }, + "paying with no account": { + fee: 1, + valid: false, + err: sdkerrors.ErrUnknownAddress, + malleate: func(suite *AnteTestSuite) (TestAccount, sdk.AccAddress) { + // Do not register the account + priv, _, addr := testdata.KeyTestPubAddr() + return TestAccount{ + acc: authtypes.NewBaseAccountWithAddress(addr), + priv: priv, + }, nil + }, + }, + "no fee with real account": { + fee: 0, + valid: true, + malleate: func(suite *AnteTestSuite) (TestAccount, sdk.AccAddress) { + accs := suite.CreateTestAccounts(1) + return accs[0], nil + }, + }, + "no fee with no account": { + fee: 0, + valid: false, + err: sdkerrors.ErrUnknownAddress, + malleate: func(suite *AnteTestSuite) (TestAccount, sdk.AccAddress) { + // Do not register the account + priv, _, addr := testdata.KeyTestPubAddr() + return TestAccount{ + acc: authtypes.NewBaseAccountWithAddress(addr), + priv: priv, + }, nil + }, + }, + "valid fee grant": { + // note: the original test said "valid fee grant with no account". + // this is impossible given that feegrant.GrantAllowance calls + // SetAccount for the grantee. + fee: 50, + valid: true, + malleate: func(suite *AnteTestSuite) (TestAccount, sdk.AccAddress) { + accs := suite.CreateTestAccounts(2) + suite.feeGrantKeeper.EXPECT().UseGrantedFees(gomock.Any(), accs[1].acc.GetAddress(), accs[0].acc.GetAddress(), gomock.Any(), gomock.Any()).Return(nil).Times(2) + suite.bankKeeper.EXPECT().SendCoinsFromAccountToModule(gomock.Any(), accs[1].acc.GetAddress(), authtypes.FeeCollectorName, gomock.Any()).Return(nil).Times(2) + + return accs[0], accs[1].acc.GetAddress() + }, + }, + "no fee grant": { + fee: 2, + valid: false, + err: sdkerrors.ErrNotFound, + malleate: func(suite *AnteTestSuite) (TestAccount, sdk.AccAddress) { + accs := suite.CreateTestAccounts(2) + suite.feeGrantKeeper.EXPECT(). + UseGrantedFees(gomock.Any(), accs[1].acc.GetAddress(), accs[0].acc.GetAddress(), gomock.Any(), gomock.Any()). + Return(sdkerrors.ErrNotFound.Wrap("fee-grant not found")). + Times(2) + return accs[0], accs[1].acc.GetAddress() + }, + }, + "allowance smaller than requested fee": { + fee: 50, + valid: false, + err: feegrant.ErrFeeLimitExceeded, + malleate: func(suite *AnteTestSuite) (TestAccount, sdk.AccAddress) { + accs := suite.CreateTestAccounts(2) + suite.feeGrantKeeper.EXPECT(). + UseGrantedFees(gomock.Any(), accs[1].acc.GetAddress(), accs[0].acc.GetAddress(), gomock.Any(), gomock.Any()). + Return(feegrant.ErrFeeLimitExceeded.Wrap("basic allowance")). + Times(2) + return accs[0], accs[1].acc.GetAddress() + }, + }, + "granter cannot cover allowed fee grant": { + fee: 50, + valid: false, + err: sdkerrors.ErrInsufficientFunds, + malleate: func(suite *AnteTestSuite) (TestAccount, sdk.AccAddress) { + accs := suite.CreateTestAccounts(2) + suite.feeGrantKeeper.EXPECT().UseGrantedFees(gomock.Any(), accs[1].acc.GetAddress(), accs[0].acc.GetAddress(), gomock.Any(), gomock.Any()).Return(nil).Times(2) + suite.bankKeeper.EXPECT().SendCoinsFromAccountToModule(gomock.Any(), accs[1].acc.GetAddress(), authtypes.FeeCollectorName, gomock.Any()).Return(sdkerrors.ErrInsufficientFunds).Times(2) + return accs[0], accs[1].acc.GetAddress() + }, + }, + } + + for name, stc := range cases { + tc := stc // to make scopelint happy + t.Run(name, func(t *testing.T) { + suite := SetupTestSuite(t, false) + protoTxCfg := tx.NewTxConfig(codec.NewProtoCodec(suite.encCfg.InterfaceRegistry), tx.DefaultSignModes) + // this just tests our handler + dfd := ante.NewDeductFeeDecorator(suite.accountKeeper, suite.bankKeeper, suite.feeGrantKeeper, nil) + feeAnteHandler := sdk.ChainAnteDecorators(dfd) + + // this tests the whole stack + anteHandlerStack := suite.anteHandler + + signer, feeAcc := stc.malleate(suite) + + fee := sdk.NewCoins(sdk.NewInt64Coin("atom", tc.fee)) + msgs := []sdk.Msg{testdata.NewTestMsg(signer.acc.GetAddress())} + + acc := suite.accountKeeper.GetAccount(suite.ctx, signer.acc.GetAddress()) + privs, accNums, seqs := []cryptotypes.PrivKey{signer.priv}, []uint64{0}, []uint64{0} + if acc != nil { + accNums, seqs = []uint64{acc.GetAccountNumber()}, []uint64{acc.GetSequence()} + } + + var defaultGenTxGas uint64 = 10000000 + tx, err := genTxWithFeeGranter(protoTxCfg, msgs, fee, defaultGenTxGas, suite.ctx.ChainID(), accNums, seqs, feeAcc, privs...) + require.NoError(t, err) + _, err = feeAnteHandler(suite.ctx, tx, false) // tests only feegrant ante + if tc.valid { + require.NoError(t, err) + } else { + require.ErrorIs(t, err, tc.err) + } + + _, err = anteHandlerStack(suite.ctx, tx, false) // tests while stack + if tc.valid { + require.NoError(t, err) + } else { + require.ErrorIs(t, err, tc.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/ante/setup.go b/ante/setup.go new file mode 100644 index 00000000..5a101a24 --- /dev/null +++ b/ante/setup.go @@ -0,0 +1,82 @@ +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/migrations/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()) + + if cp := ctx.ConsensusParams(); cp != nil && cp.Block != nil { + // If there exists a maximum block gas limit, we must ensure that the tx + // does not exceed it. + if cp.Block.MaxGas > 0 && gasTx.GetGas() > uint64(cp.Block.MaxGas) { + return newCtx, sdkerrors.Wrapf(sdkerrors.ErrInvalidGasLimit, "tx gas limit %d exceeds block max gas %d", gasTx.GetGas(), cp.Block.MaxGas) + } + } + + // 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/ante/setup_test.go b/ante/setup_test.go new file mode 100644 index 00000000..e3488ad2 --- /dev/null +++ b/ante/setup_test.go @@ -0,0 +1,139 @@ +package ante_test + +import ( + "testing" + + tmproto "github.com/cometbft/cometbft/proto/tendermint/types" + + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + storetypes "github.com/cosmos/cosmos-sdk/store/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" + "github.com/stretchr/testify/require" +) + +func TestSetupDecorator_BlockMaxGas(t *testing.T) { + suite := SetupTestSuite(t, true) + suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() + + // keys and addresses + priv1, _, addr1 := testdata.KeyTestPubAddr() + + // msg and signatures + msg := testdata.NewTestMsg(addr1) + feeAmount := testdata.NewTestFeeAmount() + require.NoError(t, suite.txBuilder.SetMsgs(msg)) + suite.txBuilder.SetFeeAmount(feeAmount) + suite.txBuilder.SetGasLimit(101) + + privs, accNums, accSeqs := []cryptotypes.PrivKey{priv1}, []uint64{0}, []uint64{0} + tx, err := suite.CreateTestTx(privs, accNums, accSeqs, suite.ctx.ChainID()) + require.NoError(t, err) + + sud := ante.NewSetUpContextDecorator() + antehandler := sdk.ChainAnteDecorators(sud) + + suite.ctx = suite.ctx. + WithBlockHeight(1). + WithGasMeter(storetypes.NewGasMeter(0)). + WithConsensusParams(&tmproto.ConsensusParams{ + Block: &tmproto.BlockParams{ + MaxGas: 100, + }, + }) + + _, err = antehandler(suite.ctx, tx, false) + require.Error(t, err) +} + +func TestSetup(t *testing.T) { + suite := SetupTestSuite(t, true) + 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() + require.NoError(t, 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()) + require.NoError(t, 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).WithGasMeter(sdk.NewGasMeter(0)) + + // Context GasMeter Limit not set + require.Equal(t, uint64(0), suite.ctx.GasMeter().Limit(), "GasMeter set with limit before setup") + + newCtx, err := antehandler(suite.ctx, tx, false) + require.Nil(t, err, "SetUpContextDecorator returned error") + + // Context GasMeter Limit should be set after SetUpContextDecorator runs + require.Equal(t, gasLimit, newCtx.GasMeter().Limit(), "GasMeter not set correctly") +} + +func TestRecoverPanic(t *testing.T) { + suite := SetupTestSuite(t, true) + 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() + require.NoError(t, 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()) + require.NoError(t, 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) + + require.NotNil(t, err, "Did not return error on OutOfGas panic") + + require.True(t, sdkerrors.ErrOutOfGas.Is(err), "Returned error is not an out of gas error") + require.Equal(t, gasLimit, newCtx.GasMeter().Limit()) + + antehandler = sdk.ChainAnteDecorators(sud, PanicDecorator{}) + require.Panics(t, 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/ante/sigverify.go b/ante/sigverify.go new file mode 100644 index 00000000..358d20d4 --- /dev/null +++ b/ante/sigverify.go @@ -0,0 +1,515 @@ +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/migrations/legacytx" + authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing" + "github.com/cosmos/cosmos-sdk/x/auth/types" + "github.com/sideprotocol/side/bitcoin/keys/segwit" +) + +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 { + if sigGasConsumer == nil { + sigGasConsumer = DefaultSigVerificationGasConsumer + } + + 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 will not check signatures 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) { + 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{ + Address: acc.GetAddress().String(), + ChainID: chainID, + AccountNumber: accNum, + Sequence: acc.GetSequence(), + PubKey: pubKey, + } + + // no need to verify signatures on recheck tx + if !simulate && !ctx.IsReCheckTx() { + 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 need to execute IncrementSequenceDecorator on RecheckTx since +// BaseApp.Commit() will set the check state based on the latest header. +// +// 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 *segwit.PubKey: + meter.ConsumeGas(params.SigVerifyCostSecp256k1, "ante verify: secp256k1") + 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/ante/sigverify_benchmark_test.go b/ante/sigverify_benchmark_test.go new file mode 100644 index 00000000..9094eabf --- /dev/null +++ b/ante/sigverify_benchmark_test.go @@ -0,0 +1,44 @@ +package ante_test + +import ( + "testing" + + tmcrypto "github.com/cometbft/cometbft/crypto" + "github.com/stretchr/testify/require" + + "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/ante/sigverify_test.go b/ante/sigverify_test.go new file mode 100644 index 00000000..9f7a14f5 --- /dev/null +++ b/ante/sigverify_test.go @@ -0,0 +1,405 @@ +package ante_test + +import ( + "fmt" + "testing" + + "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/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/migrations/legacytx" + "github.com/cosmos/cosmos-sdk/x/auth/types" + "github.com/stretchr/testify/require" +) + +func TestSetPubKey(t *testing.T) { + suite := SetupTestSuite(t, true) + 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.New(t)) + + 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.accountKeeper.NewAccountWithAddress(suite.ctx, addr) + require.NoError(t, acc.SetAccountNumber(uint64(i))) + suite.accountKeeper.SetAccount(suite.ctx, acc) + msgs[i] = testdata.NewTestMsg(addr) + } + require.NoError(t, 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(t, err) + + spkd := ante.NewSetPubKeyDecorator(suite.accountKeeper) + antehandler := sdk.ChainAnteDecorators(spkd) + + ctx, err := antehandler(suite.ctx, tx, false) + require.NoError(t, err) + + // Require that all accounts have pubkey set after Decorator runs + for i, addr := range addrs { + pk, err := suite.accountKeeper.GetPubKey(ctx, addr) + require.NoError(t, err, "Error on retrieving pubkey from account") + require.True(t, pubs[i].Equals(pk), + "Wrong Pubkey retrieved from AccountKeeper, idx=%d\nexpected=%s\n got=%s", i, pubs[i], pk) + } +} + +func TestConsumeSignatureVerificationGas(t *testing.T) { + suite := SetupTestSuite(t, true) + params := types.DefaultParams() + msg := []byte{1, 2, 3, 4} + + 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(suite.clientCtx.LegacyAmino, stdSig) + require.NoError(t, err) + err = multisig.AddSignatureV2(multisignature1, sigV2, pkSet1) + require.NoError(t, 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 { + require.NotNil(t, err) + } else { + require.Nil(t, err) + require.Equal(t, tt.gasConsumed, tt.args.meter.GasConsumed(), fmt.Sprintf("%d != %d", tt.gasConsumed, tt.args.meter.GasConsumed())) + } + } +} + +func TestSigVerification(t *testing.T) { + suite := SetupTestSuite(t, true) + 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.accountKeeper.NewAccountWithAddress(suite.ctx, addr) + require.NoError(t, acc.SetAccountNumber(uint64(i))) + suite.accountKeeper.SetAccount(suite.ctx, acc) + msgs[i] = testdata.NewTestMsg(addr) + } + + feeAmount := testdata.NewTestFeeAmount() + gasLimit := testdata.NewTestGasLimit() + + spkd := ante.NewSetPubKeyDecorator(suite.accountKeeper) + svd := ante.NewSigVerificationDecorator(suite.accountKeeper, suite.clientCtx.TxConfig.SignModeHandler()) + antehandler := sdk.ChainAnteDecorators(spkd, svd) + + type testCase struct { + name string + privs []cryptotypes.PrivKey + accNums []uint64 + accSeqs []uint64 + invalidSigs bool + recheck bool + shouldErr bool + } + validSigs := false + testCases := []testCase{ + {"no signers", []cryptotypes.PrivKey{}, []uint64{}, []uint64{}, validSigs, false, true}, + {"not enough signers", []cryptotypes.PrivKey{priv1, priv2}, []uint64{0, 1}, []uint64{0, 0}, validSigs, false, true}, + {"wrong order signers", []cryptotypes.PrivKey{priv3, priv2, priv1}, []uint64{2, 1, 0}, []uint64{0, 0, 0}, validSigs, false, true}, + {"wrong accnums", []cryptotypes.PrivKey{priv1, priv2, priv3}, []uint64{7, 8, 9}, []uint64{0, 0, 0}, validSigs, false, true}, + {"wrong sequences", []cryptotypes.PrivKey{priv1, priv2, priv3}, []uint64{0, 1, 2}, []uint64{3, 4, 5}, validSigs, false, true}, + {"valid tx", []cryptotypes.PrivKey{priv1, priv2, priv3}, []uint64{0, 1, 2}, []uint64{0, 0, 0}, validSigs, false, false}, + {"no err on recheck", []cryptotypes.PrivKey{priv1, priv2, priv3}, []uint64{0, 0, 0}, []uint64{0, 0, 0}, !validSigs, 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 + + require.NoError(t, 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()) + require.NoError(t, err) + if tc.invalidSigs { + txSigs, _ := tx.GetSignaturesV2() + badSig, _ := tc.privs[0].Sign([]byte("unrelated message")) + txSigs[0] = signing.SignatureV2{ + PubKey: tc.privs[0].PubKey(), + Data: &signing.SingleSignatureData{ + SignMode: suite.clientCtx.TxConfig.SignModeHandler().DefaultMode(), + Signature: badSig, + }, + Sequence: tc.accSeqs[0], + } + suite.txBuilder.SetSignatures(txSigs...) + tx = suite.txBuilder.GetTx() + } + + _, err = antehandler(suite.ctx, tx, false) + if tc.shouldErr { + require.NotNil(t, err, "TestCase %d: %s did not error as expected", i, tc.name) + } else { + require.Nil(t, 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 TestSigVerification_ExplicitAmino(t *testing.T) { + suite := SetupTestSuite(t, true) + // 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.accountKeeper, + BankKeeper: suite.bankKeeper, + FeegrantKeeper: suite.feeGrantKeeper, + SignModeHandler: txConfig.SignModeHandler(), + SigGasConsumer: ante.DefaultSigVerificationGasConsumer, + }, + ) + + require.NoError(t, 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.accountKeeper.NewAccountWithAddress(suite.ctx, addr) + require.NoError(t, acc.SetAccountNumber(uint64(i))) + suite.accountKeeper.SetAccount(suite.ctx, acc) + msgs[i] = testdata.NewTestMsg(addr) + } + + feeAmount := testdata.NewTestFeeAmount() + gasLimit := testdata.NewTestGasLimit() + + spkd := ante.NewSetPubKeyDecorator(suite.accountKeeper) + svd := ante.NewSigVerificationDecorator(suite.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{priv1, priv2, priv3}, []uint64{0, 1, 2}, []uint64{0, 0, 0}, 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 + + require.NoError(t, 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()) + require.NoError(t, err) + + _, err = antehandler(suite.ctx, tx, false) + if tc.shouldErr { + require.NotNil(t, err, "TestCase %d: %s did not error as expected", i, tc.name) + } else { + require.Nil(t, err, "TestCase %d: %s errored unexpectedly. Err: %v", i, tc.name, err) + } + } +} + +func TestSigIntegration(t *testing.T) { + // generate private keys + privs := []cryptotypes.PrivKey{ + secp256k1.GenPrivKey(), + secp256k1.GenPrivKey(), + secp256k1.GenPrivKey(), + } + + params := types.DefaultParams() + initialSigCost := params.SigVerifyCostSecp256k1 + initialCost, err := runSigDecorators(t, params, false, privs...) + require.Nil(t, err) + + params.SigVerifyCostSecp256k1 *= 2 + doubleCost, err := runSigDecorators(t, params, false, privs...) + require.Nil(t, err) + + require.Equal(t, initialSigCost*uint64(len(privs)), doubleCost-initialCost) +} + +func runSigDecorators(t *testing.T, params types.Params, _ bool, privs ...cryptotypes.PrivKey) (sdk.Gas, error) { + suite := SetupTestSuite(t, true) + suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() + + // Make block-height non-zero to include accNum in SignBytes + suite.ctx = suite.ctx.WithBlockHeight(1) + err := suite.accountKeeper.SetParams(suite.ctx, params) + require.NoError(t, err) + + 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.accountKeeper.NewAccountWithAddress(suite.ctx, addr) + require.NoError(t, acc.SetAccountNumber(uint64(i))) + suite.accountKeeper.SetAccount(suite.ctx, acc) + msgs[i] = testdata.NewTestMsg(addr) + accNums[i] = uint64(i) + accSeqs[i] = uint64(0) + } + require.NoError(t, 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()) + require.NoError(t, err) + + spkd := ante.NewSetPubKeyDecorator(suite.accountKeeper) + svgc := ante.NewSigGasConsumeDecorator(suite.accountKeeper, ante.DefaultSigVerificationGasConsumer) + svd := ante.NewSigVerificationDecorator(suite.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 TestIncrementSequenceDecorator(t *testing.T) { + suite := SetupTestSuite(t, true) + suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() + + priv, _, addr := testdata.KeyTestPubAddr() + acc := suite.accountKeeper.NewAccountWithAddress(suite.ctx, addr) + require.NoError(t, acc.SetAccountNumber(uint64(50))) + suite.accountKeeper.SetAccount(suite.ctx, acc) + + msgs := []sdk.Msg{testdata.NewTestMsg(addr)} + require.NoError(t, suite.txBuilder.SetMsgs(msgs...)) + privs := []cryptotypes.PrivKey{priv} + accNums := []uint64{suite.accountKeeper.GetAccount(suite.ctx, addr).GetAccountNumber()} + accSeqs := []uint64{suite.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()) + require.NoError(t, err) + + isd := ante.NewIncrementSequenceDecorator(suite.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) + require.NoError(t, err, "unexpected error; tc #%d, %v", i, tc) + require.Equal(t, tc.expectedSeq, suite.accountKeeper.GetAccount(suite.ctx, addr).GetSequence()) + } +} diff --git a/ante/testutil/expected_keepers_mocks.go b/ante/testutil/expected_keepers_mocks.go new file mode 100644 index 00000000..ff2803af --- /dev/null +++ b/ante/testutil/expected_keepers_mocks.go @@ -0,0 +1,127 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: x/auth/ante/expected_keepers.go + +// Package testutil is a generated GoMock package. +package testutil + +import ( + reflect "reflect" + + types "github.com/cosmos/cosmos-sdk/types" + types0 "github.com/cosmos/cosmos-sdk/x/auth/types" + gomock "github.com/golang/mock/gomock" +) + +// MockAccountKeeper is a mock of AccountKeeper interface. +type MockAccountKeeper struct { + ctrl *gomock.Controller + recorder *MockAccountKeeperMockRecorder +} + +// MockAccountKeeperMockRecorder is the mock recorder for MockAccountKeeper. +type MockAccountKeeperMockRecorder struct { + mock *MockAccountKeeper +} + +// NewMockAccountKeeper creates a new mock instance. +func NewMockAccountKeeper(ctrl *gomock.Controller) *MockAccountKeeper { + mock := &MockAccountKeeper{ctrl: ctrl} + mock.recorder = &MockAccountKeeperMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockAccountKeeper) EXPECT() *MockAccountKeeperMockRecorder { + return m.recorder +} + +// GetAccount mocks base method. +func (m *MockAccountKeeper) GetAccount(ctx types.Context, addr types.AccAddress) types0.AccountI { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetAccount", ctx, addr) + ret0, _ := ret[0].(types0.AccountI) + return ret0 +} + +// GetAccount indicates an expected call of GetAccount. +func (mr *MockAccountKeeperMockRecorder) GetAccount(ctx, addr interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAccount", reflect.TypeOf((*MockAccountKeeper)(nil).GetAccount), ctx, addr) +} + +// GetModuleAddress mocks base method. +func (m *MockAccountKeeper) GetModuleAddress(moduleName string) types.AccAddress { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetModuleAddress", moduleName) + ret0, _ := ret[0].(types.AccAddress) + return ret0 +} + +// GetModuleAddress indicates an expected call of GetModuleAddress. +func (mr *MockAccountKeeperMockRecorder) GetModuleAddress(moduleName interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetModuleAddress", reflect.TypeOf((*MockAccountKeeper)(nil).GetModuleAddress), moduleName) +} + +// GetParams mocks base method. +func (m *MockAccountKeeper) GetParams(ctx types.Context) types0.Params { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetParams", ctx) + ret0, _ := ret[0].(types0.Params) + return ret0 +} + +// GetParams indicates an expected call of GetParams. +func (mr *MockAccountKeeperMockRecorder) GetParams(ctx interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetParams", reflect.TypeOf((*MockAccountKeeper)(nil).GetParams), ctx) +} + +// SetAccount mocks base method. +func (m *MockAccountKeeper) SetAccount(ctx types.Context, acc types0.AccountI) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "SetAccount", ctx, acc) +} + +// SetAccount indicates an expected call of SetAccount. +func (mr *MockAccountKeeperMockRecorder) SetAccount(ctx, acc interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetAccount", reflect.TypeOf((*MockAccountKeeper)(nil).SetAccount), ctx, acc) +} + +// MockFeegrantKeeper is a mock of FeegrantKeeper interface. +type MockFeegrantKeeper struct { + ctrl *gomock.Controller + recorder *MockFeegrantKeeperMockRecorder +} + +// MockFeegrantKeeperMockRecorder is the mock recorder for MockFeegrantKeeper. +type MockFeegrantKeeperMockRecorder struct { + mock *MockFeegrantKeeper +} + +// NewMockFeegrantKeeper creates a new mock instance. +func NewMockFeegrantKeeper(ctrl *gomock.Controller) *MockFeegrantKeeper { + mock := &MockFeegrantKeeper{ctrl: ctrl} + mock.recorder = &MockFeegrantKeeperMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockFeegrantKeeper) EXPECT() *MockFeegrantKeeperMockRecorder { + return m.recorder +} + +// UseGrantedFees mocks base method. +func (m *MockFeegrantKeeper) UseGrantedFees(ctx types.Context, granter, grantee types.AccAddress, fee types.Coins, msgs []types.Msg) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UseGrantedFees", ctx, granter, grantee, fee, msgs) + ret0, _ := ret[0].(error) + return ret0 +} + +// UseGrantedFees indicates an expected call of UseGrantedFees. +func (mr *MockFeegrantKeeperMockRecorder) UseGrantedFees(ctx, granter, grantee, fee, msgs interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UseGrantedFees", reflect.TypeOf((*MockFeegrantKeeper)(nil).UseGrantedFees), ctx, granter, grantee, fee, msgs) +} diff --git a/ante/testutil_test.go b/ante/testutil_test.go new file mode 100644 index 00000000..93318e7f --- /dev/null +++ b/ante/testutil_test.go @@ -0,0 +1,222 @@ +package ante_test + +import ( + "testing" + + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/require" + + "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/testutil" + "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" + "github.com/cosmos/cosmos-sdk/x/auth/ante" + "github.com/cosmos/cosmos-sdk/x/auth/keeper" + + moduletestutil "github.com/cosmos/cosmos-sdk/types/module/testutil" + antetestutil "github.com/cosmos/cosmos-sdk/x/auth/ante/testutil" + xauthsigning "github.com/cosmos/cosmos-sdk/x/auth/signing" + authtestutil "github.com/cosmos/cosmos-sdk/x/auth/testutil" + "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 { + anteHandler sdk.AnteHandler + ctx sdk.Context + clientCtx client.Context + txBuilder client.TxBuilder + accountKeeper keeper.AccountKeeper + bankKeeper *authtestutil.MockBankKeeper + feeGrantKeeper *antetestutil.MockFeegrantKeeper + encCfg moduletestutil.TestEncodingConfig +} + +// SetupTest setups a new test, with new app, context, and anteHandler. +func SetupTestSuite(t *testing.T, isCheckTx bool) *AnteTestSuite { + suite := &AnteTestSuite{} + ctrl := gomock.NewController(t) + suite.bankKeeper = authtestutil.NewMockBankKeeper(ctrl) + + suite.feeGrantKeeper = antetestutil.NewMockFeegrantKeeper(ctrl) + + key := sdk.NewKVStoreKey(types.StoreKey) + testCtx := testutil.DefaultContextWithDB(t, key, sdk.NewTransientStoreKey("transient_test")) + suite.ctx = testCtx.Ctx.WithIsCheckTx(isCheckTx).WithBlockHeight(1) // app.BaseApp.NewContext(isCheckTx, tmproto.Header{}).WithBlockHeight(1) + suite.encCfg = moduletestutil.MakeTestEncodingConfig(auth.AppModuleBasic{}) + + maccPerms := map[string][]string{ + "fee_collector": nil, + "mint": {"minter"}, + "bonded_tokens_pool": {"burner", "staking"}, + "not_bonded_tokens_pool": {"burner", "staking"}, + "multiPerm": {"burner", "minter", "staking"}, + "random": {"random"}, + } + + suite.accountKeeper = keeper.NewAccountKeeper( + suite.encCfg.Codec, key, types.ProtoBaseAccount, maccPerms, sdk.Bech32MainPrefix, types.NewModuleAddress("gov").String(), + ) + suite.accountKeeper.GetModuleAccount(suite.ctx, types.FeeCollectorName) + err := suite.accountKeeper.SetParams(suite.ctx, types.DefaultParams()) + require.NoError(t, err) + + // We're using TestMsg encoding in some tests, so register it here. + suite.encCfg.Amino.RegisterConcrete(&testdata.TestMsg{}, "testdata.TestMsg", nil) + testdata.RegisterInterfaces(suite.encCfg.InterfaceRegistry) + + suite.clientCtx = client.Context{}. + WithTxConfig(suite.encCfg.TxConfig) + + anteHandler, err := ante.NewAnteHandler( + ante.HandlerOptions{ + AccountKeeper: suite.accountKeeper, + BankKeeper: suite.bankKeeper, + FeegrantKeeper: suite.feeGrantKeeper, + SignModeHandler: suite.encCfg.TxConfig.SignModeHandler(), + SigGasConsumer: ante.DefaultSigVerificationGasConsumer, + }, + ) + + require.NoError(t, err) + suite.anteHandler = anteHandler + + suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() + + return suite +} + +func (suite *AnteTestSuite) CreateTestAccounts(numAccs int) []TestAccount { + var accounts []TestAccount + + for i := 0; i < numAccs; i++ { + priv, _, addr := testdata.KeyTestPubAddr() + acc := suite.accountKeeper.NewAccountWithAddress(suite.ctx, addr) + acc.SetAccountNumber(uint64(i)) + suite.accountKeeper.SetAccount(suite.ctx, acc) + accounts = append(accounts, TestAccount{acc, priv}) + } + + return accounts +} + +// TestCase represents a test case used in test tables. +type TestCase struct { + desc string + malleate func(*AnteTestSuite) TestCaseArgs + simulate bool + expPass bool + expErr error +} + +type TestCaseArgs struct { + chainID string + accNums []uint64 + accSeqs []uint64 + feeAmount sdk.Coins + gasLimit uint64 + msgs []sdk.Msg + privs []cryptotypes.PrivKey +} + +// DeliverMsgs constructs a tx and runs it through the ante handler. This is used to set the context for a test case, for +// example to test for replay protection. +func (suite *AnteTestSuite) DeliverMsgs(t *testing.T, privs []cryptotypes.PrivKey, msgs []sdk.Msg, feeAmount sdk.Coins, gasLimit uint64, accNums, accSeqs []uint64, chainID string, simulate bool) (sdk.Context, error) { + require.NoError(t, suite.txBuilder.SetMsgs(msgs...)) + suite.txBuilder.SetFeeAmount(feeAmount) + suite.txBuilder.SetGasLimit(gasLimit) + + tx, txErr := suite.CreateTestTx(privs, accNums, accSeqs, chainID) + require.NoError(t, txErr) + return suite.anteHandler(suite.ctx, tx, simulate) +} + +func (suite *AnteTestSuite) RunTestCase(t *testing.T, tc TestCase, args TestCaseArgs) { + require.NoError(t, suite.txBuilder.SetMsgs(args.msgs...)) + suite.txBuilder.SetFeeAmount(args.feeAmount) + suite.txBuilder.SetGasLimit(args.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(args.privs, args.accNums, args.accSeqs, args.chainID) + newCtx, anteErr := suite.anteHandler(suite.ctx, tx, tc.simulate) + + if tc.expPass { + require.NoError(t, txErr) + require.NoError(t, anteErr) + require.NotNil(t, newCtx) + + suite.ctx = newCtx + } else { + switch { + case txErr != nil: + require.Error(t, txErr) + require.ErrorIs(t, txErr, tc.expErr) + + case anteErr != nil: + require.Error(t, anteErr) + require.ErrorIs(t, anteErr, tc.expErr) + + default: + t.Fatal("expected one of txErr, anteErr to be an error") + } + } +} + +// 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 +} diff --git a/ante/validator_tx_fee.go b/ante/validator_tx_fee.go new file mode 100644 index 00000000..e8c865b7 --- /dev/null +++ b/ante/validator_tx_fee.go @@ -0,0 +1,66 @@ +package ante + +import ( + "math" + + sdkmath "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" +) + +// checkTxFeeWithValidatorMinGasPrices implements the default fee logic, where the minimum price per +// unit of gas is fixed and set by each validator, can the tx priority is computed from the gas price. +func checkTxFeeWithValidatorMinGasPrices(ctx sdk.Context, tx sdk.Tx) (sdk.Coins, int64, error) { + feeTx, ok := tx.(sdk.FeeTx) + if !ok { + return nil, 0, 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() { + 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 := sdkmath.LegacyNewDec(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 nil, 0, sdkerrors.Wrapf(sdkerrors.ErrInsufficientFee, "insufficient fees; got: %s required: %s", feeCoins, requiredFees) + } + } + } + + priority := getTxPriority(feeCoins, int64(gas)) + return feeCoins, priority, nil +} + +// getTxPriority returns a naive tx priority based on the amount of the smallest denomination of the gas price +// provided in a transaction. +// NOTE: This implementation should be used with a great consideration as it opens potential attack vectors +// where txs with multiple coins could not be prioritize as expected. +func getTxPriority(fee sdk.Coins, gas int64) int64 { + var priority int64 + for _, c := range fee { + p := int64(math.MaxInt64) + gasPrice := c.Amount.QuoRaw(gas) + if gasPrice.IsInt64() { + p = gasPrice.Int64() + } + if priority == 0 || p < priority { + priority = p + } + } + + return priority +} diff --git a/app/app.go b/app/app.go index 9269467b..262e54ad 100644 --- a/app/app.go +++ b/app/app.go @@ -28,7 +28,6 @@ import ( "github.com/cosmos/cosmos-sdk/types/module" "github.com/cosmos/cosmos-sdk/version" "github.com/cosmos/cosmos-sdk/x/auth" - "github.com/cosmos/cosmos-sdk/x/auth/ante" authsims "github.com/cosmos/cosmos-sdk/x/auth/simulation" authtx "github.com/cosmos/cosmos-sdk/x/auth/tx" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" @@ -51,12 +50,14 @@ import ( stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types" icatypes "github.com/cosmos/ibc-go/v7/modules/apps/27-interchain-accounts/types" + "github.com/sideprotocol/side/ante" ibcfeetypes "github.com/cosmos/ibc-go/v7/modules/apps/29-fee/types" ibctransfertypes "github.com/cosmos/ibc-go/v7/modules/apps/transfer/types" ibcexported "github.com/cosmos/ibc-go/v7/modules/core/exported" - "github.com/prometheus/client_golang/prometheus" + + // "github.com/prometheus/client_golang/prometheus" "github.com/spf13/cast" gmmmoduletypes "github.com/sideprotocol/side/x/gmm/types" @@ -69,9 +70,9 @@ import ( "github.com/sideprotocol/side/docs" // wasmd module integrate - "github.com/CosmWasm/wasmd/x/wasm" - wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper" - wasmTypes "github.com/CosmWasm/wasmd/x/wasm/types" + //"github.com/CosmWasm/wasmd/x/wasm" + //wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper" + //wasmTypes "github.com/CosmWasm/wasmd/x/wasm/types" "github.com/sideprotocol/side/app/upgrades" // upgrades @@ -79,6 +80,7 @@ import ( v1 "github.com/sideprotocol/side/app/upgrades/v1" v2 "github.com/sideprotocol/side/app/upgrades/v2" // packet forward module + // bitcoincdc "github.com/sideprotocol/side/bitcoin/codec" ) const ( @@ -152,6 +154,8 @@ func New( interfaceRegistry := encodingConfig.InterfaceRegistry txConfig := encodingConfig.TxConfig + //bitcoincdc.RegisterInterfaces(interfaceRegistry) + //bitcoincdc.RegisterCrypto(cdc) bApp := baseapp.NewBaseApp( Name, logger, @@ -174,14 +178,14 @@ func New( } app.homePath = homePath - wasmDir := filepath.Join(homePath, "wasm") - wasmConfig, err := wasm.ReadWasmConfig(appOpts) - wasmOpts := GetWasmOpts(appOpts) + //wasmDir := filepath.Join(homePath, "wasm") + //wasmConfig, err := wasm.ReadWasmConfig(appOpts) + // wasmOpts := GetWasmOpts(appOpts) // Uncomment this for debugging contracts. In the future this could be made into a param passed by the tests // wasmConfig.ContractDebugMode = true - if err != nil { - panic(fmt.Sprintf("error while reading wasm config: %s", err)) - } + // if err != nil { + // panic(fmt.Sprintf("error while reading wasm config: %s", err)) + // } app.InitSpecialKeepers( appCodec, @@ -198,9 +202,9 @@ func New( encodingConfig, bApp, moduleAccountPermissions, - wasmDir, - wasmConfig, - wasmOpts, + //wasmDir, + //wasmConfig, + //wasmOpts, app.BlockedAddrs(), ) @@ -265,7 +269,7 @@ func New( upgradetypes.ModuleName, vestingtypes.ModuleName, consensusparamtypes.ModuleName, - wasmTypes.ModuleName, + //wasmTypes.ModuleName, gmmmoduletypes.ModuleName, yieldmoduletypes.ModuleName, // this line is used by starport scaffolding # stargate/app/initGenesis @@ -457,16 +461,16 @@ func (app *App) ModuleManager() *module.Manager { return app.mm } -func GetWasmOpts(appOpts servertypes.AppOptions) []wasm.Option { - var wasmOpts []wasm.Option - if cast.ToBool(appOpts.Get("telemetry.enabled")) { - wasmOpts = append(wasmOpts, wasmkeeper.WithVMCacheMetrics(prometheus.DefaultRegisterer)) - } +// func GetWasmOpts(appOpts servertypes.AppOptions) []wasm.Option { +// var wasmOpts []wasm.Option +// if cast.ToBool(appOpts.Get("telemetry.enabled")) { +// wasmOpts = append(wasmOpts, wasmkeeper.WithVMCacheMetrics(prometheus.DefaultRegisterer)) +// } - wasmOpts = append(wasmOpts, wasmkeeper.WithGasRegister(NewSideWasmGasRegister())) +// wasmOpts = append(wasmOpts, wasmkeeper.WithGasRegister(NewSideWasmGasRegister())) - return wasmOpts -} +// return wasmOpts +// } func (app *App) setupUpgradeStoreLoaders() { upgradeInfo, err := app.UpgradeKeeper.ReadUpgradeInfoFromDisk() diff --git a/app/encoding.go b/app/encoding.go index 91f5666f..16a1b855 100644 --- a/app/encoding.go +++ b/app/encoding.go @@ -6,13 +6,21 @@ import ( "github.com/cosmos/cosmos-sdk/std" "github.com/cosmos/cosmos-sdk/x/auth/tx" + cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" "github.com/sideprotocol/side/app/params" + bitcoincdc "github.com/sideprotocol/side/bitcoin/codec" ) // makeEncodingConfig creates an EncodingConfig for an amino based test configuration. func makeEncodingConfig() params.EncodingConfig { amino := codec.NewLegacyAmino() + bitcoincdc.RegisterCrypto(amino) + //cryptocodec.RegisterCrypto(amino) + interfaceRegistry := types.NewInterfaceRegistry() + bitcoincdc.RegisterInterfaces(interfaceRegistry) + cryptocodec.RegisterInterfaces(interfaceRegistry) + marshaler := codec.NewProtoCodec(interfaceRegistry) txCfg := tx.NewTxConfig(marshaler, tx.DefaultSignModes) @@ -27,9 +35,12 @@ func makeEncodingConfig() params.EncodingConfig { // MakeEncodingConfig creates an EncodingConfig for testing func MakeEncodingConfig() params.EncodingConfig { encodingConfig := makeEncodingConfig() + //bitcoincdc.RegisterCrypto(encodingConfig.Amino) + //bitcoincdc.RegisterInterfaces(encodingConfig.InterfaceRegistry) std.RegisterLegacyAminoCodec(encodingConfig.Amino) std.RegisterInterfaces(encodingConfig.InterfaceRegistry) ModuleBasics.RegisterLegacyAminoCodec(encodingConfig.Amino) ModuleBasics.RegisterInterfaces(encodingConfig.InterfaceRegistry) + return encodingConfig } diff --git a/app/keepers/keepers.go b/app/keepers/keepers.go index 02d8c613..e994ee5e 100644 --- a/app/keepers/keepers.go +++ b/app/keepers/keepers.go @@ -35,7 +35,8 @@ import ( paramskeeper "github.com/cosmos/cosmos-sdk/x/params/keeper" paramstypes "github.com/cosmos/cosmos-sdk/x/params/types" paramproposal "github.com/cosmos/cosmos-sdk/x/params/types/proposal" - swasm "github.com/sideprotocol/side/wasmbinding" + + //swasm "github.com/sideprotocol/side/wasmbinding" slashingkeeper "github.com/cosmos/cosmos-sdk/x/slashing/keeper" slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types" @@ -76,19 +77,23 @@ import ( appparams "github.com/sideprotocol/side/app/params" - "github.com/CosmWasm/wasmd/x/wasm" - wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper" - wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" + //"github.com/CosmWasm/wasmd/x/wasm" + //wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper" + //wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" - "github.com/sideprotocol/packet-forward-middleware/v7/packetforward" + // "github.com/sideprotocol/packet-forward-middleware/v7/packetforward" packetforwardkeeper "github.com/sideprotocol/packet-forward-middleware/v7/packetforward/keeper" packetforwardtypes "github.com/sideprotocol/packet-forward-middleware/v7/packetforward/types" ) const ( - AccountAddressPrefix = "osmo" + AccountAddressPrefix = "side" ) +// var ( +// govAuthor = "bc1qjs3uxh3w8n6qhlegzjymyq9xn4jp8jam2qgy7x" //govAuthor// authtypes.NewModuleAddress(govtypes.ModuleName).String() +// ) + type AppKeepers struct { // keepers AccountKeeper authkeeper.AccountKeeper @@ -115,7 +120,7 @@ type AppKeepers struct { FeeGrantKeeper feegrantkeeper.Keeper GroupKeeper groupkeeper.Keeper ConsensusParamsKeeper consensusparamkeeper.Keeper - WasmKeeper wasm.Keeper + //WasmKeeper wasm.Keeper // make scoped keepers public for test purposes ScopedIBCKeeper capabilitykeeper.ScopedKeeper @@ -150,9 +155,11 @@ func (appKeepers *AppKeepers) InitSpecialKeepers( paramsKeeper := appKeepers.initParamsKeeper(appCodec, cdc, appKeepers.keys[paramstypes.StoreKey], appKeepers.tkeys[paramstypes.TStoreKey]) appKeepers.ParamsKeeper = paramsKeeper + govAuthor := authtypes.NewModuleAddress(govtypes.ModuleName).String() + // set the BaseApp's parameter store consensusParamsKeeper := consensusparamkeeper.NewKeeper( - appCodec, appKeepers.keys[consensusparamtypes.StoreKey], authtypes.NewModuleAddress(govtypes.ModuleName).String()) + appCodec, appKeepers.keys[consensusparamtypes.StoreKey], govAuthor) appKeepers.ConsensusParamsKeeper = consensusParamsKeeper bApp.SetParamStore(&appKeepers.ConsensusParamsKeeper) @@ -162,7 +169,7 @@ func (appKeepers *AppKeepers) InitSpecialKeepers( appCodec, homePath, bApp, - authtypes.NewModuleAddress(govtypes.ModuleName).String(), + govAuthor, ) appKeepers.UpgradeKeeper = upgradeKeeper @@ -171,12 +178,12 @@ func (appKeepers *AppKeepers) InitSpecialKeepers( appKeepers.ScopedIBCKeeper = appKeepers.CapabilityKeeper.ScopeToModule(ibcexported.ModuleName) appKeepers.ScopedICAHostKeeper = appKeepers.CapabilityKeeper.ScopeToModule(icahosttypes.SubModuleName) appKeepers.ScopedTransferKeeper = appKeepers.CapabilityKeeper.ScopeToModule(ibctransfertypes.ModuleName) - appKeepers.scopedWasmKeeper = appKeepers.CapabilityKeeper.ScopeToModule(wasm.ModuleName) + //appKeepers.scopedWasmKeeper = appKeepers.CapabilityKeeper.ScopeToModule(wasm.ModuleName) // TODO: Make a SetInvCheckPeriod fn on CrisisKeeper. // IMO, its bad design atm that it requires this in state machine initialization crisisKeeper := crisiskeeper.NewKeeper( - appCodec, appKeepers.keys[crisistypes.StoreKey], invCheckPeriod, appKeepers.BankKeeper, authtypes.FeeCollectorName, authtypes.NewModuleAddress(govtypes.ModuleName).String(), + appCodec, appKeepers.keys[crisistypes.StoreKey], invCheckPeriod, appKeepers.BankKeeper, authtypes.FeeCollectorName, govAuthor, ) appKeepers.CrisisKeeper = crisisKeeper } @@ -187,20 +194,21 @@ func (appKeepers *AppKeepers) InitNormalKeepers( encodingConfig appparams.EncodingConfig, bApp *baseapp.BaseApp, maccPerms map[string][]string, - wasmDir string, - wasmConfig wasmtypes.WasmConfig, - wasmOpts []wasmkeeper.Option, + //wasmDir string, + //wasmConfig wasmtypes.WasmConfig, + //wasmOpts []wasmkeeper.Option, blockedAddress map[string]bool, ) { legacyAmino := encodingConfig.Amino // add keepers + govAuthor := authtypes.NewModuleAddress(govtypes.ModuleName).String() appKeepers.AccountKeeper = authkeeper.NewAccountKeeper( appCodec, appKeepers.keys[authtypes.StoreKey], authtypes.ProtoBaseAccount, maccPerms, sdk.Bech32PrefixAccAddr, - authtypes.NewModuleAddress(govtypes.ModuleName).String(), + govAuthor, ) appKeepers.AuthzKeeper = authzkeeper.NewKeeper( @@ -215,7 +223,7 @@ func (appKeepers *AppKeepers) InitNormalKeepers( appKeepers.keys[banktypes.StoreKey], appKeepers.AccountKeeper, blockedAddress, - authtypes.NewModuleAddress(govtypes.ModuleName).String(), + govAuthor, ) appKeepers.StakingKeeper = stakingkeeper.NewKeeper( @@ -223,7 +231,7 @@ func (appKeepers *AppKeepers) InitNormalKeepers( appKeepers.keys[stakingtypes.StoreKey], appKeepers.AccountKeeper, appKeepers.BankKeeper, - authtypes.NewModuleAddress(govtypes.ModuleName).String(), + govAuthor, ) appKeepers.FeeGrantKeeper = feegrantkeeper.NewKeeper( @@ -239,7 +247,7 @@ func (appKeepers *AppKeepers) InitNormalKeepers( appKeepers.AccountKeeper, appKeepers.BankKeeper, authtypes.FeeCollectorName, - authtypes.NewModuleAddress(govtypes.ModuleName).String(), + govAuthor, ) appKeepers.DistrKeeper = distrkeeper.NewKeeper( @@ -249,7 +257,7 @@ func (appKeepers *AppKeepers) InitNormalKeepers( appKeepers.BankKeeper, appKeepers.StakingKeeper, authtypes.FeeCollectorName, - authtypes.NewModuleAddress(govtypes.ModuleName).String(), + govAuthor, ) slashingKeeper := slashingkeeper.NewKeeper( @@ -257,7 +265,7 @@ func (appKeepers *AppKeepers) InitNormalKeepers( legacyAmino, appKeepers.keys[slashingtypes.StoreKey], appKeepers.StakingKeeper, - authtypes.NewModuleAddress(govtypes.ModuleName).String(), + govAuthor, ) appKeepers.SlashingKeeper = slashingKeeper @@ -330,7 +338,7 @@ func (appKeepers *AppKeepers) InitNormalKeepers( appKeepers.DistrKeeper, appKeepers.BankKeeper, appKeepers.IBCKeeper.ChannelKeeper, - authtypes.NewModuleAddress(govtypes.ModuleName).String(), + govAuthor, ) // Create Transfer Keepers @@ -366,7 +374,7 @@ func (appKeepers *AppKeepers) InitNormalKeepers( appKeepers.StakingKeeper, bApp.MsgServiceRouter(), govConfig, - authtypes.NewModuleAddress(govtypes.ModuleName).String(), + govAuthor, ) appKeepers.GovKeeper = govKeeper @@ -402,33 +410,33 @@ func (appKeepers *AppKeepers) InitNormalKeepers( // The last arguments can contain custom message handlers, and custom query handlers, // if we want to allow any custom callbacks - supportedFeatures := "iterator,staking,stargate,side,cosmwasm_1_1,cosmwasm_1_2,cosmwasm_1_4" + // supportedFeatures := "iterator,staking,stargate,side,cosmwasm_1_1,cosmwasm_1_2,cosmwasm_1_4" - wasmOpts = append(swasm.RegisterCustomPlugins(&appKeepers.BankKeeper, &appKeepers.GmmKeeper), wasmOpts...) + // wasmOpts = append(swasm.RegisterCustomPlugins(&appKeepers.BankKeeper, &appKeepers.GmmKeeper), wasmOpts...) // Create Wasmd Keepers // this line is used by starport scaffolding # stargate/app/scopedKeeper // availableCapabilities := strings.Join(AllCapabilities(), ",") - appKeepers.WasmKeeper = wasmkeeper.NewKeeper( - appCodec, - appKeepers.keys[wasmtypes.StoreKey], - appKeepers.AccountKeeper, - appKeepers.BankKeeper, - appKeepers.StakingKeeper, - distrkeeper.NewQuerier(appKeepers.DistrKeeper), - appKeepers.IBCFeeKeeper, - appKeepers.IBCKeeper.ChannelKeeper, - &appKeepers.IBCKeeper.PortKeeper, - appKeepers.scopedWasmKeeper, - appKeepers.TransferKeeper, - bApp.MsgServiceRouter(), - bApp.GRPCQueryRouter(), - wasmDir, - wasmConfig, - supportedFeatures, - authtypes.NewModuleAddress(govtypes.ModuleName).String(), - wasmOpts..., - ) + // appKeepers.WasmKeeper = wasmkeeper.NewKeeper( + // appCodec, + // appKeepers.keys[wasmtypes.StoreKey], + // appKeepers.AccountKeeper, + // appKeepers.BankKeeper, + // appKeepers.StakingKeeper, + // distrkeeper.NewQuerier(appKeepers.DistrKeeper), + // appKeepers.IBCFeeKeeper, + // appKeepers.IBCKeeper.ChannelKeeper, + // &appKeepers.IBCKeeper.PortKeeper, + // appKeepers.scopedWasmKeeper, + // appKeepers.TransferKeeper, + // bApp.MsgServiceRouter(), + // bApp.GRPCQueryRouter(), + // wasmDir, + // wasmConfig, + // supportedFeatures, + // govAuthor, + // wasmOpts..., + // ) transferIBCModule := transfer.NewIBCModule(appKeepers.TransferKeeper) icaHostIBCModule := icahost.NewIBCModule(appKeepers.ICAHostKeeper) @@ -437,20 +445,20 @@ func (appKeepers *AppKeepers) InitNormalKeepers( // wire up x/wasm to IBC // Create static IBC router, add transfer route, then set and seal it - var ics101WasmStack ibcporttypes.IBCModule - ics101WasmStack = wasm.NewIBCHandler(appKeepers.WasmKeeper, appKeepers.IBCKeeper.ChannelKeeper, appKeepers.IBCKeeper.ChannelKeeper) - ics101WasmStack = packetforward.NewIBCMiddleware( - ics101WasmStack, - appKeepers.PacketForwardKeeper, - 0, // retries on timeout - packetforwardkeeper.DefaultForwardTransferPacketTimeoutTimestamp, // forward timeout - packetforwardkeeper.DefaultRefundTransferPacketTimeoutTimestamp, // refund timeout - ) + // var ics101WasmStack ibcporttypes.IBCModule + // ics101WasmStack = wasm.NewIBCHandler(appKeepers.WasmKeeper, appKeepers.IBCKeeper.ChannelKeeper, appKeepers.IBCKeeper.ChannelKeeper) + // ics101WasmStack = packetforward.NewIBCMiddleware( + // ics101WasmStack, + // appKeepers.PacketForwardKeeper, + // 0, // retries on timeout + // packetforwardkeeper.DefaultForwardTransferPacketTimeoutTimestamp, // forward timeout + // packetforwardkeeper.DefaultRefundTransferPacketTimeoutTimestamp, // refund timeout + // ) ibcRouter := ibcporttypes.NewRouter() ibcRouter.AddRoute(icahosttypes.SubModuleName, icaHostIBCModule). AddRoute(ibctransfertypes.ModuleName, transferIBCModule) - ibcRouter.AddRoute(wasm.ModuleName, ics101WasmStack) + //ibcRouter.AddRoute(wasm.ModuleName, ics101WasmStack) // this line is used by starport scaffolding # ibc/app/router appKeepers.IBCKeeper.SetRouter(ibcRouter) } @@ -503,7 +511,8 @@ func KVStoreKeys() []string { crisistypes.StoreKey, minttypes.StoreKey, distrtypes.StoreKey, slashingtypes.StoreKey, govtypes.StoreKey, paramstypes.StoreKey, ibcexported.StoreKey, upgradetypes.StoreKey, feegrant.StoreKey, evidencetypes.StoreKey, ibctransfertypes.StoreKey, icahosttypes.StoreKey, - capabilitytypes.StoreKey, group.StoreKey, icacontrollertypes.StoreKey, consensusparamtypes.StoreKey, wasmtypes.StoreKey, ibcfeetypes.StoreKey, + capabilitytypes.StoreKey, group.StoreKey, icacontrollertypes.StoreKey, consensusparamtypes.StoreKey, //wasmtypes.StoreKey, + ibcfeetypes.StoreKey, gmmmoduletypes.StoreKey, yieldmoduletypes.StoreKey, packetforwardtypes.StoreKey, diff --git a/app/keepers/modules.go b/app/keepers/modules.go index f253c87e..a1c9bb41 100644 --- a/app/keepers/modules.go +++ b/app/keepers/modules.go @@ -41,7 +41,7 @@ import ( // this line is used by starport scaffolding # stargate/app/moduleImport // wasmd module integrate - "github.com/CosmWasm/wasmd/x/wasm" + //"github.com/CosmWasm/wasmd/x/wasm" // upgrades "github.com/sideprotocol/packet-forward-middleware/v7/packetforward" ) @@ -72,7 +72,7 @@ var AppModuleBasics = module.NewBasicManager( vesting.AppModuleBasic{}, consensus.AppModuleBasic{}, ibcfee.AppModuleBasic{}, - wasm.AppModuleBasic{}, + //wasm.AppModuleBasic{}, gmmmodule.AppModuleBasic{}, yieldmodule.AppModuleBasic{}, packetforward.AppModuleBasic{}, diff --git a/app/modules.go b/app/modules.go index 8e911c9f..2b3911f1 100644 --- a/app/modules.go +++ b/app/modules.go @@ -129,7 +129,7 @@ func appModules( upgrade.NewAppModule(app.UpgradeKeeper), evidence.NewAppModule(app.EvidenceKeeper), consensus.NewAppModule(appCodec, app.ConsensusParamsKeeper), - wasm.NewAppModule(appCodec, &app.WasmKeeper, app.StakingKeeper, app.AccountKeeper, app.BankKeeper, app.MsgServiceRouter(), app.GetSubspace(wasmTypes.ModuleName)), + //wasm.NewAppModule(appCodec, &app.WasmKeeper, app.StakingKeeper, app.AccountKeeper, app.BankKeeper, app.MsgServiceRouter(), app.GetSubspace(wasmTypes.ModuleName)), ibc.NewAppModule(app.IBCKeeper), ibcfee.NewAppModule(app.IBCFeeKeeper), transferModule, diff --git a/app/test_setup.go b/app/test_setup.go index b5ae4f97..499b5ba0 100644 --- a/app/test_setup.go +++ b/app/test_setup.go @@ -54,9 +54,9 @@ func SetupConfig() { } // TODO: Do we want to allow addresses of lengths other than 20 and 32 bytes? - if len(bytes) != 20 && len(bytes) != 32 { - return errorsmod.Wrapf(sdkerrors.ErrUnknownAddress, "address length must be 20 or 32 bytes, got %d", len(bytes)) - } + // if len(bytes) != 20 && len(bytes) != 32 { + // return errorsmod.Wrapf(sdkerrors.ErrUnknownAddress, "address length must be 20 or 32 bytes, got %d", len(bytes)) + // } return nil }) diff --git a/bitcoin/codec/amino.go b/bitcoin/codec/amino.go index 1f49f4c0..07d9771e 100644 --- a/bitcoin/codec/amino.go +++ b/bitcoin/codec/amino.go @@ -8,11 +8,8 @@ import ( // RegisterCrypto registers all crypto dependency types with the provided Amino // codec. func RegisterCrypto(cdc *codec.LegacyAmino) { - cdc.RegisterInterface((*segwit.PubKey)(nil), nil) cdc.RegisterConcrete(segwit.PubKey{}, segwit.PubKeyName, nil) - - cdc.RegisterInterface((*segwit.PrivKey)(nil), nil) cdc.RegisterConcrete(segwit.PrivKey{}, segwit.PrivKeyName, nil) } diff --git a/bitcoin/codec/proto.go b/bitcoin/codec/proto.go index db903c8c..71bae2e9 100644 --- a/bitcoin/codec/proto.go +++ b/bitcoin/codec/proto.go @@ -2,8 +2,6 @@ package codec import ( codectypes "github.com/cosmos/cosmos-sdk/codec/types" - "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" - "github.com/cosmos/cosmos-sdk/crypto/keys/multisig" "github.com/cosmos/cosmos-sdk/crypto/keys/secp256r1" cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" "github.com/sideprotocol/side/bitcoin/keys/segwit" @@ -12,14 +10,9 @@ import ( // RegisterInterfaces registers the sdk.Tx interface. func RegisterInterfaces(registry codectypes.InterfaceRegistry) { var pk *cryptotypes.PubKey - registry.RegisterInterface("cosmos.crypto.PubKey", pk) - registry.RegisterImplementations(pk, &ed25519.PubKey{}) registry.RegisterImplementations(pk, &segwit.PubKey{}) - registry.RegisterImplementations(pk, &multisig.LegacyAminoPubKey{}) var priv *cryptotypes.PrivKey - registry.RegisterInterface("cosmos.crypto.PrivKey", priv) registry.RegisterImplementations(priv, &segwit.PrivKey{}) - registry.RegisterImplementations(priv, &ed25519.PrivKey{}) secp256r1.RegisterInterfaces(registry) } diff --git a/bitcoin/keys/segwit/segwit_cgo.go b/bitcoin/keys/segwit/segwit_cgo.go index 527e201f..ee442762 100644 --- a/bitcoin/keys/segwit/segwit_cgo.go +++ b/bitcoin/keys/segwit/segwit_cgo.go @@ -1,20 +1,22 @@ package segwit +import ( + "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" +) + // Sign creates an ECDSA signature on curve Secp256k1, using SHA256 on the msg. func (privKey *PrivKey) Sign(msg []byte) ([]byte, error) { - // rsv, err := secp256k1.Sign(crypto.Sha256(msg), privKey.Key) - // if err != nil { - // return nil, err - // } - // // we do not need v in r||s||v: - // rs := rsv[:len(rsv)-1] - // return rs, nil - return nil, nil + derivedKey := secp256k1.PrivKey{ + Key: privKey.Key, + } + return derivedKey.Sign(msg) } // VerifySignature validates the signature. // The msg will be hashed prior to signature verification. -func (pubKey *PubKey) VerifySignature(msg, sigStr []byte) bool { - // return secp256k1.VerifySignature(pubKey.Bytes(), crypto.Sha256(msg), sigStr) - return false +func (pubKey *PubKey) VerifySignature(msg []byte, sigStr []byte) bool { + derivedPubKey := secp256k1.PubKey{ + Key: pubKey.Key, + } + return derivedPubKey.VerifySignature(msg, sigStr) } diff --git a/bitcoin/keys/segwit/segwit_test.go b/bitcoin/keys/segwit/segwit_test.go index 13cd047f..9c246603 100644 --- a/bitcoin/keys/segwit/segwit_test.go +++ b/bitcoin/keys/segwit/segwit_test.go @@ -2,14 +2,19 @@ package segwit_test import ( //"fmt" + "strings" "testing" "github.com/cosmos/cosmos-sdk/crypto/hd" "github.com/sideprotocol/side/bitcoin/keys/segwit" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/cosmos/btcutil/bech32" + + sdk "github.com/cosmos/cosmos-sdk/types" + sdkbech32 "github.com/cosmos/cosmos-sdk/types/bech32" "github.com/cosmos/go-bip39" ) @@ -20,8 +25,8 @@ func TestSegwit(t *testing.T) { masterKey, chParams := hd.ComputeMastersFromSeed(seed) derivedPrivKey, err := hd.DerivePrivateKeyForPath(masterKey, chParams, "m/84'/0'/0'/0/0") assert.NoError(t, err, "Private key derivation should not fail") - privKey := segwit.PrivKey{Key: derivedPrivKey} + pubKey := privKey.PubKey() assert.NotNil(t, pubKey, "Public key should not be nil") @@ -42,11 +47,18 @@ func TestSegwit(t *testing.T) { t.Log(hrp) hrp, bz, err := bech32.Decode(bech32Address, 1000) + //hrp, bz, err := bech32.Decode("bc1qc2zm9xeje96yh6st7wmy60mmsteemsm3tfr2tn", 1000) assert.NoError(t, err) println(hrp, bz) + sdk.GetConfig().SetBech32PrefixForAccount("bc", "bc") + sdk.GetConfig().Seal() + acc, err := sdk.AccAddressFromBech32(bech32Address) + require.NoError(t, err) + t.Logf("Generated SegWit Address: %s", acc) + addr := []byte{123, 95, 226, 43, 84, 70, 247, 198, 46, 162, 123, 139, 215, 28, 239, 148, 224, 63, 61, 242} + _, err = sdkbech32.ConvertAndEncode("bc", addr) - // acc, err := sdk.AccAddressFromBech32(bech32Address) - // require.NoError(t, err) - // t.Logf("Generated SegWit Address: %s", acc) + //t.Logf("parsed address", dd) + require.NoError(t, err) } diff --git a/cmd/sided/cmd/config.go b/cmd/sided/cmd/config.go index 855e220d..aba94a3a 100644 --- a/cmd/sided/cmd/config.go +++ b/cmd/sided/cmd/config.go @@ -19,5 +19,6 @@ func initSDKConfig() { config.SetBech32PrefixForAccount(app.AccountAddressPrefix, accountPubKeyPrefix) config.SetBech32PrefixForValidator(validatorAddressPrefix, validatorPubKeyPrefix) config.SetBech32PrefixForConsensusNode(consNodeAddressPrefix, consNodePubKeyPrefix) + config.Seal() } diff --git a/cmd/sided/cmd/genaccounts.go b/cmd/sided/cmd/genaccounts.go index 51cb8fe4..58dd6aa5 100644 --- a/cmd/sided/cmd/genaccounts.go +++ b/cmd/sided/cmd/genaccounts.go @@ -40,7 +40,6 @@ contain valid denominations. Accounts may optionally be supplied with vesting pa RunE: func(cmd *cobra.Command, args []string) error { clientCtx := client.GetClientContextFromCmd(cmd) cdc := clientCtx.Codec - serverCtx := server.GetServerContextFromCmd(cmd) config := serverCtx.Config @@ -53,9 +52,6 @@ contain valid denominations. Accounts may optionally be supplied with vesting pa addr, err := sdk.AccAddressFromBech32(args[0]) - // fmt.Println("args--->", args[0]) - // fmt.Println("address--->", addr) - // fmt.Println("err--->", err) if err != nil { inBuf := bufio.NewReader(cmd.InOrStdin()) keyringBackend, err := cmd.Flags().GetString(flags.FlagKeyringBackend) @@ -64,18 +60,20 @@ contain valid denominations. Accounts may optionally be supplied with vesting pa } // attempt to lookup address from Keybase if no address was provided + //registry := codectypes.NewInterfaceRegistry() + //codec.NewProtoCodec(registry) kb, err := keyring.New(sdk.KeyringServiceName(), keyringBackend, clientCtx.HomeDir, inBuf, cdc, sidekr.Option()) if err != nil { return err } info, err := kb.Key(args[0]) - fmt.Println("address--->", err) if err != nil { return fmt.Errorf("failed to get address from Keybase: %w", err) } addr, err = info.GetAddress() + fmt.Println("addrress:", addr) if err != nil { return fmt.Errorf("failed to get address from Keybase: %w", err) } @@ -196,3 +194,10 @@ contain valid denominations. Accounts may optionally be supplied with vesting pa return cmd } + +// func getBitcoinCodec() codec.Codec { +// registry := codectypes.NewInterfaceRegistry() +// bitcoincdc.RegisterInterfaces(registry) +// cryptocodec.RegisterInterfaces(registry) +// return codec.NewProtoCodec(registry) +// } diff --git a/go.mod b/go.mod index ccaed061..fe28b1c5 100644 --- a/go.mod +++ b/go.mod @@ -58,7 +58,7 @@ require ( github.com/containerd/typeurl v1.0.2 // indirect github.com/cosmos/gogogateway v1.2.0 // indirect github.com/cosmos/iavl v0.20.1 // indirect - github.com/cosmos/interchain-security/v3 v3.2.0 // indirect + github.com/cosmos/interchain-security/v3 v3.1.0 // indirect github.com/cosmos/ledger-cosmos-go v0.12.4 // indirect github.com/cosmos/rosetta-sdk-go v0.10.0 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect @@ -154,6 +154,7 @@ require ( github.com/pkg/errors v0.9.1 // indirect github.com/pkg/profile v1.6.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/prometheus/client_golang v1.15.0 // indirect github.com/prometheus/client_model v0.3.0 // indirect github.com/prometheus/common v0.42.0 // indirect github.com/prometheus/procfs v0.9.0 // indirect @@ -185,6 +186,7 @@ require ( go.uber.org/atomic v1.10.0 // indirect go.uber.org/multierr v1.8.0 // indirect go.uber.org/zap v1.23.0 // indirect + golang.org/x/crypto v0.16.0 // indirect golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 // indirect golang.org/x/mod v0.12.0 // indirect golang.org/x/net v0.19.0 // indirect @@ -201,12 +203,12 @@ require ( gopkg.in/yaml.v3 v3.0.1 // indirect nhooyr.io/websocket v1.8.6 // indirect pgregory.net/rapid v1.1.0 // indirect - sigs.k8s.io/yaml v1.3.0 // indirect + sigs.k8s.io/yaml v1.4.0 // indirect ) require ( cosmossdk.io/errors v1.0.1 - cosmossdk.io/math v1.2.0 + cosmossdk.io/math v1.3.0 github.com/CosmWasm/wasmd v0.40.1 github.com/CosmWasm/wasmvm v1.2.4 github.com/Stride-Labs/stride/v16 v16.0.0 @@ -218,10 +220,8 @@ require ( github.com/cosmos/go-bip39 v1.0.0 github.com/cosmos/ics23/go v0.10.0 github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 - github.com/prometheus/client_golang v1.15.0 github.com/sideprotocol/packet-forward-middleware/v7 v7.0.0-20240301130023-4ffe7ac5619b gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b - golang.org/x/crypto v0.16.0 golang.org/x/tools v0.12.0 google.golang.org/genproto/googleapis/api v0.0.0-20231212172506-995d672761c0 google.golang.org/grpc v1.60.1 @@ -231,6 +231,8 @@ require ( ) replace ( + //github.com/cosmos/cosmos-sdk => github.com/sideprotocol/cosmos-sdk v0.0.0-20240308174555-c53307ac27f7 + github.com/cosmos/cosmos-sdk => ../cosmos-sdk github.com/cosmos/interchain-security/v3 => github.com/Stride-Labs/interchain-security/v3 v3.1.0-remove-validation-bug-7d3d9d github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1 github.com/syndtr/goleveldb => github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 diff --git a/go.sum b/go.sum index afa1d2aa..26be152e 100644 --- a/go.sum +++ b/go.sum @@ -197,8 +197,8 @@ cosmossdk.io/errors v1.0.1 h1:bzu+Kcr0kS/1DuPBtUFdWjzLqyUuCiyHjyJB6srBV/0= cosmossdk.io/errors v1.0.1/go.mod h1:MeelVSZThMi4bEakzhhhE/CKqVv3nOJDA25bIqRDu/U= cosmossdk.io/log v1.3.1 h1:UZx8nWIkfbbNEWusZqzAx3ZGvu54TZacWib3EzUYmGI= cosmossdk.io/log v1.3.1/go.mod h1:2/dIomt8mKdk6vl3OWJcPk2be3pGOS8OQaLUM/3/tCM= -cosmossdk.io/math v1.2.0 h1:8gudhTkkD3NxOP2YyyJIYYmt6dQ55ZfJkDOaxXpy7Ig= -cosmossdk.io/math v1.2.0/go.mod h1:l2Gnda87F0su8a/7FEKJfFdJrM0JZRXQaohlgJeyQh0= +cosmossdk.io/math v1.3.0 h1:RC+jryuKeytIiictDslBP9i1fhkVm6ZDmZEoNP316zE= +cosmossdk.io/math v1.3.0/go.mod h1:vnRTxewy+M7BtXBNFybkuhSH4WfedVAAnERHgVFhp3k= cosmossdk.io/tools/rosetta v0.2.1 h1:ddOMatOH+pbxWbrGJKRAawdBkPYLfKXutK9IETnjYxw= cosmossdk.io/tools/rosetta v0.2.1/go.mod h1:Pqdc1FdvkNV3LcNIkYWt2RQY6IP1ge6YWZk8MhhO9Hw= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= @@ -357,8 +357,6 @@ github.com/cosmos/btcutil v1.0.5 h1:t+ZFcX77LpKtDBhjucvnOH8C2l2ioGsBNEQ3jef8xFk= github.com/cosmos/btcutil v1.0.5/go.mod h1:IyB7iuqZMJlthe2tkIFL33xPyzbFYP0XVdS8P5lUPis= github.com/cosmos/cosmos-proto v1.0.0-beta.4 h1:aEL7tU/rLOmxZQ9z4i7mzxcLbSCY48OdY7lIWTLG7oU= github.com/cosmos/cosmos-proto v1.0.0-beta.4/go.mod h1:oeB+FyVzG3XrQJbJng0EnV8Vljfk9XvTIpGILNU/9Co= -github.com/cosmos/cosmos-sdk v0.47.9 h1:D51VLkF59D53PMLsbNtp6JyWR+6MbetFyomrH88+y08= -github.com/cosmos/cosmos-sdk v0.47.9/go.mod h1:cmAawe8FV/52oPKbgeHLt4UpNkrNu8R5KD+kw0kxJFc= github.com/cosmos/go-bip39 v0.0.0-20180819234021-555e2067c45d/go.mod h1:tSxLoYXyBmiFeKpvmq4dzayMdCjCnu8uqmCysIGBT2Y= github.com/cosmos/go-bip39 v1.0.0 h1:pcomnQdrdH22njcAatO0yWojsUnCO3y2tNoV1cb6hHY= github.com/cosmos/go-bip39 v1.0.0/go.mod h1:RNJv0H/pOIVgxw6KS7QeX2a0Uo0aKUlfhZ4xuwvCdJw= @@ -1742,6 +1740,6 @@ rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8 rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= -sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= -sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= +sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= +sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= diff --git a/local_node.sh b/local_node.sh index 2c70a752..e81218cb 100755 --- a/local_node.sh +++ b/local_node.sh @@ -1,7 +1,7 @@ #!/bin/bash -KEYS=("dev0" "dev1" "dev2") -CHAINID="side-testnet-1" +KEYS=("dev0") +CHAINID="side-devnet-1" MONIKER="freebird" BINARY="sided" DENOMS=("uside" "uusdc") @@ -59,6 +59,10 @@ if [[ $overwrite == "y" || $overwrite == "Y" ]]; then for KEY in "${KEYS[@]}"; do $BINARY keys add "$KEY" --keyring-backend $KEYRING --algo $KEYALGO --home "$HOMEDIR" done + # for KEY in "${KEYS[@]}"; do + # # Add the --recover flag to initiate recovery mode + # $BINARY keys add "$KEY" --keyring-backend $KEYRING --algo $KEYALGO --recover --home "$HOMEDIR" + # done # Set moniker and chain-id for Cascadia (Moniker can be anything, chain-id must be an integer) $BINARY init $MONIKER -o --chain-id $CHAINID --home "$HOMEDIR" diff --git a/local_node_test.sh b/local_node_test.sh new file mode 100644 index 00000000..2c70a752 --- /dev/null +++ b/local_node_test.sh @@ -0,0 +1,121 @@ +#!/bin/bash + +KEYS=("dev0" "dev1" "dev2") +CHAINID="side-testnet-1" +MONIKER="freebird" +BINARY="sided" +DENOMS=("uside" "uusdc") +INITIAL_SUPPLY="100000000000000000000" +BLOCK_GAS=10000000 +MAX_GAS=10000000000 + +# Remember to change to other types of keyring like 'file' in-case exposing to outside world, +# otherwise your balance will be wiped quickly +# The keyring test does not require private key to steal tokens from you +KEYRING="test" +KEYALGO="segwit" +LOGLEVEL="info" +# Set dedicated home directory for the $BINARY instance +HOMEDIR="$HOME/.side" + +# Path variables +CONFIG=$HOMEDIR/config/config.toml +APP_TOML=$HOMEDIR/config/app.toml +GENESIS=$HOMEDIR/config/genesis.json +TMP_GENESIS=$HOMEDIR/config/tmp_genesis.json + +# validate dependencies are installed +command -v jq >/dev/null 2>&1 || { + echo >&2 "jq not installed. More info: https://stedolan.github.io/jq/download/" + exit 1 +} + +# used to exit on first error (any non-zero exit code) +set -e + +# Reinstall daemon +make install + +# User prompt if an existing local node configuration is found. +if [ -d "$HOMEDIR" ]; then + printf "\nAn existing folder at '%s' was found. You can choose to delete this folder and start a new local node with new keys from genesis. When declined, the existing local node is started. \n" "$HOMEDIR" + echo "Overwrite the existing configuration and start a new local node? [y/n]" + read -r overwrite +else + overwrite="Y" +fi + + +# Setup local node if overwrite is set to Yes, otherwise skip setup +if [[ $overwrite == "y" || $overwrite == "Y" ]]; then + # Remove the previous folder + rm -rf "$HOMEDIR" + + # Set client config + $BINARY config keyring-backend $KEYRING --home "$HOMEDIR" + $BINARY config chain-id $CHAINID --home "$HOMEDIR" + + # If keys exist they should be deleted + for KEY in "${KEYS[@]}"; do + $BINARY keys add "$KEY" --keyring-backend $KEYRING --algo $KEYALGO --home "$HOMEDIR" + done + + # Set moniker and chain-id for Cascadia (Moniker can be anything, chain-id must be an integer) + $BINARY init $MONIKER -o --chain-id $CHAINID --home "$HOMEDIR" + + jq --arg denom "${DENOMS[0]}" '.app_state["staking"]["params"]["bond_denom"]=$denom' "$GENESIS" >"$TMP_GENESIS" && mv "$TMP_GENESIS" "$GENESIS" + jq --arg denom "${DENOMS[0]}" '.app_state["crisis"]["constant_fee"]["denom"]=$denom' "$GENESIS" >"$TMP_GENESIS" && mv "$TMP_GENESIS" "$GENESIS" + jq --arg denom "${DENOMS[0]}" '.app_state["gov"]["deposit_params"]["min_deposit"][0]["denom"]=$denom' "$GENESIS" >"$TMP_GENESIS" && mv "$TMP_GENESIS" "$GENESIS" + jq --arg gas "$BLOCK_GAS" '.app_state["feemarket"]["block_gas"]=$gas' "$GENESIS" >"$TMP_GENESIS" && mv "$TMP_GENESIS" "$GENESIS" + # Set gas limit in genesis + jq --arg max_gas "$MAX_GAS" '.consensus_params["block"]["max_gas"]=$max_gas' "$GENESIS" >"$TMP_GENESIS" && mv "$TMP_GENESIS" "$GENESIS" + # set custom pruning settings + sed -i.bak 's/pruning = "default"/pruning = "custom"/g' "$APP_TOML" + sed -i.bak 's/pruning-keep-recent = "0"/pruning-keep-recent = "2"/g' "$APP_TOML" + sed -i.bak 's/pruning-interval = "0"/pruning-interval = "10"/g' "$APP_TOML" + + sed -i.bak 's/127.0.0.1:26657/0.0.0.0:26657/g' "$CONFIG" + sed -i.bak 's/cors_allowed_origins\s*=\s*\[\]/cors_allowed_origins = ["*",]/g' "$CONFIG" + sed -i.bak 's/swagger = false/swagger = true/g' $APP_TOML + + # Allocate genesis accounts (cosmos formatted addresses) + for KEY in "${KEYS[@]}"; do + BALANCES="" + for DENOM in "${DENOMS[@]}"; do + BALANCES+=",${INITIAL_SUPPLY}$DENOM" + done + $BINARY add-genesis-account "$KEY" ${BALANCES:1} --keyring-backend $KEYRING --home "$HOMEDIR" + done + + # Adjust total supply + for DENOM in "${DENOMS[@]}"; do + total_supply=$(echo "${#KEYS[@]} * $INITIAL_SUPPLY" | bc) + if ! jq -e --arg denom "$DENOM" '.app_state["bank"]["supply"] | any(.denom == $denom)' "$GENESIS" >/dev/null; then + jq -r --arg total_supply "$total_supply" --arg denom "$DENOM" '.app_state["bank"]["supply"] += [{"denom": $denom, "amount": $total_supply}]' "$GENESIS" >"$TMP_GENESIS" && mv "$TMP_GENESIS" "$GENESIS" + fi + done + + # Sign genesis transaction + $BINARY gentx "${KEYS[0]}" ${INITIAL_SUPPLY}${DENOMS[0]} --keyring-backend $KEYRING --chain-id $CHAINID --home "$HOMEDIR" + + ## In case you want to create multiple validators at genesis + ## 1. Back to `$BINARY keys add` step, init more keys + ## 2. Back to `$BINARY add-genesis-account` step, add balance for those + ## 3. Clone this ~/.$BINARY home directory into some others, let's say `~/.clonedCascadiad` + ## 4. Run `gentx` in each of those folders + ## 5. Copy the `gentx-*` folders under `~/.clonedCascadiad/config/gentx/` folders into the original `~/.$BINARY/config/gentx` + + # Collect genesis tx + $BINARY collect-gentxs --home "$HOMEDIR" + + # Run this to ensure everything worked and that the genesis file is setup correctly + $BINARY validate-genesis --home "$HOMEDIR" + + if [[ $1 == "pending" ]]; then + echo "pending mode is on, please wait for the first block committed." + fi +fi + + +# Start the node (remove the --pruning=nothing flag if historical queries are not needed) +$BINARY start --log_level info --minimum-gas-prices=0.0001${DENOMS[0]} \ No newline at end of file