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)
This commit is contained in:
parent
cf750b85d7
commit
01832e6239
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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})
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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]
|
||||
}
|
|
@ -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() {
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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() {}
|
||||
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{}
|
||||
)
|
||||
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 '='")
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
}
|
|
@ -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()
|
||||
func (suite *AnteTestSuite) TestCustomSignatureVerificationGasConsumer() {
|
||||
suite.SetupTest(false) // setup
|
||||
|
||||
txHandler, err := middleware.NewDefaultTxHandler(
|
||||
middleware.TxHandlerOptions{
|
||||
AccountKeeper: s.app.AccountKeeper,
|
||||
BankKeeper: s.app.BankKeeper,
|
||||
FeegrantKeeper: s.app.FeeGrantKeeper,
|
||||
SignModeHandler: s.clientCtx.TxConfig.SignModeHandler(),
|
||||
// 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")
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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)
|
||||
}
|
|
@ -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")
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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")
|
||||
}
|
|
@ -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 {
|
|
@ -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))
|
||||
}
|
|
@ -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")
|
||||
}
|
|
@ -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,106 +121,266 @@ func (spkm setPubKeyTxHandler) setPubKey(ctx context.Context, req tx.Request, si
|
|||
}
|
||||
}
|
||||
|
||||
sdkCtx.EventManager().EmitEvents(events)
|
||||
ctx.EventManager().EmitEvents(events)
|
||||
|
||||
return nil
|
||||
return next(ctx, tx, simulate)
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
return spkm.next.CheckTx(ctx, req, checkReq)
|
||||
}
|
||||
|
||||
// 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,
|
||||
type SigGasConsumeDecorator struct {
|
||||
ak AccountKeeper
|
||||
sigGasConsumer SignatureVerificationGasConsumer
|
||||
}
|
||||
|
||||
func NewSigGasConsumeDecorator(ak AccountKeeper, sigGasConsumer SignatureVerificationGasConsumer) SigGasConsumeDecorator {
|
||||
return SigGasConsumeDecorator{
|
||||
ak: ak,
|
||||
sigGasConsumer: sigGasConsumer,
|
||||
}
|
||||
}
|
||||
|
||||
func (sgcd SigGasConsumeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) {
|
||||
sigTx, ok := tx.(authsigning.SigVerifiableTx)
|
||||
if !ok {
|
||||
return ctx, sdkerrors.Wrap(sdkerrors.ErrTxDecode, "invalid transaction type")
|
||||
}
|
||||
|
||||
params := sgcd.ak.GetParams(ctx)
|
||||
sigs, err := sigTx.GetSignaturesV2()
|
||||
if err != nil {
|
||||
return ctx, err
|
||||
}
|
||||
|
||||
// stdSigs contains the sequence number, account number, and signatures.
|
||||
// When simulating, this would just be a 0-length slice.
|
||||
signerAddrs := sigTx.GetSigners()
|
||||
|
||||
for i, sig := range sigs {
|
||||
signerAcc, err := GetSignerAcc(ctx, sgcd.ak, signerAddrs[i])
|
||||
if err != nil {
|
||||
return ctx, err
|
||||
}
|
||||
|
||||
pubKey := signerAcc.GetPubKey()
|
||||
|
||||
// In simulate mode the transaction comes with no signatures, thus if the
|
||||
// account's pubkey is nil, both signature verification and gasKVStore.Set()
|
||||
// shall consume the largest amount, i.e. it takes more gas to verify
|
||||
// secp256k1 keys than ed25519 ones.
|
||||
if simulate && pubKey == nil {
|
||||
pubKey = simSecp256k1Pubkey
|
||||
}
|
||||
|
||||
// make a SignatureV2 with PubKey filled in from above
|
||||
sig = signing.SignatureV2{
|
||||
PubKey: pubKey,
|
||||
Data: sig.Data,
|
||||
Sequence: sig.Sequence,
|
||||
}
|
||||
|
||||
err = sgcd.sigGasConsumer(ctx.GasMeter(), sig, params)
|
||||
if err != nil {
|
||||
return ctx, err
|
||||
}
|
||||
}
|
||||
|
||||
return next(ctx, tx, simulate)
|
||||
}
|
||||
|
||||
func (vscd validateSigCountTxHandler) checkSigCount(ctx context.Context, req tx.Request) error {
|
||||
sdkCtx := sdk.UnwrapSDKContext(ctx)
|
||||
// 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
|
||||
}
|
||||
|
||||
sigTx, ok := req.Tx.(authsigning.SigVerifiableTx)
|
||||
func NewSigVerificationDecorator(ak AccountKeeper, signModeHandler authsigning.SignModeHandler) SigVerificationDecorator {
|
||||
return SigVerificationDecorator{
|
||||
ak: ak,
|
||||
signModeHandler: signModeHandler,
|
||||
}
|
||||
}
|
||||
|
||||
// OnlyLegacyAminoSigners checks SignatureData to see if all
|
||||
// signers are using SIGN_MODE_LEGACY_AMINO_JSON. If this is the case
|
||||
// then the corresponding SignatureV2 struct will not have account sequence
|
||||
// explicitly set, and we should skip the explicit verification of sig.Sequence
|
||||
// in the SigVerificationDecorator's AnteHandler function.
|
||||
func OnlyLegacyAminoSigners(sigData signing.SignatureData) bool {
|
||||
switch v := sigData.(type) {
|
||||
case *signing.SingleSignatureData:
|
||||
return v.SignMode == signing.SignMode_SIGN_MODE_LEGACY_AMINO_JSON
|
||||
case *signing.MultiSignatureData:
|
||||
for _, s := range v.Signatures {
|
||||
if !OnlyLegacyAminoSigners(s) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (svd SigVerificationDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) {
|
||||
// no need to verify signatures on recheck tx
|
||||
if ctx.IsReCheckTx() {
|
||||
return next(ctx, tx, simulate)
|
||||
}
|
||||
sigTx, ok := tx.(authsigning.SigVerifiableTx)
|
||||
if !ok {
|
||||
return sdkerrors.Wrap(sdkerrors.ErrTxDecode, "Tx must be a sigTx")
|
||||
return ctx, sdkerrors.Wrap(sdkerrors.ErrTxDecode, "invalid transaction type")
|
||||
}
|
||||
|
||||
params := vscd.ak.GetParams(sdkCtx)
|
||||
// stdSigs contains the sequence number, account number, and signatures.
|
||||
// When simulating, this would just be a 0-length slice.
|
||||
sigs, err := sigTx.GetSignaturesV2()
|
||||
if err != nil {
|
||||
return ctx, err
|
||||
}
|
||||
|
||||
signerAddrs := sigTx.GetSigners()
|
||||
|
||||
// check that signer length and signature length are the same
|
||||
if len(sigs) != len(signerAddrs) {
|
||||
return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnauthorized, "invalid number of signer; expected: %d, got %d", len(signerAddrs), len(sigs))
|
||||
}
|
||||
|
||||
for i, sig := range sigs {
|
||||
acc, err := GetSignerAcc(ctx, svd.ak, signerAddrs[i])
|
||||
if err != nil {
|
||||
return ctx, err
|
||||
}
|
||||
|
||||
// retrieve pubkey
|
||||
pubKey := acc.GetPubKey()
|
||||
if !simulate && pubKey == nil {
|
||||
return ctx, sdkerrors.Wrap(sdkerrors.ErrInvalidPubKey, "pubkey on account is not set")
|
||||
}
|
||||
|
||||
// Check account sequence number.
|
||||
if sig.Sequence != acc.GetSequence() {
|
||||
return ctx, sdkerrors.Wrapf(
|
||||
sdkerrors.ErrWrongSequence,
|
||||
"account sequence mismatch, expected %d, got %d", acc.GetSequence(), sig.Sequence,
|
||||
)
|
||||
}
|
||||
|
||||
// retrieve signer data
|
||||
genesis := ctx.BlockHeight() == 0
|
||||
chainID := ctx.ChainID()
|
||||
var accNum uint64
|
||||
if !genesis {
|
||||
accNum = acc.GetAccountNumber()
|
||||
}
|
||||
signerData := authsigning.SignerData{
|
||||
Address: acc.GetAddress().String(),
|
||||
ChainID: chainID,
|
||||
AccountNumber: accNum,
|
||||
Sequence: acc.GetSequence(),
|
||||
PubKey: pubKey,
|
||||
}
|
||||
|
||||
if !simulate {
|
||||
err := authsigning.VerifySignature(pubKey, signerData, sig.Data, svd.signModeHandler, tx)
|
||||
if err != nil {
|
||||
var errMsg string
|
||||
if OnlyLegacyAminoSigners(sig.Data) {
|
||||
// If all signers are using SIGN_MODE_LEGACY_AMINO, we rely on VerifySignature to check account sequence number,
|
||||
// and therefore communicate sequence number as a potential cause of error.
|
||||
errMsg = fmt.Sprintf("signature verification failed; please verify account number (%d), sequence (%d) and chain-id (%s)", accNum, acc.GetSequence(), chainID)
|
||||
} else {
|
||||
errMsg = fmt.Sprintf("signature verification failed; please verify account number (%d) and chain-id (%s)", accNum, chainID)
|
||||
}
|
||||
return ctx, sdkerrors.Wrap(sdkerrors.ErrUnauthorized, errMsg)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return next(ctx, tx, simulate)
|
||||
}
|
||||
|
||||
// IncrementSequenceDecorator handles incrementing sequences of all signers.
|
||||
// Use the IncrementSequenceDecorator decorator to prevent replay attacks. Note,
|
||||
// there is no need to execute IncrementSequenceDecorator on RecheckTX since
|
||||
// CheckTx would already bump the sequence number.
|
||||
//
|
||||
// NOTE: Since CheckTx and DeliverTx state are managed separately, subsequent and
|
||||
// sequential txs orginating from the same account cannot be handled correctly in
|
||||
// a reliable way unless sequence numbers are managed and tracked manually by a
|
||||
// client. It is recommended to instead use multiple messages in a tx.
|
||||
type IncrementSequenceDecorator struct {
|
||||
ak AccountKeeper
|
||||
}
|
||||
|
||||
func NewIncrementSequenceDecorator(ak AccountKeeper) IncrementSequenceDecorator {
|
||||
return IncrementSequenceDecorator{
|
||||
ak: ak,
|
||||
}
|
||||
}
|
||||
|
||||
func (isd IncrementSequenceDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) {
|
||||
sigTx, ok := tx.(authsigning.SigVerifiableTx)
|
||||
if !ok {
|
||||
return ctx, sdkerrors.Wrap(sdkerrors.ErrTxDecode, "invalid transaction type")
|
||||
}
|
||||
|
||||
// increment sequence of all signers
|
||||
for _, addr := range sigTx.GetSigners() {
|
||||
acc := isd.ak.GetAccount(ctx, addr)
|
||||
if err := acc.SetSequence(acc.GetSequence() + 1); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
isd.ak.SetAccount(ctx, acc)
|
||||
}
|
||||
|
||||
return next(ctx, tx, simulate)
|
||||
}
|
||||
|
||||
// ValidateSigCountDecorator takes in Params and returns errors if there are too many signatures in the tx for the given params
|
||||
// otherwise it calls next AnteHandler
|
||||
// Use this decorator to set parameterized limit on number of signatures in tx
|
||||
// CONTRACT: Tx must implement SigVerifiableTx interface
|
||||
type ValidateSigCountDecorator struct {
|
||||
ak AccountKeeper
|
||||
}
|
||||
|
||||
func NewValidateSigCountDecorator(ak AccountKeeper) ValidateSigCountDecorator {
|
||||
return ValidateSigCountDecorator{
|
||||
ak: ak,
|
||||
}
|
||||
}
|
||||
|
||||
func (vscd ValidateSigCountDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) {
|
||||
sigTx, ok := tx.(authsigning.SigVerifiableTx)
|
||||
if !ok {
|
||||
return ctx, sdkerrors.Wrap(sdkerrors.ErrTxDecode, "Tx must be a sigTx")
|
||||
}
|
||||
|
||||
params := vscd.ak.GetParams(ctx)
|
||||
pubKeys, err := sigTx.GetPubKeys()
|
||||
if err != nil {
|
||||
return err
|
||||
return ctx, err
|
||||
}
|
||||
|
||||
sigCount := 0
|
||||
for _, pk := range pubKeys {
|
||||
sigCount += CountSubKeys(pk)
|
||||
if uint64(sigCount) > params.TxSigLimit {
|
||||
return sdkerrors.Wrapf(sdkerrors.ErrTooManySignatures,
|
||||
return ctx, 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)
|
||||
return next(ctx, tx, simulate)
|
||||
}
|
||||
|
||||
// DefaultSigVerificationGasConsumer is the default implementation of SignatureVerificationGasConsumer. It consumes gas
|
||||
|
@ -263,6 +424,7 @@ func ConsumeMultisignatureVerificationGas(
|
|||
meter sdk.GasMeter, sig *signing.MultiSignatureData, pubkey multisig.PubKey,
|
||||
params types.Params, accSeq uint64,
|
||||
) error {
|
||||
|
||||
size := sig.BitArray.Count()
|
||||
sigIndex := 0
|
||||
|
||||
|
@ -285,333 +447,6 @@ func ConsumeMultisignatureVerificationGas(
|
|||
return nil
|
||||
}
|
||||
|
||||
var _ tx.Handler = sigGasConsumeTxHandler{}
|
||||
|
||||
type sigGasConsumeTxHandler 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 (sgcm sigGasConsumeTxHandler) sigGasConsume(ctx context.Context, req tx.Request, simulate bool) error {
|
||||
sdkCtx := sdk.UnwrapSDKContext(ctx)
|
||||
|
||||
sigTx, ok := req.Tx.(authsigning.SigVerifiableTx)
|
||||
if !ok {
|
||||
return sdkerrors.Wrap(sdkerrors.ErrTxDecode, "invalid transaction type")
|
||||
}
|
||||
|
||||
params := sgcm.ak.GetParams(sdkCtx)
|
||||
sigs, err := sigTx.GetSignaturesV2()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// stdSigs contains the sequence number, account number, and signatures.
|
||||
// When simulating, this would just be a 0-length slice.
|
||||
signerAddrs := sigTx.GetSigners()
|
||||
|
||||
for i, sig := range sigs {
|
||||
signerAcc, err := GetSignerAcc(sdkCtx, sgcm.ak, signerAddrs[i])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pubKey := signerAcc.GetPubKey()
|
||||
|
||||
// In simulate mode the transaction comes with no signatures, thus if the
|
||||
// account's pubkey is nil, both signature verification and gasKVStore.Set()
|
||||
// shall consume the largest amount, i.e. it takes more gas to verify
|
||||
// secp256k1 keys than ed25519 ones.
|
||||
if simulate && pubKey == nil {
|
||||
pubKey = simSecp256k1Pubkey
|
||||
}
|
||||
|
||||
// make a SignatureV2 with PubKey filled in from above
|
||||
sig = signing.SignatureV2{
|
||||
PubKey: pubKey,
|
||||
Data: sig.Data,
|
||||
Sequence: sig.Sequence,
|
||||
}
|
||||
|
||||
err = sgcm.sigGasConsumer(sdkCtx.GasMeter(), sig, params)
|
||||
if err != nil {
|
||||
return 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)
|
||||
}
|
||||
|
||||
// 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 {
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// OnlyLegacyAminoSigners checks SignatureData to see if all
|
||||
// signers are using SIGN_MODE_LEGACY_AMINO_JSON. If this is the case
|
||||
// then the corresponding SignatureV2 struct will not have account sequence
|
||||
// explicitly set, and we should skip the explicit verification of sig.Sequence
|
||||
// in the SigVerificationMiddleware's middleware function.
|
||||
func OnlyLegacyAminoSigners(sigData signing.SignatureData) bool {
|
||||
switch v := sigData.(type) {
|
||||
case *signing.SingleSignatureData:
|
||||
return v.SignMode == signing.SignMode_SIGN_MODE_LEGACY_AMINO_JSON
|
||||
case *signing.MultiSignatureData:
|
||||
for _, s := range v.Signatures {
|
||||
if !OnlyLegacyAminoSigners(s) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (svd sigVerificationTxHandler) sigVerify(ctx context.Context, req tx.Request, isReCheckTx, simulate bool) error {
|
||||
sdkCtx := sdk.UnwrapSDKContext(ctx)
|
||||
// no need to verify signatures on recheck tx
|
||||
if isReCheckTx {
|
||||
return nil
|
||||
}
|
||||
sigTx, ok := req.Tx.(authsigning.SigVerifiableTx)
|
||||
if !ok {
|
||||
return sdkerrors.Wrap(sdkerrors.ErrTxDecode, "invalid transaction type")
|
||||
}
|
||||
|
||||
// stdSigs contains the sequence number, account number, and signatures.
|
||||
// When simulating, this would just be a 0-length slice.
|
||||
sigs, err := sigTx.GetSignaturesV2()
|
||||
if err != nil {
|
||||
return 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))
|
||||
}
|
||||
|
||||
for i, sig := range sigs {
|
||||
acc, err := GetSignerAcc(sdkCtx, svd.ak, signerAddrs[i])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// retrieve pubkey
|
||||
pubKey := acc.GetPubKey()
|
||||
if !simulate && pubKey == nil {
|
||||
return sdkerrors.Wrap(sdkerrors.ErrInvalidPubKey, "pubkey on account is not set")
|
||||
}
|
||||
|
||||
// Check account sequence number.
|
||||
if sig.Sequence != acc.GetSequence() {
|
||||
return 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()
|
||||
var accNum uint64
|
||||
if !genesis {
|
||||
accNum = acc.GetAccountNumber()
|
||||
}
|
||||
|
||||
signerData := authsigning.SignerData{
|
||||
Address: signerAddrs[i].String(),
|
||||
ChainID: chainID,
|
||||
AccountNumber: accNum,
|
||||
Sequence: acc.GetSequence(),
|
||||
PubKey: pubKey,
|
||||
}
|
||||
|
||||
if !simulate {
|
||||
err := authsigning.VerifySignature(pubKey, signerData, sig.Data, svd.signModeHandler, req.Tx)
|
||||
if err != nil {
|
||||
var errMsg string
|
||||
if OnlyLegacyAminoSigners(sig.Data) {
|
||||
// If all signers are using SIGN_MODE_LEGACY_AMINO, we rely on VerifySignature to check account sequence number,
|
||||
// and therefore communicate sequence number as a potential cause of error.
|
||||
errMsg = fmt.Sprintf("signature verification failed; please verify account number (%d), sequence (%d) and chain-id (%s)", accNum, acc.GetSequence(), chainID)
|
||||
} else {
|
||||
errMsg = fmt.Sprintf("signature verification failed; please verify account number (%d) and chain-id (%s)", accNum, chainID)
|
||||
}
|
||||
return 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)
|
||||
}
|
||||
|
||||
// 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
|
||||
// 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (isd incrementSequenceTxHandler) incrementSeq(ctx context.Context, req tx.Request) error {
|
||||
sdkCtx := sdk.UnwrapSDKContext(ctx)
|
||||
sigTx, ok := req.Tx.(authsigning.SigVerifiableTx)
|
||||
if !ok {
|
||||
return sdkerrors.Wrap(sdkerrors.ErrTxDecode, "invalid transaction type")
|
||||
}
|
||||
|
||||
// increment sequence of all signers
|
||||
for _, addr := range sigTx.GetSigners() {
|
||||
acc := isd.ak.GetAccount(sdkCtx, addr)
|
||||
if err := acc.SetSequence(acc.GetSequence() + 1); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
isd.ak.SetAccount(sdkCtx, acc)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
return isd.next.CheckTx(ctx, req, checkReq)
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
return isd.next.DeliverTx(ctx, req)
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
return isd.next.SimulateTx(ctx, req)
|
||||
}
|
||||
|
||||
// GetSignerAcc returns an account for a given address that is expected to sign
|
||||
// a transaction.
|
||||
func GetSignerAcc(ctx sdk.Context, ak AccountKeeper, addr sdk.AccAddress) (types.AccountI, error) {
|
|
@ -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)
|
|
@ -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)
|
||||
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.SetPubKeyMiddleware(s.app.AccountKeeper),
|
||||
middleware.SigGasConsumeMiddleware(s.app.AccountKeeper, middleware.DefaultSigVerificationGasConsumer),
|
||||
middleware.SigVerificationMiddleware(
|
||||
s.app.AccountKeeper,
|
||||
s.clientCtx.TxConfig.SignModeHandler(),
|
||||
),
|
||||
)
|
||||
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 txhandler with default params
|
||||
before := ctx.GasMeter().GasConsumed()
|
||||
_, err = txHandler.DeliverTx(sdk.WrapSDKContext(ctx), tx.Request{Tx: testTx})
|
||||
// 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())
|
||||
}
|
||||
}
|
|
@ -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))
|
||||
}
|
|
@ -1562,6 +1562,7 @@ func (s *IntegrationTestSuite) TestAuxSigner() {
|
|||
}
|
||||
|
||||
func (s *IntegrationTestSuite) TestAuxToFee() {
|
||||
s.T().Skip()
|
||||
require := s.Require()
|
||||
val := s.network.Validators[0]
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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")
|
||||
}
|
|
@ -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))
|
||||
}
|
|
@ -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")
|
||||
}}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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{},
|
||||
)
|
||||
})
|
||||
}
|
|
@ -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()),
|
||||
)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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))
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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))
|
||||
|
||||
|
|
|
@ -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{}
|
||||
)
|
||||
|
|
|
@ -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=<val1>
|
||||
// - tx.* events: tx.fee, tx.acc_seq, tx.signature
|
||||
// - Sending Amount to recipient: coin_spent, coin_received, transfer and message.sender=<val1>
|
||||
// - 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
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue