diff --git a/CHANGELOG.md b/CHANGELOG.md index 260f2bb1b21..f3ee0f67bd5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/baseapp/abci.go b/baseapp/abci.go index 925f2f6b0aa..fca332fe185 100644 --- a/baseapp/abci.go +++ b/baseapp/abci.go @@ -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 diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index d45656ac987..6044b9518d1 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -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) } diff --git a/baseapp/baseapp_test.go b/baseapp/baseapp_test.go index 85d07661a3e..e8a371ccd92 100644 --- a/baseapp/baseapp_test.go +++ b/baseapp/baseapp_test.go @@ -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)) - - 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) - } - } - }() + ante := func(ctx sdk.Context, tx sdk.Tx, simulate bool) (sdk.Context, error) { + count := tx.(txTest).Counter + ctx.GasMeter().ConsumeGas(uint64(count), "counter-ante") - 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) { + cdc := codec.NewLegacyAmino() + + 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) } - - cdc := codec.NewLegacyAmino() - app := setupBaseApp(t, anteOpt, routerOpt) + 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)) - - 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") - } + 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") + } - return - }) + return ctx, nil } - routerOpt := func(bapp *BaseApp) { + cdc := codec.NewLegacyAmino() + registerTestCodec(cdc) + + 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) - cdc := codec.NewLegacyAmino() - registerTestCodec(cdc) - - app := setupBaseApp(t, anteOpt, routerOpt) 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{}}) + + txHandlerOpt := func(bapp *BaseApp) { + customRouter := &testCustomRouter{routes: sync.Map{}} r := sdk.NewRoute(routeMsgCounter, handlerMsgCounter(t, capKey1, deliverKey)) - bapp.Router().AddRoute(r) + 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, anteOpt, routerOpt) + app := setupBaseApp(t, txHandlerOpt) app.InitChain(abci.RequestInitChain{}) // Create same codec used in txDecoder diff --git a/baseapp/options.go b/baseapp/options.go index be9fbdc659a..b0b3af2ca70 100644 --- a/baseapp/options.go +++ b/baseapp/options.go @@ -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) } diff --git a/baseapp/recovery.go b/baseapp/recovery.go deleted file mode 100644 index 7f0687800c6..00000000000 --- a/baseapp/recovery.go +++ /dev/null @@ -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) -} diff --git a/baseapp/recovery_test.go b/baseapp/recovery_test.go deleted file mode 100644 index b75892c6381..00000000000 --- a/baseapp/recovery_test.go +++ /dev/null @@ -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) - } -} diff --git a/baseapp/test_helpers.go b/baseapp/test_helpers.go index 407ebd9a7cd..64924523493 100644 --- a/baseapp/test_helpers.go +++ b/baseapp/test_helpers.go @@ -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. diff --git a/server/mock/app.go b/server/mock/app.go index e3fdd2c9147..b1fe740a193 100644 --- a/server/mock/app.go +++ b/server/mock/app.go @@ -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 { diff --git a/server/mock/tx.go b/server/mock/tx.go index 0cb79c28986..afa3c266b7b 100644 --- a/server/mock/tx.go +++ b/server/mock/tx.go @@ -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) { diff --git a/simapp/app.go b/simapp/app.go index b165e2b187e..f53df16bf46 100644 --- a/simapp/app.go +++ b/simapp/app.go @@ -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,31 +388,48 @@ func NewSimApp( // initialize BaseApp app.SetInitChainer(app.InitChainer) app.SetBeginBlocker(app.BeginBlocker) + app.SetEndBlocker(app.EndBlocker) + app.setTxHandler(encodingConfig.TxConfig, cast.ToStringSlice(appOpts.Get(server.FlagIndexEvents))) + if loadLatest { + if err := app.LoadLatestVersion(); err != nil { + tmos.Exit(err.Error()) + } + } + + return app +} + +func (app *SimApp) setTxHandler(txConfig client.TxConfig, indexEventsStr []string) { anteHandler, err := ante.NewAnteHandler( ante.HandlerOptions{ AccountKeeper: app.AccountKeeper, BankKeeper: app.BankKeeper, - SignModeHandler: encodingConfig.TxConfig.SignModeHandler(), + SignModeHandler: txConfig.SignModeHandler(), FeegrantKeeper: app.FeeGrantKeeper, SigGasConsumer: ante.DefaultSigVerificationGasConsumer, }, ) - if err != nil { panic(err) } - app.SetAnteHandler(anteHandler) - app.SetEndBlocker(app.EndBlocker) - - if loadLatest { - if err := app.LoadLatestVersion(); err != nil { - tmos.Exit(err.Error()) - } + 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) } - return app + app.SetTxHandler(txHandler) } // Name returns the name of the App diff --git a/simapp/app_test.go b/simapp/app_test.go index 76c1a423d1f..249775349b8 100644 --- a/simapp/app_test.go +++ b/simapp/app_test.go @@ -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. diff --git a/simapp/test_helpers.go b/simapp/test_helpers.go index 722fc2b142f..df8f6a5794c 100644 --- a/simapp/test_helpers.go +++ b/simapp/test_helpers.go @@ -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) diff --git a/types/tx/middleware.go b/types/tx/middleware.go new file mode 100644 index 00000000000..c99df63d95c --- /dev/null +++ b/types/tx/middleware.go @@ -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 diff --git a/x/auth/ante/ante.go b/x/auth/ante/ante.go index 3b4aa6a56f1..dbb40aeb13c 100644 --- a/x/auth/ante/ante.go +++ b/x/auth/ante/ante.go @@ -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(), diff --git a/x/auth/ante/setup_test.go b/x/auth/ante/setup_test.go deleted file mode 100644 index 86c633d899f..00000000000 --- a/x/auth/ante/setup_test.go +++ /dev/null @@ -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") -} diff --git a/x/auth/ante/testutil_test.go b/x/auth/ante/testutil_test.go index 15598b3b23b..faf2e7cdf65 100644 --- a/x/auth/ante/testutil_test.go +++ b/x/auth/ante/testutil_test.go @@ -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, - }, - ) - - suite.Require().NoError(err) - suite.anteHandler = anteHandler + // We're not using ante.NewAnteHandler here because: + // - ante.NewAnteHandler doesn't have SetUpContextDecorator, as it has been + // moved to the gas TxMiddleware + // - whereas these tests have not been migrated to middlewares yet, so + // still need the SetUpContextDecorator. + // + // TODO: migrate all antehandler tests to middleware tests. + // https://github.com/cosmos/cosmos-sdk/issues/9585 + anteDecorators := []sdk.AnteDecorator{ + ante.NewSetUpContextDecorator(), + ante.NewRejectExtensionOptionsDecorator(), + ante.NewMempoolFeeDecorator(), + ante.NewValidateBasicDecorator(), + ante.NewTxTimeoutHeightDecorator(), + ante.NewValidateMemoDecorator(suite.app.AccountKeeper), + ante.NewConsumeGasForTxSizeDecorator(suite.app.AccountKeeper), + ante.NewDeductFeeDecorator(suite.app.AccountKeeper, suite.app.BankKeeper, suite.app.FeeGrantKeeper), + // SetPubKeyDecorator must be called before all signature verification decorators + ante.NewSetPubKeyDecorator(suite.app.AccountKeeper), + ante.NewValidateSigCountDecorator(suite.app.AccountKeeper), + ante.NewSigGasConsumeDecorator(suite.app.AccountKeeper, ante.DefaultSigVerificationGasConsumer), + ante.NewSigVerificationDecorator(suite.app.AccountKeeper, encodingConfig.TxConfig.SignModeHandler()), + ante.NewIncrementSequenceDecorator(suite.app.AccountKeeper), + } + + suite.anteHandler = sdk.ChainAnteDecorators(anteDecorators...) } // CreateTestAccounts creates `numAccs` accounts, and return all relevant diff --git a/x/auth/middleware/gas.go b/x/auth/middleware/gas.go new file mode 100644 index 00000000000..b49ce43702c --- /dev/null +++ b/x/auth/middleware/gas.go @@ -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)) +} diff --git a/x/auth/middleware/gas_test.go b/x/auth/middleware/gas_test.go new file mode 100644 index 00000000000..287160b10c3 --- /dev/null +++ b/x/auth/middleware/gas_test.go @@ -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 +} diff --git a/x/auth/middleware/index_events.go b/x/auth/middleware/index_events.go new file mode 100644 index 00000000000..30c7aea4015 --- /dev/null +++ b/x/auth/middleware/index_events.go @@ -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 +} diff --git a/x/auth/middleware/legacy_ante.go b/x/auth/middleware/legacy_ante.go new file mode 100644 index 00000000000..14f68208299 --- /dev/null +++ b/x/auth/middleware/legacy_ante.go @@ -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 +} diff --git a/baseapp/router.go b/x/auth/middleware/legacy_router.go similarity index 71% rename from baseapp/router.go rename to x/auth/middleware/legacy_router.go index 7e2e70a0c6f..caf4424c982 100644 --- a/baseapp/router.go +++ b/x/auth/middleware/legacy_router.go @@ -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] } diff --git a/baseapp/router_test.go b/x/auth/middleware/legacy_router_test.go similarity index 79% rename from baseapp/router_test.go rename to x/auth/middleware/legacy_router_test.go index 1e11dc0ca08..97517dcdf9b 100644 --- a/baseapp/router_test.go +++ b/x/auth/middleware/legacy_router_test.go @@ -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() { diff --git a/x/auth/middleware/middleware.go b/x/auth/middleware/middleware.go new file mode 100644 index 00000000000..de9d986cb3f --- /dev/null +++ b/x/auth/middleware/middleware.go @@ -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 +} diff --git a/baseapp/msg_service_router.go b/x/auth/middleware/msg_service_router.go similarity index 94% rename from baseapp/msg_service_router.go rename to x/auth/middleware/msg_service_router.go index 1b7f8f89bf7..5254810da30 100644 --- a/baseapp/msg_service_router.go +++ b/x/auth/middleware/msg_service_router.go @@ -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 diff --git a/baseapp/msg_service_router_test.go b/x/auth/middleware/msg_service_router_test.go similarity index 85% rename from baseapp/msg_service_router_test.go rename to x/auth/middleware/msg_service_router_test.go index d599d0cbe47..6223293617e 100644 --- a/baseapp/msg_service_router_test.go +++ b/x/auth/middleware/msg_service_router_test.go @@ -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 diff --git a/x/auth/middleware/recovery.go b/x/auth/middleware/recovery.go new file mode 100644 index 00000000000..42091c35417 --- /dev/null +++ b/x/auth/middleware/recovery.go @@ -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()), + ) + } +} diff --git a/x/auth/middleware/run_msgs.go b/x/auth/middleware/run_msgs.go new file mode 100644 index 00000000000..d1ae2960369 --- /dev/null +++ b/x/auth/middleware/run_msgs.go @@ -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 +} diff --git a/x/auth/middleware/testutil_test.go b/x/auth/middleware/testutil_test.go new file mode 100644 index 00000000000..a2ba1c3cf16 --- /dev/null +++ b/x/auth/middleware/testutil_test.go @@ -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)) +} diff --git a/x/auth/tx/service_test.go b/x/auth/tx/service_test.go index de11169d009..442087b7ea5 100644 --- a/x/auth/tx/service_test.go +++ b/x/auth/tx/service_test.go @@ -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= + // - tx.* events: tx.fee, tx.acc_seq, tx.signature + // - Sending Amount to recipient: coin_spent, coin_received, transfer and message.sender= + // - Msg events: message.module=bank and message.action=/cosmos.bank.v1beta1.MsgSend + s.Require().Equal(len(res.GetResult().GetEvents()), 13) + // 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. } }) } diff --git a/x/authz/keeper/keeper.go b/x/authz/keeper/keeper.go index e166a63e580..b884d41bc4f 100644 --- a/x/authz/keeper/keeper.go +++ b/x/authz/keeper/keeper.go @@ -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, diff --git a/x/authz/simulation/operations.go b/x/authz/simulation/operations.go index b2a5f20d4d9..841acb79f66 100644 --- a/x/authz/simulation/operations.go +++ b/x/authz/simulation/operations.go @@ -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 } diff --git a/x/bank/bench_test.go b/x/bank/bench_test.go index 3550f44f7b2..a847d0bf2bf 100644 --- a/x/bank/bench_test.go +++ b/x/bank/bench_test.go @@ -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() diff --git a/x/bank/simulation/operations.go b/x/bank/simulation/operations.go index 3b30157b857..05d14a7375f 100644 --- a/x/bank/simulation/operations.go +++ b/x/bank/simulation/operations.go @@ -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 } diff --git a/x/gov/simulation/operations.go b/x/gov/simulation/operations.go index 214fb946567..02261327766 100644 --- a/x/gov/simulation/operations.go +++ b/x/gov/simulation/operations.go @@ -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 } diff --git a/x/simulation/util.go b/x/simulation/util.go index aa348bfd357..498c056ebda 100644 --- a/x/simulation/util.go +++ b/x/simulation/util.go @@ -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 } diff --git a/x/slashing/simulation/operations.go b/x/slashing/simulation/operations.go index 3b98b2af05a..a4c82e0f4af 100644 --- a/x/slashing/simulation/operations.go +++ b/x/slashing/simulation/operations.go @@ -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