refactor!: BaseApp {Check,Deliver}Tx with middleware design (#9920)

<!--
The default pull request template is for types feat, fix, or refactor.
For other templates, add one of the following parameters to the url:
- template=docs.md
- template=other.md
-->

## Description

ref: #9585 

This PR if the 1st step (out of 2) in the #9585 refactor. It transforms baseapp's ABCI {Check,Deliver}Tx into middleware stacks. A middleware is defined by the following interfaces:

```go
// types/tx package

type Handler interface {
	CheckTx(ctx context.Context, tx sdk.Tx, req abci.RequestCheckTx) (abci.ResponseCheckTx, error)
	DeliverTx(ctx context.Context, tx sdk.Tx, req abci.RequestDeliverTx) (abci.ResponseDeliverTx, error)
	SimulateTx(ctx context.Context, tx sdk.Tx, req RequestSimulateTx) (ResponseSimulateTx, error)
}

type Middleware func(Handler) Handler
```

This Pr doesn't migrate antehandlers, but only baseapp's runTx and runMsgs as middlewares. It bundles antehandlers into one single middleware (for now).

More specifically, it introduces the 5 following middlewares:

| Middleware              | Description                                                                                                                                                                                                                                                                                                                                                                                            |
| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| RunMsgsTxMiddleware     | This middleware replaces the old baseapp's `runMsgs`.                                                                                                                                                                                                                                                                                                                                                  |
| LegacyAnteMiddleware    | This middleware is temporary, it bundles all antehandlers into one middleware. It's only created for the purpose of breaking the refactor into 2 pieces. **It will be removed in Part 2**, and each antehandler will be replaced by its own middleware.                                                                                                                                                                                                  |
| IndexEventsTxMiddleware | This is a simple middleware that chooses which events to index in Tendermint. Replaces `baseapp.indexEvents` (which unfortunately still exists in baseapp too, because it's used to index Begin/EndBlock events)                                                                                                                                                                                        |
| RecoveryTxMiddleware    | This index recovers from panics. It replaces baseapp.runTx's panic recovery.                                                                                                                                                                                                                                                                                                                           |
| GasTxMiddleware         | This replaces the [`Setup`](https://github.com/cosmos/cosmos-sdk/blob/master/x/auth/ante/setup.go) Antehandler. It sets a GasMeter on sdk.Context. Note that before, GasMeter was set on sdk.Context inside the antehandlers, and there was some mess around the fact that antehandlers had their own panic recovery system so that the GasMeter could be read by baseapp's recovery system. Now, this mess is all removed: one middleware sets GasMeter, another one handles recovery. |

---

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

- [x] included the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title
- [x] added `!` to the type prefix if API or client breaking change
- [x] targeted the correct branch (see [PR Targeting](https://github.com/cosmos/cosmos-sdk/blob/master/CONTRIBUTING.md#pr-targeting))
- [x] provided a link to the relevant issue or specification
- [ ] followed the guidelines for [building modules](https://github.com/cosmos/cosmos-sdk/blob/master/docs/building-modules)
- [x] included the necessary unit and integration [tests](https://github.com/cosmos/cosmos-sdk/blob/master/CONTRIBUTING.md#testing)
- [x] added a changelog entry to `CHANGELOG.md`
- [x] included comments for [documenting Go code](https://blog.golang.org/godoc)
- [ ] updated the relevant documentation or specification
- [x] 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 2021-08-25 16:40:33 +02:00 committed by GitHub
parent 598fc0c8e5
commit a15d7e31d4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
37 changed files with 1346 additions and 874 deletions

View File

@ -67,7 +67,12 @@ Ref: https://keepachangelog.com/en/1.0.0/
* [\#9650](https://github.com/cosmos/cosmos-sdk/pull/9650) Removed deprecated message handler implementation from the SDK modules.
* (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}`.
### Client Breaking Changes

View File

@ -240,18 +240,18 @@ func (app *BaseApp) CheckTx(req abci.RequestCheckTx) abci.ResponseCheckTx {
panic(fmt.Sprintf("unknown RequestCheckTx type: %s", req.Type))
}
gInfo, result, err := app.runTx(mode, req.Tx)
tx, err := app.txDecoder(req.Tx)
if err != nil {
return sdkerrors.ResponseCheckTx(err, gInfo.GasWanted, gInfo.GasUsed, app.trace)
return sdkerrors.ResponseCheckTx(err, 0, 0, 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),
ctx := app.getContextForTx(mode, req.Tx)
res, err := app.txHandler.CheckTx(ctx, tx, req)
if err != nil {
return sdkerrors.ResponseCheckTx(err, uint64(res.GasUsed), uint64(res.GasWanted), app.trace)
}
return res
}
// DeliverTx implements the ABCI interface and executes a tx in DeliverTx mode.
@ -262,29 +262,18 @@ func (app *BaseApp) CheckTx(req abci.RequestCheckTx) abci.ResponseCheckTx {
func (app *BaseApp) DeliverTx(req abci.RequestDeliverTx) abci.ResponseDeliverTx {
defer telemetry.MeasureSince(time.Now(), "abci", "deliver_tx")
gInfo := sdk.GasInfo{}
resultStr := "successful"
defer func() {
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")
}()
gInfo, result, err := app.runTx(runTxModeDeliver, req.Tx)
tx, err := app.txDecoder(req.Tx)
if err != nil {
resultStr = "failed"
return sdkerrors.ResponseDeliverTx(err, gInfo.GasWanted, gInfo.GasUsed, app.trace)
return sdkerrors.ResponseDeliverTx(err, 0, 0, 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),
ctx := app.getContextForTx(runTxModeDeliver, req.Tx)
res, err := app.txHandler.DeliverTx(ctx, tx, req)
if err != nil {
return sdkerrors.ResponseDeliverTx(err, uint64(res.GasUsed), uint64(res.GasWanted), app.trace)
}
return res
}
// Commit implements the ABCI interface. It will commit all state that exists in

View File

@ -1,14 +1,12 @@
package baseapp
import (
"context"
"errors"
"fmt"
"reflect"
"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"
@ -18,8 +16,7 @@ import (
"github.com/cosmos/cosmos-sdk/store"
"github.com/cosmos/cosmos-sdk/store/rootmulti"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/cosmos/cosmos-sdk/x/auth/migrations/legacytx"
"github.com/cosmos/cosmos-sdk/types/tx"
)
const (
@ -52,14 +49,12 @@ 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 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
anteHandler sdk.AnteHandler // ante handler for fee and auth
txHandler tx.Handler // txHandler for {Deliver,Check}Tx and simulations
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
@ -124,9 +119,6 @@ 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
@ -144,17 +136,15 @@ func NewBaseApp(
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,
router: NewRouter(),
queryRouter: NewQueryRouter(),
grpcQueryRouter: NewGRPCQueryRouter(),
msgServiceRouter: NewMsgServiceRouter(),
txDecoder: txDecoder,
fauxMerkleMode: false,
logger: logger,
name: name,
db: db,
cms: store.NewCommitMultiStore(db),
storeLoader: DefaultStoreLoader,
queryRouter: NewQueryRouter(),
grpcQueryRouter: NewGRPCQueryRouter(),
txDecoder: txDecoder,
fauxMerkleMode: false,
}
for _, option := range options {
@ -165,8 +155,6 @@ func NewBaseApp(
app.cms.SetInterBlockCache(app.interBlockCache)
}
app.runTxRecoveryMiddleware = newDefaultRecoveryMiddleware()
return app
}
@ -195,9 +183,6 @@ 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 ...sdk.StoreKey) {
@ -352,17 +337,6 @@ func (app *BaseApp) setIndexEvents(ie []string) {
}
}
// Router returns the 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 }
@ -429,13 +403,6 @@ func (app *BaseApp) GetConsensusParams(ctx sdk.Context) *abci.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 *abci.ConsensusParams) {
if app.paramStore == nil {
@ -503,22 +470,6 @@ 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 {
@ -530,7 +481,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) sdk.Context {
func (app *BaseApp) getContextForTx(mode runTxMode, txBytes []byte) context.Context {
ctx := app.getState(mode).ctx.
WithTxBytes(txBytes).
WithVoteInfos(app.voteInfos)
@ -545,226 +496,5 @@ func (app *BaseApp) getContextForTx(mode runTxMode, txBytes []byte) sdk.Context
ctx, _ = ctx.CacheContext()
}
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, 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() {
gInfo = sdk.GasInfo{GasUsed: ctx.BlockGasMeter().GasConsumed()}
return gInfo, nil, sdkerrors.Wrap(sdkerrors.ErrOutOfGas, "no block gas left to run tx")
}
var startingGas uint64
if mode == runTxModeDeliver {
startingGas = ctx.BlockGasMeter().GasConsumed()
}
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()}
}()
// 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.
defer func() {
if mode == runTxModeDeliver {
ctx.BlockGasMeter().ConsumeGas(
ctx.GasMeter().GasConsumedToLimit(), "block gas meter",
)
if ctx.BlockGasMeter().GasConsumed() < startingGas {
panic(sdk.ErrorGasOverflow{Descriptor: "tx gas summation"})
}
}
}()
tx, err := app.txDecoder(txBytes)
if err != nil {
return sdk.GasInfo{}, nil, err
}
msgs := tx.GetMsgs()
if err := validateBasicTxMsgs(msgs); err != nil {
return sdk.GasInfo{}, nil, err
}
var events sdk.Events
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, err
}
msCache.Write()
}
// 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 {
msCache.Write()
if len(events) > 0 {
// append the events in the order of occurrence
result.Events = append(events.ToABCIEvents(), result.Events...)
}
}
return gInfo, result, 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()
txMsgData := &sdk.TxMsgData{
Data: make([]*sdk.MsgData, 0, len(msgs)),
}
// 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)
txMsgData.Data = append(txMsgData.Data, &sdk.MsgData{MsgType: sdk.MsgTypeURL(msg), Data: msgResult.Data})
msgLogs = append(msgLogs, sdk.NewABCIMessageLog(uint32(i), msgResult.Log, msgEvents))
}
data, err := proto.Marshal(txMsgData)
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(),
}, nil
return sdk.WrapSDKContext(ctx)
}

View File

@ -6,6 +6,7 @@ import (
"encoding/json"
"fmt"
"io/ioutil"
"math"
"math/rand"
"os"
"strings"
@ -29,12 +30,15 @@ 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/x/auth/middleware"
"github.com/cosmos/cosmos-sdk/x/auth/migrations/legacytx"
)
var (
capKey1 = sdk.NewKVStoreKey("key1")
capKey2 = sdk.NewKVStoreKey("key2")
interfaceRegistry = testdata.NewTestInterfaceRegistry()
)
type paramStore struct {
@ -125,11 +129,19 @@ func setupBaseAppWithSnapshots(t *testing.T, blocks uint, blockTxs int, options
codec := codec.NewLegacyAmino()
registerTestCodec(codec)
routerOpt := func(bapp *BaseApp) {
bapp.Router().AddRoute(sdk.NewRoute(routeMsgKeyValue, func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) {
legacyRouter := middleware.NewLegacyRouter()
legacyRouter.AddRoute(sdk.NewRoute(routeMsgKeyValue, func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) {
kv := msg.(*msgKeyValue)
bapp.cms.GetCommitKVStore(capKey2).Set(kv.Key, kv.Value)
return &sdk.Result{}, nil
}))
txHandler, err := middleware.NewDefaultTxHandler(middleware.TxHandlerOptions{
LegacyAnteHandler: func(ctx sdk.Context, tx sdk.Tx, simulate bool) (sdk.Context, error) { return ctx, nil },
LegacyRouter: legacyRouter,
MsgServiceRouter: middleware.NewMsgServiceRouter(interfaceRegistry),
})
require.NoError(t, err)
bapp.SetTxHandler(txHandler)
}
snapshotInterval := uint64(2)
@ -528,7 +540,7 @@ func TestBaseAppOptionSeal(t *testing.T) {
app.SetEndBlocker(nil)
})
require.Panics(t, func() {
app.SetAnteHandler(nil)
app.SetTxHandler(nil)
})
require.Panics(t, func() {
app.SetAddrPeerFilter(nil)
@ -539,9 +551,6 @@ func TestBaseAppOptionSeal(t *testing.T) {
require.Panics(t, func() {
app.SetFauxMerkleMode()
})
require.Panics(t, func() {
app.SetRouter(NewRouter())
})
}
func TestSetMinGasPrices(t *testing.T) {
@ -682,6 +691,7 @@ type txTest struct {
Msgs []sdk.Msg
Counter int64
FailOnAnte bool
GasLimit uint64
}
func (tx *txTest) setFailOnAnte(fail bool) {
@ -698,6 +708,9 @@ func (tx *txTest) setFailOnHandler(fail bool) {
func (tx txTest) GetMsgs() []sdk.Msg { return tx.Msgs }
func (tx txTest) ValidateBasic() error { return nil }
// Implements GasTx
func (tx txTest) GetGas() uint64 { return tx.GasLimit }
const (
routeMsgCounter = "msgCounter"
routeMsgCounter2 = "msgCounter2"
@ -728,13 +741,13 @@ func (msg msgCounter) ValidateBasic() error {
return sdkerrors.Wrap(sdkerrors.ErrInvalidSequence, "counter should be a non-negative integer")
}
func newTxCounter(counter int64, msgCounters ...int64) *txTest {
func newTxCounter(counter int64, msgCounters ...int64) txTest {
msgs := make([]sdk.Msg, 0, len(msgCounters))
for _, c := range msgCounters {
msgs = append(msgs, msgCounter{c, false})
}
return &txTest{msgs, counter, false}
return txTest{msgs, counter, false, math.MaxUint64}
}
// a msg we dont know how to route
@ -916,15 +929,22 @@ func TestCheckTx(t *testing.T) {
// This ensures changes to the kvstore persist across successive CheckTx.
counterKey := []byte("counter-key")
anteOpt := func(bapp *BaseApp) { bapp.SetAnteHandler(anteHandlerTxTest(t, capKey1, counterKey)) }
routerOpt := func(bapp *BaseApp) {
txHandlerOpt := func(bapp *BaseApp) {
legacyRouter := middleware.NewLegacyRouter()
// TODO: can remove this once CheckTx doesnt process msgs.
bapp.Router().AddRoute(sdk.NewRoute(routeMsgCounter, func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) {
legacyRouter.AddRoute(sdk.NewRoute(routeMsgCounter, func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) {
return &sdk.Result{}, nil
}))
txHandler, err := middleware.NewDefaultTxHandler(middleware.TxHandlerOptions{
LegacyRouter: legacyRouter,
LegacyAnteHandler: anteHandlerTxTest(t, capKey1, counterKey),
MsgServiceRouter: middleware.NewMsgServiceRouter(interfaceRegistry),
})
require.NoError(t, err)
bapp.SetTxHandler(txHandler)
}
app := setupBaseApp(t, anteOpt, routerOpt)
app := setupBaseApp(t, txHandlerOpt)
nTxs := int64(5)
app.InitChain(abci.RequestInitChain{})
@ -968,16 +988,21 @@ func TestCheckTx(t *testing.T) {
func TestDeliverTx(t *testing.T) {
// test increments in the ante
anteKey := []byte("ante-key")
anteOpt := func(bapp *BaseApp) { bapp.SetAnteHandler(anteHandlerTxTest(t, capKey1, anteKey)) }
// test increments in the handler
deliverKey := []byte("deliver-key")
routerOpt := func(bapp *BaseApp) {
txHandlerOpt := func(bapp *BaseApp) {
legacyRouter := middleware.NewLegacyRouter()
r := sdk.NewRoute(routeMsgCounter, handlerMsgCounter(t, capKey1, deliverKey))
bapp.Router().AddRoute(r)
legacyRouter.AddRoute(r)
txHandler, err := middleware.NewDefaultTxHandler(middleware.TxHandlerOptions{
LegacyRouter: legacyRouter,
LegacyAnteHandler: anteHandlerTxTest(t, capKey1, anteKey),
MsgServiceRouter: middleware.NewMsgServiceRouter(interfaceRegistry),
})
require.NoError(t, err)
bapp.SetTxHandler(txHandler)
}
app := setupBaseApp(t, anteOpt, routerOpt)
app := setupBaseApp(t, txHandlerOpt)
app.InitChain(abci.RequestInitChain{})
// Create same codec used in txDecoder
@ -1021,19 +1046,24 @@ func TestMultiMsgCheckTx(t *testing.T) {
func TestMultiMsgDeliverTx(t *testing.T) {
// increment the tx counter
anteKey := []byte("ante-key")
anteOpt := func(bapp *BaseApp) { bapp.SetAnteHandler(anteHandlerTxTest(t, capKey1, anteKey)) }
// increment the msg counter
deliverKey := []byte("deliver-key")
deliverKey2 := []byte("deliver-key2")
routerOpt := func(bapp *BaseApp) {
txHandlerOpt := func(bapp *BaseApp) {
legacyRouter := middleware.NewLegacyRouter()
r1 := sdk.NewRoute(routeMsgCounter, handlerMsgCounter(t, capKey1, deliverKey))
r2 := sdk.NewRoute(routeMsgCounter2, handlerMsgCounter(t, capKey1, deliverKey2))
bapp.Router().AddRoute(r1)
bapp.Router().AddRoute(r2)
legacyRouter.AddRoute(r1)
legacyRouter.AddRoute(r2)
txHandler, err := middleware.NewDefaultTxHandler(middleware.TxHandlerOptions{
LegacyRouter: legacyRouter,
LegacyAnteHandler: anteHandlerTxTest(t, capKey1, anteKey),
MsgServiceRouter: middleware.NewMsgServiceRouter(interfaceRegistry),
})
require.NoError(t, err)
bapp.SetTxHandler(txHandler)
}
app := setupBaseApp(t, anteOpt, routerOpt)
app := setupBaseApp(t, txHandlerOpt)
// Create same codec used in txDecoder
codec := codec.NewLegacyAmino()
@ -1097,22 +1127,22 @@ func TestConcurrentCheckDeliver(t *testing.T) {
func TestSimulateTx(t *testing.T) {
gasConsumed := uint64(5)
anteOpt := func(bapp *BaseApp) {
bapp.SetAnteHandler(func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, err error) {
newCtx = ctx.WithGasMeter(sdk.NewGasMeter(gasConsumed))
return
})
}
routerOpt := func(bapp *BaseApp) {
txHandlerOpt := func(bapp *BaseApp) {
legacyRouter := middleware.NewLegacyRouter()
r := sdk.NewRoute(routeMsgCounter, func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) {
ctx.GasMeter().ConsumeGas(gasConsumed, "test")
return &sdk.Result{}, nil
})
bapp.Router().AddRoute(r)
legacyRouter.AddRoute(r)
txHandler, err := middleware.NewDefaultTxHandler(middleware.TxHandlerOptions{
LegacyRouter: legacyRouter,
LegacyAnteHandler: func(ctx sdk.Context, tx sdk.Tx, simulate bool) (sdk.Context, error) { return ctx, nil },
MsgServiceRouter: middleware.NewMsgServiceRouter(interfaceRegistry),
})
require.NoError(t, err)
bapp.SetTxHandler(txHandler)
}
app := setupBaseApp(t, anteOpt, routerOpt)
app := setupBaseApp(t, txHandlerOpt)
app.InitChain(abci.RequestInitChain{})
@ -1127,6 +1157,7 @@ func TestSimulateTx(t *testing.T) {
app.BeginBlock(abci.RequestBeginBlock{Header: header})
tx := newTxCounter(count, count)
tx.GasLimit = gasConsumed
txBytes, err := cdc.Marshal(tx)
require.Nil(t, err)
@ -1164,19 +1195,23 @@ func TestSimulateTx(t *testing.T) {
}
func TestRunInvalidTransaction(t *testing.T) {
anteOpt := func(bapp *BaseApp) {
bapp.SetAnteHandler(func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, err error) {
return
})
}
routerOpt := func(bapp *BaseApp) {
txHandlerOpt := func(bapp *BaseApp) {
legacyRouter := middleware.NewLegacyRouter()
r := sdk.NewRoute(routeMsgCounter, func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) {
return &sdk.Result{}, nil
})
bapp.Router().AddRoute(r)
legacyRouter.AddRoute(r)
txHandler, err := middleware.NewDefaultTxHandler(middleware.TxHandlerOptions{
LegacyRouter: legacyRouter,
LegacyAnteHandler: func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, err error) {
return
},
MsgServiceRouter: middleware.NewMsgServiceRouter(interfaceRegistry),
})
require.NoError(t, err)
bapp.SetTxHandler(txHandler)
}
app := setupBaseApp(t, anteOpt, routerOpt)
app := setupBaseApp(t, txHandlerOpt)
header := tmproto.Header{Height: 1}
app.BeginBlock(abci.RequestBeginBlock{Header: header})
@ -1184,8 +1219,7 @@ func TestRunInvalidTransaction(t *testing.T) {
// transaction with no messages
{
emptyTx := &txTest{}
_, result, err := app.Deliver(aminoTxEncoder(), emptyTx)
require.Error(t, err)
_, result, err := app.SimDeliver(aminoTxEncoder(), emptyTx)
require.Nil(t, result)
space, code, _ := sdkerrors.ABCIInfo(err, false)
@ -1196,7 +1230,7 @@ func TestRunInvalidTransaction(t *testing.T) {
// transaction where ValidateBasic fails
{
testCases := []struct {
tx *txTest
tx txTest
fail bool
}{
{newTxCounter(0, 0), false},
@ -1211,7 +1245,7 @@ func TestRunInvalidTransaction(t *testing.T) {
for _, testCase := range testCases {
tx := testCase.tx
_, result, err := app.Deliver(aminoTxEncoder(), tx)
_, result, err := app.SimDeliver(aminoTxEncoder(), tx)
if testCase.fail {
require.Error(t, err)
@ -1227,8 +1261,8 @@ func TestRunInvalidTransaction(t *testing.T) {
// transaction with no known route
{
unknownRouteTx := txTest{[]sdk.Msg{msgNoRoute{}}, 0, false}
_, result, err := app.Deliver(aminoTxEncoder(), unknownRouteTx)
unknownRouteTx := txTest{[]sdk.Msg{msgNoRoute{}}, 0, false, math.MaxUint64}
_, result, err := app.SimDeliver(aminoTxEncoder(), unknownRouteTx)
require.Error(t, err)
require.Nil(t, result)
@ -1236,8 +1270,8 @@ func TestRunInvalidTransaction(t *testing.T) {
require.EqualValues(t, sdkerrors.ErrUnknownRequest.Codespace(), space, err)
require.EqualValues(t, sdkerrors.ErrUnknownRequest.ABCICode(), code, err)
unknownRouteTx = txTest{[]sdk.Msg{msgCounter{}, msgNoRoute{}}, 0, false}
_, result, err = app.Deliver(aminoTxEncoder(), unknownRouteTx)
unknownRouteTx = txTest{[]sdk.Msg{msgCounter{}, msgNoRoute{}}, 0, false, math.MaxUint64}
_, result, err = app.SimDeliver(aminoTxEncoder(), unknownRouteTx)
require.Error(t, err)
require.Nil(t, result)
@ -1268,49 +1302,36 @@ func TestRunInvalidTransaction(t *testing.T) {
// Test that transactions exceeding gas limits fail
func TestTxGasLimits(t *testing.T) {
gasGranted := uint64(10)
anteOpt := func(bapp *BaseApp) {
bapp.SetAnteHandler(func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, err error) {
newCtx = ctx.WithGasMeter(sdk.NewGasMeter(gasGranted))
// AnteHandlers must have their own defer/recover in order for the BaseApp
// to know how much gas was used! This is because the GasMeter is created in
// the AnteHandler, but if it panics the context won't be set properly in
// runTx's recover call.
defer func() {
if r := recover(); r != nil {
switch rType := r.(type) {
case sdk.ErrorOutOfGas:
err = sdkerrors.Wrapf(sdkerrors.ErrOutOfGas, "out of gas in location: %v", rType.Descriptor)
default:
panic(r)
}
}
}()
count := tx.(txTest).Counter
newCtx.GasMeter().ConsumeGas(uint64(count), "counter-ante")
return newCtx, nil
})
ante := func(ctx sdk.Context, tx sdk.Tx, simulate bool) (sdk.Context, error) {
count := tx.(txTest).Counter
ctx.GasMeter().ConsumeGas(uint64(count), "counter-ante")
return ctx, nil
}
routerOpt := func(bapp *BaseApp) {
txHandlerOpt := func(bapp *BaseApp) {
legacyRouter := middleware.NewLegacyRouter()
r := sdk.NewRoute(routeMsgCounter, func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) {
count := msg.(*msgCounter).Counter
count := msg.(msgCounter).Counter
ctx.GasMeter().ConsumeGas(uint64(count), "counter-handler")
return &sdk.Result{}, nil
})
bapp.Router().AddRoute(r)
legacyRouter.AddRoute(r)
txHandler, err := middleware.NewDefaultTxHandler(middleware.TxHandlerOptions{
LegacyRouter: legacyRouter,
LegacyAnteHandler: ante,
MsgServiceRouter: middleware.NewMsgServiceRouter(interfaceRegistry),
})
require.NoError(t, err)
bapp.SetTxHandler(txHandler)
}
app := setupBaseApp(t, anteOpt, routerOpt)
app := setupBaseApp(t, txHandlerOpt)
header := tmproto.Header{Height: 1}
app.BeginBlock(abci.RequestBeginBlock{Header: header})
testCases := []struct {
tx *txTest
tx txTest
gasUsed uint64
fail bool
}{
@ -1335,7 +1356,8 @@ func TestTxGasLimits(t *testing.T) {
for i, tc := range testCases {
tx := tc.tx
gInfo, result, err := app.Deliver(aminoTxEncoder(), tx)
tx.GasLimit = gasGranted
gInfo, result, err := app.SimDeliver(aminoTxEncoder(), tx)
// check gas used and wanted
require.Equal(t, tc.gasUsed, gInfo.GasUsed, fmt.Sprintf("tc #%d; gas: %v, result: %v, err: %s", i, gInfo, result, err))
@ -1357,38 +1379,31 @@ func TestTxGasLimits(t *testing.T) {
// Test that transactions exceeding gas limits fail
func TestMaxBlockGasLimits(t *testing.T) {
gasGranted := uint64(10)
anteOpt := func(bapp *BaseApp) {
bapp.SetAnteHandler(func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, err error) {
newCtx = ctx.WithGasMeter(sdk.NewGasMeter(gasGranted))
ante := func(ctx sdk.Context, tx sdk.Tx, simulate bool) (sdk.Context, error) {
count := tx.(txTest).Counter
ctx.GasMeter().ConsumeGas(uint64(count), "counter-ante")
defer func() {
if r := recover(); r != nil {
switch rType := r.(type) {
case sdk.ErrorOutOfGas:
err = sdkerrors.Wrapf(sdkerrors.ErrOutOfGas, "out of gas in location: %v", rType.Descriptor)
default:
panic(r)
}
}
}()
count := tx.(txTest).Counter
newCtx.GasMeter().ConsumeGas(uint64(count), "counter-ante")
return
})
return ctx, nil
}
routerOpt := func(bapp *BaseApp) {
txHandlerOpt := func(bapp *BaseApp) {
legacyRouter := middleware.NewLegacyRouter()
r := sdk.NewRoute(routeMsgCounter, func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) {
count := msg.(*msgCounter).Counter
count := msg.(msgCounter).Counter
ctx.GasMeter().ConsumeGas(uint64(count), "counter-handler")
return &sdk.Result{}, nil
})
bapp.Router().AddRoute(r)
legacyRouter.AddRoute(r)
txHandler, err := middleware.NewDefaultTxHandler(middleware.TxHandlerOptions{
LegacyRouter: legacyRouter,
LegacyAnteHandler: ante,
MsgServiceRouter: middleware.NewMsgServiceRouter(interfaceRegistry),
})
require.NoError(t, err)
bapp.SetTxHandler(txHandler)
}
app := setupBaseApp(t, txHandlerOpt)
app := setupBaseApp(t, anteOpt, routerOpt)
app.InitChain(abci.RequestInitChain{
ConsensusParams: &abci.ConsensusParams{
Block: &abci.BlockParams{
@ -1398,7 +1413,7 @@ func TestMaxBlockGasLimits(t *testing.T) {
})
testCases := []struct {
tx *txTest
tx txTest
numDelivers int
gasUsedPerDeliver uint64
fail bool
@ -1418,6 +1433,7 @@ func TestMaxBlockGasLimits(t *testing.T) {
for i, tc := range testCases {
tx := tc.tx
tx.GasLimit = gasGranted
// reset the block gas
header := tmproto.Header{Height: app.LastBlockHeight() + 1}
@ -1425,7 +1441,7 @@ func TestMaxBlockGasLimits(t *testing.T) {
// execute the transaction multiple times
for j := 0; j < tc.numDelivers; j++ {
_, result, err := app.Deliver(aminoTxEncoder(), tx)
_, result, err := app.SimDeliver(aminoTxEncoder(), tx)
ctx := app.getState(runTxModeDeliver).ctx
@ -1454,63 +1470,24 @@ func TestMaxBlockGasLimits(t *testing.T) {
}
}
// Test custom panic handling within app.DeliverTx method
func TestCustomRunTxPanicHandler(t *testing.T) {
const customPanicMsg = "test panic"
anteErr := sdkerrors.Register("fakeModule", 100500, "fakeError")
anteOpt := func(bapp *BaseApp) {
bapp.SetAnteHandler(func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, err error) {
panic(sdkerrors.Wrap(anteErr, "anteHandler"))
})
}
routerOpt := func(bapp *BaseApp) {
r := sdk.NewRoute(routeMsgCounter, func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) {
return &sdk.Result{}, nil
})
bapp.Router().AddRoute(r)
}
app := setupBaseApp(t, anteOpt, routerOpt)
header := tmproto.Header{Height: 1}
app.BeginBlock(abci.RequestBeginBlock{Header: header})
app.AddRunTxRecoveryHandler(func(recoveryObj interface{}) error {
err, ok := recoveryObj.(error)
if !ok {
return nil
}
if anteErr.Is(err) {
panic(customPanicMsg)
} else {
return nil
}
})
// Transaction should panic with custom handler above
{
tx := newTxCounter(0, 0)
require.PanicsWithValue(t, customPanicMsg, func() { app.Deliver(aminoTxEncoder(), tx) })
}
}
func TestBaseAppAnteHandler(t *testing.T) {
anteKey := []byte("ante-key")
anteOpt := func(bapp *BaseApp) {
bapp.SetAnteHandler(anteHandlerTxTest(t, capKey1, anteKey))
}
deliverKey := []byte("deliver-key")
routerOpt := func(bapp *BaseApp) {
r := sdk.NewRoute(routeMsgCounter, handlerMsgCounter(t, capKey1, deliverKey))
bapp.Router().AddRoute(r)
}
cdc := codec.NewLegacyAmino()
app := setupBaseApp(t, anteOpt, routerOpt)
txHandlerOpt := func(bapp *BaseApp) {
legacyRouter := middleware.NewLegacyRouter()
r := sdk.NewRoute(routeMsgCounter, handlerMsgCounter(t, capKey1, deliverKey))
legacyRouter.AddRoute(r)
txHandler, err := middleware.NewDefaultTxHandler(middleware.TxHandlerOptions{
LegacyRouter: legacyRouter,
LegacyAnteHandler: anteHandlerTxTest(t, capKey1, anteKey),
MsgServiceRouter: middleware.NewMsgServiceRouter(interfaceRegistry),
})
require.NoError(t, err)
bapp.SetTxHandler(txHandler)
}
app := setupBaseApp(t, txHandlerOpt)
app.InitChain(abci.RequestInitChain{})
registerTestCodec(cdc)
@ -1574,45 +1551,37 @@ func TestBaseAppAnteHandler(t *testing.T) {
func TestGasConsumptionBadTx(t *testing.T) {
gasWanted := uint64(5)
anteOpt := func(bapp *BaseApp) {
bapp.SetAnteHandler(func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, err error) {
newCtx = ctx.WithGasMeter(sdk.NewGasMeter(gasWanted))
ante := func(ctx sdk.Context, tx sdk.Tx, simulate bool) (sdk.Context, error) {
txTest := tx.(txTest)
ctx.GasMeter().ConsumeGas(uint64(txTest.Counter), "counter-ante")
if txTest.FailOnAnte {
return ctx, sdkerrors.Wrap(sdkerrors.ErrUnauthorized, "ante handler failure")
}
defer func() {
if r := recover(); r != nil {
switch rType := r.(type) {
case sdk.ErrorOutOfGas:
log := fmt.Sprintf("out of gas in location: %v", rType.Descriptor)
err = sdkerrors.Wrap(sdkerrors.ErrOutOfGas, log)
default:
panic(r)
}
}
}()
txTest := tx.(txTest)
newCtx.GasMeter().ConsumeGas(uint64(txTest.Counter), "counter-ante")
if txTest.FailOnAnte {
return newCtx, sdkerrors.Wrap(sdkerrors.ErrUnauthorized, "ante handler failure")
}
return
})
}
routerOpt := func(bapp *BaseApp) {
r := sdk.NewRoute(routeMsgCounter, func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) {
count := msg.(*msgCounter).Counter
ctx.GasMeter().ConsumeGas(uint64(count), "counter-handler")
return &sdk.Result{}, nil
})
bapp.Router().AddRoute(r)
return ctx, nil
}
cdc := codec.NewLegacyAmino()
registerTestCodec(cdc)
app := setupBaseApp(t, anteOpt, routerOpt)
txHandlerOpt := func(bapp *BaseApp) {
legacyRouter := middleware.NewLegacyRouter()
r := sdk.NewRoute(routeMsgCounter, func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) {
count := msg.(msgCounter).Counter
ctx.GasMeter().ConsumeGas(uint64(count), "counter-handler")
return &sdk.Result{}, nil
})
legacyRouter.AddRoute(r)
txHandler, err := middleware.NewDefaultTxHandler(middleware.TxHandlerOptions{
LegacyRouter: legacyRouter,
LegacyAnteHandler: ante,
MsgServiceRouter: middleware.NewMsgServiceRouter(interfaceRegistry),
})
require.NoError(t, err)
bapp.SetTxHandler(txHandler)
}
app := setupBaseApp(t, txHandlerOpt)
app.InitChain(abci.RequestInitChain{
ConsensusParams: &abci.ConsensusParams{
Block: &abci.BlockParams{
@ -1627,6 +1596,7 @@ func TestGasConsumptionBadTx(t *testing.T) {
app.BeginBlock(abci.RequestBeginBlock{Header: header})
tx := newTxCounter(5, 0)
tx.GasLimit = gasWanted
tx.setFailOnAnte(true)
txBytes, err := cdc.Marshal(tx)
require.NoError(t, err)
@ -1646,24 +1616,28 @@ func TestGasConsumptionBadTx(t *testing.T) {
// Test that we can only query from the latest committed state.
func TestQuery(t *testing.T) {
key, value := []byte("hello"), []byte("goodbye")
anteOpt := func(bapp *BaseApp) {
bapp.SetAnteHandler(func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, err error) {
store := ctx.KVStore(capKey1)
store.Set(key, value)
return
})
}
routerOpt := func(bapp *BaseApp) {
txHandlerOpt := func(bapp *BaseApp) {
legacyRouter := middleware.NewLegacyRouter()
r := sdk.NewRoute(routeMsgCounter, func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) {
store := ctx.KVStore(capKey1)
store.Set(key, value)
return &sdk.Result{}, nil
})
bapp.Router().AddRoute(r)
legacyRouter.AddRoute(r)
txHandler, err := middleware.NewDefaultTxHandler(middleware.TxHandlerOptions{
LegacyRouter: legacyRouter,
LegacyAnteHandler: func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, err error) {
store := ctx.KVStore(capKey1)
store.Set(key, value)
return
},
MsgServiceRouter: middleware.NewMsgServiceRouter(interfaceRegistry),
})
require.NoError(t, err)
bapp.SetTxHandler(txHandler)
}
app := setupBaseApp(t, anteOpt, routerOpt)
app := setupBaseApp(t, txHandlerOpt)
app.InitChain(abci.RequestInitChain{})
@ -1681,7 +1655,7 @@ func TestQuery(t *testing.T) {
require.Equal(t, 0, len(res.Value))
// query is still empty after a CheckTx
_, resTx, err := app.Check(aminoTxEncoder(), tx)
_, resTx, err := app.SimCheck(aminoTxEncoder(), tx)
require.NoError(t, err)
require.NotNil(t, resTx)
res = app.Query(query)
@ -1691,7 +1665,7 @@ func TestQuery(t *testing.T) {
header := tmproto.Header{Height: app.LastBlockHeight() + 1}
app.BeginBlock(abci.RequestBeginBlock{Header: header})
_, resTx, err = app.Deliver(aminoTxEncoder(), tx)
_, resTx, err = app.SimDeliver(aminoTxEncoder(), tx)
require.NoError(t, err)
require.NotNil(t, resTx)
res = app.Query(query)
@ -1968,17 +1942,22 @@ func (rtr *testCustomRouter) Route(ctx sdk.Context, path string) sdk.Handler {
func TestWithRouter(t *testing.T) {
// test increments in the ante
anteKey := []byte("ante-key")
anteOpt := func(bapp *BaseApp) { bapp.SetAnteHandler(anteHandlerTxTest(t, capKey1, anteKey)) }
// test increments in the handler
deliverKey := []byte("deliver-key")
routerOpt := func(bapp *BaseApp) {
bapp.SetRouter(&testCustomRouter{routes: sync.Map{}})
r := sdk.NewRoute(routeMsgCounter, handlerMsgCounter(t, capKey1, deliverKey))
bapp.Router().AddRoute(r)
}
app := setupBaseApp(t, anteOpt, routerOpt)
txHandlerOpt := func(bapp *BaseApp) {
customRouter := &testCustomRouter{routes: sync.Map{}}
r := sdk.NewRoute(routeMsgCounter, handlerMsgCounter(t, capKey1, deliverKey))
customRouter.AddRoute(r)
txHandler, err := middleware.NewDefaultTxHandler(middleware.TxHandlerOptions{
LegacyRouter: customRouter,
LegacyAnteHandler: anteHandlerTxTest(t, capKey1, anteKey),
MsgServiceRouter: middleware.NewMsgServiceRouter(interfaceRegistry),
})
require.NoError(t, err)
bapp.SetTxHandler(txHandler)
}
app := setupBaseApp(t, txHandlerOpt)
app.InitChain(abci.RequestInitChain{})
// Create same codec used in txDecoder

View File

@ -10,6 +10,7 @@ import (
"github.com/cosmos/cosmos-sdk/snapshots"
"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,
@ -148,12 +149,12 @@ func (app *BaseApp) SetEndBlocker(endBlocker sdk.EndBlocker) {
app.endBlocker = endBlocker
}
func (app *BaseApp) SetAnteHandler(ah sdk.AnteHandler) {
func (app *BaseApp) SetTxHandler(txHandler tx.Handler) {
if app.sealed {
panic("SetAnteHandler() on sealed BaseApp")
panic("SetTxHandler() on sealed BaseApp")
}
app.anteHandler = ah
app.txHandler = txHandler
}
func (app *BaseApp) SetAddrPeerFilter(pf sdk.PeerFilter) {
@ -195,14 +196,6 @@ 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
}
// SetSnapshotStore sets the snapshot store.
func (app *BaseApp) SetSnapshotStore(snapshotStore *snapshots.Store) {
if app.sealed {
@ -235,5 +228,4 @@ func (app *BaseApp) SetSnapshotKeepRecent(snapshotKeepRecent uint32) {
func (app *BaseApp) SetInterfaceRegistry(registry types.InterfaceRegistry) {
app.interfaceRegistry = registry
app.grpcQueryRouter.SetInterfaceRegistry(registry)
app.msgServiceRouter.SetInterfaceRegistry(registry)
}

View File

@ -1,77 +0,0 @@
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)
}

View File

@ -1,64 +0,0 @@
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,34 +1,67 @@
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"
)
func (app *BaseApp) Check(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
// SimCheck defines a CheckTx helper function that used in tests and simulations.
func (app *BaseApp) SimCheck(txEncoder sdk.TxEncoder, tx 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
// this helper is only used in tests/simulation, it's fine.
bz, err := txEncoder(tx)
if err != nil {
return sdk.GasInfo{}, nil, sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "%s", err)
}
return app.runTx(runTxModeCheck, bz)
ctx := app.getContextForTx(runTxModeDeliver, bz)
res, err := app.txHandler.CheckTx(ctx, tx, abci.RequestCheckTx{Tx: bz, Type: abci.CheckTxType_New})
gInfo := sdk.GasInfo{GasWanted: uint64(res.GasWanted), GasUsed: uint64(res.GasUsed)}
if err != nil {
return gInfo, nil, err
}
return gInfo, &sdk.Result{Data: res.Data, Log: res.Log, Events: res.Events}, nil
}
// Simulate executes a tx in simulate mode to get result and gas info.
func (app *BaseApp) Simulate(txBytes []byte) (sdk.GasInfo, *sdk.Result, error) {
return app.runTx(runTxModeSimulate, txBytes)
sdkTx, err := app.txDecoder(txBytes)
if err != nil {
return sdk.GasInfo{}, nil, err
}
ctx := app.getContextForTx(runTxModeSimulate, txBytes)
res, err := app.txHandler.SimulateTx(ctx, sdkTx, tx.RequestSimulateTx{TxBytes: txBytes})
if err != nil {
return res.GasInfo, nil, err
}
return res.GasInfo, res.Result, nil
}
func (app *BaseApp) Deliver(txEncoder sdk.TxEncoder, tx sdk.Tx) (sdk.GasInfo, *sdk.Result, error) {
// SimDeliver defines a DeliverTx helper function that used in tests and
// simulations.
func (app *BaseApp) SimDeliver(txEncoder sdk.TxEncoder, tx sdk.Tx) (sdk.GasInfo, *sdk.Result, error) {
// See comment for Check().
bz, err := txEncoder(tx)
if err != nil {
return sdk.GasInfo{}, nil, sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "%s", err)
}
return app.runTx(runTxModeDeliver, bz)
ctx := app.getContextForTx(runTxModeDeliver, bz)
res, err := app.txHandler.DeliverTx(ctx, tx, abci.RequestDeliverTx{Tx: bz})
gInfo := sdk.GasInfo{GasWanted: uint64(res.GasWanted), GasUsed: uint64(res.GasUsed)}
if err != nil {
return gInfo, nil, err
}
return gInfo, &sdk.Result{Data: res.Data, Log: res.Log, Events: res.Events}, nil
}
// Context with current {check, deliver}State of the app used by tests.

View File

@ -13,7 +13,9 @@ import (
bam "github.com/cosmos/cosmos-sdk/baseapp"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/simapp"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth/middleware"
)
// NewApp creates a simple mock kvstore app for testing. It should work
@ -37,7 +39,19 @@ func NewApp(rootDir string, logger log.Logger) (abci.Application, error) {
baseApp.SetInitChainer(InitChainer(capKeyMainStore))
// Set a Route.
baseApp.Router().AddRoute(sdk.NewRoute("kvstore", KVStoreHandler(capKeyMainStore)))
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, err := middleware.NewDefaultTxHandler(middleware.TxHandlerOptions{
LegacyRouter: legacyRouter,
MsgServiceRouter: middleware.NewMsgServiceRouter(encCfg.InterfaceRegistry),
})
if err != nil {
return nil, err
}
baseApp.SetTxHandler(txHandler)
// Load latest version.
if err := baseApp.LoadLatestVersion(); err != nil {

View File

@ -4,12 +4,16 @@ 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"
)
// An sdk.Tx which is its own sdk.Msg.
// 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.
type kvstoreTx struct {
key []byte
value []byte
@ -23,6 +27,7 @@ func (msg kvstoreTx) ProtoMessage() {}
var _ sdk.Tx = kvstoreTx{}
var _ sdk.Msg = kvstoreTx{}
var _ middleware.GasTx = kvstoreTx{}
func NewTx(key, value string) kvstoreTx {
bytes := fmt.Sprintf("%s=%s", key, value)
@ -62,6 +67,10 @@ 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) {

View File

@ -20,6 +20,7 @@ import (
"github.com/cosmos/cosmos-sdk/client/grpc/tmservice"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/codec/types"
"github.com/cosmos/cosmos-sdk/server"
"github.com/cosmos/cosmos-sdk/server/api"
"github.com/cosmos/cosmos-sdk/server/config"
servertypes "github.com/cosmos/cosmos-sdk/server/types"
@ -42,6 +43,7 @@ import (
capabilitykeeper "github.com/cosmos/cosmos-sdk/x/capability/keeper"
capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/types"
authmiddleware "github.com/cosmos/cosmos-sdk/x/auth/middleware"
"github.com/cosmos/cosmos-sdk/x/authz"
authzkeeper "github.com/cosmos/cosmos-sdk/x/authz/keeper"
authzmodule "github.com/cosmos/cosmos-sdk/x/authz/module"
@ -140,6 +142,8 @@ type SimApp struct {
legacyAmino *codec.LegacyAmino
appCodec codec.Codec
interfaceRegistry types.InterfaceRegistry
msgSvcRouter *authmiddleware.MsgServiceRouter
legacyRouter sdk.Router
invCheckPeriod uint
@ -216,6 +220,8 @@ func NewSimApp(
legacyAmino: legacyAmino,
appCodec: appCodec,
interfaceRegistry: interfaceRegistry,
legacyRouter: authmiddleware.NewLegacyRouter(),
msgSvcRouter: authmiddleware.NewMsgServiceRouter(interfaceRegistry),
invCheckPeriod: invCheckPeriod,
keys: keys,
tkeys: tkeys,
@ -266,7 +272,7 @@ func NewSimApp(
stakingtypes.NewMultiStakingHooks(app.DistrKeeper.Hooks(), app.SlashingKeeper.Hooks()),
)
app.AuthzKeeper = authzkeeper.NewKeeper(keys[authzkeeper.StoreKey], appCodec, app.BaseApp.MsgServiceRouter())
app.AuthzKeeper = authzkeeper.NewKeeper(keys[authzkeeper.StoreKey], appCodec, app.msgSvcRouter)
// register the proposal types
govRouter := govtypes.NewRouter()
@ -346,8 +352,8 @@ func NewSimApp(
)
app.mm.RegisterInvariants(&app.CrisisKeeper)
app.mm.RegisterRoutes(app.Router(), app.QueryRouter(), encodingConfig.Amino)
app.configurator = module.NewConfigurator(app.appCodec, app.MsgServiceRouter(), app.GRPCQueryRouter())
app.mm.RegisterRoutes(app.legacyRouter, app.QueryRouter(), encodingConfig.Amino)
app.configurator = module.NewConfigurator(app.appCodec, app.msgSvcRouter, app.GRPCQueryRouter())
app.mm.RegisterServices(app.configurator)
// add test gRPC service for testing gRPC queries in isolation
@ -382,23 +388,8 @@ func NewSimApp(
// initialize BaseApp
app.SetInitChainer(app.InitChainer)
app.SetBeginBlocker(app.BeginBlocker)
anteHandler, err := ante.NewAnteHandler(
ante.HandlerOptions{
AccountKeeper: app.AccountKeeper,
BankKeeper: app.BankKeeper,
SignModeHandler: encodingConfig.TxConfig.SignModeHandler(),
FeegrantKeeper: app.FeeGrantKeeper,
SigGasConsumer: ante.DefaultSigVerificationGasConsumer,
},
)
if err != nil {
panic(err)
}
app.SetAnteHandler(anteHandler)
app.SetEndBlocker(app.EndBlocker)
app.setTxHandler(encodingConfig.TxConfig, cast.ToStringSlice(appOpts.Get(server.FlagIndexEvents)))
if loadLatest {
if err := app.LoadLatestVersion(); err != nil {
@ -409,6 +400,38 @@ func NewSimApp(
return app
}
func (app *SimApp) setTxHandler(txConfig client.TxConfig, indexEventsStr []string) {
anteHandler, err := ante.NewAnteHandler(
ante.HandlerOptions{
AccountKeeper: app.AccountKeeper,
BankKeeper: app.BankKeeper,
SignModeHandler: txConfig.SignModeHandler(),
FeegrantKeeper: app.FeeGrantKeeper,
SigGasConsumer: ante.DefaultSigVerificationGasConsumer,
},
)
if err != nil {
panic(err)
}
indexEvents := map[string]struct{}{}
for _, e := range indexEventsStr {
indexEvents[e] = struct{}{}
}
txHandler, err := authmiddleware.NewDefaultTxHandler(authmiddleware.TxHandlerOptions{
Debug: app.Trace(),
IndexEvents: indexEvents,
LegacyRouter: app.legacyRouter,
MsgServiceRouter: app.msgSvcRouter,
LegacyAnteHandler: anteHandler,
})
if err != nil {
panic(err)
}
app.SetTxHandler(txHandler)
}
// Name returns the name of the App
func (app *SimApp) Name() string { return app.BaseApp.Name() }

View File

@ -17,6 +17,7 @@ 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"
@ -82,8 +83,9 @@ func TestRunMigrations(t *testing.T) {
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, app.MsgServiceRouter(), app.GRPCQueryRouter())
app.configurator = module.NewConfigurator(app.appCodec, msr, 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

@ -352,7 +352,7 @@ func SignCheckDeliver(
// Simulate a sending a transaction and committing a block
app.BeginBlock(abci.RequestBeginBlock{Header: header})
gInfo, res, err := app.Deliver(txCfg.TxEncoder(), tx)
gInfo, res, err := app.SimDeliver(txCfg.TxEncoder(), tx)
if expPass {
require.NoError(t, err)

33
types/tx/middleware.go Normal file
View File

@ -0,0 +1,33 @@
package tx
import (
context "context"
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
}
// 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, tx sdk.Tx, req abci.RequestCheckTx) (abci.ResponseCheckTx, error)
DeliverTx(ctx context.Context, tx sdk.Tx, req abci.RequestDeliverTx) (abci.ResponseDeliverTx, error)
SimulateTx(ctx context.Context, tx sdk.Tx, req RequestSimulateTx) (ResponseSimulateTx, error)
}
// TxMiddleware defines one layer of the TxHandler middleware stack.
type Middleware func(Handler) Handler

View File

@ -39,7 +39,6 @@ func NewAnteHandler(options HandlerOptions) (sdk.AnteHandler, error) {
}
anteDecorators := []sdk.AnteDecorator{
NewSetUpContextDecorator(), // outermost AnteDecorator. SetUpContext must be called first
NewRejectExtensionOptionsDecorator(),
NewMempoolFeeDecorator(),
NewValidateBasicDecorator(),

View File

@ -1,101 +0,0 @@
package ante_test
import (
"math"
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
"github.com/cosmos/cosmos-sdk/testutil/testdata"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/cosmos/cosmos-sdk/x/auth/ante"
)
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)
// Context GasMeter Limit set to MaxUint64
suite.Require().Equal(uint64(math.MaxUint64), suite.ctx.GasMeter().Limit(), "GasMeter set with limit other than MaxUint64 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

@ -63,18 +63,32 @@ func (suite *AnteTestSuite) SetupTest(isCheckTx bool) {
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,
},
)
// We're not using ante.NewAnteHandler here because:
// - ante.NewAnteHandler doesn't have SetUpContextDecorator, as it has been
// moved to the gas TxMiddleware
// - whereas these tests have not been migrated to middlewares yet, so
// still need the SetUpContextDecorator.
//
// TODO: migrate all antehandler tests to middleware tests.
// https://github.com/cosmos/cosmos-sdk/issues/9585
anteDecorators := []sdk.AnteDecorator{
ante.NewSetUpContextDecorator(),
ante.NewRejectExtensionOptionsDecorator(),
ante.NewMempoolFeeDecorator(),
ante.NewValidateBasicDecorator(),
ante.NewTxTimeoutHeightDecorator(),
ante.NewValidateMemoDecorator(suite.app.AccountKeeper),
ante.NewConsumeGasForTxSizeDecorator(suite.app.AccountKeeper),
ante.NewDeductFeeDecorator(suite.app.AccountKeeper, suite.app.BankKeeper, suite.app.FeeGrantKeeper),
// SetPubKeyDecorator must be called before all signature verification decorators
ante.NewSetPubKeyDecorator(suite.app.AccountKeeper),
ante.NewValidateSigCountDecorator(suite.app.AccountKeeper),
ante.NewSigGasConsumeDecorator(suite.app.AccountKeeper, ante.DefaultSigVerificationGasConsumer),
ante.NewSigVerificationDecorator(suite.app.AccountKeeper, encodingConfig.TxConfig.SignModeHandler()),
ante.NewIncrementSequenceDecorator(suite.app.AccountKeeper),
}
suite.Require().NoError(err)
suite.anteHandler = anteHandler
suite.anteHandler = sdk.ChainAnteDecorators(anteDecorators...)
}
// CreateTestAccounts creates `numAccs` accounts, and return all relevant

99
x/auth/middleware/gas.go Normal file
View File

@ -0,0 +1,99 @@
package middleware
import (
"context"
abci "github.com/tendermint/tendermint/abci/types"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/cosmos/cosmos-sdk/types/tx"
)
// 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, tx sdk.Tx, req abci.RequestCheckTx) (abci.ResponseCheckTx, error) {
sdkCtx, err := gasContext(sdk.UnwrapSDKContext(ctx), tx, false)
if err != nil {
return abci.ResponseCheckTx{}, err
}
res, err := txh.next.CheckTx(sdk.WrapSDKContext(sdkCtx), tx, req)
res.GasUsed = int64(sdkCtx.GasMeter().GasConsumed())
res.GasWanted = int64(sdkCtx.GasMeter().Limit())
return res, err
}
// DeliverTx implements tx.Handler.DeliverTx.
func (txh gasTxHandler) DeliverTx(ctx context.Context, tx sdk.Tx, req abci.RequestDeliverTx) (abci.ResponseDeliverTx, error) {
sdkCtx, err := gasContext(sdk.UnwrapSDKContext(ctx), tx, false)
if err != nil {
return abci.ResponseDeliverTx{}, err
}
res, err := txh.next.DeliverTx(sdk.WrapSDKContext(sdkCtx), tx, req)
res.GasUsed = int64(sdkCtx.GasMeter().GasConsumed())
res.GasWanted = int64(sdkCtx.GasMeter().Limit())
return res, err
}
// SimulateTx implements tx.Handler.SimulateTx method.
func (txh gasTxHandler) SimulateTx(ctx context.Context, sdkTx sdk.Tx, req tx.RequestSimulateTx) (tx.ResponseSimulateTx, error) {
sdkCtx, err := gasContext(sdk.UnwrapSDKContext(ctx), sdkTx, true)
if err != nil {
return tx.ResponseSimulateTx{}, err
}
res, err := txh.next.SimulateTx(sdk.WrapSDKContext(sdkCtx), sdkTx, req)
res.GasInfo = sdk.GasInfo{
GasWanted: sdkCtx.GasMeter().Limit(),
GasUsed: sdkCtx.GasMeter().GasConsumed(),
}
return res, err
}
// 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
// during runTx.
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

@ -0,0 +1,138 @@
package middleware_test
import (
"context"
"errors"
abci "github.com/tendermint/tendermint/abci/types"
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
"github.com/cosmos/cosmos-sdk/testutil/testdata"
sdk "github.com/cosmos/cosmos-sdk/types"
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() {
tx, _, 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", tx, gasLimit, false, ""},
}
for _, tc := range testcases {
s.Run(tc.name, func() {
res, err := txHandler.CheckTx(sdk.WrapSDKContext(ctx), tc.tx, abci.RequestCheckTx{})
if tc.expErr {
s.Require().EqualError(err, tc.errorStr)
} else {
s.Require().Nil(err, "SetUpContextDecorator returned error")
s.Require().Equal(tc.expGasLimit, uint64(res.GasWanted))
}
})
}
}
func (s *MWTestSuite) TestRecoverPanic() {
tx, txBytes, ctx, gasLimit := s.setupGasTx()
txHandler := middleware.ComposeMiddlewares(outOfGasTxHandler{}, middleware.GasTxMiddleware, middleware.RecoveryTxMiddleware)
res, err := txHandler.CheckTx(sdk.WrapSDKContext(ctx), tx, abci.RequestCheckTx{Tx: txBytes})
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, abci.RequestCheckTx{Tx: txBytes}) }, "Recovered from non-Out-of-Gas panic")
}
// outOfGasTxHandler is a test middleware that will throw OutOfGas panic.
type outOfGasTxHandler struct{}
var _ tx.Handler = outOfGasTxHandler{}
func (txh outOfGasTxHandler) DeliverTx(ctx context.Context, _ sdk.Tx, _ abci.RequestDeliverTx) (abci.ResponseDeliverTx, error) {
sdkCtx := sdk.UnwrapSDKContext(ctx)
overLimit := sdkCtx.GasMeter().Limit() + 1
// Should panic with outofgas error
sdkCtx.GasMeter().ConsumeGas(overLimit, "test panic")
panic("not reached")
}
func (txh outOfGasTxHandler) CheckTx(ctx context.Context, _ sdk.Tx, _ abci.RequestCheckTx) (abci.ResponseCheckTx, error) {
sdkCtx := sdk.UnwrapSDKContext(ctx)
overLimit := sdkCtx.GasMeter().Limit() + 1
// Should panic with outofgas error
sdkCtx.GasMeter().ConsumeGas(overLimit, "test panic")
panic("not reached")
}
func (txh outOfGasTxHandler) SimulateTx(ctx context.Context, _ sdk.Tx, _ tx.RequestSimulateTx) (tx.ResponseSimulateTx, error) {
sdkCtx := sdk.UnwrapSDKContext(ctx)
overLimit := sdkCtx.GasMeter().Limit() + 1
// Should panic with outofgas error
sdkCtx.GasMeter().ConsumeGas(overLimit, "test panic")
panic("not reached")
}
// noopTxHandler is a test middleware that does nothing.
type noopTxHandler struct{}
var _ tx.Handler = noopTxHandler{}
func (txh noopTxHandler) CheckTx(_ context.Context, _ sdk.Tx, _ abci.RequestCheckTx) (abci.ResponseCheckTx, error) {
return abci.ResponseCheckTx{}, nil
}
func (txh noopTxHandler) SimulateTx(_ context.Context, _ sdk.Tx, _ tx.RequestSimulateTx) (tx.ResponseSimulateTx, error) {
return tx.ResponseSimulateTx{}, nil
}
func (txh noopTxHandler) DeliverTx(ctx context.Context, _ sdk.Tx, _ abci.RequestDeliverTx) (abci.ResponseDeliverTx, error) {
return abci.ResponseDeliverTx{}, nil
}

View File

@ -0,0 +1,63 @@
package middleware
import (
"context"
abci "github.com/tendermint/tendermint/abci/types"
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{}
inner 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,
inner: txHandler,
}
}
}
var _ tx.Handler = indexEventsTxHandler{}
// CheckTx implements tx.Handler.CheckTx method.
func (txh indexEventsTxHandler) CheckTx(ctx context.Context, tx sdk.Tx, req abci.RequestCheckTx) (abci.ResponseCheckTx, error) {
res, err := txh.inner.CheckTx(ctx, tx, req)
if err != nil {
return res, err
}
res.Events = sdk.MarkEventsToIndex(res.Events, txh.indexEvents)
return res, nil
}
// DeliverTx implements tx.Handler.DeliverTx method.
func (txh indexEventsTxHandler) DeliverTx(ctx context.Context, tx sdk.Tx, req abci.RequestDeliverTx) (abci.ResponseDeliverTx, error) {
res, err := txh.inner.DeliverTx(ctx, tx, 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, tx sdk.Tx, req tx.RequestSimulateTx) (tx.ResponseSimulateTx, error) {
res, err := txh.inner.SimulateTx(ctx, tx, req)
if err != nil {
return res, err
}
res.Result.Events = sdk.MarkEventsToIndex(res.Result.Events, txh.indexEvents)
return res, nil
}

View File

@ -0,0 +1,115 @@
package middleware
import (
"context"
abci "github.com/tendermint/tendermint/abci/types"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/cosmos/cosmos-sdk/types/tx"
)
type legacyAnteTxHandler struct {
anteHandler sdk.AnteHandler
inner tx.Handler
}
func newLegacyAnteMiddleware(anteHandler sdk.AnteHandler) tx.Middleware {
return func(txHandler tx.Handler) tx.Handler {
return legacyAnteTxHandler{
anteHandler: anteHandler,
inner: txHandler,
}
}
}
var _ tx.Handler = legacyAnteTxHandler{}
// CheckTx implements tx.Handler.CheckTx method.
func (txh legacyAnteTxHandler) CheckTx(ctx context.Context, tx sdk.Tx, req abci.RequestCheckTx) (abci.ResponseCheckTx, error) {
sdkCtx, err := txh.runAnte(ctx, tx, req.Tx, false)
if err != nil {
return abci.ResponseCheckTx{}, err
}
return txh.inner.CheckTx(sdk.WrapSDKContext(sdkCtx), tx, req)
}
// DeliverTx implements tx.Handler.DeliverTx method.
func (txh legacyAnteTxHandler) DeliverTx(ctx context.Context, tx sdk.Tx, req abci.RequestDeliverTx) (abci.ResponseDeliverTx, error) {
sdkCtx, err := txh.runAnte(ctx, tx, req.Tx, false)
if err != nil {
return abci.ResponseDeliverTx{}, err
}
return txh.inner.DeliverTx(sdk.WrapSDKContext(sdkCtx), tx, req)
}
// SimulateTx implements tx.Handler.SimulateTx method.
func (txh legacyAnteTxHandler) SimulateTx(ctx context.Context, sdkTx sdk.Tx, req tx.RequestSimulateTx) (tx.ResponseSimulateTx, error) {
sdkCtx, err := txh.runAnte(ctx, sdkTx, req.TxBytes, true)
if err != nil {
return tx.ResponseSimulateTx{}, err
}
return txh.inner.SimulateTx(sdk.WrapSDKContext(sdkCtx), sdkTx, req)
}
func (txh legacyAnteTxHandler) runAnte(ctx context.Context, tx sdk.Tx, txBytes []byte, isSimulate bool) (sdk.Context, error) {
err := validateBasicTxMsgs(tx.GetMsgs())
if err != nil {
return sdk.Context{}, err
}
sdkCtx := sdk.UnwrapSDKContext(ctx)
if txh.anteHandler == nil {
return sdkCtx, nil
}
ms := sdkCtx.MultiStore()
// Branch context before AnteHandler call in case it aborts.
// This is required for both CheckTx and DeliverTx.
// Ref: https://github.com/cosmos/cosmos-sdk/issues/2772
//
// NOTE: Alternatively, we could require that AnteHandler ensures that
// writes do not happen if aborted/failed. This may have some
// performance benefits, but it'll be more difficult to get right.
anteCtx, msCache := cacheTxContext(sdkCtx, txBytes)
anteCtx = anteCtx.WithEventManager(sdk.NewEventManager())
newCtx, err := txh.anteHandler(anteCtx, tx, isSimulate)
if err != nil {
return sdk.Context{}, err
}
if !newCtx.IsZero() {
// At this point, newCtx.MultiStore() is a store branch, or something else
// replaced by the AnteHandler. We want the original multistore.
//
// Also, in the case of the tx aborting, we need to track gas consumed via
// the instantiated gas meter in the AnteHandler, so we update the context
// prior to returning.
sdkCtx = newCtx.WithMultiStore(ms)
}
msCache.Write()
return sdkCtx, nil
}
// validateBasicTxMsgs executes basic validator calls for messages.
func validateBasicTxMsgs(msgs []sdk.Msg) error {
if len(msgs) == 0 {
return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "must contain at least one message")
}
for _, msg := range msgs {
err := msg.ValidateBasic()
if err != nil {
return err
}
}
return nil
}

View File

@ -1,4 +1,4 @@
package baseapp
package middleware
import (
"fmt"
@ -6,22 +6,22 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types"
)
type Router struct {
type LegacyRouter struct {
routes map[string]sdk.Handler
}
var _ sdk.Router = NewRouter()
var _ sdk.Router = NewLegacyRouter()
// NewRouter returns a reference to a new router.
func NewRouter() *Router {
return &Router{
func NewLegacyRouter() *LegacyRouter {
return &LegacyRouter{
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 *Router) AddRoute(route sdk.Route) sdk.Router {
func (rtr *LegacyRouter) 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 *Router) AddRoute(route sdk.Route) sdk.Router {
// Route returns a handler for a given route path.
//
// TODO: Handle expressive matches.
func (rtr *Router) Route(_ sdk.Context, path string) sdk.Handler {
func (rtr *LegacyRouter) Route(_ sdk.Context, path string) sdk.Handler {
return rtr.routes[path]
}

View File

@ -1,4 +1,4 @@
package baseapp
package middleware_test
import (
"testing"
@ -6,14 +6,15 @@ 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 TestRouter(t *testing.T) {
rtr := NewRouter()
func TestLegacyRouter(t *testing.T) {
rtr := middleware.NewLegacyRouter()
// require panic on invalid route
require.Panics(t, func() {

View File

@ -0,0 +1,62 @@
package middleware
import (
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/tx"
)
// ComposeMiddlewares compose multiple middlewares on top of a tx.Handler. The
// middleware order in the variadic arguments is from inside to outside.
//
// 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
// 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
LegacyAnteHandler sdk.AnteHandler
}
// NewDefaultTxHandler defines a TxHandler middleware stacks that should work
// for most applications.
func NewDefaultTxHandler(options TxHandlerOptions) (tx.Handler, error) {
return ComposeMiddlewares(
NewRunMsgsTxHandler(options.MsgServiceRouter, options.LegacyRouter),
// 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),
// Temporary middleware to bundle antehandlers.
// TODO Remove in https://github.com/cosmos/cosmos-sdk/issues/9585.
newLegacyAnteMiddleware(options.LegacyAnteHandler),
), nil
}

View File

@ -1,4 +1,4 @@
package baseapp
package middleware
import (
"context"
@ -22,9 +22,10 @@ type MsgServiceRouter struct {
var _ gogogrpc.Server = &MsgServiceRouter{}
// NewMsgServiceRouter creates a new MsgServiceRouter.
func NewMsgServiceRouter() *MsgServiceRouter {
func NewMsgServiceRouter(registry codectypes.InterfaceRegistry) *MsgServiceRouter {
return &MsgServiceRouter{
routes: map[string]MsgServiceHandler{},
interfaceRegistry: registry,
routes: map[string]MsgServiceHandler{},
}
}
@ -129,11 +130,6 @@ 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

@ -1,4 +1,4 @@
package baseapp_test
package middleware_test
import (
"os"
@ -15,19 +15,17 @@ import (
"github.com/cosmos/cosmos-sdk/simapp"
"github.com/cosmos/cosmos-sdk/testutil/testdata"
"github.com/cosmos/cosmos-sdk/types/tx/signing"
"github.com/cosmos/cosmos-sdk/x/auth/middleware"
authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing"
)
func TestRegisterMsgService(t *testing.T) {
db := dbm.NewMemDB()
// Create an encoding config that doesn't register testdata Msg services.
encCfg := simapp.MakeTestEncodingConfig()
app := baseapp.NewBaseApp("test", log.NewTMLogger(log.NewSyncWriter(os.Stdout)), db, encCfg.TxConfig.TxDecoder())
app.SetInterfaceRegistry(encCfg.InterfaceRegistry)
msr := middleware.NewMsgServiceRouter(encCfg.InterfaceRegistry)
require.Panics(t, func() {
testdata.RegisterMsgServer(
app.MsgServiceRouter(),
msr,
testdata.MsgServerImpl{},
)
})
@ -36,7 +34,7 @@ func TestRegisterMsgService(t *testing.T) {
testdata.RegisterInterfaces(encCfg.InterfaceRegistry)
require.NotPanics(t, func() {
testdata.RegisterMsgServer(
app.MsgServiceRouter(),
msr,
testdata.MsgServerImpl{},
)
})
@ -44,16 +42,14 @@ func TestRegisterMsgService(t *testing.T) {
func TestRegisterMsgServiceTwice(t *testing.T) {
// Setup baseapp.
db := dbm.NewMemDB()
encCfg := simapp.MakeTestEncodingConfig()
app := baseapp.NewBaseApp("test", log.NewTMLogger(log.NewSyncWriter(os.Stdout)), db, encCfg.TxConfig.TxDecoder())
app.SetInterfaceRegistry(encCfg.InterfaceRegistry)
msr := middleware.NewMsgServiceRouter(encCfg.InterfaceRegistry)
testdata.RegisterInterfaces(encCfg.InterfaceRegistry)
// First time registering service shouldn't panic.
require.NotPanics(t, func() {
testdata.RegisterMsgServer(
app.MsgServiceRouter(),
msr,
testdata.MsgServerImpl{},
)
})
@ -61,7 +57,7 @@ func TestRegisterMsgServiceTwice(t *testing.T) {
// Second time should panic.
require.Panics(t, func() {
testdata.RegisterMsgServer(
app.MsgServiceRouter(),
msr,
testdata.MsgServerImpl{},
)
})
@ -74,8 +70,14 @@ func TestMsgService(t *testing.T) {
db := dbm.NewMemDB()
app := baseapp.NewBaseApp("test", log.NewTMLogger(log.NewSyncWriter(os.Stdout)), db, encCfg.TxConfig.TxDecoder())
app.SetInterfaceRegistry(encCfg.InterfaceRegistry)
msr := middleware.NewMsgServiceRouter(encCfg.InterfaceRegistry)
txHandler, err := middleware.NewDefaultTxHandler(middleware.TxHandlerOptions{
MsgServiceRouter: msr,
})
require.NoError(t, err)
app.SetTxHandler(txHandler)
testdata.RegisterMsgServer(
app.MsgServiceRouter(),
msr,
testdata.MsgServerImpl{},
)
_ = app.BeginBlock(abci.RequestBeginBlock{Header: tmproto.Header{Height: 1}})
@ -84,7 +86,7 @@ func TestMsgService(t *testing.T) {
txBuilder := encCfg.TxConfig.NewTxBuilder()
txBuilder.SetFeeAmount(testdata.NewTestFeeAmount())
txBuilder.SetGasLimit(testdata.NewTestGasLimit())
err := txBuilder.SetMsgs(&msg)
err = txBuilder.SetMsgs(&msg)
require.NoError(t, err)
// First round: we gather all the signer infos. We use the "set empty

View File

@ -0,0 +1,103 @@
package middleware
import (
"context"
"runtime/debug"
abci "github.com/tendermint/tendermint/abci/types"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/cosmos/cosmos-sdk/types/tx"
)
type 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, tx sdk.Tx, req abci.RequestCheckTx) (res abci.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, tx, req)
}
// DeliverTx implements tx.Handler.DeliverTx method.
func (txh recoveryTxHandler) DeliverTx(ctx context.Context, tx sdk.Tx, req abci.RequestDeliverTx) (res abci.ResponseDeliverTx, 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
}
startingGas := sdkCtx.BlockGasMeter().GasConsumed()
// Panic recovery.
defer func() {
if r := recover(); r != nil {
err = handleRecovery(r, sdkCtx)
}
}()
// 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.
defer func() {
sdkCtx.BlockGasMeter().ConsumeGas(
sdkCtx.GasMeter().GasConsumedToLimit(), "block gas meter",
)
if sdkCtx.BlockGasMeter().GasConsumed() < startingGas {
panic(sdk.ErrorGasOverflow{Descriptor: "tx gas summation"})
}
}()
return txh.next.DeliverTx(ctx, tx, req)
}
// SimulateTx implements tx.Handler.SimulateTx method.
func (txh recoveryTxHandler) SimulateTx(ctx context.Context, sdkTx sdk.Tx, req tx.RequestSimulateTx) (res tx.ResponseSimulateTx, err error) {
sdkCtx := sdk.UnwrapSDKContext(ctx)
// Panic recovery.
defer func() {
if r := recover(); r != nil {
err = handleRecovery(r, sdkCtx)
}
}()
return txh.next.SimulateTx(ctx, sdkTx, 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

@ -0,0 +1,164 @@
package middleware
import (
"context"
"fmt"
"strings"
"github.com/gogo/protobuf/proto"
abci "github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/crypto/tmhash"
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, tx sdk.Tx, req abci.RequestCheckTx) (abci.ResponseCheckTx, error) {
// Don't run Msgs during CheckTx.
return abci.ResponseCheckTx{}, nil
}
// DeliverTx implements tx.Handler.DeliverTx method.
func (txh runMsgsTxHandler) DeliverTx(ctx context.Context, tx sdk.Tx, req abci.RequestDeliverTx) (abci.ResponseDeliverTx, error) {
res, err := txh.runMsgs(sdk.UnwrapSDKContext(ctx), tx.GetMsgs(), req.Tx)
if err != nil {
return abci.ResponseDeliverTx{}, err
}
return abci.ResponseDeliverTx{
// GasInfo will be populated by the Gas middleware.
Log: res.Log,
Data: res.Data,
Events: res.Events,
}, nil
}
// SimulateTx implements tx.Handler.SimulateTx method.
func (txh runMsgsTxHandler) SimulateTx(ctx context.Context, sdkTx sdk.Tx, req tx.RequestSimulateTx) (tx.ResponseSimulateTx, error) {
res, err := txh.runMsgs(sdk.UnwrapSDKContext(ctx), sdkTx.GetMsgs(), req.TxBytes)
if err != nil {
return tx.ResponseSimulateTx{}, err
}
return tx.ResponseSimulateTx{
// GasInfo will be populated by the Gas middleware.
Result: res,
}, nil
}
// 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) (*sdk.Result, error) {
// 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 := cacheTxContext(sdkCtx, 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.
msgLogs := make(sdk.ABCIMessageLogs, 0, len(msgs))
events := sdkCtx.EventManager().Events()
txMsgData := &sdk.TxMsgData{
Data: make([]*sdk.MsgData, 0, len(msgs)),
}
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(runMsgCtx, 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 nil, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "unrecognized message route: %s; message index: %d", msgRoute, i)
}
msgResult, err = handler(sdkCtx, 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)
txMsgData.Data = append(txMsgData.Data, &sdk.MsgData{MsgType: sdk.MsgTypeURL(msg), Data: msgResult.Data})
msgLogs = append(msgLogs, sdk.NewABCIMessageLog(uint32(i), msgResult.Log, msgEvents))
}
msCache.Write()
data, err := proto.Marshal(txMsgData)
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(),
}, 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

@ -0,0 +1,142 @@
package middleware_test
import (
"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"
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"
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
}
// MWTestSuite is a test suite to be used with middleware tests.
type MWTestSuite struct {
suite.Suite
app *simapp.SimApp
clientCtx client.Context
}
// 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 and context.
func (s *MWTestSuite) SetupTest(isCheckTx bool) sdk.Context {
var ctx sdk.Context
s.app, ctx = createTestApp(s.T(), isCheckTx)
ctx = 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)
s.clientCtx = client.Context{}.
WithTxConfig(encodingConfig.TxConfig)
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) []testAccount {
var accounts []testAccount
for i := 0; i < numAccs; i++ {
priv, _, addr := testdata.KeyTestPubAddr()
acc := s.app.AccountKeeper.NewAccountWithAddress(ctx, addr)
err := acc.SetAccountNumber(uint64(i))
s.Require().NoError(err)
s.app.AccountKeeper.SetAccount(ctx, acc)
someCoins := sdk.Coins{
sdk.NewInt64Coin("atom", 10000000),
}
err = s.app.BankKeeper.MintCoins(ctx, minttypes.ModuleName, someCoins)
s.Require().NoError(err)
err = s.app.BankKeeper.SendCoinsFromModuleToAccount(ctx, minttypes.ModuleName, addr, someCoins)
s.Require().NoError(err)
accounts = append(accounts, testAccount{acc, priv})
}
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{
ChainID: chainID,
AccountNumber: accNums[i],
Sequence: accSeqs[i],
}
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 TestMWTestSuite(t *testing.T) {
suite.Run(t, new(MWTestSuite))
}

View File

@ -122,8 +122,15 @@ func (s IntegrationTestSuite) TestSimulateTx_GRPC() {
} else {
s.Require().NoError(err)
// Check the result and gas used are correct.
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.
//
// 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)
// 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.
}
})
}
@ -163,8 +170,8 @@ 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()), 6) // 1 coin recv, 1 coin spent,1 transfer, 3 messages.
s.Require().True(result.GetGasInfo().GetGasUsed() > 0) // Gas used sometimes change, just check it's not empty.
s.Require().Equal(len(result.GetResult().GetEvents()), 13) // See TestSimulateTx_GRPC for the 13 events.
s.Require().True(result.GetGasInfo().GetGasUsed() > 0) // Gas used sometimes change, just check it's not empty.
}
})
}

View File

@ -8,22 +8,22 @@ 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"
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/authz"
)
type Keeper struct {
storeKey sdk.StoreKey
cdc codec.BinaryCodec
router *baseapp.MsgServiceRouter
router *middleware.MsgServiceRouter
}
// NewKeeper constructs a message authorization Keeper
func NewKeeper(storeKey sdk.StoreKey, cdc codec.BinaryCodec, router *baseapp.MsgServiceRouter) Keeper {
func NewKeeper(storeKey sdk.StoreKey, cdc codec.BinaryCodec, router *middleware.MsgServiceRouter) Keeper {
return Keeper{
storeKey: storeKey,
cdc: cdc,

View File

@ -131,7 +131,7 @@ func SimulateMsgGrant(ak authz.AccountKeeper, bk authz.BankKeeper, _ keeper.Keep
return simtypes.NoOpMsg(authz.ModuleName, TypeMsgGrant, "unable to generate mock tx"), nil, err
}
_, _, err = app.Deliver(txCfg.TxEncoder(), tx)
_, _, err = app.SimDeliver(txCfg.TxEncoder(), tx)
if err != nil {
return simtypes.NoOpMsg(authz.ModuleName, sdk.MsgTypeURL(msg), "unable to deliver tx"), nil, err
}
@ -197,7 +197,7 @@ func SimulateMsgRevoke(ak authz.AccountKeeper, bk authz.BankKeeper, k keeper.Kee
return simtypes.NoOpMsg(authz.ModuleName, TypeMsgRevoke, err.Error()), nil, err
}
_, _, err = app.Deliver(txCfg.TxEncoder(), tx)
_, _, err = app.SimDeliver(txCfg.TxEncoder(), tx)
if err != nil {
return simtypes.NoOpMsg(authz.ModuleName, TypeMsgRevoke, "unable to deliver tx"), nil, err
}
@ -283,7 +283,7 @@ func SimulateMsgExec(ak authz.AccountKeeper, bk authz.BankKeeper, k keeper.Keepe
return simtypes.NoOpMsg(authz.ModuleName, TypeMsgExec, err.Error()), nil, err
}
_, _, err = app.Deliver(txCfg.TxEncoder(), tx)
_, _, err = app.SimDeliver(txCfg.TxEncoder(), tx)
if err != nil {
return simtypes.NoOpMsg(authz.ModuleName, TypeMsgExec, err.Error()), nil, err
}

View File

@ -47,12 +47,12 @@ func BenchmarkOneBankSendTxPerBlock(b *testing.B) {
// Committing, and what time comes from Check/Deliver Tx.
for i := 0; i < b.N; i++ {
benchmarkApp.BeginBlock(abci.RequestBeginBlock{Header: tmproto.Header{Height: height}})
_, _, err := benchmarkApp.Check(txGen.TxEncoder(), txs[i])
_, _, err := benchmarkApp.SimCheck(txGen.TxEncoder(), txs[i])
if err != nil {
panic("something is broken in checking transaction")
}
_, _, err = benchmarkApp.Deliver(txGen.TxEncoder(), txs[i])
_, _, err = benchmarkApp.SimDeliver(txGen.TxEncoder(), txs[i])
require.NoError(b, err)
benchmarkApp.EndBlock(abci.RequestEndBlock{Height: height})
benchmarkApp.Commit()
@ -89,12 +89,12 @@ func BenchmarkOneBankMultiSendTxPerBlock(b *testing.B) {
// Committing, and what time comes from Check/Deliver Tx.
for i := 0; i < b.N; i++ {
benchmarkApp.BeginBlock(abci.RequestBeginBlock{Header: tmproto.Header{Height: height}})
_, _, err := benchmarkApp.Check(txGen.TxEncoder(), txs[i])
_, _, err := benchmarkApp.SimCheck(txGen.TxEncoder(), txs[i])
if err != nil {
panic("something is broken in checking transaction")
}
_, _, err = benchmarkApp.Deliver(txGen.TxEncoder(), txs[i])
_, _, err = benchmarkApp.SimDeliver(txGen.TxEncoder(), txs[i])
require.NoError(b, err)
benchmarkApp.EndBlock(abci.RequestEndBlock{Height: height})
benchmarkApp.Commit()

View File

@ -152,7 +152,7 @@ func sendMsgSend(
return err
}
_, _, err = app.Deliver(txGen.TxEncoder(), tx)
_, _, err = app.SimDeliver(txGen.TxEncoder(), tx)
if err != nil {
return err
}
@ -374,7 +374,7 @@ func sendMsgMultiSend(
return err
}
_, _, err = app.Deliver(txGen.TxEncoder(), tx)
_, _, err = app.SimDeliver(txGen.TxEncoder(), tx)
if err != nil {
return err
}

View File

@ -169,7 +169,7 @@ func SimulateMsgSubmitProposal(
return simtypes.NoOpMsg(types.ModuleName, msg.Type(), "unable to generate mock tx"), nil, err
}
_, _, err = app.Deliver(txGen.TxEncoder(), tx)
_, _, err = app.SimDeliver(txGen.TxEncoder(), tx)
if err != nil {
return simtypes.NoOpMsg(types.ModuleName, msg.Type(), "unable to deliver tx"), nil, err
}

View File

@ -115,7 +115,7 @@ func GenAndDeliverTx(txCtx OperationInput, fees sdk.Coins) (simtypes.OperationMs
return simtypes.NoOpMsg(txCtx.ModuleName, txCtx.MsgType, "unable to generate mock tx"), nil, err
}
_, _, err = txCtx.App.Deliver(txCtx.TxGen.TxEncoder(), tx)
_, _, err = txCtx.App.SimDeliver(txCtx.TxGen.TxEncoder(), tx)
if err != nil {
return simtypes.NoOpMsg(txCtx.ModuleName, txCtx.MsgType, "unable to deliver tx"), nil, err
}

View File

@ -103,7 +103,7 @@ func SimulateMsgUnjail(ak types.AccountKeeper, bk types.BankKeeper, k keeper.Kee
return simtypes.NoOpMsg(types.ModuleName, msg.Type(), "unable to generate mock tx"), nil, err
}
_, res, err := app.Deliver(txGen.TxEncoder(), tx)
_, res, err := app.SimDeliver(txGen.TxEncoder(), tx)
// result should fail if:
// - validator cannot be unjailed due to tombstone