From f726a2398a26bdaf71d78dbf56a82621e84fd098 Mon Sep 17 00:00:00 2001 From: atheeshp <59333759+atheeshp@users.noreply.github.com> Date: Fri, 1 Oct 2021 20:00:22 +0530 Subject: [PATCH] refactor: migrate ante hanlders to middlewares (#10028) ## Description Closes: #9585 --- ### Author Checklist *All items are required. Please add a note to the item if the item is not applicable and please add links to any relevant follow up issues.* I have... - [ ] included the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title - [ ] added `!` to the type prefix if API or client breaking change - [ ] targeted the correct branch (see [PR Targeting](https://github.com/cosmos/cosmos-sdk/blob/master/CONTRIBUTING.md#pr-targeting)) - [ ] provided a link to the relevant issue or specification - [ ] followed the guidelines for [building modules](https://github.com/cosmos/cosmos-sdk/blob/master/docs/building-modules) - [ ] included the necessary unit and integration [tests](https://github.com/cosmos/cosmos-sdk/blob/master/CONTRIBUTING.md#testing) - [ ] added a changelog entry to `CHANGELOG.md` - [ ] included comments for [documenting Go code](https://blog.golang.org/godoc) - [ ] updated the relevant documentation or specification - [ ] reviewed "Files changed" and left comments if necessary - [ ] confirmed all CI checks have passed ### Reviewers Checklist *All items are required. Please add a note if the item is not applicable and please add your handle next to the items reviewed if you only reviewed selected items.* I have... - [ ] confirmed the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title - [ ] confirmed `!` in the type prefix if API or client breaking change - [ ] confirmed all author checklist items have been addressed - [ ] reviewed state machine logic - [ ] reviewed API design and naming - [ ] reviewed documentation is accurate - [ ] reviewed tests and test coverage - [ ] manually tested (if applicable) --- baseapp/abci_test.go | 55 +- baseapp/baseapp_test.go | 335 +++++------ baseapp/custom_txhandler_test.go | 117 ++++ baseapp/queryrouter_test.go | 5 +- baseapp/util_test.go | 67 +++ contrib/rosetta/configuration/bootstrap.json | 2 +- contrib/rosetta/rosetta-ci/data.tar.gz | Bin 35581 -> 39804 bytes server/mock/app.go | 23 +- simapp/app.go | 28 +- x/auth/ante/ante.go | 57 -- x/auth/ante/basic.go | 207 ------- x/auth/ante/basic_test.go | 224 -------- x/auth/ante/ext.go | 36 -- x/auth/ante/ext_test.go | 36 -- x/auth/ante/fee.go | 140 ----- x/auth/ante/fee_test.go | 104 ---- x/auth/ante/setup.go | 76 --- x/auth/ante/testutil_test.go | 214 ------- x/auth/middleware/basic.go | 358 ++++++++++++ x/auth/middleware/basic_test.go | 222 ++++++++ .../{ante => middleware}/expected_keepers.go | 4 +- x/auth/middleware/ext.go | 71 +++ x/auth/middleware/ext_test.go | 36 ++ x/auth/middleware/fee.go | 194 +++++++ x/auth/middleware/fee_test.go | 99 ++++ x/auth/{ante => middleware}/feegrant_test.go | 57 +- x/auth/middleware/gas_test.go | 2 +- x/auth/middleware/legacy_ante.go | 115 ---- x/auth/middleware/middleware.go | 44 +- .../middleware_test.go} | 373 ++++++------- x/auth/middleware/msg_service_router_test.go | 69 --- x/auth/middleware/run_msgs.go | 1 + x/auth/middleware/run_msgs_test.go | 36 ++ x/auth/{ante => middleware}/sigverify.go | 523 ++++++++++++------ .../sigverify_benchmark_test.go | 4 +- x/auth/{ante => middleware}/sigverify_test.go | 292 +++++----- x/auth/middleware/testutil_test.go | 78 ++- x/auth/signing/verify_test.go | 5 +- x/auth/tx/builder.go | 10 +- x/feegrant/keeper/keeper.go | 4 +- 40 files changed, 2284 insertions(+), 2039 deletions(-) create mode 100644 baseapp/custom_txhandler_test.go create mode 100644 baseapp/util_test.go delete mode 100644 x/auth/ante/ante.go delete mode 100644 x/auth/ante/basic.go delete mode 100644 x/auth/ante/basic_test.go delete mode 100644 x/auth/ante/ext.go delete mode 100644 x/auth/ante/ext_test.go delete mode 100644 x/auth/ante/fee.go delete mode 100644 x/auth/ante/fee_test.go delete mode 100644 x/auth/ante/setup.go delete mode 100644 x/auth/ante/testutil_test.go create mode 100644 x/auth/middleware/basic.go create mode 100644 x/auth/middleware/basic_test.go rename x/auth/{ante => middleware}/expected_keepers.go (85%) create mode 100644 x/auth/middleware/ext.go create mode 100644 x/auth/middleware/ext_test.go create mode 100644 x/auth/middleware/fee.go create mode 100644 x/auth/middleware/fee_test.go rename x/auth/{ante => middleware}/feegrant_test.go (82%) delete mode 100644 x/auth/middleware/legacy_ante.go rename x/auth/{ante/ante_test.go => middleware/middleware_test.go} (67%) create mode 100644 x/auth/middleware/run_msgs_test.go rename x/auth/{ante => middleware}/sigverify.go (52%) rename x/auth/{ante => middleware}/sigverify_benchmark_test.go (89%) rename x/auth/{ante => middleware}/sigverify_test.go (53%) diff --git a/baseapp/abci_test.go b/baseapp/abci_test.go index 8a61a0aebfc2..6f954f5aaa69 100644 --- a/baseapp/abci_test.go +++ b/baseapp/abci_test.go @@ -1,4 +1,4 @@ -package baseapp +package baseapp_test import ( "fmt" @@ -9,6 +9,7 @@ import ( tmprototypes "github.com/tendermint/tendermint/proto/tendermint/types" dbm "github.com/tendermint/tm-db" + "github.com/cosmos/cosmos-sdk/baseapp" sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -18,81 +19,81 @@ func TestGetBlockRentionHeight(t *testing.T) { name := t.Name() testCases := map[string]struct { - bapp *BaseApp + bapp *baseapp.BaseApp maxAgeBlocks int64 commitHeight int64 expected int64 }{ "defaults": { - bapp: NewBaseApp(name, logger, db, nil), + bapp: baseapp.NewBaseApp(name, logger, db, nil), maxAgeBlocks: 0, commitHeight: 499000, expected: 0, }, "pruning unbonding time only": { - bapp: NewBaseApp(name, logger, db, nil, SetMinRetainBlocks(1)), + bapp: baseapp.NewBaseApp(name, logger, db, nil, baseapp.SetMinRetainBlocks(1)), maxAgeBlocks: 362880, commitHeight: 499000, expected: 136120, }, "pruning iavl snapshot only": { - bapp: NewBaseApp( + bapp: baseapp.NewBaseApp( name, logger, db, nil, - SetPruning(sdk.PruningOptions{KeepEvery: 10000}), - SetMinRetainBlocks(1), + baseapp.SetPruning(sdk.PruningOptions{KeepEvery: 10000}), + baseapp.SetMinRetainBlocks(1), ), maxAgeBlocks: 0, commitHeight: 499000, expected: 490000, }, "pruning state sync snapshot only": { - bapp: NewBaseApp( + bapp: baseapp.NewBaseApp( name, logger, db, nil, - SetSnapshotInterval(50000), - SetSnapshotKeepRecent(3), - SetMinRetainBlocks(1), + baseapp.SetSnapshotInterval(50000), + baseapp.SetSnapshotKeepRecent(3), + baseapp.SetMinRetainBlocks(1), ), maxAgeBlocks: 0, commitHeight: 499000, expected: 349000, }, "pruning min retention only": { - bapp: NewBaseApp( + bapp: baseapp.NewBaseApp( name, logger, db, nil, - SetMinRetainBlocks(400000), + baseapp.SetMinRetainBlocks(400000), ), maxAgeBlocks: 0, commitHeight: 499000, expected: 99000, }, "pruning all conditions": { - bapp: NewBaseApp( + bapp: baseapp.NewBaseApp( name, logger, db, nil, - SetPruning(sdk.PruningOptions{KeepEvery: 10000}), - SetMinRetainBlocks(400000), - SetSnapshotInterval(50000), SetSnapshotKeepRecent(3), + baseapp.SetPruning(sdk.PruningOptions{KeepEvery: 10000}), + baseapp.SetMinRetainBlocks(400000), + baseapp.SetSnapshotInterval(50000), baseapp.SetSnapshotKeepRecent(3), ), maxAgeBlocks: 362880, commitHeight: 499000, expected: 99000, }, "no pruning due to no persisted state": { - bapp: NewBaseApp( + bapp: baseapp.NewBaseApp( name, logger, db, nil, - SetPruning(sdk.PruningOptions{KeepEvery: 10000}), - SetMinRetainBlocks(400000), - SetSnapshotInterval(50000), SetSnapshotKeepRecent(3), + baseapp.SetPruning(sdk.PruningOptions{KeepEvery: 10000}), + baseapp.SetMinRetainBlocks(400000), + baseapp.SetSnapshotInterval(50000), baseapp.SetSnapshotKeepRecent(3), ), maxAgeBlocks: 362880, commitHeight: 10000, expected: 0, }, "disable pruning": { - bapp: NewBaseApp( + bapp: baseapp.NewBaseApp( name, logger, db, nil, - SetPruning(sdk.PruningOptions{KeepEvery: 10000}), - SetMinRetainBlocks(0), - SetSnapshotInterval(50000), SetSnapshotKeepRecent(3), + baseapp.SetPruning(sdk.PruningOptions{KeepEvery: 10000}), + baseapp.SetMinRetainBlocks(0), + baseapp.SetSnapshotInterval(50000), baseapp.SetSnapshotKeepRecent(3), ), maxAgeBlocks: 362880, commitHeight: 499000, @@ -126,14 +127,14 @@ func TestBaseAppCreateQueryContextRejectsNegativeHeights(t *testing.T) { logger := defaultLogger() db := dbm.NewMemDB() name := t.Name() - app := NewBaseApp(name, logger, db, nil) + app := baseapp.NewBaseApp(name, logger, db, nil) proves := []bool{ false, true, } for _, prove := range proves { t.Run(fmt.Sprintf("prove=%t", prove), func(t *testing.T) { - sctx, err := app.createQueryContext(-10, true) + sctx, err := app.CreateQueryContext(-10, true) require.Error(t, err) require.Equal(t, sctx, sdk.Context{}) }) diff --git a/baseapp/baseapp_test.go b/baseapp/baseapp_test.go index e8a371ccd92e..63abd71b5633 100644 --- a/baseapp/baseapp_test.go +++ b/baseapp/baseapp_test.go @@ -1,4 +1,4 @@ -package baseapp +package baseapp_test import ( "bytes" @@ -22,6 +22,7 @@ import ( tmproto "github.com/tendermint/tendermint/proto/tendermint/types" dbm "github.com/tendermint/tm-db" + "github.com/cosmos/cosmos-sdk/baseapp" "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/snapshots" snapshottypes "github.com/cosmos/cosmos-sdk/snapshots/types" @@ -30,6 +31,7 @@ import ( "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" "github.com/cosmos/cosmos-sdk/x/auth/middleware" "github.com/cosmos/cosmos-sdk/x/auth/migrations/legacytx" ) @@ -82,12 +84,12 @@ func defaultLogger() log.Logger { return log.NewTMLogger(log.NewSyncWriter(os.Stdout)).With("module", "sdk/app") } -func newBaseApp(name string, options ...func(*BaseApp)) *BaseApp { +func newBaseApp(name string, options ...func(*baseapp.BaseApp)) *baseapp.BaseApp { logger := defaultLogger() db := dbm.NewMemDB() codec := codec.NewLegacyAmino() registerTestCodec(codec) - return NewBaseApp(name, logger, db, testTxDecoder(codec), options...) + return baseapp.NewBaseApp(name, logger, db, testTxDecoder(codec), options...) } func registerTestCodec(cdc *codec.LegacyAmino) { @@ -111,7 +113,7 @@ func aminoTxEncoder() sdk.TxEncoder { } // simple one store baseapp -func setupBaseApp(t *testing.T, options ...func(*BaseApp)) *BaseApp { +func setupBaseApp(t *testing.T, options ...func(*baseapp.BaseApp)) *baseapp.BaseApp { app := newBaseApp(t.Name(), options...) require.Equal(t, t.Name(), app.Name()) @@ -124,23 +126,37 @@ func setupBaseApp(t *testing.T, options ...func(*BaseApp)) *BaseApp { return app } +// testTxHandler is a tx.Handler used for the mock app, it does not +// contain any signature verification logic. +func testTxHandler(options middleware.TxHandlerOptions, customTxHandlerMiddleware handlerFun) tx.Handler { + return middleware.ComposeMiddlewares( + middleware.NewRunMsgsTxHandler(options.MsgServiceRouter, options.LegacyRouter), + middleware.GasTxMiddleware, + middleware.RecoveryTxMiddleware, + middleware.NewIndexEventsTxMiddleware(options.IndexEvents), + middleware.ValidateBasicMiddleware, + CustomTxHandlerMiddleware(customTxHandlerMiddleware), + ) +} + // simple one store baseapp with data and snapshots. Each tx is 1 MB in size (uncompressed). -func setupBaseAppWithSnapshots(t *testing.T, blocks uint, blockTxs int, options ...func(*BaseApp)) (*BaseApp, func()) { +func setupBaseAppWithSnapshots(t *testing.T, blocks uint, blockTxs int, options ...func(*baseapp.BaseApp)) (*baseapp.BaseApp, func()) { codec := codec.NewLegacyAmino() registerTestCodec(codec) - routerOpt := func(bapp *BaseApp) { + routerOpt := func(bapp *baseapp.BaseApp) { legacyRouter := middleware.NewLegacyRouter() legacyRouter.AddRoute(sdk.NewRoute(routeMsgKeyValue, func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) { kv := msg.(*msgKeyValue) - bapp.cms.GetCommitKVStore(capKey2).Set(kv.Key, kv.Value) + bapp.CMS().GetCommitKVStore(capKey2).Set(kv.Key, kv.Value) return &sdk.Result{}, nil })) - txHandler, err := middleware.NewDefaultTxHandler(middleware.TxHandlerOptions{ - LegacyAnteHandler: func(ctx sdk.Context, tx sdk.Tx, simulate bool) (sdk.Context, error) { return ctx, nil }, - LegacyRouter: legacyRouter, - MsgServiceRouter: middleware.NewMsgServiceRouter(interfaceRegistry), - }) - require.NoError(t, err) + txHandler := testTxHandler( + middleware.TxHandlerOptions{ + LegacyRouter: legacyRouter, + MsgServiceRouter: middleware.NewMsgServiceRouter(interfaceRegistry), + }, + func(ctx sdk.Context, tx sdk.Tx, simulate bool) (sdk.Context, error) { return ctx, nil }, + ) bapp.SetTxHandler(txHandler) } @@ -155,9 +171,9 @@ func setupBaseAppWithSnapshots(t *testing.T, blocks uint, blockTxs int, options } app := setupBaseApp(t, append(options, - SetSnapshotStore(snapshotStore), - SetSnapshotInterval(snapshotInterval), - SetPruning(sdk.PruningOptions{KeepEvery: 1}), + baseapp.SetSnapshotStore(snapshotStore), + baseapp.SetSnapshotInterval(snapshotInterval), + baseapp.SetPruning(sdk.PruningOptions{KeepEvery: 1}), routerOpt)...) app.InitChain(abci.RequestInitChain{}) @@ -208,9 +224,9 @@ func TestMountStores(t *testing.T) { app := setupBaseApp(t) // check both stores - store1 := app.cms.GetCommitKVStore(capKey1) + store1 := app.CMS().GetCommitKVStore(capKey1) require.NotNil(t, store1) - store2 := app.cms.GetCommitKVStore(capKey2) + store2 := app.CMS().GetCommitKVStore(capKey2) require.NotNil(t, store2) } @@ -218,10 +234,10 @@ func TestMountStores(t *testing.T) { // Test that LoadLatestVersion actually does. func TestLoadVersion(t *testing.T) { logger := defaultLogger() - pruningOpt := SetPruning(store.PruneNothing) + pruningOpt := baseapp.SetPruning(store.PruneNothing) db := dbm.NewMemDB() name := t.Name() - app := NewBaseApp(name, logger, db, nil, pruningOpt) + app := baseapp.NewBaseApp(name, logger, db, nil, pruningOpt) // make a cap key and mount the store err := app.LoadLatestVersion() // needed to make stores non-nil @@ -248,7 +264,7 @@ func TestLoadVersion(t *testing.T) { commitID2 := sdk.CommitID{Version: 2, Hash: res.Data} // reload with LoadLatestVersion - app = NewBaseApp(name, logger, db, nil, pruningOpt) + app = baseapp.NewBaseApp(name, logger, db, nil, pruningOpt) app.MountStores() err = app.LoadLatestVersion() require.Nil(t, err) @@ -256,7 +272,7 @@ func TestLoadVersion(t *testing.T) { // reload with LoadVersion, see if you can commit the same block and get // the same result - app = NewBaseApp(name, logger, db, nil, pruningOpt) + app = baseapp.NewBaseApp(name, logger, db, nil, pruningOpt) err = app.LoadVersion(1) require.Nil(t, err) testLoadVersionHelper(t, app, int64(1), commitID1) @@ -265,8 +281,8 @@ func TestLoadVersion(t *testing.T) { testLoadVersionHelper(t, app, int64(2), commitID2) } -func useDefaultLoader(app *BaseApp) { - app.SetStoreLoader(DefaultStoreLoader) +func useDefaultLoader(app *baseapp.BaseApp) { + app.SetStoreLoader(baseapp.DefaultStoreLoader) } func initStore(t *testing.T, db dbm.DB, storeKey string, k, v []byte) { @@ -305,7 +321,7 @@ func checkStore(t *testing.T, db dbm.DB, ver int64, storeKey string, k, v []byte // Test that LoadLatestVersion actually does. func TestSetLoader(t *testing.T) { cases := map[string]struct { - setLoader func(*BaseApp) + setLoader func(*baseapp.BaseApp) origStoreKey string loadStoreKey string }{ @@ -331,11 +347,11 @@ func TestSetLoader(t *testing.T) { initStore(t, db, tc.origStoreKey, k, v) // load the app with the existing db - opts := []func(*BaseApp){SetPruning(store.PruneNothing)} + opts := []func(*baseapp.BaseApp){baseapp.SetPruning(store.PruneNothing)} if tc.setLoader != nil { opts = append(opts, tc.setLoader) } - app := NewBaseApp(t.Name(), defaultLogger(), db, nil, opts...) + app := baseapp.NewBaseApp(t.Name(), defaultLogger(), db, nil, opts...) app.MountStores(sdk.NewKVStoreKey(tc.loadStoreKey)) err := app.LoadLatestVersion() require.Nil(t, err) @@ -354,10 +370,10 @@ func TestSetLoader(t *testing.T) { func TestVersionSetterGetter(t *testing.T) { logger := defaultLogger() - pruningOpt := SetPruning(store.PruneDefault) + pruningOpt := baseapp.SetPruning(store.PruneDefault) db := dbm.NewMemDB() name := t.Name() - app := NewBaseApp(name, logger, db, nil, pruningOpt) + app := baseapp.NewBaseApp(name, logger, db, nil, pruningOpt) require.Equal(t, "", app.Version()) res := app.Query(abci.RequestQuery{Path: "app/version"}) @@ -374,10 +390,10 @@ func TestVersionSetterGetter(t *testing.T) { func TestLoadVersionInvalid(t *testing.T) { logger := log.NewNopLogger() - pruningOpt := SetPruning(store.PruneNothing) + pruningOpt := baseapp.SetPruning(store.PruneNothing) db := dbm.NewMemDB() name := t.Name() - app := NewBaseApp(name, logger, db, nil, pruningOpt) + app := baseapp.NewBaseApp(name, logger, db, nil, pruningOpt) err := app.LoadLatestVersion() require.Nil(t, err) @@ -392,7 +408,7 @@ func TestLoadVersionInvalid(t *testing.T) { commitID1 := sdk.CommitID{Version: 1, Hash: res.Data} // create a new app with the stores mounted under the same cap key - app = NewBaseApp(name, logger, db, nil, pruningOpt) + app = baseapp.NewBaseApp(name, logger, db, nil, pruningOpt) // require we can load the latest version err = app.LoadVersion(1) @@ -411,10 +427,10 @@ func TestLoadVersionPruning(t *testing.T) { KeepEvery: 3, Interval: 1, } - pruningOpt := SetPruning(pruningOptions) + pruningOpt := baseapp.SetPruning(pruningOptions) db := dbm.NewMemDB() name := t.Name() - app := NewBaseApp(name, logger, db, nil, pruningOpt) + app := baseapp.NewBaseApp(name, logger, db, nil, pruningOpt) // make a cap key and mount the store capKey := sdk.NewKVStoreKey("key1") @@ -442,17 +458,17 @@ func TestLoadVersionPruning(t *testing.T) { } for _, v := range []int64{1, 2, 4} { - _, err = app.cms.CacheMultiStoreWithVersion(v) + _, err = app.CMS().CacheMultiStoreWithVersion(v) require.NoError(t, err) } for _, v := range []int64{3, 5, 6, 7} { - _, err = app.cms.CacheMultiStoreWithVersion(v) + _, err = app.CMS().CacheMultiStoreWithVersion(v) require.NoError(t, err) } // reload with LoadLatestVersion, check it loads last version - app = NewBaseApp(name, logger, db, nil, pruningOpt) + app = baseapp.NewBaseApp(name, logger, db, nil, pruningOpt) app.MountStores(capKey) err = app.LoadLatestVersion() @@ -460,7 +476,7 @@ func TestLoadVersionPruning(t *testing.T) { testLoadVersionHelper(t, app, int64(7), lastCommitID) } -func testLoadVersionHelper(t *testing.T, app *BaseApp, expectedHeight int64, expectedID sdk.CommitID) { +func testLoadVersionHelper(t *testing.T, app *baseapp.BaseApp, expectedHeight int64, expectedID sdk.CommitID) { lastHeight := app.LastBlockHeight() lastID := app.LastCommitID() require.Equal(t, expectedHeight, lastHeight) @@ -470,13 +486,13 @@ func testLoadVersionHelper(t *testing.T, app *BaseApp, expectedHeight int64, exp func TestOptionFunction(t *testing.T) { logger := defaultLogger() db := dbm.NewMemDB() - bap := NewBaseApp("starting name", logger, db, nil, testChangeNameHelper("new name")) - require.Equal(t, bap.name, "new name", "BaseApp should have had name changed via option function") + bap := baseapp.NewBaseApp("starting name", logger, db, nil, testChangeNameHelper("new name")) + require.Equal(t, bap.GetName(), "new name", "BaseApp should have had name changed via option function") } -func testChangeNameHelper(name string) func(*BaseApp) { - return func(bap *BaseApp) { - bap.name = name +func testChangeNameHelper(name string) func(*baseapp.BaseApp) { + return func(bap *baseapp.BaseApp) { + bap.SetName(name) } } @@ -490,7 +506,7 @@ func TestTxDecoder(t *testing.T) { tx := newTxCounter(1, 0) txBytes := codec.MustMarshal(tx) - dTx, err := app.txDecoder(txBytes) + dTx, err := app.TxDecoder(txBytes) require.NoError(t, err) cTx := dTx.(txTest) @@ -555,8 +571,8 @@ func TestBaseAppOptionSeal(t *testing.T) { func TestSetMinGasPrices(t *testing.T) { minGasPrices := sdk.DecCoins{sdk.NewInt64DecCoin("stake", 5000)} - app := newBaseApp(t.Name(), SetMinGasPrices(minGasPrices.String())) - require.Equal(t, minGasPrices, app.minGasPrices) + app := newBaseApp(t.Name(), baseapp.SetMinGasPrices(minGasPrices.String())) + require.Equal(t, minGasPrices, app.MinGasPrices()) } func TestInitChainer(t *testing.T) { @@ -565,7 +581,7 @@ func TestInitChainer(t *testing.T) { // we can reload the same app later db := dbm.NewMemDB() logger := defaultLogger() - app := NewBaseApp(name, logger, db, nil) + app := baseapp.NewBaseApp(name, logger, db, nil) capKey := sdk.NewKVStoreKey("main") capKey2 := sdk.NewKVStoreKey("key2") app.MountStores(capKey, capKey2) @@ -608,10 +624,10 @@ func TestInitChainer(t *testing.T) { ) // assert that chainID is set correctly in InitChain - chainID := app.deliverState.ctx.ChainID() + chainID := app.DeliverState().Context().ChainID() require.Equal(t, "test-chain-id", chainID, "ChainID in deliverState not set correctly in InitChain") - chainID = app.checkState.ctx.ChainID() + chainID = app.CheckState().Context().ChainID() require.Equal(t, "test-chain-id", chainID, "ChainID in checkState not set correctly in InitChain") app.Commit() @@ -620,7 +636,7 @@ func TestInitChainer(t *testing.T) { require.Equal(t, value, res.Value) // reload app - app = NewBaseApp(name, logger, db, nil) + app = baseapp.NewBaseApp(name, logger, db, nil) app.SetInitChainer(initChainer) app.MountStores(capKey, capKey2) err = app.LoadLatestVersion() // needed to make stores non-nil @@ -644,7 +660,7 @@ func TestInitChain_WithInitialHeight(t *testing.T) { name := t.Name() db := dbm.NewMemDB() logger := defaultLogger() - app := NewBaseApp(name, logger, db, nil) + app := baseapp.NewBaseApp(name, logger, db, nil) app.InitChain( abci.RequestInitChain{ @@ -660,7 +676,7 @@ func TestBeginBlock_WithInitialHeight(t *testing.T) { name := t.Name() db := dbm.NewMemDB() logger := defaultLogger() - app := NewBaseApp(name, logger, db, nil) + app := baseapp.NewBaseApp(name, logger, db, nil) app.InitChain( abci.RequestInitChain{ @@ -711,6 +727,9 @@ func (tx txTest) ValidateBasic() error { return nil } // Implements GasTx func (tx txTest) GetGas() uint64 { return tx.GasLimit } +// Implements TxWithTimeoutHeight +func (tx txTest) GetTimeoutHeight() uint64 { return 0 } + const ( routeMsgCounter = "msgCounter" routeMsgCounter2 = "msgCounter2" @@ -826,7 +845,7 @@ func testTxDecoder(cdc *codec.LegacyAmino) sdk.TxDecoder { } } -func anteHandlerTxTest(t *testing.T, capKey sdk.StoreKey, storeKey []byte) sdk.AnteHandler { +func customHandlerTxTest(t *testing.T, capKey sdk.StoreKey, storeKey []byte) handlerFun { return func(ctx sdk.Context, tx sdk.Tx, simulate bool) (sdk.Context, error) { store := ctx.KVStore(capKey) txTest := tx.(txTest) @@ -841,7 +860,7 @@ func anteHandlerTxTest(t *testing.T, capKey sdk.StoreKey, storeKey []byte) sdk.A } ctx.EventManager().EmitEvents( - counterEvent("ante_handler", txTest.Counter), + counterEvent("post_handlers", txTest.Counter), ) return ctx, nil @@ -929,18 +948,19 @@ func TestCheckTx(t *testing.T) { // This ensures changes to the kvstore persist across successive CheckTx. counterKey := []byte("counter-key") - txHandlerOpt := func(bapp *BaseApp) { + txHandlerOpt := func(bapp *baseapp.BaseApp) { legacyRouter := middleware.NewLegacyRouter() // TODO: can remove this once CheckTx doesnt process msgs. legacyRouter.AddRoute(sdk.NewRoute(routeMsgCounter, func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) { return &sdk.Result{}, nil })) - txHandler, err := middleware.NewDefaultTxHandler(middleware.TxHandlerOptions{ - LegacyRouter: legacyRouter, - LegacyAnteHandler: anteHandlerTxTest(t, capKey1, counterKey), - MsgServiceRouter: middleware.NewMsgServiceRouter(interfaceRegistry), - }) - require.NoError(t, err) + txHandler := testTxHandler( + middleware.TxHandlerOptions{ + LegacyRouter: legacyRouter, + MsgServiceRouter: middleware.NewMsgServiceRouter(interfaceRegistry), + }, + customHandlerTxTest(t, capKey1, counterKey), + ) bapp.SetTxHandler(txHandler) } @@ -962,23 +982,23 @@ func TestCheckTx(t *testing.T) { require.True(t, r.IsOK(), fmt.Sprintf("%v", r)) } - checkStateStore := app.checkState.ctx.KVStore(capKey1) + checkStateStore := app.CheckState().Context().KVStore(capKey1) storedCounter := getIntFromStore(checkStateStore, counterKey) - // Ensure AnteHandler ran + // Ensure storedCounter require.Equal(t, nTxs, storedCounter) // If a block is committed, CheckTx state should be reset. header := tmproto.Header{Height: 1} app.BeginBlock(abci.RequestBeginBlock{Header: header, Hash: []byte("hash")}) - require.NotNil(t, app.checkState.ctx.BlockGasMeter(), "block gas meter should have been set to checkState") - require.NotEmpty(t, app.checkState.ctx.HeaderHash()) + require.NotNil(t, app.CheckState().Context().BlockGasMeter(), "block gas meter should have been set to checkState") + require.NotEmpty(t, app.CheckState().Context().HeaderHash()) app.EndBlock(abci.RequestEndBlock{}) app.Commit() - checkStateStore = app.checkState.ctx.KVStore(capKey1) + checkStateStore = app.CheckState().Context().KVStore(capKey1) storedBytes := checkStateStore.Get(counterKey) require.Nil(t, storedBytes) } @@ -986,20 +1006,21 @@ func TestCheckTx(t *testing.T) { // Test that successive DeliverTx can see each others' effects // on the store, both within and across blocks. func TestDeliverTx(t *testing.T) { - // test increments in the ante + // test increments in the post txHandler anteKey := []byte("ante-key") // test increments in the handler deliverKey := []byte("deliver-key") - txHandlerOpt := func(bapp *BaseApp) { + txHandlerOpt := func(bapp *baseapp.BaseApp) { legacyRouter := middleware.NewLegacyRouter() r := sdk.NewRoute(routeMsgCounter, handlerMsgCounter(t, capKey1, deliverKey)) legacyRouter.AddRoute(r) - txHandler, err := middleware.NewDefaultTxHandler(middleware.TxHandlerOptions{ - LegacyRouter: legacyRouter, - LegacyAnteHandler: anteHandlerTxTest(t, capKey1, anteKey), - MsgServiceRouter: middleware.NewMsgServiceRouter(interfaceRegistry), - }) - require.NoError(t, err) + txHandler := testTxHandler( + middleware.TxHandlerOptions{ + LegacyRouter: legacyRouter, + MsgServiceRouter: middleware.NewMsgServiceRouter(interfaceRegistry), + }, + customHandlerTxTest(t, capKey1, anteKey), + ) bapp.SetTxHandler(txHandler) } app := setupBaseApp(t, txHandlerOpt) @@ -1027,7 +1048,7 @@ func TestDeliverTx(t *testing.T) { require.True(t, res.IsOK(), fmt.Sprintf("%v", res)) events := res.GetEvents() require.Len(t, events, 3, "should contain ante handler, message type and counter events respectively") - require.Equal(t, sdk.MarkEventsToIndex(counterEvent("ante_handler", counter).ToABCIEvents(), map[string]struct{}{})[0], events[0], "ante handler event") + require.Equal(t, sdk.MarkEventsToIndex(counterEvent("post_handlers", counter).ToABCIEvents(), map[string]struct{}{})[0], events[0], "ante handler event") require.Equal(t, sdk.MarkEventsToIndex(counterEvent(sdk.EventTypeMessage, counter).ToABCIEvents(), map[string]struct{}{})[0], events[2], "msg handler update counter event") } @@ -1049,18 +1070,19 @@ func TestMultiMsgDeliverTx(t *testing.T) { // increment the msg counter deliverKey := []byte("deliver-key") deliverKey2 := []byte("deliver-key2") - txHandlerOpt := func(bapp *BaseApp) { + txHandlerOpt := func(bapp *baseapp.BaseApp) { legacyRouter := middleware.NewLegacyRouter() r1 := sdk.NewRoute(routeMsgCounter, handlerMsgCounter(t, capKey1, deliverKey)) r2 := sdk.NewRoute(routeMsgCounter2, handlerMsgCounter(t, capKey1, deliverKey2)) legacyRouter.AddRoute(r1) legacyRouter.AddRoute(r2) - txHandler, err := middleware.NewDefaultTxHandler(middleware.TxHandlerOptions{ - LegacyRouter: legacyRouter, - LegacyAnteHandler: anteHandlerTxTest(t, capKey1, anteKey), - MsgServiceRouter: middleware.NewMsgServiceRouter(interfaceRegistry), - }) - require.NoError(t, err) + txHandler := testTxHandler( + middleware.TxHandlerOptions{ + LegacyRouter: legacyRouter, + MsgServiceRouter: middleware.NewMsgServiceRouter(interfaceRegistry), + }, + customHandlerTxTest(t, capKey1, anteKey), + ) bapp.SetTxHandler(txHandler) } app := setupBaseApp(t, txHandlerOpt) @@ -1080,7 +1102,7 @@ func TestMultiMsgDeliverTx(t *testing.T) { res := app.DeliverTx(abci.RequestDeliverTx{Tx: txBytes}) require.True(t, res.IsOK(), fmt.Sprintf("%v", res)) - store := app.deliverState.ctx.KVStore(capKey1) + store := app.DeliverState().Context().KVStore(capKey1) // tx counter only incremented once txCounter := getIntFromStore(store, anteKey) @@ -1100,7 +1122,7 @@ func TestMultiMsgDeliverTx(t *testing.T) { res = app.DeliverTx(abci.RequestDeliverTx{Tx: txBytes}) require.True(t, res.IsOK(), fmt.Sprintf("%v", res)) - store = app.deliverState.ctx.KVStore(capKey1) + store = app.DeliverState().Context().KVStore(capKey1) // tx counter only incremented once txCounter = getIntFromStore(store, anteKey) @@ -1127,19 +1149,20 @@ func TestConcurrentCheckDeliver(t *testing.T) { func TestSimulateTx(t *testing.T) { gasConsumed := uint64(5) - txHandlerOpt := func(bapp *BaseApp) { + txHandlerOpt := func(bapp *baseapp.BaseApp) { legacyRouter := middleware.NewLegacyRouter() r := sdk.NewRoute(routeMsgCounter, func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) { ctx.GasMeter().ConsumeGas(gasConsumed, "test") return &sdk.Result{}, nil }) legacyRouter.AddRoute(r) - txHandler, err := middleware.NewDefaultTxHandler(middleware.TxHandlerOptions{ - LegacyRouter: legacyRouter, - LegacyAnteHandler: func(ctx sdk.Context, tx sdk.Tx, simulate bool) (sdk.Context, error) { return ctx, nil }, - MsgServiceRouter: middleware.NewMsgServiceRouter(interfaceRegistry), - }) - require.NoError(t, err) + txHandler := testTxHandler( + middleware.TxHandlerOptions{ + LegacyRouter: legacyRouter, + MsgServiceRouter: middleware.NewMsgServiceRouter(interfaceRegistry), + }, + func(ctx sdk.Context, tx sdk.Tx, simulate bool) (sdk.Context, error) { return ctx, nil }, + ) bapp.SetTxHandler(txHandler) } app := setupBaseApp(t, txHandlerOpt) @@ -1195,20 +1218,21 @@ func TestSimulateTx(t *testing.T) { } func TestRunInvalidTransaction(t *testing.T) { - txHandlerOpt := func(bapp *BaseApp) { + txHandlerOpt := func(bapp *baseapp.BaseApp) { legacyRouter := middleware.NewLegacyRouter() r := sdk.NewRoute(routeMsgCounter, func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) { return &sdk.Result{}, nil }) legacyRouter.AddRoute(r) - txHandler, err := middleware.NewDefaultTxHandler(middleware.TxHandlerOptions{ - LegacyRouter: legacyRouter, - LegacyAnteHandler: func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, err error) { + txHandler := testTxHandler( + middleware.TxHandlerOptions{ + LegacyRouter: legacyRouter, + MsgServiceRouter: middleware.NewMsgServiceRouter(interfaceRegistry), + }, + func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, err error) { return }, - MsgServiceRouter: middleware.NewMsgServiceRouter(interfaceRegistry), - }) - require.NoError(t, err) + ) bapp.SetTxHandler(txHandler) } app := setupBaseApp(t, txHandlerOpt) @@ -1309,7 +1333,7 @@ func TestTxGasLimits(t *testing.T) { return ctx, nil } - txHandlerOpt := func(bapp *BaseApp) { + txHandlerOpt := func(bapp *baseapp.BaseApp) { legacyRouter := middleware.NewLegacyRouter() r := sdk.NewRoute(routeMsgCounter, func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) { count := msg.(msgCounter).Counter @@ -1317,12 +1341,14 @@ func TestTxGasLimits(t *testing.T) { return &sdk.Result{}, nil }) legacyRouter.AddRoute(r) - txHandler, err := middleware.NewDefaultTxHandler(middleware.TxHandlerOptions{ - LegacyRouter: legacyRouter, - LegacyAnteHandler: ante, - MsgServiceRouter: middleware.NewMsgServiceRouter(interfaceRegistry), - }) - require.NoError(t, err) + txHandler := testTxHandler( + middleware.TxHandlerOptions{ + LegacyRouter: legacyRouter, + MsgServiceRouter: middleware.NewMsgServiceRouter(interfaceRegistry), + }, + ante, + ) + bapp.SetTxHandler(txHandler) } app := setupBaseApp(t, txHandlerOpt) @@ -1386,7 +1412,7 @@ func TestMaxBlockGasLimits(t *testing.T) { return ctx, nil } - txHandlerOpt := func(bapp *BaseApp) { + txHandlerOpt := func(bapp *baseapp.BaseApp) { legacyRouter := middleware.NewLegacyRouter() r := sdk.NewRoute(routeMsgCounter, func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) { count := msg.(msgCounter).Counter @@ -1394,12 +1420,13 @@ func TestMaxBlockGasLimits(t *testing.T) { return &sdk.Result{}, nil }) legacyRouter.AddRoute(r) - txHandler, err := middleware.NewDefaultTxHandler(middleware.TxHandlerOptions{ - LegacyRouter: legacyRouter, - LegacyAnteHandler: ante, - MsgServiceRouter: middleware.NewMsgServiceRouter(interfaceRegistry), - }) - require.NoError(t, err) + txHandler := testTxHandler( + middleware.TxHandlerOptions{ + LegacyRouter: legacyRouter, + MsgServiceRouter: middleware.NewMsgServiceRouter(interfaceRegistry), + }, + ante, + ) bapp.SetTxHandler(txHandler) } app := setupBaseApp(t, txHandlerOpt) @@ -1443,7 +1470,7 @@ func TestMaxBlockGasLimits(t *testing.T) { for j := 0; j < tc.numDelivers; j++ { _, result, err := app.SimDeliver(aminoTxEncoder(), tx) - ctx := app.getState(runTxModeDeliver).ctx + ctx := app.DeliverState().Context() // check for failed transactions if tc.fail && (j+1) > tc.failAfterDeliver { @@ -1470,21 +1497,22 @@ func TestMaxBlockGasLimits(t *testing.T) { } } -func TestBaseAppAnteHandler(t *testing.T) { +func TestBaseAppMiddleware(t *testing.T) { anteKey := []byte("ante-key") deliverKey := []byte("deliver-key") cdc := codec.NewLegacyAmino() - txHandlerOpt := func(bapp *BaseApp) { + txHandlerOpt := func(bapp *baseapp.BaseApp) { legacyRouter := middleware.NewLegacyRouter() r := sdk.NewRoute(routeMsgCounter, handlerMsgCounter(t, capKey1, deliverKey)) legacyRouter.AddRoute(r) - txHandler, err := middleware.NewDefaultTxHandler(middleware.TxHandlerOptions{ - LegacyRouter: legacyRouter, - LegacyAnteHandler: anteHandlerTxTest(t, capKey1, anteKey), - MsgServiceRouter: middleware.NewMsgServiceRouter(interfaceRegistry), - }) - require.NoError(t, err) + txHandler := testTxHandler( + middleware.TxHandlerOptions{ + LegacyRouter: legacyRouter, + MsgServiceRouter: middleware.NewMsgServiceRouter(interfaceRegistry), + }, + customHandlerTxTest(t, capKey1, anteKey), + ) bapp.SetTxHandler(txHandler) } app := setupBaseApp(t, txHandlerOpt) @@ -1498,7 +1526,7 @@ func TestBaseAppAnteHandler(t *testing.T) { // execute a tx that will fail ante handler execution // // NOTE: State should not be mutated here. This will be implicitly checked by - // the next txs ante handler execution (anteHandlerTxTest). + // the next txs ante handler execution (customHandlerTxTest). tx := newTxCounter(0, 0) tx.setFailOnAnte(true) txBytes, err := cdc.Marshal(tx) @@ -1507,7 +1535,7 @@ func TestBaseAppAnteHandler(t *testing.T) { require.Empty(t, res.Events) require.False(t, res.IsOK(), fmt.Sprintf("%v", res)) - ctx := app.getState(runTxModeDeliver).ctx + ctx := app.DeliverState().Context() store := ctx.KVStore(capKey1) require.Equal(t, int64(0), getIntFromStore(store, anteKey)) @@ -1523,7 +1551,7 @@ func TestBaseAppAnteHandler(t *testing.T) { require.Empty(t, res.Events) require.False(t, res.IsOK(), fmt.Sprintf("%v", res)) - ctx = app.getState(runTxModeDeliver).ctx + ctx = app.DeliverState().Context() store = ctx.KVStore(capKey1) require.Equal(t, int64(1), getIntFromStore(store, anteKey)) require.Equal(t, int64(0), getIntFromStore(store, deliverKey)) @@ -1539,7 +1567,7 @@ func TestBaseAppAnteHandler(t *testing.T) { require.NotEmpty(t, res.Events) require.True(t, res.IsOK(), fmt.Sprintf("%v", res)) - ctx = app.getState(runTxModeDeliver).ctx + ctx = app.DeliverState().Context() store = ctx.KVStore(capKey1) require.Equal(t, int64(2), getIntFromStore(store, anteKey)) require.Equal(t, int64(1), getIntFromStore(store, deliverKey)) @@ -1564,7 +1592,7 @@ func TestGasConsumptionBadTx(t *testing.T) { cdc := codec.NewLegacyAmino() registerTestCodec(cdc) - txHandlerOpt := func(bapp *BaseApp) { + txHandlerOpt := func(bapp *baseapp.BaseApp) { legacyRouter := middleware.NewLegacyRouter() r := sdk.NewRoute(routeMsgCounter, func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) { count := msg.(msgCounter).Counter @@ -1572,12 +1600,13 @@ func TestGasConsumptionBadTx(t *testing.T) { return &sdk.Result{}, nil }) legacyRouter.AddRoute(r) - txHandler, err := middleware.NewDefaultTxHandler(middleware.TxHandlerOptions{ - LegacyRouter: legacyRouter, - LegacyAnteHandler: ante, - MsgServiceRouter: middleware.NewMsgServiceRouter(interfaceRegistry), - }) - require.NoError(t, err) + txHandler := testTxHandler( + middleware.TxHandlerOptions{ + LegacyRouter: legacyRouter, + MsgServiceRouter: middleware.NewMsgServiceRouter(interfaceRegistry), + }, + ante, + ) bapp.SetTxHandler(txHandler) } app := setupBaseApp(t, txHandlerOpt) @@ -1617,7 +1646,7 @@ func TestGasConsumptionBadTx(t *testing.T) { func TestQuery(t *testing.T) { key, value := []byte("hello"), []byte("goodbye") - txHandlerOpt := func(bapp *BaseApp) { + txHandlerOpt := func(bapp *baseapp.BaseApp) { legacyRouter := middleware.NewLegacyRouter() r := sdk.NewRoute(routeMsgCounter, func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) { store := ctx.KVStore(capKey1) @@ -1625,16 +1654,17 @@ func TestQuery(t *testing.T) { return &sdk.Result{}, nil }) legacyRouter.AddRoute(r) - txHandler, err := middleware.NewDefaultTxHandler(middleware.TxHandlerOptions{ - LegacyRouter: legacyRouter, - LegacyAnteHandler: func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, err error) { + txHandler := testTxHandler( + middleware.TxHandlerOptions{ + LegacyRouter: legacyRouter, + MsgServiceRouter: middleware.NewMsgServiceRouter(interfaceRegistry), + }, + func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, err error) { store := ctx.KVStore(capKey1) store.Set(key, value) return }, - MsgServiceRouter: middleware.NewMsgServiceRouter(interfaceRegistry), - }) - require.NoError(t, err) + ) bapp.SetTxHandler(txHandler) } app := setupBaseApp(t, txHandlerOpt) @@ -1678,7 +1708,7 @@ func TestQuery(t *testing.T) { } func TestGRPCQuery(t *testing.T) { - grpcQueryOpt := func(bapp *BaseApp) { + grpcQueryOpt := func(bapp *baseapp.BaseApp) { testdata.RegisterQueryServer( bapp.GRPCQueryRouter(), testdata.QueryImpl{}, @@ -1713,14 +1743,14 @@ func TestGRPCQuery(t *testing.T) { // Test p2p filter queries func TestP2PQuery(t *testing.T) { - addrPeerFilterOpt := func(bapp *BaseApp) { + addrPeerFilterOpt := func(bapp *baseapp.BaseApp) { bapp.SetAddrPeerFilter(func(addrport string) abci.ResponseQuery { require.Equal(t, "1.1.1.1:8000", addrport) return abci.ResponseQuery{Code: uint32(3)} }) } - idPeerFilterOpt := func(bapp *BaseApp) { + idPeerFilterOpt := func(bapp *baseapp.BaseApp) { bapp.SetIDPeerFilter(func(id string) abci.ResponseQuery { require.Equal(t, "testid", id) return abci.ResponseQuery{Code: uint32(4)} @@ -1748,16 +1778,16 @@ func TestGetMaximumBlockGas(t *testing.T) { ctx := app.NewContext(true, tmproto.Header{}) app.StoreConsensusParams(ctx, &abci.ConsensusParams{Block: &abci.BlockParams{MaxGas: 0}}) - require.Equal(t, uint64(0), app.getMaximumBlockGas(ctx)) + require.Equal(t, uint64(0), app.GetMaximumBlockGas(ctx)) app.StoreConsensusParams(ctx, &abci.ConsensusParams{Block: &abci.BlockParams{MaxGas: -1}}) - require.Equal(t, uint64(0), app.getMaximumBlockGas(ctx)) + require.Equal(t, uint64(0), app.GetMaximumBlockGas(ctx)) app.StoreConsensusParams(ctx, &abci.ConsensusParams{Block: &abci.BlockParams{MaxGas: 5000000}}) - require.Equal(t, uint64(5000000), app.getMaximumBlockGas(ctx)) + require.Equal(t, uint64(5000000), app.GetMaximumBlockGas(ctx)) app.StoreConsensusParams(ctx, &abci.ConsensusParams{Block: &abci.BlockParams{MaxGas: -5000000}}) - require.Panics(t, func() { app.getMaximumBlockGas(ctx) }) + require.Panics(t, func() { app.GetMaximumBlockGas(ctx) }) } func TestListSnapshots(t *testing.T) { @@ -1940,21 +1970,14 @@ func (rtr *testCustomRouter) Route(ctx sdk.Context, path string) sdk.Handler { } func TestWithRouter(t *testing.T) { - // test increments in the ante - anteKey := []byte("ante-key") // test increments in the handler deliverKey := []byte("deliver-key") - txHandlerOpt := func(bapp *BaseApp) { + txHandlerOpt := func(bapp *baseapp.BaseApp) { customRouter := &testCustomRouter{routes: sync.Map{}} r := sdk.NewRoute(routeMsgCounter, handlerMsgCounter(t, capKey1, deliverKey)) customRouter.AddRoute(r) - txHandler, err := middleware.NewDefaultTxHandler(middleware.TxHandlerOptions{ - LegacyRouter: customRouter, - LegacyAnteHandler: anteHandlerTxTest(t, capKey1, anteKey), - MsgServiceRouter: middleware.NewMsgServiceRouter(interfaceRegistry), - }) - require.NoError(t, err) + txHandler := middleware.NewRunMsgsTxHandler(middleware.NewMsgServiceRouter(interfaceRegistry), customRouter) bapp.SetTxHandler(txHandler) } app := setupBaseApp(t, txHandlerOpt) @@ -1998,7 +2021,7 @@ func TestBaseApp_EndBlock(t *testing.T) { }, } - app := NewBaseApp(name, logger, db, nil) + app := baseapp.NewBaseApp(name, logger, db, nil) app.SetParamStore(¶mStore{db: dbm.NewMemDB()}) app.InitChain(abci.RequestInitChain{ ConsensusParams: cp, diff --git a/baseapp/custom_txhandler_test.go b/baseapp/custom_txhandler_test.go new file mode 100644 index 000000000000..6582dda66184 --- /dev/null +++ b/baseapp/custom_txhandler_test.go @@ -0,0 +1,117 @@ +package baseapp_test + +import ( + "context" + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/tx" + abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/crypto/tmhash" +) + +type handlerFun func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, err error) + +type customTxHandler struct { + handler handlerFun + next tx.Handler +} + +var _ tx.Handler = customTxHandler{} + +// CustomTxMiddleware is being used in tests for testing +// custom pre-`runMsgs` logic (also called antehandlers before). +func CustomTxHandlerMiddleware(handler handlerFun) tx.Middleware { + return func(txHandler tx.Handler) tx.Handler { + return customTxHandler{ + handler: handler, + next: txHandler, + } + } +} + +// CheckTx implements tx.Handler.CheckTx method. +func (txh customTxHandler) CheckTx(ctx context.Context, tx sdk.Tx, req abci.RequestCheckTx) (abci.ResponseCheckTx, error) { + sdkCtx, err := txh.runHandler(ctx, tx, req.Tx, false) + if err != nil { + return abci.ResponseCheckTx{}, err + } + + return txh.next.CheckTx(sdk.WrapSDKContext(sdkCtx), tx, req) +} + +// DeliverTx implements tx.Handler.DeliverTx method. +func (txh customTxHandler) DeliverTx(ctx context.Context, tx sdk.Tx, req abci.RequestDeliverTx) (abci.ResponseDeliverTx, error) { + sdkCtx, err := txh.runHandler(ctx, tx, req.Tx, false) + if err != nil { + return abci.ResponseDeliverTx{}, err + } + + return txh.next.DeliverTx(sdk.WrapSDKContext(sdkCtx), tx, req) +} + +// SimulateTx implements tx.Handler.SimulateTx method. +func (txh customTxHandler) SimulateTx(ctx context.Context, sdkTx sdk.Tx, req tx.RequestSimulateTx) (tx.ResponseSimulateTx, error) { + sdkCtx, err := txh.runHandler(ctx, sdkTx, req.TxBytes, true) + if err != nil { + return tx.ResponseSimulateTx{}, err + } + + return txh.next.SimulateTx(sdk.WrapSDKContext(sdkCtx), sdkTx, req) +} + +func (txh customTxHandler) runHandler(ctx context.Context, tx sdk.Tx, txBytes []byte, isSimulate bool) (sdk.Context, error) { + sdkCtx := sdk.UnwrapSDKContext(ctx) + if txh.handler == nil { + return sdkCtx, nil + } + + ms := sdkCtx.MultiStore() + + // Branch context before Handler call in case it aborts. + // This is required for both CheckTx and DeliverTx. + // Ref: https://github.com/cosmos/cosmos-sdk/issues/2772 + // + // NOTE: Alternatively, we could require that Handler ensures that + // writes do not happen if aborted/failed. This may have some + // performance benefits, but it'll be more difficult to get right. + cacheCtx, msCache := cacheTxContext(sdkCtx, txBytes) + cacheCtx = cacheCtx.WithEventManager(sdk.NewEventManager()) + newCtx, err := txh.handler(cacheCtx, tx, isSimulate) + if err != nil { + return sdk.Context{}, err + } + + if !newCtx.IsZero() { + // At this point, newCtx.MultiStore() is a store branch, or something else + // replaced by the Handler. We want the original multistore. + // + // Also, in the case of the tx aborting, we need to track gas consumed via + // the instantiated gas meter in the Handler, so we update the context + // prior to returning. + sdkCtx = newCtx.WithMultiStore(ms) + } + + msCache.Write() + + return sdkCtx, nil +} + +// cacheTxContext returns a new context based off of the provided context with +// a branched multi-store. +func cacheTxContext(sdkCtx sdk.Context, txBytes []byte) (sdk.Context, sdk.CacheMultiStore) { + ms := sdkCtx.MultiStore() + // TODO: https://github.com/cosmos/cosmos-sdk/issues/2824 + msCache := ms.CacheMultiStore() + if msCache.TracingEnabled() { + msCache = msCache.SetTracingContext( + sdk.TraceContext( + map[string]interface{}{ + "txHash": fmt.Sprintf("%X", tmhash.Sum(txBytes)), + }, + ), + ).(sdk.CacheMultiStore) + } + + return sdkCtx.WithMultiStore(msCache), msCache +} diff --git a/baseapp/queryrouter_test.go b/baseapp/queryrouter_test.go index c7637f17000e..4b38f6458641 100644 --- a/baseapp/queryrouter_test.go +++ b/baseapp/queryrouter_test.go @@ -1,4 +1,4 @@ -package baseapp +package baseapp_test import ( "testing" @@ -7,6 +7,7 @@ import ( abci "github.com/tendermint/tendermint/abci/types" + "github.com/cosmos/cosmos-sdk/baseapp" sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -15,7 +16,7 @@ var testQuerier = func(_ sdk.Context, _ []string, _ abci.RequestQuery) ([]byte, } func TestQueryRouter(t *testing.T) { - qr := NewQueryRouter() + qr := baseapp.NewQueryRouter() // require panic on invalid route require.Panics(t, func() { diff --git a/baseapp/util_test.go b/baseapp/util_test.go new file mode 100644 index 000000000000..5f7504af85ec --- /dev/null +++ b/baseapp/util_test.go @@ -0,0 +1,67 @@ +package baseapp + +import ( + "github.com/cosmos/cosmos-sdk/types" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// TODO: Can be removed once we move all middleware tests into x/auth/middleware +// ref: #https://github.com/cosmos/cosmos-sdk/issues/10282 + +// CheckState is an exported method to be able to access baseapp's +// checkState in tests. +// +// This method is only accessible in baseapp tests. +func (app *BaseApp) CheckState() *state { + return app.checkState +} + +// DeliverState is an exported method to be able to access baseapp's +// deliverState in tests. +// +// This method is only accessible in baseapp tests. +func (app *BaseApp) DeliverState() *state { + return app.deliverState +} + +// CMS is an exported method to be able to access baseapp's cms in tests. +// +// This method is only accessible in baseapp tests. +func (app *BaseApp) CMS() types.CommitMultiStore { + return app.cms +} + +// GetMaximumBlockGas return maximum blocks gas. +// +// This method is only accessible in baseapp tests. +func (app *BaseApp) GetMaximumBlockGas(ctx sdk.Context) uint64 { + return app.getMaximumBlockGas(ctx) +} + +// GetName return name. +// +// This method is only accessible in baseapp tests. +func (app *BaseApp) GetName() string { + return app.name +} + +// GetName return name. +// +// This method is only accessible in baseapp tests. +func (app *BaseApp) TxDecoder(txBytes []byte) (sdk.Tx, error) { + return app.txDecoder(txBytes) +} + +// CreateQueryContext calls app's createQueryContext. +// +// This method is only accessible in baseapp tests. +func (app *BaseApp) CreateQueryContext(height int64, prove bool) (sdk.Context, error) { + return app.createQueryContext(height, prove) +} + +// MinGasPrices returns minGasPrices. +// +// This method is only accessible in baseapp tests. +func (app *BaseApp) MinGasPrices() sdk.DecCoins { + return app.minGasPrices +} diff --git a/contrib/rosetta/configuration/bootstrap.json b/contrib/rosetta/configuration/bootstrap.json index 6fbfac1a509a..ad30b1611e2a 100644 --- a/contrib/rosetta/configuration/bootstrap.json +++ b/contrib/rosetta/configuration/bootstrap.json @@ -1,7 +1,7 @@ [ { "account_identifier": { - "address":"cosmos1y3awd3vl7g29q44uvz0yrevcduf2exvkwxk3uq" + "address":"cosmos1wy36cv7hveh7xt4ushy2twp5czqxnz5v6rn3xw" }, "currency":{ "symbol":"stake", diff --git a/contrib/rosetta/rosetta-ci/data.tar.gz b/contrib/rosetta/rosetta-ci/data.tar.gz index b3b890e1153840cf0c97bc1f79158916062c0595..7b9a99a6db0f67ad07ed884b7e27197464f08563 100644 GIT binary patch literal 39804 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%0DGwA2ZX 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

d5&;nl{mDeeqvIk}!76vE26wQRLpZMsfn&pQYS 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 diff --git a/server/mock/app.go b/server/mock/app.go index b1fe740a1936..d5f7c911c252 100644 --- a/server/mock/app.go +++ b/server/mock/app.go @@ -15,9 +15,19 @@ import ( "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/simapp" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/tx" "github.com/cosmos/cosmos-sdk/x/auth/middleware" ) +func testTxHandler(options middleware.TxHandlerOptions) tx.Handler { + return middleware.ComposeMiddlewares( + middleware.NewRunMsgsTxHandler(options.MsgServiceRouter, options.LegacyRouter), + middleware.GasTxMiddleware, + middleware.RecoveryTxMiddleware, + middleware.NewIndexEventsTxMiddleware(options.IndexEvents), + ) +} + // NewApp creates a simple mock kvstore app for testing. It should work // similar to a real app. Make sure rootDir is empty before running the test, // in order to guarantee consistent results @@ -44,13 +54,12 @@ func NewApp(rootDir string, logger log.Logger) (abci.Application, error) { // We're adding a test legacy route here, which accesses the kvstore // and simply sets the Msg's key/value pair in the kvstore. legacyRouter.AddRoute(sdk.NewRoute("kvstore", KVStoreHandler(capKeyMainStore))) - txHandler, err := middleware.NewDefaultTxHandler(middleware.TxHandlerOptions{ - LegacyRouter: legacyRouter, - MsgServiceRouter: middleware.NewMsgServiceRouter(encCfg.InterfaceRegistry), - }) - if err != nil { - return nil, err - } + txHandler := testTxHandler( + middleware.TxHandlerOptions{ + LegacyRouter: legacyRouter, + MsgServiceRouter: middleware.NewMsgServiceRouter(encCfg.InterfaceRegistry), + }, + ) baseApp.SetTxHandler(txHandler) // Load latest version. diff --git a/simapp/app.go b/simapp/app.go index f53df16bf462..b3323ec076f4 100644 --- a/simapp/app.go +++ b/simapp/app.go @@ -30,7 +30,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" authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" authsims "github.com/cosmos/cosmos-sdk/x/auth/simulation" authtx "github.com/cosmos/cosmos-sdk/x/auth/tx" @@ -401,29 +400,20 @@ func NewSimApp( } func (app *SimApp) setTxHandler(txConfig client.TxConfig, indexEventsStr []string) { - anteHandler, err := ante.NewAnteHandler( - ante.HandlerOptions{ - AccountKeeper: app.AccountKeeper, - BankKeeper: app.BankKeeper, - SignModeHandler: txConfig.SignModeHandler(), - FeegrantKeeper: app.FeeGrantKeeper, - SigGasConsumer: ante.DefaultSigVerificationGasConsumer, - }, - ) - if err != nil { - panic(err) - } - indexEvents := map[string]struct{}{} for _, e := range indexEventsStr { indexEvents[e] = struct{}{} } txHandler, err := authmiddleware.NewDefaultTxHandler(authmiddleware.TxHandlerOptions{ - Debug: app.Trace(), - IndexEvents: indexEvents, - LegacyRouter: app.legacyRouter, - MsgServiceRouter: app.msgSvcRouter, - LegacyAnteHandler: anteHandler, + Debug: app.Trace(), + IndexEvents: indexEvents, + LegacyRouter: app.legacyRouter, + MsgServiceRouter: app.msgSvcRouter, + AccountKeeper: app.AccountKeeper, + BankKeeper: app.BankKeeper, + FeegrantKeeper: app.FeeGrantKeeper, + SignModeHandler: txConfig.SignModeHandler(), + SigGasConsumer: authmiddleware.DefaultSigVerificationGasConsumer, }) if err != nil { panic(err) diff --git a/x/auth/ante/ante.go b/x/auth/ante/ante.go deleted file mode 100644 index dbb40aeb13ce..000000000000 --- a/x/auth/ante/ante.go +++ /dev/null @@ -1,57 +0,0 @@ -package ante - -import ( - sdk "github.com/cosmos/cosmos-sdk/types" - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" - "github.com/cosmos/cosmos-sdk/types/tx/signing" - authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing" - "github.com/cosmos/cosmos-sdk/x/auth/types" -) - -// HandlerOptions are the options required for constructing a default SDK AnteHandler. -type HandlerOptions struct { - AccountKeeper AccountKeeper - BankKeeper types.BankKeeper - FeegrantKeeper FeegrantKeeper - SignModeHandler authsigning.SignModeHandler - SigGasConsumer func(meter sdk.GasMeter, sig signing.SignatureV2, params types.Params) error -} - -// NewAnteHandler returns an AnteHandler that checks and increments sequence -// numbers, checks signatures & account numbers, and deducts fees from the first -// signer. -func NewAnteHandler(options HandlerOptions) (sdk.AnteHandler, error) { - if options.AccountKeeper == nil { - return nil, sdkerrors.Wrap(sdkerrors.ErrLogic, "account keeper is required for ante builder") - } - - if options.BankKeeper == nil { - return nil, sdkerrors.Wrap(sdkerrors.ErrLogic, "bank keeper is required for ante builder") - } - - if options.SignModeHandler == nil { - return nil, sdkerrors.Wrap(sdkerrors.ErrLogic, "sign mode handler is required for ante builder") - } - - var sigGasConsumer = options.SigGasConsumer - if sigGasConsumer == nil { - sigGasConsumer = DefaultSigVerificationGasConsumer - } - - anteDecorators := []sdk.AnteDecorator{ - NewRejectExtensionOptionsDecorator(), - NewMempoolFeeDecorator(), - NewValidateBasicDecorator(), - NewTxTimeoutHeightDecorator(), - NewValidateMemoDecorator(options.AccountKeeper), - NewConsumeGasForTxSizeDecorator(options.AccountKeeper), - NewDeductFeeDecorator(options.AccountKeeper, options.BankKeeper, options.FeegrantKeeper), - NewSetPubKeyDecorator(options.AccountKeeper), // SetPubKeyDecorator must be called before all signature verification decorators - NewValidateSigCountDecorator(options.AccountKeeper), - NewSigGasConsumeDecorator(options.AccountKeeper, sigGasConsumer), - NewSigVerificationDecorator(options.AccountKeeper, options.SignModeHandler), - NewIncrementSequenceDecorator(options.AccountKeeper), - } - - return sdk.ChainAnteDecorators(anteDecorators...), nil -} diff --git a/x/auth/ante/basic.go b/x/auth/ante/basic.go deleted file mode 100644 index d42aed214444..000000000000 --- a/x/auth/ante/basic.go +++ /dev/null @@ -1,207 +0,0 @@ -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, msg.ValidateBasic(for each msg inside tx) -// and return any non-nil error. -// If ValidateBasic passes, decorator calls next AnteHandler in chain. Note, -// ValidateBasicDecorator decorator will not get executed on ReCheckTx since it -// is not dependent on application state. -type ValidateBasicDecorator struct{} - -func NewValidateBasicDecorator() ValidateBasicDecorator { - return ValidateBasicDecorator{} -} - -func (vbd ValidateBasicDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) { - // no need to validate basic on recheck tx, call next antehandler - if ctx.IsReCheckTx() { - return next(ctx, tx, simulate) - } - - if err := tx.ValidateBasic(); err != nil { - return ctx, err - } - - return next(ctx, tx, simulate) -} - -// ValidateMemoDecorator will validate memo given the parameters passed in -// If memo is too large decorator returns with error, otherwise call next AnteHandler -// CONTRACT: Tx must implement TxWithMemo interface -type ValidateMemoDecorator struct { - ak AccountKeeper -} - -func NewValidateMemoDecorator(ak AccountKeeper) ValidateMemoDecorator { - return ValidateMemoDecorator{ - ak: ak, - } -} - -func (vmd ValidateMemoDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) { - memoTx, ok := tx.(sdk.TxWithMemo) - if !ok { - return ctx, sdkerrors.Wrap(sdkerrors.ErrTxDecode, "invalid transaction type") - } - - params := vmd.ak.GetParams(ctx) - - memoLength := len(memoTx.GetMemo()) - if uint64(memoLength) > params.MaxMemoCharacters { - return ctx, sdkerrors.Wrapf(sdkerrors.ErrMemoTooLarge, - "maximum number of characters is %d but received %d characters", - params.MaxMemoCharacters, memoLength, - ) - } - - return next(ctx, tx, simulate) -} - -// ConsumeTxSizeGasDecorator will take in parameters and consume gas proportional -// to the size of tx before calling next AnteHandler. Note, the gas costs will be -// slightly over estimated due to the fact that any given signing account may need -// to be retrieved from state. -// -// CONTRACT: If simulate=true, then signatures must either be completely filled -// in or empty. -// CONTRACT: To use this decorator, signatures of transaction must be represented -// as legacytx.StdSignature otherwise simulate mode will incorrectly estimate gas cost. -type ConsumeTxSizeGasDecorator struct { - ak AccountKeeper -} - -func NewConsumeGasForTxSizeDecorator(ak AccountKeeper) ConsumeTxSizeGasDecorator { - return ConsumeTxSizeGasDecorator{ - ak: ak, - } -} - -func (cgts ConsumeTxSizeGasDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) { - sigTx, ok := tx.(authsigning.SigVerifiableTx) - if !ok { - return ctx, sdkerrors.Wrap(sdkerrors.ErrTxDecode, "invalid tx type") - } - params := cgts.ak.GetParams(ctx) - - ctx.GasMeter().ConsumeGas(params.TxSizeCostPerByte*sdk.Gas(len(ctx.TxBytes())), "txSize") - - // simulate gas cost for signatures in simulate mode - if simulate { - // in simulate mode, each element should be a nil signature - sigs, err := sigTx.GetSignaturesV2() - if err != nil { - return ctx, err - } - n := len(sigs) - - for i, signer := range sigTx.GetSigners() { - // if signature is already filled in, no need to simulate gas cost - if i < n && !isIncompleteSignature(sigs[i].Data) { - continue - } - - var pubkey cryptotypes.PubKey - - acc := cgts.ak.GetAccount(ctx, signer) - - // use placeholder simSecp256k1Pubkey if sig is nil - if acc == nil || acc.GetPubKey() == nil { - pubkey = simSecp256k1Pubkey - } else { - pubkey = acc.GetPubKey() - } - - // use stdsignature to mock the size of a full signature - simSig := legacytx.StdSignature{ //nolint:staticcheck // this will be removed when proto is ready - Signature: simSecp256k1Sig[:], - PubKey: pubkey, - } - - sigBz := legacy.Cdc.MustMarshal(simSig) - cost := sdk.Gas(len(sigBz) + 6) - - // If the pubkey is a multi-signature pubkey, then we estimate for the maximum - // number of signers. - if _, ok := pubkey.(*multisig.LegacyAminoPubKey); ok { - cost *= params.TxSigLimit - } - - ctx.GasMeter().ConsumeGas(params.TxSizeCostPerByte*cost, "txSize") - } - } - - return next(ctx, tx, simulate) -} - -// isIncompleteSignature tests whether SignatureData is fully filled in for simulation purposes -func isIncompleteSignature(data signing.SignatureData) bool { - if data == nil { - return true - } - - switch data := data.(type) { - case *signing.SingleSignatureData: - return len(data.Signature) == 0 - case *signing.MultiSignatureData: - if len(data.Signatures) == 0 { - return true - } - for _, s := range data.Signatures { - if isIncompleteSignature(s) { - return true - } - } - } - - return false -} - -type ( - // TxTimeoutHeightDecorator defines an AnteHandler decorator that checks for a - // tx height timeout. - TxTimeoutHeightDecorator struct{} - - // TxWithTimeoutHeight defines the interface a tx must implement in order for - // TxHeightTimeoutDecorator to process the tx. - TxWithTimeoutHeight interface { - sdk.Tx - - GetTimeoutHeight() uint64 - } -) - -// TxTimeoutHeightDecorator defines an AnteHandler decorator that checks for a -// tx height timeout. -func NewTxTimeoutHeightDecorator() TxTimeoutHeightDecorator { - return TxTimeoutHeightDecorator{} -} - -// AnteHandle implements an AnteHandler decorator for the TxHeightTimeoutDecorator -// type where the current block height is checked against the tx's height timeout. -// If a height timeout is provided (non-zero) and is less than the current block -// height, then an error is returned. -func (txh TxTimeoutHeightDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) { - timeoutTx, ok := tx.(TxWithTimeoutHeight) - if !ok { - return ctx, sdkerrors.Wrap(sdkerrors.ErrTxDecode, "expected tx to implement TxWithTimeoutHeight") - } - - timeoutHeight := timeoutTx.GetTimeoutHeight() - if timeoutHeight > 0 && uint64(ctx.BlockHeight()) > timeoutHeight { - return ctx, sdkerrors.Wrapf( - sdkerrors.ErrTxTimeoutHeight, "block height: %d, timeout height: %d", ctx.BlockHeight(), timeoutHeight, - ) - } - - return next(ctx, tx, simulate) -} diff --git a/x/auth/ante/basic_test.go b/x/auth/ante/basic_test.go deleted file mode 100644 index 4a8cb830fdf6..000000000000 --- a/x/auth/ante/basic_test.go +++ /dev/null @@ -1,224 +0,0 @@ -package ante_test - -import ( - "strings" - - cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" - "github.com/cosmos/cosmos-sdk/crypto/types/multisig" - "github.com/cosmos/cosmos-sdk/testutil/testdata" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/types/tx/signing" - "github.com/cosmos/cosmos-sdk/x/auth/ante" -) - -func (suite *AnteTestSuite) TestValidateBasic() { - suite.SetupTest(true) // setup - suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() - - // keys and addresses - priv1, _, addr1 := testdata.KeyTestPubAddr() - - // msg and signatures - msg := testdata.NewTestMsg(addr1) - feeAmount := testdata.NewTestFeeAmount() - gasLimit := testdata.NewTestGasLimit() - suite.Require().NoError(suite.txBuilder.SetMsgs(msg)) - suite.txBuilder.SetFeeAmount(feeAmount) - suite.txBuilder.SetGasLimit(gasLimit) - - privs, accNums, accSeqs := []cryptotypes.PrivKey{}, []uint64{}, []uint64{} - invalidTx, err := suite.CreateTestTx(privs, accNums, accSeqs, suite.ctx.ChainID()) - suite.Require().NoError(err) - - vbd := ante.NewValidateBasicDecorator() - antehandler := sdk.ChainAnteDecorators(vbd) - _, err = antehandler(suite.ctx, invalidTx, false) - - suite.Require().NotNil(err, "Did not error on invalid tx") - - privs, accNums, accSeqs = []cryptotypes.PrivKey{priv1}, []uint64{0}, []uint64{0} - validTx, err := suite.CreateTestTx(privs, accNums, accSeqs, suite.ctx.ChainID()) - suite.Require().NoError(err) - - _, err = antehandler(suite.ctx, validTx, false) - suite.Require().Nil(err, "ValidateBasicDecorator returned error on valid tx. err: %v", err) - - // test decorator skips on recheck - suite.ctx = suite.ctx.WithIsReCheckTx(true) - - // decorator should skip processing invalidTx on recheck and thus return nil-error - _, err = antehandler(suite.ctx, invalidTx, false) - - suite.Require().Nil(err, "ValidateBasicDecorator ran on ReCheck") -} - -func (suite *AnteTestSuite) TestValidateMemo() { - suite.SetupTest(true) // setup - suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() - - // keys and addresses - priv1, _, addr1 := testdata.KeyTestPubAddr() - - // msg and signatures - msg := testdata.NewTestMsg(addr1) - feeAmount := testdata.NewTestFeeAmount() - gasLimit := testdata.NewTestGasLimit() - suite.Require().NoError(suite.txBuilder.SetMsgs(msg)) - suite.txBuilder.SetFeeAmount(feeAmount) - suite.txBuilder.SetGasLimit(gasLimit) - - privs, accNums, accSeqs := []cryptotypes.PrivKey{priv1}, []uint64{0}, []uint64{0} - suite.txBuilder.SetMemo(strings.Repeat("01234567890", 500)) - invalidTx, err := suite.CreateTestTx(privs, accNums, accSeqs, suite.ctx.ChainID()) - suite.Require().NoError(err) - - // require that long memos get rejected - vmd := ante.NewValidateMemoDecorator(suite.app.AccountKeeper) - antehandler := sdk.ChainAnteDecorators(vmd) - _, err = antehandler(suite.ctx, invalidTx, false) - - suite.Require().NotNil(err, "Did not error on tx with high memo") - - suite.txBuilder.SetMemo(strings.Repeat("01234567890", 10)) - validTx, err := suite.CreateTestTx(privs, accNums, accSeqs, suite.ctx.ChainID()) - suite.Require().NoError(err) - - // require small memos pass ValidateMemo Decorator - _, err = antehandler(suite.ctx, validTx, false) - suite.Require().Nil(err, "ValidateBasicDecorator returned error on valid tx. err: %v", err) -} - -func (suite *AnteTestSuite) TestConsumeGasForTxSize() { - suite.SetupTest(true) // setup - - // keys and addresses - priv1, _, addr1 := testdata.KeyTestPubAddr() - - // msg and signatures - msg := testdata.NewTestMsg(addr1) - feeAmount := testdata.NewTestFeeAmount() - gasLimit := testdata.NewTestGasLimit() - - cgtsd := ante.NewConsumeGasForTxSizeDecorator(suite.app.AccountKeeper) - antehandler := sdk.ChainAnteDecorators(cgtsd) - - testCases := []struct { - name string - sigV2 signing.SignatureV2 - }{ - {"SingleSignatureData", signing.SignatureV2{PubKey: priv1.PubKey()}}, - {"MultiSignatureData", signing.SignatureV2{PubKey: priv1.PubKey(), Data: multisig.NewMultisig(2)}}, - } - - for _, tc := range testCases { - suite.Run(tc.name, func() { - suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() - suite.Require().NoError(suite.txBuilder.SetMsgs(msg)) - suite.txBuilder.SetFeeAmount(feeAmount) - suite.txBuilder.SetGasLimit(gasLimit) - suite.txBuilder.SetMemo(strings.Repeat("01234567890", 10)) - - privs, accNums, accSeqs := []cryptotypes.PrivKey{priv1}, []uint64{0}, []uint64{0} - tx, err := suite.CreateTestTx(privs, accNums, accSeqs, suite.ctx.ChainID()) - suite.Require().NoError(err) - - txBytes, err := suite.clientCtx.TxConfig.TxJSONEncoder()(tx) - suite.Require().Nil(err, "Cannot marshal tx: %v", err) - - params := suite.app.AccountKeeper.GetParams(suite.ctx) - expectedGas := sdk.Gas(len(txBytes)) * params.TxSizeCostPerByte - - // Set suite.ctx with TxBytes manually - suite.ctx = suite.ctx.WithTxBytes(txBytes) - - // track how much gas is necessary to retrieve parameters - beforeGas := suite.ctx.GasMeter().GasConsumed() - suite.app.AccountKeeper.GetParams(suite.ctx) - afterGas := suite.ctx.GasMeter().GasConsumed() - expectedGas += afterGas - beforeGas - - beforeGas = suite.ctx.GasMeter().GasConsumed() - suite.ctx, err = antehandler(suite.ctx, tx, false) - suite.Require().Nil(err, "ConsumeTxSizeGasDecorator returned error: %v", err) - - // require that decorator consumes expected amount of gas - consumedGas := suite.ctx.GasMeter().GasConsumed() - beforeGas - suite.Require().Equal(expectedGas, consumedGas, "Decorator did not consume the correct amount of gas") - - // simulation must not underestimate gas of this decorator even with nil signatures - txBuilder, err := suite.clientCtx.TxConfig.WrapTxBuilder(tx) - suite.Require().NoError(err) - suite.Require().NoError(txBuilder.SetSignatures(tc.sigV2)) - tx = txBuilder.GetTx() - - simTxBytes, err := suite.clientCtx.TxConfig.TxJSONEncoder()(tx) - suite.Require().Nil(err, "Cannot marshal tx: %v", err) - // require that simulated tx is smaller than tx with signatures - suite.Require().True(len(simTxBytes) < len(txBytes), "simulated tx still has signatures") - - // Set suite.ctx with smaller simulated TxBytes manually - suite.ctx = suite.ctx.WithTxBytes(simTxBytes) - - beforeSimGas := suite.ctx.GasMeter().GasConsumed() - - // run antehandler with simulate=true - suite.ctx, err = antehandler(suite.ctx, tx, true) - consumedSimGas := suite.ctx.GasMeter().GasConsumed() - beforeSimGas - - // require that antehandler passes and does not underestimate decorator cost - suite.Require().Nil(err, "ConsumeTxSizeGasDecorator returned error: %v", err) - suite.Require().True(consumedSimGas >= expectedGas, "Simulate mode underestimates gas on AnteDecorator. Simulated cost: %d, expected cost: %d", consumedSimGas, expectedGas) - - }) - } - -} - -func (suite *AnteTestSuite) TestTxHeightTimeoutDecorator() { - suite.SetupTest(true) - - antehandler := sdk.ChainAnteDecorators(ante.NewTxTimeoutHeightDecorator()) - - // keys and addresses - priv1, _, addr1 := testdata.KeyTestPubAddr() - - // msg and signatures - msg := testdata.NewTestMsg(addr1) - feeAmount := testdata.NewTestFeeAmount() - gasLimit := testdata.NewTestGasLimit() - - testCases := []struct { - name string - timeout uint64 - height int64 - expectErr bool - }{ - {"default value", 0, 10, false}, - {"no timeout (greater height)", 15, 10, false}, - {"no timeout (same height)", 10, 10, false}, - {"timeout (smaller height)", 9, 10, true}, - } - - for _, tc := range testCases { - tc := tc - - suite.Run(tc.name, func() { - suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() - - suite.Require().NoError(suite.txBuilder.SetMsgs(msg)) - - suite.txBuilder.SetFeeAmount(feeAmount) - suite.txBuilder.SetGasLimit(gasLimit) - suite.txBuilder.SetMemo(strings.Repeat("01234567890", 10)) - suite.txBuilder.SetTimeoutHeight(tc.timeout) - - privs, accNums, accSeqs := []cryptotypes.PrivKey{priv1}, []uint64{0}, []uint64{0} - tx, err := suite.CreateTestTx(privs, accNums, accSeqs, suite.ctx.ChainID()) - suite.Require().NoError(err) - - ctx := suite.ctx.WithBlockHeight(tc.height) - _, err = antehandler(ctx, tx, true) - suite.Require().Equal(tc.expectErr, err != nil, err) - }) - } -} diff --git a/x/auth/ante/ext.go b/x/auth/ante/ext.go deleted file mode 100644 index 362b8d32a971..000000000000 --- a/x/auth/ante/ext.go +++ /dev/null @@ -1,36 +0,0 @@ -package ante - -import ( - codectypes "github.com/cosmos/cosmos-sdk/codec/types" - "github.com/cosmos/cosmos-sdk/types" - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" -) - -type HasExtensionOptionsTx interface { - GetExtensionOptions() []*codectypes.Any - GetNonCriticalExtensionOptions() []*codectypes.Any -} - -// RejectExtensionOptionsDecorator is an AnteDecorator that rejects all extension -// options which can optionally be included in protobuf transactions. Users that -// need extension options should create a custom AnteHandler chain that handles -// needed extension options properly and rejects unknown ones. -type RejectExtensionOptionsDecorator struct{} - -// NewRejectExtensionOptionsDecorator creates a new RejectExtensionOptionsDecorator -func NewRejectExtensionOptionsDecorator() RejectExtensionOptionsDecorator { - return RejectExtensionOptionsDecorator{} -} - -var _ types.AnteDecorator = RejectExtensionOptionsDecorator{} - -// AnteHandle implements the AnteDecorator.AnteHandle method -func (r RejectExtensionOptionsDecorator) AnteHandle(ctx types.Context, tx types.Tx, simulate bool, next types.AnteHandler) (newCtx types.Context, err error) { - if hasExtOptsTx, ok := tx.(HasExtensionOptionsTx); ok { - if len(hasExtOptsTx.GetExtensionOptions()) != 0 { - return ctx, sdkerrors.ErrUnknownExtensionOptions - } - } - - return next(ctx, tx, simulate) -} diff --git a/x/auth/ante/ext_test.go b/x/auth/ante/ext_test.go deleted file mode 100644 index 89ce6a7d649f..000000000000 --- a/x/auth/ante/ext_test.go +++ /dev/null @@ -1,36 +0,0 @@ -package ante_test - -import ( - "github.com/cosmos/cosmos-sdk/codec/types" - "github.com/cosmos/cosmos-sdk/testutil/testdata" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/auth/ante" - "github.com/cosmos/cosmos-sdk/x/auth/tx" -) - -func (suite *AnteTestSuite) TestRejectExtensionOptionsDecorator() { - suite.SetupTest(true) // setup - suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() - - reod := ante.NewRejectExtensionOptionsDecorator() - antehandler := sdk.ChainAnteDecorators(reod) - - // no extension options should not trigger an error - theTx := suite.txBuilder.GetTx() - _, err := antehandler(suite.ctx, theTx, false) - suite.Require().NoError(err) - - extOptsTxBldr, ok := suite.txBuilder.(tx.ExtensionOptionsTxBuilder) - if !ok { - // if we can't set extension options, this decorator doesn't apply and we're done - return - } - - // setting any extension option should cause an error - any, err := types.NewAnyWithValue(testdata.NewTestMsg()) - suite.Require().NoError(err) - extOptsTxBldr.SetExtensionOptions(any) - theTx = suite.txBuilder.GetTx() - _, err = antehandler(suite.ctx, theTx, false) - suite.Require().EqualError(err, "unknown extension options") -} diff --git a/x/auth/ante/fee.go b/x/auth/ante/fee.go deleted file mode 100644 index b1d1d72a770e..000000000000 --- a/x/auth/ante/fee.go +++ /dev/null @@ -1,140 +0,0 @@ -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" -) - -// MempoolFeeDecorator will check if the transaction's fee is at least as large -// as the local validator's minimum gasFee (defined in validator config). -// If fee is too low, decorator returns error and tx is rejected from mempool. -// Note this only applies when ctx.CheckTx = true -// If fee is high enough or not CheckTx, then call next AnteHandler -// CONTRACT: Tx must implement FeeTx to use MempoolFeeDecorator -type MempoolFeeDecorator struct{} - -func NewMempoolFeeDecorator() MempoolFeeDecorator { - return MempoolFeeDecorator{} -} - -func (mfd MempoolFeeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) { - feeTx, ok := tx.(sdk.FeeTx) - if !ok { - return ctx, sdkerrors.Wrap(sdkerrors.ErrTxDecode, "Tx must be a FeeTx") - } - - feeCoins := feeTx.GetFee() - gas := feeTx.GetGas() - - // Ensure that the provided fees meet a minimum threshold for the validator, - // if this is a CheckTx. This is only for local mempool purposes, and thus - // is only ran on check tx. - if ctx.IsCheckTx() && !simulate { - minGasPrices := ctx.MinGasPrices() - if !minGasPrices.IsZero() { - requiredFees := make(sdk.Coins, len(minGasPrices)) - - // Determine the required fees by multiplying each required minimum gas - // price by the gas limit, where fee = ceil(minGasPrice * gasLimit). - glDec := sdk.NewDec(int64(gas)) - for i, gp := range minGasPrices { - fee := gp.Amount.Mul(glDec) - requiredFees[i] = sdk.NewCoin(gp.Denom, fee.Ceil().RoundInt()) - } - - if !feeCoins.IsAnyGTE(requiredFees) { - return ctx, sdkerrors.Wrapf(sdkerrors.ErrInsufficientFee, "insufficient fees; got: %s required: %s", feeCoins, requiredFees) - } - } - } - - return next(ctx, tx, simulate) -} - -// DeductFeeDecorator deducts fees from the first signer of the tx -// If the first signer does not have the funds to pay for the fees, return with InsufficientFunds error -// Call next AnteHandler if fees successfully deducted -// CONTRACT: Tx must implement FeeTx interface to use DeductFeeDecorator -type DeductFeeDecorator struct { - ak AccountKeeper - bankKeeper types.BankKeeper - feegrantKeeper FeegrantKeeper -} - -func NewDeductFeeDecorator(ak AccountKeeper, bk types.BankKeeper, fk FeegrantKeeper) DeductFeeDecorator { - return DeductFeeDecorator{ - ak: ak, - bankKeeper: bk, - feegrantKeeper: fk, - } -} - -func (dfd DeductFeeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) { - feeTx, ok := tx.(sdk.FeeTx) - if !ok { - return ctx, sdkerrors.Wrap(sdkerrors.ErrTxDecode, "Tx must be a FeeTx") - } - - if addr := dfd.ak.GetModuleAddress(types.FeeCollectorName); addr == nil { - panic(fmt.Sprintf("%s module account has not been set", types.FeeCollectorName)) - } - - fee := feeTx.GetFee() - feePayer := feeTx.FeePayer() - feeGranter := feeTx.FeeGranter() - - deductFeesFrom := feePayer - - // if feegranter set deduct fee from feegranter account. - // this works with only when feegrant enabled. - if feeGranter != nil { - if dfd.feegrantKeeper == nil { - return ctx, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "fee grants are not enabled") - } else if !feeGranter.Equals(feePayer) { - err := dfd.feegrantKeeper.UseGrantedFees(ctx, feeGranter, feePayer, fee, tx.GetMsgs()) - - if err != nil { - return ctx, sdkerrors.Wrapf(err, "%s not allowed to pay fees from %s", feeGranter, feePayer) - } - } - - deductFeesFrom = feeGranter - } - - deductFeesFromAcc := dfd.ak.GetAccount(ctx, deductFeesFrom) - if deductFeesFromAcc == nil { - return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnknownAddress, "fee payer address: %s does not exist", deductFeesFrom) - } - - // deduct the fees - if !feeTx.GetFee().IsZero() { - err = DeductFees(dfd.bankKeeper, ctx, deductFeesFromAcc, feeTx.GetFee()) - if err != nil { - return ctx, err - } - } - - events := sdk.Events{sdk.NewEvent(sdk.EventTypeTx, - sdk.NewAttribute(sdk.AttributeKeyFee, feeTx.GetFee().String()), - )} - ctx.EventManager().EmitEvents(events) - - return next(ctx, tx, simulate) -} - -// DeductFees deducts fees from the given account. -func DeductFees(bankKeeper 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/x/auth/ante/fee_test.go b/x/auth/ante/fee_test.go deleted file mode 100644 index 06ccb4d3948f..000000000000 --- a/x/auth/ante/fee_test.go +++ /dev/null @@ -1,104 +0,0 @@ -package ante_test - -import ( - cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" - "github.com/cosmos/cosmos-sdk/testutil/testdata" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/auth/ante" - "github.com/cosmos/cosmos-sdk/x/bank/testutil" -) - -func (suite *AnteTestSuite) TestEnsureMempoolFees() { - suite.SetupTest(true) // setup - suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() - - mfd := ante.NewMempoolFeeDecorator() - antehandler := sdk.ChainAnteDecorators(mfd) - - // keys and addresses - priv1, _, addr1 := testdata.KeyTestPubAddr() - - // msg and signatures - msg := testdata.NewTestMsg(addr1) - feeAmount := testdata.NewTestFeeAmount() - gasLimit := testdata.NewTestGasLimit() - suite.Require().NoError(suite.txBuilder.SetMsgs(msg)) - suite.txBuilder.SetFeeAmount(feeAmount) - suite.txBuilder.SetGasLimit(gasLimit) - - privs, accNums, accSeqs := []cryptotypes.PrivKey{priv1}, []uint64{0}, []uint64{0} - tx, err := suite.CreateTestTx(privs, accNums, accSeqs, suite.ctx.ChainID()) - suite.Require().NoError(err) - - // Set high gas price so standard test fee fails - atomPrice := sdk.NewDecCoinFromDec("atom", sdk.NewDec(200).Quo(sdk.NewDec(100000))) - highGasPrice := []sdk.DecCoin{atomPrice} - suite.ctx = suite.ctx.WithMinGasPrices(highGasPrice) - - // Set IsCheckTx to true - suite.ctx = suite.ctx.WithIsCheckTx(true) - - // antehandler errors with insufficient fees - _, err = antehandler(suite.ctx, tx, false) - suite.Require().NotNil(err, "Decorator should have errored on too low fee for local gasPrice") - - // Set IsCheckTx to false - suite.ctx = suite.ctx.WithIsCheckTx(false) - - // antehandler should not error since we do not check minGasPrice in DeliverTx - _, err = antehandler(suite.ctx, tx, false) - suite.Require().Nil(err, "MempoolFeeDecorator returned error in DeliverTx") - - // Set IsCheckTx back to true for testing sufficient mempool fee - suite.ctx = suite.ctx.WithIsCheckTx(true) - - atomPrice = sdk.NewDecCoinFromDec("atom", sdk.NewDec(0).Quo(sdk.NewDec(100000))) - lowGasPrice := []sdk.DecCoin{atomPrice} - suite.ctx = suite.ctx.WithMinGasPrices(lowGasPrice) - - _, err = antehandler(suite.ctx, tx, false) - suite.Require().Nil(err, "Decorator should not have errored on fee higher than local gasPrice") -} - -func (suite *AnteTestSuite) TestDeductFees() { - suite.SetupTest(false) // setup - suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() - - // keys and addresses - priv1, _, addr1 := testdata.KeyTestPubAddr() - - // msg and signatures - msg := testdata.NewTestMsg(addr1) - feeAmount := testdata.NewTestFeeAmount() - gasLimit := testdata.NewTestGasLimit() - suite.Require().NoError(suite.txBuilder.SetMsgs(msg)) - suite.txBuilder.SetFeeAmount(feeAmount) - suite.txBuilder.SetGasLimit(gasLimit) - - privs, accNums, accSeqs := []cryptotypes.PrivKey{priv1}, []uint64{0}, []uint64{0} - tx, err := suite.CreateTestTx(privs, accNums, accSeqs, suite.ctx.ChainID()) - suite.Require().NoError(err) - - // Set account with insufficient funds - acc := suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, addr1) - suite.app.AccountKeeper.SetAccount(suite.ctx, acc) - coins := sdk.NewCoins(sdk.NewCoin("atom", sdk.NewInt(10))) - err = testutil.FundAccount(suite.app.BankKeeper, suite.ctx, addr1, coins) - suite.Require().NoError(err) - - dfd := ante.NewDeductFeeDecorator(suite.app.AccountKeeper, suite.app.BankKeeper, nil) - antehandler := sdk.ChainAnteDecorators(dfd) - - _, err = antehandler(suite.ctx, tx, false) - - suite.Require().NotNil(err, "Tx did not error when fee payer had insufficient funds") - - // Set account with sufficient funds - suite.app.AccountKeeper.SetAccount(suite.ctx, acc) - err = testutil.FundAccount(suite.app.BankKeeper, suite.ctx, addr1, sdk.NewCoins(sdk.NewCoin("atom", sdk.NewInt(200)))) - suite.Require().NoError(err) - - _, err = antehandler(suite.ctx, tx, false) - - suite.Require().Nil(err, "Tx errored after account has been set with sufficient funds") -} diff --git a/x/auth/ante/setup.go b/x/auth/ante/setup.go deleted file mode 100644 index 737cc295b980..000000000000 --- a/x/auth/ante/setup.go +++ /dev/null @@ -1,76 +0,0 @@ -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()) - - // 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( - "insufficient gas, gasOffered: %d, gasRequired: %d, code location: %v", - gasTx.GetGas(), newCtx.GasMeter().GasConsumed(), rType.Descriptor) - - err = sdkerrors.Wrap(sdkerrors.ErrOutOfGas, log) - default: - panic(r) - } - } - }() - - return next(newCtx, tx, simulate) -} - -// SetGasMeter returns a new context with a gas meter set from a given context. -func SetGasMeter(simulate bool, ctx sdk.Context, gasLimit uint64) sdk.Context { - // In various cases such as simulation and during the genesis block, we do not - // meter any gas utilization. - if simulate || ctx.BlockHeight() == 0 { - return ctx.WithGasMeter(sdk.NewInfiniteGasMeter()) - } - - return ctx.WithGasMeter(sdk.NewGasMeter(gasLimit)) -} diff --git a/x/auth/ante/testutil_test.go b/x/auth/ante/testutil_test.go deleted file mode 100644 index faf2e7cdf658..000000000000 --- a/x/auth/ante/testutil_test.go +++ /dev/null @@ -1,214 +0,0 @@ -package ante_test - -import ( - "errors" - "fmt" - "testing" - - minttypes "github.com/cosmos/cosmos-sdk/x/mint/types" - - "github.com/stretchr/testify/suite" - tmproto "github.com/tendermint/tendermint/proto/tendermint/types" - - "github.com/cosmos/cosmos-sdk/client" - "github.com/cosmos/cosmos-sdk/client/tx" - cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" - "github.com/cosmos/cosmos-sdk/simapp" - "github.com/cosmos/cosmos-sdk/testutil/testdata" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/types/tx/signing" - "github.com/cosmos/cosmos-sdk/x/auth/ante" - xauthsigning "github.com/cosmos/cosmos-sdk/x/auth/signing" - "github.com/cosmos/cosmos-sdk/x/auth/types" - authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" -) - -// TestAccount represents an account used in the tests in x/auth/ante. -type TestAccount struct { - acc types.AccountI - priv cryptotypes.PrivKey -} - -// AnteTestSuite is a test suite to be used with ante handler tests. -type AnteTestSuite struct { - suite.Suite - - app *simapp.SimApp - anteHandler sdk.AnteHandler - ctx sdk.Context - clientCtx client.Context - txBuilder client.TxBuilder -} - -// returns context and app with params set on account keeper -func createTestApp(t *testing.T, isCheckTx bool) (*simapp.SimApp, sdk.Context) { - app := simapp.Setup(t, isCheckTx) - ctx := app.BaseApp.NewContext(isCheckTx, tmproto.Header{}) - app.AccountKeeper.SetParams(ctx, authtypes.DefaultParams()) - - return app, ctx -} - -// SetupTest setups a new test, with new app, context, and anteHandler. -func (suite *AnteTestSuite) SetupTest(isCheckTx bool) { - suite.app, suite.ctx = createTestApp(suite.T(), isCheckTx) - suite.ctx = suite.ctx.WithBlockHeight(1) - - // Set up TxConfig. - encodingConfig := simapp.MakeTestEncodingConfig() - // We're using TestMsg encoding in some tests, so register it here. - encodingConfig.Amino.RegisterConcrete(&testdata.TestMsg{}, "testdata.TestMsg", nil) - testdata.RegisterInterfaces(encodingConfig.InterfaceRegistry) - - suite.clientCtx = client.Context{}. - WithTxConfig(encodingConfig.TxConfig) - - // We're not using ante.NewAnteHandler here because: - // - ante.NewAnteHandler doesn't have SetUpContextDecorator, as it has been - // moved to the gas TxMiddleware - // - whereas these tests have not been migrated to middlewares yet, so - // still need the SetUpContextDecorator. - // - // TODO: migrate all antehandler tests to middleware tests. - // https://github.com/cosmos/cosmos-sdk/issues/9585 - anteDecorators := []sdk.AnteDecorator{ - ante.NewSetUpContextDecorator(), - ante.NewRejectExtensionOptionsDecorator(), - ante.NewMempoolFeeDecorator(), - ante.NewValidateBasicDecorator(), - ante.NewTxTimeoutHeightDecorator(), - ante.NewValidateMemoDecorator(suite.app.AccountKeeper), - ante.NewConsumeGasForTxSizeDecorator(suite.app.AccountKeeper), - ante.NewDeductFeeDecorator(suite.app.AccountKeeper, suite.app.BankKeeper, suite.app.FeeGrantKeeper), - // SetPubKeyDecorator must be called before all signature verification decorators - ante.NewSetPubKeyDecorator(suite.app.AccountKeeper), - ante.NewValidateSigCountDecorator(suite.app.AccountKeeper), - ante.NewSigGasConsumeDecorator(suite.app.AccountKeeper, ante.DefaultSigVerificationGasConsumer), - ante.NewSigVerificationDecorator(suite.app.AccountKeeper, encodingConfig.TxConfig.SignModeHandler()), - ante.NewIncrementSequenceDecorator(suite.app.AccountKeeper), - } - - suite.anteHandler = sdk.ChainAnteDecorators(anteDecorators...) -} - -// CreateTestAccounts creates `numAccs` accounts, and return all relevant -// information about them including their private keys. -func (suite *AnteTestSuite) CreateTestAccounts(numAccs int) []TestAccount { - var accounts []TestAccount - - for i := 0; i < numAccs; i++ { - priv, _, addr := testdata.KeyTestPubAddr() - acc := suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, addr) - err := acc.SetAccountNumber(uint64(i)) - suite.Require().NoError(err) - suite.app.AccountKeeper.SetAccount(suite.ctx, acc) - someCoins := sdk.Coins{ - sdk.NewInt64Coin("atom", 10000000), - } - err = suite.app.BankKeeper.MintCoins(suite.ctx, minttypes.ModuleName, someCoins) - suite.Require().NoError(err) - - err = suite.app.BankKeeper.SendCoinsFromModuleToAccount(suite.ctx, minttypes.ModuleName, addr, someCoins) - suite.Require().NoError(err) - - accounts = append(accounts, TestAccount{acc, priv}) - } - - return accounts -} - -// CreateTestTx is a helper function to create a tx given multiple inputs. -func (suite *AnteTestSuite) CreateTestTx(privs []cryptotypes.PrivKey, accNums []uint64, accSeqs []uint64, chainID string) (xauthsigning.Tx, error) { - // First round: we gather all the signer infos. We use the "set empty - // signature" hack to do that. - var sigsV2 []signing.SignatureV2 - for i, priv := range privs { - sigV2 := signing.SignatureV2{ - PubKey: priv.PubKey(), - Data: &signing.SingleSignatureData{ - SignMode: suite.clientCtx.TxConfig.SignModeHandler().DefaultMode(), - Signature: nil, - }, - Sequence: accSeqs[i], - } - - sigsV2 = append(sigsV2, sigV2) - } - err := suite.txBuilder.SetSignatures(sigsV2...) - if err != nil { - return nil, err - } - - // Second round: all signer infos are set, so each signer can sign. - sigsV2 = []signing.SignatureV2{} - for i, priv := range privs { - signerData := xauthsigning.SignerData{ - ChainID: chainID, - AccountNumber: accNums[i], - Sequence: accSeqs[i], - } - sigV2, err := tx.SignWithPrivKey( - suite.clientCtx.TxConfig.SignModeHandler().DefaultMode(), signerData, - suite.txBuilder, priv, suite.clientCtx.TxConfig, accSeqs[i]) - if err != nil { - return nil, err - } - - sigsV2 = append(sigsV2, sigV2) - } - err = suite.txBuilder.SetSignatures(sigsV2...) - if err != nil { - return nil, err - } - - return suite.txBuilder.GetTx(), nil -} - -// TestCase represents a test case used in test tables. -type TestCase struct { - desc string - malleate func() - simulate bool - expPass bool - expErr error -} - -// CreateTestTx is a helper function to create a tx given multiple inputs. -func (suite *AnteTestSuite) RunTestCase(privs []cryptotypes.PrivKey, msgs []sdk.Msg, feeAmount sdk.Coins, gasLimit uint64, accNums, accSeqs []uint64, chainID string, tc TestCase) { - suite.Run(fmt.Sprintf("Case %s", tc.desc), func() { - suite.Require().NoError(suite.txBuilder.SetMsgs(msgs...)) - suite.txBuilder.SetFeeAmount(feeAmount) - suite.txBuilder.SetGasLimit(gasLimit) - - // Theoretically speaking, ante handler unit tests should only test - // ante handlers, but here we sometimes also test the tx creation - // process. - tx, txErr := suite.CreateTestTx(privs, accNums, accSeqs, chainID) - newCtx, anteErr := suite.anteHandler(suite.ctx, tx, tc.simulate) - - if tc.expPass { - suite.Require().NoError(txErr) - suite.Require().NoError(anteErr) - suite.Require().NotNil(newCtx) - - suite.ctx = newCtx - } else { - switch { - case txErr != nil: - suite.Require().Error(txErr) - suite.Require().True(errors.Is(txErr, tc.expErr)) - - case anteErr != nil: - suite.Require().Error(anteErr) - suite.Require().True(errors.Is(anteErr, tc.expErr)) - - default: - suite.Fail("expected one of txErr,anteErr to be an error") - } - } - }) -} - -func TestAnteTestSuite(t *testing.T) { - suite.Run(t, new(AnteTestSuite)) -} diff --git a/x/auth/middleware/basic.go b/x/auth/middleware/basic.go new file mode 100644 index 000000000000..04ea10bf416e --- /dev/null +++ b/x/auth/middleware/basic.go @@ -0,0 +1,358 @@ +package middleware + +import ( + "context" + + "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" + "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" + abci "github.com/tendermint/tendermint/abci/types" +) + +type validateBasicTxHandler struct { + next tx.Handler +} + +// ValidateBasicMiddleware will call tx.ValidateBasic, msg.ValidateBasic(for each msg inside tx) +// and return any non-nil error. +// If ValidateBasic passes, middleware calls next middleware in chain. Note, +// validateBasicTxHandler will not get executed on ReCheckTx since it +// is not dependent on application state. +func ValidateBasicMiddleware(txh tx.Handler) tx.Handler { + return validateBasicTxHandler{ + next: txh, + } +} + +var _ tx.Handler = validateBasicTxHandler{} + +// validateBasicTxMsgs executes basic validator calls for messages. +func validateBasicTxMsgs(msgs []sdk.Msg) error { + if len(msgs) == 0 { + return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "must contain at least one message") + } + + for _, msg := range msgs { + err := msg.ValidateBasic() + if err != nil { + return err + } + } + + return nil +} + +// CheckTx implements tx.Handler.CheckTx. +func (txh validateBasicTxHandler) CheckTx(ctx context.Context, tx sdk.Tx, req abci.RequestCheckTx) (abci.ResponseCheckTx, error) { + // no need to validate basic on recheck tx, call next middleware + if req.Type == abci.CheckTxType_Recheck { + return txh.next.CheckTx(ctx, tx, req) + } + + if err := validateBasicTxMsgs(tx.GetMsgs()); err != nil { + return abci.ResponseCheckTx{}, err + } + + if err := tx.ValidateBasic(); err != nil { + return abci.ResponseCheckTx{}, err + } + + return txh.next.CheckTx(ctx, tx, req) +} + +// DeliverTx implements tx.Handler.DeliverTx. +func (txh validateBasicTxHandler) DeliverTx(ctx context.Context, tx sdk.Tx, req abci.RequestDeliverTx) (abci.ResponseDeliverTx, error) { + if err := tx.ValidateBasic(); err != nil { + return abci.ResponseDeliverTx{}, err + } + + if err := validateBasicTxMsgs(tx.GetMsgs()); err != nil { + return abci.ResponseDeliverTx{}, err + } + + return txh.next.DeliverTx(ctx, tx, req) +} + +// SimulateTx implements tx.Handler.SimulateTx. +func (txh validateBasicTxHandler) SimulateTx(ctx context.Context, sdkTx sdk.Tx, req tx.RequestSimulateTx) (tx.ResponseSimulateTx, error) { + if err := sdkTx.ValidateBasic(); err != nil { + return tx.ResponseSimulateTx{}, err + } + + if err := validateBasicTxMsgs(sdkTx.GetMsgs()); err != nil { + return tx.ResponseSimulateTx{}, err + } + + return txh.next.SimulateTx(ctx, sdkTx, req) +} + +var _ tx.Handler = txTimeoutHeightTxHandler{} + +type txTimeoutHeightTxHandler struct { + next tx.Handler +} + +// TxTimeoutHeightMiddleware defines a middleware that checks for a +// tx height timeout. +func TxTimeoutHeightMiddleware(txh tx.Handler) tx.Handler { + return txTimeoutHeightTxHandler{ + next: txh, + } +} + +func checkTimeout(ctx context.Context, tx sdk.Tx) error { + sdkCtx := sdk.UnwrapSDKContext(ctx) + timeoutTx, ok := tx.(sdk.TxWithTimeoutHeight) + if !ok { + return sdkerrors.Wrap(sdkerrors.ErrTxDecode, "expected tx to implement TxWithTimeoutHeight") + } + + timeoutHeight := timeoutTx.GetTimeoutHeight() + if timeoutHeight > 0 && uint64(sdkCtx.BlockHeight()) > timeoutHeight { + return sdkerrors.Wrapf( + sdkerrors.ErrTxTimeoutHeight, "block height: %d, timeout height: %d", sdkCtx.BlockHeight(), timeoutHeight, + ) + } + + return nil +} + +// CheckTx implements tx.Handler.CheckTx. +func (txh txTimeoutHeightTxHandler) CheckTx(ctx context.Context, tx sdk.Tx, req abci.RequestCheckTx) (abci.ResponseCheckTx, error) { + if err := checkTimeout(ctx, tx); err != nil { + return abci.ResponseCheckTx{}, err + } + + return txh.next.CheckTx(ctx, tx, req) +} + +// DeliverTx implements tx.Handler.DeliverTx. +func (txh txTimeoutHeightTxHandler) DeliverTx(ctx context.Context, tx sdk.Tx, req abci.RequestDeliverTx) (abci.ResponseDeliverTx, error) { + if err := checkTimeout(ctx, tx); err != nil { + return abci.ResponseDeliverTx{}, err + } + + return txh.next.DeliverTx(ctx, tx, req) +} + +// SimulateTx implements tx.Handler.SimulateTx. +func (txh txTimeoutHeightTxHandler) SimulateTx(ctx context.Context, sdkTx sdk.Tx, req tx.RequestSimulateTx) (tx.ResponseSimulateTx, error) { + if err := checkTimeout(ctx, sdkTx); err != nil { + return tx.ResponseSimulateTx{}, err + } + + return txh.next.SimulateTx(ctx, sdkTx, req) +} + +type validateMemoTxHandler struct { + ak AccountKeeper + next tx.Handler +} + +// ValidateMemoMiddleware will validate memo given the parameters passed in +// If memo is too large middleware returns with error, otherwise call next middleware +// CONTRACT: Tx must implement TxWithMemo interface +func ValidateMemoMiddleware(ak AccountKeeper) tx.Middleware { + return func(txHandler tx.Handler) tx.Handler { + return validateMemoTxHandler{ + ak: ak, + next: txHandler, + } + } +} + +var _ tx.Handler = validateMemoTxHandler{} + +func (vmm validateMemoTxHandler) checkForValidMemo(ctx context.Context, tx sdk.Tx) error { + sdkCtx := sdk.UnwrapSDKContext(ctx) + memoTx, ok := tx.(sdk.TxWithMemo) + if !ok { + return sdkerrors.Wrap(sdkerrors.ErrTxDecode, "invalid transaction type") + } + + params := vmm.ak.GetParams(sdkCtx) + + memoLength := len(memoTx.GetMemo()) + if uint64(memoLength) > params.MaxMemoCharacters { + return sdkerrors.Wrapf(sdkerrors.ErrMemoTooLarge, + "maximum number of characters is %d but received %d characters", + params.MaxMemoCharacters, memoLength, + ) + } + + return nil +} + +// CheckTx implements tx.Handler.CheckTx method. +func (vmm validateMemoTxHandler) CheckTx(ctx context.Context, tx sdk.Tx, req abci.RequestCheckTx) (abci.ResponseCheckTx, error) { + if err := vmm.checkForValidMemo(ctx, tx); err != nil { + return abci.ResponseCheckTx{}, err + } + + return vmm.next.CheckTx(ctx, tx, req) +} + +// DeliverTx implements tx.Handler.DeliverTx method. +func (vmm validateMemoTxHandler) DeliverTx(ctx context.Context, tx sdk.Tx, req abci.RequestDeliverTx) (abci.ResponseDeliverTx, error) { + if err := vmm.checkForValidMemo(ctx, tx); err != nil { + return abci.ResponseDeliverTx{}, err + } + + return vmm.next.DeliverTx(ctx, tx, req) +} + +// SimulateTx implements tx.Handler.SimulateTx method. +func (vmm validateMemoTxHandler) SimulateTx(ctx context.Context, sdkTx sdk.Tx, req tx.RequestSimulateTx) (tx.ResponseSimulateTx, error) { + if err := vmm.checkForValidMemo(ctx, sdkTx); err != nil { + return tx.ResponseSimulateTx{}, err + } + + return vmm.next.SimulateTx(ctx, sdkTx, req) +} + +var _ tx.Handler = consumeTxSizeGasTxHandler{} + +type consumeTxSizeGasTxHandler struct { + ak AccountKeeper + next tx.Handler +} + +// ConsumeTxSizeGasMiddleware will take in parameters and consume gas proportional +// to the size of tx before calling next middleware. 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 middleware, signatures of transaction must be represented +// as legacytx.StdSignature otherwise simulate mode will incorrectly estimate gas cost. +func ConsumeTxSizeGasMiddleware(ak AccountKeeper) tx.Middleware { + return func(txHandler tx.Handler) tx.Handler { + return consumeTxSizeGasTxHandler{ + ak: ak, + next: txHandler, + } + } +} + +func (cgts consumeTxSizeGasTxHandler) simulateSigGasCost(ctx context.Context, tx sdk.Tx) error { + sdkCtx := sdk.UnwrapSDKContext(ctx) + params := cgts.ak.GetParams(sdkCtx) + + sigTx, ok := tx.(authsigning.SigVerifiableTx) + if !ok { + return sdkerrors.Wrap(sdkerrors.ErrTxDecode, "invalid tx type") + } + + // in simulate mode, each element should be a nil signature + sigs, err := sigTx.GetSignaturesV2() + if err != nil { + return 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(sdkCtx, 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 + } + + sdkCtx.GasMeter().ConsumeGas(params.TxSizeCostPerByte*cost, "txSize") + } + + return nil +} + +func (cgts consumeTxSizeGasTxHandler) consumeTxSizeGas(ctx context.Context, tx sdk.Tx, txBytes []byte, simulate bool) error { + sdkCtx := sdk.UnwrapSDKContext(ctx) + params := cgts.ak.GetParams(sdkCtx) + sdkCtx.GasMeter().ConsumeGas(params.TxSizeCostPerByte*sdk.Gas(len(txBytes)), "txSize") + + return nil +} + +// CheckTx implements tx.Handler.CheckTx. +func (cgts consumeTxSizeGasTxHandler) CheckTx(ctx context.Context, tx sdk.Tx, req abci.RequestCheckTx) (abci.ResponseCheckTx, error) { + if err := cgts.consumeTxSizeGas(ctx, tx, req.GetTx(), false); err != nil { + return abci.ResponseCheckTx{}, err + } + + return cgts.next.CheckTx(ctx, tx, req) +} + +// DeliverTx implements tx.Handler.DeliverTx. +func (cgts consumeTxSizeGasTxHandler) DeliverTx(ctx context.Context, tx sdk.Tx, req abci.RequestDeliverTx) (abci.ResponseDeliverTx, error) { + if err := cgts.consumeTxSizeGas(ctx, tx, req.GetTx(), false); err != nil { + return abci.ResponseDeliverTx{}, err + } + + return cgts.next.DeliverTx(ctx, tx, req) +} + +// SimulateTx implements tx.Handler.SimulateTx. +func (cgts consumeTxSizeGasTxHandler) SimulateTx(ctx context.Context, sdkTx sdk.Tx, req tx.RequestSimulateTx) (tx.ResponseSimulateTx, error) { + if err := cgts.consumeTxSizeGas(ctx, sdkTx, req.TxBytes, true); err != nil { + return tx.ResponseSimulateTx{}, err + } + + if err := cgts.simulateSigGasCost(ctx, sdkTx); err != nil { + return tx.ResponseSimulateTx{}, err + } + + return cgts.next.SimulateTx(ctx, sdkTx, req) +} + +// 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 +} diff --git a/x/auth/middleware/basic_test.go b/x/auth/middleware/basic_test.go new file mode 100644 index 000000000000..8e1ad1db6a30 --- /dev/null +++ b/x/auth/middleware/basic_test.go @@ -0,0 +1,222 @@ +package middleware_test + +import ( + "strings" + + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + "github.com/cosmos/cosmos-sdk/crypto/types/multisig" + "github.com/cosmos/cosmos-sdk/testutil/testdata" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/tx" + "github.com/cosmos/cosmos-sdk/types/tx/signing" + "github.com/cosmos/cosmos-sdk/x/auth/middleware" + "github.com/tendermint/tendermint/abci/types" +) + +func (s *MWTestSuite) TestValidateBasic() { + ctx := s.SetupTest(true) // setup + txBuilder := s.clientCtx.TxConfig.NewTxBuilder() + + txHandler := middleware.ComposeMiddlewares(noopTxHandler{}, middleware.ValidateBasicMiddleware) + + // keys and addresses + priv1, _, addr1 := testdata.KeyTestPubAddr() + + // msg and signatures + msg := testdata.NewTestMsg(addr1) + feeAmount := testdata.NewTestFeeAmount() + gasLimit := testdata.NewTestGasLimit() + s.Require().NoError(txBuilder.SetMsgs(msg)) + txBuilder.SetFeeAmount(feeAmount) + txBuilder.SetGasLimit(gasLimit) + + privs, accNums, accSeqs := []cryptotypes.PrivKey{}, []uint64{}, []uint64{} + invalidTx, _, err := s.createTestTx(txBuilder, privs, accNums, accSeqs, ctx.ChainID()) + s.Require().NoError(err) + + _, err = txHandler.DeliverTx(sdk.WrapSDKContext(ctx), invalidTx, types.RequestDeliverTx{}) + s.Require().NotNil(err, "Did not error on invalid tx") + + privs, accNums, accSeqs = []cryptotypes.PrivKey{priv1}, []uint64{0}, []uint64{0} + validTx, _, err := s.createTestTx(txBuilder, privs, accNums, accSeqs, ctx.ChainID()) + s.Require().NoError(err) + + _, err = txHandler.DeliverTx(sdk.WrapSDKContext(ctx), validTx, types.RequestDeliverTx{}) + s.Require().Nil(err, "ValidateBasicMiddleware returned error on valid tx. err: %v", err) + + // test middleware skips on recheck + ctx = ctx.WithIsReCheckTx(true) + + // middleware should skip processing invalidTx on recheck and thus return nil-error + _, err = txHandler.DeliverTx(sdk.WrapSDKContext(ctx), invalidTx, types.RequestDeliverTx{}) + s.Require().Nil(err, "ValidateBasicMiddleware ran on ReCheck") +} + +func (s *MWTestSuite) TestValidateMemo() { + ctx := s.SetupTest(true) // setup + txBuilder := s.clientCtx.TxConfig.NewTxBuilder() + txHandler := middleware.ComposeMiddlewares(noopTxHandler{}, middleware.ValidateMemoMiddleware(s.app.AccountKeeper)) + + // keys and addresses + priv1, _, addr1 := testdata.KeyTestPubAddr() + + // msg and signatures + msg := testdata.NewTestMsg(addr1) + feeAmount := testdata.NewTestFeeAmount() + gasLimit := testdata.NewTestGasLimit() + s.Require().NoError(txBuilder.SetMsgs(msg)) + txBuilder.SetFeeAmount(feeAmount) + txBuilder.SetGasLimit(gasLimit) + + privs, accNums, accSeqs := []cryptotypes.PrivKey{priv1}, []uint64{0}, []uint64{0} + txBuilder.SetMemo(strings.Repeat("01234567890", 500)) + invalidTx, _, err := s.createTestTx(txBuilder, privs, accNums, accSeqs, ctx.ChainID()) + s.Require().NoError(err) + + // require that long memos get rejected + _, err = txHandler.DeliverTx(sdk.WrapSDKContext(ctx), invalidTx, types.RequestDeliverTx{}) + + s.Require().NotNil(err, "Did not error on tx with high memo") + + txBuilder.SetMemo(strings.Repeat("01234567890", 10)) + validTx, _, err := s.createTestTx(txBuilder, privs, accNums, accSeqs, ctx.ChainID()) + s.Require().NoError(err) + + // require small memos pass ValidateMemo middleware + _, err = txHandler.DeliverTx(sdk.WrapSDKContext(ctx), validTx, types.RequestDeliverTx{}) + s.Require().Nil(err, "ValidateBasicMiddleware returned error on valid tx. err: %v", err) +} + +func (s *MWTestSuite) TestConsumeGasForTxSize() { + ctx := s.SetupTest(true) // setup + txBuilder := s.clientCtx.TxConfig.NewTxBuilder() + + txHandler := middleware.ComposeMiddlewares(noopTxHandler{}, middleware.ConsumeTxSizeGasMiddleware(s.app.AccountKeeper)) + + // keys and addresses + priv1, _, addr1 := testdata.KeyTestPubAddr() + + // msg and signatures + msg := testdata.NewTestMsg(addr1) + feeAmount := testdata.NewTestFeeAmount() + gasLimit := testdata.NewTestGasLimit() + + 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 { + s.Run(tc.name, func() { + txBuilder = s.clientCtx.TxConfig.NewTxBuilder() + s.Require().NoError(txBuilder.SetMsgs(msg)) + txBuilder.SetFeeAmount(feeAmount) + txBuilder.SetGasLimit(gasLimit) + txBuilder.SetMemo(strings.Repeat("01234567890", 10)) + + privs, accNums, accSeqs := []cryptotypes.PrivKey{priv1}, []uint64{0}, []uint64{0} + testTx, _, err := s.createTestTx(txBuilder, privs, accNums, accSeqs, ctx.ChainID()) + s.Require().NoError(err) + + txBytes, err := s.clientCtx.TxConfig.TxJSONEncoder()(testTx) + s.Require().Nil(err, "Cannot marshal tx: %v", err) + + params := s.app.AccountKeeper.GetParams(ctx) + expectedGas := sdk.Gas(len(txBytes)) * params.TxSizeCostPerByte + + // Set ctx with TxBytes manually + ctx = ctx.WithTxBytes(txBytes) + + // track how much gas is necessary to retrieve parameters + beforeGas := ctx.GasMeter().GasConsumed() + s.app.AccountKeeper.GetParams(ctx) + afterGas := ctx.GasMeter().GasConsumed() + expectedGas += afterGas - beforeGas + + beforeGas = ctx.GasMeter().GasConsumed() + _, err = txHandler.DeliverTx(sdk.WrapSDKContext(ctx), testTx, types.RequestDeliverTx{Tx: txBytes}) + + s.Require().Nil(err, "ConsumeTxSizeGasMiddleware returned error: %v", err) + + // require that middleware consumes expected amount of gas + consumedGas := ctx.GasMeter().GasConsumed() - beforeGas + s.Require().Equal(expectedGas, consumedGas, "Middleware did not consume the correct amount of gas") + + // simulation must not underestimate gas of this middleware even with nil signatures + txBuilder, err := s.clientCtx.TxConfig.WrapTxBuilder(testTx) + s.Require().NoError(err) + s.Require().NoError(txBuilder.SetSignatures(tc.sigV2)) + testTx = txBuilder.GetTx() + + simTxBytes, err := s.clientCtx.TxConfig.TxJSONEncoder()(testTx) + s.Require().Nil(err, "Cannot marshal tx: %v", err) + // require that simulated tx is smaller than tx with signatures + s.Require().True(len(simTxBytes) < len(txBytes), "simulated tx still has signatures") + + // Set s.ctx with smaller simulated TxBytes manually + ctx = ctx.WithTxBytes(simTxBytes) + + beforeSimGas := ctx.GasMeter().GasConsumed() + + // run txhandler in simulate mode + _, err = txHandler.SimulateTx(sdk.WrapSDKContext(ctx), testTx, tx.RequestSimulateTx{TxBytes: simTxBytes}) + consumedSimGas := ctx.GasMeter().GasConsumed() - beforeSimGas + + // require that txhandler passes and does not underestimate middleware cost + s.Require().Nil(err, "ConsumeTxSizeGasMiddleware returned error: %v", err) + s.Require().True(consumedSimGas >= expectedGas, "Simulate mode underestimates gas on Middleware. Simulated cost: %d, expected cost: %d", consumedSimGas, expectedGas) + }) + } +} + +func (s *MWTestSuite) TestTxHeightTimeoutMiddleware() { + ctx := s.SetupTest(true) + + txHandler := middleware.ComposeMiddlewares(noopTxHandler{}, middleware.TxTimeoutHeightMiddleware) + + // keys and addresses + priv1, _, addr1 := testdata.KeyTestPubAddr() + + // msg and signatures + msg := testdata.NewTestMsg(addr1) + feeAmount := testdata.NewTestFeeAmount() + gasLimit := testdata.NewTestGasLimit() + + testCases := []struct { + name string + timeout uint64 + height int64 + expectErr bool + }{ + {"default value", 0, 10, false}, + {"no timeout (greater height)", 15, 10, false}, + {"no timeout (same height)", 10, 10, false}, + {"timeout (smaller height)", 9, 10, true}, + } + + for _, tc := range testCases { + tc := tc + + s.Run(tc.name, func() { + txBuilder := s.clientCtx.TxConfig.NewTxBuilder() + + s.Require().NoError(txBuilder.SetMsgs(msg)) + + txBuilder.SetFeeAmount(feeAmount) + txBuilder.SetGasLimit(gasLimit) + txBuilder.SetMemo(strings.Repeat("01234567890", 10)) + txBuilder.SetTimeoutHeight(tc.timeout) + + privs, accNums, accSeqs := []cryptotypes.PrivKey{priv1}, []uint64{0}, []uint64{0} + testTx, _, err := s.createTestTx(txBuilder, privs, accNums, accSeqs, ctx.ChainID()) + s.Require().NoError(err) + + ctx := ctx.WithBlockHeight(tc.height) + _, err = txHandler.SimulateTx(sdk.WrapSDKContext(ctx), testTx, tx.RequestSimulateTx{}) + s.Require().Equal(tc.expectErr, err != nil, err) + }) + } +} diff --git a/x/auth/ante/expected_keepers.go b/x/auth/middleware/expected_keepers.go similarity index 85% rename from x/auth/ante/expected_keepers.go rename to x/auth/middleware/expected_keepers.go index 4dbbbd21c713..33bb6339c1f3 100644 --- a/x/auth/ante/expected_keepers.go +++ b/x/auth/middleware/expected_keepers.go @@ -1,4 +1,4 @@ -package ante +package middleware import ( sdk "github.com/cosmos/cosmos-sdk/types" @@ -6,7 +6,7 @@ import ( ) // AccountKeeper defines the contract needed for AccountKeeper related APIs. -// Interface provides support to use non-sdk AccountKeeper for AnteHandler's decorators. +// Interface provides support to use non-sdk AccountKeeper for TxHandler's middlewares. type AccountKeeper interface { GetParams(ctx sdk.Context) (params types.Params) GetAccount(ctx sdk.Context, addr sdk.AccAddress) types.AccountI diff --git a/x/auth/middleware/ext.go b/x/auth/middleware/ext.go new file mode 100644 index 000000000000..3fec1f674a47 --- /dev/null +++ b/x/auth/middleware/ext.go @@ -0,0 +1,71 @@ +package middleware + +import ( + "context" + + abci "github.com/tendermint/tendermint/abci/types" + + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + "github.com/cosmos/cosmos-sdk/types/tx" +) + +type HasExtensionOptionsTx interface { + GetExtensionOptions() []*codectypes.Any + GetNonCriticalExtensionOptions() []*codectypes.Any +} + +type rejectExtensionOptionsTxHandler struct { + next tx.Handler +} + +// RejectExtensionOptionsMiddleware creates a new rejectExtensionOptionsMiddleware. +// rejectExtensionOptionsMiddleware is a middleware that rejects all extension +// options which can optionally be included in protobuf transactions. Users that +// need extension options should create a custom middleware chain that handles +// needed extension options properly and rejects unknown ones. +func RejectExtensionOptionsMiddleware(txh tx.Handler) tx.Handler { + return rejectExtensionOptionsTxHandler{ + next: txh, + } +} + +var _ tx.Handler = rejectExtensionOptionsTxHandler{} + +func checkExtOpts(tx sdk.Tx) error { + if hasExtOptsTx, ok := tx.(HasExtensionOptionsTx); ok { + if len(hasExtOptsTx.GetExtensionOptions()) != 0 { + return sdkerrors.ErrUnknownExtensionOptions + } + } + + return nil +} + +// CheckTx implements tx.Handler.CheckTx. +func (txh rejectExtensionOptionsTxHandler) CheckTx(ctx context.Context, tx sdk.Tx, req abci.RequestCheckTx) (abci.ResponseCheckTx, error) { + if err := checkExtOpts(tx); err != nil { + return abci.ResponseCheckTx{}, err + } + + return txh.next.CheckTx(ctx, tx, req) +} + +// DeliverTx implements tx.Handler.DeliverTx. +func (txh rejectExtensionOptionsTxHandler) DeliverTx(ctx context.Context, tx sdk.Tx, req abci.RequestDeliverTx) (abci.ResponseDeliverTx, error) { + if err := checkExtOpts(tx); err != nil { + return abci.ResponseDeliverTx{}, err + } + + return txh.next.DeliverTx(ctx, tx, req) +} + +// SimulateTx implements tx.Handler.SimulateTx method. +func (txh rejectExtensionOptionsTxHandler) SimulateTx(ctx context.Context, sdkTx sdk.Tx, req tx.RequestSimulateTx) (tx.ResponseSimulateTx, error) { + if err := checkExtOpts(sdkTx); err != nil { + return tx.ResponseSimulateTx{}, err + } + + return txh.next.SimulateTx(ctx, sdkTx, req) +} diff --git a/x/auth/middleware/ext_test.go b/x/auth/middleware/ext_test.go new file mode 100644 index 000000000000..a2fd323e3bb8 --- /dev/null +++ b/x/auth/middleware/ext_test.go @@ -0,0 +1,36 @@ +package middleware_test + +import ( + "github.com/cosmos/cosmos-sdk/codec/types" + "github.com/cosmos/cosmos-sdk/testutil/testdata" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth/middleware" + "github.com/cosmos/cosmos-sdk/x/auth/tx" + abci "github.com/tendermint/tendermint/abci/types" +) + +func (s *MWTestSuite) TestRejectExtensionOptionsMiddleware() { + ctx := s.SetupTest(true) // setup + txBuilder := s.clientCtx.TxConfig.NewTxBuilder() + + txHandler := middleware.ComposeMiddlewares(noopTxHandler{}, middleware.RejectExtensionOptionsMiddleware) + + // no extension options should not trigger an error + theTx := txBuilder.GetTx() + _, err := txHandler.CheckTx(sdk.WrapSDKContext(ctx), theTx, abci.RequestCheckTx{}) + s.Require().NoError(err) + + extOptsTxBldr, ok := txBuilder.(tx.ExtensionOptionsTxBuilder) + if !ok { + // if we can't set extension options, this middleware doesn't apply and we're done + return + } + + // setting any extension option should cause an error + any, err := types.NewAnyWithValue(testdata.NewTestMsg()) + s.Require().NoError(err) + extOptsTxBldr.SetExtensionOptions(any) + theTx = txBuilder.GetTx() + _, err = txHandler.CheckTx(sdk.WrapSDKContext(ctx), theTx, abci.RequestCheckTx{}) + s.Require().EqualError(err, "unknown extension options") +} diff --git a/x/auth/middleware/fee.go b/x/auth/middleware/fee.go new file mode 100644 index 000000000000..7285d530cfb2 --- /dev/null +++ b/x/auth/middleware/fee.go @@ -0,0 +1,194 @@ +package middleware + +import ( + "context" + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + + "github.com/cosmos/cosmos-sdk/types/tx" + "github.com/cosmos/cosmos-sdk/x/auth/types" + abci "github.com/tendermint/tendermint/abci/types" +) + +var _ tx.Handler = mempoolFeeTxHandler{} + +type mempoolFeeTxHandler struct { + next tx.Handler +} + +// MempoolFeeMiddleware will check if the transaction's fee is at least as large +// as the local validator's minimum gasFee (defined in validator config). +// If fee is too low, middleware returns error and tx is rejected from mempool. +// Note this only applies when ctx.CheckTx = true +// If fee is high enough or not CheckTx, then call next middleware +// CONTRACT: Tx must implement FeeTx to use MempoolFeeMiddleware +func MempoolFeeMiddleware(txh tx.Handler) tx.Handler { + return mempoolFeeTxHandler{ + next: txh, + } +} + +// CheckTx implements tx.Handler.CheckTx. +func (txh mempoolFeeTxHandler) CheckTx(ctx context.Context, tx sdk.Tx, req abci.RequestCheckTx) (abci.ResponseCheckTx, error) { + sdkCtx := sdk.UnwrapSDKContext(ctx) + + feeTx, ok := tx.(sdk.FeeTx) + if !ok { + return abci.ResponseCheckTx{}, 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. + minGasPrices := sdkCtx.MinGasPrices() + if !minGasPrices.IsZero() { + requiredFees := make(sdk.Coins, len(minGasPrices)) + + // Determine the required fees by multiplying each required minimum gas + // price by the gas limit, where fee = ceil(minGasPrice * gasLimit). + glDec := sdk.NewDec(int64(gas)) + for i, gp := range minGasPrices { + fee := gp.Amount.Mul(glDec) + requiredFees[i] = sdk.NewCoin(gp.Denom, fee.Ceil().RoundInt()) + } + + if !feeCoins.IsAnyGTE(requiredFees) { + return abci.ResponseCheckTx{}, sdkerrors.Wrapf(sdkerrors.ErrInsufficientFee, "insufficient fees; got: %s required: %s", feeCoins, requiredFees) + } + } + + return txh.next.CheckTx(ctx, tx, req) +} + +// DeliverTx implements tx.Handler.DeliverTx. +func (txh mempoolFeeTxHandler) DeliverTx(ctx context.Context, tx sdk.Tx, req abci.RequestDeliverTx) (abci.ResponseDeliverTx, error) { + return txh.next.DeliverTx(ctx, tx, req) +} + +// SimulateTx implements tx.Handler.SimulateTx. +func (txh mempoolFeeTxHandler) SimulateTx(ctx context.Context, tx sdk.Tx, req tx.RequestSimulateTx) (tx.ResponseSimulateTx, error) { + return txh.next.SimulateTx(ctx, tx, req) +} + +var _ tx.Handler = deductFeeTxHandler{} + +type deductFeeTxHandler struct { + accountKeeper AccountKeeper + bankKeeper types.BankKeeper + feegrantKeeper FeegrantKeeper + next tx.Handler +} + +// DeductFeeMiddleware deducts fees from the first signer of the tx +// If the first signer does not have the funds to pay for the fees, return with InsufficientFunds error +// Call next middleware if fees successfully deducted +// CONTRACT: Tx must implement FeeTx interface to use deductFeeTxHandler +func DeductFeeMiddleware(ak AccountKeeper, bk types.BankKeeper, fk FeegrantKeeper) tx.Middleware { + return func(txh tx.Handler) tx.Handler { + return deductFeeTxHandler{ + accountKeeper: ak, + bankKeeper: bk, + feegrantKeeper: fk, + next: txh, + } + } +} + +func (dfd deductFeeTxHandler) checkDeductFee(ctx context.Context, tx sdk.Tx) error { + sdkCtx := sdk.UnwrapSDKContext(ctx) + feeTx, ok := tx.(sdk.FeeTx) + if !ok { + return sdkerrors.Wrap(sdkerrors.ErrTxDecode, "Tx must be a FeeTx") + } + + if addr := dfd.accountKeeper.GetModuleAddress(types.FeeCollectorName); addr == nil { + panic(fmt.Sprintf("%s module account has not been set", types.FeeCollectorName)) + } + + fee := feeTx.GetFee() + feePayer := feeTx.FeePayer() + feeGranter := feeTx.FeeGranter() + + deductFeesFrom := feePayer + + // if feegranter set deduct fee from feegranter account. + // this works with only when feegrant enabled. + if feeGranter != nil { + if dfd.feegrantKeeper == nil { + return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "fee grants are not enabled") + } else if !feeGranter.Equals(feePayer) { + err := dfd.feegrantKeeper.UseGrantedFees(sdkCtx, feeGranter, feePayer, fee, tx.GetMsgs()) + + if err != nil { + return sdkerrors.Wrapf(err, "%s not allowed to pay fees from %s", feeGranter, feePayer) + } + } + + deductFeesFrom = feeGranter + } + + deductFeesFromAcc := dfd.accountKeeper.GetAccount(sdkCtx, deductFeesFrom) + if deductFeesFromAcc == nil { + return sdkerrors.Wrapf(sdkerrors.ErrUnknownAddress, "fee payer address: %s does not exist", deductFeesFrom) + } + + // deduct the fees + if !feeTx.GetFee().IsZero() { + err := DeductFees(dfd.bankKeeper, sdkCtx, deductFeesFromAcc, feeTx.GetFee()) + if err != nil { + return err + } + } + + events := sdk.Events{sdk.NewEvent(sdk.EventTypeTx, + sdk.NewAttribute(sdk.AttributeKeyFee, feeTx.GetFee().String()), + )} + sdkCtx.EventManager().EmitEvents(events) + + return nil +} + +// CheckTx implements tx.Handler.CheckTx. +func (dfd deductFeeTxHandler) CheckTx(ctx context.Context, tx sdk.Tx, req abci.RequestCheckTx) (abci.ResponseCheckTx, error) { + if err := dfd.checkDeductFee(ctx, tx); err != nil { + return abci.ResponseCheckTx{}, err + } + + return dfd.next.CheckTx(ctx, tx, req) +} + +// DeliverTx implements tx.Handler.DeliverTx. +func (dfd deductFeeTxHandler) DeliverTx(ctx context.Context, tx sdk.Tx, req abci.RequestDeliverTx) (abci.ResponseDeliverTx, error) { + if err := dfd.checkDeductFee(ctx, tx); err != nil { + return abci.ResponseDeliverTx{}, err + } + + return dfd.next.DeliverTx(ctx, tx, req) +} + +func (dfd deductFeeTxHandler) SimulateTx(ctx context.Context, sdkTx sdk.Tx, req tx.RequestSimulateTx) (tx.ResponseSimulateTx, error) { + if err := dfd.checkDeductFee(ctx, sdkTx); err != nil { + return tx.ResponseSimulateTx{}, err + } + + return dfd.next.SimulateTx(ctx, sdkTx, req) +} + +// 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/x/auth/middleware/fee_test.go b/x/auth/middleware/fee_test.go new file mode 100644 index 000000000000..219d790d0d61 --- /dev/null +++ b/x/auth/middleware/fee_test.go @@ -0,0 +1,99 @@ +package middleware_test + +import ( + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + "github.com/cosmos/cosmos-sdk/testutil/testdata" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth/middleware" + "github.com/cosmos/cosmos-sdk/x/bank/testutil" + abci "github.com/tendermint/tendermint/abci/types" +) + +func (s *MWTestSuite) TestEnsureMempoolFees() { + ctx := s.SetupTest(true) // setup + txBuilder := s.clientCtx.TxConfig.NewTxBuilder() + + txHandler := middleware.ComposeMiddlewares(noopTxHandler{}, middleware.MempoolFeeMiddleware) + + // keys and addresses + priv1, _, addr1 := testdata.KeyTestPubAddr() + + // msg and signatures + msg := testdata.NewTestMsg(addr1) + feeAmount := testdata.NewTestFeeAmount() + gasLimit := testdata.NewTestGasLimit() + s.Require().NoError(txBuilder.SetMsgs(msg)) + txBuilder.SetFeeAmount(feeAmount) + txBuilder.SetGasLimit(gasLimit) + + privs, accNums, accSeqs := []cryptotypes.PrivKey{priv1}, []uint64{0}, []uint64{0} + tx, _, err := s.createTestTx(txBuilder, privs, accNums, accSeqs, ctx.ChainID()) + s.Require().NoError(err) + + // Set high gas price so standard test fee fails + atomPrice := sdk.NewDecCoinFromDec("atom", sdk.NewDec(200).Quo(sdk.NewDec(100000))) + highGasPrice := []sdk.DecCoin{atomPrice} + ctx = ctx.WithMinGasPrices(highGasPrice) + + // txHandler errors with insufficient fees + _, err = txHandler.CheckTx(sdk.WrapSDKContext(ctx), tx, abci.RequestCheckTx{}) + s.Require().NotNil(err, "Middleware should have errored on too low fee for local gasPrice") + + // txHandler should not error since we do not check minGasPrice in DeliverTx + _, err = txHandler.DeliverTx(sdk.WrapSDKContext(ctx), tx, abci.RequestDeliverTx{}) + s.Require().Nil(err, "MempoolFeeMiddleware returned error in DeliverTx") + + atomPrice = sdk.NewDecCoinFromDec("atom", sdk.NewDec(0).Quo(sdk.NewDec(100000))) + lowGasPrice := []sdk.DecCoin{atomPrice} + ctx = ctx.WithMinGasPrices(lowGasPrice) + + _, err = txHandler.CheckTx(sdk.WrapSDKContext(ctx), tx, abci.RequestCheckTx{}) + s.Require().Nil(err, "Middleware should not have errored on fee higher than local gasPrice") +} + +func (s *MWTestSuite) TestDeductFees() { + ctx := s.SetupTest(false) // setup + txBuilder := s.clientCtx.TxConfig.NewTxBuilder() + txHandler := middleware.ComposeMiddlewares( + noopTxHandler{}, + middleware.DeductFeeMiddleware( + s.app.AccountKeeper, + s.app.BankKeeper, + s.app.FeeGrantKeeper, + ), + ) + + // keys and addresses + priv1, _, addr1 := testdata.KeyTestPubAddr() + + // msg and signatures + msg := testdata.NewTestMsg(addr1) + feeAmount := testdata.NewTestFeeAmount() + gasLimit := testdata.NewTestGasLimit() + s.Require().NoError(txBuilder.SetMsgs(msg)) + txBuilder.SetFeeAmount(feeAmount) + txBuilder.SetGasLimit(gasLimit) + + privs, accNums, accSeqs := []cryptotypes.PrivKey{priv1}, []uint64{0}, []uint64{0} + tx, _, err := s.createTestTx(txBuilder, privs, accNums, accSeqs, ctx.ChainID()) + s.Require().NoError(err) + + // Set account with insufficient funds + acc := s.app.AccountKeeper.NewAccountWithAddress(ctx, addr1) + s.app.AccountKeeper.SetAccount(ctx, acc) + coins := sdk.NewCoins(sdk.NewCoin("atom", sdk.NewInt(10))) + err = testutil.FundAccount(s.app.BankKeeper, ctx, addr1, coins) + s.Require().NoError(err) + + _, err = txHandler.DeliverTx(sdk.WrapSDKContext(ctx), tx, abci.RequestDeliverTx{}) + s.Require().NotNil(err, "Tx did not error when fee payer had insufficient funds") + + // Set account with sufficient funds + s.app.AccountKeeper.SetAccount(ctx, acc) + err = testutil.FundAccount(s.app.BankKeeper, ctx, addr1, sdk.NewCoins(sdk.NewCoin("atom", sdk.NewInt(200)))) + s.Require().NoError(err) + + _, err = txHandler.DeliverTx(sdk.WrapSDKContext(ctx), tx, abci.RequestDeliverTx{}) + + s.Require().Nil(err, "Tx errored after account has been set with sufficient funds") +} diff --git a/x/auth/ante/feegrant_test.go b/x/auth/middleware/feegrant_test.go similarity index 82% rename from x/auth/ante/feegrant_test.go rename to x/auth/middleware/feegrant_test.go index 7c03e3dbefd6..d45b44160496 100644 --- a/x/auth/ante/feegrant_test.go +++ b/x/auth/middleware/feegrant_test.go @@ -1,10 +1,11 @@ -package ante_test +package middleware_test import ( "math/rand" "testing" "time" + abci "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/crypto" "github.com/cosmos/cosmos-sdk/client" @@ -15,7 +16,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/simulation" "github.com/cosmos/cosmos-sdk/types/tx/signing" - "github.com/cosmos/cosmos-sdk/x/auth/ante" + "github.com/cosmos/cosmos-sdk/x/auth/middleware" 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" @@ -23,19 +24,20 @@ import ( "github.com/cosmos/cosmos-sdk/x/feegrant" ) -func (suite *AnteTestSuite) TestDeductFeesNoDelegation() { - suite.SetupTest(false) - // setup - app, ctx := suite.app, suite.ctx +func (s *MWTestSuite) TestDeductFeesNoDelegation() { + ctx := s.SetupTest(false) // setup + app := s.app protoTxCfg := tx.NewTxConfig(codec.NewProtoCodec(app.InterfaceRegistry()), tx.DefaultSignModes) - // this just tests our handler - dfd := ante.NewDeductFeeDecorator(app.AccountKeeper, app.BankKeeper, app.FeeGrantKeeper) - feeAnteHandler := sdk.ChainAnteDecorators(dfd) - - // this tests the whole stack - anteHandlerStack := suite.anteHandler + txHandler := middleware.ComposeMiddlewares( + noopTxHandler{}, + middleware.DeductFeeMiddleware( + s.app.AccountKeeper, + s.app.BankKeeper, + s.app.FeeGrantKeeper, + ), + ) // keys and addresses priv1, _, addr1 := testdata.KeyTestPubAddr() @@ -45,24 +47,24 @@ func (suite *AnteTestSuite) TestDeductFeesNoDelegation() { priv5, _, addr5 := testdata.KeyTestPubAddr() // Set addr1 with insufficient funds - err := testutil.FundAccount(suite.app.BankKeeper, suite.ctx, addr1, []sdk.Coin{sdk.NewCoin("atom", sdk.NewInt(10))}) - suite.Require().NoError(err) + err := testutil.FundAccount(s.app.BankKeeper, ctx, addr1, []sdk.Coin{sdk.NewCoin("atom", sdk.NewInt(10))}) + s.Require().NoError(err) // Set addr2 with more funds - err = testutil.FundAccount(suite.app.BankKeeper, suite.ctx, addr2, []sdk.Coin{sdk.NewCoin("atom", sdk.NewInt(99999))}) - suite.Require().NoError(err) + err = testutil.FundAccount(s.app.BankKeeper, ctx, addr2, []sdk.Coin{sdk.NewCoin("atom", sdk.NewInt(99999))}) + s.Require().NoError(err) // grant fee allowance from `addr2` to `addr3` (plenty to pay) err = app.FeeGrantKeeper.GrantAllowance(ctx, addr2, addr3, &feegrant.BasicAllowance{ SpendLimit: sdk.NewCoins(sdk.NewInt64Coin("atom", 500)), }) - suite.Require().NoError(err) + s.Require().NoError(err) // grant low fee allowance (20atom), to check the tx requesting more than allowed. err = app.FeeGrantKeeper.GrantAllowance(ctx, addr2, addr4, &feegrant.BasicAllowance{ SpendLimit: sdk.NewCoins(sdk.NewInt64Coin("atom", 20)), }) - suite.Require().NoError(err) + s.Require().NoError(err) cases := map[string]struct { signerKey cryptotypes.PrivKey @@ -133,7 +135,7 @@ func (suite *AnteTestSuite) TestDeductFeesNoDelegation() { for name, stc := range cases { tc := stc // to make scopelint happy - suite.T().Run(name, func(t *testing.T) { + s.T().Run(name, func(t *testing.T) { fee := sdk.NewCoins(sdk.NewInt64Coin("atom", tc.fee)) msgs := []sdk.Msg{testdata.NewTestMsg(tc.signer)} @@ -144,19 +146,22 @@ func (suite *AnteTestSuite) TestDeductFeesNoDelegation() { } tx, err := genTxWithFeeGranter(protoTxCfg, msgs, fee, helpers.DefaultGenTxGas, ctx.ChainID(), accNums, seqs, tc.feeAccount, privs...) - suite.Require().NoError(err) - _, err = feeAnteHandler(ctx, tx, false) // tests only feegrant ante + s.Require().NoError(err) + + // tests only feegrant middleware + _, err = txHandler.DeliverTx(sdk.WrapSDKContext(ctx), tx, abci.RequestDeliverTx{}) if tc.valid { - suite.Require().NoError(err) + s.Require().NoError(err) } else { - suite.Require().Error(err) + s.Require().Error(err) } - _, err = anteHandlerStack(ctx, tx, false) // tests while stack + // tests while stack + _, err = s.txHandler.DeliverTx(sdk.WrapSDKContext(ctx), tx, abci.RequestDeliverTx{}) if tc.valid { - suite.Require().NoError(err) + s.Require().NoError(err) } else { - suite.Require().Error(err) + s.Require().Error(err) } }) } diff --git a/x/auth/middleware/gas_test.go b/x/auth/middleware/gas_test.go index 287160b10c3d..30534ad7e8ad 100644 --- a/x/auth/middleware/gas_test.go +++ b/x/auth/middleware/gas_test.go @@ -70,7 +70,7 @@ func (s *MWTestSuite) TestSetup() { if tc.expErr { s.Require().EqualError(err, tc.errorStr) } else { - s.Require().Nil(err, "SetUpContextDecorator returned error") + s.Require().Nil(err, "SetUpContextMiddleware returned error") s.Require().Equal(tc.expGasLimit, uint64(res.GasWanted)) } }) diff --git a/x/auth/middleware/legacy_ante.go b/x/auth/middleware/legacy_ante.go deleted file mode 100644 index 14f682082994..000000000000 --- a/x/auth/middleware/legacy_ante.go +++ /dev/null @@ -1,115 +0,0 @@ -package middleware - -import ( - "context" - - abci "github.com/tendermint/tendermint/abci/types" - - sdk "github.com/cosmos/cosmos-sdk/types" - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" - "github.com/cosmos/cosmos-sdk/types/tx" -) - -type legacyAnteTxHandler struct { - anteHandler sdk.AnteHandler - inner tx.Handler -} - -func newLegacyAnteMiddleware(anteHandler sdk.AnteHandler) tx.Middleware { - return func(txHandler tx.Handler) tx.Handler { - return legacyAnteTxHandler{ - anteHandler: anteHandler, - inner: txHandler, - } - } -} - -var _ tx.Handler = legacyAnteTxHandler{} - -// CheckTx implements tx.Handler.CheckTx method. -func (txh legacyAnteTxHandler) CheckTx(ctx context.Context, tx sdk.Tx, req abci.RequestCheckTx) (abci.ResponseCheckTx, error) { - sdkCtx, err := txh.runAnte(ctx, tx, req.Tx, false) - if err != nil { - return abci.ResponseCheckTx{}, err - } - - return txh.inner.CheckTx(sdk.WrapSDKContext(sdkCtx), tx, req) -} - -// DeliverTx implements tx.Handler.DeliverTx method. -func (txh legacyAnteTxHandler) DeliverTx(ctx context.Context, tx sdk.Tx, req abci.RequestDeliverTx) (abci.ResponseDeliverTx, error) { - sdkCtx, err := txh.runAnte(ctx, tx, req.Tx, false) - if err != nil { - return abci.ResponseDeliverTx{}, err - } - - return txh.inner.DeliverTx(sdk.WrapSDKContext(sdkCtx), tx, req) -} - -// SimulateTx implements tx.Handler.SimulateTx method. -func (txh legacyAnteTxHandler) SimulateTx(ctx context.Context, sdkTx sdk.Tx, req tx.RequestSimulateTx) (tx.ResponseSimulateTx, error) { - sdkCtx, err := txh.runAnte(ctx, sdkTx, req.TxBytes, true) - if err != nil { - return tx.ResponseSimulateTx{}, err - } - - return txh.inner.SimulateTx(sdk.WrapSDKContext(sdkCtx), sdkTx, req) -} - -func (txh legacyAnteTxHandler) runAnte(ctx context.Context, tx sdk.Tx, txBytes []byte, isSimulate bool) (sdk.Context, error) { - err := validateBasicTxMsgs(tx.GetMsgs()) - if err != nil { - return sdk.Context{}, err - } - - sdkCtx := sdk.UnwrapSDKContext(ctx) - if txh.anteHandler == nil { - return sdkCtx, nil - } - - ms := sdkCtx.MultiStore() - - // Branch context before AnteHandler call in case it aborts. - // This is required for both CheckTx and DeliverTx. - // Ref: https://github.com/cosmos/cosmos-sdk/issues/2772 - // - // NOTE: Alternatively, we could require that AnteHandler ensures that - // writes do not happen if aborted/failed. This may have some - // performance benefits, but it'll be more difficult to get right. - anteCtx, msCache := cacheTxContext(sdkCtx, txBytes) - anteCtx = anteCtx.WithEventManager(sdk.NewEventManager()) - newCtx, err := txh.anteHandler(anteCtx, tx, isSimulate) - if err != nil { - return sdk.Context{}, err - } - - if !newCtx.IsZero() { - // At this point, newCtx.MultiStore() is a store branch, or something else - // replaced by the AnteHandler. We want the original multistore. - // - // Also, in the case of the tx aborting, we need to track gas consumed via - // the instantiated gas meter in the AnteHandler, so we update the context - // prior to returning. - sdkCtx = newCtx.WithMultiStore(ms) - } - - msCache.Write() - - return sdkCtx, nil -} - -// validateBasicTxMsgs executes basic validator calls for messages. -func validateBasicTxMsgs(msgs []sdk.Msg) error { - if len(msgs) == 0 { - return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "must contain at least one message") - } - - for _, msg := range msgs { - err := msg.ValidateBasic() - if err != nil { - return err - } - } - - return nil -} diff --git a/x/auth/middleware/middleware.go b/x/auth/middleware/middleware.go index 80ad606cd219..60820bb46cc5 100644 --- a/x/auth/middleware/middleware.go +++ b/x/auth/middleware/middleware.go @@ -2,7 +2,11 @@ package middleware import ( sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/cosmos/cosmos-sdk/types/tx" + "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" ) // ComposeMiddlewares compose multiple middlewares on top of a tx.Handler. The @@ -35,12 +39,33 @@ type TxHandlerOptions struct { LegacyRouter sdk.Router MsgServiceRouter *MsgServiceRouter - LegacyAnteHandler sdk.AnteHandler + AccountKeeper AccountKeeper + BankKeeper types.BankKeeper + FeegrantKeeper FeegrantKeeper + SignModeHandler authsigning.SignModeHandler + SigGasConsumer func(meter sdk.GasMeter, sig signing.SignatureV2, params types.Params) error } // NewDefaultTxHandler defines a TxHandler middleware stacks that should work // for most applications. func NewDefaultTxHandler(options TxHandlerOptions) (tx.Handler, error) { + if options.AccountKeeper == nil { + return nil, sdkerrors.Wrap(sdkerrors.ErrLogic, "account keeper is required for compose middlewares") + } + + if options.BankKeeper == nil { + return nil, sdkerrors.Wrap(sdkerrors.ErrLogic, "bank keeper is required for compose middlewares") + } + + if options.SignModeHandler == nil { + return nil, sdkerrors.Wrap(sdkerrors.ErrLogic, "sign mode handler is required for compose middlewares") + } + + var sigGasConsumer = options.SigGasConsumer + if sigGasConsumer == nil { + sigGasConsumer = DefaultSigVerificationGasConsumer + } + return ComposeMiddlewares( NewRunMsgsTxHandler(options.MsgServiceRouter, options.LegacyRouter), // Set a new GasMeter on sdk.Context. @@ -55,8 +80,19 @@ func NewDefaultTxHandler(options TxHandlerOptions) (tx.Handler, error) { // Choose which events to index in Tendermint. Make sure no events are // emitted outside of this middleware. NewIndexEventsTxMiddleware(options.IndexEvents), - // Temporary middleware to bundle antehandlers. - // TODO Remove in https://github.com/cosmos/cosmos-sdk/issues/9585. - newLegacyAnteMiddleware(options.LegacyAnteHandler), + // Reject all extension options which can optionally be included in the + // tx. + RejectExtensionOptionsMiddleware, + MempoolFeeMiddleware, + ValidateBasicMiddleware, + TxTimeoutHeightMiddleware, + ValidateMemoMiddleware(options.AccountKeeper), + ConsumeTxSizeGasMiddleware(options.AccountKeeper), + DeductFeeMiddleware(options.AccountKeeper, options.BankKeeper, options.FeegrantKeeper), + SetPubKeyMiddleware(options.AccountKeeper), + ValidateSigCountMiddleware(options.AccountKeeper), + SigGasConsumeMiddleware(options.AccountKeeper, sigGasConsumer), + SigVerificationMiddleware(options.AccountKeeper, options.SignModeHandler), + IncrementSequenceMiddleware(options.AccountKeeper), ), nil } diff --git a/x/auth/ante/ante_test.go b/x/auth/middleware/middleware_test.go similarity index 67% rename from x/auth/ante/ante_test.go rename to x/auth/middleware/middleware_test.go index 3ce43a5a17e3..e3b04983b6f5 100644 --- a/x/auth/ante/ante_test.go +++ b/x/auth/middleware/middleware_test.go @@ -1,4 +1,4 @@ -package ante_test +package middleware_test import ( "encoding/json" @@ -7,8 +7,6 @@ import ( "strings" "testing" - "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" @@ -17,18 +15,21 @@ import ( 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/cosmos/cosmos-sdk/x/auth/middleware" "github.com/cosmos/cosmos-sdk/x/auth/types" "github.com/cosmos/cosmos-sdk/x/bank/testutil" minttypes "github.com/cosmos/cosmos-sdk/x/mint/types" + "github.com/stretchr/testify/require" + abci "github.com/tendermint/tendermint/abci/types" ) // Test that simulate transaction accurately estimates gas cost -func (suite *AnteTestSuite) TestSimulateGasCost() { - suite.SetupTest(false) // reset +func (s *MWTestSuite) TestSimulateGasCost() { + ctx := s.SetupTest(false) // reset + txBuilder := s.clientCtx.TxConfig.NewTxBuilder() // Same data for every test cases - accounts := suite.CreateTestAccounts(3) + accounts := s.createTestAccounts(ctx, 3) msgs := []sdk.Msg{ testdata.NewTestMsg(accounts[0].acc.GetAddress(), accounts[1].acc.GetAddress()), testdata.NewTestMsg(accounts[2].acc.GetAddress(), accounts[0].acc.GetAddress()), @@ -44,8 +45,8 @@ func (suite *AnteTestSuite) TestSimulateGasCost() { { "tx with 150atom fee", func() { - suite.txBuilder.SetFeeAmount(feeAmount) - suite.txBuilder.SetGasLimit(gasLimit) + txBuilder.SetFeeAmount(feeAmount) + txBuilder.SetGasLimit(gasLimit) }, true, true, @@ -54,11 +55,11 @@ func (suite *AnteTestSuite) TestSimulateGasCost() { { "with previously estimated gas", func() { - simulatedGas := suite.ctx.GasMeter().GasConsumed() + simulatedGas := ctx.GasMeter().GasConsumed() accSeqs = []uint64{1, 1, 1} - suite.txBuilder.SetFeeAmount(feeAmount) - suite.txBuilder.SetGasLimit(simulatedGas) + txBuilder.SetFeeAmount(feeAmount) + txBuilder.SetGasLimit(simulatedGas) }, false, true, @@ -67,18 +68,18 @@ func (suite *AnteTestSuite) TestSimulateGasCost() { } for _, tc := range testCases { - suite.Run(fmt.Sprintf("Case %s", tc.desc), func() { - suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() + s.Run(fmt.Sprintf("Case %s", tc.desc), func() { tc.malleate() - suite.RunTestCase(privs, msgs, feeAmount, gasLimit, accNums, accSeqs, suite.ctx.ChainID(), tc) + s.runTestCase(ctx, txBuilder, privs, msgs, feeAmount, gasLimit, accNums, accSeqs, ctx.ChainID(), tc) }) } } -// Test various error cases in the AnteHandler control flow. -func (suite *AnteTestSuite) TestAnteHandlerSigErrors() { - suite.SetupTest(false) // reset +// Test various error cases in the TxHandler control flow. +func (s *MWTestSuite) TestTxHandlerSigErrors() { + ctx := s.SetupTest(false) // reset + txBuilder := s.clientCtx.TxConfig.NewTxBuilder() // Same data for every test cases priv0, _, addr0 := testdata.KeyTestPubAddr() @@ -105,12 +106,12 @@ func (suite *AnteTestSuite) TestAnteHandlerSigErrors() { privs, accNums, accSeqs = []cryptotypes.PrivKey{}, []uint64{}, []uint64{} // Create tx manually to test the tx's signers - suite.Require().NoError(suite.txBuilder.SetMsgs(msgs...)) - tx, err := suite.CreateTestTx(privs, accNums, accSeqs, suite.ctx.ChainID()) - suite.Require().NoError(err) + s.Require().NoError(txBuilder.SetMsgs(msgs...)) + tx, _, err := s.createTestTx(txBuilder, privs, accNums, accSeqs, ctx.ChainID()) + s.Require().NoError(err) // tx.GetSigners returns addresses in correct order: addr1, addr2, addr3 expectedSigners := []sdk.AccAddress{addr0, addr1, addr2} - suite.Require().Equal(expectedSigners, tx.GetSigners()) + s.Require().Equal(expectedSigners, tx.GetSigners()) }, false, false, @@ -137,12 +138,12 @@ func (suite *AnteTestSuite) TestAnteHandlerSigErrors() { { "save the first account, but second is still unrecognized", func() { - acc1 := suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, addr0) - suite.app.AccountKeeper.SetAccount(suite.ctx, acc1) - err := suite.app.BankKeeper.MintCoins(suite.ctx, minttypes.ModuleName, feeAmount) - suite.Require().NoError(err) - err = suite.app.BankKeeper.SendCoinsFromModuleToAccount(suite.ctx, minttypes.ModuleName, addr0, feeAmount) - suite.Require().NoError(err) + acc1 := s.app.AccountKeeper.NewAccountWithAddress(ctx, addr0) + s.app.AccountKeeper.SetAccount(ctx, acc1) + err := s.app.BankKeeper.MintCoins(ctx, minttypes.ModuleName, feeAmount) + s.Require().NoError(err) + err = s.app.BankKeeper.SendCoinsFromModuleToAccount(ctx, minttypes.ModuleName, addr0, feeAmount) + s.Require().NoError(err) }, false, false, @@ -151,21 +152,21 @@ func (suite *AnteTestSuite) TestAnteHandlerSigErrors() { } for _, tc := range testCases { - suite.Run(fmt.Sprintf("Case %s", tc.desc), func() { - suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() + s.Run(fmt.Sprintf("Case %s", tc.desc), func() { tc.malleate() - suite.RunTestCase(privs, msgs, feeAmount, gasLimit, accNums, accSeqs, suite.ctx.ChainID(), tc) + s.runTestCase(ctx, txBuilder, privs, msgs, feeAmount, gasLimit, accNums, accSeqs, ctx.ChainID(), tc) }) } } // Test logic around account number checking with one signer and many signers. -func (suite *AnteTestSuite) TestAnteHandlerAccountNumbers() { - suite.SetupTest(false) // reset +func (s *MWTestSuite) TestTxHandlerAccountNumbers() { + ctx := s.SetupTest(false) // reset + txBuilder := s.clientCtx.TxConfig.NewTxBuilder() // Same data for every test cases - accounts := suite.CreateTestAccounts(2) + accounts := s.createTestAccounts(ctx, 2) feeAmount := testdata.NewTestFeeAmount() gasLimit := testdata.NewTestGasLimit() @@ -232,22 +233,22 @@ func (suite *AnteTestSuite) TestAnteHandlerAccountNumbers() { } for _, tc := range testCases { - suite.Run(fmt.Sprintf("Case %s", tc.desc), func() { - suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() + s.Run(fmt.Sprintf("Case %s", tc.desc), func() { tc.malleate() - suite.RunTestCase(privs, msgs, feeAmount, gasLimit, accNums, accSeqs, suite.ctx.ChainID(), tc) + s.runTestCase(ctx, txBuilder, privs, msgs, feeAmount, gasLimit, accNums, accSeqs, ctx.ChainID(), tc) }) } } // Test logic around account number checking with many signers when BlockHeight is 0. -func (suite *AnteTestSuite) TestAnteHandlerAccountNumbersAtBlockHeightZero() { - suite.SetupTest(false) // setup - suite.ctx = suite.ctx.WithBlockHeight(0) +func (s *MWTestSuite) TestTxHandlerAccountNumbersAtBlockHeightZero() { + ctx := s.SetupTest(false) // setup + ctx = ctx.WithBlockHeight(0) + txBuilder := s.clientCtx.TxConfig.NewTxBuilder() // Same data for every test cases - accounts := suite.CreateTestAccounts(2) + accounts := s.createTestAccounts(ctx, 2) feeAmount := testdata.NewTestFeeAmount() gasLimit := testdata.NewTestGasLimit() @@ -316,21 +317,21 @@ func (suite *AnteTestSuite) TestAnteHandlerAccountNumbersAtBlockHeightZero() { } for _, tc := range testCases { - suite.Run(fmt.Sprintf("Case %s", tc.desc), func() { - suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() + s.Run(fmt.Sprintf("Case %s", tc.desc), func() { tc.malleate() - suite.RunTestCase(privs, msgs, feeAmount, gasLimit, accNums, accSeqs, suite.ctx.ChainID(), tc) + s.runTestCase(ctx, txBuilder, privs, msgs, feeAmount, gasLimit, accNums, accSeqs, ctx.ChainID(), tc) }) } } // Test logic around sequence checking with one signer and many signers. -func (suite *AnteTestSuite) TestAnteHandlerSequences() { - suite.SetupTest(false) // setup +func (s *MWTestSuite) TestTxHandlerSequences() { + ctx := s.SetupTest(false) // setup + txBuilder := s.clientCtx.TxConfig.NewTxBuilder() // Same data for every test cases - accounts := suite.CreateTestAccounts(3) + accounts := s.createTestAccounts(ctx, 3) feeAmount := testdata.NewTestFeeAmount() gasLimit := testdata.NewTestGasLimit() @@ -428,24 +429,24 @@ func (suite *AnteTestSuite) TestAnteHandlerSequences() { } for _, tc := range testCases { - suite.Run(fmt.Sprintf("Case %s", tc.desc), func() { - suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() + s.Run(fmt.Sprintf("Case %s", tc.desc), func() { tc.malleate() - suite.RunTestCase(privs, msgs, feeAmount, gasLimit, accNums, accSeqs, suite.ctx.ChainID(), tc) + s.runTestCase(ctx, txBuilder, privs, msgs, feeAmount, gasLimit, accNums, accSeqs, ctx.ChainID(), tc) }) } } // Test logic around fee deduction. -func (suite *AnteTestSuite) TestAnteHandlerFees() { - suite.SetupTest(false) // setup +func (s *MWTestSuite) TestTxHandlerFees() { + ctx := s.SetupTest(false) // setup + txBuilder := s.clientCtx.TxConfig.NewTxBuilder() // Same data for every test cases priv0, _, addr0 := testdata.KeyTestPubAddr() - acc1 := suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, addr0) - suite.app.AccountKeeper.SetAccount(suite.ctx, acc1) + acc1 := s.app.AccountKeeper.NewAccountWithAddress(ctx, addr0) + s.app.AccountKeeper.SetAccount(ctx, acc1) msgs := []sdk.Msg{testdata.NewTestMsg(addr0)} feeAmount := testdata.NewTestFeeAmount() gasLimit := testdata.NewTestGasLimit() @@ -470,8 +471,8 @@ func (suite *AnteTestSuite) TestAnteHandlerFees() { { "signer does not have enough funds to pay the fee", func() { - err := testutil.FundAccount(suite.app.BankKeeper, suite.ctx, addr0, sdk.NewCoins(sdk.NewInt64Coin("atom", 149))) - suite.Require().NoError(err) + err := testutil.FundAccount(s.app.BankKeeper, ctx, addr0, sdk.NewCoins(sdk.NewInt64Coin("atom", 149))) + s.Require().NoError(err) }, false, false, @@ -482,13 +483,13 @@ func (suite *AnteTestSuite) TestAnteHandlerFees() { func() { accNums = []uint64{acc1.GetAccountNumber()} - modAcc := suite.app.AccountKeeper.GetModuleAccount(suite.ctx, types.FeeCollectorName) + modAcc := s.app.AccountKeeper.GetModuleAccount(ctx, types.FeeCollectorName) - suite.Require().True(suite.app.BankKeeper.GetAllBalances(suite.ctx, modAcc.GetAddress()).Empty()) - require.True(sdk.IntEq(suite.T(), suite.app.BankKeeper.GetAllBalances(suite.ctx, addr0).AmountOf("atom"), sdk.NewInt(149))) + s.Require().True(s.app.BankKeeper.GetAllBalances(ctx, modAcc.GetAddress()).Empty()) + require.True(sdk.IntEq(s.T(), s.app.BankKeeper.GetAllBalances(ctx, addr0).AmountOf("atom"), sdk.NewInt(149))) - err := testutil.FundAccount(suite.app.BankKeeper, suite.ctx, addr0, sdk.NewCoins(sdk.NewInt64Coin("atom", 1))) - suite.Require().NoError(err) + err := testutil.FundAccount(s.app.BankKeeper, ctx, addr0, sdk.NewCoins(sdk.NewInt64Coin("atom", 1))) + s.Require().NoError(err) }, false, true, @@ -497,10 +498,10 @@ func (suite *AnteTestSuite) TestAnteHandlerFees() { { "signer doesn't have any more funds", func() { - modAcc := suite.app.AccountKeeper.GetModuleAccount(suite.ctx, types.FeeCollectorName) + modAcc := s.app.AccountKeeper.GetModuleAccount(ctx, types.FeeCollectorName) - require.True(sdk.IntEq(suite.T(), suite.app.BankKeeper.GetAllBalances(suite.ctx, modAcc.GetAddress()).AmountOf("atom"), sdk.NewInt(150))) - require.True(sdk.IntEq(suite.T(), suite.app.BankKeeper.GetAllBalances(suite.ctx, addr0).AmountOf("atom"), sdk.NewInt(0))) + require.True(sdk.IntEq(s.T(), s.app.BankKeeper.GetAllBalances(ctx, modAcc.GetAddress()).AmountOf("atom"), sdk.NewInt(150))) + require.True(sdk.IntEq(s.T(), s.app.BankKeeper.GetAllBalances(ctx, addr0).AmountOf("atom"), sdk.NewInt(0))) }, false, false, @@ -509,22 +510,21 @@ func (suite *AnteTestSuite) TestAnteHandlerFees() { } for _, tc := range testCases { - suite.Run(fmt.Sprintf("Case %s", tc.desc), func() { - - suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() + s.Run(fmt.Sprintf("Case %s", tc.desc), func() { tc.malleate() - suite.RunTestCase(privs, msgs, feeAmount, gasLimit, accNums, accSeqs, suite.ctx.ChainID(), tc) + s.runTestCase(ctx, txBuilder, privs, msgs, feeAmount, gasLimit, accNums, accSeqs, ctx.ChainID(), tc) }) } } // Test logic around memo gas consumption. -func (suite *AnteTestSuite) TestAnteHandlerMemoGas() { - suite.SetupTest(false) // setup +func (s *MWTestSuite) TestTxHandlerMemoGas() { + ctx := s.SetupTest(false) // setup + txBuilder := s.clientCtx.TxConfig.NewTxBuilder() // Same data for every test cases - accounts := suite.CreateTestAccounts(1) + accounts := s.createTestAccounts(ctx, 1) msgs := []sdk.Msg{testdata.NewTestMsg(accounts[0].acc.GetAddress())} privs, accNums, accSeqs := []cryptotypes.PrivKey{accounts[0].priv}, []uint64{0}, []uint64{0} @@ -550,7 +550,7 @@ func (suite *AnteTestSuite) TestAnteHandlerMemoGas() { func() { feeAmount = sdk.NewCoins(sdk.NewInt64Coin("atom", 0)) gasLimit = 801 - suite.txBuilder.SetMemo("abcininasidniandsinasindiansdiansdinaisndiasndiadninsd") + txBuilder.SetMemo("abcininasidniandsinasindiansdiansdinaisndiasndiadninsd") }, false, false, @@ -561,7 +561,7 @@ func (suite *AnteTestSuite) TestAnteHandlerMemoGas() { func() { feeAmount = sdk.NewCoins(sdk.NewInt64Coin("atom", 0)) gasLimit = 50000 - suite.txBuilder.SetMemo(strings.Repeat("01234567890", 500)) + txBuilder.SetMemo(strings.Repeat("01234567890", 500)) }, false, false, @@ -572,7 +572,7 @@ func (suite *AnteTestSuite) TestAnteHandlerMemoGas() { func() { feeAmount = sdk.NewCoins(sdk.NewInt64Coin("atom", 0)) gasLimit = 50000 - suite.txBuilder.SetMemo(strings.Repeat("0123456789", 10)) + txBuilder.SetMemo(strings.Repeat("0123456789", 10)) }, false, true, @@ -581,20 +581,20 @@ func (suite *AnteTestSuite) TestAnteHandlerMemoGas() { } for _, tc := range testCases { - suite.Run(fmt.Sprintf("Case %s", tc.desc), func() { - suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() + s.Run(fmt.Sprintf("Case %s", tc.desc), func() { tc.malleate() - suite.RunTestCase(privs, msgs, feeAmount, gasLimit, accNums, accSeqs, suite.ctx.ChainID(), tc) + s.runTestCase(ctx, txBuilder, privs, msgs, feeAmount, gasLimit, accNums, accSeqs, ctx.ChainID(), tc) }) } } -func (suite *AnteTestSuite) TestAnteHandlerMultiSigner() { - suite.SetupTest(false) // setup +func (s *MWTestSuite) TestTxHandlerMultiSigner() { + ctx := s.SetupTest(false) // setup + txBuilder := s.clientCtx.TxConfig.NewTxBuilder() // Same data for every test cases - accounts := suite.CreateTestAccounts(3) + accounts := s.createTestAccounts(ctx, 3) msg1 := testdata.NewTestMsg(accounts[0].acc.GetAddress(), accounts[1].acc.GetAddress()) msg2 := testdata.NewTestMsg(accounts[2].acc.GetAddress(), accounts[0].acc.GetAddress()) msg3 := testdata.NewTestMsg(accounts[1].acc.GetAddress(), accounts[2].acc.GetAddress()) @@ -615,7 +615,7 @@ func (suite *AnteTestSuite) TestAnteHandlerMultiSigner() { func() { msgs = []sdk.Msg{msg1, msg2, msg3} privs, accNums, accSeqs = []cryptotypes.PrivKey{accounts[0].priv, accounts[1].priv, accounts[2].priv}, []uint64{0, 1, 2}, []uint64{0, 0, 0} - suite.txBuilder.SetMemo("Check signers are in expected order and different account numbers works") + txBuilder.SetMemo("Check signers are in expected order and different account numbers works") }, false, true, @@ -654,20 +654,20 @@ func (suite *AnteTestSuite) TestAnteHandlerMultiSigner() { } for _, tc := range testCases { - suite.Run(fmt.Sprintf("Case %s", tc.desc), func() { - suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() + s.Run(fmt.Sprintf("Case %s", tc.desc), func() { tc.malleate() - suite.RunTestCase(privs, msgs, feeAmount, gasLimit, accNums, accSeqs, suite.ctx.ChainID(), tc) + s.runTestCase(ctx, txBuilder, privs, msgs, feeAmount, gasLimit, accNums, accSeqs, ctx.ChainID(), tc) }) } } -func (suite *AnteTestSuite) TestAnteHandlerBadSignBytes() { - suite.SetupTest(false) // setup +func (s *MWTestSuite) TestTxHandlerBadSignBytes() { + ctx := s.SetupTest(true) // setup + txBuilder := s.clientCtx.TxConfig.NewTxBuilder() // Same data for every test cases - accounts := suite.CreateTestAccounts(2) + accounts := s.createTestAccounts(ctx, 2) msg0 := testdata.NewTestMsg(accounts[0].acc.GetAddress()) // Variable data per test case @@ -685,7 +685,7 @@ func (suite *AnteTestSuite) TestAnteHandlerBadSignBytes() { { "test good tx and signBytes", func() { - chainID = suite.ctx.ChainID() + chainID = ctx.ChainID() feeAmount = testdata.NewTestFeeAmount() gasLimit = testdata.NewTestGasLimit() msgs = []sdk.Msg{msg0} @@ -708,7 +708,7 @@ func (suite *AnteTestSuite) TestAnteHandlerBadSignBytes() { { "test wrong accSeqs", func() { - chainID = suite.ctx.ChainID() // Back to correct chainID + chainID = ctx.ChainID() // Back to correct chainID accSeqs = []uint64{2} }, false, @@ -780,20 +780,20 @@ func (suite *AnteTestSuite) TestAnteHandlerBadSignBytes() { } for _, tc := range testCases { - suite.Run(fmt.Sprintf("Case %s", tc.desc), func() { - suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() + s.Run(fmt.Sprintf("Case %s", tc.desc), func() { tc.malleate() - suite.RunTestCase(privs, msgs, feeAmount, gasLimit, accNums, accSeqs, chainID, tc) + s.runTestCase(ctx, txBuilder, privs, msgs, feeAmount, gasLimit, accNums, accSeqs, chainID, tc) }) } } -func (suite *AnteTestSuite) TestAnteHandlerSetPubKey() { - suite.SetupTest(false) // setup +func (s *MWTestSuite) TestTxHandlerSetPubKey() { + ctx := s.SetupTest(true) // setup + txBuilder := s.clientCtx.TxConfig.NewTxBuilder() // Same data for every test cases - accounts := suite.CreateTestAccounts(2) + accounts := s.createTestAccounts(ctx, 2) feeAmount := testdata.NewTestFeeAmount() gasLimit := testdata.NewTestGasLimit() @@ -820,8 +820,8 @@ func (suite *AnteTestSuite) TestAnteHandlerSetPubKey() { "make sure public key has been set (tx itself should fail because of replay protection)", func() { // Make sure public key has been set from previous test. - acc0 := suite.app.AccountKeeper.GetAccount(suite.ctx, accounts[0].acc.GetAddress()) - suite.Require().Equal(acc0.GetPubKey(), accounts[0].priv.PubKey()) + acc0 := s.app.AccountKeeper.GetAccount(ctx, accounts[0].acc.GetAddress()) + s.Require().Equal(acc0.GetPubKey(), accounts[0].priv.PubKey()) }, false, false, @@ -841,30 +841,30 @@ func (suite *AnteTestSuite) TestAnteHandlerSetPubKey() { "make sure public key is not set, when tx has no pubkey or signature", func() { // Make sure public key has not been set from previous test. - acc1 := suite.app.AccountKeeper.GetAccount(suite.ctx, accounts[1].acc.GetAddress()) - suite.Require().Nil(acc1.GetPubKey()) + acc1 := s.app.AccountKeeper.GetAccount(ctx, accounts[1].acc.GetAddress()) + s.Require().Nil(acc1.GetPubKey()) privs, accNums, accSeqs = []cryptotypes.PrivKey{accounts[1].priv}, []uint64{1}, []uint64{0} msgs = []sdk.Msg{testdata.NewTestMsg(accounts[1].acc.GetAddress())} - suite.txBuilder.SetMsgs(msgs...) - suite.txBuilder.SetFeeAmount(feeAmount) - suite.txBuilder.SetGasLimit(gasLimit) + txBuilder.SetMsgs(msgs...) + txBuilder.SetFeeAmount(feeAmount) + txBuilder.SetGasLimit(gasLimit) // Manually create tx, and remove signature. - tx, err := suite.CreateTestTx(privs, accNums, accSeqs, suite.ctx.ChainID()) - suite.Require().NoError(err) - txBuilder, err := suite.clientCtx.TxConfig.WrapTxBuilder(tx) - suite.Require().NoError(err) - suite.Require().NoError(txBuilder.SetSignatures()) + tx, _, err := s.createTestTx(txBuilder, privs, accNums, accSeqs, ctx.ChainID()) + s.Require().NoError(err) + txBuilder, err := s.clientCtx.TxConfig.WrapTxBuilder(tx) + s.Require().NoError(err) + s.Require().NoError(txBuilder.SetSignatures()) - // Run anteHandler manually, expect ErrNoSignatures. - _, err = suite.anteHandler(suite.ctx, txBuilder.GetTx(), false) - suite.Require().Error(err) - suite.Require().True(errors.Is(err, sdkerrors.ErrNoSignatures)) + // Run txHandler manually, expect ErrNoSignatures. + _, err = s.txHandler.CheckTx(sdk.WrapSDKContext(ctx), txBuilder.GetTx(), abci.RequestCheckTx{}) + s.Require().Error(err) + s.Require().True(errors.Is(err, sdkerrors.ErrNoSignatures)) // Make sure public key has not been set. - acc1 = suite.app.AccountKeeper.GetAccount(suite.ctx, accounts[1].acc.GetAddress()) - suite.Require().Nil(acc1.GetPubKey()) + acc1 = s.app.AccountKeeper.GetAccount(ctx, accounts[1].acc.GetAddress()) + s.Require().Nil(acc1.GetPubKey()) // Set incorrect accSeq, to generate incorrect signature. privs, accNums, accSeqs = []cryptotypes.PrivKey{accounts[1].priv}, []uint64{1}, []uint64{1} @@ -876,10 +876,10 @@ func (suite *AnteTestSuite) TestAnteHandlerSetPubKey() { { "make sure previous public key has been set after wrong signature", func() { - // Make sure public key has been set, as SetPubKeyDecorator - // is called before all signature verification decorators. - acc1 := suite.app.AccountKeeper.GetAccount(suite.ctx, accounts[1].acc.GetAddress()) - suite.Require().Equal(acc1.GetPubKey(), accounts[1].priv.PubKey()) + // Make sure public key has been set, as SetPubKeyMiddleware + // is called before all signature verification middlewares. + acc1 := s.app.AccountKeeper.GetAccount(ctx, accounts[1].acc.GetAddress()) + s.Require().Equal(acc1.GetPubKey(), accounts[1].priv.PubKey()) }, false, false, @@ -888,11 +888,10 @@ func (suite *AnteTestSuite) TestAnteHandlerSetPubKey() { } for _, tc := range testCases { - suite.Run(fmt.Sprintf("Case %s", tc.desc), func() { - suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() + s.Run(fmt.Sprintf("Case %s", tc.desc), func() { tc.malleate() - suite.RunTestCase(privs, msgs, feeAmount, gasLimit, accNums, accSeqs, suite.ctx.ChainID(), tc) + s.runTestCase(ctx, txBuilder, privs, msgs, feeAmount, gasLimit, accNums, accSeqs, ctx.ChainID(), tc) }) } } @@ -962,16 +961,17 @@ func TestCountSubkeys(t *testing.T) { } for _, tc := range testCases { t.Run(tc.name, func(T *testing.T) { - require.Equal(t, tc.want, ante.CountSubKeys(tc.args.pub)) + require.Equal(t, tc.want, middleware.CountSubKeys(tc.args.pub)) }) } } -func (suite *AnteTestSuite) TestAnteHandlerSigLimitExceeded() { - suite.SetupTest(false) // setup +func (s *MWTestSuite) TestTxHandlerSigLimitExceeded() { + ctx := s.SetupTest(false) // setup + txBuilder := s.clientCtx.TxConfig.NewTxBuilder() // Same data for every test cases - accounts := suite.CreateTestAccounts(8) + accounts := s.createTestAccounts(ctx, 8) var addrs []sdk.AccAddress var privs []cryptotypes.PrivKey for i := 0; i < 8; i++ { @@ -994,26 +994,25 @@ func (suite *AnteTestSuite) TestAnteHandlerSigLimitExceeded() { } for _, tc := range testCases { - suite.Run(fmt.Sprintf("Case %s", tc.desc), func() { - suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() + s.Run(fmt.Sprintf("Case %s", tc.desc), func() { tc.malleate() - suite.RunTestCase(privs, msgs, feeAmount, gasLimit, accNums, accSeqs, suite.ctx.ChainID(), tc) + s.runTestCase(ctx, txBuilder, privs, msgs, feeAmount, gasLimit, accNums, accSeqs, ctx.ChainID(), tc) }) } } // Test custom SignatureVerificationGasConsumer -func (suite *AnteTestSuite) TestCustomSignatureVerificationGasConsumer() { - suite.SetupTest(false) // setup - - // setup an ante handler that only accepts PubKeyEd25519 - anteHandler, err := ante.NewAnteHandler( - ante.HandlerOptions{ - AccountKeeper: suite.app.AccountKeeper, - BankKeeper: suite.app.BankKeeper, - FeegrantKeeper: suite.app.FeeGrantKeeper, - SignModeHandler: suite.clientCtx.TxConfig.SignModeHandler(), +func (s *MWTestSuite) TestCustomSignatureVerificationGasConsumer() { + ctx := s.SetupTest(false) // setup + txBuilder := s.clientCtx.TxConfig.NewTxBuilder() + + txHandler, err := middleware.NewDefaultTxHandler( + middleware.TxHandlerOptions{ + AccountKeeper: s.app.AccountKeeper, + BankKeeper: s.app.BankKeeper, + FeegrantKeeper: s.app.FeeGrantKeeper, + SignModeHandler: s.clientCtx.TxConfig.SignModeHandler(), SigGasConsumer: func(meter sdk.GasMeter, sig signing.SignatureV2, params types.Params) error { switch pubkey := sig.PubKey.(type) { case *ed25519.PubKey: @@ -1025,19 +1024,19 @@ func (suite *AnteTestSuite) TestCustomSignatureVerificationGasConsumer() { }, }, ) + s.Require().NoError(err) - suite.Require().NoError(err) - suite.anteHandler = anteHandler + s.Require().NoError(err) // Same data for every test cases - accounts := suite.CreateTestAccounts(1) - feeAmount := testdata.NewTestFeeAmount() - gasLimit := testdata.NewTestGasLimit() + accounts := s.createTestAccounts(ctx, 1) + txBuilder.SetFeeAmount(testdata.NewTestFeeAmount()) + txBuilder.SetGasLimit(testdata.NewTestGasLimit()) + txBuilder.SetMsgs(testdata.NewTestMsg(accounts[0].acc.GetAddress())) // Variable data per test case var ( accNums []uint64 - msgs []sdk.Msg privs []cryptotypes.PrivKey accSeqs []uint64 ) @@ -1046,7 +1045,6 @@ func (suite *AnteTestSuite) TestCustomSignatureVerificationGasConsumer() { { "verify that an secp256k1 account gets rejected", func() { - msgs = []sdk.Msg{testdata.NewTestMsg(accounts[0].acc.GetAddress())} privs, accNums, accSeqs = []cryptotypes.PrivKey{accounts[0].priv}, []uint64{0}, []uint64{0} }, false, @@ -1056,54 +1054,57 @@ func (suite *AnteTestSuite) TestCustomSignatureVerificationGasConsumer() { } for _, tc := range testCases { - suite.Run(fmt.Sprintf("Case %s", tc.desc), func() { - suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() + s.Run(fmt.Sprintf("Case %s", tc.desc), func() { tc.malleate() - suite.RunTestCase(privs, msgs, feeAmount, gasLimit, accNums, accSeqs, suite.ctx.ChainID(), tc) + tx, txBytes, err := s.createTestTx(txBuilder, privs, accNums, accSeqs, ctx.ChainID()) + s.Require().NoError(err) + _, err = txHandler.DeliverTx(sdk.WrapSDKContext(ctx), tx, abci.RequestDeliverTx{Tx: txBytes}) + s.Require().Error(err) + s.Require().True(errors.Is(err, tc.expErr)) }) } } -func (suite *AnteTestSuite) TestAnteHandlerReCheck() { - suite.SetupTest(false) // setup +func (s *MWTestSuite) TestTxHandlerReCheck() { + ctx := s.SetupTest(false) // setup // Set recheck=true - suite.ctx = suite.ctx.WithIsReCheckTx(true) - suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() + ctx = ctx.WithIsReCheckTx(true) + txBuilder := s.clientCtx.TxConfig.NewTxBuilder() // Same data for every test cases - accounts := suite.CreateTestAccounts(1) + accounts := s.createTestAccounts(ctx, 1) feeAmount := testdata.NewTestFeeAmount() gasLimit := testdata.NewTestGasLimit() - suite.txBuilder.SetFeeAmount(feeAmount) - suite.txBuilder.SetGasLimit(gasLimit) + txBuilder.SetFeeAmount(feeAmount) + txBuilder.SetGasLimit(gasLimit) msg := testdata.NewTestMsg(accounts[0].acc.GetAddress()) msgs := []sdk.Msg{msg} - suite.Require().NoError(suite.txBuilder.SetMsgs(msgs...)) + s.Require().NoError(txBuilder.SetMsgs(msgs...)) - suite.txBuilder.SetMemo("thisisatestmemo") + txBuilder.SetMemo("thisisatestmemo") // test that operations skipped on recheck do not run privs, accNums, accSeqs := []cryptotypes.PrivKey{accounts[0].priv}, []uint64{0}, []uint64{0} - tx, err := suite.CreateTestTx(privs, accNums, accSeqs, suite.ctx.ChainID()) - suite.Require().NoError(err) + tx, _, err := s.createTestTx(txBuilder, privs, accNums, accSeqs, ctx.ChainID()) + s.Require().NoError(err) - // make signature array empty which would normally cause ValidateBasicDecorator and SigVerificationDecorator fail - // since these decorators don't run on recheck, the tx should pass the antehandler - txBuilder, err := suite.clientCtx.TxConfig.WrapTxBuilder(tx) - suite.Require().NoError(err) - suite.Require().NoError(txBuilder.SetSignatures()) + // make signature array empty which would normally cause ValidateBasicMiddleware and SigVerificationMiddleware fail + // since these middlewares don't run on recheck, the tx should pass the middleware + txBuilder, err = s.clientCtx.TxConfig.WrapTxBuilder(tx) + s.Require().NoError(err) + s.Require().NoError(txBuilder.SetSignatures()) - _, err = suite.anteHandler(suite.ctx, txBuilder.GetTx(), false) - suite.Require().Nil(err, "AnteHandler errored on recheck unexpectedly: %v", err) + _, err = s.txHandler.CheckTx(sdk.WrapSDKContext(ctx), txBuilder.GetTx(), abci.RequestCheckTx{Type: abci.CheckTxType_Recheck}) + s.Require().Nil(err, "TxHandler errored on recheck unexpectedly: %v", err) - tx, err = suite.CreateTestTx(privs, accNums, accSeqs, suite.ctx.ChainID()) - suite.Require().NoError(err) + tx, _, err = s.createTestTx(txBuilder, privs, accNums, accSeqs, ctx.ChainID()) + s.Require().NoError(err) txBytes, err := json.Marshal(tx) - suite.Require().Nil(err, "Error marshalling tx: %v", err) - suite.ctx = suite.ctx.WithTxBytes(txBytes) + s.Require().Nil(err, "Error marshalling tx: %v", err) + ctx = 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 { @@ -1114,35 +1115,37 @@ func (suite *AnteTestSuite) TestAnteHandlerReCheck() { {"txsize check", types.NewParams(types.DefaultMaxMemoCharacters, types.DefaultTxSigLimit, 10000000, types.DefaultSigVerifyCostED25519, types.DefaultSigVerifyCostSecp256k1)}, {"sig verify cost check", types.NewParams(types.DefaultMaxMemoCharacters, types.DefaultTxSigLimit, types.DefaultTxSizeCostPerByte, types.DefaultSigVerifyCostED25519, 100000000)}, } + for _, tc := range testCases { // set testcase parameters - suite.app.AccountKeeper.SetParams(suite.ctx, tc.params) + s.app.AccountKeeper.SetParams(ctx, tc.params) - _, err := suite.anteHandler(suite.ctx, tx, false) + _, err = s.txHandler.CheckTx(sdk.WrapSDKContext(ctx), tx, abci.RequestCheckTx{Tx: txBytes, Type: abci.CheckTxType_Recheck}) - suite.Require().NotNil(err, "tx does not fail on recheck with updated params in test case: %s", tc.name) + s.Require().NotNil(err, "tx does not fail on recheck with updated params in test case: %s", tc.name) // reset parameters to default values - suite.app.AccountKeeper.SetParams(suite.ctx, types.DefaultParams()) + s.app.AccountKeeper.SetParams(ctx, types.DefaultParams()) } // require that local mempool fee check is still run on recheck since validator may change minFee between check and recheck - // create new minimum gas price so antehandler fails on recheck - suite.ctx = suite.ctx.WithMinGasPrices([]sdk.DecCoin{{ + // create new minimum gas price so txhandler fails on recheck + ctx = ctx.WithMinGasPrices([]sdk.DecCoin{{ Denom: "dnecoin", // fee does not have this denom Amount: sdk.NewDec(5), }}) - _, err = suite.anteHandler(suite.ctx, tx, false) - suite.Require().NotNil(err, "antehandler on recheck did not fail when mingasPrice was changed") + _, err = s.txHandler.CheckTx(sdk.WrapSDKContext(ctx), tx, abci.RequestCheckTx{}) + + s.Require().NotNil(err, "txhandler on recheck did not fail when mingasPrice was changed") // reset min gasprice - suite.ctx = suite.ctx.WithMinGasPrices(sdk.DecCoins{}) + ctx = ctx.WithMinGasPrices(sdk.DecCoins{}) - // remove funds for account so antehandler fails on recheck - suite.app.AccountKeeper.SetAccount(suite.ctx, accounts[0].acc) - balances := suite.app.BankKeeper.GetAllBalances(suite.ctx, accounts[0].acc.GetAddress()) - err = suite.app.BankKeeper.SendCoinsFromAccountToModule(suite.ctx, accounts[0].acc.GetAddress(), minttypes.ModuleName, balances) - suite.Require().NoError(err) + // remove funds for account so txhandler fails on recheck + s.app.AccountKeeper.SetAccount(ctx, accounts[0].acc) + balances := s.app.BankKeeper.GetAllBalances(ctx, accounts[0].acc.GetAddress()) + err = s.app.BankKeeper.SendCoinsFromAccountToModule(ctx, accounts[0].acc.GetAddress(), minttypes.ModuleName, balances) + s.Require().NoError(err) - _, err = suite.anteHandler(suite.ctx, tx, false) - suite.Require().NotNil(err, "antehandler on recheck did not fail once feePayer no longer has sufficient funds") + _, err = s.txHandler.CheckTx(sdk.WrapSDKContext(ctx), tx, abci.RequestCheckTx{}) + s.Require().NotNil(err, "txhandler on recheck did not fail once feePayer no longer has sufficient funds") } diff --git a/x/auth/middleware/msg_service_router_test.go b/x/auth/middleware/msg_service_router_test.go index 6223293617e4..ca6ec79b5b9a 100644 --- a/x/auth/middleware/msg_service_router_test.go +++ b/x/auth/middleware/msg_service_router_test.go @@ -1,22 +1,13 @@ package middleware_test import ( - "os" "testing" "github.com/stretchr/testify/require" - abci "github.com/tendermint/tendermint/abci/types" - "github.com/tendermint/tendermint/libs/log" - tmproto "github.com/tendermint/tendermint/proto/tendermint/types" - dbm "github.com/tendermint/tm-db" - "github.com/cosmos/cosmos-sdk/baseapp" - "github.com/cosmos/cosmos-sdk/client/tx" "github.com/cosmos/cosmos-sdk/simapp" "github.com/cosmos/cosmos-sdk/testutil/testdata" - "github.com/cosmos/cosmos-sdk/types/tx/signing" "github.com/cosmos/cosmos-sdk/x/auth/middleware" - authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing" ) func TestRegisterMsgService(t *testing.T) { @@ -62,63 +53,3 @@ func TestRegisterMsgServiceTwice(t *testing.T) { ) }) } - -func TestMsgService(t *testing.T) { - priv, _, _ := testdata.KeyTestPubAddr() - encCfg := simapp.MakeTestEncodingConfig() - testdata.RegisterInterfaces(encCfg.InterfaceRegistry) - db := dbm.NewMemDB() - app := baseapp.NewBaseApp("test", log.NewTMLogger(log.NewSyncWriter(os.Stdout)), db, encCfg.TxConfig.TxDecoder()) - app.SetInterfaceRegistry(encCfg.InterfaceRegistry) - msr := middleware.NewMsgServiceRouter(encCfg.InterfaceRegistry) - txHandler, err := middleware.NewDefaultTxHandler(middleware.TxHandlerOptions{ - MsgServiceRouter: msr, - }) - require.NoError(t, err) - app.SetTxHandler(txHandler) - testdata.RegisterMsgServer( - msr, - testdata.MsgServerImpl{}, - ) - _ = app.BeginBlock(abci.RequestBeginBlock{Header: tmproto.Header{Height: 1}}) - - msg := testdata.MsgCreateDog{Dog: &testdata.Dog{Name: "Spot"}} - txBuilder := encCfg.TxConfig.NewTxBuilder() - txBuilder.SetFeeAmount(testdata.NewTestFeeAmount()) - txBuilder.SetGasLimit(testdata.NewTestGasLimit()) - err = txBuilder.SetMsgs(&msg) - require.NoError(t, err) - - // First round: we gather all the signer infos. We use the "set empty - // signature" hack to do that. - sigV2 := signing.SignatureV2{ - PubKey: priv.PubKey(), - Data: &signing.SingleSignatureData{ - SignMode: encCfg.TxConfig.SignModeHandler().DefaultMode(), - Signature: nil, - }, - Sequence: 0, - } - - err = txBuilder.SetSignatures(sigV2) - require.NoError(t, err) - - // Second round: all signer infos are set, so each signer can sign. - signerData := authsigning.SignerData{ - ChainID: "test", - AccountNumber: 0, - Sequence: 0, - } - sigV2, err = tx.SignWithPrivKey( - encCfg.TxConfig.SignModeHandler().DefaultMode(), signerData, - txBuilder, priv, encCfg.TxConfig, 0) - require.NoError(t, err) - err = txBuilder.SetSignatures(sigV2) - require.NoError(t, err) - - // Send the tx to the app - txBytes, err := encCfg.TxConfig.TxEncoder()(txBuilder.GetTx()) - require.NoError(t, err) - res := app.DeliverTx(abci.RequestDeliverTx{Tx: txBytes}) - require.Equal(t, abci.CodeTypeOK, res.Code, "res=%+v", res) -} diff --git a/x/auth/middleware/run_msgs.go b/x/auth/middleware/run_msgs.go index d1ae2960369b..e9073b59a9af 100644 --- a/x/auth/middleware/run_msgs.go +++ b/x/auth/middleware/run_msgs.go @@ -83,6 +83,7 @@ func (txh runMsgsTxHandler) runMsgs(sdkCtx sdk.Context, msgs []sdk.Msg, txBytes Data: make([]*sdk.MsgData, 0, len(msgs)), } + // NOTE: GasWanted is determined by the Gas TxHandler and GasUsed by the GasMeter. for i, msg := range msgs { var ( msgResult *sdk.Result diff --git a/x/auth/middleware/run_msgs_test.go b/x/auth/middleware/run_msgs_test.go new file mode 100644 index 000000000000..909de101d46d --- /dev/null +++ b/x/auth/middleware/run_msgs_test.go @@ -0,0 +1,36 @@ +package middleware_test + +import ( + "github.com/tendermint/tendermint/abci/types" + + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + "github.com/cosmos/cosmos-sdk/testutil/testdata" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth/middleware" +) + +func (s *MWTestSuite) TestRunMsgs() { + ctx := s.SetupTest(true) // setup + + msr := middleware.NewMsgServiceRouter(s.clientCtx.InterfaceRegistry) + testdata.RegisterMsgServer(msr, testdata.MsgServerImpl{}) + txHandler := middleware.NewRunMsgsTxHandler(msr, nil) + + priv, _, _ := testdata.KeyTestPubAddr() + txBuilder := s.clientCtx.TxConfig.NewTxBuilder() + txBuilder.SetMsgs(&testdata.MsgCreateDog{Dog: &testdata.Dog{Name: "Spot"}}) + privs, accNums, accSeqs := []cryptotypes.PrivKey{priv}, []uint64{0}, []uint64{0} + tx, _, err := s.createTestTx(txBuilder, privs, accNums, accSeqs, ctx.ChainID()) + s.Require().NoError(err) + txBytes, err := s.clientCtx.TxConfig.TxEncoder()(tx) + s.Require().NoError(err) + + res, err := txHandler.DeliverTx(sdk.WrapSDKContext(ctx), tx, types.RequestDeliverTx{Tx: txBytes}) + s.Require().NoError(err) + s.Require().NotEmpty(res.Data) + var txMsgData sdk.TxMsgData + err = s.clientCtx.Codec.Unmarshal(res.Data, &txMsgData) + s.Require().NoError(err) + s.Require().Len(txMsgData.Data, 1) + s.Require().Equal(sdk.MsgTypeURL(&testdata.MsgCreateDog{}), txMsgData.Data[0].MsgType) +} diff --git a/x/auth/ante/sigverify.go b/x/auth/middleware/sigverify.go similarity index 52% rename from x/auth/ante/sigverify.go rename to x/auth/middleware/sigverify.go index 5097478da237..9ae464932684 100644 --- a/x/auth/ante/sigverify.go +++ b/x/auth/middleware/sigverify.go @@ -1,9 +1,9 @@ -package ante +package middleware import ( "bytes" + "context" "encoding/base64" - "encoding/hex" "fmt" "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" @@ -14,10 +14,11 @@ import ( "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" "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" + abci "github.com/tendermint/tendermint/abci/types" ) var ( @@ -25,44 +26,42 @@ var ( 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 +var _ tx.Handler = setPubKeyTxHandler{} + +type setPubKeyTxHandler struct { + ak AccountKeeper + next tx.Handler } -func NewSetPubKeyDecorator(ak AccountKeeper) SetPubKeyDecorator { - return SetPubKeyDecorator{ - ak: ak, +// SetPubKeyMiddleware 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 middlewares run +// CONTRACT: Tx must implement SigVerifiableTx interface +func SetPubKeyMiddleware(ak AccountKeeper) tx.Middleware { + return func(txh tx.Handler) tx.Handler { + return setPubKeyTxHandler{ + ak: ak, + next: txh, + } } } -func (spkd SetPubKeyDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) { +func (spkm setPubKeyTxHandler) setPubKey(ctx context.Context, tx sdk.Tx, simulate bool) error { + sdkCtx := sdk.UnwrapSDKContext(ctx) sigTx, ok := tx.(authsigning.SigVerifiableTx) if !ok { - return ctx, sdkerrors.Wrap(sdkerrors.ErrTxDecode, "invalid tx type") + return sdkerrors.Wrap(sdkerrors.ErrTxDecode, "invalid tx type") } pubkeys, err := sigTx.GetPubKeys() if err != nil { - return ctx, err + return err } signers := sigTx.GetSigners() @@ -76,13 +75,13 @@ func (spkd SetPubKeyDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate b } // Only make check if simulate=false if !simulate && !bytes.Equal(pk.Address(), signers[i]) { - return ctx, sdkerrors.Wrapf(sdkerrors.ErrInvalidPubKey, + return 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]) + acc, err := GetSignerAcc(sdkCtx, spkm.ak, signers[i]) if err != nil { - return ctx, err + return err } // account already has pubkey set,no need to reset if acc.GetPubKey() != nil { @@ -90,9 +89,9 @@ func (spkd SetPubKeyDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate b } err = acc.SetPubKey(pk) if err != nil { - return ctx, sdkerrors.Wrap(sdkerrors.ErrInvalidPubKey, err.Error()) + return sdkerrors.Wrap(sdkerrors.ErrInvalidPubKey, err.Error()) } - spkd.ak.SetAccount(ctx, acc) + spkm.ak.SetAccount(sdkCtx, acc) } // Also emit the following events, so that txs can be indexed by these @@ -101,7 +100,7 @@ func (spkd SetPubKeyDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate b // - concat(address,"/",sequence) (via `tx.acc_seq='cosmos1abc...def/42'`). sigs, err := sigTx.GetSignaturesV2() if err != nil { - return ctx, err + return err } var events sdk.Events @@ -112,7 +111,7 @@ func (spkd SetPubKeyDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate b sigBzs, err := signatureDataToBz(sig.Data) if err != nil { - return ctx, err + return err } for _, sigBz := range sigBzs { events = append(events, sdk.NewEvent(sdk.EventTypeTx, @@ -121,37 +120,206 @@ func (spkd SetPubKeyDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate b } } - ctx.EventManager().EmitEvents(events) + sdkCtx.EventManager().EmitEvents(events) + + return nil +} + +// CheckTx implements tx.Handler.CheckTx. +func (spkm setPubKeyTxHandler) CheckTx(ctx context.Context, tx sdk.Tx, req abci.RequestCheckTx) (abci.ResponseCheckTx, error) { + if err := spkm.setPubKey(ctx, tx, false); err != nil { + return abci.ResponseCheckTx{}, err + } - return next(ctx, tx, simulate) + return spkm.next.CheckTx(ctx, tx, req) } -// 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 +// DeliverTx implements tx.Handler.DeliverTx. +func (spkm setPubKeyTxHandler) DeliverTx(ctx context.Context, tx sdk.Tx, req abci.RequestDeliverTx) (abci.ResponseDeliverTx, error) { + if err := spkm.setPubKey(ctx, tx, false); err != nil { + return abci.ResponseDeliverTx{}, err + } + return spkm.next.DeliverTx(ctx, tx, req) +} + +// SimulateTx implements tx.Handler.SimulateTx. +func (spkm setPubKeyTxHandler) SimulateTx(ctx context.Context, sdkTx sdk.Tx, req tx.RequestSimulateTx) (tx.ResponseSimulateTx, error) { + if err := spkm.setPubKey(ctx, sdkTx, true); err != nil { + return tx.ResponseSimulateTx{}, err + } + return spkm.next.SimulateTx(ctx, sdkTx, req) +} + +var _ tx.Handler = validateSigCountTxHandler{} + +type validateSigCountTxHandler struct { + ak AccountKeeper + next tx.Handler +} + +// ValidateSigCountMiddleware takes in Params and returns errors if there are too many signatures in the tx for the given params +// otherwise it calls next middleware +// Use this middleware to set parameterized limit on number of signatures in tx // CONTRACT: Tx must implement SigVerifiableTx interface -type SigGasConsumeDecorator struct { +func ValidateSigCountMiddleware(ak AccountKeeper) tx.Middleware { + return func(txh tx.Handler) tx.Handler { + return validateSigCountTxHandler{ + ak: ak, + next: txh, + } + } +} + +func (vscd validateSigCountTxHandler) checkSigCount(ctx context.Context, tx sdk.Tx) error { + sdkCtx := sdk.UnwrapSDKContext(ctx) + + sigTx, ok := tx.(authsigning.SigVerifiableTx) + if !ok { + return sdkerrors.Wrap(sdkerrors.ErrTxDecode, "Tx must be a sigTx") + } + + params := vscd.ak.GetParams(sdkCtx) + pubKeys, err := sigTx.GetPubKeys() + if err != nil { + return err + } + + sigCount := 0 + for _, pk := range pubKeys { + sigCount += CountSubKeys(pk) + if uint64(sigCount) > params.TxSigLimit { + return sdkerrors.Wrapf(sdkerrors.ErrTooManySignatures, + "signatures: %d, limit: %d", sigCount, params.TxSigLimit) + } + } + return nil +} + +// CheckTx implements tx.Handler.CheckTx. +func (vscd validateSigCountTxHandler) CheckTx(ctx context.Context, tx sdk.Tx, req abci.RequestCheckTx) (abci.ResponseCheckTx, error) { + if err := vscd.checkSigCount(ctx, tx); err != nil { + return abci.ResponseCheckTx{}, err + } + + return vscd.next.CheckTx(ctx, tx, req) +} + +// DeliverTx implements tx.Handler.DeliverTx. +func (vscd validateSigCountTxHandler) SimulateTx(ctx context.Context, sdkTx sdk.Tx, req tx.RequestSimulateTx) (tx.ResponseSimulateTx, error) { + if err := vscd.checkSigCount(ctx, sdkTx); err != nil { + return tx.ResponseSimulateTx{}, err + } + + return vscd.next.SimulateTx(ctx, sdkTx, req) +} + +// SimulateTx implements tx.Handler.SimulateTx. +func (vscd validateSigCountTxHandler) DeliverTx(ctx context.Context, tx sdk.Tx, req abci.RequestDeliverTx) (abci.ResponseDeliverTx, error) { + if err := vscd.checkSigCount(ctx, tx); err != nil { + return abci.ResponseDeliverTx{}, err + } + + return vscd.next.DeliverTx(ctx, tx, req) +} + +// DefaultSigVerificationGasConsumer is the default implementation of SignatureVerificationGasConsumer. It consumes gas +// for signature verification based upon the public key type. The cost is fetched from the given params and is matched +// by the concrete type. +func DefaultSigVerificationGasConsumer( + meter sdk.GasMeter, sig signing.SignatureV2, params types.Params, +) error { + pubkey := sig.PubKey + switch pubkey := pubkey.(type) { + case *ed25519.PubKey: + meter.ConsumeGas(params.SigVerifyCostED25519, "ante verify: ed25519") + return sdkerrors.Wrap(sdkerrors.ErrInvalidPubKey, "ED25519 public keys are unsupported") + + case *secp256k1.PubKey: + meter.ConsumeGas(params.SigVerifyCostSecp256k1, "ante verify: secp256k1") + return nil + + case *secp256r1.PubKey: + meter.ConsumeGas(params.SigVerifyCostSecp256r1(), "ante verify: secp256r1") + return nil + + case multisig.PubKey: + multisignature, ok := sig.Data.(*signing.MultiSignatureData) + if !ok { + return fmt.Errorf("expected %T, got, %T", &signing.MultiSignatureData{}, sig.Data) + } + err := ConsumeMultisignatureVerificationGas(meter, multisignature, pubkey, params, sig.Sequence) + if err != nil { + return err + } + return nil + + default: + return sdkerrors.Wrapf(sdkerrors.ErrInvalidPubKey, "unrecognized public key type: %T", pubkey) + } +} + +// ConsumeMultisignatureVerificationGas consumes gas from a GasMeter for verifying a multisig pubkey signature +func ConsumeMultisignatureVerificationGas( + meter sdk.GasMeter, sig *signing.MultiSignatureData, pubkey multisig.PubKey, + params types.Params, accSeq uint64, +) error { + + size := sig.BitArray.Count() + sigIndex := 0 + + for i := 0; i < size; i++ { + if !sig.BitArray.GetIndex(i) { + continue + } + sigV2 := signing.SignatureV2{ + PubKey: pubkey.GetPubKeys()[i], + Data: sig.Signatures[sigIndex], + Sequence: accSeq, + } + err := DefaultSigVerificationGasConsumer(meter, sigV2, params) + if err != nil { + return err + } + sigIndex++ + } + + return nil +} + +var _ tx.Handler = sigGasConsumeTxHandler{} + +type sigGasConsumeTxHandler struct { ak AccountKeeper sigGasConsumer SignatureVerificationGasConsumer + next tx.Handler } -func NewSigGasConsumeDecorator(ak AccountKeeper, sigGasConsumer SignatureVerificationGasConsumer) SigGasConsumeDecorator { - return SigGasConsumeDecorator{ - ak: ak, - sigGasConsumer: sigGasConsumer, +// SigGasConsumeMiddleware consumes parameter-defined amount of gas for each signature according to the passed-in SignatureVerificationGasConsumer function +// before calling the next middleware +// CONTRACT: Pubkeys are set in context for all signers before this middleware runs +// CONTRACT: Tx must implement SigVerifiableTx interface +func SigGasConsumeMiddleware(ak AccountKeeper, sigGasConsumer SignatureVerificationGasConsumer) tx.Middleware { + return func(h tx.Handler) tx.Handler { + return sigGasConsumeTxHandler{ + ak: ak, + sigGasConsumer: sigGasConsumer, + next: h, + } } } -func (sgcd SigGasConsumeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) { +func (sgcm sigGasConsumeTxHandler) sigGasConsume(ctx context.Context, tx sdk.Tx, simulate bool) error { + sdkCtx := sdk.UnwrapSDKContext(ctx) + sigTx, ok := tx.(authsigning.SigVerifiableTx) if !ok { - return ctx, sdkerrors.Wrap(sdkerrors.ErrTxDecode, "invalid transaction type") + return sdkerrors.Wrap(sdkerrors.ErrTxDecode, "invalid transaction type") } - params := sgcd.ak.GetParams(ctx) + params := sgcm.ak.GetParams(sdkCtx) sigs, err := sigTx.GetSignaturesV2() if err != nil { - return ctx, err + return err } // stdSigs contains the sequence number, account number, and signatures. @@ -159,9 +327,9 @@ func (sgcd SigGasConsumeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simula signerAddrs := sigTx.GetSigners() for i, sig := range sigs { - signerAcc, err := GetSignerAcc(ctx, sgcd.ak, signerAddrs[i]) + signerAcc, err := GetSignerAcc(sdkCtx, sgcm.ak, signerAddrs[i]) if err != nil { - return ctx, err + return err } pubKey := signerAcc.GetPubKey() @@ -181,29 +349,62 @@ func (sgcd SigGasConsumeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simula Sequence: sig.Sequence, } - err = sgcd.sigGasConsumer(ctx.GasMeter(), sig, params) + err = sgcm.sigGasConsumer(sdkCtx.GasMeter(), sig, params) if err != nil { - return ctx, err + return err } } - return next(ctx, tx, simulate) + return nil } -// Verify all signatures for a tx and return an error if any are invalid. Note, -// the SigVerificationDecorator decorator will not get executed on ReCheck. -// -// CONTRACT: Pubkeys are set in context for all signers before this decorator runs -// CONTRACT: Tx must implement SigVerifiableTx interface -type SigVerificationDecorator struct { +// CheckTx implements tx.Handler.CheckTx. +func (sgcm sigGasConsumeTxHandler) CheckTx(ctx context.Context, tx sdk.Tx, req abci.RequestCheckTx) (abci.ResponseCheckTx, error) { + if err := sgcm.sigGasConsume(ctx, tx, false); err != nil { + return abci.ResponseCheckTx{}, err + } + + return sgcm.next.CheckTx(ctx, tx, req) +} + +// DeliverTx implements tx.Handler.DeliverTx. +func (sgcm sigGasConsumeTxHandler) DeliverTx(ctx context.Context, tx sdk.Tx, req abci.RequestDeliverTx) (abci.ResponseDeliverTx, error) { + if err := sgcm.sigGasConsume(ctx, tx, false); err != nil { + return abci.ResponseDeliverTx{}, err + } + + return sgcm.next.DeliverTx(ctx, tx, req) +} + +// SimulateTx implements tx.Handler.SimulateTx. +func (sgcm sigGasConsumeTxHandler) SimulateTx(ctx context.Context, sdkTx sdk.Tx, req tx.RequestSimulateTx) (tx.ResponseSimulateTx, error) { + if err := sgcm.sigGasConsume(ctx, sdkTx, true); err != nil { + return tx.ResponseSimulateTx{}, err + } + + return sgcm.next.SimulateTx(ctx, sdkTx, req) +} + +var _ tx.Handler = sigVerificationTxHandler{} + +type sigVerificationTxHandler struct { ak AccountKeeper signModeHandler authsigning.SignModeHandler + next tx.Handler } -func NewSigVerificationDecorator(ak AccountKeeper, signModeHandler authsigning.SignModeHandler) SigVerificationDecorator { - return SigVerificationDecorator{ - ak: ak, - signModeHandler: signModeHandler, +// SigVerificationMiddleware verifies all signatures for a tx and return an error if any are invalid. Note, +// the sigVerificationTxHandler middleware will not get executed on ReCheck. +// +// CONTRACT: Pubkeys are set in context for all signers before this middleware runs +// CONTRACT: Tx must implement SigVerifiableTx interface +func SigVerificationMiddleware(ak AccountKeeper, signModeHandler authsigning.SignModeHandler) tx.Middleware { + return func(h tx.Handler) tx.Handler { + return sigVerificationTxHandler{ + ak: ak, + signModeHandler: signModeHandler, + next: h, + } } } @@ -211,7 +412,7 @@ func NewSigVerificationDecorator(ak AccountKeeper, signModeHandler authsigning.S // 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. +// in the SigVerificationMiddleware's middleware function. func OnlyLegacyAminoSigners(sigData signing.SignatureData) bool { switch v := sigData.(type) { case *signing.SingleSignatureData: @@ -228,53 +429,54 @@ func OnlyLegacyAminoSigners(sigData signing.SignatureData) bool { } } -func (svd SigVerificationDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) { +func (svm sigVerificationTxHandler) sigVerify(ctx context.Context, tx sdk.Tx, isReCheckTx, simulate bool) error { + sdkCtx := sdk.UnwrapSDKContext(ctx) // no need to verify signatures on recheck tx - if ctx.IsReCheckTx() { - return next(ctx, tx, simulate) + if isReCheckTx { + return nil } sigTx, ok := tx.(authsigning.SigVerifiableTx) if !ok { - return ctx, sdkerrors.Wrap(sdkerrors.ErrTxDecode, "invalid transaction type") + return 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 + return 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)) + return 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]) + acc, err := GetSignerAcc(sdkCtx, svm.ak, signerAddrs[i]) if err != nil { - return ctx, err + return err } // retrieve pubkey pubKey := acc.GetPubKey() if !simulate && pubKey == nil { - return ctx, sdkerrors.Wrap(sdkerrors.ErrInvalidPubKey, "pubkey on account is not set") + return sdkerrors.Wrap(sdkerrors.ErrInvalidPubKey, "pubkey on account is not set") } // Check account sequence number. if sig.Sequence != acc.GetSequence() { - return ctx, sdkerrors.Wrapf( + return 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() + genesis := sdkCtx.BlockHeight() == 0 + chainID := sdkCtx.ChainID() var accNum uint64 if !genesis { accNum = acc.GetAccountNumber() @@ -286,7 +488,7 @@ func (svd SigVerificationDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simul } if !simulate { - err := authsigning.VerifySignature(pubKey, signerData, sig.Data, svd.signModeHandler, tx) + err := authsigning.VerifySignature(pubKey, signerData, sig.Data, svm.signModeHandler, tx) if err != nil { var errMsg string if OnlyLegacyAminoSigners(sig.Data) { @@ -296,153 +498,112 @@ func (svd SigVerificationDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simul } 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 sdkerrors.Wrap(sdkerrors.ErrUnauthorized, errMsg) } } } - return next(ctx, tx, simulate) + return nil +} + +// CheckTx implements tx.Handler.CheckTx. +func (svd sigVerificationTxHandler) CheckTx(ctx context.Context, tx sdk.Tx, req abci.RequestCheckTx) (abci.ResponseCheckTx, error) { + if err := svd.sigVerify(ctx, tx, req.Type == abci.CheckTxType_Recheck, false); err != nil { + return abci.ResponseCheckTx{}, err + } + + return svd.next.CheckTx(ctx, tx, req) +} + +// DeliverTx implements tx.Handler.DeliverTx. +func (svd sigVerificationTxHandler) DeliverTx(ctx context.Context, tx sdk.Tx, req abci.RequestDeliverTx) (abci.ResponseDeliverTx, error) { + if err := svd.sigVerify(ctx, tx, false, false); err != nil { + return abci.ResponseDeliverTx{}, err + } + + return svd.next.DeliverTx(ctx, tx, req) +} + +// SimulateTx implements tx.Handler.SimulateTx. +func (svd sigVerificationTxHandler) SimulateTx(ctx context.Context, sdkTx sdk.Tx, req tx.RequestSimulateTx) (tx.ResponseSimulateTx, error) { + if err := svd.sigVerify(ctx, sdkTx, false, true); err != nil { + return tx.ResponseSimulateTx{}, err + } + + return svd.next.SimulateTx(ctx, sdkTx, req) +} + +var _ tx.Handler = incrementSequenceTxHandler{} + +type incrementSequenceTxHandler struct { + ak AccountKeeper + next tx.Handler } -// IncrementSequenceDecorator handles incrementing sequences of all signers. -// Use the IncrementSequenceDecorator decorator to prevent replay attacks. Note, -// there is no need to execute IncrementSequenceDecorator on RecheckTX since +// IncrementSequenceMiddleware handles incrementing sequences of all signers. +// Use the incrementSequenceTxHandler middleware to prevent replay attacks. Note, +// there is no need to execute incrementSequenceTxHandler on RecheckTX since // CheckTx would already bump the sequence number. // // NOTE: Since CheckTx and DeliverTx state are managed separately, subsequent and // sequential txs orginating from the same account cannot be handled correctly in // a reliable way unless sequence numbers are managed and tracked manually by a // client. It is recommended to instead use multiple messages in a tx. -type IncrementSequenceDecorator struct { - ak AccountKeeper -} - -func NewIncrementSequenceDecorator(ak AccountKeeper) IncrementSequenceDecorator { - return IncrementSequenceDecorator{ - ak: ak, +func IncrementSequenceMiddleware(ak AccountKeeper) tx.Middleware { + return func(h tx.Handler) tx.Handler { + return incrementSequenceTxHandler{ + ak: ak, + next: h, + } } } -func (isd IncrementSequenceDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) { +func (isd incrementSequenceTxHandler) incrementSeq(ctx context.Context, tx sdk.Tx) error { + sdkCtx := sdk.UnwrapSDKContext(ctx) sigTx, ok := tx.(authsigning.SigVerifiableTx) if !ok { - return ctx, sdkerrors.Wrap(sdkerrors.ErrTxDecode, "invalid transaction type") + return sdkerrors.Wrap(sdkerrors.ErrTxDecode, "invalid transaction type") } // increment sequence of all signers for _, addr := range sigTx.GetSigners() { - acc := isd.ak.GetAccount(ctx, addr) + acc := isd.ak.GetAccount(sdkCtx, addr) if err := acc.SetSequence(acc.GetSequence() + 1); err != nil { panic(err) } - isd.ak.SetAccount(ctx, acc) + isd.ak.SetAccount(sdkCtx, 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, - } + return nil } -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") +// CheckTx implements tx.Handler.CheckTx. +func (isd incrementSequenceTxHandler) CheckTx(ctx context.Context, tx sdk.Tx, req abci.RequestCheckTx) (abci.ResponseCheckTx, error) { + if err := isd.incrementSeq(ctx, tx); err != nil { + return abci.ResponseCheckTx{}, err } - 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) + return isd.next.CheckTx(ctx, tx, req) } -// DefaultSigVerificationGasConsumer is the default implementation of SignatureVerificationGasConsumer. It consumes gas -// for signature verification based upon the public key type. The cost is fetched from the given params and is matched -// by the concrete type. -func DefaultSigVerificationGasConsumer( - meter sdk.GasMeter, sig signing.SignatureV2, params types.Params, -) error { - pubkey := sig.PubKey - switch pubkey := pubkey.(type) { - case *ed25519.PubKey: - meter.ConsumeGas(params.SigVerifyCostED25519, "ante verify: ed25519") - return sdkerrors.Wrap(sdkerrors.ErrInvalidPubKey, "ED25519 public keys are unsupported") - - case *secp256k1.PubKey: - meter.ConsumeGas(params.SigVerifyCostSecp256k1, "ante verify: secp256k1") - return nil - - case *secp256r1.PubKey: - meter.ConsumeGas(params.SigVerifyCostSecp256r1(), "ante verify: secp256r1") - return nil - - case multisig.PubKey: - multisignature, ok := sig.Data.(*signing.MultiSignatureData) - if !ok { - return fmt.Errorf("expected %T, got, %T", &signing.MultiSignatureData{}, sig.Data) - } - err := ConsumeMultisignatureVerificationGas(meter, multisignature, pubkey, params, sig.Sequence) - if err != nil { - return err - } - return nil - - default: - return sdkerrors.Wrapf(sdkerrors.ErrInvalidPubKey, "unrecognized public key type: %T", pubkey) +// DeliverTx implements tx.Handler.DeliverTx. +func (isd incrementSequenceTxHandler) DeliverTx(ctx context.Context, tx sdk.Tx, req abci.RequestDeliverTx) (abci.ResponseDeliverTx, error) { + if err := isd.incrementSeq(ctx, tx); err != nil { + return abci.ResponseDeliverTx{}, err } -} -// 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 + return isd.next.DeliverTx(ctx, tx, req) +} - 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++ +// SimulateTx implements tx.Handler.SimulateTx. +func (isd incrementSequenceTxHandler) SimulateTx(ctx context.Context, sdkTx sdk.Tx, req tx.RequestSimulateTx) (tx.ResponseSimulateTx, error) { + if err := isd.incrementSeq(ctx, sdkTx); err != nil { + return tx.ResponseSimulateTx{}, err } - return nil + return isd.next.SimulateTx(ctx, sdkTx, req) } // GetSignerAcc returns an account for a given address that is expected to sign diff --git a/x/auth/ante/sigverify_benchmark_test.go b/x/auth/middleware/sigverify_benchmark_test.go similarity index 89% rename from x/auth/ante/sigverify_benchmark_test.go rename to x/auth/middleware/sigverify_benchmark_test.go index 56e596fa6b55..dc635985170b 100644 --- a/x/auth/ante/sigverify_benchmark_test.go +++ b/x/auth/middleware/sigverify_benchmark_test.go @@ -1,4 +1,4 @@ -package ante_test +package middleware_test import ( "testing" @@ -10,7 +10,7 @@ import ( "github.com/cosmos/cosmos-sdk/crypto/keys/secp256r1" ) -// This benchmark is used to asses the ante.Secp256k1ToR1GasFactor value +// This benchmark is used to asses the middleware.Secp256k1ToR1GasFactor value func BenchmarkSig(b *testing.B) { require := require.New(b) msg := tmcrypto.CRandBytes(1000) diff --git a/x/auth/ante/sigverify_test.go b/x/auth/middleware/sigverify_test.go similarity index 53% rename from x/auth/ante/sigverify_test.go rename to x/auth/middleware/sigverify_test.go index 074f4c33afc1..59c46938f619 100644 --- a/x/auth/ante/sigverify_test.go +++ b/x/auth/middleware/sigverify_test.go @@ -1,10 +1,10 @@ -package ante_test +package middleware_test import ( "fmt" "github.com/cosmos/cosmos-sdk/client" - "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/codec/legacy" "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" @@ -14,16 +14,22 @@ import ( "github.com/cosmos/cosmos-sdk/simapp" "github.com/cosmos/cosmos-sdk/testutil/testdata" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/tx" "github.com/cosmos/cosmos-sdk/types/tx/signing" - "github.com/cosmos/cosmos-sdk/x/auth/ante" + "github.com/cosmos/cosmos-sdk/x/auth/middleware" "github.com/cosmos/cosmos-sdk/x/auth/migrations/legacytx" "github.com/cosmos/cosmos-sdk/x/auth/types" + abci "github.com/tendermint/tendermint/abci/types" ) -func (suite *AnteTestSuite) TestSetPubKey() { - suite.SetupTest(true) // setup - require := suite.Require() - suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() +func (s *MWTestSuite) TestSetPubKey() { + ctx := s.SetupTest(true) // setup + txBuilder := s.clientCtx.TxConfig.NewTxBuilder() + require := s.Require() + txHandler := middleware.ComposeMiddlewares( + noopTxHandler{}, + middleware.SetPubKeyMiddleware(s.app.AccountKeeper), + ) // keys and addresses priv1, pub1, addr1 := testdata.KeyTestPubAddr() @@ -36,35 +42,32 @@ func (suite *AnteTestSuite) TestSetPubKey() { msgs := make([]sdk.Msg, len(addrs)) // set accounts and create msg for each address for i, addr := range addrs { - acc := suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, addr) + acc := s.app.AccountKeeper.NewAccountWithAddress(ctx, addr) require.NoError(acc.SetAccountNumber(uint64(i))) - suite.app.AccountKeeper.SetAccount(suite.ctx, acc) + s.app.AccountKeeper.SetAccount(ctx, acc) msgs[i] = testdata.NewTestMsg(addr) } - require.NoError(suite.txBuilder.SetMsgs(msgs...)) - suite.txBuilder.SetFeeAmount(testdata.NewTestFeeAmount()) - suite.txBuilder.SetGasLimit(testdata.NewTestGasLimit()) + require.NoError(txBuilder.SetMsgs(msgs...)) + txBuilder.SetFeeAmount(testdata.NewTestFeeAmount()) + 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()) + testTx, _, err := s.createTestTx(txBuilder, privs, accNums, accSeqs, ctx.ChainID()) require.NoError(err) - spkd := ante.NewSetPubKeyDecorator(suite.app.AccountKeeper) - antehandler := sdk.ChainAnteDecorators(spkd) - - ctx, err := antehandler(suite.ctx, tx, false) + _, err = txHandler.DeliverTx(sdk.WrapSDKContext(ctx), testTx, abci.RequestDeliverTx{}) require.NoError(err) - // Require that all accounts have pubkey set after Decorator runs + // Require that all accounts have pubkey set after middleware runs for i, addr := range addrs { - pk, err := suite.app.AccountKeeper.GetPubKey(ctx, addr) + pk, err := s.app.AccountKeeper.GetPubKey(ctx, addr) require.NoError(err, "Error on retrieving pubkey from account") require.True(pubs[i].Equals(pk), "Wrong Pubkey retrieved from AccountKeeper, idx=%d\nexpected=%s\n got=%s", i, pubs[i], pk) } } -func (suite *AnteTestSuite) TestConsumeSignatureVerificationGas() { +func (s *MWTestSuite) TestConsumeSignatureVerificationGas() { params := types.DefaultParams() msg := []byte{1, 2, 3, 4} cdc := simapp.MakeTestEncodingConfig().Amino @@ -78,9 +81,9 @@ func (suite *AnteTestSuite) TestConsumeSignatureVerificationGas() { for i := 0; i < len(pkSet1); i++ { stdSig := legacytx.StdSignature{PubKey: pkSet1[i], Signature: sigSet1[i]} sigV2, err := legacytx.StdSignatureToSignatureV2(cdc, stdSig) - suite.Require().NoError(err) + s.Require().NoError(err) err = multisig.AddSignatureV2(multisignature1, sigV2, pkSet1) - suite.Require().NoError(err) + s.Require().NoError(err) } type args struct { @@ -107,23 +110,30 @@ func (suite *AnteTestSuite) TestConsumeSignatureVerificationGas() { Data: tt.args.sig, Sequence: 0, // Arbitrary account sequence } - err := ante.DefaultSigVerificationGasConsumer(tt.args.meter, sigV2, tt.args.params) + err := middleware.DefaultSigVerificationGasConsumer(tt.args.meter, sigV2, tt.args.params) if tt.shouldErr { - suite.Require().NotNil(err) + s.Require().NotNil(err) } else { - suite.Require().Nil(err) - suite.Require().Equal(tt.gasConsumed, tt.args.meter.GasConsumed(), fmt.Sprintf("%d != %d", tt.gasConsumed, tt.args.meter.GasConsumed())) + s.Require().Nil(err) + s.Require().Equal(tt.gasConsumed, tt.args.meter.GasConsumed(), fmt.Sprintf("%d != %d", tt.gasConsumed, tt.args.meter.GasConsumed())) } } } -func (suite *AnteTestSuite) TestSigVerification() { - suite.SetupTest(true) // setup - suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() +func (s *MWTestSuite) TestSigVerification() { + ctx := s.SetupTest(true) // setup // make block height non-zero to ensure account numbers part of signBytes - suite.ctx = suite.ctx.WithBlockHeight(1) + ctx = ctx.WithBlockHeight(1) + txHandler := middleware.ComposeMiddlewares( + noopTxHandler{}, + middleware.SetPubKeyMiddleware(s.app.AccountKeeper), + middleware.SigVerificationMiddleware( + s.app.AccountKeeper, + s.clientCtx.TxConfig.SignModeHandler(), + ), + ) // keys and addresses priv1, _, addr1 := testdata.KeyTestPubAddr() @@ -135,19 +145,15 @@ func (suite *AnteTestSuite) TestSigVerification() { msgs := make([]sdk.Msg, len(addrs)) // set accounts and create msg for each address for i, addr := range addrs { - acc := suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, addr) - suite.Require().NoError(acc.SetAccountNumber(uint64(i))) - suite.app.AccountKeeper.SetAccount(suite.ctx, acc) + acc := s.app.AccountKeeper.NewAccountWithAddress(ctx, addr) + s.Require().NoError(acc.SetAccountNumber(uint64(i))) + s.app.AccountKeeper.SetAccount(ctx, acc) msgs[i] = testdata.NewTestMsg(addr) } feeAmount := testdata.NewTestFeeAmount() gasLimit := testdata.NewTestGasLimit() - spkd := ante.NewSetPubKeyDecorator(suite.app.AccountKeeper) - svd := ante.NewSigVerificationDecorator(suite.app.AccountKeeper, suite.clientCtx.TxConfig.SignModeHandler()) - antehandler := sdk.ChainAnteDecorators(spkd, svd) - type testCase struct { name string privs []cryptotypes.PrivKey @@ -166,21 +172,25 @@ func (suite *AnteTestSuite) TestSigVerification() { {"no err on recheck", []cryptotypes.PrivKey{}, []uint64{}, []uint64{}, true, false}, } for i, tc := range testCases { - suite.ctx = suite.ctx.WithIsReCheckTx(tc.recheck) - suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() // Create new txBuilder for each test + ctx = ctx.WithIsReCheckTx(tc.recheck) + txBuilder := s.clientCtx.TxConfig.NewTxBuilder() // Create new txBuilder for each test - suite.Require().NoError(suite.txBuilder.SetMsgs(msgs...)) - suite.txBuilder.SetFeeAmount(feeAmount) - suite.txBuilder.SetGasLimit(gasLimit) + s.Require().NoError(txBuilder.SetMsgs(msgs...)) + txBuilder.SetFeeAmount(feeAmount) + txBuilder.SetGasLimit(gasLimit) - tx, err := suite.CreateTestTx(tc.privs, tc.accNums, tc.accSeqs, suite.ctx.ChainID()) - suite.Require().NoError(err) + testTx, _, err := s.createTestTx(txBuilder, tc.privs, tc.accNums, tc.accSeqs, ctx.ChainID()) + s.Require().NoError(err) - _, err = antehandler(suite.ctx, tx, false) + if tc.recheck { + _, err = txHandler.CheckTx(sdk.WrapSDKContext(ctx), testTx, abci.RequestCheckTx{Type: abci.CheckTxType_Recheck}) + } else { + _, err = txHandler.CheckTx(sdk.WrapSDKContext(ctx), testTx, abci.RequestCheckTx{}) + } if tc.shouldErr { - suite.Require().NotNil(err, "TestCase %d: %s did not error as expected", i, tc.name) + s.Require().NotNil(err, "TestCase %d: %s did not error as expected", i, tc.name) } else { - suite.Require().Nil(err, "TestCase %d: %s errored unexpectedly. Err: %v", i, tc.name, err) + s.Require().Nil(err, "TestCase %d: %s errored unexpectedly. Err: %v", i, tc.name, err) } } } @@ -191,35 +201,23 @@ func (suite *AnteTestSuite) TestSigVerification() { // this, since it'll be handled by the test matrix. // In the meantime, we want to make double-sure amino compatibility works. // ref: https://github.com/cosmos/cosmos-sdk/issues/7229 -func (suite *AnteTestSuite) TestSigVerification_ExplicitAmino() { - suite.app, suite.ctx = createTestApp(suite.T(), true) - suite.ctx = suite.ctx.WithBlockHeight(1) +func (s *MWTestSuite) TestSigVerification_ExplicitAmino() { + ctx := s.SetupTest(true) + ctx = ctx.WithBlockHeight(1) // Set up TxConfig. - aminoCdc := codec.NewLegacyAmino() + aminoCdc := legacy.Cdc + aminoCdc.RegisterInterface((*sdk.Msg)(nil), nil) + aminoCdc.RegisterConcrete(&testdata.TestMsg{}, "testdata.TestMsg", nil) + // We're using TestMsg amino encoding in some tests, so register it here. txConfig := legacytx.StdTxConfig{Cdc: aminoCdc} - suite.clientCtx = client.Context{}. + s.clientCtx = client.Context{}. WithTxConfig(txConfig) - anteHandler, err := ante.NewAnteHandler( - ante.HandlerOptions{ - AccountKeeper: suite.app.AccountKeeper, - BankKeeper: suite.app.BankKeeper, - FeegrantKeeper: suite.app.FeeGrantKeeper, - SignModeHandler: txConfig.SignModeHandler(), - SigGasConsumer: ante.DefaultSigVerificationGasConsumer, - }, - ) - - suite.Require().NoError(err) - suite.anteHandler = anteHandler - - suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() - // make block height non-zero to ensure account numbers part of signBytes - suite.ctx = suite.ctx.WithBlockHeight(1) + ctx = ctx.WithBlockHeight(1) // keys and addresses priv1, _, addr1 := testdata.KeyTestPubAddr() @@ -231,18 +229,23 @@ func (suite *AnteTestSuite) TestSigVerification_ExplicitAmino() { msgs := make([]sdk.Msg, len(addrs)) // set accounts and create msg for each address for i, addr := range addrs { - acc := suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, addr) - suite.Require().NoError(acc.SetAccountNumber(uint64(i))) - suite.app.AccountKeeper.SetAccount(suite.ctx, acc) + acc := s.app.AccountKeeper.NewAccountWithAddress(ctx, addr) + s.Require().NoError(acc.SetAccountNumber(uint64(i))) + s.app.AccountKeeper.SetAccount(ctx, acc) msgs[i] = testdata.NewTestMsg(addr) } feeAmount := testdata.NewTestFeeAmount() gasLimit := testdata.NewTestGasLimit() - spkd := ante.NewSetPubKeyDecorator(suite.app.AccountKeeper) - svd := ante.NewSigVerificationDecorator(suite.app.AccountKeeper, suite.clientCtx.TxConfig.SignModeHandler()) - antehandler := sdk.ChainAnteDecorators(spkd, svd) + txHandler := middleware.ComposeMiddlewares( + noopTxHandler{}, + middleware.SetPubKeyMiddleware(s.app.AccountKeeper), + middleware.SigVerificationMiddleware( + s.app.AccountKeeper, + s.clientCtx.TxConfig.SignModeHandler(), + ), + ) type testCase struct { name string @@ -252,6 +255,7 @@ func (suite *AnteTestSuite) TestSigVerification_ExplicitAmino() { 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}, @@ -261,27 +265,32 @@ func (suite *AnteTestSuite) TestSigVerification_ExplicitAmino() { {"valid tx", []cryptotypes.PrivKey{priv1, priv2, priv3}, []uint64{0, 1, 2}, []uint64{0, 0, 0}, false, false}, {"no err on recheck", []cryptotypes.PrivKey{}, []uint64{}, []uint64{}, true, false}, } + for i, tc := range testCases { - suite.ctx = suite.ctx.WithIsReCheckTx(tc.recheck) - suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() // Create new txBuilder for each test + ctx = ctx.WithIsReCheckTx(tc.recheck) + txBuilder := s.clientCtx.TxConfig.NewTxBuilder() // Create new txBuilder for each test - suite.Require().NoError(suite.txBuilder.SetMsgs(msgs...)) - suite.txBuilder.SetFeeAmount(feeAmount) - suite.txBuilder.SetGasLimit(gasLimit) + s.Require().NoError(txBuilder.SetMsgs(msgs...)) + txBuilder.SetFeeAmount(feeAmount) + txBuilder.SetGasLimit(gasLimit) - tx, err := suite.CreateTestTx(tc.privs, tc.accNums, tc.accSeqs, suite.ctx.ChainID()) - suite.Require().NoError(err) + testTx, _, err := s.createTestTx(txBuilder, tc.privs, tc.accNums, tc.accSeqs, ctx.ChainID()) + s.Require().NoError(err) - _, err = antehandler(suite.ctx, tx, false) + if tc.recheck { + _, err = txHandler.CheckTx(sdk.WrapSDKContext(ctx), testTx, abci.RequestCheckTx{Type: abci.CheckTxType_Recheck}) + } else { + _, err = txHandler.CheckTx(sdk.WrapSDKContext(ctx), testTx, abci.RequestCheckTx{}) + } if tc.shouldErr { - suite.Require().NotNil(err, "TestCase %d: %s did not error as expected", i, tc.name) + s.Require().NotNil(err, "TestCase %d: %s did not error as expected", i, tc.name) } else { - suite.Require().Nil(err, "TestCase %d: %s errored unexpectedly. Err: %v", i, tc.name, err) + s.Require().Nil(err, "TestCase %d: %s errored unexpectedly. Err: %v", i, tc.name, err) } } } -func (suite *AnteTestSuite) TestSigIntegration() { +func (s *MWTestSuite) TestSigIntegration() { // generate private keys privs := []cryptotypes.PrivKey{ secp256k1.GenPrivKey(), @@ -291,23 +300,23 @@ func (suite *AnteTestSuite) TestSigIntegration() { params := types.DefaultParams() initialSigCost := params.SigVerifyCostSecp256k1 - initialCost, err := suite.runSigDecorators(params, false, privs...) - suite.Require().Nil(err) + initialCost, err := s.runSigMiddlewares(params, false, privs...) + s.Require().Nil(err) params.SigVerifyCostSecp256k1 *= 2 - doubleCost, err := suite.runSigDecorators(params, false, privs...) - suite.Require().Nil(err) + doubleCost, err := s.runSigMiddlewares(params, false, privs...) + s.Require().Nil(err) - suite.Require().Equal(initialSigCost*uint64(len(privs)), doubleCost-initialCost) + s.Require().Equal(initialSigCost*uint64(len(privs)), doubleCost-initialCost) } -func (suite *AnteTestSuite) runSigDecorators(params types.Params, _ bool, privs ...cryptotypes.PrivKey) (sdk.Gas, error) { - suite.SetupTest(true) // setup - suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() +func (s *MWTestSuite) runSigMiddlewares(params types.Params, _ bool, privs ...cryptotypes.PrivKey) (sdk.Gas, error) { + ctx := s.SetupTest(true) // setup + txBuilder := s.clientCtx.TxConfig.NewTxBuilder() // Make block-height non-zero to include accNum in SignBytes - suite.ctx = suite.ctx.WithBlockHeight(1) - suite.app.AccountKeeper.SetParams(suite.ctx, params) + ctx = ctx.WithBlockHeight(1) + s.app.AccountKeeper.SetParams(ctx, params) msgs := make([]sdk.Msg, len(privs)) accNums := make([]uint64, len(privs)) @@ -315,76 +324,89 @@ func (suite *AnteTestSuite) runSigDecorators(params types.Params, _ bool, privs // set accounts and create msg for each address for i, priv := range privs { addr := sdk.AccAddress(priv.PubKey().Address()) - acc := suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, addr) - suite.Require().NoError(acc.SetAccountNumber(uint64(i))) - suite.app.AccountKeeper.SetAccount(suite.ctx, acc) + acc := s.app.AccountKeeper.NewAccountWithAddress(ctx, addr) + s.Require().NoError(acc.SetAccountNumber(uint64(i))) + s.app.AccountKeeper.SetAccount(ctx, acc) msgs[i] = testdata.NewTestMsg(addr) accNums[i] = uint64(i) accSeqs[i] = uint64(0) } - suite.Require().NoError(suite.txBuilder.SetMsgs(msgs...)) + s.Require().NoError(txBuilder.SetMsgs(msgs...)) feeAmount := testdata.NewTestFeeAmount() gasLimit := testdata.NewTestGasLimit() - suite.txBuilder.SetFeeAmount(feeAmount) - suite.txBuilder.SetGasLimit(gasLimit) - - tx, err := suite.CreateTestTx(privs, accNums, accSeqs, suite.ctx.ChainID()) - suite.Require().NoError(err) - - spkd := ante.NewSetPubKeyDecorator(suite.app.AccountKeeper) - svgc := ante.NewSigGasConsumeDecorator(suite.app.AccountKeeper, ante.DefaultSigVerificationGasConsumer) - svd := ante.NewSigVerificationDecorator(suite.app.AccountKeeper, suite.clientCtx.TxConfig.SignModeHandler()) - antehandler := sdk.ChainAnteDecorators(spkd, svgc, svd) + txBuilder.SetFeeAmount(feeAmount) + txBuilder.SetGasLimit(gasLimit) + + testTx, _, err := s.createTestTx(txBuilder, privs, accNums, accSeqs, ctx.ChainID()) + s.Require().NoError(err) + + txHandler := middleware.ComposeMiddlewares( + noopTxHandler{}, + middleware.SetPubKeyMiddleware(s.app.AccountKeeper), + middleware.SigGasConsumeMiddleware(s.app.AccountKeeper, middleware.DefaultSigVerificationGasConsumer), + middleware.SigVerificationMiddleware( + s.app.AccountKeeper, + s.clientCtx.TxConfig.SignModeHandler(), + ), + ) - // Determine gas consumption of antehandler with default params - before := suite.ctx.GasMeter().GasConsumed() - ctx, err := antehandler(suite.ctx, tx, false) + // Determine gas consumption of txhandler with default params + before := ctx.GasMeter().GasConsumed() + _, err = txHandler.DeliverTx(sdk.WrapSDKContext(ctx), testTx, abci.RequestDeliverTx{}) after := ctx.GasMeter().GasConsumed() return after - before, err } -func (suite *AnteTestSuite) TestIncrementSequenceDecorator() { - suite.SetupTest(true) // setup - suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() +func (s *MWTestSuite) TestIncrementSequenceMiddleware() { + ctx := s.SetupTest(true) // setup + txBuilder := s.clientCtx.TxConfig.NewTxBuilder() priv, _, addr := testdata.KeyTestPubAddr() - acc := suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, addr) - suite.Require().NoError(acc.SetAccountNumber(uint64(50))) - suite.app.AccountKeeper.SetAccount(suite.ctx, acc) + acc := s.app.AccountKeeper.NewAccountWithAddress(ctx, addr) + s.Require().NoError(acc.SetAccountNumber(uint64(50))) + s.app.AccountKeeper.SetAccount(ctx, acc) msgs := []sdk.Msg{testdata.NewTestMsg(addr)} - suite.Require().NoError(suite.txBuilder.SetMsgs(msgs...)) + s.Require().NoError(txBuilder.SetMsgs(msgs...)) privs := []cryptotypes.PrivKey{priv} - accNums := []uint64{suite.app.AccountKeeper.GetAccount(suite.ctx, addr).GetAccountNumber()} - accSeqs := []uint64{suite.app.AccountKeeper.GetAccount(suite.ctx, addr).GetSequence()} + accNums := []uint64{s.app.AccountKeeper.GetAccount(ctx, addr).GetAccountNumber()} + accSeqs := []uint64{s.app.AccountKeeper.GetAccount(ctx, addr).GetSequence()} feeAmount := testdata.NewTestFeeAmount() gasLimit := testdata.NewTestGasLimit() - suite.txBuilder.SetFeeAmount(feeAmount) - suite.txBuilder.SetGasLimit(gasLimit) + txBuilder.SetFeeAmount(feeAmount) + txBuilder.SetGasLimit(gasLimit) - tx, err := suite.CreateTestTx(privs, accNums, accSeqs, suite.ctx.ChainID()) - suite.Require().NoError(err) + testTx, _, err := s.createTestTx(txBuilder, privs, accNums, accSeqs, ctx.ChainID()) + s.Require().NoError(err) - isd := ante.NewIncrementSequenceDecorator(suite.app.AccountKeeper) - antehandler := sdk.ChainAnteDecorators(isd) + txHandler := middleware.ComposeMiddlewares( + noopTxHandler{}, + middleware.IncrementSequenceMiddleware(s.app.AccountKeeper), + ) 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}, + {ctx.WithIsReCheckTx(true), false, 1}, + {ctx.WithIsCheckTx(true).WithIsReCheckTx(false), false, 2}, + {ctx.WithIsReCheckTx(true), false, 3}, + {ctx.WithIsReCheckTx(true), false, 4}, + {ctx.WithIsReCheckTx(true), true, 5}, } for i, tc := range testCases { - _, err := antehandler(tc.ctx, tx, tc.simulate) - suite.Require().NoError(err, "unexpected error; tc #%d, %v", i, tc) - suite.Require().Equal(tc.expectedSeq, suite.app.AccountKeeper.GetAccount(suite.ctx, addr).GetSequence()) + var err error + if tc.simulate { + _, err = txHandler.SimulateTx(sdk.WrapSDKContext(tc.ctx), testTx, tx.RequestSimulateTx{}) + } else { + _, err = txHandler.DeliverTx(sdk.WrapSDKContext(tc.ctx), testTx, abci.RequestDeliverTx{}) + } + + s.Require().NoError(err, "unexpected error; tc #%d, %v", i, tc) + s.Require().Equal(tc.expectedSeq, s.app.AccountKeeper.GetAccount(ctx, addr).GetSequence()) } } diff --git a/x/auth/middleware/testutil_test.go b/x/auth/middleware/testutil_test.go index a2ba1c3cf164..11cfaf72c37e 100644 --- a/x/auth/middleware/testutil_test.go +++ b/x/auth/middleware/testutil_test.go @@ -1,18 +1,24 @@ package middleware_test import ( + "errors" + "fmt" "testing" "github.com/stretchr/testify/suite" + "github.com/tendermint/tendermint/abci/types" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/tx" + "github.com/cosmos/cosmos-sdk/codec" cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" "github.com/cosmos/cosmos-sdk/simapp" "github.com/cosmos/cosmos-sdk/testutil/testdata" sdk "github.com/cosmos/cosmos-sdk/types" + txtypes "github.com/cosmos/cosmos-sdk/types/tx" "github.com/cosmos/cosmos-sdk/types/tx/signing" + "github.com/cosmos/cosmos-sdk/x/auth/middleware" xauthsigning "github.com/cosmos/cosmos-sdk/x/auth/signing" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" minttypes "github.com/cosmos/cosmos-sdk/x/mint/types" @@ -30,12 +36,13 @@ type MWTestSuite struct { app *simapp.SimApp clientCtx client.Context + txHandler txtypes.Handler } // returns context and app with params set on account keeper func createTestApp(t *testing.T, isCheckTx bool) (*simapp.SimApp, sdk.Context) { app := simapp.Setup(t, isCheckTx) - ctx := app.BaseApp.NewContext(isCheckTx, tmproto.Header{}) + ctx := app.BaseApp.NewContext(isCheckTx, tmproto.Header{}).WithBlockGasMeter(sdk.NewInfiniteGasMeter()) app.AccountKeeper.SetParams(ctx, authtypes.DefaultParams()) return app, ctx @@ -54,14 +61,35 @@ func (s *MWTestSuite) SetupTest(isCheckTx bool) sdk.Context { testdata.RegisterInterfaces(encodingConfig.InterfaceRegistry) s.clientCtx = client.Context{}. - WithTxConfig(encodingConfig.TxConfig) + WithTxConfig(encodingConfig.TxConfig). + WithInterfaceRegistry(encodingConfig.InterfaceRegistry). + WithCodec(codec.NewAminoCodec(encodingConfig.Amino)) + + // We don't use simapp's own txHandler. For more flexibility (i.e. around + // using testdata), we create own own txHandler for this test suite. + msr := middleware.NewMsgServiceRouter(encodingConfig.InterfaceRegistry) + testdata.RegisterMsgServer(msr, testdata.MsgServerImpl{}) + legacyRouter := middleware.NewLegacyRouter() + legacyRouter.AddRoute(sdk.NewRoute((&testdata.TestMsg{}).Route(), func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) { return &sdk.Result{}, nil })) + txHandler, err := middleware.NewDefaultTxHandler(middleware.TxHandlerOptions{ + Debug: s.app.Trace(), + MsgServiceRouter: msr, + LegacyRouter: legacyRouter, + AccountKeeper: s.app.AccountKeeper, + BankKeeper: s.app.BankKeeper, + FeegrantKeeper: s.app.FeeGrantKeeper, + SignModeHandler: encodingConfig.TxConfig.SignModeHandler(), + SigGasConsumer: middleware.DefaultSigVerificationGasConsumer, + }) + s.Require().NoError(err) + s.txHandler = txHandler return ctx } -// CreatetestAccounts creates `numAccs` accounts, and return all relevant +// createTestAccounts creates `numAccs` accounts, and return all relevant // information about them including their private keys. -func (s *MWTestSuite) CreatetestAccounts(ctx sdk.Context, numAccs int) []testAccount { +func (s *MWTestSuite) createTestAccounts(ctx sdk.Context, numAccs int) []testAccount { var accounts []testAccount for i := 0; i < numAccs; i++ { @@ -137,6 +165,48 @@ func (s *MWTestSuite) createTestTx(txBuilder client.TxBuilder, privs []cryptotyp return txBuilder.GetTx(), txBytes, nil } +func (s *MWTestSuite) runTestCase(ctx sdk.Context, txBuilder client.TxBuilder, privs []cryptotypes.PrivKey, msgs []sdk.Msg, feeAmount sdk.Coins, gasLimit uint64, accNums, accSeqs []uint64, chainID string, tc TestCase) { + s.Run(fmt.Sprintf("Case %s", tc.desc), func() { + s.Require().NoError(txBuilder.SetMsgs(msgs...)) + txBuilder.SetFeeAmount(feeAmount) + txBuilder.SetGasLimit(gasLimit) + + // Theoretically speaking, middleware unit tests should only test + // middlewares, but here we sometimes also test the tx creation + // process. + tx, _, txErr := s.createTestTx(txBuilder, privs, accNums, accSeqs, chainID) + newCtx, txHandlerErr := s.txHandler.DeliverTx(sdk.WrapSDKContext(ctx), tx, types.RequestDeliverTx{}) + + if tc.expPass { + s.Require().NoError(txErr) + s.Require().NoError(txHandlerErr) + s.Require().NotNil(newCtx) + } else { + switch { + case txErr != nil: + s.Require().Error(txErr) + s.Require().True(errors.Is(txErr, tc.expErr)) + + case txHandlerErr != nil: + s.Require().Error(txHandlerErr) + s.Require().True(errors.Is(txHandlerErr, tc.expErr)) + + default: + s.Fail("expected one of txErr,txHandlerErr to be an error") + } + } + }) +} + +// TestCase represents a test case used in test tables. +type TestCase struct { + desc string + malleate func() + simulate bool + expPass bool + expErr error +} + func TestMWTestSuite(t *testing.T) { suite.Run(t, new(MWTestSuite)) } diff --git a/x/auth/signing/verify_test.go b/x/auth/signing/verify_test.go index 7a7f015dbef1..8ae55a3891a5 100644 --- a/x/auth/signing/verify_test.go +++ b/x/auth/signing/verify_test.go @@ -13,7 +13,7 @@ import ( "github.com/cosmos/cosmos-sdk/simapp" "github.com/cosmos/cosmos-sdk/testutil/testdata" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/auth/ante" + "github.com/cosmos/cosmos-sdk/x/auth/middleware" "github.com/cosmos/cosmos-sdk/x/auth/migrations/legacytx" "github.com/cosmos/cosmos-sdk/x/auth/signing" "github.com/cosmos/cosmos-sdk/x/auth/types" @@ -42,7 +42,8 @@ func TestVerifySignature(t *testing.T) { app.AccountKeeper.SetAccount(ctx, acc1) balances := sdk.NewCoins(sdk.NewInt64Coin("atom", 200)) require.NoError(t, testutil.FundAccount(app.BankKeeper, ctx, addr, balances)) - acc, err := ante.GetSignerAcc(ctx, app.AccountKeeper, addr) + acc, err := middleware.GetSignerAcc(ctx, app.AccountKeeper, addr) + require.NoError(t, err) require.NoError(t, testutil.FundAccount(app.BankKeeper, ctx, addr, balances)) msgs := []sdk.Msg{testdata.NewTestMsg(addr)} diff --git a/x/auth/tx/builder.go b/x/auth/tx/builder.go index 359c646087a5..6382f5272d1e 100644 --- a/x/auth/tx/builder.go +++ b/x/auth/tx/builder.go @@ -10,7 +10,7 @@ import ( sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/cosmos/cosmos-sdk/types/tx" "github.com/cosmos/cosmos-sdk/types/tx/signing" - "github.com/cosmos/cosmos-sdk/x/auth/ante" + "github.com/cosmos/cosmos-sdk/x/auth/middleware" authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing" ) @@ -31,10 +31,10 @@ type wrapper struct { } var ( - _ authsigning.Tx = &wrapper{} - _ client.TxBuilder = &wrapper{} - _ ante.HasExtensionOptionsTx = &wrapper{} - _ ExtensionOptionsTxBuilder = &wrapper{} + _ authsigning.Tx = &wrapper{} + _ client.TxBuilder = &wrapper{} + _ middleware.HasExtensionOptionsTx = &wrapper{} + _ ExtensionOptionsTxBuilder = &wrapper{} ) // ExtensionOptionsTxBuilder defines a TxBuilder that can also set extensions. diff --git a/x/feegrant/keeper/keeper.go b/x/feegrant/keeper/keeper.go index fd2614ccf72e..8e6750463217 100644 --- a/x/feegrant/keeper/keeper.go +++ b/x/feegrant/keeper/keeper.go @@ -8,7 +8,7 @@ import ( "github.com/cosmos/cosmos-sdk/codec" 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/cosmos/cosmos-sdk/x/auth/middleware" "github.com/cosmos/cosmos-sdk/x/feegrant" ) @@ -20,7 +20,7 @@ type Keeper struct { authKeeper feegrant.AccountKeeper } -var _ ante.FeegrantKeeper = &Keeper{} +var _ middleware.FeegrantKeeper = &Keeper{} // NewKeeper creates a fee grant Keeper func NewKeeper(cdc codec.BinaryCodec, storeKey sdk.StoreKey, ak feegrant.AccountKeeper) Keeper {