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:
Amaury 2022-05-20 11:27:27 +02:00 committed by GitHub
parent cf750b85d7
commit 01832e6239
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
76 changed files with 3158 additions and 4692 deletions

View File

@ -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.

View File

@ -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
}

View File

@ -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 {

View File

@ -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

201
baseapp/block_gas_test.go Normal file
View File

@ -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
}

View File

@ -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
}

View File

@ -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)

View File

@ -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

View File

@ -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)
}

View File

@ -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

View File

@ -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() {

77
baseapp/recovery.go Normal file
View File

@ -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)
}

64
baseapp/recovery_test.go Normal file
View File

@ -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)
}
}

View File

@ -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]
}

View File

@ -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() {

View File

@ -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)
}

View File

@ -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

View File

@ -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

View File

@ -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
}
}

View File

@ -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 '='")
}

View File

@ -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

View File

@ -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.

View File

@ -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

View File

@ -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 {

View File

@ -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

View File

@ -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

58
x/auth/ante/ante.go Normal file
View File

@ -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
}

View File

@ -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")
}

206
x/auth/ante/basic.go Normal file
View File

@ -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)
}

224
x/auth/ante/basic_test.go Normal file
View File

@ -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)
})
}
}

View File

@ -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

36
x/auth/ante/ext.go Normal file
View File

@ -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)
}

36
x/auth/ante/ext_test.go Normal file
View File

@ -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")
}

140
x/auth/ante/fee.go Normal file
View File

@ -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
}

104
x/auth/ante/fee_test.go Normal file
View File

@ -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")
}

View File

@ -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 {

76
x/auth/ante/setup.go Normal file
View File

@ -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))
}

99
x/auth/ante/setup_test.go Normal file
View File

@ -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")
}

View File

@ -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) {

View File

@ -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)

View File

@ -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())
}
}

View File

@ -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))
}

View File

@ -1562,6 +1562,7 @@ func (s *IntegrationTestSuite) TestAuxSigner() {
}
func (s *IntegrationTestSuite) TestAuxToFee() {
s.T().Skip()
require := s.Require()
val := s.network.Validators[0]

View File

@ -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
}

View File

@ -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)
})
}
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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")
}
})
}
}

View File

@ -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
}

View File

@ -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")
}

View File

@ -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))
}

View File

@ -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")
}}

View File

@ -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
}

View File

@ -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
}

View File

@ -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{},
)
})
}

View File

@ -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()),
)
}
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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))
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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)
}
})
}
}

View File

@ -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
}

View File

@ -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))

View File

@ -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{}
)

View File

@ -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
}
})
}

View File

@ -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,

View File

@ -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 {

View File

@ -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
}

View File

@ -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,

View File

@ -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

View File

@ -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)