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:
+16
-27
@@ -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
|
||||
|
||||
+14
-284
@@ -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)
|
||||
}
|
||||
|
||||
+194
-215
@@ -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
|
||||
|
||||
@@ -1,140 +0,0 @@
|
||||
package baseapp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
gogogrpc "github.com/gogo/protobuf/grpc"
|
||||
"github.com/gogo/protobuf/proto"
|
||||
"google.golang.org/grpc"
|
||||
|
||||
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
||||
)
|
||||
|
||||
// MsgServiceRouter routes fully-qualified Msg service methods to their handler.
|
||||
type MsgServiceRouter struct {
|
||||
interfaceRegistry codectypes.InterfaceRegistry
|
||||
routes map[string]MsgServiceHandler
|
||||
}
|
||||
|
||||
var _ gogogrpc.Server = &MsgServiceRouter{}
|
||||
|
||||
// NewMsgServiceRouter creates a new MsgServiceRouter.
|
||||
func NewMsgServiceRouter() *MsgServiceRouter {
|
||||
return &MsgServiceRouter{
|
||||
routes: map[string]MsgServiceHandler{},
|
||||
}
|
||||
}
|
||||
|
||||
// MsgServiceHandler defines a function type which handles Msg service message.
|
||||
type MsgServiceHandler = func(ctx sdk.Context, req sdk.Msg) (*sdk.Result, error)
|
||||
|
||||
// Handler returns the MsgServiceHandler for a given msg or nil if not found.
|
||||
func (msr *MsgServiceRouter) Handler(msg sdk.Msg) MsgServiceHandler {
|
||||
return msr.routes[sdk.MsgTypeURL(msg)]
|
||||
}
|
||||
|
||||
// HandlerByTypeURL returns the MsgServiceHandler for a given query route path or nil
|
||||
// if not found.
|
||||
func (msr *MsgServiceRouter) HandlerByTypeURL(typeURL string) MsgServiceHandler {
|
||||
return msr.routes[typeURL]
|
||||
}
|
||||
|
||||
// RegisterService implements the gRPC Server.RegisterService method. sd is a gRPC
|
||||
// service description, handler is an object which implements that gRPC service.
|
||||
//
|
||||
// This function PANICs:
|
||||
// - if it is called before the service `Msg`s have been registered using
|
||||
// RegisterInterfaces,
|
||||
// - or if a service is being registered twice.
|
||||
func (msr *MsgServiceRouter) RegisterService(sd *grpc.ServiceDesc, handler interface{}) {
|
||||
// Adds a top-level query handler based on the gRPC service name.
|
||||
for _, method := range sd.Methods {
|
||||
fqMethod := fmt.Sprintf("/%s/%s", sd.ServiceName, method.MethodName)
|
||||
methodHandler := method.Handler
|
||||
|
||||
var requestTypeName string
|
||||
|
||||
// NOTE: This is how we pull the concrete request type for each handler for registering in the InterfaceRegistry.
|
||||
// This approach is maybe a bit hacky, but less hacky than reflecting on the handler object itself.
|
||||
// We use a no-op interceptor to avoid actually calling into the handler itself.
|
||||
_, _ = methodHandler(nil, context.Background(), func(i interface{}) error {
|
||||
msg, ok := i.(sdk.Msg)
|
||||
if !ok {
|
||||
// We panic here because there is no other alternative and the app cannot be initialized correctly
|
||||
// this should only happen if there is a problem with code generation in which case the app won't
|
||||
// work correctly anyway.
|
||||
panic(fmt.Errorf("can't register request type %T for service method %s", i, fqMethod))
|
||||
}
|
||||
|
||||
requestTypeName = sdk.MsgTypeURL(msg)
|
||||
return nil
|
||||
}, noopInterceptor)
|
||||
|
||||
// Check that the service Msg fully-qualified method name has already
|
||||
// been registered (via RegisterInterfaces). If the user registers a
|
||||
// service without registering according service Msg type, there might be
|
||||
// some unexpected behavior down the road. Since we can't return an error
|
||||
// (`Server.RegisterService` interface restriction) we panic (at startup).
|
||||
reqType, err := msr.interfaceRegistry.Resolve(requestTypeName)
|
||||
if err != nil || reqType == nil {
|
||||
panic(
|
||||
fmt.Errorf(
|
||||
"type_url %s has not been registered yet. "+
|
||||
"Before calling RegisterService, you must register all interfaces by calling the `RegisterInterfaces` "+
|
||||
"method on module.BasicManager. Each module should call `msgservice.RegisterMsgServiceDesc` inside its "+
|
||||
"`RegisterInterfaces` method with the `_Msg_serviceDesc` generated by proto-gen",
|
||||
requestTypeName,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
// Check that each service is only registered once. If a service is
|
||||
// registered more than once, then we should error. Since we can't
|
||||
// return an error (`Server.RegisterService` interface restriction) we
|
||||
// panic (at startup).
|
||||
_, found := msr.routes[requestTypeName]
|
||||
if found {
|
||||
panic(
|
||||
fmt.Errorf(
|
||||
"msg service %s has already been registered. Please make sure to only register each service once. "+
|
||||
"This usually means that there are conflicting modules registering the same msg service",
|
||||
fqMethod,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
msr.routes[requestTypeName] = func(ctx sdk.Context, req sdk.Msg) (*sdk.Result, error) {
|
||||
ctx = ctx.WithEventManager(sdk.NewEventManager())
|
||||
interceptor := func(goCtx context.Context, _ interface{}, _ *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
|
||||
goCtx = context.WithValue(goCtx, sdk.SdkContextKey, ctx)
|
||||
return handler(goCtx, req)
|
||||
}
|
||||
// Call the method handler from the service description with the handler object.
|
||||
// We don't do any decoding here because the decoding was already done.
|
||||
res, err := methodHandler(handler, sdk.WrapSDKContext(ctx), noopDecoder, interceptor)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resMsg, ok := res.(proto.Message)
|
||||
if !ok {
|
||||
return nil, sdkerrors.Wrapf(sdkerrors.ErrInvalidType, "Expecting proto.Message, got %T", resMsg)
|
||||
}
|
||||
|
||||
return sdk.WrapServiceResult(ctx, resMsg, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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,122 +0,0 @@
|
||||
package baseapp_test
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
"github.com/tendermint/tendermint/libs/log"
|
||||
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
|
||||
dbm "github.com/tendermint/tm-db"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/baseapp"
|
||||
"github.com/cosmos/cosmos-sdk/client/tx"
|
||||
"github.com/cosmos/cosmos-sdk/simapp"
|
||||
"github.com/cosmos/cosmos-sdk/testutil/testdata"
|
||||
"github.com/cosmos/cosmos-sdk/types/tx/signing"
|
||||
authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing"
|
||||
)
|
||||
|
||||
func TestRegisterMsgService(t *testing.T) {
|
||||
db := dbm.NewMemDB()
|
||||
|
||||
// Create an encoding config that doesn't register testdata Msg services.
|
||||
encCfg := simapp.MakeTestEncodingConfig()
|
||||
app := baseapp.NewBaseApp("test", log.NewTMLogger(log.NewSyncWriter(os.Stdout)), db, encCfg.TxConfig.TxDecoder())
|
||||
app.SetInterfaceRegistry(encCfg.InterfaceRegistry)
|
||||
require.Panics(t, func() {
|
||||
testdata.RegisterMsgServer(
|
||||
app.MsgServiceRouter(),
|
||||
testdata.MsgServerImpl{},
|
||||
)
|
||||
})
|
||||
|
||||
// Register testdata Msg services, and rerun `RegisterService`.
|
||||
testdata.RegisterInterfaces(encCfg.InterfaceRegistry)
|
||||
require.NotPanics(t, func() {
|
||||
testdata.RegisterMsgServer(
|
||||
app.MsgServiceRouter(),
|
||||
testdata.MsgServerImpl{},
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
func TestRegisterMsgServiceTwice(t *testing.T) {
|
||||
// Setup baseapp.
|
||||
db := dbm.NewMemDB()
|
||||
encCfg := simapp.MakeTestEncodingConfig()
|
||||
app := baseapp.NewBaseApp("test", log.NewTMLogger(log.NewSyncWriter(os.Stdout)), db, encCfg.TxConfig.TxDecoder())
|
||||
app.SetInterfaceRegistry(encCfg.InterfaceRegistry)
|
||||
testdata.RegisterInterfaces(encCfg.InterfaceRegistry)
|
||||
|
||||
// First time registering service shouldn't panic.
|
||||
require.NotPanics(t, func() {
|
||||
testdata.RegisterMsgServer(
|
||||
app.MsgServiceRouter(),
|
||||
testdata.MsgServerImpl{},
|
||||
)
|
||||
})
|
||||
|
||||
// Second time should panic.
|
||||
require.Panics(t, func() {
|
||||
testdata.RegisterMsgServer(
|
||||
app.MsgServiceRouter(),
|
||||
testdata.MsgServerImpl{},
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
func TestMsgService(t *testing.T) {
|
||||
priv, _, _ := testdata.KeyTestPubAddr()
|
||||
encCfg := simapp.MakeTestEncodingConfig()
|
||||
testdata.RegisterInterfaces(encCfg.InterfaceRegistry)
|
||||
db := dbm.NewMemDB()
|
||||
app := baseapp.NewBaseApp("test", log.NewTMLogger(log.NewSyncWriter(os.Stdout)), db, encCfg.TxConfig.TxDecoder())
|
||||
app.SetInterfaceRegistry(encCfg.InterfaceRegistry)
|
||||
testdata.RegisterMsgServer(
|
||||
app.MsgServiceRouter(),
|
||||
testdata.MsgServerImpl{},
|
||||
)
|
||||
_ = app.BeginBlock(abci.RequestBeginBlock{Header: tmproto.Header{Height: 1}})
|
||||
|
||||
msg := testdata.MsgCreateDog{Dog: &testdata.Dog{Name: "Spot"}}
|
||||
txBuilder := encCfg.TxConfig.NewTxBuilder()
|
||||
txBuilder.SetFeeAmount(testdata.NewTestFeeAmount())
|
||||
txBuilder.SetGasLimit(testdata.NewTestGasLimit())
|
||||
err := txBuilder.SetMsgs(&msg)
|
||||
require.NoError(t, err)
|
||||
|
||||
// First round: we gather all the signer infos. We use the "set empty
|
||||
// signature" hack to do that.
|
||||
sigV2 := signing.SignatureV2{
|
||||
PubKey: priv.PubKey(),
|
||||
Data: &signing.SingleSignatureData{
|
||||
SignMode: encCfg.TxConfig.SignModeHandler().DefaultMode(),
|
||||
Signature: nil,
|
||||
},
|
||||
Sequence: 0,
|
||||
}
|
||||
|
||||
err = txBuilder.SetSignatures(sigV2)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Second round: all signer infos are set, so each signer can sign.
|
||||
signerData := authsigning.SignerData{
|
||||
ChainID: "test",
|
||||
AccountNumber: 0,
|
||||
Sequence: 0,
|
||||
}
|
||||
sigV2, err = tx.SignWithPrivKey(
|
||||
encCfg.TxConfig.SignModeHandler().DefaultMode(), signerData,
|
||||
txBuilder, priv, encCfg.TxConfig, 0)
|
||||
require.NoError(t, err)
|
||||
err = txBuilder.SetSignatures(sigV2)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Send the tx to the app
|
||||
txBytes, err := encCfg.TxConfig.TxEncoder()(txBuilder.GetTx())
|
||||
require.NoError(t, err)
|
||||
res := app.DeliverTx(abci.RequestDeliverTx{Tx: txBytes})
|
||||
require.Equal(t, abci.CodeTypeOK, res.Code, "res=%+v", res)
|
||||
}
|
||||
+4
-12
@@ -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,41 +0,0 @@
|
||||
package baseapp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
type Router struct {
|
||||
routes map[string]sdk.Handler
|
||||
}
|
||||
|
||||
var _ sdk.Router = NewRouter()
|
||||
|
||||
// NewRouter returns a reference to a new router.
|
||||
func NewRouter() *Router {
|
||||
return &Router{
|
||||
routes: make(map[string]sdk.Handler),
|
||||
}
|
||||
}
|
||||
|
||||
// AddRoute adds a route path to the router with a given handler. The route must
|
||||
// be alphanumeric.
|
||||
func (rtr *Router) AddRoute(route sdk.Route) sdk.Router {
|
||||
if !sdk.IsAlphaNumeric(route.Path()) {
|
||||
panic("route expressions can only contain alphanumeric characters")
|
||||
}
|
||||
if rtr.routes[route.Path()] != nil {
|
||||
panic(fmt.Sprintf("route %s has already been initialized", route.Path()))
|
||||
}
|
||||
|
||||
rtr.routes[route.Path()] = route.Handler()
|
||||
return rtr
|
||||
}
|
||||
|
||||
// Route returns a handler for a given route path.
|
||||
//
|
||||
// TODO: Handle expressive matches.
|
||||
func (rtr *Router) Route(_ sdk.Context, path string) sdk.Handler {
|
||||
return rtr.routes[path]
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
package baseapp
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
var testHandler = func(_ sdk.Context, _ sdk.Msg) (*sdk.Result, error) {
|
||||
return &sdk.Result{}, nil
|
||||
}
|
||||
|
||||
func TestRouter(t *testing.T) {
|
||||
rtr := NewRouter()
|
||||
|
||||
// require panic on invalid route
|
||||
require.Panics(t, func() {
|
||||
rtr.AddRoute(sdk.NewRoute("*", testHandler))
|
||||
})
|
||||
|
||||
rtr.AddRoute(sdk.NewRoute("testRoute", testHandler))
|
||||
h := rtr.Route(sdk.Context{}, "testRoute")
|
||||
require.NotNil(t, h)
|
||||
|
||||
// require panic on duplicate route
|
||||
require.Panics(t, func() {
|
||||
rtr.AddRoute(sdk.NewRoute("testRoute", testHandler))
|
||||
})
|
||||
}
|
||||
+40
-7
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user