Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[backport/v0.40.x] Implement ADR031 #7599

Merged
merged 14 commits into from
Oct 20, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 22 additions & 6 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,23 +39,39 @@ Ref: https://keepachangelog.com/en/1.0.0/
### Client Breaking Changes

* __Modules__
* (x/staking) [\#7499](https://github.com/cosmos/cosmos-sdk/pull/7499) `BondStatus` is now a protobuf `enum` instead of an `int32`, and JSON serialized using its protobuf name, so expect names like `BOND_STATUS_UNBONDING` as opposed to `Unbonding`.
* (x/staking) [\#7499](https://github.com/cosmos/cosmos-sdk/pull/7499) `BondStatus` is now a protobuf `enum` instead
of an `int32`, and JSON serialized using its protobuf name, so expect names like `BOND_STATUS_UNBONDING` as opposed
to `Unbonding`.
* (x/staking) [\#7556](https://github.com/cosmos/cosmos-sdk/pull/7556) The ABCI's `Result.Data` field for
`MsgBeginRedelegate` and `MsgUndelegate` responses does not contain custom binary marshaled `completionTime`, but the
protobuf encoded `MsgBeginRedelegateResponse` and `MsgUndelegateResponse` structs respectively
* (x/evidence) [\#7538](https://github.com/cosmos/cosmos-sdk/pull/7538) The ABCI's `Result.Data` field for
`MsgSubmitEvidence` responses does not contain the raw evidence's hash, but the protobuf encoded
`MsgSubmitEvidenceResponse` struct.
* (x/gov) [\#7533](https://github.com/cosmos/cosmos-sdk/pull/7533) The ABCI's `Result.Data` field for
`MsgSubmitProposal` responses does not contain a raw binary encoding of the `proposalID`, but the protobuf encoded
`MsgSubmitSubmitProposalResponse` struct.

### API Breaking

* (AppModule) [\#7518](https://github.com/cosmos/cosmos-sdk/pull/7518) [\#7584](https://github.com/cosmos/cosmos-sdk/pull/7584) Rename `AppModule.RegisterQueryServices` to `AppModule.RegisterServices`, as this method now registers multiple services (the gRPC query service and the protobuf Msg service). A `Configurator` struct is used to hold the different services.

### Features

* (cli) \#7221 Add the option of emitting amino encoded json from the CLI
* (codec) [\#7519](https://github.com/cosmos/cosmos-sdk/pull/7519) `InterfaceRegistry` now inherits `jsonpb.AnyResolver`, and has a `RegisterCustomTypeURL` method to support ADR 031 packing of `Any`s. `AnyResolver` is now a required parameter to `RejectUnknownFields`.
* (baseapp) [\#7519](https://github.com/cosmos/cosmos-sdk/pull/7519) Add `ServiceMsgRouter` to BaseApp to handle routing of protobuf service `Msg`s. The two new types defined in ADR 031, `sdk.ServiceMsg` and `sdk.MsgRequest` are introduced with this router.
* (cli) [\#7221](https://github.com/cosmos/cosmos-sdk/pull/) Add the option of emitting amino encoded json from the CLI

### Bug Fixes

* (kvstore) [\#7415](https://github.com/cosmos/cosmos-sdk/pull/7415) Allow new stores to be registered during on-chain upgrades.

### Improvements

* (tendermint) \#7527 Update sdk to tendermint 0.34-rc5
* (iavl) \#7549 Update sdk to IAVL 0.15.0-rc4
* (tendermint) [\#7527](https://github.com/cosmos/cosmos-sdk/pull/7527) Update sdk to tendermint 0.34-rc5
* (iavl) [\#7549](https://github.com/cosmos/cosmos-sdk/pull/7549) Update sdk to IAVL 0.15.0-rc4


## v0.40.0-rc0 - 2020-10-13
## [v0.40.0-rc0](https://github.com/cosmos/cosmos-sdk/releases/tag/v0.40.0-rc0) - 2020-10-13

v0.40.0, known as the Stargate release of the Cosmos SDK, is one of the largest releases
of the Cosmos SDK since launch. Please read through this changelog and [release notes](./RELEASE_NOTES.md) to make sure you are aware of any relevant breaking changes.
Expand Down
2 changes: 1 addition & 1 deletion baseapp/abci.go
Original file line number Diff line number Diff line change
Expand Up @@ -673,7 +673,7 @@ func handleQueryApp(app *BaseApp, path []string, req abci.RequestQuery) abci.Res
Result: res,
}

bz, err := codec.ProtoMarshalJSON(simRes)
bz, err := codec.ProtoMarshalJSON(simRes, app.interfaceRegistry)
if err != nil {
return sdkerrors.QueryResult(sdkerrors.Wrap(err, "failed to JSON encode simulation response"))
}
Expand Down
77 changes: 51 additions & 26 deletions baseapp/baseapp.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
dbm "github.com/tendermint/tm-db"

"github.com/cosmos/cosmos-sdk/codec/types"
"github.com/cosmos/cosmos-sdk/snapshots"
"github.com/cosmos/cosmos-sdk/store"
"github.com/cosmos/cosmos-sdk/store/rootmulti"
Expand Down Expand Up @@ -45,15 +46,17 @@ type (
// BaseApp reflects the ABCI application implementation.
type BaseApp struct { // nolint: maligned
// initialized on creation
logger log.Logger
name string // application name from abci.Info
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
txDecoder sdk.TxDecoder // unmarshal []byte into sdk.Tx
logger log.Logger
name string // application name from abci.Info
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
initChainer sdk.InitChainer // initialize state with validators and state blob
Expand Down Expand Up @@ -136,16 +139,17 @@ 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(),
txDecoder: txDecoder,
fauxMerkleMode: false,
logger: logger,
name: name,
db: db,
cms: store.NewCommitMultiStore(db),
storeLoader: DefaultStoreLoader,
router: NewRouter(),
queryRouter: NewQueryRouter(),
grpcQueryRouter: NewGRPCQueryRouter(),
msgServiceRouter: NewMsgServiceRouter(),
txDecoder: txDecoder,
fauxMerkleMode: false,
}

for _, option := range options {
Expand Down Expand Up @@ -176,6 +180,9 @@ func (app *BaseApp) Logger() log.Logger {
return app.logger
}

// 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) {
Expand Down Expand Up @@ -687,20 +694,38 @@ func (app *BaseApp) runMsgs(ctx sdk.Context, msgs []sdk.Msg, mode runTxMode) (*s
break
}

msgRoute := msg.Route()
handler := app.router.Route(ctx, msgRoute)
var (
msgEvents sdk.Events
msgResult *sdk.Result
msgFqName string
err error
)

if svcMsg, ok := msg.(sdk.ServiceMsg); ok {
msgFqName = svcMsg.MethodName
handler := app.msgServiceRouter.Handler(msgFqName)
if handler == nil {
return nil, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "unrecognized message service method: %s; message index: %d", msgFqName, i)
}
msgResult, err = handler(ctx, svcMsg.Request)
} else {
// legacy sdk.Msg routing
msgRoute := msg.Route()
msgFqName = msg.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)
}

if handler == nil {
return nil, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "unrecognized message route: %s; message index: %d", msgRoute, i)
msgResult, err = handler(ctx, msg)
}

msgResult, err := handler(ctx, 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, msg.Type())),
msgEvents = sdk.Events{
sdk.NewEvent(sdk.EventTypeMessage, sdk.NewAttribute(sdk.AttributeKeyAction, msgFqName)),
}
msgEvents = msgEvents.AppendEvents(msgResult.GetEvents())

Expand Down
6 changes: 3 additions & 3 deletions baseapp/baseapp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1701,9 +1701,9 @@ func TestQuery(t *testing.T) {

func TestGRPCQuery(t *testing.T) {
grpcQueryOpt := func(bapp *BaseApp) {
testdata.RegisterTestServiceServer(
testdata.RegisterQueryServer(
bapp.GRPCQueryRouter(),
testdata.TestServiceImpl{},
testdata.QueryImpl{},
)
}

Expand All @@ -1720,7 +1720,7 @@ func TestGRPCQuery(t *testing.T) {

reqQuery := abci.RequestQuery{
Data: reqBz,
Path: "/testdata.TestService/SayHello",
Path: "/testdata.Query/SayHello",
}

resQuery := app.Query(reqQuery)
Expand Down
11 changes: 6 additions & 5 deletions baseapp/grpcrouter_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,11 @@ import (
gocontext "context"
"fmt"

"github.com/cosmos/cosmos-sdk/codec/types"

gogogrpc "github.com/gogo/protobuf/grpc"
abci "github.com/tendermint/tendermint/abci/types"
"google.golang.org/grpc"

"github.com/cosmos/cosmos-sdk/codec/types"
sdk "github.com/cosmos/cosmos-sdk/types"
)

Expand All @@ -22,6 +21,11 @@ type QueryServiceTestHelper struct {
ctx sdk.Context
}

var (
_ gogogrpc.Server = &QueryServiceTestHelper{}
_ gogogrpc.ClientConn = &QueryServiceTestHelper{}
)

// NewQueryServerTestHelper creates a new QueryServiceTestHelper that wraps
// the provided sdk.Context
func NewQueryServerTestHelper(ctx sdk.Context, interfaceRegistry types.InterfaceRegistry) *QueryServiceTestHelper {
Expand Down Expand Up @@ -62,6 +66,3 @@ func (q *QueryServiceTestHelper) Invoke(_ gocontext.Context, method string, args
func (q *QueryServiceTestHelper) NewStream(gocontext.Context, *grpc.StreamDesc, string, ...grpc.CallOption) (grpc.ClientStream, error) {
return nil, fmt.Errorf("not supported")
}

var _ gogogrpc.Server = &QueryServiceTestHelper{}
var _ gogogrpc.ClientConn = &QueryServiceTestHelper{}
4 changes: 2 additions & 2 deletions baseapp/grpcrouter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@ func TestGRPCRouter(t *testing.T) {
qr := NewGRPCQueryRouter()
interfaceRegistry := testdata.NewTestInterfaceRegistry()
qr.SetInterfaceRegistry(interfaceRegistry)
testdata.RegisterTestServiceServer(qr, testdata.TestServiceImpl{})
testdata.RegisterQueryServer(qr, testdata.QueryImpl{})
helper := &QueryServiceTestHelper{
GRPCQueryRouter: qr,
ctx: sdk.Context{}.WithContext(context.Background()),
}
client := testdata.NewTestServiceClient(helper)
client := testdata.NewQueryClient(helper)

res, err := client.Echo(context.Background(), &testdata.EchoRequest{Message: "hello"})
require.Nil(t, err)
Expand Down
94 changes: 94 additions & 0 deletions baseapp/msg_service_router.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package baseapp

import (
"context"
"fmt"

"github.com/gogo/protobuf/proto"

gogogrpc "github.com/gogo/protobuf/grpc"
"google.golang.org/grpc"

codectypes "github.com/cosmos/cosmos-sdk/codec/types"
sdk "github.com/cosmos/cosmos-sdk/types"
)

// 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.MsgRequest) (*sdk.Result, error)

// Handler returns the MsgServiceHandler for a given query route path or nil
// if not found.
func (msr *MsgServiceRouter) Handler(methodName string) MsgServiceHandler {
return msr.routes[methodName]
}

// RegisterService implements the gRPC Server.RegisterService method. sd is a gRPC
// service description, handler is an object which implements that gRPC service.
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

// 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.(proto.Message)
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))
}

msr.interfaceRegistry.RegisterCustomTypeURL((*sdk.MsgRequest)(nil), fqMethod, msg)
return nil
}, func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
return nil, nil
})

msr.routes[fqMethod] = func(ctx sdk.Context, req sdk.MsgRequest) (*sdk.Result, error) {
ctx = ctx.WithEventManager(sdk.NewEventManager())

// Call the method handler from the service description with the handler object.
res, err := methodHandler(handler, sdk.WrapSDKContext(ctx), func(_ interface{}) error {
// We don't do any decoding here because the decoding was already done.
return nil
}, func(goCtx context.Context, _ interface{}, _ *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
goCtx = context.WithValue(goCtx, sdk.SdkContextKey, ctx)
return handler(goCtx, req)
})
if err != nil {
return nil, err
}

resMsg, ok := res.(proto.Message)
if !ok {
return nil, fmt.Errorf("can't proto encode %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
}
Loading