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)
2021-08-25 07:40:33 -07:00
|
|
|
package middleware
|
2020-10-15 06:07:59 -07:00
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"fmt"
|
|
|
|
|
|
|
|
gogogrpc "github.com/gogo/protobuf/grpc"
|
2020-10-16 02:43:28 -07:00
|
|
|
"github.com/gogo/protobuf/proto"
|
2020-10-15 06:07:59 -07:00
|
|
|
"google.golang.org/grpc"
|
|
|
|
|
|
|
|
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
|
|
|
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
2020-10-16 02:43:28 -07:00
|
|
|
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
2020-10-15 06:07:59 -07:00
|
|
|
)
|
|
|
|
|
|
|
|
// 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.
|
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)
2021-08-25 07:40:33 -07:00
|
|
|
func NewMsgServiceRouter(registry codectypes.InterfaceRegistry) *MsgServiceRouter {
|
2020-10-15 06:07:59 -07:00
|
|
|
return &MsgServiceRouter{
|
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)
2021-08-25 07:40:33 -07:00
|
|
|
interfaceRegistry: registry,
|
|
|
|
routes: map[string]MsgServiceHandler{},
|
2020-10-15 06:07:59 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// MsgServiceHandler defines a function type which handles Msg service message.
|
2021-04-30 04:00:47 -07:00
|
|
|
type MsgServiceHandler = func(ctx sdk.Context, req sdk.Msg) (*sdk.Result, error)
|
2020-10-15 06:07:59 -07:00
|
|
|
|
2021-04-30 04:00:47 -07:00
|
|
|
// 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)]
|
|
|
|
}
|
|
|
|
|
2021-05-07 03:37:55 -07:00
|
|
|
// HandlerByTypeURL returns the MsgServiceHandler for a given query route path or nil
|
2020-10-15 06:07:59 -07:00
|
|
|
// if not found.
|
2021-05-07 03:37:55 -07:00
|
|
|
func (msr *MsgServiceRouter) HandlerByTypeURL(typeURL string) MsgServiceHandler {
|
2021-04-30 04:00:47 -07:00
|
|
|
return msr.routes[typeURL]
|
2020-10-15 06:07:59 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// RegisterService implements the gRPC Server.RegisterService method. sd is a gRPC
|
|
|
|
// service description, handler is an object which implements that gRPC service.
|
2020-10-28 12:20:45 -07:00
|
|
|
//
|
2020-10-29 08:32:47 -07:00
|
|
|
// 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.
|
2020-10-15 06:07:59 -07:00
|
|
|
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
|
|
|
|
|
2021-04-30 04:00:47 -07:00
|
|
|
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)
|
|
|
|
|
2020-10-28 12:20:45 -07:00
|
|
|
// 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).
|
2021-04-30 04:00:47 -07:00
|
|
|
reqType, err := msr.interfaceRegistry.Resolve(requestTypeName)
|
|
|
|
if err != nil || reqType == nil {
|
2020-10-28 12:20:45 -07:00
|
|
|
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",
|
2021-04-30 04:00:47 -07:00
|
|
|
requestTypeName,
|
2020-10-28 12:20:45 -07:00
|
|
|
),
|
|
|
|
)
|
|
|
|
}
|
2020-10-15 06:07:59 -07:00
|
|
|
|
2020-10-29 08:32:47 -07:00
|
|
|
// 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).
|
2021-04-30 04:00:47 -07:00
|
|
|
_, found := msr.routes[requestTypeName]
|
2020-10-29 08:32:47 -07:00
|
|
|
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,
|
|
|
|
),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2021-04-30 04:00:47 -07:00
|
|
|
msr.routes[requestTypeName] = func(ctx sdk.Context, req sdk.Msg) (*sdk.Result, error) {
|
2020-10-15 06:07:59 -07:00
|
|
|
ctx = ctx.WithEventManager(sdk.NewEventManager())
|
2020-10-16 02:43:28 -07:00
|
|
|
interceptor := func(goCtx context.Context, _ interface{}, _ *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
|
2020-10-15 06:07:59 -07:00
|
|
|
goCtx = context.WithValue(goCtx, sdk.SdkContextKey, ctx)
|
|
|
|
return handler(goCtx, req)
|
2020-10-16 02:43:28 -07:00
|
|
|
}
|
|
|
|
// 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)
|
2020-10-15 06:07:59 -07:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
resMsg, ok := res.(proto.Message)
|
|
|
|
if !ok {
|
2020-10-16 02:43:28 -07:00
|
|
|
return nil, sdkerrors.Wrapf(sdkerrors.ErrInvalidType, "Expecting proto.Message, got %T", resMsg)
|
2020-10-15 06:07:59 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
return sdk.WrapServiceResult(ctx, resMsg, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-16 02:43:28 -07:00
|
|
|
func noopDecoder(_ interface{}) error { return nil }
|
2021-04-30 04:00:47 -07:00
|
|
|
func noopInterceptor(_ context.Context, _ interface{}, _ *grpc.UnaryServerInfo, _ grpc.UnaryHandler) (interface{}, error) {
|
|
|
|
return nil, nil
|
|
|
|
}
|