From 01832e62390315049dee3a8fe9addeed36a3caec Mon Sep 17 00:00:00 2001 From: Amaury <1293565+amaurym@users.noreply.github.com> Date: Fri, 20 May 2022 11:27:27 +0200 Subject: [PATCH] refactor: Revert middlewares to antehandlers (part 1/2: baseapp) (#11979) ## Description We decided to remove middlewares, and revert to antehandlers (exactly like in v045) for this release. A better middleware solution will be implemented after v046. ref: #11955 Because this refactor is big, so I decided to cut it into two. This PR is part 1 of 2: - part 1: Revert baseapp and middlewares to v0.45.4 - part 2: Add posthandler, tips, priority --- Suggestion for reviewers: This PR might still be hard to review though. I think it's easier to actually review the diff between v0.45.4 and this PR: - `git difftool -d v0.45.4..am/revert-045-baseapp baseapp` - most important parts to review: runTx, runMsgs - `git difftool -d v0.45.4..am/revert-045-baseapp x/auth/ante` - only cosmetic changes - `git difftool -d v0.45.4..am/revert-045-baseapp simapp` --- ### 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/main/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/main/docs/building-modules) - [ ] included the necessary unit and integration [tests](https://github.com/cosmos/cosmos-sdk/blob/main/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) --- CHANGELOG.md | 7 +- baseapp/abci.go | 87 +- baseapp/abci_test.go | 72 +- baseapp/baseapp.go | 318 ++++- baseapp/baseapp_test.go | 1060 ++++++----------- baseapp/block_gas_test.go | 201 ++++ baseapp/custom_txhandler_test.go | 116 -- baseapp/grpcrouter_test.go | 6 +- .../msg_service_router.go | 12 +- baseapp/msg_service_router_test.go | 121 ++ baseapp/options.go | 16 +- baseapp/queryrouter_test.go | 5 +- baseapp/recovery.go | 77 ++ baseapp/recovery_test.go | 64 + .../legacy_router.go => baseapp/router.go | 14 +- .../router_test.go | 7 +- baseapp/test_helpers.go | 70 +- .../adr-045-check-delivertx-middlewares.md | 3 +- proto/cosmos/base/abci/v1beta1/abci.proto | 2 +- server/mock/app.go | 46 +- server/mock/tx.go | 31 +- simapp/app.go | 46 +- simapp/app_test.go | 6 +- store/streaming/constructor_test.go | 2 +- types/errors/abci.go | 28 + types/handler.go | 3 - types/tx/middleware.go | 71 -- x/auth/ante/ante.go | 58 + .../middleware_test.go => ante/ante_test.go} | 384 +++--- x/auth/ante/basic.go | 206 ++++ x/auth/ante/basic_test.go | 224 ++++ .../{middleware => ante}/expected_keepers.go | 4 +- 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/{middleware => ante}/feegrant_test.go | 63 +- x/auth/ante/setup.go | 76 ++ x/auth/ante/setup_test.go | 99 ++ x/auth/{middleware => ante}/sigverify.go | 537 +++------ .../sigverify_benchmark_test.go | 4 +- x/auth/{middleware => ante}/sigverify_test.go | 296 +++-- x/auth/ante/testutil_test.go | 200 ++++ x/auth/client/testutil/suite.go | 1 + x/auth/middleware/basic.go | 359 ------ x/auth/middleware/basic_test.go | 251 ---- x/auth/middleware/block_gas.go | 52 - x/auth/middleware/branch_store.go | 70 -- x/auth/middleware/branch_store_test.go | 129 -- x/auth/middleware/ext.go | 86 -- x/auth/middleware/ext_test.go | 54 - x/auth/middleware/fee.go | 153 --- x/auth/middleware/fee_test.go | 126 -- x/auth/middleware/gas.go | 96 -- x/auth/middleware/gas_test.go | 129 -- x/auth/middleware/index_events.go | 61 - x/auth/middleware/middleware.go | 115 -- x/auth/middleware/msg_service_router_test.go | 55 - x/auth/middleware/recovery.go | 78 -- x/auth/middleware/run_msgs.go | 117 -- x/auth/middleware/run_msgs_test.go | 40 - x/auth/middleware/testutil_test.go | 222 ---- x/auth/middleware/tips.go | 69 -- x/auth/middleware/tips_test.go | 209 ---- x/auth/middleware/tx.go | 77 -- x/auth/middleware/tx_test.go | 64 - x/auth/middleware/validator_tx_fee.go | 59 - x/auth/signing/verify_test.go | 4 +- x/auth/tx/builder.go | 4 +- x/auth/tx/service_test.go | 18 +- x/authz/keeper/keeper.go | 6 +- x/feegrant/keeper/keeper.go | 4 +- x/gov/keeper/keeper.go | 8 +- x/group/keeper/keeper.go | 6 +- x/group/keeper/proposal_executor.go | 4 +- x/upgrade/types/storeloader_test.go | 4 +- 76 files changed, 3077 insertions(+), 4611 deletions(-) create mode 100644 baseapp/block_gas_test.go delete mode 100644 baseapp/custom_txhandler_test.go rename {x/auth/middleware => baseapp}/msg_service_router.go (94%) create mode 100644 baseapp/msg_service_router_test.go create mode 100644 baseapp/recovery.go create mode 100644 baseapp/recovery_test.go rename x/auth/middleware/legacy_router.go => baseapp/router.go (71%) rename x/auth/middleware/legacy_router_test.go => baseapp/router_test.go (79%) delete mode 100644 types/tx/middleware.go create mode 100644 x/auth/ante/ante.go rename x/auth/{middleware/middleware_test.go => ante/ante_test.go} (67%) create mode 100644 x/auth/ante/basic.go create mode 100644 x/auth/ante/basic_test.go rename x/auth/{middleware => ante}/expected_keepers.go (85%) create mode 100644 x/auth/ante/ext.go create mode 100644 x/auth/ante/ext_test.go create mode 100644 x/auth/ante/fee.go create mode 100644 x/auth/ante/fee_test.go rename x/auth/{middleware => ante}/feegrant_test.go (78%) create mode 100644 x/auth/ante/setup.go create mode 100644 x/auth/ante/setup_test.go rename x/auth/{middleware => ante}/sigverify.go (51%) rename x/auth/{middleware => ante}/sigverify_benchmark_test.go (89%) rename x/auth/{middleware => ante}/sigverify_test.go (52%) create mode 100644 x/auth/ante/testutil_test.go delete mode 100644 x/auth/middleware/basic.go delete mode 100644 x/auth/middleware/basic_test.go delete mode 100644 x/auth/middleware/block_gas.go delete mode 100644 x/auth/middleware/branch_store.go delete mode 100644 x/auth/middleware/branch_store_test.go delete mode 100644 x/auth/middleware/ext.go delete mode 100644 x/auth/middleware/ext_test.go delete mode 100644 x/auth/middleware/fee.go delete mode 100644 x/auth/middleware/fee_test.go delete mode 100644 x/auth/middleware/gas.go delete mode 100644 x/auth/middleware/gas_test.go delete mode 100644 x/auth/middleware/index_events.go delete mode 100644 x/auth/middleware/middleware.go delete mode 100644 x/auth/middleware/msg_service_router_test.go delete mode 100644 x/auth/middleware/recovery.go delete mode 100644 x/auth/middleware/run_msgs.go delete mode 100644 x/auth/middleware/run_msgs_test.go delete mode 100644 x/auth/middleware/testutil_test.go delete mode 100644 x/auth/middleware/tips.go delete mode 100644 x/auth/middleware/tips_test.go delete mode 100644 x/auth/middleware/tx.go delete mode 100644 x/auth/middleware/tx_test.go delete mode 100644 x/auth/middleware/validator_tx_fee.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 25bc3234cbd..c83455d36d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -138,12 +138,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ * [\#10248](https://github.com/cosmos/cosmos-sdk/pull/10248) Remove unused `KeyPowerReduction` variable from x/staking types. * (x/bank) [\#9832](https://github.com/cosmos/cosmos-sdk/pull/9832) `AddressFromBalancesStore` renamed to `AddressAndDenomFromBalancesStore`. * (tests) [\#9938](https://github.com/cosmos/cosmos-sdk/pull/9938) `simapp.Setup` accepts additional `testing.T` argument. -* (baseapp) [\#9920](https://github.com/cosmos/cosmos-sdk/pull/9920) BaseApp `{Check,Deliver,Simulate}Tx` methods are now replaced by a middleware stack. - * Replace the Antehandler interface with the `tx.Handler` and `tx.Middleware` interfaces. - * Replace `baseapp.SetAnteHandler` with `baseapp.SetTxHandler`. - * Move Msg routers from BaseApp to middlewares. - * Move Baseapp panic recovery into a middleware. - * Rename simulation helper methods `baseapp.{Check,Deliver}` to `baseapp.Sim{Check,Deliver}**`. +* (baseapp) [\#11979](https://github.com/cosmos/cosmos-sdk/pull/11979) Rename baseapp simulation helper methods `baseapp.{Check,Deliver}` to `baseapp.Sim{Check,Deliver}`. * (x/gov) [\#10373](https://github.com/cosmos/cosmos-sdk/pull/10373) Removed gov `keeper.{MustMarshal, MustUnmarshal}`. * [\#10348](https://github.com/cosmos/cosmos-sdk/pull/10348) StdSignBytes takes a new argument of type `*tx.Tip` for signing over tips using LEGACY_AMINO_JSON. * [\#10208](https://github.com/cosmos/cosmos-sdk/pull/10208) The `x/auth/signing.Tx` interface now also includes a new `GetTip() *tx.Tip` method for verifying tipped transactions. The `x/auth/types` expected BankKeeper interface now expects the `SendCoins` method too. diff --git a/baseapp/abci.go b/baseapp/abci.go index 87529d2560e..9b0b971343a 100644 --- a/baseapp/abci.go +++ b/baseapp/abci.go @@ -17,9 +17,9 @@ import ( "github.com/cosmos/cosmos-sdk/codec" snapshottypes "github.com/cosmos/cosmos-sdk/snapshots/types" + "github.com/cosmos/cosmos-sdk/telemetry" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" - "github.com/cosmos/cosmos-sdk/types/tx" ) // Supported ABCI Query prefixes @@ -233,8 +233,8 @@ func (app *BaseApp) EndBlock(req abci.RequestEndBlock) (res abci.ResponseEndBloc // CheckTx implements the ABCI interface and executes a tx in CheckTx mode. In // CheckTx mode, messages are not executed. This means messages are only validated -// and only the wired middlewares are executed. State is persisted to the BaseApp's -// internal CheckTx state if the middlewares' CheckTx pass. Otherwise, the ResponseCheckTx +// and only the AnteHandler is executed. State is persisted to the BaseApp's +// internal CheckTx state if the AnteHandler passes. Otherwise, the ResponseCheckTx // will contain releveant error information. Regardless of tx execution outcome, // the ResponseCheckTx will contain relevant gas execution context. func (app *BaseApp) CheckTx(req abci.RequestCheckTx) abci.ResponseCheckTx { @@ -251,18 +251,18 @@ func (app *BaseApp) CheckTx(req abci.RequestCheckTx) abci.ResponseCheckTx { panic(fmt.Sprintf("unknown RequestCheckTx type: %s", req.Type)) } - ctx := app.getContextForTx(mode, req.Tx) - res, checkRes, err := app.txHandler.CheckTx(ctx, tx.Request{TxBytes: req.Tx}, tx.RequestCheckTx{Type: req.Type}) + gInfo, result, anteEvents, err := app.runTx(mode, req.Tx) if err != nil { - return sdkerrors.ResponseCheckTx(err, uint64(res.GasUsed), uint64(res.GasWanted), app.trace) + return sdkerrors.ResponseCheckTxWithEvents(err, gInfo.GasWanted, gInfo.GasUsed, anteEvents, app.trace) } - abciRes, err := convertTxResponseToCheckTx(res, checkRes) - if err != nil { - return sdkerrors.ResponseCheckTx(err, uint64(res.GasUsed), uint64(res.GasWanted), app.trace) + return abci.ResponseCheckTx{ + GasWanted: int64(gInfo.GasWanted), // TODO: Should type accept unsigned ints? + GasUsed: int64(gInfo.GasUsed), // TODO: Should type accept unsigned ints? + Log: result.Log, + Data: result.Data, + Events: sdk.MarkEventsToIndex(result.Events, app.indexEvents), } - - return abciRes } // DeliverTx implements the ABCI interface and executes a tx in DeliverTx mode. @@ -271,28 +271,29 @@ func (app *BaseApp) CheckTx(req abci.RequestCheckTx) abci.ResponseCheckTx { // Regardless of tx execution outcome, the ResponseDeliverTx will contain relevant // gas execution context. func (app *BaseApp) DeliverTx(req abci.RequestDeliverTx) abci.ResponseDeliverTx { - var abciRes abci.ResponseDeliverTx + gInfo := sdk.GasInfo{} + resultStr := "successful" + defer func() { - for _, streamingListener := range app.abciListeners { - if err := streamingListener.ListenDeliverTx(app.deliverState.ctx, req, abciRes); err != nil { - app.logger.Error("DeliverTx listening hook failed", "err", err) - } - } + telemetry.IncrCounter(1, "tx", "count") + telemetry.IncrCounter(1, "tx", resultStr) + telemetry.SetGauge(float32(gInfo.GasUsed), "tx", "gas", "used") + telemetry.SetGauge(float32(gInfo.GasWanted), "tx", "gas", "wanted") }() - ctx := app.getContextForTx(runTxModeDeliver, req.Tx) - res, err := app.txHandler.DeliverTx(ctx, tx.Request{TxBytes: req.Tx}) + gInfo, result, anteEvents, err := app.runTx(runTxModeDeliver, req.Tx) if err != nil { - abciRes = sdkerrors.ResponseDeliverTx(err, uint64(res.GasUsed), uint64(res.GasWanted), app.trace) - return abciRes + resultStr = "failed" + return sdkerrors.ResponseDeliverTxWithEvents(err, gInfo.GasWanted, gInfo.GasUsed, anteEvents, app.trace) } - abciRes, err = convertTxResponseToDeliverTx(res) - if err != nil { - return sdkerrors.ResponseDeliverTx(err, uint64(res.GasUsed), uint64(res.GasWanted), app.trace) + return abci.ResponseDeliverTx{ + GasWanted: int64(gInfo.GasWanted), // TODO: Should type accept unsigned ints? + GasUsed: int64(gInfo.GasUsed), // TODO: Should type accept unsigned ints? + Log: result.Log, + Data: result.Data, + Events: sdk.MarkEventsToIndex(result.Events, app.indexEvents), } - - return abciRes } // Commit implements the ABCI interface. It will commit all state that exists in @@ -853,37 +854,3 @@ func SplitABCIQueryPath(requestPath string) (path []string) { return path } - -// makeABCIData generates the Data field to be sent to ABCI Check/DeliverTx. -func makeABCIData(txRes tx.Response) ([]byte, error) { - return proto.Marshal(&sdk.TxMsgData{MsgResponses: txRes.MsgResponses}) -} - -// convertTxResponseToCheckTx converts a tx.Response into a abci.ResponseCheckTx. -func convertTxResponseToCheckTx(txRes tx.Response, checkRes tx.ResponseCheckTx) (abci.ResponseCheckTx, error) { - data, err := makeABCIData(txRes) - if err != nil { - return abci.ResponseCheckTx{}, nil - } - - return abci.ResponseCheckTx{ - Data: data, - Log: txRes.Log, - Events: txRes.Events, - Priority: checkRes.Priority, - }, nil -} - -// convertTxResponseToDeliverTx converts a tx.Response into a abci.ResponseDeliverTx. -func convertTxResponseToDeliverTx(txRes tx.Response) (abci.ResponseDeliverTx, error) { - data, err := makeABCIData(txRes) - if err != nil { - return abci.ResponseDeliverTx{}, nil - } - - return abci.ResponseDeliverTx{ - Data: data, - Log: txRes.Log, - Events: txRes.Events, - }, nil -} diff --git a/baseapp/abci_test.go b/baseapp/abci_test.go index 58c29f0012c..c33b3c79ecf 100644 --- a/baseapp/abci_test.go +++ b/baseapp/abci_test.go @@ -1,14 +1,14 @@ -package baseapp_test +package baseapp import ( "testing" "github.com/stretchr/testify/require" abci "github.com/tendermint/tendermint/abci/types" + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" tmprototypes "github.com/tendermint/tendermint/proto/tendermint/types" dbm "github.com/tendermint/tm-db" - "github.com/cosmos/cosmos-sdk/baseapp" pruningtypes "github.com/cosmos/cosmos-sdk/pruning/types" "github.com/cosmos/cosmos-sdk/snapshots" snapshottypes "github.com/cosmos/cosmos-sdk/snapshots/types" @@ -24,81 +24,81 @@ func TestGetBlockRentionHeight(t *testing.T) { require.NoError(t, err) testCases := map[string]struct { - bapp *baseapp.BaseApp + bapp *BaseApp maxAgeBlocks int64 commitHeight int64 expected int64 }{ "defaults": { - bapp: baseapp.NewBaseApp(name, logger, db), + bapp: NewBaseApp(name, logger, db, nil), maxAgeBlocks: 0, commitHeight: 499000, expected: 0, }, "pruning unbonding time only": { - bapp: baseapp.NewBaseApp(name, logger, db, baseapp.SetMinRetainBlocks(1)), + bapp: NewBaseApp(name, logger, db, nil, SetMinRetainBlocks(1)), maxAgeBlocks: 362880, commitHeight: 499000, expected: 136120, }, "pruning iavl snapshot only": { - bapp: baseapp.NewBaseApp( - name, logger, db, - baseapp.SetPruning(pruningtypes.NewPruningOptions(pruningtypes.PruningNothing)), - baseapp.SetMinRetainBlocks(1), - baseapp.SetSnapshot(snapshotStore, snapshottypes.NewSnapshotOptions(10000, 1)), + bapp: NewBaseApp( + name, logger, db, nil, + SetPruning(pruningtypes.NewPruningOptions(pruningtypes.PruningNothing)), + SetMinRetainBlocks(1), + SetSnapshot(snapshotStore, snapshottypes.NewSnapshotOptions(10000, 1)), ), maxAgeBlocks: 0, commitHeight: 499000, expected: 489000, }, "pruning state sync snapshot only": { - bapp: baseapp.NewBaseApp( - name, logger, db, - baseapp.SetSnapshot(snapshotStore, snapshottypes.NewSnapshotOptions(50000, 3)), - baseapp.SetMinRetainBlocks(1), + bapp: NewBaseApp( + name, logger, db, nil, + SetSnapshot(snapshotStore, snapshottypes.NewSnapshotOptions(50000, 3)), + SetMinRetainBlocks(1), ), maxAgeBlocks: 0, commitHeight: 499000, expected: 349000, }, "pruning min retention only": { - bapp: baseapp.NewBaseApp( - name, logger, db, - baseapp.SetMinRetainBlocks(400000), + bapp: NewBaseApp( + name, logger, db, nil, + SetMinRetainBlocks(400000), ), maxAgeBlocks: 0, commitHeight: 499000, expected: 99000, }, "pruning all conditions": { - bapp: baseapp.NewBaseApp( - name, logger, db, - baseapp.SetPruning(pruningtypes.NewCustomPruningOptions(0, 0)), - baseapp.SetMinRetainBlocks(400000), - baseapp.SetSnapshot(snapshotStore, snapshottypes.NewSnapshotOptions(50000, 3)), + bapp: NewBaseApp( + name, logger, db, nil, + SetPruning(pruningtypes.NewCustomPruningOptions(0, 0)), + SetMinRetainBlocks(400000), + SetSnapshot(snapshotStore, snapshottypes.NewSnapshotOptions(50000, 3)), ), maxAgeBlocks: 362880, commitHeight: 499000, expected: 99000, }, "no pruning due to no persisted state": { - bapp: baseapp.NewBaseApp( - name, logger, db, - baseapp.SetPruning(pruningtypes.NewCustomPruningOptions(0, 0)), - baseapp.SetMinRetainBlocks(400000), - baseapp.SetSnapshot(snapshotStore, snapshottypes.NewSnapshotOptions(50000, 3)), + bapp: NewBaseApp( + name, logger, db, nil, + SetPruning(pruningtypes.NewCustomPruningOptions(0, 0)), + SetMinRetainBlocks(400000), + SetSnapshot(snapshotStore, snapshottypes.NewSnapshotOptions(50000, 3)), ), maxAgeBlocks: 362880, commitHeight: 10000, expected: 0, }, "disable pruning": { - bapp: baseapp.NewBaseApp( - name, logger, db, - baseapp.SetPruning(pruningtypes.NewCustomPruningOptions(0, 0)), - baseapp.SetMinRetainBlocks(0), - baseapp.SetSnapshot(snapshotStore, snapshottypes.NewSnapshotOptions(50000, 3)), + bapp: NewBaseApp( + name, logger, db, nil, + SetPruning(pruningtypes.NewCustomPruningOptions(0, 0)), + SetMinRetainBlocks(0), + SetSnapshot(snapshotStore, snapshottypes.NewSnapshotOptions(50000, 3)), ), maxAgeBlocks: 362880, commitHeight: 499000, @@ -134,12 +134,12 @@ func TestBaseAppCreateQueryContext(t *testing.T) { logger := defaultLogger() db := dbm.NewMemDB() name := t.Name() - app := baseapp.NewBaseApp(name, logger, db) + app := NewBaseApp(name, logger, db, nil) - app.BeginBlock(abci.RequestBeginBlock{Header: tmprototypes.Header{Height: 1}}) + app.BeginBlock(abci.RequestBeginBlock{Header: tmproto.Header{Height: 1}}) app.Commit() - app.BeginBlock(abci.RequestBeginBlock{Header: tmprototypes.Header{Height: 2}}) + app.BeginBlock(abci.RequestBeginBlock{Header: tmproto.Header{Height: 2}}) app.Commit() testCases := []struct { @@ -156,7 +156,7 @@ func TestBaseAppCreateQueryContext(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - _, err := app.CreateQueryContext(tc.height, tc.prove) + _, err := app.createQueryContext(tc.height, tc.prove) if tc.expErr { require.Error(t, err) } else { diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index 7634416e656..31122c29799 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -1,21 +1,25 @@ package baseapp import ( - "context" "fmt" + "strings" + "github.com/gogo/protobuf/proto" abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/crypto/tmhash" "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/codec/types" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" "github.com/cosmos/cosmos-sdk/snapshots" "github.com/cosmos/cosmos-sdk/store" "github.com/cosmos/cosmos-sdk/store/rootmulti" storetypes "github.com/cosmos/cosmos-sdk/store/types" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/types/tx" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + "github.com/cosmos/cosmos-sdk/x/auth/migrations/legacytx" ) const ( @@ -46,11 +50,14 @@ type BaseApp struct { // nolint: maligned db dbm.DB // common DB backend cms sdk.CommitMultiStore // Main (uncached) state storeLoader StoreLoader // function to handle store loading, may be overridden with SetStoreLoader() + router sdk.Router // handle any kind of legacy message queryRouter sdk.QueryRouter // router for redirecting query calls grpcQueryRouter *GRPCQueryRouter // router for redirecting gRPC query calls + msgServiceRouter *MsgServiceRouter // router for redirecting Msg service messages interfaceRegistry types.InterfaceRegistry + txDecoder sdk.TxDecoder // unmarshal []byte into sdk.Tx - txHandler tx.Handler // txHandler for {Deliver,Check}Tx and simulations + anteHandler sdk.AnteHandler // ante handler for fee and auth initChainer sdk.InitChainer // initialize state with validators and state blob beginBlocker sdk.BeginBlocker // logic to run before any txs endBlocker sdk.EndBlocker // logic to run after all txs, and to determine valset changes @@ -113,6 +120,9 @@ type BaseApp struct { // nolint: maligned // if BaseApp is passed to the upgrade keeper's NewKeeper method. appVersion uint64 + // recovery handler for app.runTx method + runTxRecoveryMiddleware recoveryMiddleware + // trace set will return full stack traces for errors in ABCI Log field trace bool @@ -131,17 +141,20 @@ type BaseApp struct { // nolint: maligned // // NOTE: The db is used to store the version number for now. func NewBaseApp( - name string, logger log.Logger, db dbm.DB, options ...func(*BaseApp), + name string, logger log.Logger, db dbm.DB, txDecoder sdk.TxDecoder, options ...func(*BaseApp), ) *BaseApp { app := &BaseApp{ - logger: logger, - name: name, - db: db, - cms: store.NewCommitMultiStore(db), - storeLoader: DefaultStoreLoader, - queryRouter: NewQueryRouter(), - grpcQueryRouter: NewGRPCQueryRouter(), - fauxMerkleMode: false, + logger: logger, + name: name, + db: db, + cms: store.NewCommitMultiStore(db), + storeLoader: DefaultStoreLoader, + router: NewRouter(), + queryRouter: NewQueryRouter(), + grpcQueryRouter: NewGRPCQueryRouter(), + msgServiceRouter: NewMsgServiceRouter(), + txDecoder: txDecoder, + fauxMerkleMode: false, } for _, option := range options { @@ -152,6 +165,8 @@ func NewBaseApp( app.cms.SetInterBlockCache(app.interBlockCache) } + app.runTxRecoveryMiddleware = newDefaultRecoveryMiddleware() + return app } @@ -180,6 +195,9 @@ func (app *BaseApp) Trace() bool { return app.trace } +// MsgServiceRouter returns the MsgServiceRouter of a BaseApp. +func (app *BaseApp) MsgServiceRouter() *MsgServiceRouter { return app.msgServiceRouter } + // MountStores mounts all IAVL or DB stores to the provided keys in the BaseApp // multistore. func (app *BaseApp) MountStores(keys ...storetypes.StoreKey) { @@ -344,6 +362,17 @@ func (app *BaseApp) setIndexEvents(ie []string) { } } +// Router returns the legacy router of the BaseApp. +func (app *BaseApp) Router() sdk.Router { + if app.sealed { + // We cannot return a Router when the app is sealed because we can't have + // any routes modified which would cause unexpected routing behavior. + panic("Router() on sealed BaseApp") + } + + return app.router +} + // QueryRouter returns the QueryRouter of a BaseApp. func (app *BaseApp) QueryRouter() sdk.QueryRouter { return app.queryRouter } @@ -410,6 +439,13 @@ func (app *BaseApp) GetConsensusParams(ctx sdk.Context) *tmproto.ConsensusParams return cp } +// AddRunTxRecoveryHandler adds custom app.runTx method panic handlers. +func (app *BaseApp) AddRunTxRecoveryHandler(handlers ...RecoveryHandler) { + for _, h := range handlers { + app.runTxRecoveryMiddleware = newRecoveryMiddleware(h, app.runTxRecoveryMiddleware) + } +} + // StoreConsensusParams sets the consensus parameters to the baseapp's param store. func (app *BaseApp) StoreConsensusParams(ctx sdk.Context, cp *tmproto.ConsensusParams) { if app.paramStore == nil { @@ -477,6 +513,22 @@ func (app *BaseApp) validateHeight(req abci.RequestBeginBlock) error { return 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 +} + // Returns the applications's deliverState if app is in runTxModeDeliver, // otherwise it returns the application's checkstate. func (app *BaseApp) getState(mode runTxMode) *state { @@ -488,7 +540,7 @@ func (app *BaseApp) getState(mode runTxMode) *state { } // retrieve the context for the tx w/ txBytes and other memoized values. -func (app *BaseApp) getContextForTx(mode runTxMode, txBytes []byte) context.Context { +func (app *BaseApp) getContextForTx(mode runTxMode, txBytes []byte) sdk.Context { ctx := app.getState(mode).ctx. WithTxBytes(txBytes). WithVoteInfos(app.voteInfos) @@ -503,5 +555,243 @@ func (app *BaseApp) getContextForTx(mode runTxMode, txBytes []byte) context.Cont ctx, _ = ctx.CacheContext() } - return sdk.WrapSDKContext(ctx) + return ctx +} + +// cacheTxContext returns a new context based off of the provided context with +// a branched multi-store. +func (app *BaseApp) cacheTxContext(ctx sdk.Context, txBytes []byte) (sdk.Context, sdk.CacheMultiStore) { + ms := ctx.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 ctx.WithMultiStore(msCache), msCache +} + +// runTx processes a transaction within a given execution mode, encoded transaction +// bytes, and the decoded transaction itself. All state transitions occur through +// a cached Context depending on the mode provided. State only gets persisted +// if all messages get executed successfully and the execution mode is DeliverTx. +// Note, gas execution info is always returned. A reference to a Result is +// returned if the tx does not run out of gas and if all the messages are valid +// and execute successfully. An error is returned otherwise. +func (app *BaseApp) runTx(mode runTxMode, txBytes []byte) (gInfo sdk.GasInfo, result *sdk.Result, anteEvents []abci.Event, err error) { + // NOTE: GasWanted should be returned by the AnteHandler. GasUsed is + // determined by the GasMeter. We need access to the context to get the gas + // meter so we initialize upfront. + var gasWanted uint64 + + ctx := app.getContextForTx(mode, txBytes) + ms := ctx.MultiStore() + + // only run the tx if there is block gas remaining + if mode == runTxModeDeliver && ctx.BlockGasMeter().IsOutOfGas() { + return gInfo, nil, nil, sdkerrors.Wrap(sdkerrors.ErrOutOfGas, "no block gas left to run tx") + } + + defer func() { + if r := recover(); r != nil { + recoveryMW := newOutOfGasRecoveryMiddleware(gasWanted, ctx, app.runTxRecoveryMiddleware) + err, result = processRecovery(r, recoveryMW), nil + } + + gInfo = sdk.GasInfo{GasWanted: gasWanted, GasUsed: ctx.GasMeter().GasConsumed()} + }() + + blockGasConsumed := false + // consumeBlockGas makes sure block gas is consumed at most once. It must happen after + // tx processing, and must be execute even if tx processing fails. Hence we use trick with `defer` + consumeBlockGas := func() { + if !blockGasConsumed { + blockGasConsumed = true + ctx.BlockGasMeter().ConsumeGas( + ctx.GasMeter().GasConsumedToLimit(), "block gas meter", + ) + } + } + + // If BlockGasMeter() panics it will be caught by the above recover and will + // return an error - in any case BlockGasMeter will consume gas past the limit. + // + // NOTE: This must exist in a separate defer function for the above recovery + // to recover from this one. + if mode == runTxModeDeliver { + defer consumeBlockGas() + } + + tx, err := app.txDecoder(txBytes) + if err != nil { + return sdk.GasInfo{}, nil, nil, err + } + + msgs := tx.GetMsgs() + if err := validateBasicTxMsgs(msgs); err != nil { + return sdk.GasInfo{}, nil, nil, err + } + + if app.anteHandler != nil { + var ( + anteCtx sdk.Context + msCache sdk.CacheMultiStore + ) + + // 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 = app.cacheTxContext(ctx, txBytes) + anteCtx = anteCtx.WithEventManager(sdk.NewEventManager()) + newCtx, err := app.anteHandler(anteCtx, tx, mode == runTxModeSimulate) + + 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. + ctx = newCtx.WithMultiStore(ms) + } + + events := ctx.EventManager().Events() + + // GasMeter expected to be set in AnteHandler + gasWanted = ctx.GasMeter().Limit() + + if err != nil { + return gInfo, nil, nil, err + } + + msCache.Write() + anteEvents = events.ToABCIEvents() + } + + // Create a new Context based off of the existing Context with a MultiStore branch + // in case message processing fails. At this point, the MultiStore + // is a branch of a branch. + runMsgCtx, msCache := app.cacheTxContext(ctx, txBytes) + + // Attempt to execute all messages and only update state if all messages pass + // and we're in DeliverTx. Note, runMsgs will never return a reference to a + // Result if any single message fails or does not have a registered Handler. + result, err = app.runMsgs(runMsgCtx, msgs, mode) + if err == nil && mode == runTxModeDeliver { + // When block gas exceeds, it'll panic and won't commit the cached store. + consumeBlockGas() + + msCache.Write() + + if len(anteEvents) > 0 { + // append the events in the order of occurrence + result.Events = append(anteEvents, result.Events...) + } + } + + return gInfo, result, anteEvents, err +} + +// runMsgs iterates through a list of messages and executes them with the provided +// Context and execution mode. Messages will only be executed during simulation +// and DeliverTx. An error is returned if any single message fails or if a +// Handler does not exist for a given message route. Otherwise, a reference to a +// Result is returned. The caller must not commit state if an error is returned. +func (app *BaseApp) runMsgs(ctx sdk.Context, msgs []sdk.Msg, mode runTxMode) (*sdk.Result, error) { + msgLogs := make(sdk.ABCIMessageLogs, 0, len(msgs)) + events := sdk.EmptyEvents() + var msgResponses []*codectypes.Any + + // NOTE: GasWanted is determined by the AnteHandler and GasUsed by the GasMeter. + for i, msg := range msgs { + // skip actual execution for (Re)CheckTx mode + if mode == runTxModeCheck || mode == runTxModeReCheck { + break + } + + var ( + msgResult *sdk.Result + eventMsgName string // name to use as value in event `message.action` + err error + ) + + if handler := app.msgServiceRouter.Handler(msg); handler != nil { + // ADR 031 request type routing + msgResult, err = handler(ctx, msg) + eventMsgName = sdk.MsgTypeURL(msg) + } else if legacyMsg, ok := msg.(legacytx.LegacyMsg); ok { + // legacy sdk.Msg routing + // Assuming that the app developer has migrated all their Msgs to + // proto messages and has registered all `Msg services`, then this + // path should never be called, because all those Msgs should be + // registered within the `msgServiceRouter` already. + msgRoute := legacyMsg.Route() + eventMsgName = legacyMsg.Type() + handler := app.router.Route(ctx, msgRoute) + if handler == nil { + return nil, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "unrecognized message route: %s; message index: %d", msgRoute, i) + } + + msgResult, err = handler(ctx, msg) + } else { + return nil, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "can't route message %+v", msg) + } + + if err != nil { + return nil, sdkerrors.Wrapf(err, "failed to execute message; message index: %d", i) + } + + msgEvents := sdk.Events{ + sdk.NewEvent(sdk.EventTypeMessage, sdk.NewAttribute(sdk.AttributeKeyAction, eventMsgName)), + } + msgEvents = msgEvents.AppendEvents(msgResult.GetEvents()) + + // append message events, data and logs + // + // Note: Each message result's data must be length-prefixed in order to + // separate each result. + events = events.AppendEvents(msgEvents) + + // Each individual sdk.Result that went through the MsgServiceRouter + // (which should represent 99% of the Msgs now, since everyone should + // be using protobuf Msgs) has exactly one Msg response, set inside + // `WrapServiceResult`. We take that Msg response, and aggregate it + // into an array. + if len(msgResult.MsgResponses) > 0 { + msgResponse := msgResult.MsgResponses[0] + if msgResponse == nil { + return nil, sdkerrors.ErrLogic.Wrapf("got nil Msg response at index %d for msg %s", i, sdk.MsgTypeURL(msg)) + } + msgResponses = append(msgResponses, msgResponse) + } + + msgLogs = append(msgLogs, sdk.NewABCIMessageLog(uint32(i), msgResult.Log, msgEvents)) + } + + data, err := makeABCIData(msgResponses) + if err != nil { + return nil, sdkerrors.Wrap(err, "failed to marshal tx data") + } + + return &sdk.Result{ + Data: data, + Log: strings.TrimSpace(msgLogs.String()), + Events: events.ToABCIEvents(), + MsgResponses: msgResponses, + }, nil +} + +// makeABCIData generates the Data field to be sent to ABCI Check/DeliverTx. +func makeABCIData(msgResponses []*codectypes.Any) ([]byte, error) { + return proto.Marshal(&sdk.TxMsgData{MsgResponses: msgResponses}) } diff --git a/baseapp/baseapp_test.go b/baseapp/baseapp_test.go index 2c17071aefe..fcf8b1a788c 100644 --- a/baseapp/baseapp_test.go +++ b/baseapp/baseapp_test.go @@ -1,12 +1,10 @@ -package baseapp_test +package baseapp import ( "bytes" - "context" "encoding/binary" "encoding/json" "fmt" - "math" "math/rand" "strings" "sync" @@ -20,39 +18,26 @@ import ( "github.com/tendermint/tendermint/libs/log" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" dbm "github.com/tendermint/tm-db" - "google.golang.org/protobuf/proto" - "github.com/cosmos/cosmos-sdk/baseapp" "github.com/cosmos/cosmos-sdk/codec" - "github.com/cosmos/cosmos-sdk/codec/legacy" codectypes "github.com/cosmos/cosmos-sdk/codec/types" pruningtypes "github.com/cosmos/cosmos-sdk/pruning/types" - "github.com/cosmos/cosmos-sdk/simapp" "github.com/cosmos/cosmos-sdk/snapshots" snapshottypes "github.com/cosmos/cosmos-sdk/snapshots/types" "github.com/cosmos/cosmos-sdk/store/rootmulti" storetypes "github.com/cosmos/cosmos-sdk/store/types" "github.com/cosmos/cosmos-sdk/testutil" "github.com/cosmos/cosmos-sdk/testutil/testdata" - "github.com/cosmos/cosmos-sdk/testutil/testdata_pulsar" 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" ) var ( capKey1 = sdk.NewKVStoreKey("key1") capKey2 = sdk.NewKVStoreKey("key2") - - encCfg = simapp.MakeTestEncodingConfig() ) -func init() { - registerTestCodec(encCfg.Amino) -} - type paramStore struct { db *dbm.MemDB } @@ -71,9 +56,7 @@ func (ps *paramStore) Set(_ sdk.Context, key []byte, value interface{}) { panic(err) } - if err := ps.db.Set(key, bz); err != nil { - panic(err) - } + ps.db.Set(key, bz) } func (ps *paramStore) Has(_ sdk.Context, key []byte) bool { @@ -101,34 +84,39 @@ func (ps *paramStore) Get(_ sdk.Context, key []byte, ptr interface{}) { } func defaultLogger() log.Logger { - logger, _ := log.NewDefaultLogger("plain", "info", false) - return logger.With("module", "sdk/app") + return log.MustNewDefaultLogger("plain", "info", false).With("module", "sdk/app") } -func newBaseApp(name string, options ...func(*baseapp.BaseApp)) *baseapp.BaseApp { +func newBaseApp(name string, options ...func(*BaseApp)) *BaseApp { logger := defaultLogger() db := dbm.NewMemDB() codec := codec.NewLegacyAmino() registerTestCodec(codec) - return baseapp.NewBaseApp(name, logger, db, options...) + return NewBaseApp(name, logger, db, testTxDecoder(codec), options...) } func registerTestCodec(cdc *codec.LegacyAmino) { + // register Tx, Msg + sdk.RegisterLegacyAminoCodec(cdc) + // register test types cdc.RegisterConcrete(&txTest{}, "cosmos-sdk/baseapp/txTest", nil) - legacy.RegisterAminoMsg(cdc, &msgCounter{}, "cosmos-sdk/baseapp/msgCounter") - legacy.RegisterAminoMsg(cdc, &msgCounter2{}, "cosmos-sdk/baseapp/msgCounter2") - legacy.RegisterAminoMsg(cdc, &msgKeyValue{}, "cosmos-sdk/baseapp/msgKeyValue") - legacy.RegisterAminoMsg(cdc, &msgNoRoute{}, "cosmos-sdk/baseapp/msgNoRoute") + cdc.RegisterConcrete(&msgCounter{}, "cosmos-sdk/baseapp/msgCounter", nil) + cdc.RegisterConcrete(&msgCounter2{}, "cosmos-sdk/baseapp/msgCounter2", nil) + cdc.RegisterConcrete(&msgKeyValue{}, "cosmos-sdk/baseapp/msgKeyValue", nil) + cdc.RegisterConcrete(&msgNoRoute{}, "cosmos-sdk/baseapp/msgNoRoute", nil) } // aminoTxEncoder creates a amino TxEncoder for testing purposes. -func aminoTxEncoder(cdc *codec.LegacyAmino) sdk.TxEncoder { +func aminoTxEncoder() sdk.TxEncoder { + cdc := codec.NewLegacyAmino() + registerTestCodec(cdc) + return legacytx.StdTxConfig{Cdc: cdc}.TxEncoder() } // simple one store baseapp -func setupBaseApp(t *testing.T, options ...func(*baseapp.BaseApp)) (*baseapp.BaseApp, error) { +func setupBaseApp(t *testing.T, options ...func(*BaseApp)) *BaseApp { app := newBaseApp(t.Name(), options...) require.Equal(t, t.Name(), app.Name()) @@ -137,61 +125,27 @@ func setupBaseApp(t *testing.T, options ...func(*baseapp.BaseApp)) (*baseapp.Bas // stores are mounted err := app.LoadLatestVersion() - return app, err -} - -// 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.NewTxDecoderMiddleware(options.TxDecoder), - middleware.GasTxMiddleware, - middleware.RecoveryTxMiddleware, - middleware.NewIndexEventsTxMiddleware(options.IndexEvents), - middleware.ValidateBasicMiddleware, - middleware.ConsumeBlockGasMiddleware, - CustomTxHandlerMiddleware(customTxHandlerMiddleware), - ) + require.Nil(t, err) + return app } // simple one store baseapp with data and snapshots. Each tx is 1 MB in size (uncompressed). -func setupBaseAppWithSnapshots(t *testing.T, config *setupConfig) (*baseapp.BaseApp, error) { +func setupBaseAppWithSnapshots(t *testing.T, config *setupConfig) (*BaseApp, error) { codec := codec.NewLegacyAmino() registerTestCodec(codec) - routerOpt := func(bapp *baseapp.BaseApp) { - legacyRouter := middleware.NewLegacyRouter() - legacyRouter.AddRoute(sdk.NewRoute(routeMsgKeyValue, func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) { + routerOpt := func(bapp *BaseApp) { + bapp.Router().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) - any, err := codectypes.NewAnyWithValue(msg) - if err != nil { - return nil, err - } - - return &sdk.Result{ - MsgResponses: []*codectypes.Any{any}, - }, nil + bapp.cms.GetCommitKVStore(capKey2).Set(kv.Key, kv.Value) + return &sdk.Result{}, nil })) - txHandler := testTxHandler( - middleware.TxHandlerOptions{ - LegacyRouter: legacyRouter, - MsgServiceRouter: middleware.NewMsgServiceRouter(encCfg.InterfaceRegistry), - TxDecoder: testTxDecoder(encCfg.Amino), - }, - func(ctx sdk.Context, tx sdk.Tx, simulate bool) (sdk.Context, error) { return ctx, nil }, - ) - bapp.SetTxHandler(txHandler) } snapshotTimeout := 1 * time.Minute snapshotStore, err := snapshots.NewStore(dbm.NewMemDB(), testutil.GetTempDir(t)) require.NoError(t, err) - app, err := setupBaseApp(t, routerOpt, baseapp.SetSnapshot(snapshotStore, snapshottypes.NewSnapshotOptions(config.snapshotInterval, uint32(config.snapshotKeepRecent))), baseapp.SetPruning(config.pruningOpts)) - if err != nil { - return nil, err - } + app := setupBaseApp(t, routerOpt, SetSnapshot(snapshotStore, snapshottypes.NewSnapshotOptions(config.snapshotInterval, uint32(config.snapshotKeepRecent))), SetPruning(config.pruningOpts)) app.InitChain(abci.RequestInitChain{}) @@ -209,7 +163,7 @@ func setupBaseAppWithSnapshots(t *testing.T, config *setupConfig) (*baseapp.Base tx.Msgs = append(tx.Msgs, msgKeyValue{Key: key, Value: value}) keyCounter++ } - txBytes, err := encCfg.Amino.Marshal(tx) + txBytes, err := codec.Marshal(tx) require.NoError(t, err) resp := app.DeliverTx(abci.RequestDeliverTx{Tx: txBytes}) require.True(t, resp.IsOK(), "%v", resp.String()) @@ -238,71 +192,23 @@ func setupBaseAppWithSnapshots(t *testing.T, config *setupConfig) (*baseapp.Base } func TestMountStores(t *testing.T) { - app, err := setupBaseApp(t) - require.NoError(t, err) + 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) } -type MockTxHandler struct { - T *testing.T -} - -func (th MockTxHandler) CheckTx(goCtx context.Context, req tx.Request, checkReq tx.RequestCheckTx) (tx.Response, tx.ResponseCheckTx, error) { - ctx := sdk.UnwrapSDKContext(goCtx) - require.NotNil(th.T, ctx.ConsensusParams()) - return tx.Response{}, tx.ResponseCheckTx{}, nil -} - -func (th MockTxHandler) DeliverTx(goCtx context.Context, req tx.Request) (tx.Response, error) { - ctx := sdk.UnwrapSDKContext(goCtx) - require.NotNil(th.T, ctx.ConsensusParams()) - return tx.Response{}, nil -} - -func (th MockTxHandler) SimulateTx(goCtx context.Context, req tx.Request) (tx.Response, error) { - ctx := sdk.UnwrapSDKContext(goCtx) - require.NotNil(th.T, ctx.ConsensusParams()) - return tx.Response{}, nil -} - -func TestConsensusParamsNotNil(t *testing.T) { - app, err := setupBaseApp(t, func(app *baseapp.BaseApp) { - app.SetBeginBlocker(func(ctx sdk.Context, req abci.RequestBeginBlock) abci.ResponseBeginBlock { - require.NotNil(t, ctx.ConsensusParams()) - return abci.ResponseBeginBlock{} - }) - }, func(app *baseapp.BaseApp) { - app.SetEndBlocker(func(ctx sdk.Context, req abci.RequestEndBlock) abci.ResponseEndBlock { - require.NotNil(t, ctx.ConsensusParams()) - return abci.ResponseEndBlock{} - }) - }, func(app *baseapp.BaseApp) { - app.SetTxHandler(MockTxHandler{T: t}) - }) - require.NoError(t, err) - - header := tmproto.Header{Height: 1} - app.BeginBlock(abci.RequestBeginBlock{Header: header}) - app.EndBlock(abci.RequestEndBlock{Height: header.Height}) - app.CheckTx(abci.RequestCheckTx{}) - app.DeliverTx(abci.RequestDeliverTx{}) - _, _, err = app.Simulate([]byte{}) - require.NoError(t, err) -} - // Test that we can make commits and then reload old versions. // Test that LoadLatestVersion actually does. func TestLoadVersion(t *testing.T) { logger := defaultLogger() - pruningOpt := baseapp.SetPruning(pruningtypes.NewPruningOptions(pruningtypes.PruningNothing)) + pruningOpt := SetPruning(pruningtypes.NewPruningOptions(pruningtypes.PruningNothing)) db := dbm.NewMemDB() name := t.Name() - app := baseapp.NewBaseApp(name, logger, db, pruningOpt) + app := NewBaseApp(name, logger, db, nil, pruningOpt) // make a cap key and mount the store err := app.LoadLatestVersion() // needed to make stores non-nil @@ -329,7 +235,7 @@ func TestLoadVersion(t *testing.T) { commitID2 := storetypes.CommitID{Version: 2, Hash: res.Data} // reload with LoadLatestVersion - app = baseapp.NewBaseApp(name, logger, db, pruningOpt) + app = NewBaseApp(name, logger, db, nil, pruningOpt) app.MountStores() err = app.LoadLatestVersion() require.Nil(t, err) @@ -337,7 +243,7 @@ func TestLoadVersion(t *testing.T) { // reload with LoadVersion, see if you can commit the same block and get // the same result - app = baseapp.NewBaseApp(name, logger, db, pruningOpt) + app = NewBaseApp(name, logger, db, nil, pruningOpt) err = app.LoadVersion(1) require.Nil(t, err) testLoadVersionHelper(t, app, int64(1), commitID1) @@ -346,8 +252,8 @@ func TestLoadVersion(t *testing.T) { testLoadVersionHelper(t, app, int64(2), commitID2) } -func useDefaultLoader(app *baseapp.BaseApp) { - app.SetStoreLoader(baseapp.DefaultStoreLoader) +func useDefaultLoader(app *BaseApp) { + app.SetStoreLoader(DefaultStoreLoader) } func initStore(t *testing.T, db dbm.DB, storeKey string, k, v []byte) { @@ -386,7 +292,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.BaseApp) + setLoader func(*BaseApp) origStoreKey string loadStoreKey string }{ @@ -412,11 +318,11 @@ func TestSetLoader(t *testing.T) { initStore(t, db, tc.origStoreKey, k, v) // load the app with the existing db - opts := []func(*baseapp.BaseApp){baseapp.SetPruning(pruningtypes.NewPruningOptions(pruningtypes.PruningNothing))} + opts := []func(*BaseApp){SetPruning(pruningtypes.NewPruningOptions(pruningtypes.PruningNothing))} if tc.setLoader != nil { opts = append(opts, tc.setLoader) } - app := baseapp.NewBaseApp(t.Name(), defaultLogger(), db, opts...) + app := NewBaseApp(t.Name(), defaultLogger(), db, nil, opts...) app.MountStores(sdk.NewKVStoreKey(tc.loadStoreKey)) err := app.LoadLatestVersion() require.Nil(t, err) @@ -435,10 +341,10 @@ func TestSetLoader(t *testing.T) { func TestVersionSetterGetter(t *testing.T) { logger := defaultLogger() - pruningOpt := baseapp.SetPruning(pruningtypes.NewPruningOptions(pruningtypes.PruningDefault)) + pruningOpt := SetPruning(pruningtypes.NewPruningOptions(pruningtypes.PruningDefault)) db := dbm.NewMemDB() name := t.Name() - app := baseapp.NewBaseApp(name, logger, db, pruningOpt) + app := NewBaseApp(name, logger, db, nil, pruningOpt) require.Equal(t, "", app.Version()) res := app.Query(abci.RequestQuery{Path: "app/version"}) @@ -455,10 +361,10 @@ func TestVersionSetterGetter(t *testing.T) { func TestLoadVersionInvalid(t *testing.T) { logger := log.NewNopLogger() - pruningOpt := baseapp.SetPruning(pruningtypes.NewPruningOptions(pruningtypes.PruningNothing)) + pruningOpt := SetPruning(pruningtypes.NewPruningOptions(pruningtypes.PruningNothing)) db := dbm.NewMemDB() name := t.Name() - app := baseapp.NewBaseApp(name, logger, db, pruningOpt) + app := NewBaseApp(name, logger, db, nil, pruningOpt) err := app.LoadLatestVersion() require.Nil(t, err) @@ -473,7 +379,7 @@ func TestLoadVersionInvalid(t *testing.T) { commitID1 := storetypes.CommitID{Version: 1, Hash: res.Data} // create a new app with the stores mounted under the same cap key - app = baseapp.NewBaseApp(name, logger, db, pruningOpt) + app = NewBaseApp(name, logger, db, nil, pruningOpt) // require we can load the latest version err = app.LoadVersion(1) @@ -488,21 +394,16 @@ func TestLoadVersionInvalid(t *testing.T) { func TestLoadVersionPruning(t *testing.T) { logger := log.NewNopLogger() pruningOptions := pruningtypes.NewCustomPruningOptions(10, 15) - pruningOpt := baseapp.SetPruning(pruningOptions) + pruningOpt := SetPruning(pruningOptions) db := dbm.NewMemDB() name := t.Name() - - snapshotStore, err := snapshots.NewStore(dbm.NewMemDB(), testutil.GetTempDir(t)) - require.NoError(t, err) - snapshotOpt := baseapp.SetSnapshot(snapshotStore, snapshottypes.NewSnapshotOptions(3, 1)) - - app := baseapp.NewBaseApp(name, logger, db, pruningOpt, snapshotOpt) + app := NewBaseApp(name, logger, db, nil, pruningOpt) // make a cap key and mount the store capKey := sdk.NewKVStoreKey("key1") app.MountStores(capKey) - err = app.LoadLatestVersion() // needed to make stores non-nil + err := app.LoadLatestVersion() // needed to make stores non-nil require.Nil(t, err) emptyCommitID := storetypes.CommitID{} @@ -515,34 +416,34 @@ func TestLoadVersionPruning(t *testing.T) { var lastCommitID storetypes.CommitID - // Commit 15 blocks, of which 15 (latest) is kept in addition to 5-14 inclusive - // (keep recent) and 3 (snapshot-interval). - for i := int64(1); i <= 15; i++ { + // Commit seven blocks, of which 7 (latest) is kept in addition to 6, 5 + // (keep recent) and 3 (keep every). + for i := int64(1); i <= 7; i++ { app.BeginBlock(abci.RequestBeginBlock{Header: tmproto.Header{Height: i}}) res := app.Commit() lastCommitID = storetypes.CommitID{Version: i, Hash: res.Data} } - for _, v := range []int64{1, 2, 3, 4} { - _, err = app.CMS().CacheMultiStoreWithVersion(v) + for _, v := range []int64{1, 2, 4} { + _, err = app.cms.CacheMultiStoreWithVersion(v) require.NoError(t, err) } - for _, v := range []int64{3, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14} { - _, err = app.CMS().CacheMultiStoreWithVersion(v) + for _, v := range []int64{3, 5, 6, 7} { + _, err = app.cms.CacheMultiStoreWithVersion(v) require.NoError(t, err) } // reload with LoadLatestVersion, check it loads last version - app = baseapp.NewBaseApp(name, logger, db, pruningOpt, snapshotOpt) + app = NewBaseApp(name, logger, db, nil, pruningOpt) app.MountStores(capKey) err = app.LoadLatestVersion() require.Nil(t, err) - testLoadVersionHelper(t, app, int64(15), lastCommitID) + testLoadVersionHelper(t, app, int64(7), lastCommitID) } -func testLoadVersionHelper(t *testing.T, app *baseapp.BaseApp, expectedHeight int64, expectedID storetypes.CommitID) { +func testLoadVersionHelper(t *testing.T, app *BaseApp, expectedHeight int64, expectedID storetypes.CommitID) { lastHeight := app.LastBlockHeight() lastID := app.LastCommitID() require.Equal(t, expectedHeight, lastHeight) @@ -552,16 +453,33 @@ func testLoadVersionHelper(t *testing.T, app *baseapp.BaseApp, expectedHeight in func TestOptionFunction(t *testing.T) { logger := defaultLogger() db := dbm.NewMemDB() - bap := baseapp.NewBaseApp("starting name", logger, db, testChangeNameHelper("new name")) - require.Equal(t, bap.GetName(), "new name", "BaseApp should have had name changed via option function") + 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") } -func testChangeNameHelper(name string) func(*baseapp.BaseApp) { - return func(bap *baseapp.BaseApp) { - bap.SetName(name) +func testChangeNameHelper(name string) func(*BaseApp) { + return func(bap *BaseApp) { + bap.name = name } } +// Test that txs can be unmarshalled and read and that +// correct error codes are returned when not +func TestTxDecoder(t *testing.T) { + codec := codec.NewLegacyAmino() + registerTestCodec(codec) + + app := newBaseApp(t.Name()) + tx := newTxCounter(1, 0) + txBytes := codec.MustMarshal(tx) + + dTx, err := app.txDecoder(txBytes) + require.NoError(t, err) + + cTx := dTx.(txTest) + require.Equal(t, tx.Counter, cTx.Counter) +} + // Test that Info returns the latest committed state. func TestInfo(t *testing.T) { app := newBaseApp(t.Name()) @@ -581,8 +499,7 @@ func TestInfo(t *testing.T) { } func TestBaseAppOptionSeal(t *testing.T) { - app, err := setupBaseApp(t) - require.NoError(t, err) + app := setupBaseApp(t) require.Panics(t, func() { app.SetName("") @@ -606,7 +523,7 @@ func TestBaseAppOptionSeal(t *testing.T) { app.SetEndBlocker(nil) }) require.Panics(t, func() { - app.SetTxHandler(nil) + app.SetAnteHandler(nil) }) require.Panics(t, func() { app.SetAddrPeerFilter(nil) @@ -617,12 +534,15 @@ func TestBaseAppOptionSeal(t *testing.T) { require.Panics(t, func() { app.SetFauxMerkleMode() }) + require.Panics(t, func() { + app.SetRouter(NewRouter()) + }) } func TestSetMinGasPrices(t *testing.T) { minGasPrices := sdk.DecCoins{sdk.NewInt64DecCoin("stake", 5000)} - app := newBaseApp(t.Name(), baseapp.SetMinGasPrices(minGasPrices.String())) - require.Equal(t, minGasPrices, app.MinGasPrices()) + app := newBaseApp(t.Name(), SetMinGasPrices(minGasPrices.String())) + require.Equal(t, minGasPrices, app.minGasPrices) } func TestInitChainer(t *testing.T) { @@ -631,7 +551,7 @@ func TestInitChainer(t *testing.T) { // we can reload the same app later db := dbm.NewMemDB() logger := defaultLogger() - app := baseapp.NewBaseApp(name, logger, db) + app := NewBaseApp(name, logger, db, nil) capKey := sdk.NewKVStoreKey("main") capKey2 := sdk.NewKVStoreKey("key2") app.MountStores(capKey, capKey2) @@ -674,10 +594,10 @@ func TestInitChainer(t *testing.T) { ) // assert that chainID is set correctly in InitChain - chainID := app.DeliverState().Context().ChainID() + chainID := app.deliverState.ctx.ChainID() require.Equal(t, "test-chain-id", chainID, "ChainID in deliverState not set correctly in InitChain") - chainID = app.CheckState().Context().ChainID() + chainID = app.checkState.ctx.ChainID() require.Equal(t, "test-chain-id", chainID, "ChainID in checkState not set correctly in InitChain") app.Commit() @@ -686,7 +606,7 @@ func TestInitChainer(t *testing.T) { require.Equal(t, value, res.Value) // reload app - app = baseapp.NewBaseApp(name, logger, db) + app = NewBaseApp(name, logger, db, nil) app.SetInitChainer(initChainer) app.MountStores(capKey, capKey2) err = app.LoadLatestVersion() // needed to make stores non-nil @@ -710,7 +630,7 @@ func TestInitChain_WithInitialHeight(t *testing.T) { name := t.Name() db := dbm.NewMemDB() logger := defaultLogger() - app := baseapp.NewBaseApp(name, logger, db) + app := NewBaseApp(name, logger, db, nil) app.InitChain( abci.RequestInitChain{ @@ -726,7 +646,7 @@ func TestBeginBlock_WithInitialHeight(t *testing.T) { name := t.Name() db := dbm.NewMemDB() logger := defaultLogger() - app := baseapp.NewBaseApp(name, logger, db) + app := NewBaseApp(name, logger, db, nil) app.InitChain( abci.RequestInitChain{ @@ -757,7 +677,6 @@ type txTest struct { Msgs []sdk.Msg Counter int64 FailOnAnte bool - GasLimit uint64 } func (tx *txTest) setFailOnAnte(fail bool) { @@ -766,7 +685,7 @@ func (tx *txTest) setFailOnAnte(fail bool) { func (tx *txTest) setFailOnHandler(fail bool) { for i, msg := range tx.Msgs { - tx.Msgs[i] = &msgCounter{msg.(*msgCounter).Counter, fail} + tx.Msgs[i] = msgCounter{msg.(msgCounter).Counter, fail} } } @@ -774,12 +693,6 @@ func (tx *txTest) setFailOnHandler(fail bool) { func (tx txTest) GetMsgs() []sdk.Msg { return tx.Msgs } 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" @@ -794,29 +707,29 @@ type msgCounter struct { } // dummy implementation of proto.Message -func (msg *msgCounter) Reset() {} -func (msg *msgCounter) String() string { return "TODO" } -func (msg *msgCounter) ProtoMessage() {} +func (msg msgCounter) Reset() {} +func (msg msgCounter) String() string { return "TODO" } +func (msg msgCounter) ProtoMessage() {} // Implements Msg -func (msg *msgCounter) Route() string { return routeMsgCounter } -func (msg *msgCounter) Type() string { return "counter1" } -func (msg *msgCounter) GetSignBytes() []byte { return nil } -func (msg *msgCounter) GetSigners() []sdk.AccAddress { return nil } -func (msg *msgCounter) ValidateBasic() error { +func (msg msgCounter) Route() string { return routeMsgCounter } +func (msg msgCounter) Type() string { return "counter1" } +func (msg msgCounter) GetSignBytes() []byte { return nil } +func (msg msgCounter) GetSigners() []sdk.AccAddress { return nil } +func (msg msgCounter) ValidateBasic() error { if msg.Counter >= 0 { return nil } return sdkerrors.Wrap(sdkerrors.ErrInvalidSequence, "counter should be a non-negative integer") } -func newTxCounter(counter int64, msgCounters ...int64) txTest { +func newTxCounter(counter int64, msgCounters ...int64) *txTest { msgs := make([]sdk.Msg, 0, len(msgCounters)) for _, c := range msgCounters { - msgs = append(msgs, &msgCounter{c, false}) + msgs = append(msgs, msgCounter{c, false}) } - return txTest{msgs, counter, false, math.MaxUint64} + return &txTest{msgs, counter, false} } // a msg we dont know how to route @@ -895,7 +808,7 @@ func testTxDecoder(cdc *codec.LegacyAmino) sdk.TxDecoder { } } -func customHandlerTxTest(t *testing.T, capKey storetypes.StoreKey, storeKey []byte) handlerFun { +func anteHandlerTxTest(t *testing.T, capKey storetypes.StoreKey, storeKey []byte) sdk.AnteHandler { return func(ctx sdk.Context, tx sdk.Tx, simulate bool) (sdk.Context, error) { store := ctx.KVStore(capKey) txTest := tx.(txTest) @@ -910,7 +823,7 @@ func customHandlerTxTest(t *testing.T, capKey storetypes.StoreKey, storeKey []by } ctx.EventManager().EmitEvents( - counterEvent("post_handlers", txTest.Counter), + counterEvent("ante_handler", txTest.Counter), ) return ctx, nil @@ -953,14 +866,6 @@ func handlerMsgCounter(t *testing.T, capKey storetypes.StoreKey, deliverKey []by } res.Events = ctx.EventManager().Events().ToABCIEvents() - - any, err := codectypes.NewAnyWithValue(msg) - if err != nil { - return nil, err - } - - res.MsgResponses = []*codectypes.Any{any} - return res, nil } } @@ -992,7 +897,7 @@ func incrementingCounter(t *testing.T, store sdk.KVStore, counterKey []byte, cou return &sdk.Result{}, nil } -// --------------------------------------------------------------------- +//--------------------------------------------------------------------- // Tx processing - CheckTx, DeliverTx, SimulateTx. // These tests use the serialized tx as input, while most others will use the // Check(), Deliver(), Simulate() methods directly. @@ -1006,55 +911,49 @@ func TestCheckTx(t *testing.T) { // This ensures changes to the kvstore persist across successive CheckTx. counterKey := []byte("counter-key") - txHandlerOpt := func(bapp *baseapp.BaseApp) { - legacyRouter := middleware.NewLegacyRouter() + anteOpt := func(bapp *BaseApp) { bapp.SetAnteHandler(anteHandlerTxTest(t, capKey1, counterKey)) } + routerOpt := func(bapp *BaseApp) { // TODO: can remove this once CheckTx doesnt process msgs. - legacyRouter.AddRoute(sdk.NewRoute(routeMsgCounter, func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) { + bapp.Router().AddRoute(sdk.NewRoute(routeMsgCounter, func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) { return &sdk.Result{}, nil })) - txHandler := testTxHandler( - middleware.TxHandlerOptions{ - LegacyRouter: legacyRouter, - MsgServiceRouter: middleware.NewMsgServiceRouter(encCfg.InterfaceRegistry), - TxDecoder: testTxDecoder(encCfg.Amino), - }, - customHandlerTxTest(t, capKey1, counterKey), - ) - bapp.SetTxHandler(txHandler) } - app, err := setupBaseApp(t, txHandlerOpt) - require.NoError(t, err) + app := setupBaseApp(t, anteOpt, routerOpt) nTxs := int64(5) app.InitChain(abci.RequestInitChain{}) + // Create same codec used in txDecoder + codec := codec.NewLegacyAmino() + registerTestCodec(codec) + for i := int64(0); i < nTxs; i++ { tx := newTxCounter(i, 0) // no messages - txBytes, err := encCfg.Amino.Marshal(tx) + txBytes, err := codec.Marshal(tx) require.NoError(t, err) r := app.CheckTx(abci.RequestCheckTx{Tx: txBytes}) require.Empty(t, r.GetEvents()) - require.True(t, r.IsOK(), fmt.Sprintf("%+v", r)) + require.True(t, r.IsOK(), fmt.Sprintf("%v", r)) } - checkStateStore := app.CheckState().Context().KVStore(capKey1) + checkStateStore := app.checkState.ctx.KVStore(capKey1) storedCounter := getIntFromStore(checkStateStore, counterKey) - // Ensure storedCounter + // Ensure AnteHandler ran 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().Context().BlockGasMeter(), "block gas meter should have been set to checkState") - require.NotEmpty(t, app.CheckState().Context().HeaderHash()) + require.NotNil(t, app.checkState.ctx.BlockGasMeter(), "block gas meter should have been set to checkState") + require.NotEmpty(t, app.checkState.ctx.HeaderHash()) app.EndBlock(abci.RequestEndBlock{}) app.Commit() - checkStateStore = app.CheckState().Context().KVStore(capKey1) + checkStateStore = app.checkState.ctx.KVStore(capKey1) storedBytes := checkStateStore.Get(counterKey) require.Nil(t, storedBytes) } @@ -1062,30 +961,24 @@ 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 post txHandler + // test increments in the ante anteKey := []byte("ante-key") + anteOpt := func(bapp *BaseApp) { bapp.SetAnteHandler(anteHandlerTxTest(t, capKey1, anteKey)) } + // test increments in the handler deliverKey := []byte("deliver-key") - - txHandlerOpt := func(bapp *baseapp.BaseApp) { - legacyRouter := middleware.NewLegacyRouter() + routerOpt := func(bapp *BaseApp) { r := sdk.NewRoute(routeMsgCounter, handlerMsgCounter(t, capKey1, deliverKey)) - legacyRouter.AddRoute(r) - txHandler := testTxHandler( - middleware.TxHandlerOptions{ - LegacyRouter: legacyRouter, - MsgServiceRouter: middleware.NewMsgServiceRouter(encCfg.InterfaceRegistry), - TxDecoder: testTxDecoder(encCfg.Amino), - }, - customHandlerTxTest(t, capKey1, anteKey), - ) - bapp.SetTxHandler(txHandler) + bapp.Router().AddRoute(r) } - app, err := setupBaseApp(t, txHandlerOpt) - require.NoError(t, err) + app := setupBaseApp(t, anteOpt, routerOpt) app.InitChain(abci.RequestInitChain{}) + // Create same codec used in txDecoder + codec := codec.NewLegacyAmino() + registerTestCodec(codec) + nBlocks := 3 txPerHeight := 5 @@ -1097,14 +990,14 @@ func TestDeliverTx(t *testing.T) { counter := int64(blockN*txPerHeight + i) tx := newTxCounter(counter, counter) - txBytes, err := encCfg.Amino.Marshal(tx) + txBytes, err := codec.Marshal(tx) require.NoError(t, err) res := app.DeliverTx(abci.RequestDeliverTx{Tx: txBytes}) 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("post_handlers", counter).ToABCIEvents(), map[string]struct{}{})[0], events[0], "ante handler event") + 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(sdk.EventTypeMessage, counter).ToABCIEvents(), map[string]struct{}{})[0], events[2], "msg handler update counter event") } @@ -1123,29 +1016,23 @@ func TestMultiMsgCheckTx(t *testing.T) { func TestMultiMsgDeliverTx(t *testing.T) { // increment the tx counter anteKey := []byte("ante-key") + anteOpt := func(bapp *BaseApp) { bapp.SetAnteHandler(anteHandlerTxTest(t, capKey1, anteKey)) } + // increment the msg counter deliverKey := []byte("deliver-key") deliverKey2 := []byte("deliver-key2") - - txHandlerOpt := func(bapp *baseapp.BaseApp) { - legacyRouter := middleware.NewLegacyRouter() + routerOpt := func(bapp *BaseApp) { r1 := sdk.NewRoute(routeMsgCounter, handlerMsgCounter(t, capKey1, deliverKey)) r2 := sdk.NewRoute(routeMsgCounter2, handlerMsgCounter(t, capKey1, deliverKey2)) - legacyRouter.AddRoute(r1) - legacyRouter.AddRoute(r2) - txHandler := testTxHandler( - middleware.TxHandlerOptions{ - LegacyRouter: legacyRouter, - MsgServiceRouter: middleware.NewMsgServiceRouter(encCfg.InterfaceRegistry), - TxDecoder: testTxDecoder(encCfg.Amino), - }, - customHandlerTxTest(t, capKey1, anteKey), - ) - bapp.SetTxHandler(txHandler) + bapp.Router().AddRoute(r1) + bapp.Router().AddRoute(r2) } - app, err := setupBaseApp(t, txHandlerOpt) - require.NoError(t, err) + app := setupBaseApp(t, anteOpt, routerOpt) + + // Create same codec used in txDecoder + codec := codec.NewLegacyAmino() + registerTestCodec(codec) // run a multi-msg tx // with all msgs the same route @@ -1153,12 +1040,12 @@ func TestMultiMsgDeliverTx(t *testing.T) { header := tmproto.Header{Height: 1} app.BeginBlock(abci.RequestBeginBlock{Header: header}) tx := newTxCounter(0, 0, 1, 2) - txBytes, err := encCfg.Amino.Marshal(tx) + txBytes, err := codec.Marshal(tx) require.NoError(t, err) res := app.DeliverTx(abci.RequestDeliverTx{Tx: txBytes}) require.True(t, res.IsOK(), fmt.Sprintf("%v", res)) - store := app.DeliverState().Context().KVStore(capKey1) + store := app.deliverState.ctx.KVStore(capKey1) // tx counter only incremented once txCounter := getIntFromStore(store, anteKey) @@ -1173,12 +1060,12 @@ func TestMultiMsgDeliverTx(t *testing.T) { tx = newTxCounter(1, 3) tx.Msgs = append(tx.Msgs, msgCounter2{0}) tx.Msgs = append(tx.Msgs, msgCounter2{1}) - txBytes, err = encCfg.Amino.Marshal(tx) + txBytes, err = codec.Marshal(tx) require.NoError(t, err) res = app.DeliverTx(abci.RequestDeliverTx{Tx: txBytes}) require.True(t, res.IsOK(), fmt.Sprintf("%v", res)) - store = app.DeliverState().Context().KVStore(capKey1) + store = app.deliverState.ctx.KVStore(capKey1) // tx counter only incremented once txCounter = getIntFromStore(store, anteKey) @@ -1205,36 +1092,29 @@ func TestConcurrentCheckDeliver(t *testing.T) { func TestSimulateTx(t *testing.T) { gasConsumed := uint64(5) - txHandlerOpt := func(bapp *baseapp.BaseApp) { - legacyRouter := middleware.NewLegacyRouter() + anteOpt := func(bapp *BaseApp) { + bapp.SetAnteHandler(func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, err error) { + newCtx = ctx.WithGasMeter(sdk.NewGasMeter(gasConsumed)) + return + }) + } + + routerOpt := func(bapp *BaseApp) { r := sdk.NewRoute(routeMsgCounter, func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) { ctx.GasMeter().ConsumeGas(gasConsumed, "test") - // Return dummy MsgResponse for msgCounter. - any, err := codectypes.NewAnyWithValue(&testdata.Dog{}) - if err != nil { - return nil, err - } - - return &sdk.Result{ - MsgResponses: []*codectypes.Any{any}, - }, nil + return &sdk.Result{}, nil }) - legacyRouter.AddRoute(r) - txHandler := testTxHandler( - middleware.TxHandlerOptions{ - LegacyRouter: legacyRouter, - MsgServiceRouter: middleware.NewMsgServiceRouter(encCfg.InterfaceRegistry), - TxDecoder: testTxDecoder(encCfg.Amino), - }, - func(ctx sdk.Context, tx sdk.Tx, simulate bool) (sdk.Context, error) { return ctx, nil }, - ) - bapp.SetTxHandler(txHandler) + bapp.Router().AddRoute(r) } - app, err := setupBaseApp(t, txHandlerOpt) - require.NoError(t, err) + + app := setupBaseApp(t, anteOpt, routerOpt) app.InitChain(abci.RequestInitChain{}) + // Create same codec used in txDecoder + cdc := codec.NewLegacyAmino() + registerTestCodec(cdc) + nBlocks := 3 for blockN := 0; blockN < nBlocks; blockN++ { count := int64(blockN + 1) @@ -1242,8 +1122,7 @@ func TestSimulateTx(t *testing.T) { app.BeginBlock(abci.RequestBeginBlock{Header: header}) tx := newTxCounter(count, count) - tx.GasLimit = gasConsumed - txBytes, err := encCfg.Amino.Marshal(tx) + txBytes, err := cdc.Marshal(tx) require.Nil(t, err) // simulate a message, check gas reported @@ -1280,33 +1159,19 @@ func TestSimulateTx(t *testing.T) { } func TestRunInvalidTransaction(t *testing.T) { - txHandlerOpt := func(bapp *baseapp.BaseApp) { - legacyRouter := middleware.NewLegacyRouter() + anteOpt := func(bapp *BaseApp) { + bapp.SetAnteHandler(func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, err error) { + return + }) + } + routerOpt := func(bapp *BaseApp) { r := sdk.NewRoute(routeMsgCounter, func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) { - any, err := codectypes.NewAnyWithValue(msg) - if err != nil { - return nil, err - } - - return &sdk.Result{ - MsgResponses: []*codectypes.Any{any}, - }, nil + return &sdk.Result{}, nil }) - legacyRouter.AddRoute(r) - txHandler := testTxHandler( - middleware.TxHandlerOptions{ - LegacyRouter: legacyRouter, - MsgServiceRouter: middleware.NewMsgServiceRouter(encCfg.InterfaceRegistry), - TxDecoder: testTxDecoder(encCfg.Amino), - }, - func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, err error) { - return - }, - ) - bapp.SetTxHandler(txHandler) + bapp.Router().AddRoute(r) } - app, err := setupBaseApp(t, txHandlerOpt) - require.NoError(t, err) + + app := setupBaseApp(t, anteOpt, routerOpt) header := tmproto.Header{Height: 1} app.BeginBlock(abci.RequestBeginBlock{Header: header}) @@ -1314,7 +1179,8 @@ func TestRunInvalidTransaction(t *testing.T) { // transaction with no messages { emptyTx := &txTest{} - _, result, err := app.SimDeliver(aminoTxEncoder(encCfg.Amino), emptyTx) + _, result, err := app.SimDeliver(aminoTxEncoder(), emptyTx) + require.Error(t, err) require.Nil(t, result) space, code, _ := sdkerrors.ABCIInfo(err, false) @@ -1325,7 +1191,7 @@ func TestRunInvalidTransaction(t *testing.T) { // transaction where ValidateBasic fails { testCases := []struct { - tx txTest + tx *txTest fail bool }{ {newTxCounter(0, 0), false}, @@ -1340,7 +1206,7 @@ func TestRunInvalidTransaction(t *testing.T) { for _, testCase := range testCases { tx := testCase.tx - _, _, err := app.SimDeliver(aminoTxEncoder(encCfg.Amino), tx) + _, result, err := app.SimDeliver(aminoTxEncoder(), tx) if testCase.fail { require.Error(t, err) @@ -1349,15 +1215,15 @@ func TestRunInvalidTransaction(t *testing.T) { require.EqualValues(t, sdkerrors.ErrInvalidSequence.Codespace(), space, err) require.EqualValues(t, sdkerrors.ErrInvalidSequence.ABCICode(), code, err) } else { - require.NoError(t, err) + require.NotNil(t, result) } } } // transaction with no known route { - unknownRouteTx := txTest{[]sdk.Msg{&msgNoRoute{}}, 0, false, math.MaxUint64} - _, result, err := app.SimDeliver(aminoTxEncoder(encCfg.Amino), unknownRouteTx) + unknownRouteTx := txTest{[]sdk.Msg{msgNoRoute{}}, 0, false} + _, result, err := app.SimDeliver(aminoTxEncoder(), unknownRouteTx) require.Error(t, err) require.Nil(t, result) @@ -1365,8 +1231,8 @@ func TestRunInvalidTransaction(t *testing.T) { require.EqualValues(t, sdkerrors.ErrUnknownRequest.Codespace(), space, err) require.EqualValues(t, sdkerrors.ErrUnknownRequest.ABCICode(), code, err) - unknownRouteTx = txTest{[]sdk.Msg{&msgCounter{}, &msgNoRoute{}}, 0, false, math.MaxUint64} - _, result, err = app.SimDeliver(aminoTxEncoder(encCfg.Amino), unknownRouteTx) + unknownRouteTx = txTest{[]sdk.Msg{msgCounter{}, msgNoRoute{}}, 0, false} + _, result, err = app.SimDeliver(aminoTxEncoder(), unknownRouteTx) require.Error(t, err) require.Nil(t, result) @@ -1378,14 +1244,12 @@ func TestRunInvalidTransaction(t *testing.T) { // Transaction with an unregistered message { tx := newTxCounter(0, 0) - tx.Msgs = append(tx.Msgs, &msgNoDecode{}) + tx.Msgs = append(tx.Msgs, msgNoDecode{}) - // new codec so we can encode the tx, but we shouldn't be able to decode, - // because baseapp's codec is not aware of msgNoDecode. + // new codec so we can encode the tx, but we shouldn't be able to decode newCdc := codec.NewLegacyAmino() - sdk.RegisterLegacyAminoCodec(newCdc) // register Tx, Msg registerTestCodec(newCdc) - legacy.RegisterAminoMsg(newCdc, &msgNoDecode{}, "cosmos-sdk/baseapp/msgNoDecode") + newCdc.RegisterConcrete(&msgNoDecode{}, "cosmos-sdk/baseapp/msgNoDecode", nil) txBytes, err := newCdc.Marshal(tx) require.NoError(t, err) @@ -1399,47 +1263,49 @@ func TestRunInvalidTransaction(t *testing.T) { // Test that transactions exceeding gas limits fail func TestTxGasLimits(t *testing.T) { gasGranted := uint64(10) + anteOpt := func(bapp *BaseApp) { + bapp.SetAnteHandler(func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, err error) { + newCtx = ctx.WithGasMeter(sdk.NewGasMeter(gasGranted)) + + // 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: + err = sdkerrors.Wrapf(sdkerrors.ErrOutOfGas, "out of gas in location: %v", rType.Descriptor) + default: + panic(r) + } + } + }() + + count := tx.(txTest).Counter + newCtx.GasMeter().ConsumeGas(uint64(count), "counter-ante") + + return newCtx, nil + }) - ante := func(ctx sdk.Context, tx sdk.Tx, simulate bool) (sdk.Context, error) { - count := tx.(txTest).Counter - ctx.GasMeter().ConsumeGas(uint64(count), "counter-ante") - return ctx, nil } - txHandlerOpt := func(bapp *baseapp.BaseApp) { - legacyRouter := middleware.NewLegacyRouter() + routerOpt := func(bapp *BaseApp) { r := sdk.NewRoute(routeMsgCounter, func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) { count := msg.(*msgCounter).Counter ctx.GasMeter().ConsumeGas(uint64(count), "counter-handler") - any, err := codectypes.NewAnyWithValue(msg) - if err != nil { - return nil, err - } - - return &sdk.Result{ - MsgResponses: []*codectypes.Any{any}, - }, nil + return &sdk.Result{}, nil }) - legacyRouter.AddRoute(r) - txHandler := testTxHandler( - middleware.TxHandlerOptions{ - LegacyRouter: legacyRouter, - MsgServiceRouter: middleware.NewMsgServiceRouter(encCfg.InterfaceRegistry), - TxDecoder: testTxDecoder(encCfg.Amino), - }, - ante, - ) - - bapp.SetTxHandler(txHandler) + bapp.Router().AddRoute(r) } - app, err := setupBaseApp(t, txHandlerOpt) - require.NoError(t, err) + + app := setupBaseApp(t, anteOpt, routerOpt) header := tmproto.Header{Height: 1} app.BeginBlock(abci.RequestBeginBlock{Header: header}) testCases := []struct { - tx txTest + tx *txTest gasUsed uint64 fail bool }{ @@ -1464,8 +1330,7 @@ func TestTxGasLimits(t *testing.T) { for i, tc := range testCases { tx := tc.tx - tx.GasLimit = gasGranted - gInfo, result, err := app.SimDeliver(aminoTxEncoder(encCfg.Amino), tx) + gInfo, result, err := app.SimDeliver(aminoTxEncoder(), tx) // check gas used and wanted require.Equal(t, tc.gasUsed, gInfo.GasUsed, fmt.Sprintf("tc #%d; gas: %v, result: %v, err: %s", i, gInfo, result, err)) @@ -1487,41 +1352,38 @@ func TestTxGasLimits(t *testing.T) { // Test that transactions exceeding gas limits fail func TestMaxBlockGasLimits(t *testing.T) { gasGranted := uint64(10) - ante := func(ctx sdk.Context, tx sdk.Tx, simulate bool) (sdk.Context, error) { - count := tx.(txTest).Counter - ctx.GasMeter().ConsumeGas(uint64(count), "counter-ante") + anteOpt := func(bapp *BaseApp) { + bapp.SetAnteHandler(func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, err error) { + newCtx = ctx.WithGasMeter(sdk.NewGasMeter(gasGranted)) + + defer func() { + if r := recover(); r != nil { + switch rType := r.(type) { + case sdk.ErrorOutOfGas: + err = sdkerrors.Wrapf(sdkerrors.ErrOutOfGas, "out of gas in location: %v", rType.Descriptor) + default: + panic(r) + } + } + }() - return ctx, nil + count := tx.(txTest).Counter + newCtx.GasMeter().ConsumeGas(uint64(count), "counter-ante") + + return + }) } - txHandlerOpt := func(bapp *baseapp.BaseApp) { - legacyRouter := middleware.NewLegacyRouter() + routerOpt := func(bapp *BaseApp) { r := sdk.NewRoute(routeMsgCounter, func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) { count := msg.(*msgCounter).Counter ctx.GasMeter().ConsumeGas(uint64(count), "counter-handler") - - any, err := codectypes.NewAnyWithValue(msg) - if err != nil { - return nil, err - } - - return &sdk.Result{ - MsgResponses: []*codectypes.Any{any}, - }, nil + return &sdk.Result{}, nil }) - legacyRouter.AddRoute(r) - txHandler := testTxHandler( - middleware.TxHandlerOptions{ - LegacyRouter: legacyRouter, - MsgServiceRouter: middleware.NewMsgServiceRouter(encCfg.InterfaceRegistry), - TxDecoder: testTxDecoder(encCfg.Amino), - }, - ante, - ) - bapp.SetTxHandler(txHandler) + bapp.Router().AddRoute(r) } - app, err := setupBaseApp(t, txHandlerOpt) - require.NoError(t, err) + + app := setupBaseApp(t, anteOpt, routerOpt) app.InitChain(abci.RequestInitChain{ ConsensusParams: &tmproto.ConsensusParams{ Block: &tmproto.BlockParams{ @@ -1531,7 +1393,7 @@ func TestMaxBlockGasLimits(t *testing.T) { }) testCases := []struct { - tx txTest + tx *txTest numDelivers int gasUsedPerDeliver uint64 fail bool @@ -1551,7 +1413,6 @@ func TestMaxBlockGasLimits(t *testing.T) { for i, tc := range testCases { tx := tc.tx - tx.GasLimit = gasGranted // reset the block gas header := tmproto.Header{Height: app.LastBlockHeight() + 1} @@ -1559,9 +1420,9 @@ func TestMaxBlockGasLimits(t *testing.T) { // execute the transaction multiple times for j := 0; j < tc.numDelivers; j++ { - _, result, err := app.SimDeliver(aminoTxEncoder(encCfg.Amino), tx) + _, result, err := app.SimDeliver(aminoTxEncoder(), tx) - ctx := app.DeliverState().Context() + ctx := app.getState(runTxModeDeliver).ctx // check for failed transactions if tc.fail && (j+1) > tc.failAfterDeliver { @@ -1588,28 +1449,66 @@ func TestMaxBlockGasLimits(t *testing.T) { } } -func TestBaseAppMiddleware(t *testing.T) { +// Test custom panic handling within app.DeliverTx method +func TestCustomRunTxPanicHandler(t *testing.T) { + const customPanicMsg = "test panic" + anteErr := sdkerrors.Register("fakeModule", 100500, "fakeError") + + anteOpt := func(bapp *BaseApp) { + bapp.SetAnteHandler(func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, err error) { + panic(sdkerrors.Wrap(anteErr, "anteHandler")) + }) + } + routerOpt := func(bapp *BaseApp) { + r := sdk.NewRoute(routeMsgCounter, func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) { + return &sdk.Result{}, nil + }) + bapp.Router().AddRoute(r) + } + + app := setupBaseApp(t, anteOpt, routerOpt) + + header := tmproto.Header{Height: 1} + app.BeginBlock(abci.RequestBeginBlock{Header: header}) + + app.AddRunTxRecoveryHandler(func(recoveryObj interface{}) error { + err, ok := recoveryObj.(error) + if !ok { + return nil + } + + if anteErr.Is(err) { + panic(customPanicMsg) + } else { + return nil + } + }) + + // Transaction should panic with custom handler above + { + tx := newTxCounter(0, 0) + + require.PanicsWithValue(t, customPanicMsg, func() { app.SimDeliver(aminoTxEncoder(), tx) }) + } +} + +func TestBaseAppAnteHandler(t *testing.T) { anteKey := []byte("ante-key") - deliverKey := []byte("deliver-key") + anteOpt := func(bapp *BaseApp) { + bapp.SetAnteHandler(anteHandlerTxTest(t, capKey1, anteKey)) + } - txHandlerOpt := func(bapp *baseapp.BaseApp) { - legacyRouter := middleware.NewLegacyRouter() + deliverKey := []byte("deliver-key") + routerOpt := func(bapp *BaseApp) { r := sdk.NewRoute(routeMsgCounter, handlerMsgCounter(t, capKey1, deliverKey)) - legacyRouter.AddRoute(r) - txHandler := testTxHandler( - middleware.TxHandlerOptions{ - LegacyRouter: legacyRouter, - MsgServiceRouter: middleware.NewMsgServiceRouter(encCfg.InterfaceRegistry), - TxDecoder: testTxDecoder(encCfg.Amino), - }, - customHandlerTxTest(t, capKey1, anteKey), - ) - bapp.SetTxHandler(txHandler) + bapp.Router().AddRoute(r) } - app, err := setupBaseApp(t, txHandlerOpt) - require.NoError(t, err) + + cdc := codec.NewLegacyAmino() + app := setupBaseApp(t, anteOpt, routerOpt) app.InitChain(abci.RequestInitChain{}) + registerTestCodec(cdc) header := tmproto.Header{Height: app.LastBlockHeight() + 1} app.BeginBlock(abci.RequestBeginBlock{Header: header}) @@ -1617,16 +1516,16 @@ func TestBaseAppMiddleware(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 (customHandlerTxTest). + // the next txs ante handler execution (anteHandlerTxTest). tx := newTxCounter(0, 0) tx.setFailOnAnte(true) - txBytes, err := encCfg.Amino.Marshal(tx) + txBytes, err := cdc.Marshal(tx) require.NoError(t, err) res := app.DeliverTx(abci.RequestDeliverTx{Tx: txBytes}) require.Empty(t, res.Events) require.False(t, res.IsOK(), fmt.Sprintf("%v", res)) - ctx := app.DeliverState().Context() + ctx := app.getState(runTxModeDeliver).ctx store := ctx.KVStore(capKey1) require.Equal(t, int64(0), getIntFromStore(store, anteKey)) @@ -1635,14 +1534,15 @@ func TestBaseAppMiddleware(t *testing.T) { tx = newTxCounter(0, 0) tx.setFailOnHandler(true) - txBytes, err = encCfg.Amino.Marshal(tx) + txBytes, err = cdc.Marshal(tx) require.NoError(t, err) res = app.DeliverTx(abci.RequestDeliverTx{Tx: txBytes}) - require.Empty(t, res.Events) + // should emit ante event + require.NotEmpty(t, res.Events) require.False(t, res.IsOK(), fmt.Sprintf("%v", res)) - ctx = app.DeliverState().Context() + ctx = app.getState(runTxModeDeliver).ctx store = ctx.KVStore(capKey1) require.Equal(t, int64(1), getIntFromStore(store, anteKey)) require.Equal(t, int64(0), getIntFromStore(store, deliverKey)) @@ -1651,14 +1551,14 @@ func TestBaseAppMiddleware(t *testing.T) { // implicitly checked by previous tx executions tx = newTxCounter(1, 0) - txBytes, err = encCfg.Amino.Marshal(tx) + txBytes, err = cdc.Marshal(tx) require.NoError(t, err) res = app.DeliverTx(abci.RequestDeliverTx{Tx: txBytes}) require.NotEmpty(t, res.Events) require.True(t, res.IsOK(), fmt.Sprintf("%v", res)) - ctx = app.DeliverState().Context() + ctx = app.getState(runTxModeDeliver).ctx store = ctx.KVStore(capKey1) require.Equal(t, int64(2), getIntFromStore(store, anteKey)) require.Equal(t, int64(1), getIntFromStore(store, deliverKey)) @@ -1670,36 +1570,45 @@ func TestBaseAppMiddleware(t *testing.T) { func TestGasConsumptionBadTx(t *testing.T) { gasWanted := uint64(5) - ante := func(ctx sdk.Context, tx sdk.Tx, simulate bool) (sdk.Context, error) { - txTest := tx.(txTest) - ctx.GasMeter().ConsumeGas(uint64(txTest.Counter), "counter-ante") - if txTest.FailOnAnte { - return ctx, sdkerrors.Wrap(sdkerrors.ErrUnauthorized, "ante handler failure") - } + anteOpt := func(bapp *BaseApp) { + bapp.SetAnteHandler(func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, err error) { + newCtx = ctx.WithGasMeter(sdk.NewGasMeter(gasWanted)) + + defer func() { + if r := recover(); r != nil { + switch rType := r.(type) { + case sdk.ErrorOutOfGas: + log := fmt.Sprintf("out of gas in location: %v", rType.Descriptor) + err = sdkerrors.Wrap(sdkerrors.ErrOutOfGas, log) + default: + panic(r) + } + } + }() - return ctx, nil + txTest := tx.(txTest) + newCtx.GasMeter().ConsumeGas(uint64(txTest.Counter), "counter-ante") + if txTest.FailOnAnte { + return newCtx, sdkerrors.Wrap(sdkerrors.ErrUnauthorized, "ante handler failure") + } + + return + }) } - txHandlerOpt := func(bapp *baseapp.BaseApp) { - legacyRouter := middleware.NewLegacyRouter() + routerOpt := func(bapp *BaseApp) { r := sdk.NewRoute(routeMsgCounter, func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) { count := msg.(*msgCounter).Counter ctx.GasMeter().ConsumeGas(uint64(count), "counter-handler") return &sdk.Result{}, nil }) - legacyRouter.AddRoute(r) - txHandler := testTxHandler( - middleware.TxHandlerOptions{ - LegacyRouter: legacyRouter, - MsgServiceRouter: middleware.NewMsgServiceRouter(encCfg.InterfaceRegistry), - TxDecoder: testTxDecoder(encCfg.Amino), - }, - ante, - ) - bapp.SetTxHandler(txHandler) + bapp.Router().AddRoute(r) } - app, err := setupBaseApp(t, txHandlerOpt) - require.NoError(t, err) + + cdc := codec.NewLegacyAmino() + registerTestCodec(cdc) + + app := setupBaseApp(t, anteOpt, routerOpt) app.InitChain(abci.RequestInitChain{ ConsensusParams: &tmproto.ConsensusParams{ Block: &tmproto.BlockParams{ @@ -1714,9 +1623,8 @@ func TestGasConsumptionBadTx(t *testing.T) { app.BeginBlock(abci.RequestBeginBlock{Header: header}) tx := newTxCounter(5, 0) - tx.GasLimit = gasWanted tx.setFailOnAnte(true) - txBytes, err := encCfg.Amino.Marshal(tx) + txBytes, err := cdc.Marshal(tx) require.NoError(t, err) res := app.DeliverTx(abci.RequestDeliverTx{Tx: txBytes}) @@ -1724,7 +1632,7 @@ func TestGasConsumptionBadTx(t *testing.T) { // require next tx to fail due to black gas limit tx = newTxCounter(5, 0) - txBytes, err = encCfg.Amino.Marshal(tx) + txBytes, err = cdc.Marshal(tx) require.NoError(t, err) res = app.DeliverTx(abci.RequestDeliverTx{Tx: txBytes}) @@ -1734,38 +1642,24 @@ func TestGasConsumptionBadTx(t *testing.T) { // Test that we can only query from the latest committed state. func TestQuery(t *testing.T) { key, value := []byte("hello"), []byte("goodbye") + anteOpt := func(bapp *BaseApp) { + bapp.SetAnteHandler(func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, err error) { + store := ctx.KVStore(capKey1) + store.Set(key, value) + return + }) + } - txHandlerOpt := func(bapp *baseapp.BaseApp) { - legacyRouter := middleware.NewLegacyRouter() + routerOpt := func(bapp *BaseApp) { r := sdk.NewRoute(routeMsgCounter, func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) { store := ctx.KVStore(capKey1) store.Set(key, value) - - any, err := codectypes.NewAnyWithValue(msg) - if err != nil { - return nil, err - } - return &sdk.Result{ - MsgResponses: []*codectypes.Any{any}, - }, nil + return &sdk.Result{}, nil }) - legacyRouter.AddRoute(r) - txHandler := testTxHandler( - middleware.TxHandlerOptions{ - LegacyRouter: legacyRouter, - MsgServiceRouter: middleware.NewMsgServiceRouter(encCfg.InterfaceRegistry), - TxDecoder: testTxDecoder(encCfg.Amino), - }, - func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, err error) { - store := ctx.KVStore(capKey1) - store.Set(key, value) - return - }, - ) - bapp.SetTxHandler(txHandler) + bapp.Router().AddRoute(r) } - app, err := setupBaseApp(t, txHandlerOpt) - require.NoError(t, err) + + app := setupBaseApp(t, anteOpt, routerOpt) app.InitChain(abci.RequestInitChain{}) @@ -1783,7 +1677,7 @@ func TestQuery(t *testing.T) { require.Equal(t, 0, len(res.Value)) // query is still empty after a CheckTx - _, resTx, err := app.SimCheck(aminoTxEncoder(encCfg.Amino), tx) + _, resTx, err := app.SimCheck(aminoTxEncoder(), tx) require.NoError(t, err) require.NotNil(t, resTx) res = app.Query(query) @@ -1793,7 +1687,7 @@ func TestQuery(t *testing.T) { header := tmproto.Header{Height: app.LastBlockHeight() + 1} app.BeginBlock(abci.RequestBeginBlock{Header: header}) - _, resTx, err = app.SimDeliver(aminoTxEncoder(encCfg.Amino), tx) + _, resTx, err = app.SimDeliver(aminoTxEncoder(), tx) require.NoError(t, err) require.NotNil(t, resTx) res = app.Query(query) @@ -1806,15 +1700,14 @@ func TestQuery(t *testing.T) { } func TestGRPCQuery(t *testing.T) { - grpcQueryOpt := func(bapp *baseapp.BaseApp) { + grpcQueryOpt := func(bapp *BaseApp) { testdata.RegisterQueryServer( bapp.GRPCQueryRouter(), testdata.QueryImpl{}, ) } - app, err := setupBaseApp(t, grpcQueryOpt) - require.NoError(t, err) + app := setupBaseApp(t, grpcQueryOpt) app.GRPCQueryRouter().SetInterfaceRegistry(codectypes.NewInterfaceRegistry()) app.InitChain(abci.RequestInitChain{}) @@ -1841,60 +1734,23 @@ func TestGRPCQuery(t *testing.T) { require.Equal(t, "Hello foo!", res.Greeting) } -func TestGRPCQueryPulsar(t *testing.T) { - grpcQueryOpt := func(bapp *baseapp.BaseApp) { - testdata_pulsar.RegisterQueryServer( - bapp.GRPCQueryRouter(), - testdata_pulsar.QueryImpl{}, - ) - } - - app, err := setupBaseApp(t, grpcQueryOpt) - require.NoError(t, err) - app.GRPCQueryRouter().SetInterfaceRegistry(codectypes.NewInterfaceRegistry()) - - app.InitChain(abci.RequestInitChain{}) - header := tmproto.Header{Height: app.LastBlockHeight() + 1} - app.BeginBlock(abci.RequestBeginBlock{Header: header}) - app.Commit() - - req := &testdata_pulsar.SayHelloRequest{Name: "foo"} - reqBz, err := proto.Marshal(req) - require.NoError(t, err) - - reqQuery := abci.RequestQuery{ - Data: reqBz, - Path: "/testdata.Query/SayHello", - } - - resQuery := app.Query(reqQuery) - - require.Equal(t, abci.CodeTypeOK, resQuery.Code, resQuery) - - var res testdata_pulsar.SayHelloResponse - err = proto.Unmarshal(resQuery.Value, &res) - require.NoError(t, err) - require.Equal(t, "Hello foo!", res.Greeting) -} - // Test p2p filter queries func TestP2PQuery(t *testing.T) { - addrPeerFilterOpt := func(bapp *baseapp.BaseApp) { + addrPeerFilterOpt := func(bapp *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.BaseApp) { + idPeerFilterOpt := func(bapp *BaseApp) { bapp.SetIDPeerFilter(func(id string) abci.ResponseQuery { require.Equal(t, "testid", id) return abci.ResponseQuery{Code: uint32(4)} }) } - app, err := setupBaseApp(t, addrPeerFilterOpt, idPeerFilterOpt) - require.NoError(t, err) + app := setupBaseApp(t, addrPeerFilterOpt, idPeerFilterOpt) addrQuery := abci.RequestQuery{ Path: "/p2p/filter/addr/1.1.1.1:8000", @@ -1910,22 +1766,21 @@ func TestP2PQuery(t *testing.T) { } func TestGetMaximumBlockGas(t *testing.T) { - app, err := setupBaseApp(t) - require.NoError(t, err) + app := setupBaseApp(t) app.InitChain(abci.RequestInitChain{}) ctx := app.NewContext(true, tmproto.Header{}) app.StoreConsensusParams(ctx, &tmproto.ConsensusParams{Block: &tmproto.BlockParams{MaxGas: 0}}) - require.Equal(t, uint64(0), app.GetMaximumBlockGas(ctx)) + require.Equal(t, uint64(0), app.getMaximumBlockGas(ctx)) app.StoreConsensusParams(ctx, &tmproto.ConsensusParams{Block: &tmproto.BlockParams{MaxGas: -1}}) - require.Equal(t, uint64(0), app.GetMaximumBlockGas(ctx)) + require.Equal(t, uint64(0), app.getMaximumBlockGas(ctx)) app.StoreConsensusParams(ctx, &tmproto.ConsensusParams{Block: &tmproto.BlockParams{MaxGas: 5000000}}) - require.Equal(t, uint64(5000000), app.GetMaximumBlockGas(ctx)) + require.Equal(t, uint64(5000000), app.getMaximumBlockGas(ctx)) app.StoreConsensusParams(ctx, &tmproto.ConsensusParams{Block: &tmproto.BlockParams{MaxGas: -5000000}}) - require.Panics(t, func() { app.GetMaximumBlockGas(ctx) }) + require.Panics(t, func() { app.getMaximumBlockGas(ctx) }) } func TestListSnapshots(t *testing.T) { @@ -2276,23 +2131,25 @@ 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") + anteOpt := func(bapp *BaseApp) { bapp.SetAnteHandler(anteHandlerTxTest(t, capKey1, anteKey)) } + // test increments in the handler deliverKey := []byte("deliver-key") - - txHandlerOpt := func(bapp *baseapp.BaseApp) { - customRouter := &testCustomRouter{routes: sync.Map{}} + routerOpt := func(bapp *BaseApp) { + bapp.SetRouter(&testCustomRouter{routes: sync.Map{}}) r := sdk.NewRoute(routeMsgCounter, handlerMsgCounter(t, capKey1, deliverKey)) - customRouter.AddRoute(r) - txHandler := middleware.ComposeMiddlewares( - middleware.NewRunMsgsTxHandler(middleware.NewMsgServiceRouter(encCfg.InterfaceRegistry), customRouter), - middleware.NewTxDecoderMiddleware(testTxDecoder(encCfg.Amino)), - ) - bapp.SetTxHandler(txHandler) + bapp.Router().AddRoute(r) } - app, err := setupBaseApp(t, txHandlerOpt) - require.NoError(t, err) + + app := setupBaseApp(t, anteOpt, routerOpt) app.InitChain(abci.RequestInitChain{}) + // Create same codec used in txDecoder + codec := codec.NewLegacyAmino() + registerTestCodec(codec) + nBlocks := 3 txPerHeight := 5 @@ -2304,7 +2161,7 @@ func TestWithRouter(t *testing.T) { counter := int64(blockN*txPerHeight + i) tx := newTxCounter(counter, counter) - txBytes, err := encCfg.Amino.Marshal(tx) + txBytes, err := codec.Marshal(tx) require.NoError(t, err) res := app.DeliverTx(abci.RequestDeliverTx{Tx: txBytes}) @@ -2327,7 +2184,7 @@ func TestBaseApp_EndBlock(t *testing.T) { }, } - app := baseapp.NewBaseApp(name, logger, db) + app := NewBaseApp(name, logger, db, nil) app.SetParamStore(¶mStore{db: dbm.NewMemDB()}) app.InitChain(abci.RequestInitChain{ ConsensusParams: cp, @@ -2347,164 +2204,3 @@ func TestBaseApp_EndBlock(t *testing.T) { require.Equal(t, int64(100), res.GetValidatorUpdates()[0].Power) require.Equal(t, cp.Block.MaxGas, res.ConsensusParamUpdates.Block.MaxGas) } - -func TestBaseApp_Init(t *testing.T) { - db := dbm.NewMemDB() - name := t.Name() - logger := defaultLogger() - - snapshotStore, err := snapshots.NewStore(dbm.NewMemDB(), testutil.GetTempDir(t)) - require.NoError(t, err) - - testCases := map[string]struct { - bapp *baseapp.BaseApp - expectedPruning pruningtypes.PruningOptions - expectedSnapshot snapshottypes.SnapshotOptions - expectedErr error - }{ - "snapshot but no pruning": { - baseapp.NewBaseApp(name, logger, db, - baseapp.SetSnapshot(snapshotStore, snapshottypes.NewSnapshotOptions(1500, 2)), - ), - pruningtypes.NewPruningOptions(pruningtypes.PruningNothing), - snapshottypes.NewSnapshotOptions(1500, 2), - // if no pruning is set, the default is PruneNothing - nil, - }, - "pruning everything only": { - baseapp.NewBaseApp(name, logger, db, - baseapp.SetPruning(pruningtypes.NewPruningOptions(pruningtypes.PruningEverything)), - ), - pruningtypes.NewPruningOptions(pruningtypes.PruningEverything), - snapshottypes.NewSnapshotOptions(snapshottypes.SnapshotIntervalOff, 0), - nil, - }, - "pruning nothing only": { - baseapp.NewBaseApp(name, logger, db, - baseapp.SetPruning(pruningtypes.NewPruningOptions(pruningtypes.PruningNothing)), - ), - pruningtypes.NewPruningOptions(pruningtypes.PruningNothing), - snapshottypes.NewSnapshotOptions(snapshottypes.SnapshotIntervalOff, 0), - nil, - }, - "pruning default only": { - baseapp.NewBaseApp(name, logger, db, - baseapp.SetPruning(pruningtypes.NewPruningOptions(pruningtypes.PruningDefault)), - ), - pruningtypes.NewPruningOptions(pruningtypes.PruningDefault), - snapshottypes.NewSnapshotOptions(snapshottypes.SnapshotIntervalOff, 0), - nil, - }, - "pruning custom only": { - baseapp.NewBaseApp(name, logger, db, - baseapp.SetPruning(pruningtypes.NewCustomPruningOptions(10, 10)), - ), - pruningtypes.NewCustomPruningOptions(10, 10), - snapshottypes.NewSnapshotOptions(snapshottypes.SnapshotIntervalOff, 0), - nil, - }, - "pruning everything and snapshots": { - baseapp.NewBaseApp(name, logger, db, - baseapp.SetPruning(pruningtypes.NewPruningOptions(pruningtypes.PruningEverything)), - baseapp.SetSnapshot(snapshotStore, snapshottypes.NewSnapshotOptions(1500, 2)), - ), - pruningtypes.NewPruningOptions(pruningtypes.PruningEverything), - snapshottypes.NewSnapshotOptions(1500, 2), - nil, - }, - "pruning nothing and snapshots": { - baseapp.NewBaseApp(name, logger, db, - baseapp.SetPruning(pruningtypes.NewPruningOptions(pruningtypes.PruningNothing)), - baseapp.SetSnapshot(snapshotStore, snapshottypes.NewSnapshotOptions(1500, 2)), - ), - pruningtypes.NewPruningOptions(pruningtypes.PruningNothing), - snapshottypes.NewSnapshotOptions(1500, 2), - nil, - }, - "pruning default and snapshots": { - baseapp.NewBaseApp(name, logger, db, - baseapp.SetPruning(pruningtypes.NewPruningOptions(pruningtypes.PruningDefault)), - baseapp.SetSnapshot(snapshotStore, snapshottypes.NewSnapshotOptions(1500, 2)), - ), - pruningtypes.NewPruningOptions(pruningtypes.PruningDefault), - snapshottypes.NewSnapshotOptions(1500, 2), - nil, - }, - "pruning custom and snapshots": { - baseapp.NewBaseApp(name, logger, db, - baseapp.SetPruning(pruningtypes.NewCustomPruningOptions(10, 10)), - baseapp.SetSnapshot(snapshotStore, snapshottypes.NewSnapshotOptions(1500, 2)), - ), - pruningtypes.NewCustomPruningOptions(10, 10), - snapshottypes.NewSnapshotOptions(1500, 2), - nil, - }, - "error custom pruning 0 interval": { - baseapp.NewBaseApp(name, logger, db, - baseapp.SetPruning(pruningtypes.NewCustomPruningOptions(10, 0)), - baseapp.SetSnapshot(snapshotStore, snapshottypes.NewSnapshotOptions(1500, 2)), - ), - pruningtypes.NewCustomPruningOptions(10, 0), - snapshottypes.NewSnapshotOptions(1500, 2), - pruningtypes.ErrPruningIntervalZero, - }, - "error custom pruning too small interval": { - baseapp.NewBaseApp(name, logger, db, - baseapp.SetPruning(pruningtypes.NewCustomPruningOptions(10, 9)), - baseapp.SetSnapshot(snapshotStore, snapshottypes.NewSnapshotOptions(1500, 2)), - ), - pruningtypes.NewCustomPruningOptions(10, 9), - snapshottypes.NewSnapshotOptions(1500, 2), - pruningtypes.ErrPruningIntervalTooSmall, - }, - "error custom pruning too small keep recent": { - baseapp.NewBaseApp(name, logger, db, - baseapp.SetPruning(pruningtypes.NewCustomPruningOptions(1, 10)), - baseapp.SetSnapshot(snapshotStore, snapshottypes.NewSnapshotOptions(1500, 2)), - ), - pruningtypes.NewCustomPruningOptions(9, 10), - snapshottypes.NewSnapshotOptions(1500, 2), - pruningtypes.ErrPruningKeepRecentTooSmall, - }, - "snapshot zero interval - manager not set": { - baseapp.NewBaseApp(name, logger, db, - baseapp.SetPruning(pruningtypes.NewCustomPruningOptions(10, 10)), - baseapp.SetSnapshot(snapshotStore, snapshottypes.NewSnapshotOptions(snapshottypes.SnapshotIntervalOff, 2)), - ), - pruningtypes.NewCustomPruningOptions(10, 10), - snapshottypes.NewSnapshotOptions(snapshottypes.SnapshotIntervalOff, 0), - nil, - }, - "snapshot zero keep recent - allowed": { - baseapp.NewBaseApp(name, logger, db, - baseapp.SetPruning(pruningtypes.NewCustomPruningOptions(10, 10)), - baseapp.SetSnapshot(snapshotStore, snapshottypes.NewSnapshotOptions(1500, 0)), - ), - pruningtypes.NewCustomPruningOptions(10, 10), - snapshottypes.NewSnapshotOptions(1500, 0), // 0 snapshot-keep-recent means keep all - nil, - }, - } - - for _, tc := range testCases { - // Init and validate - require.Equal(t, tc.expectedErr, tc.bapp.Init()) - if tc.expectedErr != nil { - continue - } - - // Check that settings were set correctly - actualPruning := tc.bapp.CMS().GetPruning() - require.Equal(t, tc.expectedPruning, actualPruning) - - snapshotManager := tc.bapp.GetSnapshotManager() - if tc.expectedSnapshot.Interval == snapshottypes.SnapshotIntervalOff { - require.Nil(t, snapshotManager) - continue - } - require.NotNil(t, snapshotManager) - - require.Equal(t, tc.expectedSnapshot.Interval, snapshotManager.GetInterval()) - require.Equal(t, tc.expectedSnapshot.KeepRecent, snapshotManager.GetKeepRecent()) - } -} diff --git a/baseapp/block_gas_test.go b/baseapp/block_gas_test.go new file mode 100644 index 00000000000..db933cd7aa7 --- /dev/null +++ b/baseapp/block_gas_test.go @@ -0,0 +1,201 @@ +package baseapp_test + +import ( + "fmt" + "math" + "testing" + + "github.com/stretchr/testify/require" + abci "github.com/tendermint/tendermint/abci/types" + tmjson "github.com/tendermint/tendermint/libs/json" + "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" + "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" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + txtypes "github.com/cosmos/cosmos-sdk/types/tx" + "github.com/cosmos/cosmos-sdk/types/tx/signing" + xauthsigning "github.com/cosmos/cosmos-sdk/x/auth/signing" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + minttypes "github.com/cosmos/cosmos-sdk/x/mint/types" +) + +var blockMaxGas = uint64(simapp.DefaultConsensusParams.Block.MaxGas) + +func TestBaseApp_BlockGas(t *testing.T) { + testcases := []struct { + name string + gasToConsume uint64 // gas to consume in the msg execution + panicTx bool // panic explicitly in tx execution + expErr bool + }{ + {"less than block gas meter", 10, false, false}, + {"more than block gas meter", blockMaxGas, false, true}, + {"more than block gas meter", uint64(float64(blockMaxGas) * 1.2), false, true}, + {"consume MaxUint64", math.MaxUint64, false, true}, + {"consume MaxGasWanted", txtypes.MaxGasWanted, false, true}, + {"consume block gas when paniced", 10, true, true}, + } + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + var app *simapp.SimApp + routerOpt := func(bapp *baseapp.BaseApp) { + route := (&testdata.TestMsg{}).Route() + bapp.Router().AddRoute(sdk.NewRoute(route, func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) { + _, ok := msg.(*testdata.TestMsg) + if !ok { + return &sdk.Result{}, fmt.Errorf("Wrong Msg type, expected %T, got %T", (*testdata.TestMsg)(nil), msg) + } + ctx.KVStore(app.GetKey(banktypes.ModuleName)).Set([]byte("ok"), []byte("ok")) + ctx.GasMeter().ConsumeGas(tc.gasToConsume, "TestMsg") + if tc.panicTx { + panic("panic in tx execution") + } + return &sdk.Result{}, nil + })) + } + encCfg := simapp.MakeTestEncodingConfig() + encCfg.Amino.RegisterConcrete(&testdata.TestMsg{}, "testdata.TestMsg", nil) + encCfg.InterfaceRegistry.RegisterImplementations((*sdk.Msg)(nil), + &testdata.TestMsg{}, + ) + app = simapp.NewSimApp(log.NewNopLogger(), dbm.NewMemDB(), nil, true, map[int64]bool{}, "", 0, encCfg, simapp.EmptyAppOptions{}, routerOpt) + genState := simapp.GenesisStateWithSingleValidator(t, app) + stateBytes, err := tmjson.MarshalIndent(genState, "", " ") + require.NoError(t, err) + app.InitChain(abci.RequestInitChain{ + Validators: []abci.ValidatorUpdate{}, + ConsensusParams: simapp.DefaultConsensusParams, + AppStateBytes: stateBytes, + }) + + ctx := app.NewContext(false, tmproto.Header{}) + + // tx fee + feeCoin := sdk.NewCoin("atom", sdk.NewInt(150)) + feeAmount := sdk.NewCoins(feeCoin) + + // test account and fund + priv1, _, addr1 := testdata.KeyTestPubAddr() + err = app.BankKeeper.MintCoins(ctx, minttypes.ModuleName, feeAmount) + require.NoError(t, err) + err = app.BankKeeper.SendCoinsFromModuleToAccount(ctx, minttypes.ModuleName, addr1, feeAmount) + require.NoError(t, err) + require.Equal(t, feeCoin.Amount, app.BankKeeper.GetBalance(ctx, addr1, feeCoin.Denom).Amount) + seq, _ := app.AccountKeeper.GetSequence(ctx, addr1) + require.Equal(t, uint64(0), seq) + + // msg and signatures + msg := testdata.NewTestMsg(addr1) + + txBuilder := encCfg.TxConfig.NewTxBuilder() + require.NoError(t, txBuilder.SetMsgs(msg)) + txBuilder.SetFeeAmount(feeAmount) + txBuilder.SetGasLimit(txtypes.MaxGasWanted) // tx validation checks that gasLimit can't be bigger than this + + privs, accNums, accSeqs := []cryptotypes.PrivKey{priv1}, []uint64{7}, []uint64{0} + _, txBytes, err := createTestTx(encCfg.TxConfig, txBuilder, privs, accNums, accSeqs, ctx.ChainID()) + require.NoError(t, err) + + app.BeginBlock(abci.RequestBeginBlock{Header: tmproto.Header{Height: 1}}) + rsp := app.DeliverTx(abci.RequestDeliverTx{Tx: txBytes}) + + // check result + ctx = app.GetContextForDeliverTx(txBytes) + okValue := ctx.KVStore(app.GetKey(banktypes.ModuleName)).Get([]byte("ok")) + + if tc.expErr { + if tc.panicTx { + require.Equal(t, sdkerrors.ErrPanic.ABCICode(), rsp.Code) + } else { + require.Equal(t, sdkerrors.ErrOutOfGas.ABCICode(), rsp.Code) + } + require.Empty(t, okValue) + } else { + require.Equal(t, uint32(0), rsp.Code) + require.Equal(t, []byte("ok"), okValue) + } + // check block gas is always consumed + baseGas := uint64(63724) // baseGas is the gas consumed before tx msg + expGasConsumed := addUint64Saturating(tc.gasToConsume, baseGas) + if expGasConsumed > txtypes.MaxGasWanted { + // capped by gasLimit + expGasConsumed = txtypes.MaxGasWanted + } + require.Equal(t, expGasConsumed, ctx.BlockGasMeter().GasConsumed()) + // tx fee is always deducted + require.Equal(t, int64(0), app.BankKeeper.GetBalance(ctx, addr1, feeCoin.Denom).Amount.Int64()) + // sender's sequence is always increased + seq, err = app.AccountKeeper.GetSequence(ctx, addr1) + require.NoError(t, err) + require.Equal(t, uint64(1), seq) + }) + } +} + +func createTestTx(txConfig client.TxConfig, txBuilder client.TxBuilder, privs []cryptotypes.PrivKey, accNums []uint64, accSeqs []uint64, chainID string) (xauthsigning.Tx, []byte, 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: txConfig.SignModeHandler().DefaultMode(), + Signature: nil, + }, + Sequence: accSeqs[i], + } + + sigsV2 = append(sigsV2, sigV2) + } + err := txBuilder.SetSignatures(sigsV2...) + if err != nil { + return nil, 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{ + Address: sdk.AccAddress(priv.PubKey().Bytes()).String(), + ChainID: chainID, + AccountNumber: accNums[i], + Sequence: accSeqs[i], + } + sigV2, err := tx.SignWithPrivKey( + txConfig.SignModeHandler().DefaultMode(), signerData, + txBuilder, priv, txConfig, accSeqs[i]) + if err != nil { + return nil, nil, err + } + + sigsV2 = append(sigsV2, sigV2) + } + err = txBuilder.SetSignatures(sigsV2...) + if err != nil { + return nil, nil, err + } + + txBytes, err := txConfig.TxEncoder()(txBuilder.GetTx()) + if err != nil { + return nil, nil, err + } + + return txBuilder.GetTx(), txBytes, nil +} + +func addUint64Saturating(a, b uint64) uint64 { + if math.MaxUint64-a < b { + return math.MaxUint64 + } + + return a + b +} diff --git a/baseapp/custom_txhandler_test.go b/baseapp/custom_txhandler_test.go deleted file mode 100644 index 78e4c8befe5..00000000000 --- a/baseapp/custom_txhandler_test.go +++ /dev/null @@ -1,116 +0,0 @@ -package baseapp_test - -import ( - "context" - "fmt" - - "github.com/tendermint/tendermint/crypto/tmhash" - - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/types/tx" -) - -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. -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, req tx.Request, checkReq tx.RequestCheckTx) (tx.Response, tx.ResponseCheckTx, error) { - sdkCtx, err := txh.runHandler(ctx, req.Tx, req.TxBytes, false) - if err != nil { - return tx.Response{}, tx.ResponseCheckTx{}, err - } - - return txh.next.CheckTx(sdk.WrapSDKContext(sdkCtx), req, checkReq) -} - -// DeliverTx implements tx.Handler.DeliverTx method. -func (txh customTxHandler) DeliverTx(ctx context.Context, req tx.Request) (tx.Response, error) { - sdkCtx, err := txh.runHandler(ctx, req.Tx, req.TxBytes, false) - if err != nil { - return tx.Response{}, err - } - - return txh.next.DeliverTx(sdk.WrapSDKContext(sdkCtx), req) -} - -// SimulateTx implements tx.Handler.SimulateTx method. -func (txh customTxHandler) SimulateTx(ctx context.Context, req tx.Request) (tx.Response, error) { - sdkCtx, err := txh.runHandler(ctx, req.Tx, req.TxBytes, true) - if err != nil { - return tx.Response{}, err - } - - return txh.next.SimulateTx(sdk.WrapSDKContext(sdkCtx), 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/grpcrouter_test.go b/baseapp/grpcrouter_test.go index 11151f0e721..f660b5d1dbe 100644 --- a/baseapp/grpcrouter_test.go +++ b/baseapp/grpcrouter_test.go @@ -5,8 +5,6 @@ import ( "sync" "testing" - "github.com/cosmos/cosmos-sdk/testutil/testdata_pulsar" - "github.com/stretchr/testify/require" "github.com/tendermint/tendermint/libs/log" dbm "github.com/tendermint/tm-db" @@ -15,6 +13,7 @@ import ( "github.com/cosmos/cosmos-sdk/codec/types" "github.com/cosmos/cosmos-sdk/simapp" "github.com/cosmos/cosmos-sdk/testutil/testdata" + "github.com/cosmos/cosmos-sdk/testutil/testdata_pulsar" sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -56,8 +55,7 @@ func TestRegisterQueryServiceTwice(t *testing.T) { // Setup baseapp. db := dbm.NewMemDB() encCfg := simapp.MakeTestEncodingConfig() - logger, _ := log.NewDefaultLogger("plain", "info", false) - app := baseapp.NewBaseApp("test", logger, db) + app := baseapp.NewBaseApp("test", log.MustNewDefaultLogger("plain", "info", false), db, encCfg.TxConfig.TxDecoder()) app.SetInterfaceRegistry(encCfg.InterfaceRegistry) testdata.RegisterInterfaces(encCfg.InterfaceRegistry) diff --git a/x/auth/middleware/msg_service_router.go b/baseapp/msg_service_router.go similarity index 94% rename from x/auth/middleware/msg_service_router.go rename to baseapp/msg_service_router.go index 79aea3350c9..f6c8d4b21ce 100644 --- a/x/auth/middleware/msg_service_router.go +++ b/baseapp/msg_service_router.go @@ -1,4 +1,4 @@ -package middleware +package baseapp import ( "context" @@ -22,10 +22,9 @@ type MsgServiceRouter struct { var _ gogogrpc.Server = &MsgServiceRouter{} // NewMsgServiceRouter creates a new MsgServiceRouter. -func NewMsgServiceRouter(registry codectypes.InterfaceRegistry) *MsgServiceRouter { +func NewMsgServiceRouter() *MsgServiceRouter { return &MsgServiceRouter{ - interfaceRegistry: registry, - routes: map[string]MsgServiceHandler{}, + routes: map[string]MsgServiceHandler{}, } } @@ -130,6 +129,11 @@ func (msr *MsgServiceRouter) RegisterService(sd *grpc.ServiceDesc, handler inter } } +// SetInterfaceRegistry sets the interface registry for the router. +func (msr *MsgServiceRouter) SetInterfaceRegistry(interfaceRegistry codectypes.InterfaceRegistry) { + msr.interfaceRegistry = interfaceRegistry +} + func noopDecoder(_ interface{}) error { return nil } func noopInterceptor(_ context.Context, _ interface{}, _ *grpc.UnaryServerInfo, _ grpc.UnaryHandler) (interface{}, error) { return nil, nil diff --git a/baseapp/msg_service_router_test.go b/baseapp/msg_service_router_test.go new file mode 100644 index 00000000000..78683d35114 --- /dev/null +++ b/baseapp/msg_service_router_test.go @@ -0,0 +1,121 @@ +package baseapp_test + +import ( + "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" + authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing" +) + +func TestRegisterMsgService(t *testing.T) { + db := dbm.NewMemDB() + + // Create an encoding config that doesn't register testdata Msg services. + encCfg := simapp.MakeTestEncodingConfig() + app := baseapp.NewBaseApp("test", log.MustNewDefaultLogger("plain", "info", false), db, encCfg.TxConfig.TxDecoder()) + app.SetInterfaceRegistry(encCfg.InterfaceRegistry) + require.Panics(t, func() { + testdata.RegisterMsgServer( + app.MsgServiceRouter(), + testdata.MsgServerImpl{}, + ) + }) + + // Register testdata Msg services, and rerun `RegisterService`. + testdata.RegisterInterfaces(encCfg.InterfaceRegistry) + require.NotPanics(t, func() { + testdata.RegisterMsgServer( + app.MsgServiceRouter(), + testdata.MsgServerImpl{}, + ) + }) +} + +func TestRegisterMsgServiceTwice(t *testing.T) { + // Setup baseapp. + db := dbm.NewMemDB() + encCfg := simapp.MakeTestEncodingConfig() + app := baseapp.NewBaseApp("test", log.MustNewDefaultLogger("plain", "info", false), db, encCfg.TxConfig.TxDecoder()) + app.SetInterfaceRegistry(encCfg.InterfaceRegistry) + testdata.RegisterInterfaces(encCfg.InterfaceRegistry) + + // First time registering service shouldn't panic. + require.NotPanics(t, func() { + testdata.RegisterMsgServer( + app.MsgServiceRouter(), + testdata.MsgServerImpl{}, + ) + }) + + // Second time should panic. + require.Panics(t, func() { + testdata.RegisterMsgServer( + app.MsgServiceRouter(), + testdata.MsgServerImpl{}, + ) + }) +} + +func TestMsgService(t *testing.T) { + priv, _, _ := testdata.KeyTestPubAddr() + encCfg := simapp.MakeTestEncodingConfig() + testdata.RegisterInterfaces(encCfg.InterfaceRegistry) + db := dbm.NewMemDB() + app := baseapp.NewBaseApp("test", log.MustNewDefaultLogger("plain", "info", false), db, encCfg.TxConfig.TxDecoder()) + app.SetInterfaceRegistry(encCfg.InterfaceRegistry) + testdata.RegisterMsgServer( + app.MsgServiceRouter(), + 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/baseapp/options.go b/baseapp/options.go index 9ac0d0e33cc..139e04c8c53 100644 --- a/baseapp/options.go +++ b/baseapp/options.go @@ -12,7 +12,6 @@ import ( snapshottypes "github.com/cosmos/cosmos-sdk/snapshots/types" "github.com/cosmos/cosmos-sdk/store" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/types/tx" ) // File for storing in-package BaseApp optional functions, @@ -146,12 +145,12 @@ func (app *BaseApp) SetEndBlocker(endBlocker sdk.EndBlocker) { app.endBlocker = endBlocker } -func (app *BaseApp) SetTxHandler(txHandler tx.Handler) { +func (app *BaseApp) SetAnteHandler(ah sdk.AnteHandler) { if app.sealed { - panic("SetTxHandler() on sealed BaseApp") + panic("SetAnteHandler() on sealed BaseApp") } - app.txHandler = txHandler + app.anteHandler = ah } func (app *BaseApp) SetAddrPeerFilter(pf sdk.PeerFilter) { @@ -193,6 +192,14 @@ func (app *BaseApp) SetStoreLoader(loader StoreLoader) { app.storeLoader = loader } +// SetRouter allows us to customize the router. +func (app *BaseApp) SetRouter(router sdk.Router) { + if app.sealed { + panic("SetRouter() on sealed BaseApp") + } + app.router = router +} + // SetSnapshot sets the snapshot store and options. func (app *BaseApp) SetSnapshot(snapshotStore *snapshots.Store, opts snapshottypes.SnapshotOptions) { if app.sealed { @@ -210,6 +217,7 @@ func (app *BaseApp) SetSnapshot(snapshotStore *snapshots.Store, opts snapshottyp func (app *BaseApp) SetInterfaceRegistry(registry types.InterfaceRegistry) { app.interfaceRegistry = registry app.grpcQueryRouter.SetInterfaceRegistry(registry) + app.msgServiceRouter.SetInterfaceRegistry(registry) } // SetStreamingService is used to set a streaming service into the BaseApp hooks and load the listeners into the multistore diff --git a/baseapp/queryrouter_test.go b/baseapp/queryrouter_test.go index 4b38f645864..c7637f17000 100644 --- a/baseapp/queryrouter_test.go +++ b/baseapp/queryrouter_test.go @@ -1,4 +1,4 @@ -package baseapp_test +package baseapp import ( "testing" @@ -7,7 +7,6 @@ import ( abci "github.com/tendermint/tendermint/abci/types" - "github.com/cosmos/cosmos-sdk/baseapp" sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -16,7 +15,7 @@ var testQuerier = func(_ sdk.Context, _ []string, _ abci.RequestQuery) ([]byte, } func TestQueryRouter(t *testing.T) { - qr := baseapp.NewQueryRouter() + qr := NewQueryRouter() // require panic on invalid route require.Panics(t, func() { diff --git a/baseapp/recovery.go b/baseapp/recovery.go new file mode 100644 index 00000000000..7f0687800c6 --- /dev/null +++ b/baseapp/recovery.go @@ -0,0 +1,77 @@ +package baseapp + +import ( + "fmt" + "runtime/debug" + + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" +) + +// RecoveryHandler handles recovery() object. +// Return a non-nil error if recoveryObj was processed. +// Return nil if recoveryObj was not processed. +type RecoveryHandler func(recoveryObj interface{}) error + +// recoveryMiddleware is wrapper for RecoveryHandler to create chained recovery handling. +// returns (recoveryMiddleware, nil) if recoveryObj was not processed and should be passed to the next middleware in chain. +// returns (nil, error) if recoveryObj was processed and middleware chain processing should be stopped. +type recoveryMiddleware func(recoveryObj interface{}) (recoveryMiddleware, error) + +// processRecovery processes recoveryMiddleware chain for recovery() object. +// Chain processing stops on non-nil error or when chain is processed. +func processRecovery(recoveryObj interface{}, middleware recoveryMiddleware) error { + if middleware == nil { + return nil + } + + next, err := middleware(recoveryObj) + if err != nil { + return err + } + + return processRecovery(recoveryObj, next) +} + +// newRecoveryMiddleware creates a RecoveryHandler middleware. +func newRecoveryMiddleware(handler RecoveryHandler, next recoveryMiddleware) recoveryMiddleware { + return func(recoveryObj interface{}) (recoveryMiddleware, error) { + if err := handler(recoveryObj); err != nil { + return nil, err + } + + return next, nil + } +} + +// newOutOfGasRecoveryMiddleware creates a standard OutOfGas recovery middleware for app.runTx method. +func newOutOfGasRecoveryMiddleware(gasWanted uint64, ctx sdk.Context, next recoveryMiddleware) recoveryMiddleware { + handler := func(recoveryObj interface{}) error { + err, ok := recoveryObj.(sdk.ErrorOutOfGas) + if !ok { + return nil + } + + return sdkerrors.Wrap( + sdkerrors.ErrOutOfGas, fmt.Sprintf( + "out of gas in location: %v; gasWanted: %d, gasUsed: %d", + err.Descriptor, gasWanted, ctx.GasMeter().GasConsumed(), + ), + ) + } + + return newRecoveryMiddleware(handler, next) +} + +// newDefaultRecoveryMiddleware creates a default (last in chain) recovery middleware for app.runTx method. +func newDefaultRecoveryMiddleware() recoveryMiddleware { + handler := func(recoveryObj interface{}) error { + return sdkerrors.Wrap( + sdkerrors.ErrPanic, fmt.Sprintf( + "recovered: %v\nstack:\n%v", recoveryObj, string(debug.Stack()), + ), + ) + } + + return newRecoveryMiddleware(handler, nil) +} diff --git a/baseapp/recovery_test.go b/baseapp/recovery_test.go new file mode 100644 index 00000000000..b75892c6381 --- /dev/null +++ b/baseapp/recovery_test.go @@ -0,0 +1,64 @@ +package baseapp + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/require" +) + +// Test that recovery chain produces expected error at specific middleware layer +func TestRecoveryChain(t *testing.T) { + createError := func(id int) error { + return fmt.Errorf("error from id: %d", id) + } + + createHandler := func(id int, handle bool) RecoveryHandler { + return func(_ interface{}) error { + if handle { + return createError(id) + } + return nil + } + } + + // check recovery chain [1] -> 2 -> 3 + { + mw := newRecoveryMiddleware(createHandler(3, false), nil) + mw = newRecoveryMiddleware(createHandler(2, false), mw) + mw = newRecoveryMiddleware(createHandler(1, true), mw) + receivedErr := processRecovery(nil, mw) + + require.Equal(t, createError(1), receivedErr) + } + + // check recovery chain 1 -> [2] -> 3 + { + mw := newRecoveryMiddleware(createHandler(3, false), nil) + mw = newRecoveryMiddleware(createHandler(2, true), mw) + mw = newRecoveryMiddleware(createHandler(1, false), mw) + receivedErr := processRecovery(nil, mw) + + require.Equal(t, createError(2), receivedErr) + } + + // check recovery chain 1 -> 2 -> [3] + { + mw := newRecoveryMiddleware(createHandler(3, true), nil) + mw = newRecoveryMiddleware(createHandler(2, false), mw) + mw = newRecoveryMiddleware(createHandler(1, false), mw) + receivedErr := processRecovery(nil, mw) + + require.Equal(t, createError(3), receivedErr) + } + + // check recovery chain 1 -> 2 -> 3 + { + mw := newRecoveryMiddleware(createHandler(3, false), nil) + mw = newRecoveryMiddleware(createHandler(2, false), mw) + mw = newRecoveryMiddleware(createHandler(1, false), mw) + receivedErr := processRecovery(nil, mw) + + require.Nil(t, receivedErr) + } +} diff --git a/x/auth/middleware/legacy_router.go b/baseapp/router.go similarity index 71% rename from x/auth/middleware/legacy_router.go rename to baseapp/router.go index caf4424c982..7e2e70a0c6f 100644 --- a/x/auth/middleware/legacy_router.go +++ b/baseapp/router.go @@ -1,4 +1,4 @@ -package middleware +package baseapp import ( "fmt" @@ -6,22 +6,22 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) -type LegacyRouter struct { +type Router struct { routes map[string]sdk.Handler } -var _ sdk.Router = NewLegacyRouter() +var _ sdk.Router = NewRouter() // NewRouter returns a reference to a new router. -func NewLegacyRouter() *LegacyRouter { - return &LegacyRouter{ +func NewRouter() *Router { + return &Router{ routes: make(map[string]sdk.Handler), } } // AddRoute adds a route path to the router with a given handler. The route must // be alphanumeric. -func (rtr *LegacyRouter) AddRoute(route sdk.Route) sdk.Router { +func (rtr *Router) AddRoute(route sdk.Route) sdk.Router { if !sdk.IsAlphaNumeric(route.Path()) { panic("route expressions can only contain alphanumeric characters") } @@ -36,6 +36,6 @@ func (rtr *LegacyRouter) AddRoute(route sdk.Route) sdk.Router { // Route returns a handler for a given route path. // // TODO: Handle expressive matches. -func (rtr *LegacyRouter) Route(_ sdk.Context, path string) sdk.Handler { +func (rtr *Router) Route(_ sdk.Context, path string) sdk.Handler { return rtr.routes[path] } diff --git a/x/auth/middleware/legacy_router_test.go b/baseapp/router_test.go similarity index 79% rename from x/auth/middleware/legacy_router_test.go rename to baseapp/router_test.go index 97517dcdf9b..1e11dc0ca08 100644 --- a/x/auth/middleware/legacy_router_test.go +++ b/baseapp/router_test.go @@ -1,4 +1,4 @@ -package middleware_test +package baseapp import ( "testing" @@ -6,15 +6,14 @@ import ( "github.com/stretchr/testify/require" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/auth/middleware" ) var testHandler = func(_ sdk.Context, _ sdk.Msg) (*sdk.Result, error) { return &sdk.Result{}, nil } -func TestLegacyRouter(t *testing.T) { - rtr := middleware.NewLegacyRouter() +func TestRouter(t *testing.T) { + rtr := NewRouter() // require panic on invalid route require.Panics(t, func() { diff --git a/baseapp/test_helpers.go b/baseapp/test_helpers.go index 6b770499c70..eda2815da41 100644 --- a/baseapp/test_helpers.go +++ b/baseapp/test_helpers.go @@ -1,81 +1,39 @@ package baseapp import ( - abci "github.com/tendermint/tendermint/abci/types" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" - "github.com/cosmos/cosmos-sdk/types/tx" ) // SimCheck defines a CheckTx helper function that used in tests and simulations. -func (app *BaseApp) SimCheck(txEncoder sdk.TxEncoder, sdkTx sdk.Tx) (sdk.GasInfo, *sdk.Result, error) { - // CheckTx expects tx bytes as argument, so we encode the tx argument into - // bytes. Note that CheckTx will actually decode those bytes again. But since +func (app *BaseApp) SimCheck(txEncoder sdk.TxEncoder, tx sdk.Tx) (sdk.GasInfo, *sdk.Result, error) { + // runTx expects tx bytes as argument, so we encode the tx argument into + // bytes. Note that runTx will actually decode those bytes again. But since // this helper is only used in tests/simulation, it's fine. - bz, err := txEncoder(sdkTx) + bz, err := txEncoder(tx) if err != nil { return sdk.GasInfo{}, nil, sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "%s", err) } - - ctx := app.getContextForTx(runTxModeDeliver, bz) - res, _, err := app.txHandler.CheckTx(ctx, tx.Request{Tx: sdkTx, TxBytes: bz}, tx.RequestCheckTx{Type: abci.CheckTxType_New}) - gInfo := sdk.GasInfo{GasWanted: uint64(res.GasWanted), GasUsed: uint64(res.GasUsed)} - if err != nil { - return gInfo, nil, err - } - - data, err := makeABCIData(res) - if err != nil { - return gInfo, nil, err - } - - return gInfo, &sdk.Result{Data: data, Log: res.Log, Events: res.Events, MsgResponses: res.MsgResponses}, nil + gasInfo, result, _, err := app.runTx(runTxModeCheck, bz) + return gasInfo, result, err } // Simulate executes a tx in simulate mode to get result and gas info. func (app *BaseApp) Simulate(txBytes []byte) (sdk.GasInfo, *sdk.Result, error) { - ctx := app.getContextForTx(runTxModeSimulate, txBytes) - res, err := app.txHandler.SimulateTx(ctx, tx.Request{TxBytes: txBytes}) - gasInfo := sdk.GasInfo{ - GasWanted: res.GasWanted, - GasUsed: res.GasUsed, - } - if err != nil { - return gasInfo, nil, err - } - - data, err := makeABCIData(res) - if err != nil { - return gasInfo, nil, err - } - - return gasInfo, &sdk.Result{Data: data, Log: res.Log, Events: res.Events, MsgResponses: res.MsgResponses}, nil + gasInfo, result, _, err := app.runTx(runTxModeSimulate, txBytes) + return gasInfo, result, err } -// SimDeliver defines a DeliverTx helper function that used in tests and -// simulations. -func (app *BaseApp) SimDeliver(txEncoder sdk.TxEncoder, sdkTx sdk.Tx) (sdk.GasInfo, *sdk.Result, error) { +func (app *BaseApp) SimDeliver(txEncoder sdk.TxEncoder, tx sdk.Tx) (sdk.GasInfo, *sdk.Result, error) { // See comment for Check(). - bz, err := txEncoder(sdkTx) + bz, err := txEncoder(tx) if err != nil { return sdk.GasInfo{}, nil, sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "%s", err) } - - ctx := app.getContextForTx(runTxModeDeliver, bz) - res, err := app.txHandler.DeliverTx(ctx, tx.Request{Tx: sdkTx, TxBytes: bz}) - gInfo := sdk.GasInfo{GasWanted: uint64(res.GasWanted), GasUsed: uint64(res.GasUsed)} - if err != nil { - return gInfo, nil, err - } - - data, err := makeABCIData(res) - if err != nil { - return gInfo, nil, err - } - - return gInfo, &sdk.Result{Data: data, Log: res.Log, Events: res.Events, MsgResponses: res.MsgResponses}, nil + gasInfo, result, _, err := app.runTx(runTxModeDeliver, bz) + return gasInfo, result, err } // Context with current {check, deliver}State of the app used by tests. @@ -91,3 +49,7 @@ func (app *BaseApp) NewContext(isCheckTx bool, header tmproto.Header) sdk.Contex func (app *BaseApp) NewUncachedContext(isCheckTx bool, header tmproto.Header) sdk.Context { return sdk.NewContext(app.cms, header, isCheckTx, app.logger) } + +func (app *BaseApp) GetContextForDeliverTx(txBytes []byte) sdk.Context { + return app.getContextForTx(runTxModeDeliver, txBytes) +} diff --git a/docs/architecture/adr-045-check-delivertx-middlewares.md b/docs/architecture/adr-045-check-delivertx-middlewares.md index 3ead7e84e5b..60172977c98 100644 --- a/docs/architecture/adr-045-check-delivertx-middlewares.md +++ b/docs/architecture/adr-045-check-delivertx-middlewares.md @@ -4,10 +4,11 @@ * 20.08.2021: Initial draft. * 07.12.2021: Update `tx.Handler` interface ([\#10693](https://github.com/cosmos/cosmos-sdk/pull/10693)). +* 17.05.2022: ADR is abandoned, as middlewares are deemed too hard to reason about. ## Status -ACCEPTED +ABANDONED. Replacement is being discussed in [#11955](https://github.com/cosmos/cosmos-sdk/issues/11955). ## Abstract diff --git a/proto/cosmos/base/abci/v1beta1/abci.proto b/proto/cosmos/base/abci/v1beta1/abci.proto index a9de5258ddb..ddaa6356177 100644 --- a/proto/cosmos/base/abci/v1beta1/abci.proto +++ b/proto/cosmos/base/abci/v1beta1/abci.proto @@ -41,7 +41,7 @@ message TxResponse { string timestamp = 12; // Events defines all the events emitted by processing a transaction. Note, // these events include those emitted by processing all the messages and those - // emitted from the middleware. Whereas Logs contains the events, with + // emitted from the ante. Whereas Logs contains the events, with // additional metadata, emitted only by processing the messages. // // Since: cosmos-sdk 0.42.11, 0.44.5, 0.45 diff --git a/server/mock/app.go b/server/mock/app.go index 83e56222355..b059d62e0e5 100644 --- a/server/mock/app.go +++ b/server/mock/app.go @@ -6,36 +6,22 @@ import ( "fmt" "path/filepath" + "github.com/tendermint/tendermint/types" + abci "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/libs/log" - "github.com/tendermint/tendermint/types" - dbm "github.com/tendermint/tm-db" bam "github.com/cosmos/cosmos-sdk/baseapp" "github.com/cosmos/cosmos-sdk/codec" - codectypes "github.com/cosmos/cosmos-sdk/codec/types" - "github.com/cosmos/cosmos-sdk/simapp" storetypes "github.com/cosmos/cosmos-sdk/store/types" 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.NewTxDecoderMiddleware(options.TxDecoder), - 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 func NewApp(rootDir string, logger log.Logger) (abci.Application, error) { - db, err := dbm.NewDB("mock", dbm.MemDBBackend, filepath.Join(rootDir, "data")) + db, err := sdk.NewLevelDB("mock", filepath.Join(rootDir, "data")) if err != nil { return nil, err } @@ -44,7 +30,7 @@ func NewApp(rootDir string, logger log.Logger) (abci.Application, error) { capKeyMainStore := sdk.NewKVStoreKey("main") // Create BaseApp. - baseApp := bam.NewBaseApp("kvstore", logger, db) + baseApp := bam.NewBaseApp("kvstore", logger, db, decodeTx) // Set mounts for BaseApp's MultiStore. baseApp.MountStores(capKeyMainStore) @@ -52,19 +38,7 @@ func NewApp(rootDir string, logger log.Logger) (abci.Application, error) { baseApp.SetInitChainer(InitChainer(capKeyMainStore)) // Set a Route. - encCfg := simapp.MakeTestEncodingConfig() - legacyRouter := middleware.NewLegacyRouter() - // 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 := testTxHandler( - middleware.TxHandlerOptions{ - LegacyRouter: legacyRouter, - MsgServiceRouter: middleware.NewMsgServiceRouter(encCfg.InterfaceRegistry), - TxDecoder: decodeTx, - }, - ) - baseApp.SetTxHandler(txHandler) + baseApp.Router().AddRoute(sdk.NewRoute("kvstore", KVStoreHandler(capKeyMainStore))) // Load latest version. if err := baseApp.LoadLatestVersion(); err != nil { @@ -78,7 +52,7 @@ func NewApp(rootDir string, logger log.Logger) (abci.Application, error) { // them to the db func KVStoreHandler(storeKey storetypes.StoreKey) sdk.Handler { return func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) { - dTx, ok := msg.(*kvstoreTx) + dTx, ok := msg.(kvstoreTx) if !ok { return nil, errors.New("KVStoreHandler should only receive kvstoreTx") } @@ -90,14 +64,8 @@ func KVStoreHandler(storeKey storetypes.StoreKey) sdk.Handler { store := ctx.KVStore(storeKey) store.Set(key, value) - any, err := codectypes.NewAnyWithValue(msg) - if err != nil { - return nil, err - } - return &sdk.Result{ - Log: fmt.Sprintf("set %s=%s", key, value), - MsgResponses: []*codectypes.Any{any}, + Log: fmt.Sprintf("set %s=%s", key, value), }, nil } } diff --git a/server/mock/tx.go b/server/mock/tx.go index e6f5d2d764e..0cb79c28986 100644 --- a/server/mock/tx.go +++ b/server/mock/tx.go @@ -4,16 +4,12 @@ package mock import ( "bytes" "fmt" - "math" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" - "github.com/cosmos/cosmos-sdk/x/auth/middleware" ) -// kvstoreTx defines a tx for mock purposes. The `key` and `value` fields will -// set those bytes in the kvstore, and the `bytes` field represents its -// GetSignBytes value. +// An sdk.Tx which is its own sdk.Msg. type kvstoreTx struct { key []byte value []byte @@ -21,15 +17,12 @@ type kvstoreTx struct { } // dummy implementation of proto.Message -func (msg *kvstoreTx) Reset() {} -func (msg *kvstoreTx) String() string { return "TODO" } -func (msg *kvstoreTx) ProtoMessage() {} - -var ( - _ sdk.Tx = &kvstoreTx{} - _ sdk.Msg = &kvstoreTx{} - _ middleware.GasTx = &kvstoreTx{} -) +func (msg kvstoreTx) Reset() {} +func (msg kvstoreTx) String() string { return "TODO" } +func (msg kvstoreTx) ProtoMessage() {} + +var _ sdk.Tx = kvstoreTx{} +var _ sdk.Msg = kvstoreTx{} func NewTx(key, value string) kvstoreTx { bytes := fmt.Sprintf("%s=%s", key, value) @@ -48,7 +41,7 @@ func (tx kvstoreTx) Type() string { return "kvstore_tx" } -func (tx *kvstoreTx) GetMsgs() []sdk.Msg { +func (tx kvstoreTx) GetMsgs() []sdk.Msg { return []sdk.Msg{tx} } @@ -69,10 +62,6 @@ func (tx kvstoreTx) GetSigners() []sdk.AccAddress { return nil } -func (tx kvstoreTx) GetGas() uint64 { - return math.MaxUint64 -} - // takes raw transaction bytes and decodes them into an sdk.Tx. An sdk.Tx has // all the signatures and can be used to authenticate. func decodeTx(txBytes []byte) (sdk.Tx, error) { @@ -81,10 +70,10 @@ func decodeTx(txBytes []byte) (sdk.Tx, error) { split := bytes.Split(txBytes, []byte("=")) if len(split) == 1 { k := split[0] - tx = &kvstoreTx{k, k, txBytes} + tx = kvstoreTx{k, k, txBytes} } else if len(split) == 2 { k, v := split[0], split[1] - tx = &kvstoreTx{k, v, txBytes} + tx = kvstoreTx{k, v, txBytes} } else { return nil, sdkerrors.Wrap(sdkerrors.ErrTxDecode, "too many '='") } diff --git a/simapp/app.go b/simapp/app.go index 6baad02cec6..b660009d753 100644 --- a/simapp/app.go +++ b/simapp/app.go @@ -32,8 +32,8 @@ 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" - authmiddleware "github.com/cosmos/cosmos-sdk/x/auth/middleware" authsims "github.com/cosmos/cosmos-sdk/x/auth/simulation" authtx "github.com/cosmos/cosmos-sdk/x/auth/tx" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" @@ -155,8 +155,6 @@ type SimApp struct { legacyAmino *codec.LegacyAmino appCodec codec.Codec interfaceRegistry types.InterfaceRegistry - msgSvcRouter *authmiddleware.MsgServiceRouter - legacyRouter sdk.Router invCheckPeriod uint @@ -212,7 +210,7 @@ func NewSimApp( legacyAmino := encodingConfig.Amino interfaceRegistry := encodingConfig.InterfaceRegistry - bApp := baseapp.NewBaseApp(appName, logger, db, baseAppOptions...) + bApp := baseapp.NewBaseApp(appName, logger, db, encodingConfig.TxConfig.TxDecoder(), baseAppOptions...) bApp.SetCommitMultiStoreTracer(traceStore) bApp.SetVersion(version.Version) bApp.SetInterfaceRegistry(interfaceRegistry) @@ -240,8 +238,6 @@ func NewSimApp( legacyAmino: legacyAmino, appCodec: appCodec, interfaceRegistry: interfaceRegistry, - legacyRouter: authmiddleware.NewLegacyRouter(), - msgSvcRouter: authmiddleware.NewMsgServiceRouter(interfaceRegistry), invCheckPeriod: invCheckPeriod, keys: keys, tkeys: tkeys, @@ -291,14 +287,14 @@ func NewSimApp( stakingtypes.NewMultiStakingHooks(app.DistrKeeper.Hooks(), app.SlashingKeeper.Hooks()), ) - app.AuthzKeeper = authzkeeper.NewKeeper(keys[authzkeeper.StoreKey], appCodec, app.msgSvcRouter, app.AccountKeeper) + app.AuthzKeeper = authzkeeper.NewKeeper(keys[authzkeeper.StoreKey], appCodec, app.MsgServiceRouter(), app.AccountKeeper) groupConfig := group.DefaultConfig() /* Example of setting group params: groupConfig.MaxMetadataLen = 1000 */ - app.GroupKeeper = groupkeeper.NewKeeper(keys[group.StoreKey], appCodec, app.msgSvcRouter, app.AccountKeeper, groupConfig) + app.GroupKeeper = groupkeeper.NewKeeper(keys[group.StoreKey], appCodec, app.MsgServiceRouter(), app.AccountKeeper, groupConfig) // register the proposal types govRouter := govv1beta1.NewRouter() @@ -313,7 +309,7 @@ func NewSimApp( */ govKeeper := govkeeper.NewKeeper( appCodec, keys[govtypes.StoreKey], app.GetSubspace(govtypes.ModuleName), app.AccountKeeper, app.BankKeeper, - &stakingKeeper, govRouter, app.msgSvcRouter, govConfig, + &stakingKeeper, govRouter, app.MsgServiceRouter(), govConfig, ) app.GovKeeper = *govKeeper.SetHooks( @@ -407,8 +403,8 @@ func NewSimApp( // app.mm.SetOrderMigrations(custom order) app.mm.RegisterInvariants(&app.CrisisKeeper) - app.mm.RegisterRoutes(app.legacyRouter, app.QueryRouter(), encodingConfig.Amino) - app.configurator = module.NewConfigurator(app.appCodec, app.msgSvcRouter, app.GRPCQueryRouter()) + app.mm.RegisterRoutes(app.Router(), app.QueryRouter(), encodingConfig.Amino) + app.configurator = module.NewConfigurator(app.appCodec, app.MsgServiceRouter(), app.GRPCQueryRouter()) app.mm.RegisterServices(app.configurator) // add test gRPC service for testing gRPC queries in isolation @@ -446,7 +442,7 @@ func NewSimApp( app.SetInitChainer(app.InitChainer) app.SetBeginBlocker(app.BeginBlocker) app.SetEndBlocker(app.EndBlocker) - app.setTxHandler(encodingConfig.TxConfig, cast.ToStringSlice(appOpts.Get(server.FlagIndexEvents))) + app.setAnteHandler(encodingConfig.TxConfig, cast.ToStringSlice(appOpts.Get(server.FlagIndexEvents))) if loadLatest { if err := app.LoadLatestVersion(); err != nil { @@ -457,28 +453,26 @@ func NewSimApp( return app } -func (app *SimApp) setTxHandler(txConfig client.TxConfig, indexEventsStr []string) { +func (app *SimApp) setAnteHandler(txConfig client.TxConfig, indexEventsStr []string) { 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, - AccountKeeper: app.AccountKeeper, - BankKeeper: app.BankKeeper, - FeegrantKeeper: app.FeeGrantKeeper, - SignModeHandler: txConfig.SignModeHandler(), - SigGasConsumer: authmiddleware.DefaultSigVerificationGasConsumer, - TxDecoder: txConfig.TxDecoder(), - }) + 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) } - app.SetTxHandler(txHandler) + app.SetAnteHandler(anteHandler) } // Name returns the name of the App diff --git a/simapp/app_test.go b/simapp/app_test.go index 72acc777d9e..e5bd3365f6b 100644 --- a/simapp/app_test.go +++ b/simapp/app_test.go @@ -16,7 +16,6 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/module" "github.com/cosmos/cosmos-sdk/x/auth" - authmiddleware "github.com/cosmos/cosmos-sdk/x/auth/middleware" "github.com/cosmos/cosmos-sdk/x/auth/vesting" authzmodule "github.com/cosmos/cosmos-sdk/x/authz/module" "github.com/cosmos/cosmos-sdk/x/bank" @@ -79,12 +78,11 @@ func TestRunMigrations(t *testing.T) { app := NewSimApp(logger, db, nil, true, map[int64]bool{}, DefaultNodeHome, 0, encCfg, EmptyAppOptions{}) // Create a new baseapp and configurator for the purpose of this test. - bApp := baseapp.NewBaseApp(appName, logger, db) + bApp := baseapp.NewBaseApp(appName, logger, db, encCfg.TxConfig.TxDecoder()) bApp.SetCommitMultiStoreTracer(nil) bApp.SetInterfaceRegistry(encCfg.InterfaceRegistry) - msr := authmiddleware.NewMsgServiceRouter(encCfg.InterfaceRegistry) app.BaseApp = bApp - app.configurator = module.NewConfigurator(app.appCodec, msr, app.GRPCQueryRouter()) + app.configurator = module.NewConfigurator(app.appCodec, bApp.MsgServiceRouter(), app.GRPCQueryRouter()) // We register all modules on the Configurator, except x/bank. x/bank will // serve as the test subject on which we run the migration tests. diff --git a/store/streaming/constructor_test.go b/store/streaming/constructor_test.go index 73d512e88ba..79051d16500 100644 --- a/store/streaming/constructor_test.go +++ b/store/streaming/constructor_test.go @@ -52,7 +52,7 @@ func TestLoadStreamingServices(t *testing.T) { db := dbm.NewMemDB() encCdc := simapp.MakeTestEncodingConfig() keys := sdk.NewKVStoreKeys("mockKey1", "mockKey2") - bApp := baseapp.NewBaseApp("appName", log.NewNopLogger(), db) + bApp := baseapp.NewBaseApp("appName", log.NewNopLogger(), db, nil) testCases := map[string]struct { appOpts serverTypes.AppOptions diff --git a/types/errors/abci.go b/types/errors/abci.go index fef8dfa8ab6..729e3c55408 100644 --- a/types/errors/abci.go +++ b/types/errors/abci.go @@ -17,6 +17,20 @@ func ResponseCheckTx(err error, gw, gu uint64, debug bool) abci.ResponseCheckTx } } +// ResponseCheckTxWithEvents returns an ABCI ResponseCheckTx object with fields filled in +// from the given error, gas values and events. +func ResponseCheckTxWithEvents(err error, gw, gu uint64, events []abci.Event, debug bool) abci.ResponseCheckTx { + space, code, log := ABCIInfo(err, debug) + return abci.ResponseCheckTx{ + Codespace: space, + Code: code, + Log: log, + GasWanted: int64(gw), + GasUsed: int64(gu), + Events: events, + } +} + // ResponseDeliverTx returns an ABCI ResponseDeliverTx object with fields filled in // from the given error and gas values. func ResponseDeliverTx(err error, gw, gu uint64, debug bool) abci.ResponseDeliverTx { @@ -30,6 +44,20 @@ func ResponseDeliverTx(err error, gw, gu uint64, debug bool) abci.ResponseDelive } } +// ResponseDeliverTxWithEvents returns an ABCI ResponseDeliverTx object with fields filled in +// from the given error, gas values and events. +func ResponseDeliverTxWithEvents(err error, gw, gu uint64, events []abci.Event, debug bool) abci.ResponseDeliverTx { + space, code, log := ABCIInfo(err, debug) + return abci.ResponseDeliverTx{ + Codespace: space, + Code: code, + Log: log, + GasWanted: int64(gw), + GasUsed: int64(gu), + Events: events, + } +} + // QueryResult returns a ResponseQuery from an error. It will try to parse ABCI // info from the error. func QueryResult(err error, debug bool) abci.ResponseQuery { diff --git a/types/handler.go b/types/handler.go index 4a92277ff92..03d1f02d580 100644 --- a/types/handler.go +++ b/types/handler.go @@ -5,11 +5,9 @@ type Handler func(ctx Context, msg Msg) (*Result, error) // AnteHandler authenticates transactions, before their internal messages are handled. // If newCtx.IsZero(), ctx is used instead. -// DEPRECATED: use middleware instead type AnteHandler func(ctx Context, tx Tx, simulate bool) (newCtx Context, err error) // AnteDecorator wraps the next AnteHandler to perform custom pre- and post-processing. -// DEPRECATED: use middleware instead type AnteDecorator interface { AnteHandle(ctx Context, tx Tx, simulate bool, next AnteHandler) (newCtx Context, err error) } @@ -28,7 +26,6 @@ type AnteDecorator interface { // transactions to be processed with an infinite gasmeter and open a DOS attack vector. // Use `ante.SetUpContextDecorator` or a custom Decorator with similar functionality. // Returns nil when no AnteDecorator are supplied. -// DEPRECATED: use middleware instead func ChainAnteDecorators(chain ...AnteDecorator) AnteHandler { if len(chain) == 0 { return nil diff --git a/types/tx/middleware.go b/types/tx/middleware.go deleted file mode 100644 index abe29be672d..00000000000 --- a/types/tx/middleware.go +++ /dev/null @@ -1,71 +0,0 @@ -package tx - -import ( - context "context" - - codectypes "github.com/cosmos/cosmos-sdk/codec/types" - abci "github.com/tendermint/tendermint/abci/types" - - sdk "github.com/cosmos/cosmos-sdk/types" -) - -// RequestSimulateTx is the request type for the tx.Handler.RequestSimulateTx -// method. -type RequestSimulateTx struct { - TxBytes []byte -} - -// ResponseSimulateTx is the response type for the tx.Handler.RequestSimulateTx -// method. -type ResponseSimulateTx struct { - GasInfo sdk.GasInfo - Result *sdk.Result -} - -// Request is the tx request type used in middlewares. -// At least one of Tx or TxBytes must be set. If only TxBytes is set, then -// Tx will be populated by the TxDecoderMiddleware. If only Tx is set, then -// some middlewares (such as signature verification) will fail. -// -// In practice, the middleware stack is called from {Check,Deliver}Tx, which -// only passes the TxBytes. Then, the TxDecoderMiddleware decodes the bytes -// into the Tx field. -type Request struct { - Tx sdk.Tx - TxBytes []byte -} - -// Response is the tx response type used in middlewares. -type Response struct { - GasWanted uint64 - GasUsed uint64 - // MsgResponses is an array containing each Msg service handler's response - // type, packed in an Any. This will get proto-serialized into the `Data` field - // in the ABCI Check/DeliverTx responses. - MsgResponses []*codectypes.Any - Log string - Events []abci.Event -} - -// RequestCheckTx is the additional request type used in middlewares CheckTx -// method. -type RequestCheckTx struct { - Type abci.CheckTxType -} - -// RequestCheckTx is the additional response type used in middlewares CheckTx -// method. -type ResponseCheckTx struct { - Priority int64 -} - -// TxHandler defines the baseapp's CheckTx, DeliverTx and Simulate respective -// handlers. It is designed as a middleware stack. -type Handler interface { - CheckTx(ctx context.Context, req Request, checkReq RequestCheckTx) (Response, ResponseCheckTx, error) - DeliverTx(ctx context.Context, req Request) (Response, error) - SimulateTx(ctx context.Context, req Request) (Response, error) -} - -// TxMiddleware defines one layer of the TxHandler middleware stack. -type Middleware func(Handler) Handler diff --git a/x/auth/ante/ante.go b/x/auth/ante/ante.go new file mode 100644 index 00000000000..3b4aa6a56f1 --- /dev/null +++ b/x/auth/ante/ante.go @@ -0,0 +1,58 @@ +package ante + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + "github.com/cosmos/cosmos-sdk/types/tx/signing" + authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing" + "github.com/cosmos/cosmos-sdk/x/auth/types" +) + +// HandlerOptions are the options required for constructing a default SDK AnteHandler. +type HandlerOptions struct { + AccountKeeper AccountKeeper + BankKeeper types.BankKeeper + FeegrantKeeper FeegrantKeeper + SignModeHandler authsigning.SignModeHandler + SigGasConsumer func(meter sdk.GasMeter, sig signing.SignatureV2, params types.Params) error +} + +// NewAnteHandler returns an AnteHandler that checks and increments sequence +// numbers, checks signatures & account numbers, and deducts fees from the first +// signer. +func NewAnteHandler(options HandlerOptions) (sdk.AnteHandler, error) { + if options.AccountKeeper == nil { + return nil, sdkerrors.Wrap(sdkerrors.ErrLogic, "account keeper is required for ante builder") + } + + if options.BankKeeper == nil { + return nil, sdkerrors.Wrap(sdkerrors.ErrLogic, "bank keeper is required for ante builder") + } + + if options.SignModeHandler == nil { + return nil, sdkerrors.Wrap(sdkerrors.ErrLogic, "sign mode handler is required for ante builder") + } + + var sigGasConsumer = options.SigGasConsumer + if sigGasConsumer == nil { + sigGasConsumer = DefaultSigVerificationGasConsumer + } + + anteDecorators := []sdk.AnteDecorator{ + NewSetUpContextDecorator(), // outermost AnteDecorator. SetUpContext must be called first + NewRejectExtensionOptionsDecorator(), + NewMempoolFeeDecorator(), + NewValidateBasicDecorator(), + NewTxTimeoutHeightDecorator(), + NewValidateMemoDecorator(options.AccountKeeper), + NewConsumeGasForTxSizeDecorator(options.AccountKeeper), + NewDeductFeeDecorator(options.AccountKeeper, options.BankKeeper, options.FeegrantKeeper), + NewSetPubKeyDecorator(options.AccountKeeper), // SetPubKeyDecorator must be called before all signature verification decorators + NewValidateSigCountDecorator(options.AccountKeeper), + NewSigGasConsumeDecorator(options.AccountKeeper, sigGasConsumer), + NewSigVerificationDecorator(options.AccountKeeper, options.SignModeHandler), + NewIncrementSequenceDecorator(options.AccountKeeper), + } + + return sdk.ChainAnteDecorators(anteDecorators...), nil +} diff --git a/x/auth/middleware/middleware_test.go b/x/auth/ante/ante_test.go similarity index 67% rename from x/auth/middleware/middleware_test.go rename to x/auth/ante/ante_test.go index 764966a8743..a8684598464 100644 --- a/x/auth/middleware/middleware_test.go +++ b/x/auth/ante/ante_test.go @@ -1,4 +1,4 @@ -package middleware_test +package ante_test import ( "encoding/json" @@ -7,6 +7,11 @@ import ( "strings" "testing" + "github.com/cosmos/cosmos-sdk/x/bank/testutil" + minttypes "github.com/cosmos/cosmos-sdk/x/mint/types" + + "github.com/stretchr/testify/require" + "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" kmultisig "github.com/cosmos/cosmos-sdk/crypto/keys/multisig" "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" @@ -14,25 +19,17 @@ 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/types/tx/signing" - "github.com/cosmos/cosmos-sdk/x/auth/middleware" + "github.com/cosmos/cosmos-sdk/x/auth/ante" "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" ) -var testCoins = sdk.Coins{sdk.NewInt64Coin("atom", 10000000)} - // Test that simulate transaction accurately estimates gas cost -func (s *MWTestSuite) TestSimulateGasCost() { - ctx := s.SetupTest(false) // reset - txBuilder := s.clientCtx.TxConfig.NewTxBuilder() +func (suite *AnteTestSuite) TestSimulateGasCost() { + suite.SetupTest(false) // reset // Same data for every test cases - accounts := s.createTestAccounts(ctx, 3, testCoins) + accounts := suite.CreateTestAccounts(3) msgs := []sdk.Msg{ testdata.NewTestMsg(accounts[0].acc.GetAddress(), accounts[1].acc.GetAddress()), testdata.NewTestMsg(accounts[2].acc.GetAddress(), accounts[0].acc.GetAddress()), @@ -48,8 +45,8 @@ func (s *MWTestSuite) TestSimulateGasCost() { { "tx with 150atom fee", func() { - txBuilder.SetFeeAmount(feeAmount) - txBuilder.SetGasLimit(gasLimit) + suite.txBuilder.SetFeeAmount(feeAmount) + suite.txBuilder.SetGasLimit(gasLimit) }, true, true, @@ -58,11 +55,11 @@ func (s *MWTestSuite) TestSimulateGasCost() { { "with previously estimated gas", func() { - simulatedGas := ctx.GasMeter().GasConsumed() + simulatedGas := suite.ctx.GasMeter().GasConsumed() accSeqs = []uint64{1, 1, 1} - txBuilder.SetFeeAmount(feeAmount) - txBuilder.SetGasLimit(simulatedGas) + suite.txBuilder.SetFeeAmount(feeAmount) + suite.txBuilder.SetGasLimit(simulatedGas) }, false, true, @@ -71,18 +68,18 @@ func (s *MWTestSuite) TestSimulateGasCost() { } for _, tc := range testCases { - s.Run(fmt.Sprintf("Case %s", tc.desc), func() { + suite.Run(fmt.Sprintf("Case %s", tc.desc), func() { + suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() tc.malleate() - s.runTestCase(ctx, txBuilder, privs, msgs, feeAmount, gasLimit, accNums, accSeqs, ctx.ChainID(), tc) + suite.RunTestCase(privs, msgs, feeAmount, gasLimit, accNums, accSeqs, suite.ctx.ChainID(), tc) }) } } -// Test various error cases in the TxHandler control flow. -func (s *MWTestSuite) TestTxHandlerSigErrors() { - ctx := s.SetupTest(false) // reset - txBuilder := s.clientCtx.TxConfig.NewTxBuilder() +// Test various error cases in the AnteHandler control flow. +func (suite *AnteTestSuite) TestAnteHandlerSigErrors() { + suite.SetupTest(false) // reset // Same data for every test cases priv0, _, addr0 := testdata.KeyTestPubAddr() @@ -109,12 +106,12 @@ func (s *MWTestSuite) TestTxHandlerSigErrors() { privs, accNums, accSeqs = []cryptotypes.PrivKey{}, []uint64{}, []uint64{} // Create tx manually to test the tx's signers - s.Require().NoError(txBuilder.SetMsgs(msgs...)) - tx, _, err := s.createTestTx(txBuilder, privs, accNums, accSeqs, ctx.ChainID()) - s.Require().NoError(err) + suite.Require().NoError(suite.txBuilder.SetMsgs(msgs...)) + tx, err := suite.CreateTestTx(privs, accNums, accSeqs, suite.ctx.ChainID()) + suite.Require().NoError(err) // tx.GetSigners returns addresses in correct order: addr1, addr2, addr3 expectedSigners := []sdk.AccAddress{addr0, addr1, addr2} - s.Require().Equal(expectedSigners, tx.GetSigners()) + suite.Require().Equal(expectedSigners, tx.GetSigners()) }, false, false, @@ -141,12 +138,12 @@ func (s *MWTestSuite) TestTxHandlerSigErrors() { { "save the first account, but second is still unrecognized", func() { - 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) + acc1 := suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, addr0) + suite.app.AccountKeeper.SetAccount(suite.ctx, acc1) + err := suite.app.BankKeeper.MintCoins(suite.ctx, minttypes.ModuleName, feeAmount) + suite.Require().NoError(err) + err = suite.app.BankKeeper.SendCoinsFromModuleToAccount(suite.ctx, minttypes.ModuleName, addr0, feeAmount) + suite.Require().NoError(err) }, false, false, @@ -155,21 +152,21 @@ func (s *MWTestSuite) TestTxHandlerSigErrors() { } for _, tc := range testCases { - s.Run(fmt.Sprintf("Case %s", tc.desc), func() { + suite.Run(fmt.Sprintf("Case %s", tc.desc), func() { + suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() tc.malleate() - s.runTestCase(ctx, txBuilder, privs, msgs, feeAmount, gasLimit, accNums, accSeqs, ctx.ChainID(), tc) + suite.RunTestCase(privs, msgs, feeAmount, gasLimit, accNums, accSeqs, suite.ctx.ChainID(), tc) }) } } // Test logic around account number checking with one signer and many signers. -func (s *MWTestSuite) TestTxHandlerAccountNumbers() { - ctx := s.SetupTest(false) // reset - txBuilder := s.clientCtx.TxConfig.NewTxBuilder() +func (suite *AnteTestSuite) TestAnteHandlerAccountNumbers() { + suite.SetupTest(false) // reset // Same data for every test cases - accounts := s.createTestAccounts(ctx, 2, testCoins) + accounts := suite.CreateTestAccounts(2) feeAmount := testdata.NewTestFeeAmount() gasLimit := testdata.NewTestGasLimit() @@ -236,22 +233,22 @@ func (s *MWTestSuite) TestTxHandlerAccountNumbers() { } for _, tc := range testCases { - s.Run(fmt.Sprintf("Case %s", tc.desc), func() { + suite.Run(fmt.Sprintf("Case %s", tc.desc), func() { + suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() tc.malleate() - s.runTestCase(ctx, txBuilder, privs, msgs, feeAmount, gasLimit, accNums, accSeqs, ctx.ChainID(), tc) + suite.RunTestCase(privs, msgs, feeAmount, gasLimit, accNums, accSeqs, suite.ctx.ChainID(), tc) }) } } // Test logic around account number checking with many signers when BlockHeight is 0. -func (s *MWTestSuite) TestTxHandlerAccountNumbersAtBlockHeightZero() { - ctx := s.SetupTest(false) // setup - ctx = ctx.WithBlockHeight(0) - txBuilder := s.clientCtx.TxConfig.NewTxBuilder() +func (suite *AnteTestSuite) TestAnteHandlerAccountNumbersAtBlockHeightZero() { + suite.SetupTest(false) // setup + suite.ctx = suite.ctx.WithBlockHeight(0) // Same data for every test cases - accounts := s.createTestAccounts(ctx, 2, testCoins) + accounts := suite.CreateTestAccounts(2) feeAmount := testdata.NewTestFeeAmount() gasLimit := testdata.NewTestGasLimit() @@ -320,21 +317,21 @@ func (s *MWTestSuite) TestTxHandlerAccountNumbersAtBlockHeightZero() { } for _, tc := range testCases { - s.Run(fmt.Sprintf("Case %s", tc.desc), func() { + suite.Run(fmt.Sprintf("Case %s", tc.desc), func() { + suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() tc.malleate() - s.runTestCase(ctx, txBuilder, privs, msgs, feeAmount, gasLimit, accNums, accSeqs, ctx.ChainID(), tc) + suite.RunTestCase(privs, msgs, feeAmount, gasLimit, accNums, accSeqs, suite.ctx.ChainID(), tc) }) } } // Test logic around sequence checking with one signer and many signers. -func (s *MWTestSuite) TestTxHandlerSequences() { - ctx := s.SetupTest(false) // setup - txBuilder := s.clientCtx.TxConfig.NewTxBuilder() +func (suite *AnteTestSuite) TestAnteHandlerSequences() { + suite.SetupTest(false) // setup // Same data for every test cases - accounts := s.createTestAccounts(ctx, 3, testCoins) + accounts := suite.CreateTestAccounts(3) feeAmount := testdata.NewTestFeeAmount() gasLimit := testdata.NewTestGasLimit() @@ -432,24 +429,24 @@ func (s *MWTestSuite) TestTxHandlerSequences() { } for _, tc := range testCases { - s.Run(fmt.Sprintf("Case %s", tc.desc), func() { + suite.Run(fmt.Sprintf("Case %s", tc.desc), func() { + suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() tc.malleate() - s.runTestCase(ctx, txBuilder, privs, msgs, feeAmount, gasLimit, accNums, accSeqs, ctx.ChainID(), tc) + suite.RunTestCase(privs, msgs, feeAmount, gasLimit, accNums, accSeqs, suite.ctx.ChainID(), tc) }) } } // Test logic around fee deduction. -func (s *MWTestSuite) TestTxHandlerFees() { - ctx := s.SetupTest(false) // setup - txBuilder := s.clientCtx.TxConfig.NewTxBuilder() +func (suite *AnteTestSuite) TestAnteHandlerFees() { + suite.SetupTest(false) // setup // Same data for every test cases priv0, _, addr0 := testdata.KeyTestPubAddr() - acc1 := s.app.AccountKeeper.NewAccountWithAddress(ctx, addr0) - s.app.AccountKeeper.SetAccount(ctx, acc1) + acc1 := suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, addr0) + suite.app.AccountKeeper.SetAccount(suite.ctx, acc1) msgs := []sdk.Msg{testdata.NewTestMsg(addr0)} feeAmount := testdata.NewTestFeeAmount() gasLimit := testdata.NewTestGasLimit() @@ -474,8 +471,8 @@ func (s *MWTestSuite) TestTxHandlerFees() { { "signer does not have enough funds to pay the fee", func() { - err := testutil.FundAccount(s.app.BankKeeper, ctx, addr0, sdk.NewCoins(sdk.NewInt64Coin("atom", 149))) - s.Require().NoError(err) + err := testutil.FundAccount(suite.app.BankKeeper, suite.ctx, addr0, sdk.NewCoins(sdk.NewInt64Coin("atom", 149))) + suite.Require().NoError(err) }, false, false, @@ -486,13 +483,13 @@ func (s *MWTestSuite) TestTxHandlerFees() { func() { accNums = []uint64{acc1.GetAccountNumber()} - modAcc := s.app.AccountKeeper.GetModuleAccount(ctx, types.FeeCollectorName) + modAcc := suite.app.AccountKeeper.GetModuleAccount(suite.ctx, types.FeeCollectorName) - 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))) + suite.Require().True(suite.app.BankKeeper.GetAllBalances(suite.ctx, modAcc.GetAddress()).Empty()) + require.True(sdk.IntEq(suite.T(), suite.app.BankKeeper.GetAllBalances(suite.ctx, addr0).AmountOf("atom"), sdk.NewInt(149))) - err := testutil.FundAccount(s.app.BankKeeper, ctx, addr0, sdk.NewCoins(sdk.NewInt64Coin("atom", 1))) - s.Require().NoError(err) + err := testutil.FundAccount(suite.app.BankKeeper, suite.ctx, addr0, sdk.NewCoins(sdk.NewInt64Coin("atom", 1))) + suite.Require().NoError(err) }, false, true, @@ -501,10 +498,10 @@ func (s *MWTestSuite) TestTxHandlerFees() { { "signer doesn't have any more funds", func() { - modAcc := s.app.AccountKeeper.GetModuleAccount(ctx, types.FeeCollectorName) + modAcc := suite.app.AccountKeeper.GetModuleAccount(suite.ctx, types.FeeCollectorName) - 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))) + require.True(sdk.IntEq(suite.T(), suite.app.BankKeeper.GetAllBalances(suite.ctx, modAcc.GetAddress()).AmountOf("atom"), sdk.NewInt(150))) + require.True(sdk.IntEq(suite.T(), suite.app.BankKeeper.GetAllBalances(suite.ctx, addr0).AmountOf("atom"), sdk.NewInt(0))) }, false, false, @@ -513,21 +510,22 @@ func (s *MWTestSuite) TestTxHandlerFees() { } for _, tc := range testCases { - s.Run(fmt.Sprintf("Case %s", tc.desc), func() { + suite.Run(fmt.Sprintf("Case %s", tc.desc), func() { + + suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() tc.malleate() - s.runTestCase(ctx, txBuilder, privs, msgs, feeAmount, gasLimit, accNums, accSeqs, ctx.ChainID(), tc) + suite.RunTestCase(privs, msgs, feeAmount, gasLimit, accNums, accSeqs, suite.ctx.ChainID(), tc) }) } } // Test logic around memo gas consumption. -func (s *MWTestSuite) TestTxHandlerMemoGas() { - ctx := s.SetupTest(false) // setup - txBuilder := s.clientCtx.TxConfig.NewTxBuilder() +func (suite *AnteTestSuite) TestAnteHandlerMemoGas() { + suite.SetupTest(false) // setup // Same data for every test cases - accounts := s.createTestAccounts(ctx, 1, testCoins) + accounts := suite.CreateTestAccounts(1) msgs := []sdk.Msg{testdata.NewTestMsg(accounts[0].acc.GetAddress())} privs, accNums, accSeqs := []cryptotypes.PrivKey{accounts[0].priv}, []uint64{0}, []uint64{0} @@ -553,7 +551,7 @@ func (s *MWTestSuite) TestTxHandlerMemoGas() { func() { feeAmount = sdk.NewCoins(sdk.NewInt64Coin("atom", 0)) gasLimit = 801 - txBuilder.SetMemo("abcininasidniandsinasindiansdiansdinaisndiasndiadninsd") + suite.txBuilder.SetMemo("abcininasidniandsinasindiansdiansdinaisndiasndiadninsd") }, false, false, @@ -564,7 +562,7 @@ func (s *MWTestSuite) TestTxHandlerMemoGas() { func() { feeAmount = sdk.NewCoins(sdk.NewInt64Coin("atom", 0)) gasLimit = 50000 - txBuilder.SetMemo(strings.Repeat("01234567890", 500)) + suite.txBuilder.SetMemo(strings.Repeat("01234567890", 500)) }, false, false, @@ -575,7 +573,7 @@ func (s *MWTestSuite) TestTxHandlerMemoGas() { func() { feeAmount = sdk.NewCoins(sdk.NewInt64Coin("atom", 0)) gasLimit = 50000 - txBuilder.SetMemo(strings.Repeat("0123456789", 10)) + suite.txBuilder.SetMemo(strings.Repeat("0123456789", 10)) }, false, true, @@ -584,20 +582,20 @@ func (s *MWTestSuite) TestTxHandlerMemoGas() { } for _, tc := range testCases { - s.Run(fmt.Sprintf("Case %s", tc.desc), func() { + suite.Run(fmt.Sprintf("Case %s", tc.desc), func() { + suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() tc.malleate() - s.runTestCase(ctx, txBuilder, privs, msgs, feeAmount, gasLimit, accNums, accSeqs, ctx.ChainID(), tc) + suite.RunTestCase(privs, msgs, feeAmount, gasLimit, accNums, accSeqs, suite.ctx.ChainID(), tc) }) } } -func (s *MWTestSuite) TestTxHandlerMultiSigner() { - ctx := s.SetupTest(false) // setup - txBuilder := s.clientCtx.TxConfig.NewTxBuilder() +func (suite *AnteTestSuite) TestAnteHandlerMultiSigner() { + suite.SetupTest(false) // setup // Same data for every test cases - accounts := s.createTestAccounts(ctx, 3, testCoins) + accounts := suite.CreateTestAccounts(3) msg1 := testdata.NewTestMsg(accounts[0].acc.GetAddress(), accounts[1].acc.GetAddress()) msg2 := testdata.NewTestMsg(accounts[2].acc.GetAddress(), accounts[0].acc.GetAddress()) msg3 := testdata.NewTestMsg(accounts[1].acc.GetAddress(), accounts[2].acc.GetAddress()) @@ -618,7 +616,7 @@ func (s *MWTestSuite) TestTxHandlerMultiSigner() { 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} - txBuilder.SetMemo("Check signers are in expected order and different account numbers works") + suite.txBuilder.SetMemo("Check signers are in expected order and different account numbers works") }, false, true, @@ -657,20 +655,20 @@ func (s *MWTestSuite) TestTxHandlerMultiSigner() { } for _, tc := range testCases { - s.Run(fmt.Sprintf("Case %s", tc.desc), func() { + suite.Run(fmt.Sprintf("Case %s", tc.desc), func() { + suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() tc.malleate() - s.runTestCase(ctx, txBuilder, privs, msgs, feeAmount, gasLimit, accNums, accSeqs, ctx.ChainID(), tc) + suite.RunTestCase(privs, msgs, feeAmount, gasLimit, accNums, accSeqs, suite.ctx.ChainID(), tc) }) } } -func (s *MWTestSuite) TestTxHandlerBadSignBytes() { - ctx := s.SetupTest(true) // setup - txBuilder := s.clientCtx.TxConfig.NewTxBuilder() +func (suite *AnteTestSuite) TestAnteHandlerBadSignBytes() { + suite.SetupTest(false) // setup // Same data for every test cases - accounts := s.createTestAccounts(ctx, 2, testCoins) + accounts := suite.CreateTestAccounts(2) msg0 := testdata.NewTestMsg(accounts[0].acc.GetAddress()) // Variable data per test case @@ -688,7 +686,7 @@ func (s *MWTestSuite) TestTxHandlerBadSignBytes() { { "test good tx and signBytes", func() { - chainID = ctx.ChainID() + chainID = suite.ctx.ChainID() feeAmount = testdata.NewTestFeeAmount() gasLimit = testdata.NewTestGasLimit() msgs = []sdk.Msg{msg0} @@ -711,7 +709,7 @@ func (s *MWTestSuite) TestTxHandlerBadSignBytes() { { "test wrong accSeqs", func() { - chainID = ctx.ChainID() // Back to correct chainID + chainID = suite.ctx.ChainID() // Back to correct chainID accSeqs = []uint64{2} }, false, @@ -783,20 +781,20 @@ func (s *MWTestSuite) TestTxHandlerBadSignBytes() { } for _, tc := range testCases { - s.Run(fmt.Sprintf("Case %s", tc.desc), func() { + suite.Run(fmt.Sprintf("Case %s", tc.desc), func() { + suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() tc.malleate() - s.runTestCase(ctx, txBuilder, privs, msgs, feeAmount, gasLimit, accNums, accSeqs, chainID, tc) + suite.RunTestCase(privs, msgs, feeAmount, gasLimit, accNums, accSeqs, chainID, tc) }) } } -func (s *MWTestSuite) TestTxHandlerSetPubKey() { - ctx := s.SetupTest(true) // setup - txBuilder := s.clientCtx.TxConfig.NewTxBuilder() +func (suite *AnteTestSuite) TestAnteHandlerSetPubKey() { + suite.SetupTest(false) // setup // Same data for every test cases - accounts := s.createTestAccounts(ctx, 2, testCoins) + accounts := suite.CreateTestAccounts(2) feeAmount := testdata.NewTestFeeAmount() gasLimit := testdata.NewTestGasLimit() @@ -823,8 +821,8 @@ func (s *MWTestSuite) TestTxHandlerSetPubKey() { "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 := s.app.AccountKeeper.GetAccount(ctx, accounts[0].acc.GetAddress()) - s.Require().Equal(acc0.GetPubKey(), accounts[0].priv.PubKey()) + acc0 := suite.app.AccountKeeper.GetAccount(suite.ctx, accounts[0].acc.GetAddress()) + suite.Require().Equal(acc0.GetPubKey(), accounts[0].priv.PubKey()) }, false, false, @@ -844,30 +842,30 @@ func (s *MWTestSuite) TestTxHandlerSetPubKey() { "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 := s.app.AccountKeeper.GetAccount(ctx, accounts[1].acc.GetAddress()) - s.Require().Nil(acc1.GetPubKey()) + acc1 := suite.app.AccountKeeper.GetAccount(suite.ctx, accounts[1].acc.GetAddress()) + suite.Require().Nil(acc1.GetPubKey()) privs, accNums, accSeqs = []cryptotypes.PrivKey{accounts[1].priv}, []uint64{1}, []uint64{0} msgs = []sdk.Msg{testdata.NewTestMsg(accounts[1].acc.GetAddress())} - txBuilder.SetMsgs(msgs...) - txBuilder.SetFeeAmount(feeAmount) - txBuilder.SetGasLimit(gasLimit) + suite.txBuilder.SetMsgs(msgs...) + suite.txBuilder.SetFeeAmount(feeAmount) + suite.txBuilder.SetGasLimit(gasLimit) // Manually create tx, and remove signature. - testTx, _, err := s.createTestTx(txBuilder, privs, accNums, accSeqs, ctx.ChainID()) - s.Require().NoError(err) - txBuilder, err := s.clientCtx.TxConfig.WrapTxBuilder(testTx) - s.Require().NoError(err) - s.Require().NoError(txBuilder.SetSignatures()) + tx, err := suite.CreateTestTx(privs, accNums, accSeqs, suite.ctx.ChainID()) + suite.Require().NoError(err) + txBuilder, err := suite.clientCtx.TxConfig.WrapTxBuilder(tx) + suite.Require().NoError(err) + suite.Require().NoError(txBuilder.SetSignatures()) - // Run txHandler manually, expect ErrNoSignatures. - _, _, err = s.txHandler.CheckTx(sdk.WrapSDKContext(ctx), tx.Request{Tx: txBuilder.GetTx()}, tx.RequestCheckTx{}) - s.Require().Error(err) - s.Require().True(errors.Is(err, sdkerrors.ErrNoSignatures)) + // Run anteHandler manually, expect ErrNoSignatures. + _, err = suite.anteHandler(suite.ctx, txBuilder.GetTx(), false) + suite.Require().Error(err) + suite.Require().True(errors.Is(err, sdkerrors.ErrNoSignatures)) // Make sure public key has not been set. - acc1 = s.app.AccountKeeper.GetAccount(ctx, accounts[1].acc.GetAddress()) - s.Require().Nil(acc1.GetPubKey()) + acc1 = suite.app.AccountKeeper.GetAccount(suite.ctx, accounts[1].acc.GetAddress()) + suite.Require().Nil(acc1.GetPubKey()) // Set incorrect accSeq, to generate incorrect signature. privs, accNums, accSeqs = []cryptotypes.PrivKey{accounts[1].priv}, []uint64{1}, []uint64{1} @@ -879,10 +877,10 @@ func (s *MWTestSuite) TestTxHandlerSetPubKey() { { "make sure previous public key has been set after wrong signature", func() { - // 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()) + // Make sure public key has been set, as SetPubKeyDecorator + // is called before all signature verification decorators. + acc1 := suite.app.AccountKeeper.GetAccount(suite.ctx, accounts[1].acc.GetAddress()) + suite.Require().Equal(acc1.GetPubKey(), accounts[1].priv.PubKey()) }, false, false, @@ -891,10 +889,11 @@ func (s *MWTestSuite) TestTxHandlerSetPubKey() { } for _, tc := range testCases { - s.Run(fmt.Sprintf("Case %s", tc.desc), func() { + suite.Run(fmt.Sprintf("Case %s", tc.desc), func() { + suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() tc.malleate() - s.runTestCase(ctx, txBuilder, privs, msgs, feeAmount, gasLimit, accNums, accSeqs, ctx.ChainID(), tc) + suite.RunTestCase(privs, msgs, feeAmount, gasLimit, accNums, accSeqs, suite.ctx.ChainID(), tc) }) } } @@ -965,17 +964,16 @@ func TestCountSubkeys(t *testing.T) { } for _, tc := range testCases { t.Run(tc.name, func(T *testing.T) { - require.Equal(t, tc.want, middleware.CountSubKeys(tc.args.pub)) + require.Equal(t, tc.want, ante.CountSubKeys(tc.args.pub)) }) } } -func (s *MWTestSuite) TestTxHandlerSigLimitExceeded() { - ctx := s.SetupTest(false) // setup - txBuilder := s.clientCtx.TxConfig.NewTxBuilder() +func (suite *AnteTestSuite) TestAnteHandlerSigLimitExceeded() { + suite.SetupTest(false) // setup // Same data for every test cases - accounts := s.createTestAccounts(ctx, 8, testCoins) + accounts := suite.CreateTestAccounts(8) var addrs []sdk.AccAddress var privs []cryptotypes.PrivKey for i := 0; i < 8; i++ { @@ -998,25 +996,26 @@ func (s *MWTestSuite) TestTxHandlerSigLimitExceeded() { } for _, tc := range testCases { - s.Run(fmt.Sprintf("Case %s", tc.desc), func() { + suite.Run(fmt.Sprintf("Case %s", tc.desc), func() { + suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() tc.malleate() - s.runTestCase(ctx, txBuilder, privs, msgs, feeAmount, gasLimit, accNums, accSeqs, ctx.ChainID(), tc) + suite.RunTestCase(privs, msgs, feeAmount, gasLimit, accNums, accSeqs, suite.ctx.ChainID(), tc) }) } } // Test custom SignatureVerificationGasConsumer -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(), +func (suite *AnteTestSuite) TestCustomSignatureVerificationGasConsumer() { + suite.SetupTest(false) // setup + + // setup an ante handler that only accepts PubKeyEd25519 + anteHandler, err := ante.NewAnteHandler( + ante.HandlerOptions{ + AccountKeeper: suite.app.AccountKeeper, + BankKeeper: suite.app.BankKeeper, + FeegrantKeeper: suite.app.FeeGrantKeeper, + SignModeHandler: suite.clientCtx.TxConfig.SignModeHandler(), SigGasConsumer: func(meter sdk.GasMeter, sig signing.SignatureV2, params types.Params) error { switch pubkey := sig.PubKey.(type) { case *ed25519.PubKey: @@ -1026,22 +1025,21 @@ func (s *MWTestSuite) TestCustomSignatureVerificationGasConsumer() { return sdkerrors.Wrapf(sdkerrors.ErrInvalidPubKey, "unrecognized public key type: %T", pubkey) } }, - TxDecoder: s.clientCtx.TxConfig.TxDecoder(), }, ) - s.Require().NoError(err) - s.Require().NoError(err) + suite.Require().NoError(err) + suite.anteHandler = anteHandler // Same data for every test cases - accounts := s.createTestAccounts(ctx, 1, testCoins) - txBuilder.SetFeeAmount(testdata.NewTestFeeAmount()) - txBuilder.SetGasLimit(testdata.NewTestGasLimit()) - txBuilder.SetMsgs(testdata.NewTestMsg(accounts[0].acc.GetAddress())) + accounts := suite.CreateTestAccounts(1) + feeAmount := testdata.NewTestFeeAmount() + gasLimit := testdata.NewTestGasLimit() // Variable data per test case var ( accNums []uint64 + msgs []sdk.Msg privs []cryptotypes.PrivKey accSeqs []uint64 ) @@ -1050,6 +1048,7 @@ func (s *MWTestSuite) 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, @@ -1059,57 +1058,54 @@ func (s *MWTestSuite) TestCustomSignatureVerificationGasConsumer() { } for _, tc := range testCases { - s.Run(fmt.Sprintf("Case %s", tc.desc), func() { + suite.Run(fmt.Sprintf("Case %s", tc.desc), func() { + suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() tc.malleate() - testTx, txBytes, err := s.createTestTx(txBuilder, privs, accNums, accSeqs, ctx.ChainID()) - s.Require().NoError(err) - _, err = txHandler.DeliverTx(sdk.WrapSDKContext(ctx), tx.Request{Tx: testTx, TxBytes: txBytes}) - s.Require().Error(err) - s.Require().True(errors.Is(err, tc.expErr)) + suite.RunTestCase(privs, msgs, feeAmount, gasLimit, accNums, accSeqs, suite.ctx.ChainID(), tc) }) } } -func (s *MWTestSuite) TestTxHandlerReCheck() { - ctx := s.SetupTest(false) // setup +func (suite *AnteTestSuite) TestAnteHandlerReCheck() { + suite.SetupTest(false) // setup // Set recheck=true - ctx = ctx.WithIsReCheckTx(true) - txBuilder := s.clientCtx.TxConfig.NewTxBuilder() + suite.ctx = suite.ctx.WithIsReCheckTx(true) + suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() // Same data for every test cases - accounts := s.createTestAccounts(ctx, 1, testCoins) + accounts := suite.CreateTestAccounts(1) feeAmount := testdata.NewTestFeeAmount() gasLimit := testdata.NewTestGasLimit() - txBuilder.SetFeeAmount(feeAmount) - txBuilder.SetGasLimit(gasLimit) + suite.txBuilder.SetFeeAmount(feeAmount) + suite.txBuilder.SetGasLimit(gasLimit) msg := testdata.NewTestMsg(accounts[0].acc.GetAddress()) msgs := []sdk.Msg{msg} - s.Require().NoError(txBuilder.SetMsgs(msgs...)) + suite.Require().NoError(suite.txBuilder.SetMsgs(msgs...)) - txBuilder.SetMemo("thisisatestmemo") + suite.txBuilder.SetMemo("thisisatestmemo") // test that operations skipped on recheck do not run privs, accNums, accSeqs := []cryptotypes.PrivKey{accounts[0].priv}, []uint64{0}, []uint64{0} - testTx, _, err := s.createTestTx(txBuilder, privs, accNums, accSeqs, ctx.ChainID()) - s.Require().NoError(err) + tx, err := suite.CreateTestTx(privs, accNums, accSeqs, suite.ctx.ChainID()) + suite.Require().NoError(err) - // 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(testTx) - s.Require().NoError(err) - s.Require().NoError(txBuilder.SetSignatures()) + // make signature array empty which would normally cause ValidateBasicDecorator and SigVerificationDecorator fail + // since these decorators don't run on recheck, the tx should pass the antehandler + txBuilder, err := suite.clientCtx.TxConfig.WrapTxBuilder(tx) + suite.Require().NoError(err) + suite.Require().NoError(txBuilder.SetSignatures()) - _, _, err = s.txHandler.CheckTx(sdk.WrapSDKContext(ctx), tx.Request{Tx: txBuilder.GetTx()}, tx.RequestCheckTx{Type: abci.CheckTxType_Recheck}) - s.Require().Nil(err, "TxHandler errored on recheck unexpectedly: %v", err) + _, err = suite.anteHandler(suite.ctx, txBuilder.GetTx(), false) + suite.Require().Nil(err, "AnteHandler errored on recheck unexpectedly: %v", err) - testTx, _, err = s.createTestTx(txBuilder, privs, accNums, accSeqs, ctx.ChainID()) - s.Require().NoError(err) - txBytes, err := json.Marshal(testTx) - s.Require().Nil(err, "Error marshalling tx: %v", err) - ctx = ctx.WithTxBytes(txBytes) + tx, err = suite.CreateTestTx(privs, accNums, accSeqs, suite.ctx.ChainID()) + suite.Require().NoError(err) + txBytes, err := json.Marshal(tx) + suite.Require().Nil(err, "Error marshalling tx: %v", err) + suite.ctx = suite.ctx.WithTxBytes(txBytes) // require that state machine param-dependent checking is still run on recheck since parameters can change between check and recheck testCases := []struct { @@ -1120,37 +1116,35 @@ func (s *MWTestSuite) TestTxHandlerReCheck() { {"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 - s.app.AccountKeeper.SetParams(ctx, tc.params) + suite.app.AccountKeeper.SetParams(suite.ctx, tc.params) - _, _, err = s.txHandler.CheckTx(sdk.WrapSDKContext(ctx), tx.Request{Tx: testTx, TxBytes: txBytes}, tx.RequestCheckTx{Type: abci.CheckTxType_Recheck}) + _, err := suite.anteHandler(suite.ctx, tx, false) - s.Require().NotNil(err, "tx does not fail on recheck with updated params in test case: %s", tc.name) + suite.Require().NotNil(err, "tx does not fail on recheck with updated params in test case: %s", tc.name) // reset parameters to default values - s.app.AccountKeeper.SetParams(ctx, types.DefaultParams()) + suite.app.AccountKeeper.SetParams(suite.ctx, types.DefaultParams()) } // require that local mempool fee check is still run on recheck since validator may change minFee between check and recheck - // create new minimum gas price so txhandler fails on recheck - ctx = ctx.WithMinGasPrices([]sdk.DecCoin{{ + // create new minimum gas price so antehandler fails on recheck + suite.ctx = suite.ctx.WithMinGasPrices([]sdk.DecCoin{{ Denom: "dnecoin", // fee does not have this denom Amount: sdk.NewDec(5), }}) - _, _, err = s.txHandler.CheckTx(sdk.WrapSDKContext(ctx), tx.Request{Tx: testTx}, tx.RequestCheckTx{}) - - s.Require().NotNil(err, "txhandler on recheck did not fail when mingasPrice was changed") + _, err = suite.anteHandler(suite.ctx, tx, false) + suite.Require().NotNil(err, "antehandler on recheck did not fail when mingasPrice was changed") // reset min gasprice - ctx = ctx.WithMinGasPrices(sdk.DecCoins{}) + suite.ctx = suite.ctx.WithMinGasPrices(sdk.DecCoins{}) - // 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) + // remove funds for account so antehandler fails on recheck + suite.app.AccountKeeper.SetAccount(suite.ctx, accounts[0].acc) + balances := suite.app.BankKeeper.GetAllBalances(suite.ctx, accounts[0].acc.GetAddress()) + err = suite.app.BankKeeper.SendCoinsFromAccountToModule(suite.ctx, accounts[0].acc.GetAddress(), minttypes.ModuleName, balances) + suite.Require().NoError(err) - _, _, err = s.txHandler.CheckTx(sdk.WrapSDKContext(ctx), tx.Request{Tx: testTx}, tx.RequestCheckTx{}) - s.Require().NotNil(err, "txhandler on recheck did not fail once feePayer no longer has sufficient funds") + _, err = suite.anteHandler(suite.ctx, tx, false) + suite.Require().NotNil(err, "antehandler on recheck did not fail once feePayer no longer has sufficient funds") } diff --git a/x/auth/ante/basic.go b/x/auth/ante/basic.go new file mode 100644 index 00000000000..52c219f79e4 --- /dev/null +++ b/x/auth/ante/basic.go @@ -0,0 +1,206 @@ +package ante + +import ( + "github.com/cosmos/cosmos-sdk/codec/legacy" + "github.com/cosmos/cosmos-sdk/crypto/keys/multisig" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + "github.com/cosmos/cosmos-sdk/types/tx/signing" + "github.com/cosmos/cosmos-sdk/x/auth/migrations/legacytx" + authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing" +) + +// ValidateBasicDecorator will call tx.ValidateBasic and return any non-nil error. +// If ValidateBasic passes, decorator calls next AnteHandler in chain. Note, +// ValidateBasicDecorator decorator will not get executed on ReCheckTx since it +// is not dependent on application state. +type ValidateBasicDecorator struct{} + +func NewValidateBasicDecorator() ValidateBasicDecorator { + return ValidateBasicDecorator{} +} + +func (vbd ValidateBasicDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) { + // no need to validate basic on recheck tx, call next antehandler + if ctx.IsReCheckTx() { + return next(ctx, tx, simulate) + } + + if err := tx.ValidateBasic(); err != nil { + return ctx, err + } + + return next(ctx, tx, simulate) +} + +// ValidateMemoDecorator will validate memo given the parameters passed in +// If memo is too large decorator returns with error, otherwise call next AnteHandler +// CONTRACT: Tx must implement TxWithMemo interface +type ValidateMemoDecorator struct { + ak AccountKeeper +} + +func NewValidateMemoDecorator(ak AccountKeeper) ValidateMemoDecorator { + return ValidateMemoDecorator{ + ak: ak, + } +} + +func (vmd ValidateMemoDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) { + memoTx, ok := tx.(sdk.TxWithMemo) + if !ok { + return ctx, sdkerrors.Wrap(sdkerrors.ErrTxDecode, "invalid transaction type") + } + + params := vmd.ak.GetParams(ctx) + + memoLength := len(memoTx.GetMemo()) + if uint64(memoLength) > params.MaxMemoCharacters { + return ctx, sdkerrors.Wrapf(sdkerrors.ErrMemoTooLarge, + "maximum number of characters is %d but received %d characters", + params.MaxMemoCharacters, memoLength, + ) + } + + return next(ctx, tx, simulate) +} + +// ConsumeTxSizeGasDecorator will take in parameters and consume gas proportional +// to the size of tx before calling next AnteHandler. Note, the gas costs will be +// slightly over estimated due to the fact that any given signing account may need +// to be retrieved from state. +// +// CONTRACT: If simulate=true, then signatures must either be completely filled +// in or empty. +// CONTRACT: To use this decorator, signatures of transaction must be represented +// as legacytx.StdSignature otherwise simulate mode will incorrectly estimate gas cost. +type ConsumeTxSizeGasDecorator struct { + ak AccountKeeper +} + +func NewConsumeGasForTxSizeDecorator(ak AccountKeeper) ConsumeTxSizeGasDecorator { + return ConsumeTxSizeGasDecorator{ + ak: ak, + } +} + +func (cgts ConsumeTxSizeGasDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) { + sigTx, ok := tx.(authsigning.SigVerifiableTx) + if !ok { + return ctx, sdkerrors.Wrap(sdkerrors.ErrTxDecode, "invalid tx type") + } + params := cgts.ak.GetParams(ctx) + + ctx.GasMeter().ConsumeGas(params.TxSizeCostPerByte*sdk.Gas(len(ctx.TxBytes())), "txSize") + + // simulate gas cost for signatures in simulate mode + if simulate { + // in simulate mode, each element should be a nil signature + sigs, err := sigTx.GetSignaturesV2() + if err != nil { + return ctx, err + } + n := len(sigs) + + for i, signer := range sigTx.GetSigners() { + // if signature is already filled in, no need to simulate gas cost + if i < n && !isIncompleteSignature(sigs[i].Data) { + continue + } + + var pubkey cryptotypes.PubKey + + acc := cgts.ak.GetAccount(ctx, signer) + + // use placeholder simSecp256k1Pubkey if sig is nil + if acc == nil || acc.GetPubKey() == nil { + pubkey = simSecp256k1Pubkey + } else { + pubkey = acc.GetPubKey() + } + + // use stdsignature to mock the size of a full signature + simSig := legacytx.StdSignature{ //nolint:staticcheck // this will be removed when proto is ready + Signature: simSecp256k1Sig[:], + PubKey: pubkey, + } + + sigBz := legacy.Cdc.MustMarshal(simSig) + cost := sdk.Gas(len(sigBz) + 6) + + // If the pubkey is a multi-signature pubkey, then we estimate for the maximum + // number of signers. + if _, ok := pubkey.(*multisig.LegacyAminoPubKey); ok { + cost *= params.TxSigLimit + } + + ctx.GasMeter().ConsumeGas(params.TxSizeCostPerByte*cost, "txSize") + } + } + + return next(ctx, tx, simulate) +} + +// isIncompleteSignature tests whether SignatureData is fully filled in for simulation purposes +func isIncompleteSignature(data signing.SignatureData) bool { + if data == nil { + return true + } + + switch data := data.(type) { + case *signing.SingleSignatureData: + return len(data.Signature) == 0 + case *signing.MultiSignatureData: + if len(data.Signatures) == 0 { + return true + } + for _, s := range data.Signatures { + if isIncompleteSignature(s) { + return true + } + } + } + + return false +} + +type ( + // TxTimeoutHeightDecorator defines an AnteHandler decorator that checks for a + // tx height timeout. + TxTimeoutHeightDecorator struct{} + + // TxWithTimeoutHeight defines the interface a tx must implement in order for + // TxHeightTimeoutDecorator to process the tx. + TxWithTimeoutHeight interface { + sdk.Tx + + GetTimeoutHeight() uint64 + } +) + +// TxTimeoutHeightDecorator defines an AnteHandler decorator that checks for a +// tx height timeout. +func NewTxTimeoutHeightDecorator() TxTimeoutHeightDecorator { + return TxTimeoutHeightDecorator{} +} + +// AnteHandle implements an AnteHandler decorator for the TxHeightTimeoutDecorator +// type where the current block height is checked against the tx's height timeout. +// If a height timeout is provided (non-zero) and is less than the current block +// height, then an error is returned. +func (txh TxTimeoutHeightDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) { + timeoutTx, ok := tx.(TxWithTimeoutHeight) + if !ok { + return ctx, sdkerrors.Wrap(sdkerrors.ErrTxDecode, "expected tx to implement TxWithTimeoutHeight") + } + + timeoutHeight := timeoutTx.GetTimeoutHeight() + if timeoutHeight > 0 && uint64(ctx.BlockHeight()) > timeoutHeight { + return ctx, sdkerrors.Wrapf( + sdkerrors.ErrTxTimeoutHeight, "block height: %d, timeout height: %d", ctx.BlockHeight(), timeoutHeight, + ) + } + + return next(ctx, tx, simulate) +} diff --git a/x/auth/ante/basic_test.go b/x/auth/ante/basic_test.go new file mode 100644 index 00000000000..4a8cb830fdf --- /dev/null +++ b/x/auth/ante/basic_test.go @@ -0,0 +1,224 @@ +package ante_test + +import ( + "strings" + + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + "github.com/cosmos/cosmos-sdk/crypto/types/multisig" + "github.com/cosmos/cosmos-sdk/testutil/testdata" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/tx/signing" + "github.com/cosmos/cosmos-sdk/x/auth/ante" +) + +func (suite *AnteTestSuite) TestValidateBasic() { + suite.SetupTest(true) // setup + suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() + + // keys and addresses + priv1, _, addr1 := testdata.KeyTestPubAddr() + + // msg and signatures + msg := testdata.NewTestMsg(addr1) + feeAmount := testdata.NewTestFeeAmount() + gasLimit := testdata.NewTestGasLimit() + suite.Require().NoError(suite.txBuilder.SetMsgs(msg)) + suite.txBuilder.SetFeeAmount(feeAmount) + suite.txBuilder.SetGasLimit(gasLimit) + + privs, accNums, accSeqs := []cryptotypes.PrivKey{}, []uint64{}, []uint64{} + invalidTx, err := suite.CreateTestTx(privs, accNums, accSeqs, suite.ctx.ChainID()) + suite.Require().NoError(err) + + vbd := ante.NewValidateBasicDecorator() + antehandler := sdk.ChainAnteDecorators(vbd) + _, err = antehandler(suite.ctx, invalidTx, false) + + suite.Require().NotNil(err, "Did not error on invalid tx") + + privs, accNums, accSeqs = []cryptotypes.PrivKey{priv1}, []uint64{0}, []uint64{0} + validTx, err := suite.CreateTestTx(privs, accNums, accSeqs, suite.ctx.ChainID()) + suite.Require().NoError(err) + + _, err = antehandler(suite.ctx, validTx, false) + suite.Require().Nil(err, "ValidateBasicDecorator returned error on valid tx. err: %v", err) + + // test decorator skips on recheck + suite.ctx = suite.ctx.WithIsReCheckTx(true) + + // decorator should skip processing invalidTx on recheck and thus return nil-error + _, err = antehandler(suite.ctx, invalidTx, false) + + suite.Require().Nil(err, "ValidateBasicDecorator ran on ReCheck") +} + +func (suite *AnteTestSuite) TestValidateMemo() { + suite.SetupTest(true) // setup + suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() + + // keys and addresses + priv1, _, addr1 := testdata.KeyTestPubAddr() + + // msg and signatures + msg := testdata.NewTestMsg(addr1) + feeAmount := testdata.NewTestFeeAmount() + gasLimit := testdata.NewTestGasLimit() + suite.Require().NoError(suite.txBuilder.SetMsgs(msg)) + suite.txBuilder.SetFeeAmount(feeAmount) + suite.txBuilder.SetGasLimit(gasLimit) + + privs, accNums, accSeqs := []cryptotypes.PrivKey{priv1}, []uint64{0}, []uint64{0} + suite.txBuilder.SetMemo(strings.Repeat("01234567890", 500)) + invalidTx, err := suite.CreateTestTx(privs, accNums, accSeqs, suite.ctx.ChainID()) + suite.Require().NoError(err) + + // require that long memos get rejected + vmd := ante.NewValidateMemoDecorator(suite.app.AccountKeeper) + antehandler := sdk.ChainAnteDecorators(vmd) + _, err = antehandler(suite.ctx, invalidTx, false) + + suite.Require().NotNil(err, "Did not error on tx with high memo") + + suite.txBuilder.SetMemo(strings.Repeat("01234567890", 10)) + validTx, err := suite.CreateTestTx(privs, accNums, accSeqs, suite.ctx.ChainID()) + suite.Require().NoError(err) + + // require small memos pass ValidateMemo Decorator + _, err = antehandler(suite.ctx, validTx, false) + suite.Require().Nil(err, "ValidateBasicDecorator returned error on valid tx. err: %v", err) +} + +func (suite *AnteTestSuite) TestConsumeGasForTxSize() { + suite.SetupTest(true) // setup + + // keys and addresses + priv1, _, addr1 := testdata.KeyTestPubAddr() + + // msg and signatures + msg := testdata.NewTestMsg(addr1) + feeAmount := testdata.NewTestFeeAmount() + gasLimit := testdata.NewTestGasLimit() + + cgtsd := ante.NewConsumeGasForTxSizeDecorator(suite.app.AccountKeeper) + antehandler := sdk.ChainAnteDecorators(cgtsd) + + testCases := []struct { + name string + sigV2 signing.SignatureV2 + }{ + {"SingleSignatureData", signing.SignatureV2{PubKey: priv1.PubKey()}}, + {"MultiSignatureData", signing.SignatureV2{PubKey: priv1.PubKey(), Data: multisig.NewMultisig(2)}}, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() + suite.Require().NoError(suite.txBuilder.SetMsgs(msg)) + suite.txBuilder.SetFeeAmount(feeAmount) + suite.txBuilder.SetGasLimit(gasLimit) + suite.txBuilder.SetMemo(strings.Repeat("01234567890", 10)) + + privs, accNums, accSeqs := []cryptotypes.PrivKey{priv1}, []uint64{0}, []uint64{0} + tx, err := suite.CreateTestTx(privs, accNums, accSeqs, suite.ctx.ChainID()) + suite.Require().NoError(err) + + txBytes, err := suite.clientCtx.TxConfig.TxJSONEncoder()(tx) + suite.Require().Nil(err, "Cannot marshal tx: %v", err) + + params := suite.app.AccountKeeper.GetParams(suite.ctx) + expectedGas := sdk.Gas(len(txBytes)) * params.TxSizeCostPerByte + + // Set suite.ctx with TxBytes manually + suite.ctx = suite.ctx.WithTxBytes(txBytes) + + // track how much gas is necessary to retrieve parameters + beforeGas := suite.ctx.GasMeter().GasConsumed() + suite.app.AccountKeeper.GetParams(suite.ctx) + afterGas := suite.ctx.GasMeter().GasConsumed() + expectedGas += afterGas - beforeGas + + beforeGas = suite.ctx.GasMeter().GasConsumed() + suite.ctx, err = antehandler(suite.ctx, tx, false) + suite.Require().Nil(err, "ConsumeTxSizeGasDecorator returned error: %v", err) + + // require that decorator consumes expected amount of gas + consumedGas := suite.ctx.GasMeter().GasConsumed() - beforeGas + suite.Require().Equal(expectedGas, consumedGas, "Decorator did not consume the correct amount of gas") + + // simulation must not underestimate gas of this decorator even with nil signatures + txBuilder, err := suite.clientCtx.TxConfig.WrapTxBuilder(tx) + suite.Require().NoError(err) + suite.Require().NoError(txBuilder.SetSignatures(tc.sigV2)) + tx = txBuilder.GetTx() + + simTxBytes, err := suite.clientCtx.TxConfig.TxJSONEncoder()(tx) + suite.Require().Nil(err, "Cannot marshal tx: %v", err) + // require that simulated tx is smaller than tx with signatures + suite.Require().True(len(simTxBytes) < len(txBytes), "simulated tx still has signatures") + + // Set suite.ctx with smaller simulated TxBytes manually + suite.ctx = suite.ctx.WithTxBytes(simTxBytes) + + beforeSimGas := suite.ctx.GasMeter().GasConsumed() + + // run antehandler with simulate=true + suite.ctx, err = antehandler(suite.ctx, tx, true) + consumedSimGas := suite.ctx.GasMeter().GasConsumed() - beforeSimGas + + // require that antehandler passes and does not underestimate decorator cost + suite.Require().Nil(err, "ConsumeTxSizeGasDecorator returned error: %v", err) + suite.Require().True(consumedSimGas >= expectedGas, "Simulate mode underestimates gas on AnteDecorator. Simulated cost: %d, expected cost: %d", consumedSimGas, expectedGas) + + }) + } + +} + +func (suite *AnteTestSuite) TestTxHeightTimeoutDecorator() { + suite.SetupTest(true) + + antehandler := sdk.ChainAnteDecorators(ante.NewTxTimeoutHeightDecorator()) + + // keys and addresses + priv1, _, addr1 := testdata.KeyTestPubAddr() + + // msg and signatures + msg := testdata.NewTestMsg(addr1) + feeAmount := testdata.NewTestFeeAmount() + gasLimit := testdata.NewTestGasLimit() + + testCases := []struct { + name string + timeout uint64 + height int64 + expectErr bool + }{ + {"default value", 0, 10, false}, + {"no timeout (greater height)", 15, 10, false}, + {"no timeout (same height)", 10, 10, false}, + {"timeout (smaller height)", 9, 10, true}, + } + + for _, tc := range testCases { + tc := tc + + suite.Run(tc.name, func() { + suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() + + suite.Require().NoError(suite.txBuilder.SetMsgs(msg)) + + suite.txBuilder.SetFeeAmount(feeAmount) + suite.txBuilder.SetGasLimit(gasLimit) + suite.txBuilder.SetMemo(strings.Repeat("01234567890", 10)) + suite.txBuilder.SetTimeoutHeight(tc.timeout) + + privs, accNums, accSeqs := []cryptotypes.PrivKey{priv1}, []uint64{0}, []uint64{0} + tx, err := suite.CreateTestTx(privs, accNums, accSeqs, suite.ctx.ChainID()) + suite.Require().NoError(err) + + ctx := suite.ctx.WithBlockHeight(tc.height) + _, err = antehandler(ctx, tx, true) + suite.Require().Equal(tc.expectErr, err != nil, err) + }) + } +} diff --git a/x/auth/middleware/expected_keepers.go b/x/auth/ante/expected_keepers.go similarity index 85% rename from x/auth/middleware/expected_keepers.go rename to x/auth/ante/expected_keepers.go index 33bb6339c1f..4dbbbd21c71 100644 --- a/x/auth/middleware/expected_keepers.go +++ b/x/auth/ante/expected_keepers.go @@ -1,4 +1,4 @@ -package middleware +package ante 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 TxHandler's middlewares. +// Interface provides support to use non-sdk AccountKeeper for AnteHandler's decorators. type AccountKeeper interface { GetParams(ctx sdk.Context) (params types.Params) GetAccount(ctx sdk.Context, addr sdk.AccAddress) types.AccountI diff --git a/x/auth/ante/ext.go b/x/auth/ante/ext.go new file mode 100644 index 00000000000..362b8d32a97 --- /dev/null +++ b/x/auth/ante/ext.go @@ -0,0 +1,36 @@ +package ante + +import ( + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" +) + +type HasExtensionOptionsTx interface { + GetExtensionOptions() []*codectypes.Any + GetNonCriticalExtensionOptions() []*codectypes.Any +} + +// RejectExtensionOptionsDecorator is an AnteDecorator that rejects all extension +// options which can optionally be included in protobuf transactions. Users that +// need extension options should create a custom AnteHandler chain that handles +// needed extension options properly and rejects unknown ones. +type RejectExtensionOptionsDecorator struct{} + +// NewRejectExtensionOptionsDecorator creates a new RejectExtensionOptionsDecorator +func NewRejectExtensionOptionsDecorator() RejectExtensionOptionsDecorator { + return RejectExtensionOptionsDecorator{} +} + +var _ types.AnteDecorator = RejectExtensionOptionsDecorator{} + +// AnteHandle implements the AnteDecorator.AnteHandle method +func (r RejectExtensionOptionsDecorator) AnteHandle(ctx types.Context, tx types.Tx, simulate bool, next types.AnteHandler) (newCtx types.Context, err error) { + if hasExtOptsTx, ok := tx.(HasExtensionOptionsTx); ok { + if len(hasExtOptsTx.GetExtensionOptions()) != 0 { + return ctx, sdkerrors.ErrUnknownExtensionOptions + } + } + + return next(ctx, tx, simulate) +} diff --git a/x/auth/ante/ext_test.go b/x/auth/ante/ext_test.go new file mode 100644 index 00000000000..89ce6a7d649 --- /dev/null +++ b/x/auth/ante/ext_test.go @@ -0,0 +1,36 @@ +package ante_test + +import ( + "github.com/cosmos/cosmos-sdk/codec/types" + "github.com/cosmos/cosmos-sdk/testutil/testdata" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth/ante" + "github.com/cosmos/cosmos-sdk/x/auth/tx" +) + +func (suite *AnteTestSuite) TestRejectExtensionOptionsDecorator() { + suite.SetupTest(true) // setup + suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() + + reod := ante.NewRejectExtensionOptionsDecorator() + antehandler := sdk.ChainAnteDecorators(reod) + + // no extension options should not trigger an error + theTx := suite.txBuilder.GetTx() + _, err := antehandler(suite.ctx, theTx, false) + suite.Require().NoError(err) + + extOptsTxBldr, ok := suite.txBuilder.(tx.ExtensionOptionsTxBuilder) + if !ok { + // if we can't set extension options, this decorator doesn't apply and we're done + return + } + + // setting any extension option should cause an error + any, err := types.NewAnyWithValue(testdata.NewTestMsg()) + suite.Require().NoError(err) + extOptsTxBldr.SetExtensionOptions(any) + theTx = suite.txBuilder.GetTx() + _, err = antehandler(suite.ctx, theTx, false) + suite.Require().EqualError(err, "unknown extension options") +} diff --git a/x/auth/ante/fee.go b/x/auth/ante/fee.go new file mode 100644 index 00000000000..19e8258cfa7 --- /dev/null +++ b/x/auth/ante/fee.go @@ -0,0 +1,140 @@ +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 { + return ctx, fmt.Errorf("Fee collector module account (%s) 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 new file mode 100644 index 00000000000..06ccb4d3948 --- /dev/null +++ b/x/auth/ante/fee_test.go @@ -0,0 +1,104 @@ +package ante_test + +import ( + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + "github.com/cosmos/cosmos-sdk/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/middleware/feegrant_test.go b/x/auth/ante/feegrant_test.go similarity index 78% rename from x/auth/middleware/feegrant_test.go rename to x/auth/ante/feegrant_test.go index e3ca6e62fbd..c88cf781eb3 100644 --- a/x/auth/middleware/feegrant_test.go +++ b/x/auth/ante/feegrant_test.go @@ -1,4 +1,4 @@ -package middleware_test +package ante_test import ( "math/rand" @@ -10,13 +10,13 @@ import ( "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/codec" cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + "github.com/cosmos/cosmos-sdk/simapp/helpers" "github.com/cosmos/cosmos-sdk/testutil/testdata" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/simulation" - txtypes "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/cosmos/cosmos-sdk/x/auth/ante" authsign "github.com/cosmos/cosmos-sdk/x/auth/signing" "github.com/cosmos/cosmos-sdk/x/auth/tx" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" @@ -24,21 +24,19 @@ import ( "github.com/cosmos/cosmos-sdk/x/feegrant" ) -func (s *MWTestSuite) TestDeductFeesNoDelegation() { - ctx := s.SetupTest(false) // setup - app := s.app +func (suite *AnteTestSuite) TestDeductFeesNoDelegation() { + suite.SetupTest(false) + // setup + app, ctx := suite.app, suite.ctx protoTxCfg := tx.NewTxConfig(codec.NewProtoCodec(app.InterfaceRegistry()), tx.DefaultSignModes) - txHandler := middleware.ComposeMiddlewares( - noopTxHandler, - middleware.DeductFeeMiddleware( - s.app.AccountKeeper, - s.app.BankKeeper, - s.app.FeeGrantKeeper, - nil, - ), - ) + // this just tests our handler + dfd := ante.NewDeductFeeDecorator(app.AccountKeeper, app.BankKeeper, app.FeeGrantKeeper) + feeAnteHandler := sdk.ChainAnteDecorators(dfd) + + // this tests the whole stack + anteHandlerStack := suite.anteHandler // keys and addresses priv1, _, addr1 := testdata.KeyTestPubAddr() @@ -48,24 +46,24 @@ func (s *MWTestSuite) TestDeductFeesNoDelegation() { priv5, _, addr5 := testdata.KeyTestPubAddr() // Set addr1 with insufficient funds - err := testutil.FundAccount(s.app.BankKeeper, ctx, addr1, []sdk.Coin{sdk.NewCoin("atom", sdk.NewInt(10))}) - s.Require().NoError(err) + err := testutil.FundAccount(suite.app.BankKeeper, suite.ctx, addr1, []sdk.Coin{sdk.NewCoin("atom", sdk.NewInt(10))}) + suite.Require().NoError(err) // Set addr2 with more funds - err = testutil.FundAccount(s.app.BankKeeper, ctx, addr2, []sdk.Coin{sdk.NewCoin("atom", sdk.NewInt(99999))}) - s.Require().NoError(err) + err = testutil.FundAccount(suite.app.BankKeeper, suite.ctx, addr2, []sdk.Coin{sdk.NewCoin("atom", sdk.NewInt(99999))}) + suite.Require().NoError(err) // grant fee allowance from `addr2` to `addr3` (plenty to pay) err = app.FeeGrantKeeper.GrantAllowance(ctx, addr2, addr3, &feegrant.BasicAllowance{ SpendLimit: sdk.NewCoins(sdk.NewInt64Coin("atom", 500)), }) - s.Require().NoError(err) + suite.Require().NoError(err) // grant low fee allowance (20atom), to check the tx requesting more than allowed. err = app.FeeGrantKeeper.GrantAllowance(ctx, addr2, addr4, &feegrant.BasicAllowance{ SpendLimit: sdk.NewCoins(sdk.NewInt64Coin("atom", 20)), }) - s.Require().NoError(err) + suite.Require().NoError(err) cases := map[string]struct { signerKey cryptotypes.PrivKey @@ -136,7 +134,7 @@ func (s *MWTestSuite) TestDeductFeesNoDelegation() { for name, stc := range cases { tc := stc // to make scopelint happy - s.T().Run(name, func(t *testing.T) { + suite.T().Run(name, func(t *testing.T) { fee := sdk.NewCoins(sdk.NewInt64Coin("atom", tc.fee)) msgs := []sdk.Msg{testdata.NewTestMsg(tc.signer)} @@ -146,23 +144,20 @@ func (s *MWTestSuite) TestDeductFeesNoDelegation() { accNums, seqs = []uint64{acc.GetAccountNumber()}, []uint64{acc.GetSequence()} } - testTx, err := genTxWithFeeGranter(protoTxCfg, msgs, fee, helpers.DefaultGenTxGas, ctx.ChainID(), accNums, seqs, tc.feeAccount, privs...) - s.Require().NoError(err) - - // tests only feegrant middleware - _, err = txHandler.DeliverTx(sdk.WrapSDKContext(ctx), txtypes.Request{Tx: testTx}) + tx, err := genTxWithFeeGranter(protoTxCfg, msgs, fee, helpers.DefaultGenTxGas, ctx.ChainID(), accNums, seqs, tc.feeAccount, privs...) + suite.Require().NoError(err) + _, err = feeAnteHandler(ctx, tx, false) // tests only feegrant ante if tc.valid { - s.Require().NoError(err) + suite.Require().NoError(err) } else { - s.Require().Error(err) + suite.Require().Error(err) } - // tests while stack - _, err = s.txHandler.DeliverTx(sdk.WrapSDKContext(ctx), txtypes.Request{Tx: testTx}) + _, err = anteHandlerStack(ctx, tx, false) // tests while stack if tc.valid { - s.Require().NoError(err) + suite.Require().NoError(err) } else { - s.Require().Error(err) + suite.Require().Error(err) } }) } @@ -214,11 +209,9 @@ func genTxWithFeeGranter(gen client.TxConfig, msgs []sdk.Msg, feeAmt sdk.Coins, // 2nd round: once all signer infos are set, every signer can sign. for i, p := range priv { signerData := authsign.SignerData{ - Address: sdk.AccAddress(p.PubKey().Address()).String(), ChainID: chainID, AccountNumber: accNums[i], Sequence: accSeqs[i], - PubKey: p.PubKey(), } signBytes, err := gen.SignModeHandler().GetSignBytes(signMode, signerData, tx.GetTx()) if err != nil { diff --git a/x/auth/ante/setup.go b/x/auth/ante/setup.go new file mode 100644 index 00000000000..6d6d4344c0c --- /dev/null +++ b/x/auth/ante/setup.go @@ -0,0 +1,76 @@ +package ante + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + "github.com/cosmos/cosmos-sdk/x/auth/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( + "out of gas in location: %v; gasWanted: %d, gasUsed: %d", + rType.Descriptor, gasTx.GetGas(), newCtx.GasMeter().GasConsumed()) + + err = sdkerrors.Wrap(sdkerrors.ErrOutOfGas, log) + default: + panic(r) + } + } + }() + + return next(newCtx, tx, simulate) +} + +// SetGasMeter returns a new context with a gas meter set from a given context. +func SetGasMeter(simulate bool, ctx sdk.Context, gasLimit uint64) sdk.Context { + // In various cases such as simulation and during the genesis block, we do not + // meter any gas utilization. + if simulate || ctx.BlockHeight() == 0 { + return ctx.WithGasMeter(sdk.NewInfiniteGasMeter()) + } + + return ctx.WithGasMeter(sdk.NewGasMeter(gasLimit)) +} diff --git a/x/auth/ante/setup_test.go b/x/auth/ante/setup_test.go new file mode 100644 index 00000000000..ed4e543b56a --- /dev/null +++ b/x/auth/ante/setup_test.go @@ -0,0 +1,99 @@ +package ante_test + +import ( + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + "github.com/cosmos/cosmos-sdk/testutil/testdata" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + "github.com/cosmos/cosmos-sdk/x/auth/ante" +) + +func (suite *AnteTestSuite) TestSetup() { + suite.SetupTest(true) // setup + suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() + + // keys and addresses + priv1, _, addr1 := testdata.KeyTestPubAddr() + + // msg and signatures + msg := testdata.NewTestMsg(addr1) + feeAmount := testdata.NewTestFeeAmount() + gasLimit := testdata.NewTestGasLimit() + suite.Require().NoError(suite.txBuilder.SetMsgs(msg)) + suite.txBuilder.SetFeeAmount(feeAmount) + suite.txBuilder.SetGasLimit(gasLimit) + + privs, accNums, accSeqs := []cryptotypes.PrivKey{priv1}, []uint64{0}, []uint64{0} + tx, err := suite.CreateTestTx(privs, accNums, accSeqs, suite.ctx.ChainID()) + suite.Require().NoError(err) + + sud := ante.NewSetUpContextDecorator() + antehandler := sdk.ChainAnteDecorators(sud) + + // Set height to non-zero value for GasMeter to be set + suite.ctx = suite.ctx.WithBlockHeight(1).WithGasMeter(sdk.NewGasMeter(0)) + + // Context GasMeter Limit not set + suite.Require().Equal(uint64(0), suite.ctx.GasMeter().Limit(), "GasMeter set with limit before setup") + + newCtx, err := antehandler(suite.ctx, tx, false) + suite.Require().Nil(err, "SetUpContextDecorator returned error") + + // Context GasMeter Limit should be set after SetUpContextDecorator runs + suite.Require().Equal(gasLimit, newCtx.GasMeter().Limit(), "GasMeter not set correctly") +} + +func (suite *AnteTestSuite) TestRecoverPanic() { + suite.SetupTest(true) // setup + suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() + + // keys and addresses + priv1, _, addr1 := testdata.KeyTestPubAddr() + + // msg and signatures + msg := testdata.NewTestMsg(addr1) + feeAmount := testdata.NewTestFeeAmount() + gasLimit := testdata.NewTestGasLimit() + suite.Require().NoError(suite.txBuilder.SetMsgs(msg)) + suite.txBuilder.SetFeeAmount(feeAmount) + suite.txBuilder.SetGasLimit(gasLimit) + + privs, accNums, accSeqs := []cryptotypes.PrivKey{priv1}, []uint64{0}, []uint64{0} + tx, err := suite.CreateTestTx(privs, accNums, accSeqs, suite.ctx.ChainID()) + suite.Require().NoError(err) + + sud := ante.NewSetUpContextDecorator() + antehandler := sdk.ChainAnteDecorators(sud, OutOfGasDecorator{}) + + // Set height to non-zero value for GasMeter to be set + suite.ctx = suite.ctx.WithBlockHeight(1) + + newCtx, err := antehandler(suite.ctx, tx, false) + + suite.Require().NotNil(err, "Did not return error on OutOfGas panic") + + suite.Require().True(sdkerrors.ErrOutOfGas.Is(err), "Returned error is not an out of gas error") + suite.Require().Equal(gasLimit, newCtx.GasMeter().Limit()) + + antehandler = sdk.ChainAnteDecorators(sud, PanicDecorator{}) + suite.Require().Panics(func() { antehandler(suite.ctx, tx, false) }, "Recovered from non-Out-of-Gas panic") // nolint:errcheck +} + +type OutOfGasDecorator struct{} + +// AnteDecorator that will throw OutOfGas panic +func (ogd OutOfGasDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) { + overLimit := ctx.GasMeter().Limit() + 1 + + // Should panic with outofgas error + ctx.GasMeter().ConsumeGas(overLimit, "test panic") + + // not reached + return next(ctx, tx, simulate) +} + +type PanicDecorator struct{} + +func (pd PanicDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) { + panic("random error") +} diff --git a/x/auth/middleware/sigverify.go b/x/auth/ante/sigverify.go similarity index 51% rename from x/auth/middleware/sigverify.go rename to x/auth/ante/sigverify.go index 33b05742754..fa29f0268e1 100644 --- a/x/auth/middleware/sigverify.go +++ b/x/auth/ante/sigverify.go @@ -1,9 +1,9 @@ -package middleware +package ante import ( "bytes" - "context" "encoding/base64" + "encoding/hex" "fmt" "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" @@ -14,11 +14,10 @@ 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 ( @@ -26,42 +25,44 @@ 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 -var _ tx.Handler = setPubKeyTxHandler{} - -type setPubKeyTxHandler struct { - ak AccountKeeper - next tx.Handler +// 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 } -// 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 NewSetPubKeyDecorator(ak AccountKeeper) SetPubKeyDecorator { + return SetPubKeyDecorator{ + ak: ak, } } -func (spkm setPubKeyTxHandler) setPubKey(ctx context.Context, req tx.Request, simulate bool) error { - sdkCtx := sdk.UnwrapSDKContext(ctx) - sigTx, ok := req.Tx.(authsigning.SigVerifiableTx) +func (spkd SetPubKeyDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) { + sigTx, ok := tx.(authsigning.SigVerifiableTx) if !ok { - return sdkerrors.Wrap(sdkerrors.ErrTxDecode, "invalid tx type") + return ctx, sdkerrors.Wrap(sdkerrors.ErrTxDecode, "invalid tx type") } pubkeys, err := sigTx.GetPubKeys() if err != nil { - return err + return ctx, err } signers := sigTx.GetSigners() @@ -75,13 +76,13 @@ func (spkm setPubKeyTxHandler) setPubKey(ctx context.Context, req tx.Request, si } // Only make check if simulate=false if !simulate && !bytes.Equal(pk.Address(), signers[i]) { - return sdkerrors.Wrapf(sdkerrors.ErrInvalidPubKey, + return ctx, sdkerrors.Wrapf(sdkerrors.ErrInvalidPubKey, "pubKey does not match signer address %s with signer index: %d", signers[i], i) } - acc, err := GetSignerAcc(sdkCtx, spkm.ak, signers[i]) + acc, err := GetSignerAcc(ctx, spkd.ak, signers[i]) if err != nil { - return err + return ctx, err } // account already has pubkey set,no need to reset if acc.GetPubKey() != nil { @@ -89,9 +90,9 @@ func (spkm setPubKeyTxHandler) setPubKey(ctx context.Context, req tx.Request, si } err = acc.SetPubKey(pk) if err != nil { - return sdkerrors.Wrap(sdkerrors.ErrInvalidPubKey, err.Error()) + return ctx, sdkerrors.Wrap(sdkerrors.ErrInvalidPubKey, err.Error()) } - spkm.ak.SetAccount(sdkCtx, acc) + spkd.ak.SetAccount(ctx, acc) } // Also emit the following events, so that txs can be indexed by these @@ -100,7 +101,7 @@ func (spkm setPubKeyTxHandler) setPubKey(ctx context.Context, req tx.Request, si // - concat(address,"/",sequence) (via `tx.acc_seq='cosmos1abc...def/42'`). sigs, err := sigTx.GetSignaturesV2() if err != nil { - return err + return ctx, err } var events sdk.Events @@ -111,7 +112,7 @@ func (spkm setPubKeyTxHandler) setPubKey(ctx context.Context, req tx.Request, si sigBzs, err := signatureDataToBz(sig.Data) if err != nil { - return err + return ctx, err } for _, sigBz := range sigBzs { events = append(events, sdk.NewEvent(sdk.EventTypeTx, @@ -120,209 +121,37 @@ func (spkm setPubKeyTxHandler) setPubKey(ctx context.Context, req tx.Request, si } } - sdkCtx.EventManager().EmitEvents(events) - - return nil -} - -// CheckTx implements tx.Handler.CheckTx. -func (spkm setPubKeyTxHandler) CheckTx(ctx context.Context, req tx.Request, checkReq tx.RequestCheckTx) (tx.Response, tx.ResponseCheckTx, error) { - if err := spkm.setPubKey(ctx, req, false); err != nil { - return tx.Response{}, tx.ResponseCheckTx{}, err - } + ctx.EventManager().EmitEvents(events) - return spkm.next.CheckTx(ctx, req, checkReq) + return next(ctx, tx, simulate) } -// DeliverTx implements tx.Handler.DeliverTx. -func (spkm setPubKeyTxHandler) DeliverTx(ctx context.Context, req tx.Request) (tx.Response, error) { - if err := spkm.setPubKey(ctx, req, false); err != nil { - return tx.Response{}, err - } - return spkm.next.DeliverTx(ctx, req) -} - -// SimulateTx implements tx.Handler.SimulateTx. -func (spkm setPubKeyTxHandler) SimulateTx(ctx context.Context, req tx.Request) (tx.Response, error) { - if err := spkm.setPubKey(ctx, req, true); err != nil { - return tx.Response{}, err - } - return spkm.next.SimulateTx(ctx, 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 +// Consume parameter-defined amount of gas for each signature according to the passed-in SignatureVerificationGasConsumer function +// before calling the next AnteHandler +// CONTRACT: Pubkeys are set in context for all signers before this decorator runs // CONTRACT: Tx must implement SigVerifiableTx interface -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, req tx.Request) error { - sdkCtx := sdk.UnwrapSDKContext(ctx) - - sigTx, ok := req.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, req tx.Request, checkReq tx.RequestCheckTx) (tx.Response, tx.ResponseCheckTx, error) { - if err := vscd.checkSigCount(ctx, req); err != nil { - return tx.Response{}, tx.ResponseCheckTx{}, err - } - - return vscd.next.CheckTx(ctx, req, checkReq) -} - -// DeliverTx implements tx.Handler.DeliverTx. -func (vscd validateSigCountTxHandler) DeliverTx(ctx context.Context, req tx.Request) (tx.Response, error) { - if err := vscd.checkSigCount(ctx, req); err != nil { - return tx.Response{}, err - } - - return vscd.next.DeliverTx(ctx, req) -} - -// SimulateTx implements tx.Handler.SimulateTx. -func (vscd validateSigCountTxHandler) SimulateTx(ctx context.Context, req tx.Request) (tx.Response, error) { - if err := vscd.checkSigCount(ctx, req); err != nil { - return tx.Response{}, err - } - - return vscd.next.SimulateTx(ctx, 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 { +type SigGasConsumeDecorator struct { ak AccountKeeper sigGasConsumer SignatureVerificationGasConsumer - next tx.Handler } -// 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 { - if sigGasConsumer == nil { - sigGasConsumer = DefaultSigVerificationGasConsumer - } - - return func(h tx.Handler) tx.Handler { - return sigGasConsumeTxHandler{ - ak: ak, - sigGasConsumer: sigGasConsumer, - next: h, - } +func NewSigGasConsumeDecorator(ak AccountKeeper, sigGasConsumer SignatureVerificationGasConsumer) SigGasConsumeDecorator { + return SigGasConsumeDecorator{ + ak: ak, + sigGasConsumer: sigGasConsumer, } } -func (sgcm sigGasConsumeTxHandler) sigGasConsume(ctx context.Context, req tx.Request, simulate bool) error { - sdkCtx := sdk.UnwrapSDKContext(ctx) - - sigTx, ok := req.Tx.(authsigning.SigVerifiableTx) +func (sgcd SigGasConsumeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) { + sigTx, ok := tx.(authsigning.SigVerifiableTx) if !ok { - return sdkerrors.Wrap(sdkerrors.ErrTxDecode, "invalid transaction type") + return ctx, sdkerrors.Wrap(sdkerrors.ErrTxDecode, "invalid transaction type") } - params := sgcm.ak.GetParams(sdkCtx) + params := sgcd.ak.GetParams(ctx) sigs, err := sigTx.GetSignaturesV2() if err != nil { - return err + return ctx, err } // stdSigs contains the sequence number, account number, and signatures. @@ -330,9 +159,9 @@ func (sgcm sigGasConsumeTxHandler) sigGasConsume(ctx context.Context, req tx.Req signerAddrs := sigTx.GetSigners() for i, sig := range sigs { - signerAcc, err := GetSignerAcc(sdkCtx, sgcm.ak, signerAddrs[i]) + signerAcc, err := GetSignerAcc(ctx, sgcd.ak, signerAddrs[i]) if err != nil { - return err + return ctx, err } pubKey := signerAcc.GetPubKey() @@ -352,62 +181,29 @@ func (sgcm sigGasConsumeTxHandler) sigGasConsume(ctx context.Context, req tx.Req Sequence: sig.Sequence, } - err = sgcm.sigGasConsumer(sdkCtx.GasMeter(), sig, params) + err = sgcd.sigGasConsumer(ctx.GasMeter(), sig, params) if err != nil { - return err + return ctx, err } } - return nil -} - -// CheckTx implements tx.Handler.CheckTx. -func (sgcm sigGasConsumeTxHandler) CheckTx(ctx context.Context, req tx.Request, checkReq tx.RequestCheckTx) (tx.Response, tx.ResponseCheckTx, error) { - if err := sgcm.sigGasConsume(ctx, req, false); err != nil { - return tx.Response{}, tx.ResponseCheckTx{}, err - } - - return sgcm.next.CheckTx(ctx, req, checkReq) -} - -// DeliverTx implements tx.Handler.DeliverTx. -func (sgcm sigGasConsumeTxHandler) DeliverTx(ctx context.Context, req tx.Request) (tx.Response, error) { - if err := sgcm.sigGasConsume(ctx, req, false); err != nil { - return tx.Response{}, err - } - - return sgcm.next.DeliverTx(ctx, req) + return next(ctx, tx, simulate) } -// SimulateTx implements tx.Handler.SimulateTx. -func (sgcm sigGasConsumeTxHandler) SimulateTx(ctx context.Context, req tx.Request) (tx.Response, error) { - if err := sgcm.sigGasConsume(ctx, req, true); err != nil { - return tx.Response{}, err - } - - return sgcm.next.SimulateTx(ctx, req) -} - -var _ tx.Handler = sigVerificationTxHandler{} - -type sigVerificationTxHandler struct { +// Verify all signatures for a tx and return an error if any are invalid. Note, +// the SigVerificationDecorator decorator will not get executed on ReCheck. +// +// CONTRACT: Pubkeys are set in context for all signers before this decorator runs +// CONTRACT: Tx must implement SigVerifiableTx interface +type SigVerificationDecorator struct { ak AccountKeeper signModeHandler authsigning.SignModeHandler - next tx.Handler } -// 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, - } +func NewSigVerificationDecorator(ak AccountKeeper, signModeHandler authsigning.SignModeHandler) SigVerificationDecorator { + return SigVerificationDecorator{ + ak: ak, + signModeHandler: signModeHandler, } } @@ -415,7 +211,7 @@ func SigVerificationMiddleware(ak AccountKeeper, signModeHandler authsigning.Sig // 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 SigVerificationMiddleware's middleware function. +// in the SigVerificationDecorator's AnteHandler function. func OnlyLegacyAminoSigners(sigData signing.SignatureData) bool { switch v := sigData.(type) { case *signing.SingleSignatureData: @@ -432,61 +228,59 @@ func OnlyLegacyAminoSigners(sigData signing.SignatureData) bool { } } -func (svd sigVerificationTxHandler) sigVerify(ctx context.Context, req tx.Request, isReCheckTx, simulate bool) error { - sdkCtx := sdk.UnwrapSDKContext(ctx) +func (svd SigVerificationDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) { // no need to verify signatures on recheck tx - if isReCheckTx { - return nil + if ctx.IsReCheckTx() { + return next(ctx, tx, simulate) } - sigTx, ok := req.Tx.(authsigning.SigVerifiableTx) + sigTx, ok := tx.(authsigning.SigVerifiableTx) if !ok { - return sdkerrors.Wrap(sdkerrors.ErrTxDecode, "invalid transaction type") + return ctx, sdkerrors.Wrap(sdkerrors.ErrTxDecode, "invalid transaction type") } // stdSigs contains the sequence number, account number, and signatures. // When simulating, this would just be a 0-length slice. sigs, err := sigTx.GetSignaturesV2() if err != nil { - return err + return ctx, err } signerAddrs := sigTx.GetSigners() // check that signer length and signature length are the same if len(sigs) != len(signerAddrs) { - return sdkerrors.Wrapf(sdkerrors.ErrUnauthorized, "invalid number of signer; expected: %d, got %d", len(signerAddrs), len(sigs)) + return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnauthorized, "invalid number of signer; expected: %d, got %d", len(signerAddrs), len(sigs)) } for i, sig := range sigs { - acc, err := GetSignerAcc(sdkCtx, svd.ak, signerAddrs[i]) + acc, err := GetSignerAcc(ctx, svd.ak, signerAddrs[i]) if err != nil { - return err + return ctx, err } // retrieve pubkey pubKey := acc.GetPubKey() if !simulate && pubKey == nil { - return sdkerrors.Wrap(sdkerrors.ErrInvalidPubKey, "pubkey on account is not set") + return ctx, sdkerrors.Wrap(sdkerrors.ErrInvalidPubKey, "pubkey on account is not set") } // Check account sequence number. if sig.Sequence != acc.GetSequence() { - return sdkerrors.Wrapf( + return ctx, sdkerrors.Wrapf( sdkerrors.ErrWrongSequence, "account sequence mismatch, expected %d, got %d", acc.GetSequence(), sig.Sequence, ) } // retrieve signer data - genesis := sdkCtx.BlockHeight() == 0 - chainID := sdkCtx.ChainID() + genesis := ctx.BlockHeight() == 0 + chainID := ctx.ChainID() var accNum uint64 if !genesis { accNum = acc.GetAccountNumber() } - signerData := authsigning.SignerData{ - Address: signerAddrs[i].String(), + Address: acc.GetAddress().String(), ChainID: chainID, AccountNumber: accNum, Sequence: acc.GetSequence(), @@ -494,7 +288,7 @@ func (svd sigVerificationTxHandler) sigVerify(ctx context.Context, req tx.Reques } if !simulate { - err := authsigning.VerifySignature(pubKey, signerData, sig.Data, svd.signModeHandler, req.Tx) + err := authsigning.VerifySignature(pubKey, signerData, sig.Data, svd.signModeHandler, tx) if err != nil { var errMsg string if OnlyLegacyAminoSigners(sig.Data) { @@ -504,112 +298,153 @@ func (svd sigVerificationTxHandler) sigVerify(ctx context.Context, req tx.Reques } else { errMsg = fmt.Sprintf("signature verification failed; please verify account number (%d) and chain-id (%s)", accNum, chainID) } - return sdkerrors.Wrap(sdkerrors.ErrUnauthorized, errMsg) + return ctx, sdkerrors.Wrap(sdkerrors.ErrUnauthorized, errMsg) } } } - return nil -} - -// CheckTx implements tx.Handler.CheckTx. -func (svd sigVerificationTxHandler) CheckTx(ctx context.Context, req tx.Request, checkReq tx.RequestCheckTx) (tx.Response, tx.ResponseCheckTx, error) { - if err := svd.sigVerify(ctx, req, checkReq.Type == abci.CheckTxType_Recheck, false); err != nil { - return tx.Response{}, tx.ResponseCheckTx{}, err - } - - return svd.next.CheckTx(ctx, req, checkReq) + return next(ctx, tx, simulate) } -// DeliverTx implements tx.Handler.DeliverTx. -func (svd sigVerificationTxHandler) DeliverTx(ctx context.Context, req tx.Request) (tx.Response, error) { - if err := svd.sigVerify(ctx, req, false, false); err != nil { - return tx.Response{}, err - } - - return svd.next.DeliverTx(ctx, req) -} - -// SimulateTx implements tx.Handler.SimulateTx. -func (svd sigVerificationTxHandler) SimulateTx(ctx context.Context, req tx.Request) (tx.Response, error) { - if err := svd.sigVerify(ctx, req, false, true); err != nil { - return tx.Response{}, err - } - - return svd.next.SimulateTx(ctx, req) -} - -var _ tx.Handler = incrementSequenceTxHandler{} - -type incrementSequenceTxHandler struct { - ak AccountKeeper - next tx.Handler -} - -// 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 +// IncrementSequenceDecorator handles incrementing sequences of all signers. +// Use the IncrementSequenceDecorator decorator to prevent replay attacks. Note, +// there is no need to execute IncrementSequenceDecorator on RecheckTX since // CheckTx would already bump the sequence number. // // NOTE: Since CheckTx and DeliverTx state are managed separately, subsequent and // sequential txs orginating from the same account cannot be handled correctly in // a reliable way unless sequence numbers are managed and tracked manually by a // client. It is recommended to instead use multiple messages in a tx. -func IncrementSequenceMiddleware(ak AccountKeeper) tx.Middleware { - return func(h tx.Handler) tx.Handler { - return incrementSequenceTxHandler{ - ak: ak, - next: h, - } +type IncrementSequenceDecorator struct { + ak AccountKeeper +} + +func NewIncrementSequenceDecorator(ak AccountKeeper) IncrementSequenceDecorator { + return IncrementSequenceDecorator{ + ak: ak, } } -func (isd incrementSequenceTxHandler) incrementSeq(ctx context.Context, req tx.Request) error { - sdkCtx := sdk.UnwrapSDKContext(ctx) - sigTx, ok := req.Tx.(authsigning.SigVerifiableTx) +func (isd IncrementSequenceDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) { + sigTx, ok := tx.(authsigning.SigVerifiableTx) if !ok { - return sdkerrors.Wrap(sdkerrors.ErrTxDecode, "invalid transaction type") + return ctx, sdkerrors.Wrap(sdkerrors.ErrTxDecode, "invalid transaction type") } // increment sequence of all signers for _, addr := range sigTx.GetSigners() { - acc := isd.ak.GetAccount(sdkCtx, addr) + acc := isd.ak.GetAccount(ctx, addr) if err := acc.SetSequence(acc.GetSequence() + 1); err != nil { panic(err) } - isd.ak.SetAccount(sdkCtx, acc) + isd.ak.SetAccount(ctx, acc) } - return nil + return next(ctx, tx, simulate) } -// CheckTx implements tx.Handler.CheckTx. -func (isd incrementSequenceTxHandler) CheckTx(ctx context.Context, req tx.Request, checkReq tx.RequestCheckTx) (tx.Response, tx.ResponseCheckTx, error) { - if err := isd.incrementSeq(ctx, req); err != nil { - return tx.Response{}, tx.ResponseCheckTx{}, err - } +// 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 +} - return isd.next.CheckTx(ctx, req, checkReq) +func NewValidateSigCountDecorator(ak AccountKeeper) ValidateSigCountDecorator { + return ValidateSigCountDecorator{ + ak: ak, + } } -// DeliverTx implements tx.Handler.DeliverTx. -func (isd incrementSequenceTxHandler) DeliverTx(ctx context.Context, req tx.Request) (tx.Response, error) { - if err := isd.incrementSeq(ctx, req); err != nil { - return tx.Response{}, err +func (vscd ValidateSigCountDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) { + sigTx, ok := tx.(authsigning.SigVerifiableTx) + if !ok { + return ctx, sdkerrors.Wrap(sdkerrors.ErrTxDecode, "Tx must be a sigTx") + } + + params := vscd.ak.GetParams(ctx) + pubKeys, err := sigTx.GetPubKeys() + if err != nil { + return ctx, err } - return isd.next.DeliverTx(ctx, req) + 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) } -// SimulateTx implements tx.Handler.SimulateTx. -func (isd incrementSequenceTxHandler) SimulateTx(ctx context.Context, req tx.Request) (tx.Response, error) { - if err := isd.incrementSeq(ctx, req); err != nil { - return tx.Response{}, err +// 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 isd.next.SimulateTx(ctx, req) + return nil } // GetSignerAcc returns an account for a given address that is expected to sign diff --git a/x/auth/middleware/sigverify_benchmark_test.go b/x/auth/ante/sigverify_benchmark_test.go similarity index 89% rename from x/auth/middleware/sigverify_benchmark_test.go rename to x/auth/ante/sigverify_benchmark_test.go index dc635985170..56e596fa6b5 100644 --- a/x/auth/middleware/sigverify_benchmark_test.go +++ b/x/auth/ante/sigverify_benchmark_test.go @@ -1,4 +1,4 @@ -package middleware_test +package ante_test import ( "testing" @@ -10,7 +10,7 @@ import ( "github.com/cosmos/cosmos-sdk/crypto/keys/secp256r1" ) -// This benchmark is used to asses the middleware.Secp256k1ToR1GasFactor value +// This benchmark is used to asses the ante.Secp256k1ToR1GasFactor value func BenchmarkSig(b *testing.B) { require := require.New(b) msg := tmcrypto.CRandBytes(1000) diff --git a/x/auth/middleware/sigverify_test.go b/x/auth/ante/sigverify_test.go similarity index 52% rename from x/auth/middleware/sigverify_test.go rename to x/auth/ante/sigverify_test.go index fd4fc562204..074f4c33afc 100644 --- a/x/auth/middleware/sigverify_test.go +++ b/x/auth/ante/sigverify_test.go @@ -1,10 +1,10 @@ -package middleware_test +package ante_test import ( "fmt" "github.com/cosmos/cosmos-sdk/client" - "github.com/cosmos/cosmos-sdk/codec/legacy" + "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" kmultisig "github.com/cosmos/cosmos-sdk/crypto/keys/multisig" "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" @@ -14,22 +14,16 @@ 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/middleware" + "github.com/cosmos/cosmos-sdk/x/auth/ante" "github.com/cosmos/cosmos-sdk/x/auth/migrations/legacytx" "github.com/cosmos/cosmos-sdk/x/auth/types" - abci "github.com/tendermint/tendermint/abci/types" ) -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), - ) +func (suite *AnteTestSuite) TestSetPubKey() { + suite.SetupTest(true) // setup + require := suite.Require() + suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() // keys and addresses priv1, pub1, addr1 := testdata.KeyTestPubAddr() @@ -42,37 +36,35 @@ func (s *MWTestSuite) TestSetPubKey() { msgs := make([]sdk.Msg, len(addrs)) // set accounts and create msg for each address for i, addr := range addrs { - acc := s.app.AccountKeeper.NewAccountWithAddress(ctx, addr) + acc := suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, addr) require.NoError(acc.SetAccountNumber(uint64(i))) - s.app.AccountKeeper.SetAccount(ctx, acc) + suite.app.AccountKeeper.SetAccount(suite.ctx, acc) msgs[i] = testdata.NewTestMsg(addr) } - require.NoError(txBuilder.SetMsgs(msgs...)) - txBuilder.SetFeeAmount(testdata.NewTestFeeAmount()) - txBuilder.SetGasLimit(testdata.NewTestGasLimit()) + require.NoError(suite.txBuilder.SetMsgs(msgs...)) + suite.txBuilder.SetFeeAmount(testdata.NewTestFeeAmount()) + suite.txBuilder.SetGasLimit(testdata.NewTestGasLimit()) privs, accNums, accSeqs := []cryptotypes.PrivKey{priv1, priv2, priv3}, []uint64{0, 1, 2}, []uint64{0, 0, 0} - testTx, _, err := s.createTestTx(txBuilder, privs, accNums, accSeqs, ctx.ChainID()) + tx, err := suite.CreateTestTx(privs, accNums, accSeqs, suite.ctx.ChainID()) require.NoError(err) - // DeliverTx - _, err = txHandler.DeliverTx(sdk.WrapSDKContext(ctx), tx.Request{Tx: testTx}) + spkd := ante.NewSetPubKeyDecorator(suite.app.AccountKeeper) + antehandler := sdk.ChainAnteDecorators(spkd) + + ctx, err := antehandler(suite.ctx, tx, false) require.NoError(err) - // Require that all accounts have pubkey set after middleware runs + // Require that all accounts have pubkey set after Decorator runs for i, addr := range addrs { - pk, err := s.app.AccountKeeper.GetPubKey(ctx, addr) + pk, err := suite.app.AccountKeeper.GetPubKey(ctx, addr) require.NoError(err, "Error on retrieving pubkey from account") require.True(pubs[i].Equals(pk), "Wrong Pubkey retrieved from AccountKeeper, idx=%d\nexpected=%s\n got=%s", i, pubs[i], pk) } - - // SimulateTx - _, err = txHandler.SimulateTx(sdk.WrapSDKContext(ctx), tx.Request{Tx: testTx}) - require.NoError(err) } -func (s *MWTestSuite) TestConsumeSignatureVerificationGas() { +func (suite *AnteTestSuite) TestConsumeSignatureVerificationGas() { params := types.DefaultParams() msg := []byte{1, 2, 3, 4} cdc := simapp.MakeTestEncodingConfig().Amino @@ -86,9 +78,9 @@ func (s *MWTestSuite) TestConsumeSignatureVerificationGas() { for i := 0; i < len(pkSet1); i++ { stdSig := legacytx.StdSignature{PubKey: pkSet1[i], Signature: sigSet1[i]} sigV2, err := legacytx.StdSignatureToSignatureV2(cdc, stdSig) - s.Require().NoError(err) + suite.Require().NoError(err) err = multisig.AddSignatureV2(multisignature1, sigV2, pkSet1) - s.Require().NoError(err) + suite.Require().NoError(err) } type args struct { @@ -115,30 +107,23 @@ func (s *MWTestSuite) TestConsumeSignatureVerificationGas() { Data: tt.args.sig, Sequence: 0, // Arbitrary account sequence } - err := middleware.DefaultSigVerificationGasConsumer(tt.args.meter, sigV2, tt.args.params) + err := ante.DefaultSigVerificationGasConsumer(tt.args.meter, sigV2, tt.args.params) if tt.shouldErr { - s.Require().NotNil(err) + suite.Require().NotNil(err) } else { - s.Require().Nil(err) - s.Require().Equal(tt.gasConsumed, tt.args.meter.GasConsumed(), fmt.Sprintf("%d != %d", tt.gasConsumed, tt.args.meter.GasConsumed())) + suite.Require().Nil(err) + suite.Require().Equal(tt.gasConsumed, tt.args.meter.GasConsumed(), fmt.Sprintf("%d != %d", tt.gasConsumed, tt.args.meter.GasConsumed())) } } } -func (s *MWTestSuite) TestSigVerification() { - ctx := s.SetupTest(true) // setup +func (suite *AnteTestSuite) TestSigVerification() { + suite.SetupTest(true) // setup + suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() // make block height non-zero to ensure account numbers part of signBytes - ctx = ctx.WithBlockHeight(1) - txHandler := middleware.ComposeMiddlewares( - noopTxHandler, - middleware.SetPubKeyMiddleware(s.app.AccountKeeper), - middleware.SigVerificationMiddleware( - s.app.AccountKeeper, - s.clientCtx.TxConfig.SignModeHandler(), - ), - ) + suite.ctx = suite.ctx.WithBlockHeight(1) // keys and addresses priv1, _, addr1 := testdata.KeyTestPubAddr() @@ -150,15 +135,19 @@ func (s *MWTestSuite) TestSigVerification() { msgs := make([]sdk.Msg, len(addrs)) // set accounts and create msg for each address for i, addr := range addrs { - acc := s.app.AccountKeeper.NewAccountWithAddress(ctx, addr) - s.Require().NoError(acc.SetAccountNumber(uint64(i))) - s.app.AccountKeeper.SetAccount(ctx, acc) + acc := suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, addr) + suite.Require().NoError(acc.SetAccountNumber(uint64(i))) + suite.app.AccountKeeper.SetAccount(suite.ctx, acc) msgs[i] = testdata.NewTestMsg(addr) } feeAmount := testdata.NewTestFeeAmount() gasLimit := testdata.NewTestGasLimit() + spkd := ante.NewSetPubKeyDecorator(suite.app.AccountKeeper) + svd := ante.NewSigVerificationDecorator(suite.app.AccountKeeper, suite.clientCtx.TxConfig.SignModeHandler()) + antehandler := sdk.ChainAnteDecorators(spkd, svd) + type testCase struct { name string privs []cryptotypes.PrivKey @@ -177,25 +166,21 @@ func (s *MWTestSuite) TestSigVerification() { {"no err on recheck", []cryptotypes.PrivKey{}, []uint64{}, []uint64{}, true, false}, } for i, tc := range testCases { - ctx = ctx.WithIsReCheckTx(tc.recheck) - txBuilder := s.clientCtx.TxConfig.NewTxBuilder() // Create new txBuilder for each test + suite.ctx = suite.ctx.WithIsReCheckTx(tc.recheck) + suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() // Create new txBuilder for each test - s.Require().NoError(txBuilder.SetMsgs(msgs...)) - txBuilder.SetFeeAmount(feeAmount) - txBuilder.SetGasLimit(gasLimit) + suite.Require().NoError(suite.txBuilder.SetMsgs(msgs...)) + suite.txBuilder.SetFeeAmount(feeAmount) + suite.txBuilder.SetGasLimit(gasLimit) - testTx, _, err := s.createTestTx(txBuilder, tc.privs, tc.accNums, tc.accSeqs, ctx.ChainID()) - s.Require().NoError(err) + tx, err := suite.CreateTestTx(tc.privs, tc.accNums, tc.accSeqs, suite.ctx.ChainID()) + suite.Require().NoError(err) - if tc.recheck { - _, _, err = txHandler.CheckTx(sdk.WrapSDKContext(ctx), tx.Request{Tx: testTx}, tx.RequestCheckTx{Type: abci.CheckTxType_Recheck}) - } else { - _, _, err = txHandler.CheckTx(sdk.WrapSDKContext(ctx), tx.Request{Tx: testTx}, tx.RequestCheckTx{}) - } + _, err = antehandler(suite.ctx, tx, false) if tc.shouldErr { - s.Require().NotNil(err, "TestCase %d: %s did not error as expected", i, tc.name) + suite.Require().NotNil(err, "TestCase %d: %s did not error as expected", i, tc.name) } else { - s.Require().Nil(err, "TestCase %d: %s errored unexpectedly. Err: %v", i, tc.name, err) + suite.Require().Nil(err, "TestCase %d: %s errored unexpectedly. Err: %v", i, tc.name, err) } } } @@ -206,22 +191,35 @@ func (s *MWTestSuite) 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 (s *MWTestSuite) TestSigVerification_ExplicitAmino() { - ctx := s.SetupTest(true) - ctx = ctx.WithBlockHeight(1) +func (suite *AnteTestSuite) TestSigVerification_ExplicitAmino() { + suite.app, suite.ctx = createTestApp(suite.T(), true) + suite.ctx = suite.ctx.WithBlockHeight(1) // Set up TxConfig. - aminoCdc := legacy.Cdc - aminoCdc.RegisterConcrete(&testdata.TestMsg{}, "testdata.TestMsg", nil) - + aminoCdc := codec.NewLegacyAmino() // We're using TestMsg amino encoding in some tests, so register it here. txConfig := legacytx.StdTxConfig{Cdc: aminoCdc} - s.clientCtx = client.Context{}. + suite.clientCtx = client.Context{}. WithTxConfig(txConfig) + anteHandler, err := ante.NewAnteHandler( + ante.HandlerOptions{ + AccountKeeper: suite.app.AccountKeeper, + BankKeeper: suite.app.BankKeeper, + FeegrantKeeper: suite.app.FeeGrantKeeper, + SignModeHandler: txConfig.SignModeHandler(), + SigGasConsumer: ante.DefaultSigVerificationGasConsumer, + }, + ) + + suite.Require().NoError(err) + suite.anteHandler = anteHandler + + suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() + // make block height non-zero to ensure account numbers part of signBytes - ctx = ctx.WithBlockHeight(1) + suite.ctx = suite.ctx.WithBlockHeight(1) // keys and addresses priv1, _, addr1 := testdata.KeyTestPubAddr() @@ -233,23 +231,18 @@ func (s *MWTestSuite) TestSigVerification_ExplicitAmino() { msgs := make([]sdk.Msg, len(addrs)) // set accounts and create msg for each address for i, addr := range addrs { - acc := s.app.AccountKeeper.NewAccountWithAddress(ctx, addr) - s.Require().NoError(acc.SetAccountNumber(uint64(i))) - s.app.AccountKeeper.SetAccount(ctx, acc) + acc := suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, addr) + suite.Require().NoError(acc.SetAccountNumber(uint64(i))) + suite.app.AccountKeeper.SetAccount(suite.ctx, acc) msgs[i] = testdata.NewTestMsg(addr) } feeAmount := testdata.NewTestFeeAmount() gasLimit := testdata.NewTestGasLimit() - txHandler := middleware.ComposeMiddlewares( - noopTxHandler, - middleware.SetPubKeyMiddleware(s.app.AccountKeeper), - middleware.SigVerificationMiddleware( - s.app.AccountKeeper, - s.clientCtx.TxConfig.SignModeHandler(), - ), - ) + 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 @@ -259,7 +252,6 @@ func (s *MWTestSuite) 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}, @@ -269,32 +261,27 @@ func (s *MWTestSuite) 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 { - ctx = ctx.WithIsReCheckTx(tc.recheck) - txBuilder := s.clientCtx.TxConfig.NewTxBuilder() // Create new txBuilder for each test + suite.ctx = suite.ctx.WithIsReCheckTx(tc.recheck) + suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() // Create new txBuilder for each test - s.Require().NoError(txBuilder.SetMsgs(msgs...)) - txBuilder.SetFeeAmount(feeAmount) - txBuilder.SetGasLimit(gasLimit) + suite.Require().NoError(suite.txBuilder.SetMsgs(msgs...)) + suite.txBuilder.SetFeeAmount(feeAmount) + suite.txBuilder.SetGasLimit(gasLimit) - testTx, _, err := s.createTestTx(txBuilder, tc.privs, tc.accNums, tc.accSeqs, ctx.ChainID()) - s.Require().NoError(err) + tx, err := suite.CreateTestTx(tc.privs, tc.accNums, tc.accSeqs, suite.ctx.ChainID()) + suite.Require().NoError(err) - if tc.recheck { - _, _, err = txHandler.CheckTx(sdk.WrapSDKContext(ctx), tx.Request{Tx: testTx}, tx.RequestCheckTx{Type: abci.CheckTxType_Recheck}) - } else { - _, _, err = txHandler.CheckTx(sdk.WrapSDKContext(ctx), tx.Request{Tx: testTx}, tx.RequestCheckTx{}) - } + _, err = antehandler(suite.ctx, tx, false) if tc.shouldErr { - s.Require().NotNil(err, "TestCase %d: %s did not error as expected", i, tc.name) + suite.Require().NotNil(err, "TestCase %d: %s did not error as expected", i, tc.name) } else { - s.Require().Nil(err, "TestCase %d: %s errored unexpectedly. Err: %v", i, tc.name, err) + suite.Require().Nil(err, "TestCase %d: %s errored unexpectedly. Err: %v", i, tc.name, err) } } } -func (s *MWTestSuite) TestSigIntegration() { +func (suite *AnteTestSuite) TestSigIntegration() { // generate private keys privs := []cryptotypes.PrivKey{ secp256k1.GenPrivKey(), @@ -304,23 +291,23 @@ func (s *MWTestSuite) TestSigIntegration() { params := types.DefaultParams() initialSigCost := params.SigVerifyCostSecp256k1 - initialCost, err := s.runSigMiddlewares(params, false, privs...) - s.Require().Nil(err) + initialCost, err := suite.runSigDecorators(params, false, privs...) + suite.Require().Nil(err) params.SigVerifyCostSecp256k1 *= 2 - doubleCost, err := s.runSigMiddlewares(params, false, privs...) - s.Require().Nil(err) + doubleCost, err := suite.runSigDecorators(params, false, privs...) + suite.Require().Nil(err) - s.Require().Equal(initialSigCost*uint64(len(privs)), doubleCost-initialCost) + suite.Require().Equal(initialSigCost*uint64(len(privs)), doubleCost-initialCost) } -func (s *MWTestSuite) runSigMiddlewares(params types.Params, _ bool, privs ...cryptotypes.PrivKey) (sdk.Gas, error) { - ctx := s.SetupTest(true) // setup - txBuilder := s.clientCtx.TxConfig.NewTxBuilder() +func (suite *AnteTestSuite) runSigDecorators(params types.Params, _ bool, privs ...cryptotypes.PrivKey) (sdk.Gas, error) { + suite.SetupTest(true) // setup + suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() // Make block-height non-zero to include accNum in SignBytes - ctx = ctx.WithBlockHeight(1) - s.app.AccountKeeper.SetParams(ctx, params) + suite.ctx = suite.ctx.WithBlockHeight(1) + suite.app.AccountKeeper.SetParams(suite.ctx, params) msgs := make([]sdk.Msg, len(privs)) accNums := make([]uint64, len(privs)) @@ -328,89 +315,76 @@ func (s *MWTestSuite) runSigMiddlewares(params types.Params, _ bool, privs ...cr // set accounts and create msg for each address for i, priv := range privs { addr := sdk.AccAddress(priv.PubKey().Address()) - acc := s.app.AccountKeeper.NewAccountWithAddress(ctx, addr) - s.Require().NoError(acc.SetAccountNumber(uint64(i))) - s.app.AccountKeeper.SetAccount(ctx, acc) + acc := suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, addr) + suite.Require().NoError(acc.SetAccountNumber(uint64(i))) + suite.app.AccountKeeper.SetAccount(suite.ctx, acc) msgs[i] = testdata.NewTestMsg(addr) accNums[i] = uint64(i) accSeqs[i] = uint64(0) } - s.Require().NoError(txBuilder.SetMsgs(msgs...)) + suite.Require().NoError(suite.txBuilder.SetMsgs(msgs...)) feeAmount := testdata.NewTestFeeAmount() gasLimit := testdata.NewTestGasLimit() - 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(), - ), - ) + suite.txBuilder.SetFeeAmount(feeAmount) + suite.txBuilder.SetGasLimit(gasLimit) - // Determine gas consumption of txhandler with default params - before := ctx.GasMeter().GasConsumed() - _, err = txHandler.DeliverTx(sdk.WrapSDKContext(ctx), tx.Request{Tx: testTx}) + tx, err := suite.CreateTestTx(privs, accNums, accSeqs, suite.ctx.ChainID()) + suite.Require().NoError(err) + + spkd := ante.NewSetPubKeyDecorator(suite.app.AccountKeeper) + svgc := ante.NewSigGasConsumeDecorator(suite.app.AccountKeeper, ante.DefaultSigVerificationGasConsumer) + svd := ante.NewSigVerificationDecorator(suite.app.AccountKeeper, suite.clientCtx.TxConfig.SignModeHandler()) + antehandler := sdk.ChainAnteDecorators(spkd, svgc, svd) + + // Determine gas consumption of antehandler with default params + before := suite.ctx.GasMeter().GasConsumed() + ctx, err := antehandler(suite.ctx, tx, false) after := ctx.GasMeter().GasConsumed() return after - before, err } -func (s *MWTestSuite) TestIncrementSequenceMiddleware() { - ctx := s.SetupTest(true) // setup - txBuilder := s.clientCtx.TxConfig.NewTxBuilder() +func (suite *AnteTestSuite) TestIncrementSequenceDecorator() { + suite.SetupTest(true) // setup + suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() priv, _, addr := testdata.KeyTestPubAddr() - acc := s.app.AccountKeeper.NewAccountWithAddress(ctx, addr) - s.Require().NoError(acc.SetAccountNumber(uint64(50))) - s.app.AccountKeeper.SetAccount(ctx, acc) + acc := suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, addr) + suite.Require().NoError(acc.SetAccountNumber(uint64(50))) + suite.app.AccountKeeper.SetAccount(suite.ctx, acc) msgs := []sdk.Msg{testdata.NewTestMsg(addr)} - s.Require().NoError(txBuilder.SetMsgs(msgs...)) + suite.Require().NoError(suite.txBuilder.SetMsgs(msgs...)) privs := []cryptotypes.PrivKey{priv} - accNums := []uint64{s.app.AccountKeeper.GetAccount(ctx, addr).GetAccountNumber()} - accSeqs := []uint64{s.app.AccountKeeper.GetAccount(ctx, addr).GetSequence()} + accNums := []uint64{suite.app.AccountKeeper.GetAccount(suite.ctx, addr).GetAccountNumber()} + accSeqs := []uint64{suite.app.AccountKeeper.GetAccount(suite.ctx, addr).GetSequence()} feeAmount := testdata.NewTestFeeAmount() gasLimit := testdata.NewTestGasLimit() - txBuilder.SetFeeAmount(feeAmount) - txBuilder.SetGasLimit(gasLimit) + suite.txBuilder.SetFeeAmount(feeAmount) + suite.txBuilder.SetGasLimit(gasLimit) - testTx, _, err := s.createTestTx(txBuilder, privs, accNums, accSeqs, ctx.ChainID()) - s.Require().NoError(err) + tx, err := suite.CreateTestTx(privs, accNums, accSeqs, suite.ctx.ChainID()) + suite.Require().NoError(err) - txHandler := middleware.ComposeMiddlewares( - noopTxHandler, - middleware.IncrementSequenceMiddleware(s.app.AccountKeeper), - ) + isd := ante.NewIncrementSequenceDecorator(suite.app.AccountKeeper) + antehandler := sdk.ChainAnteDecorators(isd) testCases := []struct { ctx sdk.Context simulate bool expectedSeq uint64 }{ - {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}, + {suite.ctx.WithIsReCheckTx(true), false, 1}, + {suite.ctx.WithIsCheckTx(true).WithIsReCheckTx(false), false, 2}, + {suite.ctx.WithIsReCheckTx(true), false, 3}, + {suite.ctx.WithIsReCheckTx(true), false, 4}, + {suite.ctx.WithIsReCheckTx(true), true, 5}, } for i, tc := range testCases { - var err error - if tc.simulate { - _, err = txHandler.SimulateTx(sdk.WrapSDKContext(tc.ctx), tx.Request{Tx: testTx}) - } else { - _, err = txHandler.DeliverTx(sdk.WrapSDKContext(tc.ctx), tx.Request{Tx: testTx}) - } - - s.Require().NoError(err, "unexpected error; tc #%d, %v", i, tc) - s.Require().Equal(tc.expectedSeq, s.app.AccountKeeper.GetAccount(ctx, addr).GetSequence()) + _, err := antehandler(tc.ctx, tx, tc.simulate) + suite.Require().NoError(err, "unexpected error; tc #%d, %v", i, tc) + suite.Require().Equal(tc.expectedSeq, suite.app.AccountKeeper.GetAccount(suite.ctx, addr).GetSequence()) } } diff --git a/x/auth/ante/testutil_test.go b/x/auth/ante/testutil_test.go new file mode 100644 index 00000000000..15598b3b23b --- /dev/null +++ b/x/auth/ante/testutil_test.go @@ -0,0 +1,200 @@ +package ante_test + +import ( + "errors" + "fmt" + "testing" + + minttypes "github.com/cosmos/cosmos-sdk/x/mint/types" + + "github.com/stretchr/testify/suite" + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/tx" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + "github.com/cosmos/cosmos-sdk/simapp" + "github.com/cosmos/cosmos-sdk/testutil/testdata" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/tx/signing" + "github.com/cosmos/cosmos-sdk/x/auth/ante" + xauthsigning "github.com/cosmos/cosmos-sdk/x/auth/signing" + "github.com/cosmos/cosmos-sdk/x/auth/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" +) + +// TestAccount represents an account used in the tests in x/auth/ante. +type TestAccount struct { + acc types.AccountI + priv cryptotypes.PrivKey +} + +// AnteTestSuite is a test suite to be used with ante handler tests. +type AnteTestSuite struct { + suite.Suite + + app *simapp.SimApp + anteHandler sdk.AnteHandler + ctx sdk.Context + clientCtx client.Context + txBuilder client.TxBuilder +} + +// returns context and app with params set on account keeper +func createTestApp(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) + + anteHandler, err := ante.NewAnteHandler( + ante.HandlerOptions{ + AccountKeeper: suite.app.AccountKeeper, + BankKeeper: suite.app.BankKeeper, + FeegrantKeeper: suite.app.FeeGrantKeeper, + SignModeHandler: encodingConfig.TxConfig.SignModeHandler(), + SigGasConsumer: ante.DefaultSigVerificationGasConsumer, + }, + ) + + suite.Require().NoError(err) + suite.anteHandler = anteHandler +} + +// CreateTestAccounts creates `numAccs` accounts, and return all relevant +// information about them including their private keys. +func (suite *AnteTestSuite) CreateTestAccounts(numAccs int) []TestAccount { + var accounts []TestAccount + + for i := 0; i < numAccs; i++ { + priv, _, addr := testdata.KeyTestPubAddr() + acc := suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, addr) + err := acc.SetAccountNumber(uint64(i)) + suite.Require().NoError(err) + suite.app.AccountKeeper.SetAccount(suite.ctx, acc) + someCoins := sdk.Coins{ + sdk.NewInt64Coin("atom", 10000000), + } + err = suite.app.BankKeeper.MintCoins(suite.ctx, minttypes.ModuleName, someCoins) + suite.Require().NoError(err) + + err = suite.app.BankKeeper.SendCoinsFromModuleToAccount(suite.ctx, minttypes.ModuleName, addr, someCoins) + suite.Require().NoError(err) + + accounts = append(accounts, TestAccount{acc, priv}) + } + + return accounts +} + +// CreateTestTx is a helper function to create a tx given multiple inputs. +func (suite *AnteTestSuite) CreateTestTx(privs []cryptotypes.PrivKey, accNums []uint64, accSeqs []uint64, chainID string) (xauthsigning.Tx, error) { + // First round: we gather all the signer infos. We use the "set empty + // signature" hack to do that. + var sigsV2 []signing.SignatureV2 + for i, priv := range privs { + sigV2 := signing.SignatureV2{ + PubKey: priv.PubKey(), + Data: &signing.SingleSignatureData{ + SignMode: suite.clientCtx.TxConfig.SignModeHandler().DefaultMode(), + Signature: nil, + }, + Sequence: accSeqs[i], + } + + sigsV2 = append(sigsV2, sigV2) + } + err := suite.txBuilder.SetSignatures(sigsV2...) + if err != nil { + return nil, err + } + + // Second round: all signer infos are set, so each signer can sign. + sigsV2 = []signing.SignatureV2{} + for i, priv := range privs { + signerData := xauthsigning.SignerData{ + ChainID: chainID, + AccountNumber: accNums[i], + Sequence: accSeqs[i], + } + sigV2, err := tx.SignWithPrivKey( + suite.clientCtx.TxConfig.SignModeHandler().DefaultMode(), signerData, + suite.txBuilder, priv, suite.clientCtx.TxConfig, accSeqs[i]) + if err != nil { + return nil, err + } + + sigsV2 = append(sigsV2, sigV2) + } + err = suite.txBuilder.SetSignatures(sigsV2...) + if err != nil { + return nil, err + } + + return suite.txBuilder.GetTx(), nil +} + +// TestCase represents a test case used in test tables. +type TestCase struct { + desc string + malleate func() + simulate bool + expPass bool + expErr error +} + +// CreateTestTx is a helper function to create a tx given multiple inputs. +func (suite *AnteTestSuite) RunTestCase(privs []cryptotypes.PrivKey, msgs []sdk.Msg, feeAmount sdk.Coins, gasLimit uint64, accNums, accSeqs []uint64, chainID string, tc TestCase) { + suite.Run(fmt.Sprintf("Case %s", tc.desc), func() { + suite.Require().NoError(suite.txBuilder.SetMsgs(msgs...)) + suite.txBuilder.SetFeeAmount(feeAmount) + suite.txBuilder.SetGasLimit(gasLimit) + + // Theoretically speaking, ante handler unit tests should only test + // ante handlers, but here we sometimes also test the tx creation + // process. + tx, txErr := suite.CreateTestTx(privs, accNums, accSeqs, chainID) + newCtx, anteErr := suite.anteHandler(suite.ctx, tx, tc.simulate) + + if tc.expPass { + suite.Require().NoError(txErr) + suite.Require().NoError(anteErr) + suite.Require().NotNil(newCtx) + + suite.ctx = newCtx + } else { + switch { + case txErr != nil: + suite.Require().Error(txErr) + suite.Require().True(errors.Is(txErr, tc.expErr)) + + case anteErr != nil: + suite.Require().Error(anteErr) + suite.Require().True(errors.Is(anteErr, tc.expErr)) + + default: + suite.Fail("expected one of txErr,anteErr to be an error") + } + } + }) +} + +func TestAnteTestSuite(t *testing.T) { + suite.Run(t, new(AnteTestSuite)) +} diff --git a/x/auth/client/testutil/suite.go b/x/auth/client/testutil/suite.go index cf9aeb46551..891facc0b66 100644 --- a/x/auth/client/testutil/suite.go +++ b/x/auth/client/testutil/suite.go @@ -1562,6 +1562,7 @@ func (s *IntegrationTestSuite) TestAuxSigner() { } func (s *IntegrationTestSuite) TestAuxToFee() { + s.T().Skip() require := s.Require() val := s.network.Validators[0] diff --git a/x/auth/middleware/basic.go b/x/auth/middleware/basic.go deleted file mode 100644 index 429c9a39537..00000000000 --- a/x/auth/middleware/basic.go +++ /dev/null @@ -1,359 +0,0 @@ -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, req tx.Request, checkReq tx.RequestCheckTx) (tx.Response, tx.ResponseCheckTx, error) { - // no need to validate basic on recheck tx, call next middleware - if checkReq.Type == abci.CheckTxType_Recheck { - return txh.next.CheckTx(ctx, req, checkReq) - } - - if err := validateBasicTxMsgs(req.Tx.GetMsgs()); err != nil { - return tx.Response{}, tx.ResponseCheckTx{}, err - } - - if err := req.Tx.ValidateBasic(); err != nil { - return tx.Response{}, tx.ResponseCheckTx{}, err - } - - return txh.next.CheckTx(ctx, req, checkReq) -} - -// DeliverTx implements tx.Handler.DeliverTx. -func (txh validateBasicTxHandler) DeliverTx(ctx context.Context, req tx.Request) (tx.Response, error) { - if err := req.Tx.ValidateBasic(); err != nil { - return tx.Response{}, err - } - - if err := validateBasicTxMsgs(req.Tx.GetMsgs()); err != nil { - return tx.Response{}, err - } - - return txh.next.DeliverTx(ctx, req) -} - -// SimulateTx implements tx.Handler.SimulateTx. -func (txh validateBasicTxHandler) SimulateTx(ctx context.Context, req tx.Request) (tx.Response, error) { - if err := req.Tx.ValidateBasic(); err != nil { - return tx.Response{}, err - } - - if err := validateBasicTxMsgs(req.Tx.GetMsgs()); err != nil { - return tx.Response{}, err - } - - return txh.next.SimulateTx(ctx, 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, req tx.Request, checkReq tx.RequestCheckTx) (tx.Response, tx.ResponseCheckTx, error) { - if err := checkTimeout(ctx, req.Tx); err != nil { - return tx.Response{}, tx.ResponseCheckTx{}, err - } - - return txh.next.CheckTx(ctx, req, checkReq) -} - -// DeliverTx implements tx.Handler.DeliverTx. -func (txh txTimeoutHeightTxHandler) DeliverTx(ctx context.Context, req tx.Request) (tx.Response, error) { - if err := checkTimeout(ctx, req.Tx); err != nil { - return tx.Response{}, err - } - - return txh.next.DeliverTx(ctx, req) -} - -// SimulateTx implements tx.Handler.SimulateTx. -func (txh txTimeoutHeightTxHandler) SimulateTx(ctx context.Context, req tx.Request) (tx.Response, error) { - if err := checkTimeout(ctx, req.Tx); err != nil { - return tx.Response{}, err - } - - return txh.next.SimulateTx(ctx, 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, req tx.Request, checkReq tx.RequestCheckTx) (tx.Response, tx.ResponseCheckTx, error) { - if err := vmm.checkForValidMemo(ctx, req.Tx); err != nil { - return tx.Response{}, tx.ResponseCheckTx{}, err - } - - return vmm.next.CheckTx(ctx, req, checkReq) -} - -// DeliverTx implements tx.Handler.DeliverTx method. -func (vmm validateMemoTxHandler) DeliverTx(ctx context.Context, req tx.Request) (tx.Response, error) { - if err := vmm.checkForValidMemo(ctx, req.Tx); err != nil { - return tx.Response{}, err - } - - return vmm.next.DeliverTx(ctx, req) -} - -// SimulateTx implements tx.Handler.SimulateTx method. -func (vmm validateMemoTxHandler) SimulateTx(ctx context.Context, req tx.Request) (tx.Response, error) { - if err := vmm.checkForValidMemo(ctx, req.Tx); err != nil { - return tx.Response{}, err - } - - return vmm.next.SimulateTx(ctx, 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 -} - -//nolint:unparam -func (cgts consumeTxSizeGasTxHandler) consumeTxSizeGas(ctx context.Context, _ sdk.Tx, txBytes []byte) 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, req tx.Request, checkReq tx.RequestCheckTx) (tx.Response, tx.ResponseCheckTx, error) { - if err := cgts.consumeTxSizeGas(ctx, req.Tx, req.TxBytes); err != nil { - return tx.Response{}, tx.ResponseCheckTx{}, err - } - - return cgts.next.CheckTx(ctx, req, checkReq) -} - -// DeliverTx implements tx.Handler.DeliverTx. -func (cgts consumeTxSizeGasTxHandler) DeliverTx(ctx context.Context, req tx.Request) (tx.Response, error) { - if err := cgts.consumeTxSizeGas(ctx, req.Tx, req.TxBytes); err != nil { - return tx.Response{}, err - } - - return cgts.next.DeliverTx(ctx, req) -} - -// SimulateTx implements tx.Handler.SimulateTx. -func (cgts consumeTxSizeGasTxHandler) SimulateTx(ctx context.Context, req tx.Request) (tx.Response, error) { - if err := cgts.consumeTxSizeGas(ctx, req.Tx, req.TxBytes); err != nil { - return tx.Response{}, err - } - - if err := cgts.simulateSigGasCost(ctx, req.Tx); err != nil { - return tx.Response{}, err - } - - return cgts.next.SimulateTx(ctx, 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 deleted file mode 100644 index 5c28ecae8ff..00000000000 --- a/x/auth/middleware/basic_test.go +++ /dev/null @@ -1,251 +0,0 @@ -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" -) - -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) - - // DeliverTx - _, err = txHandler.DeliverTx(sdk.WrapSDKContext(ctx), tx.Request{Tx: invalidTx}) - s.Require().NotNil(err, "Did not error on invalid tx") - - // SimulateTx - _, err = txHandler.SimulateTx(sdk.WrapSDKContext(ctx), tx.Request{Tx: invalidTx}) - 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) - - // DeliverTx - _, err = txHandler.DeliverTx(sdk.WrapSDKContext(ctx), tx.Request{Tx: validTx}) - s.Require().Nil(err, "ValidateBasicMiddleware returned error on valid tx. err: %v", err) - - // SimulateTx - _, err = txHandler.SimulateTx(sdk.WrapSDKContext(ctx), tx.Request{Tx: validTx}) - 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 - // DeliverTx - _, err = txHandler.DeliverTx(sdk.WrapSDKContext(ctx), tx.Request{Tx: invalidTx}) - s.Require().Nil(err, "ValidateBasicMiddleware ran on ReCheck") - - // SimulateTx - _, err = txHandler.SimulateTx(sdk.WrapSDKContext(ctx), tx.Request{Tx: invalidTx}) - 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 - // DeliverTx - _, err = txHandler.DeliverTx(sdk.WrapSDKContext(ctx), tx.Request{Tx: invalidTx}) - s.Require().NotNil(err, "Did not error on tx with high memo") - - // SimulateTx - _, err = txHandler.SimulateTx(sdk.WrapSDKContext(ctx), tx.Request{Tx: invalidTx}) - 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 - // DeliverTx - _, err = txHandler.DeliverTx(sdk.WrapSDKContext(ctx), tx.Request{Tx: validTx}) - s.Require().Nil(err, "ValidateBasicMiddleware returned error on valid tx. err: %v", err) - - // SimulateTx - _, err = txHandler.SimulateTx(sdk.WrapSDKContext(ctx), tx.Request{Tx: validTx}) - 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), tx.Request{Tx: testTx, TxBytes: 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), tx.Request{Tx: testTx, 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) - - // DeliverTx - _, err = txHandler.DeliverTx(sdk.WrapSDKContext(ctx), tx.Request{Tx: testTx}) - s.Require().Equal(tc.expectErr, err != nil, err) - - // SimulateTx - _, err = txHandler.SimulateTx(sdk.WrapSDKContext(ctx), tx.Request{Tx: testTx}) - s.Require().Equal(tc.expectErr, err != nil, err) - }) - } -} diff --git a/x/auth/middleware/block_gas.go b/x/auth/middleware/block_gas.go deleted file mode 100644 index 00632f6139b..00000000000 --- a/x/auth/middleware/block_gas.go +++ /dev/null @@ -1,52 +0,0 @@ -package middleware - -import ( - "context" - - sdk "github.com/cosmos/cosmos-sdk/types" - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" - "github.com/cosmos/cosmos-sdk/types/tx" -) - -type consumeBlockGasHandler struct { - next tx.Handler -} - -// ConsumeBlockGasMiddleware check and consume block gas meter. -func ConsumeBlockGasMiddleware(txh tx.Handler) tx.Handler { - return consumeBlockGasHandler{next: txh} -} - -var _ tx.Handler = consumeBlockGasHandler{} - -// CheckTx implements tx.Handler.CheckTx method. -func (cbgh consumeBlockGasHandler) CheckTx(ctx context.Context, req tx.Request, checkReq tx.RequestCheckTx) (res tx.Response, resCheckTx tx.ResponseCheckTx, err error) { - return cbgh.next.CheckTx(ctx, req, checkReq) -} - -// DeliverTx implements tx.Handler.DeliverTx method. -// Consume block gas meter, panic when block gas meter exceeded, -// the panic should be caught by `RecoveryTxMiddleware`. -func (cbgh consumeBlockGasHandler) DeliverTx(ctx context.Context, req tx.Request) (res tx.Response, err error) { - sdkCtx := sdk.UnwrapSDKContext(ctx) - // only run the tx if there is block gas remaining - if sdkCtx.BlockGasMeter().IsOutOfGas() { - err = sdkerrors.Wrap(sdkerrors.ErrOutOfGas, "no block gas left to run tx") - return - } - - // If BlockGasMeter() panics it will be caught by the `RecoveryTxMiddleware` and will - // return an error - in any case BlockGasMeter will consume gas past the limit. - defer func() { - sdkCtx.BlockGasMeter().ConsumeGas( - sdkCtx.GasMeter().GasConsumedToLimit(), "block gas meter", - ) - }() - - return cbgh.next.DeliverTx(ctx, req) -} - -// SimulateTx implements tx.Handler.SimulateTx method. -func (cbgh consumeBlockGasHandler) SimulateTx(ctx context.Context, req tx.Request) (res tx.Response, err error) { - return cbgh.next.SimulateTx(ctx, req) -} diff --git a/x/auth/middleware/branch_store.go b/x/auth/middleware/branch_store.go deleted file mode 100644 index 236d288c122..00000000000 --- a/x/auth/middleware/branch_store.go +++ /dev/null @@ -1,70 +0,0 @@ -package middleware - -import ( - "context" - - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/types/tx" - tmtypes "github.com/tendermint/tendermint/types" -) - -type branchStoreHandler struct { - next tx.Handler -} - -// WithBranchedStore creates a new MultiStore branch and commits the store if the downstream -// returned no error. It cancels writes from the failed transactions. -func WithBranchedStore(txh tx.Handler) tx.Handler { - return branchStoreHandler{next: txh} -} - -// CheckTx implements tx.Handler.CheckTx method. -// Do nothing during CheckTx. -func (sh branchStoreHandler) CheckTx(ctx context.Context, req tx.Request, checkReq tx.RequestCheckTx) (tx.Response, tx.ResponseCheckTx, error) { - return sh.next.CheckTx(ctx, req, checkReq) -} - -// DeliverTx implements tx.Handler.DeliverTx method. -func (sh branchStoreHandler) DeliverTx(ctx context.Context, req tx.Request) (tx.Response, error) { - return branchAndRun(ctx, req, sh.next.DeliverTx) -} - -// SimulateTx implements tx.Handler.SimulateTx method. -func (sh branchStoreHandler) SimulateTx(ctx context.Context, req tx.Request) (tx.Response, error) { - return branchAndRun(ctx, req, sh.next.SimulateTx) -} - -type nextFn func(ctx context.Context, req tx.Request) (tx.Response, error) - -// branchAndRun creates a new Context based on the existing Context with a MultiStore branch -// in case message processing fails. -func branchAndRun(ctx context.Context, req tx.Request, fn nextFn) (tx.Response, error) { - sdkCtx := sdk.UnwrapSDKContext(ctx) - runMsgCtx, branchedStore := branchStore(sdkCtx, tmtypes.Tx(req.TxBytes)) - - rsp, err := fn(sdk.WrapSDKContext(runMsgCtx), req) - if err == nil { - // commit storage iff no error - branchedStore.Write() - } - - return rsp, err -} - -// branchStore returns a new context based off of the provided context with -// a branched multi-store. -func branchStore(sdkCtx sdk.Context, tx tmtypes.Tx) (sdk.Context, sdk.CacheMultiStore) { - ms := sdkCtx.MultiStore() - msCache := ms.CacheMultiStore() - if msCache.TracingEnabled() { - msCache = msCache.SetTracingContext( - sdk.TraceContext( - map[string]interface{}{ - "txHash": tx.Hash(), - }, - ), - ).(sdk.CacheMultiStore) - } - - return sdkCtx.WithMultiStore(msCache), msCache -} diff --git a/x/auth/middleware/branch_store_test.go b/x/auth/middleware/branch_store_test.go deleted file mode 100644 index 12181052539..00000000000 --- a/x/auth/middleware/branch_store_test.go +++ /dev/null @@ -1,129 +0,0 @@ -package middleware_test - -import ( - "context" - "fmt" - "math" - - 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" - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" - "github.com/cosmos/cosmos-sdk/types/tx" - "github.com/cosmos/cosmos-sdk/x/auth/middleware" - minttypes "github.com/cosmos/cosmos-sdk/x/mint/types" -) - -var blockMaxGas = uint64(simapp.DefaultConsensusParams.Block.MaxGas) - -func (s *MWTestSuite) TestBranchStore() { - testcases := []struct { - name string - gasToConsume uint64 // gas to consume in the msg execution - panicTx bool // panic explicitly in tx execution - expErr bool - }{ - {"less than block gas meter", 10, false, false}, - {"more than block gas meter", blockMaxGas, false, true}, - {"more than block gas meter", uint64(float64(blockMaxGas) * 1.2), false, true}, - {"consume MaxUint64", math.MaxUint64, false, true}, - {"consume block gas when paniced", 10, true, true}, - } - - for _, tc := range testcases { - s.Run(tc.name, func() { - ctx := s.SetupTest(true).WithBlockGasMeter(sdk.NewGasMeter(blockMaxGas)) // setup - txBuilder := s.clientCtx.TxConfig.NewTxBuilder() - - // tx fee - feeCoin := sdk.NewCoin("atom", sdk.NewInt(150)) - feeAmount := sdk.NewCoins(feeCoin) - - // test account and fund - priv1, _, addr1 := testdata.KeyTestPubAddr() - err := s.app.BankKeeper.MintCoins(ctx, minttypes.ModuleName, feeAmount) - s.Require().NoError(err) - err = s.app.BankKeeper.SendCoinsFromModuleToAccount(ctx, minttypes.ModuleName, addr1, feeAmount) - s.Require().NoError(err) - s.Require().Equal(feeCoin.Amount, s.app.BankKeeper.GetBalance(ctx, addr1, feeCoin.Denom).Amount) - seq, _ := s.app.AccountKeeper.GetSequence(ctx, addr1) - s.Require().Equal(uint64(0), seq) - - // testMsgTxHandler is a test txHandler that handles one single TestMsg, - // consumes the given `tc.gasToConsume`, and sets the bank store "ok" key to "ok". - testMsgTxHandler := customTxHandler{func(ctx context.Context, req tx.Request) (tx.Response, error) { - msg, ok := req.Tx.GetMsgs()[0].(*testdata.TestMsg) - if !ok { - return tx.Response{}, fmt.Errorf("Wrong Msg type, expected %T, got %T", (*testdata.TestMsg)(nil), msg) - } - - sdkCtx := sdk.UnwrapSDKContext(ctx) - sdkCtx.KVStore(s.app.GetKey("bank")).Set([]byte("ok"), []byte("ok")) - sdkCtx.GasMeter().ConsumeGas(tc.gasToConsume, "TestMsg") - if tc.panicTx { - panic("panic in tx execution") - } - return tx.Response{}, nil - }} - - txHandler := middleware.ComposeMiddlewares( - testMsgTxHandler, - middleware.NewTxDecoderMiddleware(s.clientCtx.TxConfig.TxDecoder()), - middleware.GasTxMiddleware, - middleware.RecoveryTxMiddleware, - middleware.DeductFeeMiddleware(s.app.AccountKeeper, s.app.BankKeeper, s.app.FeeGrantKeeper, nil), - middleware.IncrementSequenceMiddleware(s.app.AccountKeeper), - middleware.WithBranchedStore, - middleware.ConsumeBlockGasMiddleware, - ) - - // msg and signatures - msg := testdata.NewTestMsg(addr1) - var gasLimit uint64 = math.MaxUint64 // no limit on sdk.GasMeter - s.Require().NoError(txBuilder.SetMsgs(msg)) - txBuilder.SetFeeAmount(feeAmount) - txBuilder.SetGasLimit(gasLimit) - - privs, accNums, accSeqs := []cryptotypes.PrivKey{priv1}, []uint64{0}, []uint64{0} - testTx, _, err := s.createTestTx(txBuilder, privs, accNums, accSeqs, ctx.ChainID()) - s.Require().NoError(err) - - _, err = txHandler.DeliverTx(sdk.WrapSDKContext(ctx), tx.Request{Tx: testTx}) - - bankStore := ctx.KVStore(s.app.GetKey("bank")) - okValue := bankStore.Get([]byte("ok")) - - if tc.expErr { - s.Require().Error(err) - if tc.panicTx { - s.Require().True(sdkerrors.IsOf(err, sdkerrors.ErrPanic)) - } else { - s.Require().True(sdkerrors.IsOf(err, sdkerrors.ErrOutOfGas)) - } - s.Require().Empty(okValue) - } else { - s.Require().NoError(err) - s.Require().Equal([]byte("ok"), okValue) - } - // block gas is always consumed - baseGas := uint64(24564) // baseGas is the gas consumed by middlewares - expGasConsumed := addUint64Saturating(tc.gasToConsume, baseGas) - s.Require().Equal(expGasConsumed, ctx.BlockGasMeter().GasConsumed()) - // tx fee is always deducted - s.Require().Equal(int64(0), s.app.BankKeeper.GetBalance(ctx, addr1, feeCoin.Denom).Amount.Int64()) - // sender's sequence is always increased - seq, err = s.app.AccountKeeper.GetSequence(ctx, addr1) - s.Require().NoError(err) - s.Require().Equal(uint64(1), seq) - }) - } -} - -func addUint64Saturating(a, b uint64) uint64 { - if math.MaxUint64-a < b { - return math.MaxUint64 - } - - return a + b -} diff --git a/x/auth/middleware/ext.go b/x/auth/middleware/ext.go deleted file mode 100644 index 5159e3c5f1f..00000000000 --- a/x/auth/middleware/ext.go +++ /dev/null @@ -1,86 +0,0 @@ -package middleware - -import ( - "context" - - 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 -} - -// ExtensionOptionChecker is a function that returns true if the extension option is accepted. -type ExtensionOptionChecker func(*codectypes.Any) bool - -// rejectExtensionOption is the default extension check that reject all tx -// extensions. -func rejectExtensionOption(*codectypes.Any) bool { - return false -} - -type rejectExtensionOptionsTxHandler struct { - next tx.Handler - checker ExtensionOptionChecker -} - -// NewExtensionOptionsMiddleware creates a new middleware that rejects all extension -// options which can optionally be included in protobuf transactions that don't pass the checker. -// Users that need extension options should pass a custom checker that returns true for the -// needed extension options. -func NewExtensionOptionsMiddleware(checker ExtensionOptionChecker) tx.Middleware { - if checker == nil { - checker = rejectExtensionOption - } - return func(txh tx.Handler) tx.Handler { - return rejectExtensionOptionsTxHandler{ - next: txh, - checker: checker, - } - } -} - -var _ tx.Handler = rejectExtensionOptionsTxHandler{} - -func checkExtOpts(tx sdk.Tx, checker ExtensionOptionChecker) error { - if hasExtOptsTx, ok := tx.(HasExtensionOptionsTx); ok { - for _, opt := range hasExtOptsTx.GetExtensionOptions() { - if !checker(opt) { - return sdkerrors.ErrUnknownExtensionOptions - } - } - } - - return nil -} - -// CheckTx implements tx.Handler.CheckTx. -func (txh rejectExtensionOptionsTxHandler) CheckTx(ctx context.Context, req tx.Request, checkReq tx.RequestCheckTx) (tx.Response, tx.ResponseCheckTx, error) { - if err := checkExtOpts(req.Tx, txh.checker); err != nil { - return tx.Response{}, tx.ResponseCheckTx{}, err - } - - return txh.next.CheckTx(ctx, req, checkReq) -} - -// DeliverTx implements tx.Handler.DeliverTx. -func (txh rejectExtensionOptionsTxHandler) DeliverTx(ctx context.Context, req tx.Request) (tx.Response, error) { - if err := checkExtOpts(req.Tx, txh.checker); err != nil { - return tx.Response{}, err - } - - return txh.next.DeliverTx(ctx, req) -} - -// SimulateTx implements tx.Handler.SimulateTx method. -func (txh rejectExtensionOptionsTxHandler) SimulateTx(ctx context.Context, req tx.Request) (tx.Response, error) { - if err := checkExtOpts(req.Tx, txh.checker); err != nil { - return tx.Response{}, err - } - - return txh.next.SimulateTx(ctx, req) -} diff --git a/x/auth/middleware/ext_test.go b/x/auth/middleware/ext_test.go deleted file mode 100644 index 27c294794cf..00000000000 --- a/x/auth/middleware/ext_test.go +++ /dev/null @@ -1,54 +0,0 @@ -package middleware_test - -import ( - "github.com/cosmos/cosmos-sdk/codec/types" - codectypes "github.com/cosmos/cosmos-sdk/codec/types" - "github.com/cosmos/cosmos-sdk/testutil/testdata" - sdk "github.com/cosmos/cosmos-sdk/types" - typestx "github.com/cosmos/cosmos-sdk/types/tx" - "github.com/cosmos/cosmos-sdk/x/auth/middleware" - "github.com/cosmos/cosmos-sdk/x/auth/tx" -) - -func (s *MWTestSuite) TestExtensionOptionsMiddleware() { - testCases := []struct { - msg string - allow bool - }{ - {"allow extension", true}, - {"reject extension", false}, - } - for _, tc := range testCases { - s.Run(tc.msg, func() { - ctx := s.SetupTest(true) // setup - txBuilder := s.clientCtx.TxConfig.NewTxBuilder() - - txHandler := middleware.ComposeMiddlewares(noopTxHandler, middleware.NewExtensionOptionsMiddleware(func(_ *codectypes.Any) bool { - return tc.allow - })) - - // no extension options should not trigger an error - theTx := txBuilder.GetTx() - _, _, err := txHandler.CheckTx(sdk.WrapSDKContext(ctx), typestx.Request{Tx: theTx}, typestx.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 - } - - // set an extension option and check - any, err := types.NewAnyWithValue(testdata.NewTestMsg()) - s.Require().NoError(err) - extOptsTxBldr.SetExtensionOptions(any) - theTx = txBuilder.GetTx() - _, _, err = txHandler.CheckTx(sdk.WrapSDKContext(ctx), typestx.Request{Tx: theTx}, typestx.RequestCheckTx{}) - if tc.allow { - s.Require().NoError(err) - } else { - s.Require().EqualError(err, "unknown extension options") - } - }) - } -} diff --git a/x/auth/middleware/fee.go b/x/auth/middleware/fee.go deleted file mode 100644 index 2ae83c37269..00000000000 --- a/x/auth/middleware/fee.go +++ /dev/null @@ -1,153 +0,0 @@ -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" -) - -// TxFeeChecker check if the provided fee is enough and returns the effective fee and tx priority, -// the effective fee should be deducted later, and the priority should be returned in abci response. -type TxFeeChecker func(ctx sdk.Context, tx sdk.Tx) (sdk.Coins, int64, error) - -var _ tx.Handler = deductFeeTxHandler{} - -type deductFeeTxHandler struct { - accountKeeper AccountKeeper - bankKeeper types.BankKeeper - feegrantKeeper FeegrantKeeper - txFeeChecker TxFeeChecker - 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, tfc TxFeeChecker) tx.Middleware { - if tfc == nil { - tfc = checkTxFeeWithValidatorMinGasPrices - } - return func(txh tx.Handler) tx.Handler { - return deductFeeTxHandler{ - accountKeeper: ak, - bankKeeper: bk, - feegrantKeeper: fk, - txFeeChecker: tfc, - next: txh, - } - } -} - -func (dfd deductFeeTxHandler) checkDeductFee(ctx sdk.Context, sdkTx sdk.Tx, fee sdk.Coins) error { - feeTx, ok := sdkTx.(sdk.FeeTx) - if !ok { - return sdkerrors.Wrap(sdkerrors.ErrTxDecode, "Tx must be a FeeTx") - } - - if addr := dfd.accountKeeper.GetModuleAddress(types.FeeCollectorName); addr == nil { - return fmt.Errorf("Fee collector module account (%s) has not been set", types.FeeCollectorName) - } - - feePayer := feeTx.FeePayer() - feeGranter := feeTx.FeeGranter() - deductFeesFrom := feePayer - - // if feegranter set deduct fee from feegranter account. - // this works with only when feegrant enabled. - if feeGranter != nil { - if dfd.feegrantKeeper == nil { - return sdkerrors.ErrInvalidRequest.Wrap("fee grants are not enabled") - } else if !feeGranter.Equals(feePayer) { - err := dfd.feegrantKeeper.UseGrantedFees(ctx, feeGranter, feePayer, fee, sdkTx.GetMsgs()) - if err != nil { - return sdkerrors.Wrapf(err, "%s does not not allow to pay fees for %s", feeGranter, feePayer) - } - } - - deductFeesFrom = feeGranter - } - - deductFeesFromAcc := dfd.accountKeeper.GetAccount(ctx, deductFeesFrom) - if deductFeesFromAcc == nil { - return sdkerrors.ErrUnknownAddress.Wrapf("fee payer address: %s does not exist", deductFeesFrom) - } - - // deduct the fees - if !fee.IsZero() { - err := DeductFees(dfd.bankKeeper, ctx, deductFeesFromAcc, fee) - if err != nil { - return err - } - } - - events := sdk.Events{sdk.NewEvent(sdk.EventTypeTx, - sdk.NewAttribute(sdk.AttributeKeyFee, fee.String()), - )} - ctx.EventManager().EmitEvents(events) - - return nil -} - -// CheckTx implements tx.Handler.CheckTx. -func (dfd deductFeeTxHandler) CheckTx(ctx context.Context, req tx.Request, checkReq tx.RequestCheckTx) (tx.Response, tx.ResponseCheckTx, error) { - sdkCtx := sdk.UnwrapSDKContext(ctx) - fee, priority, err := dfd.txFeeChecker(sdkCtx, req.Tx) - if err != nil { - return tx.Response{}, tx.ResponseCheckTx{}, err - } - if err := dfd.checkDeductFee(sdkCtx, req.Tx, fee); err != nil { - return tx.Response{}, tx.ResponseCheckTx{}, err - } - - res, checkRes, err := dfd.next.CheckTx(ctx, req, checkReq) - checkRes.Priority = priority - - return res, checkRes, err -} - -// DeliverTx implements tx.Handler.DeliverTx. -func (dfd deductFeeTxHandler) DeliverTx(ctx context.Context, req tx.Request) (tx.Response, error) { - sdkCtx := sdk.UnwrapSDKContext(ctx) - fee, _, err := dfd.txFeeChecker(sdkCtx, req.Tx) - if err != nil { - return tx.Response{}, err - } - if err := dfd.checkDeductFee(sdkCtx, req.Tx, fee); err != nil { - return tx.Response{}, err - } - - return dfd.next.DeliverTx(ctx, req) -} - -func (dfd deductFeeTxHandler) SimulateTx(ctx context.Context, req tx.Request) (tx.Response, error) { - sdkCtx := sdk.UnwrapSDKContext(ctx) - fee, _, err := dfd.txFeeChecker(sdkCtx, req.Tx) - if err != nil { - return tx.Response{}, err - } - if err := dfd.checkDeductFee(sdkCtx, req.Tx, fee); err != nil { - return tx.Response{}, err - } - - return dfd.next.SimulateTx(ctx, req) -} - -// Deprecated: DeductFees deducts fees from the given account. -// This function will be private in the next release. -func DeductFees(bankKeeper types.BankKeeper, ctx sdk.Context, acc types.AccountI, fees sdk.Coins) error { - if !fees.IsValid() { - return sdkerrors.ErrInsufficientFee.Wrapf("invalid fee amount: %s", fees) - } - - err := bankKeeper.SendCoinsFromAccountToModule(ctx, acc.GetAddress(), types.FeeCollectorName, fees) - if err != nil { - return sdkerrors.ErrInsufficientFunds.Wrap(err.Error()) - } - - return nil -} diff --git a/x/auth/middleware/fee_test.go b/x/auth/middleware/fee_test.go deleted file mode 100644 index 9899e2aa63c..00000000000 --- a/x/auth/middleware/fee_test.go +++ /dev/null @@ -1,126 +0,0 @@ -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/types/tx" - "github.com/cosmos/cosmos-sdk/x/auth/middleware" - "github.com/cosmos/cosmos-sdk/x/bank/testutil" -) - -func (s *MWTestSuite) TestEnsureMempoolFees() { - ctx := s.SetupTest(true) // setup - txBuilder := s.clientCtx.TxConfig.NewTxBuilder() - - txHandler := middleware.ComposeMiddlewares(noopTxHandler, middleware.DeductFeeMiddleware( - s.app.AccountKeeper, - s.app.BankKeeper, - s.app.FeeGrantKeeper, - nil, - )) - - // keys and addresses - priv1, _, addr1 := testdata.KeyTestPubAddr() - - // msg and signatures - msg := testdata.NewTestMsg(addr1) - atomCoin := sdk.NewCoin("atom", sdk.NewInt(150)) - apeCoin := sdk.NewInt64Coin("ape", 1500000) - feeAmount := sdk.NewCoins(apeCoin, atomCoin) - 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} - testTx, _, 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.Request{Tx: testTx}, tx.RequestCheckTx{}) - s.Require().NotNil(err, "Middleware should have errored on too low fee for local gasPrice") - - // txHandler should fail since we also check minGasPrice in DeliverTx - _, err = txHandler.DeliverTx(sdk.WrapSDKContext(ctx), tx.Request{Tx: testTx}) - s.Require().Error(err, "MempoolFeeMiddleware don't error in DeliverTx") - - atomPrice = sdk.NewDecCoinFromDec("atom", sdk.NewDec(0).Quo(sdk.NewDec(100000))) - lowGasPrice := []sdk.DecCoin{atomPrice} - ctx = ctx.WithMinGasPrices(lowGasPrice) - - // Set account with sufficient funds - acc := s.app.AccountKeeper.NewAccountWithAddress(ctx, addr1) - s.app.AccountKeeper.SetAccount(ctx, acc) - err = testutil.FundAccount(s.app.BankKeeper, ctx, addr1, feeAmount) - s.Require().NoError(err) - - _, checkTxRes, err := txHandler.CheckTx(sdk.WrapSDKContext(ctx), tx.Request{Tx: testTx}, tx.RequestCheckTx{}) - s.Require().Nil(err, "Middleware should not have errored on fee higher than local gasPrice") - s.Require().Equal(atomCoin.Amount.Int64(), checkTxRes.Priority, "priority should be atom amount") -} - -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, - nil, - ), - ) - - // 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} - testTx, _, 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) - - // DeliverTx - _, err = txHandler.DeliverTx(sdk.WrapSDKContext(ctx), tx.Request{Tx: testTx}) - s.Require().NotNil(err, "Tx errored when fee payer had insufficient funds") - - // SimulateTx - _, err = txHandler.SimulateTx(sdk.WrapSDKContext(ctx), tx.Request{Tx: testTx}) - s.Require().NotNil(err, "Tx errored 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) - - // DeliverTx - _, err = txHandler.DeliverTx(sdk.WrapSDKContext(ctx), tx.Request{Tx: testTx}) - s.Require().Nil(err, "Tx did not error after account has been set with sufficient funds") - - err = testutil.FundAccount(s.app.BankKeeper, ctx, addr1, sdk.NewCoins(sdk.NewCoin("atom", sdk.NewInt(200)))) - s.Require().NoError(err) - - // SimulateTx - _, err = txHandler.SimulateTx(sdk.WrapSDKContext(ctx), tx.Request{Tx: testTx}) - s.Require().Nil(err, "Tx did not error after account has been set with sufficient funds") -} diff --git a/x/auth/middleware/gas.go b/x/auth/middleware/gas.go deleted file mode 100644 index 2957566b168..00000000000 --- a/x/auth/middleware/gas.go +++ /dev/null @@ -1,96 +0,0 @@ -package middleware - -import ( - "context" - - sdk "github.com/cosmos/cosmos-sdk/types" - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" - "github.com/cosmos/cosmos-sdk/types/tx" -) - -// GasTx defines a Tx with a GetGas() method which is needed to use gasTxHandler. -type GasTx interface { - sdk.Tx - GetGas() uint64 -} - -type gasTxHandler struct { - next tx.Handler -} - -// GasTxMiddleware defines a simple middleware that sets a new GasMeter on -// the sdk.Context, and sets the GasInfo on the result. It reads the tx.GetGas() -// by default, or sets to infinity in simulate mode. -func GasTxMiddleware(txh tx.Handler) tx.Handler { - return gasTxHandler{next: txh} -} - -var _ tx.Handler = gasTxHandler{} - -// CheckTx implements tx.Handler.CheckTx. -func (txh gasTxHandler) CheckTx(ctx context.Context, req tx.Request, checkReq tx.RequestCheckTx) (tx.Response, tx.ResponseCheckTx, error) { - sdkCtx, err := gasContext(sdk.UnwrapSDKContext(ctx), req.Tx, false) - if err != nil { - return tx.Response{}, tx.ResponseCheckTx{}, err - } - - res, resCheckTx, err := txh.next.CheckTx(sdk.WrapSDKContext(sdkCtx), req, checkReq) - - return populateGas(res, sdkCtx), resCheckTx, err -} - -// DeliverTx implements tx.Handler.DeliverTx. -func (txh gasTxHandler) DeliverTx(ctx context.Context, req tx.Request) (tx.Response, error) { - sdkCtx, err := gasContext(sdk.UnwrapSDKContext(ctx), req.Tx, false) - if err != nil { - return tx.Response{}, err - } - - res, err := txh.next.DeliverTx(sdk.WrapSDKContext(sdkCtx), req) - - return populateGas(res, sdkCtx), err -} - -// SimulateTx implements tx.Handler.SimulateTx method. -func (txh gasTxHandler) SimulateTx(ctx context.Context, req tx.Request) (tx.Response, error) { - sdkCtx, err := gasContext(sdk.UnwrapSDKContext(ctx), req.Tx, true) - if err != nil { - return tx.Response{}, err - } - - res, err := txh.next.SimulateTx(sdk.WrapSDKContext(sdkCtx), req) - - return populateGas(res, sdkCtx), err -} - -// populateGas returns a new tx.Response with gas fields populated. -func populateGas(res tx.Response, sdkCtx sdk.Context) tx.Response { - res.GasWanted = sdkCtx.GasMeter().Limit() - res.GasUsed = sdkCtx.GasMeter().GasConsumed() - - return res -} - -// gasContext returns a new context with a gas meter set from a given context. -func gasContext(ctx sdk.Context, tx sdk.Tx, isSimulate bool) (sdk.Context, 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 execution. - newCtx := setGasMeter(ctx, 0, isSimulate) - return newCtx, sdkerrors.Wrap(sdkerrors.ErrTxDecode, "Tx must be GasTx") - } - - return setGasMeter(ctx, gasTx.GetGas(), isSimulate), nil -} - -// setGasMeter returns a new context with a gas meter set from a given context. -func setGasMeter(ctx sdk.Context, gasLimit uint64, simulate bool) 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/middleware/gas_test.go b/x/auth/middleware/gas_test.go deleted file mode 100644 index be7e7e9ebb9..00000000000 --- a/x/auth/middleware/gas_test.go +++ /dev/null @@ -1,129 +0,0 @@ -package middleware_test - -import ( - "context" - "errors" - - cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" - "github.com/cosmos/cosmos-sdk/testutil/testdata" - sdk "github.com/cosmos/cosmos-sdk/types" - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" - "github.com/cosmos/cosmos-sdk/types/tx" - "github.com/cosmos/cosmos-sdk/x/auth/middleware" - "github.com/cosmos/cosmos-sdk/x/auth/signing" -) - -// txTest is a dummy tx that doesn't implement GasTx. It should set the GasMeter -// to 0 in this case. -type txTest struct{} - -var _ sdk.Tx = txTest{} - -func (t txTest) GetMsgs() []sdk.Msg { return []sdk.Msg{} } -func (t txTest) ValidateBasic() error { return nil } - -func (s *MWTestSuite) setupGasTx() (signing.Tx, []byte, sdk.Context, uint64) { - ctx := s.SetupTest(true) - txBuilder := s.clientCtx.TxConfig.NewTxBuilder() - - // 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) - - // test tx - privs, accNums, accSeqs := []cryptotypes.PrivKey{priv1}, []uint64{0}, []uint64{0} - tx, txBytes, err := s.createTestTx(txBuilder, privs, accNums, accSeqs, ctx.ChainID()) - s.Require().NoError(err) - - // Set height to non-zero value for GasMeter to be set - ctx = ctx.WithBlockHeight(1) - - return tx, txBytes, ctx, gasLimit -} - -func (s *MWTestSuite) TestSetup() { - testTx, _, ctx, gasLimit := s.setupGasTx() - txHandler := middleware.ComposeMiddlewares(noopTxHandler, middleware.GasTxMiddleware) - - testcases := []struct { - name string - tx sdk.Tx - expGasLimit uint64 - expErr bool - errorStr string - }{ - {"not a gas tx", txTest{}, 0, true, "Tx must be GasTx: tx parse error"}, - {"tx with its own gas limit", testTx, gasLimit, false, ""}, - } - for _, tc := range testcases { - s.Run(tc.name, func() { - res, _, err := txHandler.CheckTx(sdk.WrapSDKContext(ctx), tx.Request{Tx: tc.tx}, tx.RequestCheckTx{}) - _, simErr := txHandler.SimulateTx(sdk.WrapSDKContext(ctx), tx.Request{Tx: tc.tx}) - if tc.expErr { - s.Require().EqualError(err, tc.errorStr) - s.Require().EqualError(simErr, tc.errorStr) - } else { - s.Require().Nil(err, "SetUpContextMiddleware returned error") - s.Require().Nil(simErr, "SetUpContextMiddleware returned error") - s.Require().Equal(tc.expGasLimit, uint64(res.GasWanted)) - } - }) - } -} - -func (s *MWTestSuite) TestRecoverPanic() { - testTx, txBytes, ctx, gasLimit := s.setupGasTx() - txHandler := middleware.ComposeMiddlewares(outOfGasTxHandler, middleware.GasTxMiddleware, middleware.RecoveryTxMiddleware) - res, _, err := txHandler.CheckTx(sdk.WrapSDKContext(ctx), tx.Request{Tx: testTx, TxBytes: txBytes}, tx.RequestCheckTx{}) - s.Require().Error(err, "Did not return error on OutOfGas panic") - s.Require().True(errors.Is(sdkerrors.ErrOutOfGas, err), "Returned error is not an out of gas error") - s.Require().Equal(gasLimit, uint64(res.GasWanted)) - - txHandler = middleware.ComposeMiddlewares(outOfGasTxHandler, middleware.GasTxMiddleware) - s.Require().Panics(func() { - txHandler.CheckTx(sdk.WrapSDKContext(ctx), tx.Request{Tx: testTx, TxBytes: txBytes}, tx.RequestCheckTx{}) - }, "Recovered from non-Out-of-Gas panic") -} - -// customTxHandler is a test middleware that will run a custom function. -type customTxHandler struct { - fn func(context.Context, tx.Request) (tx.Response, error) -} - -var _ tx.Handler = customTxHandler{} - -func (h customTxHandler) DeliverTx(ctx context.Context, req tx.Request) (tx.Response, error) { - return h.fn(ctx, req) -} - -func (h customTxHandler) CheckTx(ctx context.Context, req tx.Request, _ tx.RequestCheckTx) (tx.Response, tx.ResponseCheckTx, error) { - res, err := h.fn(ctx, req) - return res, tx.ResponseCheckTx{}, err -} - -func (h customTxHandler) SimulateTx(ctx context.Context, req tx.Request) (tx.Response, error) { - return h.fn(ctx, req) -} - -// noopTxHandler is a test middleware that returns an empty response. -var noopTxHandler = customTxHandler{func(_ context.Context, _ tx.Request) (tx.Response, error) { - return tx.Response{}, nil -}} - -// outOfGasTxHandler is a test middleware that panics with an outOfGas error. -var outOfGasTxHandler = customTxHandler{func(ctx context.Context, _ tx.Request) (tx.Response, error) { - sdkCtx := sdk.UnwrapSDKContext(ctx) - overLimit := sdkCtx.GasMeter().Limit() + 1 - - // Should panic with outofgas error - sdkCtx.GasMeter().ConsumeGas(overLimit, "test panic") - - panic("not reached") -}} diff --git a/x/auth/middleware/index_events.go b/x/auth/middleware/index_events.go deleted file mode 100644 index 2dd831417c2..00000000000 --- a/x/auth/middleware/index_events.go +++ /dev/null @@ -1,61 +0,0 @@ -package middleware - -import ( - "context" - - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/types/tx" -) - -type indexEventsTxHandler struct { - // indexEvents defines the set of events in the form {eventType}.{attributeKey}, - // which informs Tendermint what to index. If empty, all events will be indexed. - indexEvents map[string]struct{} - next tx.Handler -} - -// NewIndexEventsTxMiddleware defines a middleware to optionally only index a -// subset of the emitted events inside the Tendermint events indexer. -func NewIndexEventsTxMiddleware(indexEvents map[string]struct{}) tx.Middleware { - return func(txHandler tx.Handler) tx.Handler { - return indexEventsTxHandler{ - indexEvents: indexEvents, - next: txHandler, - } - } -} - -var _ tx.Handler = indexEventsTxHandler{} - -// CheckTx implements tx.Handler.CheckTx method. -func (txh indexEventsTxHandler) CheckTx(ctx context.Context, req tx.Request, checkReq tx.RequestCheckTx) (tx.Response, tx.ResponseCheckTx, error) { - res, resCheckTx, err := txh.next.CheckTx(ctx, req, checkReq) - if err != nil { - return res, tx.ResponseCheckTx{}, err - } - - res.Events = sdk.MarkEventsToIndex(res.Events, txh.indexEvents) - return res, resCheckTx, nil -} - -// DeliverTx implements tx.Handler.DeliverTx method. -func (txh indexEventsTxHandler) DeliverTx(ctx context.Context, req tx.Request) (tx.Response, error) { - res, err := txh.next.DeliverTx(ctx, req) - if err != nil { - return res, err - } - - res.Events = sdk.MarkEventsToIndex(res.Events, txh.indexEvents) - return res, nil -} - -// SimulateTx implements tx.Handler.SimulateTx method. -func (txh indexEventsTxHandler) SimulateTx(ctx context.Context, req tx.Request) (tx.Response, error) { - res, err := txh.next.SimulateTx(ctx, req) - if err != nil { - return res, err - } - - res.Events = sdk.MarkEventsToIndex(res.Events, txh.indexEvents) - return res, nil -} diff --git a/x/auth/middleware/middleware.go b/x/auth/middleware/middleware.go deleted file mode 100644 index 812bd96038a..00000000000 --- a/x/auth/middleware/middleware.go +++ /dev/null @@ -1,115 +0,0 @@ -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 -// middleware order in the variadic arguments is from outer to inner. -// -// Example: Given a base tx.Handler H, and two middlewares A and B, the -// middleware stack: -// ``` -// A.pre -// B.pre -// H -// B.post -// A.post -// ``` -// is created by calling `ComposeMiddlewares(H, A, B)`. -func ComposeMiddlewares(txHandler tx.Handler, middlewares ...tx.Middleware) tx.Handler { - for i := len(middlewares) - 1; i >= 0; i-- { - txHandler = middlewares[i](txHandler) - } - - return txHandler -} - -type TxHandlerOptions struct { - Debug bool - - // TxDecoder is used to decode the raw tx bytes into a sdk.Tx. - TxDecoder sdk.TxDecoder - - // IndexEvents defines the set of events in the form {eventType}.{attributeKey}, - // which informs Tendermint what to index. If empty, all events will be indexed. - IndexEvents map[string]struct{} - - LegacyRouter sdk.Router - MsgServiceRouter *MsgServiceRouter - - AccountKeeper AccountKeeper - BankKeeper types.BankKeeper - FeegrantKeeper FeegrantKeeper - SignModeHandler authsigning.SignModeHandler - SigGasConsumer func(meter sdk.GasMeter, sig signing.SignatureV2, params types.Params) error - ExtensionOptionChecker ExtensionOptionChecker - TxFeeChecker TxFeeChecker -} - -// NewDefaultTxHandler defines a TxHandler middleware stacks that should work -// for most applications. -func NewDefaultTxHandler(options TxHandlerOptions) (tx.Handler, error) { - if options.TxDecoder == nil { - return nil, sdkerrors.Wrap(sdkerrors.ErrLogic, "txDecoder is required for middlewares") - } - - if options.AccountKeeper == nil { - return nil, sdkerrors.Wrap(sdkerrors.ErrLogic, "account keeper is required for middlewares") - } - - if options.BankKeeper == nil { - return nil, sdkerrors.Wrap(sdkerrors.ErrLogic, "bank keeper is required for middlewares") - } - - if options.SignModeHandler == nil { - return nil, sdkerrors.Wrap(sdkerrors.ErrLogic, "sign mode handler is required for middlewares") - } - - return ComposeMiddlewares( - NewRunMsgsTxHandler(options.MsgServiceRouter, options.LegacyRouter), - NewTxDecoderMiddleware(options.TxDecoder), - // Set a new GasMeter on sdk.Context. - // - // Make sure the Gas middleware is outside of all other middlewares - // that reads the GasMeter. In our case, the Recovery middleware reads - // the GasMeter to populate GasInfo. - GasTxMiddleware, - // Recover from panics. Panics outside of this middleware won't be - // caught, be careful! - RecoveryTxMiddleware, - // Choose which events to index in Tendermint. Make sure no events are - // emitted outside of this middleware. - NewIndexEventsTxMiddleware(options.IndexEvents), - // Reject all extension options other than the ones needed by the feemarket. - NewExtensionOptionsMiddleware(options.ExtensionOptionChecker), - ValidateBasicMiddleware, - TxTimeoutHeightMiddleware, - ValidateMemoMiddleware(options.AccountKeeper), - ConsumeTxSizeGasMiddleware(options.AccountKeeper), - // No gas should be consumed in any middleware above in a "post" handler part. See - // ComposeMiddlewares godoc for details. - // `DeductFeeMiddleware` and `IncrementSequenceMiddleware` should be put outside of `WithBranchedStore` middleware, - // so their storage writes are not discarded when tx fails. - DeductFeeMiddleware(options.AccountKeeper, options.BankKeeper, options.FeegrantKeeper, options.TxFeeChecker), - SetPubKeyMiddleware(options.AccountKeeper), - ValidateSigCountMiddleware(options.AccountKeeper), - SigGasConsumeMiddleware(options.AccountKeeper, options.SigGasConsumer), - SigVerificationMiddleware(options.AccountKeeper, options.SignModeHandler), - IncrementSequenceMiddleware(options.AccountKeeper), - // Creates a new MultiStore branch, discards downstream writes if the downstream returns error. - // These kinds of middlewares should be put under this: - // - Could return error after messages executed succesfully. - // - Storage writes should be discarded together when tx failed. - WithBranchedStore, - // Consume block gas. All middlewares whose gas consumption after their `next` handler - // should be accounted for, should go below this middleware. - ConsumeBlockGasMiddleware, - NewTipMiddleware(options.BankKeeper), - ), nil -} diff --git a/x/auth/middleware/msg_service_router_test.go b/x/auth/middleware/msg_service_router_test.go deleted file mode 100644 index ca6ec79b5b9..00000000000 --- a/x/auth/middleware/msg_service_router_test.go +++ /dev/null @@ -1,55 +0,0 @@ -package middleware_test - -import ( - "testing" - - "github.com/stretchr/testify/require" - - "github.com/cosmos/cosmos-sdk/simapp" - "github.com/cosmos/cosmos-sdk/testutil/testdata" - "github.com/cosmos/cosmos-sdk/x/auth/middleware" -) - -func TestRegisterMsgService(t *testing.T) { - // Create an encoding config that doesn't register testdata Msg services. - encCfg := simapp.MakeTestEncodingConfig() - msr := middleware.NewMsgServiceRouter(encCfg.InterfaceRegistry) - require.Panics(t, func() { - testdata.RegisterMsgServer( - msr, - testdata.MsgServerImpl{}, - ) - }) - - // Register testdata Msg services, and rerun `RegisterService`. - testdata.RegisterInterfaces(encCfg.InterfaceRegistry) - require.NotPanics(t, func() { - testdata.RegisterMsgServer( - msr, - testdata.MsgServerImpl{}, - ) - }) -} - -func TestRegisterMsgServiceTwice(t *testing.T) { - // Setup baseapp. - encCfg := simapp.MakeTestEncodingConfig() - msr := middleware.NewMsgServiceRouter(encCfg.InterfaceRegistry) - testdata.RegisterInterfaces(encCfg.InterfaceRegistry) - - // First time registering service shouldn't panic. - require.NotPanics(t, func() { - testdata.RegisterMsgServer( - msr, - testdata.MsgServerImpl{}, - ) - }) - - // Second time should panic. - require.Panics(t, func() { - testdata.RegisterMsgServer( - msr, - testdata.MsgServerImpl{}, - ) - }) -} diff --git a/x/auth/middleware/recovery.go b/x/auth/middleware/recovery.go deleted file mode 100644 index 563389d85fc..00000000000 --- a/x/auth/middleware/recovery.go +++ /dev/null @@ -1,78 +0,0 @@ -package middleware - -import ( - "context" - "runtime/debug" - - sdk "github.com/cosmos/cosmos-sdk/types" - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" - "github.com/cosmos/cosmos-sdk/types/tx" -) - -type recoveryTxHandler struct { - next tx.Handler -} - -// RecoveryTxMiddleware defines a middleware that catches all panics that -// happen in inner middlewares. -// -// Be careful, it won't catch any panics happening outside! -func RecoveryTxMiddleware(txh tx.Handler) tx.Handler { - return recoveryTxHandler{next: txh} -} - -var _ tx.Handler = recoveryTxHandler{} - -// CheckTx implements tx.Handler.CheckTx method. -func (txh recoveryTxHandler) CheckTx(ctx context.Context, req tx.Request, checkReq tx.RequestCheckTx) (res tx.Response, resCheckTx tx.ResponseCheckTx, err error) { - sdkCtx := sdk.UnwrapSDKContext(ctx) - // Panic recovery. - defer func() { - if r := recover(); r != nil { - err = handleRecovery(r, sdkCtx) - } - }() - - return txh.next.CheckTx(ctx, req, checkReq) -} - -// DeliverTx implements tx.Handler.DeliverTx method. -func (txh recoveryTxHandler) DeliverTx(ctx context.Context, req tx.Request) (res tx.Response, err error) { - sdkCtx := sdk.UnwrapSDKContext(ctx) - // Panic recovery. - defer func() { - if r := recover(); r != nil { - err = handleRecovery(r, sdkCtx) - } - }() - - return txh.next.DeliverTx(ctx, req) -} - -// SimulateTx implements tx.Handler.SimulateTx method. -func (txh recoveryTxHandler) SimulateTx(ctx context.Context, req tx.Request) (res tx.Response, err error) { - sdkCtx := sdk.UnwrapSDKContext(ctx) - // Panic recovery. - defer func() { - if r := recover(); r != nil { - err = handleRecovery(r, sdkCtx) - } - }() - - return txh.next.SimulateTx(ctx, req) -} - -func handleRecovery(r interface{}, sdkCtx sdk.Context) error { - switch r := r.(type) { - case sdk.ErrorOutOfGas: - return sdkerrors.Wrapf(sdkerrors.ErrOutOfGas, - "out of gas in location: %v; gasWanted: %d, gasUsed: %d", - r.Descriptor, sdkCtx.GasMeter().Limit(), sdkCtx.GasMeter().GasConsumed(), - ) - - default: - return sdkerrors.ErrPanic.Wrapf( - "recovered: %v\nstack:\n%v", r, string(debug.Stack()), - ) - } -} diff --git a/x/auth/middleware/run_msgs.go b/x/auth/middleware/run_msgs.go deleted file mode 100644 index d333e591899..00000000000 --- a/x/auth/middleware/run_msgs.go +++ /dev/null @@ -1,117 +0,0 @@ -package middleware - -import ( - "context" - "strings" - - 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" - "github.com/cosmos/cosmos-sdk/x/auth/migrations/legacytx" -) - -type runMsgsTxHandler struct { - legacyRouter sdk.Router // router for redirecting legacy Msgs - msgServiceRouter *MsgServiceRouter // router for redirecting Msg service messages -} - -func NewRunMsgsTxHandler(msr *MsgServiceRouter, legacyRouter sdk.Router) tx.Handler { - return runMsgsTxHandler{ - legacyRouter: legacyRouter, - msgServiceRouter: msr, - } -} - -var _ tx.Handler = runMsgsTxHandler{} - -// CheckTx implements tx.Handler.CheckTx method. -func (txh runMsgsTxHandler) CheckTx(ctx context.Context, req tx.Request, checkReq tx.RequestCheckTx) (tx.Response, tx.ResponseCheckTx, error) { - // Don't run Msgs during CheckTx. - return tx.Response{}, tx.ResponseCheckTx{}, nil -} - -// DeliverTx implements tx.Handler.DeliverTx method. -func (txh runMsgsTxHandler) DeliverTx(ctx context.Context, req tx.Request) (tx.Response, error) { - return txh.runMsgs(sdk.UnwrapSDKContext(ctx), req.Tx.GetMsgs(), req.TxBytes) -} - -// SimulateTx implements tx.Handler.SimulateTx method. -func (txh runMsgsTxHandler) SimulateTx(ctx context.Context, req tx.Request) (tx.Response, error) { - return txh.runMsgs(sdk.UnwrapSDKContext(ctx), req.Tx.GetMsgs(), req.TxBytes) -} - -// runMsgs iterates through a list of messages and executes them with the provided -// Context and execution mode. Messages will only be executed during simulation -// and DeliverTx. An error is returned if any single message fails or if a -// Handler does not exist for a given message route. Otherwise, a reference to a -// Result is returned. The caller must not commit state if an error is returned. -func (txh runMsgsTxHandler) runMsgs(sdkCtx sdk.Context, msgs []sdk.Msg, txBytes []byte) (tx.Response, error) { - // Attempt to execute all messages and only update state if all messages pass - // and we're in DeliverTx. Note, runMsgs will never return a reference to a - // Result if any single message fails or does not have a registered Handler. - msgLogs := make(sdk.ABCIMessageLogs, 0, len(msgs)) - events := sdkCtx.EventManager().Events() - msgResponses := make([]*codectypes.Any, len(msgs)) - - // NOTE: GasWanted is determined by the Gas TxHandler and GasUsed by the GasMeter. - for i, msg := range msgs { - var ( - msgResult *sdk.Result - eventMsgName string // name to use as value in event `message.action` - err error - ) - - if handler := txh.msgServiceRouter.Handler(msg); handler != nil { - // ADR 031 request type routing - msgResult, err = handler(sdkCtx, msg) - eventMsgName = sdk.MsgTypeURL(msg) - } else if legacyMsg, ok := msg.(legacytx.LegacyMsg); ok { - // legacy sdk.Msg routing - // Assuming that the app developer has migrated all their Msgs to - // proto messages and has registered all `Msg services`, then this - // path should never be called, because all those Msgs should be - // registered within the `MsgServiceRouter` already. - msgRoute := legacyMsg.Route() - eventMsgName = legacyMsg.Type() - handler := txh.legacyRouter.Route(sdkCtx, msgRoute) - if handler == nil { - return tx.Response{}, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "unrecognized message route: %s; message index: %d", msgRoute, i) - } - - msgResult, err = handler(sdkCtx, msg) - } else { - return tx.Response{}, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "can't route message %+v", msg) - } - - if err != nil { - return tx.Response{}, sdkerrors.Wrapf(err, "failed to execute message; message index: %d", i) - } - - msgEvents := sdk.Events{ - sdk.NewEvent(sdk.EventTypeMessage, sdk.NewAttribute(sdk.AttributeKeyAction, eventMsgName)), - } - msgEvents = msgEvents.AppendEvents(msgResult.GetEvents()) - - // append message events, data and logs - // - // Note: Each message result's data must be length-prefixed in order to - // separate each result. - events = events.AppendEvents(msgEvents) - - // Each individual sdk.Result has exactly one Msg response. We aggregate here. - msgResponse := msgResult.MsgResponses[0] - if msgResponse == nil { - return tx.Response{}, sdkerrors.ErrLogic.Wrapf("got nil Msg response at index %d for msg %s", i, sdk.MsgTypeURL(msg)) - } - msgResponses[i] = msgResponse - msgLogs = append(msgLogs, sdk.NewABCIMessageLog(uint32(i), msgResult.Log, msgEvents)) - } - - return tx.Response{ - // GasInfo will be populated by the Gas middleware. - Log: strings.TrimSpace(msgLogs.String()), - Events: events.ToABCIEvents(), - MsgResponses: msgResponses, - }, nil -} diff --git a/x/auth/middleware/run_msgs_test.go b/x/auth/middleware/run_msgs_test.go deleted file mode 100644 index 143a2197538..00000000000 --- a/x/auth/middleware/run_msgs_test.go +++ /dev/null @@ -1,40 +0,0 @@ -package middleware_test - -import ( - "fmt" - - "github.com/gogo/protobuf/proto" - - cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" - "github.com/cosmos/cosmos-sdk/testutil/testdata" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/types/tx" - "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} - testTx, _, err := s.createTestTx(txBuilder, privs, accNums, accSeqs, ctx.ChainID()) - s.Require().NoError(err) - txBytes, err := s.clientCtx.TxConfig.TxEncoder()(testTx) - s.Require().NoError(err) - - // DeliverTx - res, err := txHandler.DeliverTx(sdk.WrapSDKContext(ctx), tx.Request{Tx: testTx, TxBytes: txBytes}) - s.Require().NoError(err) - s.Require().Len(res.MsgResponses, 1) - s.Require().Equal(fmt.Sprintf("/%s", proto.MessageName(&testdata.MsgCreateDogResponse{})), res.MsgResponses[0].TypeUrl) - - // SimulateTx - _, err = txHandler.SimulateTx(sdk.WrapSDKContext(ctx), tx.Request{Tx: testTx, TxBytes: txBytes}) - s.Require().NoError(err) -} diff --git a/x/auth/middleware/testutil_test.go b/x/auth/middleware/testutil_test.go deleted file mode 100644 index f493a463fa2..00000000000 --- a/x/auth/middleware/testutil_test.go +++ /dev/null @@ -1,222 +0,0 @@ -package middleware_test - -import ( - "errors" - "fmt" - "testing" - - "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" - "github.com/cosmos/cosmos-sdk/codec" - codectypes "github.com/cosmos/cosmos-sdk/codec/types" - 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" -) - -// testAccount represents an account used in the tests in x/auth/middleware. -type testAccount struct { - acc authtypes.AccountI - priv cryptotypes.PrivKey - accNum uint64 -} - -// MWTestSuite is a test suite to be used with middleware tests. -type MWTestSuite struct { - suite.Suite - - 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{Height: app.LastBlockHeight() + 1}).WithBlockGasMeter(sdk.NewInfiniteGasMeter()) - app.AccountKeeper.SetParams(ctx, authtypes.DefaultParams()) - - return app, ctx -} - -// setupTest setups a new test, with new app and context. -func (s *MWTestSuite) SetupTest(isCheckTx bool) sdk.Context { - var ctx sdk.Context - s.app, ctx = createTestApp(s.T(), isCheckTx) - - // 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) - - s.clientCtx = client.Context{}. - 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) { - any, err := codectypes.NewAnyWithValue(msg) - if err != nil { - return nil, err - } - - return &sdk.Result{ - MsgResponses: []*codectypes.Any{any}, - }, 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, - TxDecoder: s.clientCtx.TxConfig.TxDecoder(), - }) - s.Require().NoError(err) - s.txHandler = txHandler - - return ctx -} - -// createTestAccounts creates `numAccs` accounts, and return all relevant -// information about them including their private keys. -func (s *MWTestSuite) createTestAccounts(ctx sdk.Context, numAccs int, coins sdk.Coins) []testAccount { - var accounts []testAccount - - for i := 0; i < numAccs; i++ { - priv, _, addr := testdata.KeyTestPubAddr() - acc := s.app.AccountKeeper.NewAccountWithAddress(ctx, addr) - accNum := uint64(i) - err := acc.SetAccountNumber(accNum) - s.Require().NoError(err) - s.app.AccountKeeper.SetAccount(ctx, acc) - err = s.app.BankKeeper.MintCoins(ctx, minttypes.ModuleName, coins) - s.Require().NoError(err) - - err = s.app.BankKeeper.SendCoinsFromModuleToAccount(ctx, minttypes.ModuleName, addr, coins) - s.Require().NoError(err) - - accounts = append(accounts, testAccount{acc, priv, accNum}) - } - - return accounts -} - -// createTestTx is a helper function to create a tx given multiple inputs. -func (s *MWTestSuite) createTestTx(txBuilder client.TxBuilder, privs []cryptotypes.PrivKey, accNums []uint64, accSeqs []uint64, chainID string) (xauthsigning.Tx, []byte, 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: s.clientCtx.TxConfig.SignModeHandler().DefaultMode(), - Signature: nil, - }, - Sequence: accSeqs[i], - } - - sigsV2 = append(sigsV2, sigV2) - } - err := txBuilder.SetSignatures(sigsV2...) - if err != nil { - return nil, 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{ - Address: sdk.AccAddress(priv.PubKey().Address()).String(), - ChainID: chainID, - AccountNumber: accNums[i], - Sequence: accSeqs[i], - PubKey: priv.PubKey(), - } - sigV2, err := tx.SignWithPrivKey( - s.clientCtx.TxConfig.SignModeHandler().DefaultMode(), signerData, - txBuilder, priv, s.clientCtx.TxConfig, accSeqs[i]) - if err != nil { - return nil, nil, err - } - - sigsV2 = append(sigsV2, sigV2) - } - err = txBuilder.SetSignatures(sigsV2...) - if err != nil { - return nil, nil, err - } - - txBytes, err := s.clientCtx.TxConfig.TxEncoder()(txBuilder.GetTx()) - if err != nil { - return nil, nil, err - } - - 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. - testTx, _, txErr := s.createTestTx(txBuilder, privs, accNums, accSeqs, chainID) - newCtx, txHandlerErr := s.txHandler.DeliverTx(sdk.WrapSDKContext(ctx), txtypes.Request{Tx: testTx}) - - 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/middleware/tips.go b/x/auth/middleware/tips.go deleted file mode 100644 index 68749b02064..00000000000 --- a/x/auth/middleware/tips.go +++ /dev/null @@ -1,69 +0,0 @@ -package middleware - -import ( - "context" - - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/types/tx" - "github.com/cosmos/cosmos-sdk/x/auth/types" -) - -type tipsTxHandler struct { - next tx.Handler - bankKeeper types.BankKeeper -} - -// NewTipMiddleware returns a new middleware for handling transactions with -// tips. -func NewTipMiddleware(bankKeeper types.BankKeeper) tx.Middleware { - return func(txh tx.Handler) tx.Handler { - return tipsTxHandler{txh, bankKeeper} - } -} - -var _ tx.Handler = tipsTxHandler{} - -// CheckTx implements tx.Handler.CheckTx. -func (txh tipsTxHandler) CheckTx(ctx context.Context, req tx.Request, checkTx tx.RequestCheckTx) (tx.Response, tx.ResponseCheckTx, error) { - res, resCheckTx, err := txh.next.CheckTx(ctx, req, checkTx) - res, err = txh.transferTip(ctx, req, res, err) - - return res, resCheckTx, err -} - -// DeliverTx implements tx.Handler.DeliverTx. -func (txh tipsTxHandler) DeliverTx(ctx context.Context, req tx.Request) (tx.Response, error) { - res, err := txh.next.DeliverTx(ctx, req) - - return txh.transferTip(ctx, req, res, err) -} - -// SimulateTx implements tx.Handler.SimulateTx method. -func (txh tipsTxHandler) SimulateTx(ctx context.Context, req tx.Request) (tx.Response, error) { - res, err := txh.next.SimulateTx(ctx, req) - - return txh.transferTip(ctx, req, res, err) -} - -// transferTip transfers the tip from the tipper to the fee payer. -func (txh tipsTxHandler) transferTip(ctx context.Context, req tx.Request, res tx.Response, err error) (tx.Response, error) { - tipTx, ok := req.Tx.(tx.TipTx) - - // No-op if the tx doesn't have tips. - if !ok || tipTx.GetTip() == nil { - return res, err - } - - sdkCtx := sdk.UnwrapSDKContext(ctx) - tipper, err := sdk.AccAddressFromBech32(tipTx.GetTip().Tipper) - if err != nil { - return tx.Response{}, err - } - - err = txh.bankKeeper.SendCoins(sdkCtx, tipper, tipTx.FeePayer(), tipTx.GetTip().Amount) - if err != nil { - return tx.Response{}, err - } - - return res, nil -} diff --git a/x/auth/middleware/tips_test.go b/x/auth/middleware/tips_test.go deleted file mode 100644 index 28acbee205b..00000000000 --- a/x/auth/middleware/tips_test.go +++ /dev/null @@ -1,209 +0,0 @@ -package middleware_test - -import ( - "time" - - abci "github.com/tendermint/tendermint/abci/types" - tmproto "github.com/tendermint/tendermint/proto/tendermint/types" - - "github.com/cosmos/cosmos-sdk/client" - clienttx "github.com/cosmos/cosmos-sdk/client/tx" - cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/types/tx" - "github.com/cosmos/cosmos-sdk/types/tx/signing" - authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing" - banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" - govtypes "github.com/cosmos/cosmos-sdk/x/gov/types/v1" - minttypes "github.com/cosmos/cosmos-sdk/x/mint/types" -) - -var ( - initialRegens = sdk.NewCoins(sdk.NewCoin("regen", sdk.NewInt(1000))) - initialAtoms = sdk.NewCoins(sdk.NewCoin("atom", sdk.NewInt(1000))) -) - -// setupAcctsForTips sets up 2 accounts: -// - tipper has 1000 regens -// - feePayer has 1000 atoms and 1000 regens -func (s *MWTestSuite) setupAcctsForTips(ctx sdk.Context) (sdk.Context, []testAccount) { - accts := s.createTestAccounts(ctx, 2, initialRegens) - feePayer := accts[1] - err := s.app.BankKeeper.MintCoins(ctx, minttypes.ModuleName, initialAtoms) - s.Require().NoError(err) - err = s.app.BankKeeper.SendCoinsFromModuleToAccount(ctx, minttypes.ModuleName, feePayer.acc.GetAddress(), initialAtoms) - s.Require().NoError(err) - - // Create dummy proposal for tipper to vote on. - prop, err := govtypes.NewProposal([]sdk.Msg{banktypes.NewMsgSend(accts[0].acc.GetAddress(), accts[0].acc.GetAddress(), initialRegens)}, 1, "", time.Now(), time.Now().Add(time.Hour)) - s.Require().NoError(err) - s.app.GovKeeper.SetProposal(ctx, prop) - s.app.GovKeeper.ActivateVotingPeriod(ctx, prop) - - // Move to next block to commit previous data to state. - s.app.EndBlock(abci.RequestEndBlock{Height: ctx.BlockHeight()}) - s.app.Commit() - - ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1) - s.app.BeginBlock(abci.RequestBeginBlock{Header: tmproto.Header{Height: ctx.BlockHeight()}}) - - return ctx, accts -} - -func (s *MWTestSuite) TestTips() { - var msg sdk.Msg - - testcases := []struct { - name string - tip sdk.Coins - fee sdk.Coins - gasLimit uint64 - expErr bool - expErrStr string - }{ - { - "wrong tip denom", - sdk.NewCoins(sdk.NewCoin("foobar", sdk.NewInt(1000))), initialAtoms, 200000, - true, "0foobar is smaller than 1000foobar: insufficient funds", - }, - { - "insufficient tip from tipper", - sdk.NewCoins(sdk.NewCoin("regen", sdk.NewInt(5000))), initialAtoms, 200000, - true, "1000regen is smaller than 5000regen: insufficient funds", - }, - { - "insufficient fees from feePayer", - initialRegens, sdk.NewCoins(sdk.NewCoin("atom", sdk.NewInt(5000))), 200000, - true, "1000atom is smaller than 5000atom: insufficient funds: insufficient funds", - }, - { - "insufficient gas", - initialRegens, initialAtoms, 100, - true, "out of gas in location: ReadFlat; gasWanted: 100, gasUsed: 1000: out of gas", - }, - { - "happy case", - initialRegens, initialAtoms, 200000, - false, "", - }, - } - - for _, tc := range testcases { - tc := tc - s.Run(tc.name, func() { - ctx := s.SetupTest(false) // reset - ctx, accts := s.setupAcctsForTips(ctx) - tipper, feePayer := accts[0], accts[1] - - msg = govtypes.NewMsgVote(tipper.acc.GetAddress(), 1, govtypes.OptionYes, "") - - auxSignerData := s.mkTipperAuxSignerData(tipper.priv, msg, tc.tip, signing.SignMode_SIGN_MODE_DIRECT_AUX, tipper.accNum, 0, ctx.ChainID()) - feePayerTxBuilder := s.mkFeePayerTxBuilder(s.clientCtx, auxSignerData, feePayer.priv, signing.SignMode_SIGN_MODE_DIRECT, tx.Fee{Amount: tc.fee, GasLimit: tc.gasLimit}, feePayer.accNum, 0, ctx.ChainID()) - - _, res, err := s.app.SimDeliver(s.clientCtx.TxConfig.TxEncoder(), feePayerTxBuilder.GetTx()) - - if tc.expErr { - s.Require().Error(err) - s.Require().Contains(err.Error(), tc.expErrStr) - } else { - s.Require().NoError(err) - s.Require().NotNil(res) - - // Move to next block to commit previous data to state. - s.app.EndBlock(abci.RequestEndBlock{Height: ctx.BlockHeight()}) - s.app.Commit() - - ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1) - s.app.BeginBlock(abci.RequestBeginBlock{Header: tmproto.Header{Height: ctx.BlockHeight()}}) - - // Make sure tip is correctly transferred to feepayer, and fee is paid. - expTipperRegens := initialRegens.Sub(tc.tip...) - expFeePayerRegens := initialRegens.Add(tc.tip...) - expFeePayerAtoms := initialAtoms.Sub(tc.fee...) - s.Require().True(expTipperRegens.AmountOf("regen").Equal(s.app.BankKeeper.GetBalance(ctx, tipper.acc.GetAddress(), "regen").Amount)) - s.Require().True(expFeePayerRegens.AmountOf("regen").Equal(s.app.BankKeeper.GetBalance(ctx, feePayer.acc.GetAddress(), "regen").Amount)) - s.Require().True(expFeePayerAtoms.AmountOf("atom").Equal(s.app.BankKeeper.GetBalance(ctx, feePayer.acc.GetAddress(), "atom").Amount)) - // Make sure MsgVote has been submitted by tipper. - votes := s.app.GovKeeper.GetAllVotes(ctx) - s.Require().Len(votes, 1) - s.Require().Equal(tipper.acc.GetAddress().String(), votes[0].Voter) - } - }) - } -} - -func (s *MWTestSuite) mkTipperAuxSignerData( - tipperPriv cryptotypes.PrivKey, msg sdk.Msg, tip sdk.Coins, - signMode signing.SignMode, accNum, accSeq uint64, chainID string, -) tx.AuxSignerData { - tipperAddr := sdk.AccAddress(tipperPriv.PubKey().Address()).String() - b := clienttx.NewAuxTxBuilder() - b.SetAddress(tipperAddr) - b.SetAccountNumber(accNum) - b.SetSequence(accSeq) - err := b.SetMsgs(msg) - s.Require().NoError(err) - b.SetTip(&tx.Tip{Amount: tip, Tipper: tipperAddr}) - err = b.SetSignMode(signMode) - s.Require().NoError(err) - b.SetSequence(accSeq) - err = b.SetPubKey(tipperPriv.PubKey()) - s.Require().NoError(err) - b.SetChainID(chainID) - - signBz, err := b.GetSignBytes() - s.Require().NoError(err) - sig, err := tipperPriv.Sign(signBz) - s.Require().NoError(err) - b.SetSignature(sig) - - auxSignerData, err := b.GetAuxSignerData() - s.Require().NoError(err) - - return auxSignerData -} - -func (s *MWTestSuite) mkFeePayerTxBuilder( - clientCtx client.Context, - auxSignerData tx.AuxSignerData, - feePayerPriv cryptotypes.PrivKey, signMode signing.SignMode, - fee tx.Fee, accNum, accSeq uint64, chainID string, -) client.TxBuilder { - txBuilder := clientCtx.TxConfig.NewTxBuilder() - err := txBuilder.AddAuxSignerData(auxSignerData) - s.Require().NoError(err) - txBuilder.SetFeePayer(sdk.AccAddress(feePayerPriv.PubKey().Address())) - txBuilder.SetFeeAmount(fee.Amount) - txBuilder.SetGasLimit(fee.GasLimit) - - // Calling SetSignatures with empty sig to populate AuthInfo. - tipperSigsV2, err := auxSignerData.GetSignatureV2() - s.Require().NoError(err) - feePayerSigV2 := signing.SignatureV2{ - PubKey: feePayerPriv.PubKey(), - Data: &signing.SingleSignatureData{ - SignMode: signMode, - Signature: nil, - }, - } - sigsV2 := append([]signing.SignatureV2{tipperSigsV2}, feePayerSigV2) - txBuilder.SetSignatures(sigsV2...) - - // Actually sign the data. - signerData := authsigning.SignerData{ - Address: sdk.AccAddress(feePayerPriv.PubKey().Address()).String(), - ChainID: chainID, - AccountNumber: accNum, - Sequence: accSeq, - PubKey: feePayerPriv.PubKey(), - } - feePayerSigV2, err = clienttx.SignWithPrivKey( - signMode, signerData, - txBuilder, feePayerPriv, clientCtx.TxConfig, accSeq) - s.Require().NoError(err) - sigsV2 = append([]signing.SignatureV2{tipperSigsV2}, feePayerSigV2) - err = txBuilder.SetSignatures(sigsV2...) - s.Require().NoError(err) - - return txBuilder -} diff --git a/x/auth/middleware/tx.go b/x/auth/middleware/tx.go deleted file mode 100644 index f543524f3fa..00000000000 --- a/x/auth/middleware/tx.go +++ /dev/null @@ -1,77 +0,0 @@ -package middleware - -import ( - "context" - - sdk "github.com/cosmos/cosmos-sdk/types" - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" - "github.com/cosmos/cosmos-sdk/types/tx" -) - -type txDecoderHandler struct { - next tx.Handler - txDecoder sdk.TxDecoder -} - -// NewTxDecoderMiddleware creates a new middleware that will decode tx bytes -// into a sdk.Tx. As input request, at least one of Tx or TxBytes must be set. -// If only TxBytes is set, then TxDecoderMiddleware will populate the Tx field. -// If only Tx is set, then TxBytes will be left empty, but some middlewares -// such as signature verification might fail. -func NewTxDecoderMiddleware(txDecoder sdk.TxDecoder) tx.Middleware { - return func(txh tx.Handler) tx.Handler { - return txDecoderHandler{next: txh, txDecoder: txDecoder} - } -} - -var _ tx.Handler = gasTxHandler{} - -// CheckTx implements tx.Handler.CheckTx. -func (h txDecoderHandler) CheckTx(ctx context.Context, req tx.Request, checkReq tx.RequestCheckTx) (tx.Response, tx.ResponseCheckTx, error) { - newReq, err := h.populateReq(req) - if err != nil { - return tx.Response{}, tx.ResponseCheckTx{}, err - } - - return h.next.CheckTx(ctx, newReq, checkReq) -} - -// DeliverTx implements tx.Handler.DeliverTx. -func (h txDecoderHandler) DeliverTx(ctx context.Context, req tx.Request) (tx.Response, error) { - newReq, err := h.populateReq(req) - if err != nil { - return tx.Response{}, err - } - - return h.next.DeliverTx(ctx, newReq) -} - -// SimulateTx implements tx.Handler.SimulateTx method. -func (h txDecoderHandler) SimulateTx(ctx context.Context, req tx.Request) (tx.Response, error) { - newReq, err := h.populateReq(req) - if err != nil { - return tx.Response{}, err - } - - return h.next.SimulateTx(ctx, newReq) -} - -// populateReq takes a tx.Request, and if its Tx field is not set, then -// decodes the TxBytes and populates the decoded Tx field. It leaves -// req.TxBytes untouched. -func (h txDecoderHandler) populateReq(req tx.Request) (tx.Request, error) { - if len(req.TxBytes) == 0 && req.Tx == nil { - return tx.Request{}, sdkerrors.ErrInvalidRequest.Wrap("got empty tx request") - } - - sdkTx := req.Tx - var err error - if len(req.TxBytes) != 0 { - sdkTx, err = h.txDecoder(req.TxBytes) - if err != nil { - return tx.Request{}, err - } - } - - return tx.Request{Tx: sdkTx, TxBytes: req.TxBytes}, nil -} diff --git a/x/auth/middleware/tx_test.go b/x/auth/middleware/tx_test.go deleted file mode 100644 index 9e4ce65ed64..00000000000 --- a/x/auth/middleware/tx_test.go +++ /dev/null @@ -1,64 +0,0 @@ -package middleware_test - -import ( - "context" - - cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" - "github.com/cosmos/cosmos-sdk/testutil/testdata" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/types/tx" - "github.com/cosmos/cosmos-sdk/x/auth/middleware" -) - -func (s *MWTestSuite) TestTxDecoderMiddleware() { - ctx := s.SetupTest(true) // setup - require := s.Require() - - // Create a tx. - priv1, _, addr1 := testdata.KeyTestPubAddr() - txBuilder := s.clientCtx.TxConfig.NewTxBuilder() - err := txBuilder.SetMsgs(testdata.NewTestMsg(addr1)) - require.NoError(err) - sdkTx, txBz, err := s.createTestTx(txBuilder, []cryptotypes.PrivKey{priv1}, []uint64{1}, []uint64{0}, ctx.ChainID()) - require.NoError(err) - - // Create a custom tx.Handler that checks that the req.Tx field is - // correctly populated. - txReqChecker := customTxHandler{func(c context.Context, r tx.Request) (tx.Response, error) { - require.NotNil(r.Tx) - require.Equal(sdkTx.GetMsgs()[0], r.Tx.GetMsgs()[0]) - return tx.Response{}, nil - }} - - testcases := []struct { - name string - req tx.Request - expErr bool - }{ - {"empty tx bz", tx.Request{}, true}, - {"tx bz and tx both given as inputs", tx.Request{Tx: sdkTx, TxBytes: txBz}, false}, - {"tx bz only given as input", tx.Request{TxBytes: txBz}, false}, - {"tx only given as input", tx.Request{Tx: sdkTx}, false}, - } - for _, tc := range testcases { - s.Run(tc.name, func() { - txHandler := middleware.ComposeMiddlewares( - txReqChecker, - middleware.NewTxDecoderMiddleware(s.clientCtx.TxConfig.TxDecoder()), - ) - - // DeliverTx - _, err := txHandler.DeliverTx(sdk.WrapSDKContext(ctx), tc.req) - - // SimulateTx - _, simErr := txHandler.SimulateTx(sdk.WrapSDKContext(ctx), tc.req) - if tc.expErr { - require.Error(err) - require.Error(simErr) - } else { - require.NoError(err) - require.NoError(simErr) - } - }) - } -} diff --git a/x/auth/middleware/validator_tx_fee.go b/x/auth/middleware/validator_tx_fee.go deleted file mode 100644 index 687608da55a..00000000000 --- a/x/auth/middleware/validator_tx_fee.go +++ /dev/null @@ -1,59 +0,0 @@ -package middleware - -import ( - "math" - - sdk "github.com/cosmos/cosmos-sdk/types" - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" -) - -// checkTxFeeWithValidatorMinGasPrices implements the default fee logic, where the minimum price per -// unit of gas is fixed and set by each validator, can the tx priority is computed from the gas price. -func checkTxFeeWithValidatorMinGasPrices(ctx sdk.Context, tx sdk.Tx) (sdk.Coins, int64, error) { - feeTx, ok := tx.(sdk.FeeTx) - if !ok { - return nil, 0, sdkerrors.Wrap(sdkerrors.ErrTxDecode, "Tx must be a FeeTx") - } - - feeCoins := feeTx.GetFee() - gas := feeTx.GetGas() - - // Ensure that the provided fees meet a minimum threshold for the validator, - // This is only for local mempool purposes, if this is a DeliverTx, the `MinGasPrices` should be zero. - 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 nil, 0, sdkerrors.Wrapf(sdkerrors.ErrInsufficientFee, "insufficient fees; got: %s required: %s", feeCoins, requiredFees) - } - } - - priority := getTxPriority(feeCoins) - return feeCoins, priority, nil -} - -// getTxPriority returns a naive tx priority based on the amount of the smallest denomination of the fee -// provided in a transaction. -func getTxPriority(fee sdk.Coins) int64 { - var priority int64 - for _, c := range fee { - p := int64(math.MaxInt64) - if c.Amount.IsInt64() { - p = c.Amount.Int64() - } - if priority == 0 || p < priority { - priority = p - } - } - - return priority -} diff --git a/x/auth/signing/verify_test.go b/x/auth/signing/verify_test.go index 3d138e1f774..db91bc15e06 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/middleware" + "github.com/cosmos/cosmos-sdk/x/auth/ante" "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,7 @@ 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 := middleware.GetSignerAcc(ctx, app.AccountKeeper, addr) + acc, err := ante.GetSignerAcc(ctx, app.AccountKeeper, addr) require.NoError(t, err) require.NoError(t, testutil.FundAccount(app.BankKeeper, ctx, addr, balances)) diff --git a/x/auth/tx/builder.go b/x/auth/tx/builder.go index edcfd630ca8..052da8785ee 100644 --- a/x/auth/tx/builder.go +++ b/x/auth/tx/builder.go @@ -11,7 +11,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/middleware" + "github.com/cosmos/cosmos-sdk/x/auth/ante" authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing" ) @@ -37,7 +37,7 @@ var ( _ authsigning.Tx = &wrapper{} _ client.TxBuilder = &wrapper{} _ tx.TipTx = &wrapper{} - _ middleware.HasExtensionOptionsTx = &wrapper{} + _ ante.HasExtensionOptionsTx = &wrapper{} _ ExtensionOptionsTxBuilder = &wrapper{} _ tx.TipTx = &wrapper{} ) diff --git a/x/auth/tx/service_test.go b/x/auth/tx/service_test.go index 28481c57c38..019007cca92 100644 --- a/x/auth/tx/service_test.go +++ b/x/auth/tx/service_test.go @@ -79,7 +79,7 @@ func (s *IntegrationTestSuite) SetupSuite() { ) s.Require().NoError(err) s.Require().NoError(val.ClientCtx.Codec.UnmarshalJSON(out.Bytes(), &s.txRes)) - s.Require().Equal(uint32(0), s.txRes.Code) + s.Require().Equal(uint32(0), s.txRes.Code, s.txRes) out, err = bankcli.MsgSendExec( val.ClientCtx, @@ -145,16 +145,8 @@ func (s IntegrationTestSuite) TestSimulateTx_GRPC() { } else { s.Require().NoError(err) // Check the result and gas used are correct. - // - // The 13 events are: - // - Sending Fee to the pool: coin_spent, coin_received, transfer and message.sender= - // - tx.* events: tx.fee, tx.acc_seq, tx.signature - // - Sending Amount to recipient: coin_spent, coin_received, transfer and message.sender= - // - Msg events: message.module=bank and message.action=/cosmos.bank.v1beta1.MsgSend - s.Require().Equal(len(res.GetResult().GetEvents()), 13) - s.Require().Len(res.GetResult().MsgResponses, 1) - // Check the result and gas used are correct. - s.Require().True(res.GetGasInfo().GetGasUsed() > 0) // Gas used sometimes change, just check it's not empty. + s.Require().Equal(len(res.GetResult().GetEvents()), 6) // 1 coin recv 1 coin spent, 1 transfer, 3 messages. + s.Require().True(res.GetGasInfo().GetGasUsed() > 0) // Gas used sometimes change, just check it's not empty. } }) } @@ -194,9 +186,9 @@ func (s IntegrationTestSuite) TestSimulateTx_GRPCGateway() { err = val.ClientCtx.Codec.UnmarshalJSON(res, &result) s.Require().NoError(err) // Check the result and gas used are correct. - s.Require().Equal(len(result.GetResult().GetEvents()), 13) // See TestSimulateTx_GRPC for the 13 events. s.Require().Len(result.GetResult().MsgResponses, 1) - s.Require().True(result.GetGasInfo().GetGasUsed() > 0) // Gas used sometimes change, just check it's not empty. + s.Require().Equal(len(result.GetResult().GetEvents()), 6) // 1 coin recv 1 coin spent, 1 transfer, 3 messages. + s.Require().True(result.GetGasInfo().GetGasUsed() > 0) // Gas used sometimes change, jus } }) } diff --git a/x/authz/keeper/keeper.go b/x/authz/keeper/keeper.go index a1fd37d57de..5ccb7e0de6f 100644 --- a/x/authz/keeper/keeper.go +++ b/x/authz/keeper/keeper.go @@ -8,13 +8,13 @@ import ( "github.com/tendermint/tendermint/libs/log" + "github.com/cosmos/cosmos-sdk/baseapp" "github.com/cosmos/cosmos-sdk/codec" codectypes "github.com/cosmos/cosmos-sdk/codec/types" storetypes "github.com/cosmos/cosmos-sdk/store/types" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" - "github.com/cosmos/cosmos-sdk/x/auth/middleware" "github.com/cosmos/cosmos-sdk/x/authz" ) @@ -26,12 +26,12 @@ const gasCostPerIteration = uint64(20) type Keeper struct { storeKey storetypes.StoreKey cdc codec.BinaryCodec - router *middleware.MsgServiceRouter + router *baseapp.MsgServiceRouter authKeeper authkeeper.AccountKeeper } // NewKeeper constructs a message authorization Keeper -func NewKeeper(storeKey storetypes.StoreKey, cdc codec.BinaryCodec, router *middleware.MsgServiceRouter, ak authkeeper.AccountKeeper) Keeper { +func NewKeeper(storeKey storetypes.StoreKey, cdc codec.BinaryCodec, router *baseapp.MsgServiceRouter, ak authkeeper.AccountKeeper) Keeper { return Keeper{ storeKey: storeKey, cdc: cdc, diff --git a/x/feegrant/keeper/keeper.go b/x/feegrant/keeper/keeper.go index e8d83842f55..84abc4428bf 100644 --- a/x/feegrant/keeper/keeper.go +++ b/x/feegrant/keeper/keeper.go @@ -10,7 +10,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/middleware" + "github.com/cosmos/cosmos-sdk/x/auth/ante" "github.com/cosmos/cosmos-sdk/x/feegrant" ) @@ -22,7 +22,7 @@ type Keeper struct { authKeeper feegrant.AccountKeeper } -var _ middleware.FeegrantKeeper = &Keeper{} +var _ ante.FeegrantKeeper = &Keeper{} // NewKeeper creates a fee grant Keeper func NewKeeper(cdc codec.BinaryCodec, storeKey storetypes.StoreKey, ak feegrant.AccountKeeper) Keeper { diff --git a/x/gov/keeper/keeper.go b/x/gov/keeper/keeper.go index 67b68047d2a..4558075f48a 100644 --- a/x/gov/keeper/keeper.go +++ b/x/gov/keeper/keeper.go @@ -6,10 +6,10 @@ import ( "github.com/tendermint/tendermint/libs/log" + "github.com/cosmos/cosmos-sdk/baseapp" "github.com/cosmos/cosmos-sdk/codec" storetypes "github.com/cosmos/cosmos-sdk/store/types" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/auth/middleware" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" "github.com/cosmos/cosmos-sdk/x/gov/types" v1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1" @@ -40,7 +40,7 @@ type Keeper struct { legacyRouter v1beta1.Router // Msg server router - router *middleware.MsgServiceRouter + router *baseapp.MsgServiceRouter config types.Config } @@ -55,7 +55,7 @@ type Keeper struct { func NewKeeper( cdc codec.BinaryCodec, key storetypes.StoreKey, paramSpace types.ParamSubspace, authKeeper types.AccountKeeper, bankKeeper types.BankKeeper, sk types.StakingKeeper, - legacyRouter v1beta1.Router, router *middleware.MsgServiceRouter, + legacyRouter v1beta1.Router, router *baseapp.MsgServiceRouter, config types.Config, ) Keeper { // ensure governance module account is set @@ -103,7 +103,7 @@ func (keeper Keeper) Logger(ctx sdk.Context) log.Logger { } // Router returns the gov keeper's router -func (keeper Keeper) Router() *middleware.MsgServiceRouter { +func (keeper Keeper) Router() *baseapp.MsgServiceRouter { return keeper.router } diff --git a/x/group/keeper/keeper.go b/x/group/keeper/keeper.go index c61355c1720..93cd875da5a 100644 --- a/x/group/keeper/keeper.go +++ b/x/group/keeper/keeper.go @@ -6,11 +6,11 @@ import ( "github.com/tendermint/tendermint/libs/log" + "github.com/cosmos/cosmos-sdk/baseapp" "github.com/cosmos/cosmos-sdk/codec" storetypes "github.com/cosmos/cosmos-sdk/store/types" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" - authmiddleware "github.com/cosmos/cosmos-sdk/x/auth/middleware" "github.com/cosmos/cosmos-sdk/x/group" "github.com/cosmos/cosmos-sdk/x/group/errors" "github.com/cosmos/cosmos-sdk/x/group/internal/orm" @@ -75,13 +75,13 @@ type Keeper struct { voteByProposalIndex orm.Index voteByVoterIndex orm.Index - router *authmiddleware.MsgServiceRouter + router *baseapp.MsgServiceRouter config group.Config } // NewKeeper creates a new group keeper. -func NewKeeper(storeKey storetypes.StoreKey, cdc codec.Codec, router *authmiddleware.MsgServiceRouter, accKeeper group.AccountKeeper, config group.Config) Keeper { +func NewKeeper(storeKey storetypes.StoreKey, cdc codec.Codec, router *baseapp.MsgServiceRouter, accKeeper group.AccountKeeper, config group.Config) Keeper { k := Keeper{ key: storeKey, router: router, diff --git a/x/group/keeper/proposal_executor.go b/x/group/keeper/proposal_executor.go index 506fa007585..29d9d1e8bed 100644 --- a/x/group/keeper/proposal_executor.go +++ b/x/group/keeper/proposal_executor.go @@ -3,16 +3,16 @@ package keeper import ( "fmt" + "github.com/cosmos/cosmos-sdk/baseapp" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/errors" - authmiddleware "github.com/cosmos/cosmos-sdk/x/auth/middleware" "github.com/cosmos/cosmos-sdk/x/group" grouperrors "github.com/cosmos/cosmos-sdk/x/group/errors" ) // doExecuteMsgs routes the messages to the registered handlers. Messages are limited to those that require no authZ or // by the account of group policy only. Otherwise this gives access to other peoples accounts as the sdk middlewares are bypassed -func (s Keeper) doExecuteMsgs(ctx sdk.Context, router *authmiddleware.MsgServiceRouter, proposal group.Proposal, groupPolicyAcc sdk.AccAddress) ([]sdk.Result, error) { +func (s Keeper) doExecuteMsgs(ctx sdk.Context, router *baseapp.MsgServiceRouter, proposal group.Proposal, groupPolicyAcc sdk.AccAddress) ([]sdk.Result, error) { // Ensure it's not too late to execute the messages. // After https://github.com/cosmos/cosmos-sdk/issues/11245, proposals should // be pruned automatically, so this function should not even be called, as diff --git a/x/upgrade/types/storeloader_test.go b/x/upgrade/types/storeloader_test.go index 2bdf36c22a0..6aaefa69d19 100644 --- a/x/upgrade/types/storeloader_test.go +++ b/x/upgrade/types/storeloader_test.go @@ -125,7 +125,7 @@ func TestSetLoader(t *testing.T) { // load the app with the existing db opts := []func(*baseapp.BaseApp){baseapp.SetPruning(pruningtypes.NewPruningOptions(pruningtypes.PruningNothing))} - origapp := baseapp.NewBaseApp(t.Name(), defaultLogger(), db, opts...) + origapp := baseapp.NewBaseApp(t.Name(), defaultLogger(), db, nil, opts...) origapp.MountStores(sdk.NewKVStoreKey(tc.origStoreKey)) err := origapp.LoadLatestVersion() require.Nil(t, err) @@ -141,7 +141,7 @@ func TestSetLoader(t *testing.T) { } // load the new app with the original app db - app := baseapp.NewBaseApp(t.Name(), defaultLogger(), db, opts...) + app := baseapp.NewBaseApp(t.Name(), defaultLogger(), db, nil, opts...) app.MountStores(sdk.NewKVStoreKey(tc.loadStoreKey)) err = app.LoadLatestVersion() require.Nil(t, err)