refactor!: BaseApp {Check,Deliver}Tx with middleware design (#9920)
<!-- The default pull request template is for types feat, fix, or refactor. For other templates, add one of the following parameters to the url: - template=docs.md - template=other.md --> ## Description ref: #9585 This PR if the 1st step (out of 2) in the #9585 refactor. It transforms baseapp's ABCI {Check,Deliver}Tx into middleware stacks. A middleware is defined by the following interfaces: ```go // types/tx package type Handler interface { CheckTx(ctx context.Context, tx sdk.Tx, req abci.RequestCheckTx) (abci.ResponseCheckTx, error) DeliverTx(ctx context.Context, tx sdk.Tx, req abci.RequestDeliverTx) (abci.ResponseDeliverTx, error) SimulateTx(ctx context.Context, tx sdk.Tx, req RequestSimulateTx) (ResponseSimulateTx, error) } type Middleware func(Handler) Handler ``` This Pr doesn't migrate antehandlers, but only baseapp's runTx and runMsgs as middlewares. It bundles antehandlers into one single middleware (for now). More specifically, it introduces the 5 following middlewares: | Middleware | Description | | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | RunMsgsTxMiddleware | This middleware replaces the old baseapp's `runMsgs`. | | LegacyAnteMiddleware | This middleware is temporary, it bundles all antehandlers into one middleware. It's only created for the purpose of breaking the refactor into 2 pieces. **It will be removed in Part 2**, and each antehandler will be replaced by its own middleware. | | IndexEventsTxMiddleware | This is a simple middleware that chooses which events to index in Tendermint. Replaces `baseapp.indexEvents` (which unfortunately still exists in baseapp too, because it's used to index Begin/EndBlock events) | | RecoveryTxMiddleware | This index recovers from panics. It replaces baseapp.runTx's panic recovery. | | GasTxMiddleware | This replaces the [`Setup`](https://github.com/cosmos/cosmos-sdk/blob/master/x/auth/ante/setup.go) Antehandler. It sets a GasMeter on sdk.Context. Note that before, GasMeter was set on sdk.Context inside the antehandlers, and there was some mess around the fact that antehandlers had their own panic recovery system so that the GasMeter could be read by baseapp's recovery system. Now, this mess is all removed: one middleware sets GasMeter, another one handles recovery. | --- ### Author Checklist *All items are required. Please add a note to the item if the item is not applicable and please add links to any relevant follow up issues.* I have... - [x] included the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title - [x] added `!` to the type prefix if API or client breaking change - [x] targeted the correct branch (see [PR Targeting](https://github.com/cosmos/cosmos-sdk/blob/master/CONTRIBUTING.md#pr-targeting)) - [x] provided a link to the relevant issue or specification - [ ] followed the guidelines for [building modules](https://github.com/cosmos/cosmos-sdk/blob/master/docs/building-modules) - [x] included the necessary unit and integration [tests](https://github.com/cosmos/cosmos-sdk/blob/master/CONTRIBUTING.md#testing) - [x] added a changelog entry to `CHANGELOG.md` - [x] included comments for [documenting Go code](https://blog.golang.org/godoc) - [ ] updated the relevant documentation or specification - [x] reviewed "Files changed" and left comments if necessary - [ ] confirmed all CI checks have passed ### Reviewers Checklist *All items are required. Please add a note if the item is not applicable and please add your handle next to the items reviewed if you only reviewed selected items.* I have... - [ ] confirmed the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title - [ ] confirmed `!` in the type prefix if API or client breaking change - [ ] confirmed all author checklist items have been addressed - [ ] reviewed state machine logic - [ ] reviewed API design and naming - [ ] reviewed documentation is accurate - [ ] reviewed tests and test coverage - [ ] manually tested (if applicable)
This commit is contained in:
parent
598fc0c8e5
commit
a15d7e31d4
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math"
|
||||
"math/rand"
|
||||
"os"
|
||||
"strings"
|
||||
|
@ -29,12 +30,15 @@ import (
|
|||
"github.com/cosmos/cosmos-sdk/testutil/testdata"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth/middleware"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth/migrations/legacytx"
|
||||
)
|
||||
|
||||
var (
|
||||
capKey1 = sdk.NewKVStoreKey("key1")
|
||||
capKey2 = sdk.NewKVStoreKey("key2")
|
||||
|
||||
interfaceRegistry = testdata.NewTestInterfaceRegistry()
|
||||
)
|
||||
|
||||
type paramStore struct {
|
||||
|
@ -125,11 +129,19 @@ func setupBaseAppWithSnapshots(t *testing.T, blocks uint, blockTxs int, options
|
|||
codec := codec.NewLegacyAmino()
|
||||
registerTestCodec(codec)
|
||||
routerOpt := func(bapp *BaseApp) {
|
||||
bapp.Router().AddRoute(sdk.NewRoute(routeMsgKeyValue, func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) {
|
||||
legacyRouter := middleware.NewLegacyRouter()
|
||||
legacyRouter.AddRoute(sdk.NewRoute(routeMsgKeyValue, func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) {
|
||||
kv := msg.(*msgKeyValue)
|
||||
bapp.cms.GetCommitKVStore(capKey2).Set(kv.Key, kv.Value)
|
||||
return &sdk.Result{}, nil
|
||||
}))
|
||||
txHandler, err := middleware.NewDefaultTxHandler(middleware.TxHandlerOptions{
|
||||
LegacyAnteHandler: func(ctx sdk.Context, tx sdk.Tx, simulate bool) (sdk.Context, error) { return ctx, nil },
|
||||
LegacyRouter: legacyRouter,
|
||||
MsgServiceRouter: middleware.NewMsgServiceRouter(interfaceRegistry),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
bapp.SetTxHandler(txHandler)
|
||||
}
|
||||
|
||||
snapshotInterval := uint64(2)
|
||||
|
@ -528,7 +540,7 @@ func TestBaseAppOptionSeal(t *testing.T) {
|
|||
app.SetEndBlocker(nil)
|
||||
})
|
||||
require.Panics(t, func() {
|
||||
app.SetAnteHandler(nil)
|
||||
app.SetTxHandler(nil)
|
||||
})
|
||||
require.Panics(t, func() {
|
||||
app.SetAddrPeerFilter(nil)
|
||||
|
@ -539,9 +551,6 @@ func TestBaseAppOptionSeal(t *testing.T) {
|
|||
require.Panics(t, func() {
|
||||
app.SetFauxMerkleMode()
|
||||
})
|
||||
require.Panics(t, func() {
|
||||
app.SetRouter(NewRouter())
|
||||
})
|
||||
}
|
||||
|
||||
func TestSetMinGasPrices(t *testing.T) {
|
||||
|
@ -682,6 +691,7 @@ type txTest struct {
|
|||
Msgs []sdk.Msg
|
||||
Counter int64
|
||||
FailOnAnte bool
|
||||
GasLimit uint64
|
||||
}
|
||||
|
||||
func (tx *txTest) setFailOnAnte(fail bool) {
|
||||
|
@ -698,6 +708,9 @@ func (tx *txTest) setFailOnHandler(fail bool) {
|
|||
func (tx txTest) GetMsgs() []sdk.Msg { return tx.Msgs }
|
||||
func (tx txTest) ValidateBasic() error { return nil }
|
||||
|
||||
// Implements GasTx
|
||||
func (tx txTest) GetGas() uint64 { return tx.GasLimit }
|
||||
|
||||
const (
|
||||
routeMsgCounter = "msgCounter"
|
||||
routeMsgCounter2 = "msgCounter2"
|
||||
|
@ -728,13 +741,13 @@ func (msg msgCounter) ValidateBasic() error {
|
|||
return sdkerrors.Wrap(sdkerrors.ErrInvalidSequence, "counter should be a non-negative integer")
|
||||
}
|
||||
|
||||
func newTxCounter(counter int64, msgCounters ...int64) *txTest {
|
||||
func newTxCounter(counter int64, msgCounters ...int64) txTest {
|
||||
msgs := make([]sdk.Msg, 0, len(msgCounters))
|
||||
for _, c := range msgCounters {
|
||||
msgs = append(msgs, msgCounter{c, false})
|
||||
}
|
||||
|
||||
return &txTest{msgs, counter, false}
|
||||
return txTest{msgs, counter, false, math.MaxUint64}
|
||||
}
|
||||
|
||||
// a msg we dont know how to route
|
||||
|
@ -916,15 +929,22 @@ func TestCheckTx(t *testing.T) {
|
|||
// This ensures changes to the kvstore persist across successive CheckTx.
|
||||
counterKey := []byte("counter-key")
|
||||
|
||||
anteOpt := func(bapp *BaseApp) { bapp.SetAnteHandler(anteHandlerTxTest(t, capKey1, counterKey)) }
|
||||
routerOpt := func(bapp *BaseApp) {
|
||||
txHandlerOpt := func(bapp *BaseApp) {
|
||||
legacyRouter := middleware.NewLegacyRouter()
|
||||
// TODO: can remove this once CheckTx doesnt process msgs.
|
||||
bapp.Router().AddRoute(sdk.NewRoute(routeMsgCounter, func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) {
|
||||
legacyRouter.AddRoute(sdk.NewRoute(routeMsgCounter, func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) {
|
||||
return &sdk.Result{}, nil
|
||||
}))
|
||||
txHandler, err := middleware.NewDefaultTxHandler(middleware.TxHandlerOptions{
|
||||
LegacyRouter: legacyRouter,
|
||||
LegacyAnteHandler: anteHandlerTxTest(t, capKey1, counterKey),
|
||||
MsgServiceRouter: middleware.NewMsgServiceRouter(interfaceRegistry),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
bapp.SetTxHandler(txHandler)
|
||||
}
|
||||
|
||||
app := setupBaseApp(t, anteOpt, routerOpt)
|
||||
app := setupBaseApp(t, txHandlerOpt)
|
||||
|
||||
nTxs := int64(5)
|
||||
app.InitChain(abci.RequestInitChain{})
|
||||
|
@ -968,16 +988,21 @@ func TestCheckTx(t *testing.T) {
|
|||
func TestDeliverTx(t *testing.T) {
|
||||
// test increments in the ante
|
||||
anteKey := []byte("ante-key")
|
||||
anteOpt := func(bapp *BaseApp) { bapp.SetAnteHandler(anteHandlerTxTest(t, capKey1, anteKey)) }
|
||||
|
||||
// test increments in the handler
|
||||
deliverKey := []byte("deliver-key")
|
||||
routerOpt := func(bapp *BaseApp) {
|
||||
txHandlerOpt := func(bapp *BaseApp) {
|
||||
legacyRouter := middleware.NewLegacyRouter()
|
||||
r := sdk.NewRoute(routeMsgCounter, handlerMsgCounter(t, capKey1, deliverKey))
|
||||
bapp.Router().AddRoute(r)
|
||||
legacyRouter.AddRoute(r)
|
||||
txHandler, err := middleware.NewDefaultTxHandler(middleware.TxHandlerOptions{
|
||||
LegacyRouter: legacyRouter,
|
||||
LegacyAnteHandler: anteHandlerTxTest(t, capKey1, anteKey),
|
||||
MsgServiceRouter: middleware.NewMsgServiceRouter(interfaceRegistry),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
bapp.SetTxHandler(txHandler)
|
||||
}
|
||||
|
||||
app := setupBaseApp(t, anteOpt, routerOpt)
|
||||
app := setupBaseApp(t, txHandlerOpt)
|
||||
app.InitChain(abci.RequestInitChain{})
|
||||
|
||||
// Create same codec used in txDecoder
|
||||
|
@ -1021,19 +1046,24 @@ func TestMultiMsgCheckTx(t *testing.T) {
|
|||
func TestMultiMsgDeliverTx(t *testing.T) {
|
||||
// increment the tx counter
|
||||
anteKey := []byte("ante-key")
|
||||
anteOpt := func(bapp *BaseApp) { bapp.SetAnteHandler(anteHandlerTxTest(t, capKey1, anteKey)) }
|
||||
|
||||
// increment the msg counter
|
||||
deliverKey := []byte("deliver-key")
|
||||
deliverKey2 := []byte("deliver-key2")
|
||||
routerOpt := func(bapp *BaseApp) {
|
||||
txHandlerOpt := func(bapp *BaseApp) {
|
||||
legacyRouter := middleware.NewLegacyRouter()
|
||||
r1 := sdk.NewRoute(routeMsgCounter, handlerMsgCounter(t, capKey1, deliverKey))
|
||||
r2 := sdk.NewRoute(routeMsgCounter2, handlerMsgCounter(t, capKey1, deliverKey2))
|
||||
bapp.Router().AddRoute(r1)
|
||||
bapp.Router().AddRoute(r2)
|
||||
legacyRouter.AddRoute(r1)
|
||||
legacyRouter.AddRoute(r2)
|
||||
txHandler, err := middleware.NewDefaultTxHandler(middleware.TxHandlerOptions{
|
||||
LegacyRouter: legacyRouter,
|
||||
LegacyAnteHandler: anteHandlerTxTest(t, capKey1, anteKey),
|
||||
MsgServiceRouter: middleware.NewMsgServiceRouter(interfaceRegistry),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
bapp.SetTxHandler(txHandler)
|
||||
}
|
||||
|
||||
app := setupBaseApp(t, anteOpt, routerOpt)
|
||||
app := setupBaseApp(t, txHandlerOpt)
|
||||
|
||||
// Create same codec used in txDecoder
|
||||
codec := codec.NewLegacyAmino()
|
||||
|
@ -1097,22 +1127,22 @@ func TestConcurrentCheckDeliver(t *testing.T) {
|
|||
func TestSimulateTx(t *testing.T) {
|
||||
gasConsumed := uint64(5)
|
||||
|
||||
anteOpt := func(bapp *BaseApp) {
|
||||
bapp.SetAnteHandler(func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, err error) {
|
||||
newCtx = ctx.WithGasMeter(sdk.NewGasMeter(gasConsumed))
|
||||
return
|
||||
})
|
||||
}
|
||||
|
||||
routerOpt := func(bapp *BaseApp) {
|
||||
txHandlerOpt := func(bapp *BaseApp) {
|
||||
legacyRouter := middleware.NewLegacyRouter()
|
||||
r := sdk.NewRoute(routeMsgCounter, func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) {
|
||||
ctx.GasMeter().ConsumeGas(gasConsumed, "test")
|
||||
return &sdk.Result{}, nil
|
||||
})
|
||||
bapp.Router().AddRoute(r)
|
||||
legacyRouter.AddRoute(r)
|
||||
txHandler, err := middleware.NewDefaultTxHandler(middleware.TxHandlerOptions{
|
||||
LegacyRouter: legacyRouter,
|
||||
LegacyAnteHandler: func(ctx sdk.Context, tx sdk.Tx, simulate bool) (sdk.Context, error) { return ctx, nil },
|
||||
MsgServiceRouter: middleware.NewMsgServiceRouter(interfaceRegistry),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
bapp.SetTxHandler(txHandler)
|
||||
}
|
||||
|
||||
app := setupBaseApp(t, anteOpt, routerOpt)
|
||||
app := setupBaseApp(t, txHandlerOpt)
|
||||
|
||||
app.InitChain(abci.RequestInitChain{})
|
||||
|
||||
|
@ -1127,6 +1157,7 @@ func TestSimulateTx(t *testing.T) {
|
|||
app.BeginBlock(abci.RequestBeginBlock{Header: header})
|
||||
|
||||
tx := newTxCounter(count, count)
|
||||
tx.GasLimit = gasConsumed
|
||||
txBytes, err := cdc.Marshal(tx)
|
||||
require.Nil(t, err)
|
||||
|
||||
|
@ -1164,19 +1195,23 @@ func TestSimulateTx(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestRunInvalidTransaction(t *testing.T) {
|
||||
anteOpt := func(bapp *BaseApp) {
|
||||
bapp.SetAnteHandler(func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, err error) {
|
||||
return
|
||||
})
|
||||
}
|
||||
routerOpt := func(bapp *BaseApp) {
|
||||
txHandlerOpt := func(bapp *BaseApp) {
|
||||
legacyRouter := middleware.NewLegacyRouter()
|
||||
r := sdk.NewRoute(routeMsgCounter, func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) {
|
||||
return &sdk.Result{}, nil
|
||||
})
|
||||
bapp.Router().AddRoute(r)
|
||||
legacyRouter.AddRoute(r)
|
||||
txHandler, err := middleware.NewDefaultTxHandler(middleware.TxHandlerOptions{
|
||||
LegacyRouter: legacyRouter,
|
||||
LegacyAnteHandler: func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, err error) {
|
||||
return
|
||||
},
|
||||
MsgServiceRouter: middleware.NewMsgServiceRouter(interfaceRegistry),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
bapp.SetTxHandler(txHandler)
|
||||
}
|
||||
|
||||
app := setupBaseApp(t, anteOpt, routerOpt)
|
||||
app := setupBaseApp(t, txHandlerOpt)
|
||||
|
||||
header := tmproto.Header{Height: 1}
|
||||
app.BeginBlock(abci.RequestBeginBlock{Header: header})
|
||||
|
@ -1184,8 +1219,7 @@ func TestRunInvalidTransaction(t *testing.T) {
|
|||
// transaction with no messages
|
||||
{
|
||||
emptyTx := &txTest{}
|
||||
_, result, err := app.Deliver(aminoTxEncoder(), emptyTx)
|
||||
require.Error(t, err)
|
||||
_, result, err := app.SimDeliver(aminoTxEncoder(), emptyTx)
|
||||
require.Nil(t, result)
|
||||
|
||||
space, code, _ := sdkerrors.ABCIInfo(err, false)
|
||||
|
@ -1196,7 +1230,7 @@ func TestRunInvalidTransaction(t *testing.T) {
|
|||
// transaction where ValidateBasic fails
|
||||
{
|
||||
testCases := []struct {
|
||||
tx *txTest
|
||||
tx txTest
|
||||
fail bool
|
||||
}{
|
||||
{newTxCounter(0, 0), false},
|
||||
|
@ -1211,7 +1245,7 @@ func TestRunInvalidTransaction(t *testing.T) {
|
|||
|
||||
for _, testCase := range testCases {
|
||||
tx := testCase.tx
|
||||
_, result, err := app.Deliver(aminoTxEncoder(), tx)
|
||||
_, result, err := app.SimDeliver(aminoTxEncoder(), tx)
|
||||
|
||||
if testCase.fail {
|
||||
require.Error(t, err)
|
||||
|
@ -1227,8 +1261,8 @@ func TestRunInvalidTransaction(t *testing.T) {
|
|||
|
||||
// transaction with no known route
|
||||
{
|
||||
unknownRouteTx := txTest{[]sdk.Msg{msgNoRoute{}}, 0, false}
|
||||
_, result, err := app.Deliver(aminoTxEncoder(), unknownRouteTx)
|
||||
unknownRouteTx := txTest{[]sdk.Msg{msgNoRoute{}}, 0, false, math.MaxUint64}
|
||||
_, result, err := app.SimDeliver(aminoTxEncoder(), unknownRouteTx)
|
||||
require.Error(t, err)
|
||||
require.Nil(t, result)
|
||||
|
||||
|
@ -1236,8 +1270,8 @@ func TestRunInvalidTransaction(t *testing.T) {
|
|||
require.EqualValues(t, sdkerrors.ErrUnknownRequest.Codespace(), space, err)
|
||||
require.EqualValues(t, sdkerrors.ErrUnknownRequest.ABCICode(), code, err)
|
||||
|
||||
unknownRouteTx = txTest{[]sdk.Msg{msgCounter{}, msgNoRoute{}}, 0, false}
|
||||
_, result, err = app.Deliver(aminoTxEncoder(), unknownRouteTx)
|
||||
unknownRouteTx = txTest{[]sdk.Msg{msgCounter{}, msgNoRoute{}}, 0, false, math.MaxUint64}
|
||||
_, result, err = app.SimDeliver(aminoTxEncoder(), unknownRouteTx)
|
||||
require.Error(t, err)
|
||||
require.Nil(t, result)
|
||||
|
||||
|
@ -1268,49 +1302,36 @@ func TestRunInvalidTransaction(t *testing.T) {
|
|||
// Test that transactions exceeding gas limits fail
|
||||
func TestTxGasLimits(t *testing.T) {
|
||||
gasGranted := uint64(10)
|
||||
anteOpt := func(bapp *BaseApp) {
|
||||
bapp.SetAnteHandler(func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, err error) {
|
||||
newCtx = ctx.WithGasMeter(sdk.NewGasMeter(gasGranted))
|
||||
|
||||
// AnteHandlers must have their own defer/recover in order for the BaseApp
|
||||
// to know how much gas was used! This is because the GasMeter is created in
|
||||
// the AnteHandler, but if it panics the context won't be set properly in
|
||||
// runTx's recover call.
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
switch rType := r.(type) {
|
||||
case sdk.ErrorOutOfGas:
|
||||
err = sdkerrors.Wrapf(sdkerrors.ErrOutOfGas, "out of gas in location: %v", rType.Descriptor)
|
||||
default:
|
||||
panic(r)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
count := tx.(txTest).Counter
|
||||
newCtx.GasMeter().ConsumeGas(uint64(count), "counter-ante")
|
||||
|
||||
return newCtx, nil
|
||||
})
|
||||
|
||||
ante := func(ctx sdk.Context, tx sdk.Tx, simulate bool) (sdk.Context, error) {
|
||||
count := tx.(txTest).Counter
|
||||
ctx.GasMeter().ConsumeGas(uint64(count), "counter-ante")
|
||||
return ctx, nil
|
||||
}
|
||||
|
||||
routerOpt := func(bapp *BaseApp) {
|
||||
txHandlerOpt := func(bapp *BaseApp) {
|
||||
legacyRouter := middleware.NewLegacyRouter()
|
||||
r := sdk.NewRoute(routeMsgCounter, func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) {
|
||||
count := msg.(*msgCounter).Counter
|
||||
count := msg.(msgCounter).Counter
|
||||
ctx.GasMeter().ConsumeGas(uint64(count), "counter-handler")
|
||||
return &sdk.Result{}, nil
|
||||
})
|
||||
bapp.Router().AddRoute(r)
|
||||
legacyRouter.AddRoute(r)
|
||||
txHandler, err := middleware.NewDefaultTxHandler(middleware.TxHandlerOptions{
|
||||
LegacyRouter: legacyRouter,
|
||||
LegacyAnteHandler: ante,
|
||||
MsgServiceRouter: middleware.NewMsgServiceRouter(interfaceRegistry),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
bapp.SetTxHandler(txHandler)
|
||||
}
|
||||
|
||||
app := setupBaseApp(t, anteOpt, routerOpt)
|
||||
app := setupBaseApp(t, txHandlerOpt)
|
||||
|
||||
header := tmproto.Header{Height: 1}
|
||||
app.BeginBlock(abci.RequestBeginBlock{Header: header})
|
||||
|
||||
testCases := []struct {
|
||||
tx *txTest
|
||||
tx txTest
|
||||
gasUsed uint64
|
||||
fail bool
|
||||
}{
|
||||
|
@ -1335,7 +1356,8 @@ func TestTxGasLimits(t *testing.T) {
|
|||
|
||||
for i, tc := range testCases {
|
||||
tx := tc.tx
|
||||
gInfo, result, err := app.Deliver(aminoTxEncoder(), tx)
|
||||
tx.GasLimit = gasGranted
|
||||
gInfo, result, err := app.SimDeliver(aminoTxEncoder(), tx)
|
||||
|
||||
// check gas used and wanted
|
||||
require.Equal(t, tc.gasUsed, gInfo.GasUsed, fmt.Sprintf("tc #%d; gas: %v, result: %v, err: %s", i, gInfo, result, err))
|
||||
|
@ -1357,38 +1379,31 @@ func TestTxGasLimits(t *testing.T) {
|
|||
// Test that transactions exceeding gas limits fail
|
||||
func TestMaxBlockGasLimits(t *testing.T) {
|
||||
gasGranted := uint64(10)
|
||||
anteOpt := func(bapp *BaseApp) {
|
||||
bapp.SetAnteHandler(func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, err error) {
|
||||
newCtx = ctx.WithGasMeter(sdk.NewGasMeter(gasGranted))
|
||||
ante := func(ctx sdk.Context, tx sdk.Tx, simulate bool) (sdk.Context, error) {
|
||||
count := tx.(txTest).Counter
|
||||
ctx.GasMeter().ConsumeGas(uint64(count), "counter-ante")
|
||||
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
switch rType := r.(type) {
|
||||
case sdk.ErrorOutOfGas:
|
||||
err = sdkerrors.Wrapf(sdkerrors.ErrOutOfGas, "out of gas in location: %v", rType.Descriptor)
|
||||
default:
|
||||
panic(r)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
count := tx.(txTest).Counter
|
||||
newCtx.GasMeter().ConsumeGas(uint64(count), "counter-ante")
|
||||
|
||||
return
|
||||
})
|
||||
return ctx, nil
|
||||
}
|
||||
|
||||
routerOpt := func(bapp *BaseApp) {
|
||||
txHandlerOpt := func(bapp *BaseApp) {
|
||||
legacyRouter := middleware.NewLegacyRouter()
|
||||
r := sdk.NewRoute(routeMsgCounter, func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) {
|
||||
count := msg.(*msgCounter).Counter
|
||||
count := msg.(msgCounter).Counter
|
||||
ctx.GasMeter().ConsumeGas(uint64(count), "counter-handler")
|
||||
return &sdk.Result{}, nil
|
||||
})
|
||||
bapp.Router().AddRoute(r)
|
||||
legacyRouter.AddRoute(r)
|
||||
txHandler, err := middleware.NewDefaultTxHandler(middleware.TxHandlerOptions{
|
||||
LegacyRouter: legacyRouter,
|
||||
LegacyAnteHandler: ante,
|
||||
MsgServiceRouter: middleware.NewMsgServiceRouter(interfaceRegistry),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
bapp.SetTxHandler(txHandler)
|
||||
}
|
||||
app := setupBaseApp(t, txHandlerOpt)
|
||||
|
||||
app := setupBaseApp(t, anteOpt, routerOpt)
|
||||
app.InitChain(abci.RequestInitChain{
|
||||
ConsensusParams: &abci.ConsensusParams{
|
||||
Block: &abci.BlockParams{
|
||||
|
@ -1398,7 +1413,7 @@ func TestMaxBlockGasLimits(t *testing.T) {
|
|||
})
|
||||
|
||||
testCases := []struct {
|
||||
tx *txTest
|
||||
tx txTest
|
||||
numDelivers int
|
||||
gasUsedPerDeliver uint64
|
||||
fail bool
|
||||
|
@ -1418,6 +1433,7 @@ func TestMaxBlockGasLimits(t *testing.T) {
|
|||
|
||||
for i, tc := range testCases {
|
||||
tx := tc.tx
|
||||
tx.GasLimit = gasGranted
|
||||
|
||||
// reset the block gas
|
||||
header := tmproto.Header{Height: app.LastBlockHeight() + 1}
|
||||
|
@ -1425,7 +1441,7 @@ func TestMaxBlockGasLimits(t *testing.T) {
|
|||
|
||||
// execute the transaction multiple times
|
||||
for j := 0; j < tc.numDelivers; j++ {
|
||||
_, result, err := app.Deliver(aminoTxEncoder(), tx)
|
||||
_, result, err := app.SimDeliver(aminoTxEncoder(), tx)
|
||||
|
||||
ctx := app.getState(runTxModeDeliver).ctx
|
||||
|
||||
|
@ -1454,63 +1470,24 @@ func TestMaxBlockGasLimits(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
// Test custom panic handling within app.DeliverTx method
|
||||
func TestCustomRunTxPanicHandler(t *testing.T) {
|
||||
const customPanicMsg = "test panic"
|
||||
anteErr := sdkerrors.Register("fakeModule", 100500, "fakeError")
|
||||
|
||||
anteOpt := func(bapp *BaseApp) {
|
||||
bapp.SetAnteHandler(func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, err error) {
|
||||
panic(sdkerrors.Wrap(anteErr, "anteHandler"))
|
||||
})
|
||||
}
|
||||
routerOpt := func(bapp *BaseApp) {
|
||||
r := sdk.NewRoute(routeMsgCounter, func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) {
|
||||
return &sdk.Result{}, nil
|
||||
})
|
||||
bapp.Router().AddRoute(r)
|
||||
}
|
||||
|
||||
app := setupBaseApp(t, anteOpt, routerOpt)
|
||||
|
||||
header := tmproto.Header{Height: 1}
|
||||
app.BeginBlock(abci.RequestBeginBlock{Header: header})
|
||||
|
||||
app.AddRunTxRecoveryHandler(func(recoveryObj interface{}) error {
|
||||
err, ok := recoveryObj.(error)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
if anteErr.Is(err) {
|
||||
panic(customPanicMsg)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
})
|
||||
|
||||
// Transaction should panic with custom handler above
|
||||
{
|
||||
tx := newTxCounter(0, 0)
|
||||
|
||||
require.PanicsWithValue(t, customPanicMsg, func() { app.Deliver(aminoTxEncoder(), tx) })
|
||||
}
|
||||
}
|
||||
|
||||
func TestBaseAppAnteHandler(t *testing.T) {
|
||||
anteKey := []byte("ante-key")
|
||||
anteOpt := func(bapp *BaseApp) {
|
||||
bapp.SetAnteHandler(anteHandlerTxTest(t, capKey1, anteKey))
|
||||
}
|
||||
|
||||
deliverKey := []byte("deliver-key")
|
||||
routerOpt := func(bapp *BaseApp) {
|
||||
r := sdk.NewRoute(routeMsgCounter, handlerMsgCounter(t, capKey1, deliverKey))
|
||||
bapp.Router().AddRoute(r)
|
||||
}
|
||||
|
||||
cdc := codec.NewLegacyAmino()
|
||||
app := setupBaseApp(t, anteOpt, routerOpt)
|
||||
|
||||
txHandlerOpt := func(bapp *BaseApp) {
|
||||
legacyRouter := middleware.NewLegacyRouter()
|
||||
r := sdk.NewRoute(routeMsgCounter, handlerMsgCounter(t, capKey1, deliverKey))
|
||||
legacyRouter.AddRoute(r)
|
||||
txHandler, err := middleware.NewDefaultTxHandler(middleware.TxHandlerOptions{
|
||||
LegacyRouter: legacyRouter,
|
||||
LegacyAnteHandler: anteHandlerTxTest(t, capKey1, anteKey),
|
||||
MsgServiceRouter: middleware.NewMsgServiceRouter(interfaceRegistry),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
bapp.SetTxHandler(txHandler)
|
||||
}
|
||||
app := setupBaseApp(t, txHandlerOpt)
|
||||
|
||||
app.InitChain(abci.RequestInitChain{})
|
||||
registerTestCodec(cdc)
|
||||
|
@ -1574,45 +1551,37 @@ func TestBaseAppAnteHandler(t *testing.T) {
|
|||
|
||||
func TestGasConsumptionBadTx(t *testing.T) {
|
||||
gasWanted := uint64(5)
|
||||
anteOpt := func(bapp *BaseApp) {
|
||||
bapp.SetAnteHandler(func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, err error) {
|
||||
newCtx = ctx.WithGasMeter(sdk.NewGasMeter(gasWanted))
|
||||
ante := func(ctx sdk.Context, tx sdk.Tx, simulate bool) (sdk.Context, error) {
|
||||
txTest := tx.(txTest)
|
||||
ctx.GasMeter().ConsumeGas(uint64(txTest.Counter), "counter-ante")
|
||||
if txTest.FailOnAnte {
|
||||
return ctx, sdkerrors.Wrap(sdkerrors.ErrUnauthorized, "ante handler failure")
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
switch rType := r.(type) {
|
||||
case sdk.ErrorOutOfGas:
|
||||
log := fmt.Sprintf("out of gas in location: %v", rType.Descriptor)
|
||||
err = sdkerrors.Wrap(sdkerrors.ErrOutOfGas, log)
|
||||
default:
|
||||
panic(r)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
txTest := tx.(txTest)
|
||||
newCtx.GasMeter().ConsumeGas(uint64(txTest.Counter), "counter-ante")
|
||||
if txTest.FailOnAnte {
|
||||
return newCtx, sdkerrors.Wrap(sdkerrors.ErrUnauthorized, "ante handler failure")
|
||||
}
|
||||
|
||||
return
|
||||
})
|
||||
}
|
||||
|
||||
routerOpt := func(bapp *BaseApp) {
|
||||
r := sdk.NewRoute(routeMsgCounter, func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) {
|
||||
count := msg.(*msgCounter).Counter
|
||||
ctx.GasMeter().ConsumeGas(uint64(count), "counter-handler")
|
||||
return &sdk.Result{}, nil
|
||||
})
|
||||
bapp.Router().AddRoute(r)
|
||||
return ctx, nil
|
||||
}
|
||||
|
||||
cdc := codec.NewLegacyAmino()
|
||||
registerTestCodec(cdc)
|
||||
|
||||
app := setupBaseApp(t, anteOpt, routerOpt)
|
||||
txHandlerOpt := func(bapp *BaseApp) {
|
||||
legacyRouter := middleware.NewLegacyRouter()
|
||||
r := sdk.NewRoute(routeMsgCounter, func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) {
|
||||
count := msg.(msgCounter).Counter
|
||||
ctx.GasMeter().ConsumeGas(uint64(count), "counter-handler")
|
||||
return &sdk.Result{}, nil
|
||||
})
|
||||
legacyRouter.AddRoute(r)
|
||||
txHandler, err := middleware.NewDefaultTxHandler(middleware.TxHandlerOptions{
|
||||
LegacyRouter: legacyRouter,
|
||||
LegacyAnteHandler: ante,
|
||||
MsgServiceRouter: middleware.NewMsgServiceRouter(interfaceRegistry),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
bapp.SetTxHandler(txHandler)
|
||||
}
|
||||
app := setupBaseApp(t, txHandlerOpt)
|
||||
|
||||
app.InitChain(abci.RequestInitChain{
|
||||
ConsensusParams: &abci.ConsensusParams{
|
||||
Block: &abci.BlockParams{
|
||||
|
@ -1627,6 +1596,7 @@ func TestGasConsumptionBadTx(t *testing.T) {
|
|||
app.BeginBlock(abci.RequestBeginBlock{Header: header})
|
||||
|
||||
tx := newTxCounter(5, 0)
|
||||
tx.GasLimit = gasWanted
|
||||
tx.setFailOnAnte(true)
|
||||
txBytes, err := cdc.Marshal(tx)
|
||||
require.NoError(t, err)
|
||||
|
@ -1646,24 +1616,28 @@ func TestGasConsumptionBadTx(t *testing.T) {
|
|||
// Test that we can only query from the latest committed state.
|
||||
func TestQuery(t *testing.T) {
|
||||
key, value := []byte("hello"), []byte("goodbye")
|
||||
anteOpt := func(bapp *BaseApp) {
|
||||
bapp.SetAnteHandler(func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, err error) {
|
||||
store := ctx.KVStore(capKey1)
|
||||
store.Set(key, value)
|
||||
return
|
||||
})
|
||||
}
|
||||
|
||||
routerOpt := func(bapp *BaseApp) {
|
||||
txHandlerOpt := func(bapp *BaseApp) {
|
||||
legacyRouter := middleware.NewLegacyRouter()
|
||||
r := sdk.NewRoute(routeMsgCounter, func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) {
|
||||
store := ctx.KVStore(capKey1)
|
||||
store.Set(key, value)
|
||||
return &sdk.Result{}, nil
|
||||
})
|
||||
bapp.Router().AddRoute(r)
|
||||
legacyRouter.AddRoute(r)
|
||||
txHandler, err := middleware.NewDefaultTxHandler(middleware.TxHandlerOptions{
|
||||
LegacyRouter: legacyRouter,
|
||||
LegacyAnteHandler: func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, err error) {
|
||||
store := ctx.KVStore(capKey1)
|
||||
store.Set(key, value)
|
||||
return
|
||||
},
|
||||
MsgServiceRouter: middleware.NewMsgServiceRouter(interfaceRegistry),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
bapp.SetTxHandler(txHandler)
|
||||
}
|
||||
|
||||
app := setupBaseApp(t, anteOpt, routerOpt)
|
||||
app := setupBaseApp(t, txHandlerOpt)
|
||||
|
||||
app.InitChain(abci.RequestInitChain{})
|
||||
|
||||
|
@ -1681,7 +1655,7 @@ func TestQuery(t *testing.T) {
|
|||
require.Equal(t, 0, len(res.Value))
|
||||
|
||||
// query is still empty after a CheckTx
|
||||
_, resTx, err := app.Check(aminoTxEncoder(), tx)
|
||||
_, resTx, err := app.SimCheck(aminoTxEncoder(), tx)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, resTx)
|
||||
res = app.Query(query)
|
||||
|
@ -1691,7 +1665,7 @@ func TestQuery(t *testing.T) {
|
|||
header := tmproto.Header{Height: app.LastBlockHeight() + 1}
|
||||
app.BeginBlock(abci.RequestBeginBlock{Header: header})
|
||||
|
||||
_, resTx, err = app.Deliver(aminoTxEncoder(), tx)
|
||||
_, resTx, err = app.SimDeliver(aminoTxEncoder(), tx)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, resTx)
|
||||
res = app.Query(query)
|
||||
|
@ -1968,17 +1942,22 @@ func (rtr *testCustomRouter) Route(ctx sdk.Context, path string) sdk.Handler {
|
|||
func TestWithRouter(t *testing.T) {
|
||||
// test increments in the ante
|
||||
anteKey := []byte("ante-key")
|
||||
anteOpt := func(bapp *BaseApp) { bapp.SetAnteHandler(anteHandlerTxTest(t, capKey1, anteKey)) }
|
||||
|
||||
// test increments in the handler
|
||||
deliverKey := []byte("deliver-key")
|
||||
routerOpt := func(bapp *BaseApp) {
|
||||
bapp.SetRouter(&testCustomRouter{routes: sync.Map{}})
|
||||
r := sdk.NewRoute(routeMsgCounter, handlerMsgCounter(t, capKey1, deliverKey))
|
||||
bapp.Router().AddRoute(r)
|
||||
}
|
||||
|
||||
app := setupBaseApp(t, anteOpt, routerOpt)
|
||||
txHandlerOpt := func(bapp *BaseApp) {
|
||||
customRouter := &testCustomRouter{routes: sync.Map{}}
|
||||
r := sdk.NewRoute(routeMsgCounter, handlerMsgCounter(t, capKey1, deliverKey))
|
||||
customRouter.AddRoute(r)
|
||||
txHandler, err := middleware.NewDefaultTxHandler(middleware.TxHandlerOptions{
|
||||
LegacyRouter: customRouter,
|
||||
LegacyAnteHandler: anteHandlerTxTest(t, capKey1, anteKey),
|
||||
MsgServiceRouter: middleware.NewMsgServiceRouter(interfaceRegistry),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
bapp.SetTxHandler(txHandler)
|
||||
}
|
||||
app := setupBaseApp(t, txHandlerOpt)
|
||||
app.InitChain(abci.RequestInitChain{})
|
||||
|
||||
// Create same codec used in txDecoder
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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.
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -20,6 +20,7 @@ import (
|
|||
"github.com/cosmos/cosmos-sdk/client/grpc/tmservice"
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
"github.com/cosmos/cosmos-sdk/codec/types"
|
||||
"github.com/cosmos/cosmos-sdk/server"
|
||||
"github.com/cosmos/cosmos-sdk/server/api"
|
||||
"github.com/cosmos/cosmos-sdk/server/config"
|
||||
servertypes "github.com/cosmos/cosmos-sdk/server/types"
|
||||
|
@ -42,6 +43,7 @@ import (
|
|||
capabilitykeeper "github.com/cosmos/cosmos-sdk/x/capability/keeper"
|
||||
capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/types"
|
||||
|
||||
authmiddleware "github.com/cosmos/cosmos-sdk/x/auth/middleware"
|
||||
"github.com/cosmos/cosmos-sdk/x/authz"
|
||||
authzkeeper "github.com/cosmos/cosmos-sdk/x/authz/keeper"
|
||||
authzmodule "github.com/cosmos/cosmos-sdk/x/authz/module"
|
||||
|
@ -140,6 +142,8 @@ type SimApp struct {
|
|||
legacyAmino *codec.LegacyAmino
|
||||
appCodec codec.Codec
|
||||
interfaceRegistry types.InterfaceRegistry
|
||||
msgSvcRouter *authmiddleware.MsgServiceRouter
|
||||
legacyRouter sdk.Router
|
||||
|
||||
invCheckPeriod uint
|
||||
|
||||
|
@ -216,6 +220,8 @@ func NewSimApp(
|
|||
legacyAmino: legacyAmino,
|
||||
appCodec: appCodec,
|
||||
interfaceRegistry: interfaceRegistry,
|
||||
legacyRouter: authmiddleware.NewLegacyRouter(),
|
||||
msgSvcRouter: authmiddleware.NewMsgServiceRouter(interfaceRegistry),
|
||||
invCheckPeriod: invCheckPeriod,
|
||||
keys: keys,
|
||||
tkeys: tkeys,
|
||||
|
@ -266,7 +272,7 @@ func NewSimApp(
|
|||
stakingtypes.NewMultiStakingHooks(app.DistrKeeper.Hooks(), app.SlashingKeeper.Hooks()),
|
||||
)
|
||||
|
||||
app.AuthzKeeper = authzkeeper.NewKeeper(keys[authzkeeper.StoreKey], appCodec, app.BaseApp.MsgServiceRouter())
|
||||
app.AuthzKeeper = authzkeeper.NewKeeper(keys[authzkeeper.StoreKey], appCodec, app.msgSvcRouter)
|
||||
|
||||
// register the proposal types
|
||||
govRouter := govtypes.NewRouter()
|
||||
|
@ -346,8 +352,8 @@ func NewSimApp(
|
|||
)
|
||||
|
||||
app.mm.RegisterInvariants(&app.CrisisKeeper)
|
||||
app.mm.RegisterRoutes(app.Router(), app.QueryRouter(), encodingConfig.Amino)
|
||||
app.configurator = module.NewConfigurator(app.appCodec, app.MsgServiceRouter(), app.GRPCQueryRouter())
|
||||
app.mm.RegisterRoutes(app.legacyRouter, app.QueryRouter(), encodingConfig.Amino)
|
||||
app.configurator = module.NewConfigurator(app.appCodec, app.msgSvcRouter, app.GRPCQueryRouter())
|
||||
app.mm.RegisterServices(app.configurator)
|
||||
|
||||
// add test gRPC service for testing gRPC queries in isolation
|
||||
|
@ -382,23 +388,8 @@ func NewSimApp(
|
|||
// initialize BaseApp
|
||||
app.SetInitChainer(app.InitChainer)
|
||||
app.SetBeginBlocker(app.BeginBlocker)
|
||||
|
||||
anteHandler, err := ante.NewAnteHandler(
|
||||
ante.HandlerOptions{
|
||||
AccountKeeper: app.AccountKeeper,
|
||||
BankKeeper: app.BankKeeper,
|
||||
SignModeHandler: encodingConfig.TxConfig.SignModeHandler(),
|
||||
FeegrantKeeper: app.FeeGrantKeeper,
|
||||
SigGasConsumer: ante.DefaultSigVerificationGasConsumer,
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
app.SetAnteHandler(anteHandler)
|
||||
app.SetEndBlocker(app.EndBlocker)
|
||||
app.setTxHandler(encodingConfig.TxConfig, cast.ToStringSlice(appOpts.Get(server.FlagIndexEvents)))
|
||||
|
||||
if loadLatest {
|
||||
if err := app.LoadLatestVersion(); err != nil {
|
||||
|
@ -409,6 +400,38 @@ func NewSimApp(
|
|||
return app
|
||||
}
|
||||
|
||||
func (app *SimApp) setTxHandler(txConfig client.TxConfig, indexEventsStr []string) {
|
||||
anteHandler, err := ante.NewAnteHandler(
|
||||
ante.HandlerOptions{
|
||||
AccountKeeper: app.AccountKeeper,
|
||||
BankKeeper: app.BankKeeper,
|
||||
SignModeHandler: txConfig.SignModeHandler(),
|
||||
FeegrantKeeper: app.FeeGrantKeeper,
|
||||
SigGasConsumer: ante.DefaultSigVerificationGasConsumer,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
indexEvents := map[string]struct{}{}
|
||||
for _, e := range indexEventsStr {
|
||||
indexEvents[e] = struct{}{}
|
||||
}
|
||||
txHandler, err := authmiddleware.NewDefaultTxHandler(authmiddleware.TxHandlerOptions{
|
||||
Debug: app.Trace(),
|
||||
IndexEvents: indexEvents,
|
||||
LegacyRouter: app.legacyRouter,
|
||||
MsgServiceRouter: app.msgSvcRouter,
|
||||
LegacyAnteHandler: anteHandler,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
app.SetTxHandler(txHandler)
|
||||
}
|
||||
|
||||
// Name returns the name of the App
|
||||
func (app *SimApp) Name() string { return app.BaseApp.Name() }
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
|
@ -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(),
|
||||
|
|
|
@ -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")
|
||||
}
|
|
@ -63,18 +63,32 @@ func (suite *AnteTestSuite) SetupTest(isCheckTx bool) {
|
|||
suite.clientCtx = client.Context{}.
|
||||
WithTxConfig(encodingConfig.TxConfig)
|
||||
|
||||
anteHandler, err := ante.NewAnteHandler(
|
||||
ante.HandlerOptions{
|
||||
AccountKeeper: suite.app.AccountKeeper,
|
||||
BankKeeper: suite.app.BankKeeper,
|
||||
FeegrantKeeper: suite.app.FeeGrantKeeper,
|
||||
SignModeHandler: encodingConfig.TxConfig.SignModeHandler(),
|
||||
SigGasConsumer: ante.DefaultSigVerificationGasConsumer,
|
||||
},
|
||||
)
|
||||
// We're not using ante.NewAnteHandler here because:
|
||||
// - ante.NewAnteHandler doesn't have SetUpContextDecorator, as it has been
|
||||
// moved to the gas TxMiddleware
|
||||
// - whereas these tests have not been migrated to middlewares yet, so
|
||||
// still need the SetUpContextDecorator.
|
||||
//
|
||||
// TODO: migrate all antehandler tests to middleware tests.
|
||||
// https://github.com/cosmos/cosmos-sdk/issues/9585
|
||||
anteDecorators := []sdk.AnteDecorator{
|
||||
ante.NewSetUpContextDecorator(),
|
||||
ante.NewRejectExtensionOptionsDecorator(),
|
||||
ante.NewMempoolFeeDecorator(),
|
||||
ante.NewValidateBasicDecorator(),
|
||||
ante.NewTxTimeoutHeightDecorator(),
|
||||
ante.NewValidateMemoDecorator(suite.app.AccountKeeper),
|
||||
ante.NewConsumeGasForTxSizeDecorator(suite.app.AccountKeeper),
|
||||
ante.NewDeductFeeDecorator(suite.app.AccountKeeper, suite.app.BankKeeper, suite.app.FeeGrantKeeper),
|
||||
// SetPubKeyDecorator must be called before all signature verification decorators
|
||||
ante.NewSetPubKeyDecorator(suite.app.AccountKeeper),
|
||||
ante.NewValidateSigCountDecorator(suite.app.AccountKeeper),
|
||||
ante.NewSigGasConsumeDecorator(suite.app.AccountKeeper, ante.DefaultSigVerificationGasConsumer),
|
||||
ante.NewSigVerificationDecorator(suite.app.AccountKeeper, encodingConfig.TxConfig.SignModeHandler()),
|
||||
ante.NewIncrementSequenceDecorator(suite.app.AccountKeeper),
|
||||
}
|
||||
|
||||
suite.Require().NoError(err)
|
||||
suite.anteHandler = anteHandler
|
||||
suite.anteHandler = sdk.ChainAnteDecorators(anteDecorators...)
|
||||
}
|
||||
|
||||
// CreateTestAccounts creates `numAccs` accounts, and return all relevant
|
||||
|
|
|
@ -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))
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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]
|
||||
}
|
|
@ -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() {
|
|
@ -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
|
||||
}
|
|
@ -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
|
|
@ -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
|
|
@ -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()),
|
||||
)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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))
|
||||
}
|
|
@ -122,8 +122,15 @@ func (s IntegrationTestSuite) TestSimulateTx_GRPC() {
|
|||
} else {
|
||||
s.Require().NoError(err)
|
||||
// Check the result and gas used are correct.
|
||||
s.Require().Equal(len(res.GetResult().GetEvents()), 6) // 1 coin recv 1 coin spent, 1 transfer, 3 messages.
|
||||
s.Require().True(res.GetGasInfo().GetGasUsed() > 0) // Gas used sometimes change, just check it's not empty.
|
||||
//
|
||||
// The 13 events are:
|
||||
// - Sending Fee to the pool: coin_spent, coin_received, transfer and message.sender=<val1>
|
||||
// - tx.* events: tx.fee, tx.acc_seq, tx.signature
|
||||
// - Sending Amount to recipient: coin_spent, coin_received, transfer and message.sender=<val1>
|
||||
// - Msg events: message.module=bank and message.action=/cosmos.bank.v1beta1.MsgSend
|
||||
s.Require().Equal(len(res.GetResult().GetEvents()), 13)
|
||||
// Check the result and gas used are correct.
|
||||
s.Require().True(res.GetGasInfo().GetGasUsed() > 0) // Gas used sometimes change, just check it's not empty.
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -163,8 +170,8 @@ func (s IntegrationTestSuite) TestSimulateTx_GRPCGateway() {
|
|||
err = val.ClientCtx.Codec.UnmarshalJSON(res, &result)
|
||||
s.Require().NoError(err)
|
||||
// Check the result and gas used are correct.
|
||||
s.Require().Equal(len(result.GetResult().GetEvents()), 6) // 1 coin recv, 1 coin spent,1 transfer, 3 messages.
|
||||
s.Require().True(result.GetGasInfo().GetGasUsed() > 0) // Gas used sometimes change, just check it's not empty.
|
||||
s.Require().Equal(len(result.GetResult().GetEvents()), 13) // See TestSimulateTx_GRPC for the 13 events.
|
||||
s.Require().True(result.GetGasInfo().GetGasUsed() > 0) // Gas used sometimes change, just check it's not empty.
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue