Skip to content

Commit

Permalink
feat(runtime): message router service (#19571)
Browse files Browse the repository at this point in the history
  • Loading branch information
julienrbrt authored Mar 4, 2024
1 parent 83a7f0e commit cfd426f
Show file tree
Hide file tree
Showing 22 changed files with 419 additions and 55 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ Every module contains its own CHANGELOG.md. Please refer to the module you are i

### Features

* (runtime) [#19571](https://github.com/cosmos/cosmos-sdk/pull/19571) Implement `core/router.Service` it in runtime. This service is present in all modules (when using depinject).
* (types) [#19164](https://github.com/cosmos/cosmos-sdk/pull/19164) Add a ValueCodec for the math.Uint type that can be used in collections maps.
* (types) [#19281](https://github.com/cosmos/cosmos-sdk/pull/19281) Added a new method, `IsGT`, for `types.Coin`. This method is used to check if a `types.Coin` is greater than another `types.Coin`.
* (client) [#18557](https://github.com/cosmos/cosmos-sdk/pull/18557) Add `--qrcode` flag to `keys show` command to support displaying keys address QR code.
Expand Down
6 changes: 2 additions & 4 deletions baseapp/baseapp.go
Original file line number Diff line number Diff line change
Expand Up @@ -281,10 +281,8 @@ func (app *BaseApp) Trace() bool {
// MsgServiceRouter returns the MsgServiceRouter of a BaseApp.
func (app *BaseApp) MsgServiceRouter() *MsgServiceRouter { return app.msgServiceRouter }

// SetMsgServiceRouter sets the MsgServiceRouter of a BaseApp.
func (app *BaseApp) SetMsgServiceRouter(msgServiceRouter *MsgServiceRouter) {
app.msgServiceRouter = msgServiceRouter
}
// GRPCQueryRouter returns the GRPCQueryRouter of a BaseApp.
func (app *BaseApp) GRPCQueryRouter() *GRPCQueryRouter { return app.grpcQueryRouter }

// MountStores mounts all IAVL or DB stores to the provided keys in the BaseApp
// multistore.
Expand Down
17 changes: 15 additions & 2 deletions baseapp/grpcrouter.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ type GRPCQueryRouter struct {
// hybridHandlers maps the request name to the handler. It is a hybrid handler which seamlessly
// handles both gogo and protov2 messages.
hybridHandlers map[string][]func(ctx context.Context, req, resp protoiface.MessageV1) error
// responseByRequestName maps the request name to the response name.
responseByRequestName map[string]string
// binaryCodec is used to encode/decode binary protobuf messages.
binaryCodec codec.BinaryCodec
// cdc is the gRPC codec used by the router to correctly unmarshal messages.
Expand All @@ -43,8 +45,9 @@ var _ gogogrpc.Server = &GRPCQueryRouter{}
// NewGRPCQueryRouter creates a new GRPCQueryRouter
func NewGRPCQueryRouter() *GRPCQueryRouter {
return &GRPCQueryRouter{
routes: map[string]GRPCQueryHandler{},
hybridHandlers: map[string][]func(ctx context.Context, req, resp protoiface.MessageV1) error{},
routes: map[string]GRPCQueryHandler{},
hybridHandlers: map[string][]func(ctx context.Context, req, resp protoiface.MessageV1) error{},
responseByRequestName: map[string]string{},
}
}

Expand Down Expand Up @@ -133,16 +136,26 @@ func (qrt *GRPCQueryRouter) HybridHandlerByRequestName(name string) []func(ctx c
return qrt.hybridHandlers[name]
}

func (qrt *GRPCQueryRouter) ResponseNameByRequestName(requestName string) string {
return qrt.responseByRequestName[requestName]
}

func (qrt *GRPCQueryRouter) registerHybridHandler(sd *grpc.ServiceDesc, method grpc.MethodDesc, handler interface{}) error {
// extract message name from method descriptor
inputName, err := protocompat.RequestFullNameFromMethodDesc(sd, method)
if err != nil {
return err
}
outputName, err := protocompat.ResponseFullNameFromMethodDesc(sd, method)
if err != nil {
return err
}
methodHandler, err := protocompat.MakeHybridHandler(qrt.binaryCodec, sd, method, handler)
if err != nil {
return err
}
// map input name to output name
qrt.responseByRequestName[string(inputName)] = string(outputName)
qrt.hybridHandlers[string(inputName)] = append(qrt.hybridHandlers[string(inputName)], methodHandler)
return nil
}
Expand Down
3 changes: 0 additions & 3 deletions baseapp/grpcserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,6 @@ import (
grpctypes "github.com/cosmos/cosmos-sdk/types/grpc"
)

// GRPCQueryRouter returns the GRPCQueryRouter of a BaseApp.
func (app *BaseApp) GRPCQueryRouter() *GRPCQueryRouter { return app.grpcQueryRouter }

// RegisterGRPCServer registers gRPC services directly with the gRPC server.
func (app *BaseApp) RegisterGRPCServer(server gogogrpc.Server) {
// Define an interceptor for all gRPC queries: this interceptor will create
Expand Down
13 changes: 8 additions & 5 deletions baseapp/msg_service_router.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,17 @@ import (
type MessageRouter interface {
Handler(msg sdk.Msg) MsgServiceHandler
HandlerByTypeURL(typeURL string) MsgServiceHandler

ResponseNameByMsgName(msgName string) string
HybridHandlerByMsgName(msgName string) func(ctx context.Context, req, resp protoiface.MessageV1) error
}

// MsgServiceRouter routes fully-qualified Msg service methods to their handler.
type MsgServiceRouter struct {
interfaceRegistry codectypes.InterfaceRegistry
routes map[string]MsgServiceHandler
hybridHandlers map[string]func(ctx context.Context, req, resp protoiface.MessageV1) error
responseByRequest map[string]string
responseByMsgName map[string]string
circuitBreaker CircuitBreaker
}

Expand All @@ -42,7 +45,7 @@ func NewMsgServiceRouter() *MsgServiceRouter {
return &MsgServiceRouter{
routes: map[string]MsgServiceHandler{},
hybridHandlers: map[string]func(ctx context.Context, req, resp protoiface.MessageV1) error{},
responseByRequest: map[string]string{},
responseByMsgName: map[string]string{},
circuitBreaker: nil,
}
}
Expand Down Expand Up @@ -90,8 +93,8 @@ func (msr *MsgServiceRouter) HybridHandlerByMsgName(msgName string) func(ctx con
return msr.hybridHandlers[msgName]
}

func (msr *MsgServiceRouter) ResponseNameByRequestName(msgName string) string {
return msr.responseByRequest[msgName]
func (msr *MsgServiceRouter) ResponseNameByMsgName(msgName string) string {
return msr.responseByMsgName[msgName]
}

func (msr *MsgServiceRouter) registerHybridHandler(sd *grpc.ServiceDesc, method grpc.MethodDesc, handler interface{}) error {
Expand All @@ -109,7 +112,7 @@ func (msr *MsgServiceRouter) registerHybridHandler(sd *grpc.ServiceDesc, method
return err
}
// map input name to output name
msr.responseByRequest[string(inputName)] = string(outputName)
msr.responseByMsgName[string(inputName)] = string(outputName)
// if circuit breaker is not nil, then we decorate the hybrid handler with the circuit breaker
if msr.circuitBreaker == nil {
msr.hybridHandlers[string(inputName)] = hybridHandler
Expand Down
10 changes: 10 additions & 0 deletions baseapp/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -385,3 +385,13 @@ func (app *BaseApp) SetStoreMetrics(gatherer metrics.StoreMetrics) {
func (app *BaseApp) SetStreamingManager(manager storetypes.StreamingManager) {
app.streamingManager = manager
}

// SetMsgServiceRouter sets the MsgServiceRouter of a BaseApp.
func (app *BaseApp) SetMsgServiceRouter(msgServiceRouter *MsgServiceRouter) {
app.msgServiceRouter = msgServiceRouter
}

// SetGRPCQueryRouter sets the GRPCQueryRouter of the BaseApp.
func (app *BaseApp) SetGRPCQueryRouter(grpcQueryRouter *GRPCQueryRouter) {
app.grpcQueryRouter = grpcQueryRouter
}
2 changes: 1 addition & 1 deletion client/grpc_query_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ func (s *IntegrationTestSuite) SetupSuite() {
s.testClient = testdata.NewQueryClient(queryHelper)

kvs := runtime.NewKVStoreService(keys[countertypes.StoreKey])
counterKeeper := counterkeeper.NewKeeper(kvs, runtime.EventService{})
counterKeeper := counterkeeper.NewKeeper(runtime.NewEnvironment(kvs, logger))
countertypes.RegisterQueryServer(queryHelper, counterKeeper)
s.counterClient = countertypes.NewQueryClient(queryHelper)
}
Expand Down
4 changes: 3 additions & 1 deletion core/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,9 @@ Ref: https://keepachangelog.com/en/1.0.0/
* [#18457](https://github.com/cosmos/cosmos-sdk/pull/18457) Add branch.ExecuteWithGasLimit.
* [#19041](https://github.com/cosmos/cosmos-sdk/pull/19041) Add `appmodule.Environment` interface to fetch different services
* [#19370](https://github.com/cosmos/cosmos-sdk/pull/19370) Add `appmodule.Migrations` interface to handle migrations
* [#19617](https://github.com/cosmos/cosmos-sdk/pull/19617) Add DataBaseService to store non-consensus data in a database
* [#19571](https://github.com/cosmos/cosmos-sdk/pull/19571) Add `router.Service` and add it in `appmodule.Environment`
* [#19617](https://github.com/cosmos/cosmos-sdk/pull/19617) Server/v2 compatible interface:
* Add DataBaseService to store non-consensus data in a database
* Create V2 appmodule with v2 api for runtime/v2
* Introduce `Transaction.Tx` for use in runtime/v2
* Introduce `HasUpdateValidators` interface and `ValidatorUpdate` struct for validator updates
Expand Down
15 changes: 9 additions & 6 deletions core/appmodule/v2/environment.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,21 @@ import (
"cosmossdk.io/core/event"
"cosmossdk.io/core/gas"
"cosmossdk.io/core/header"
"cosmossdk.io/core/router"
"cosmossdk.io/core/store"
"cosmossdk.io/log"
)

// Environment is used to get all services to their respective module
type Environment struct {
BranchService branch.Service
EventService event.Service
GasService gas.Service
HeaderService header.Service
Logger log.Logger

BranchService branch.Service
EventService event.Service
GasService gas.Service
HeaderService header.Service
RouterService router.Service

KVStoreService store.KVStoreService
MemStoreService store.MemoryStoreService
DataBaseService store.DatabaseService
Logger log.Logger
}
File renamed without changes.
24 changes: 24 additions & 0 deletions core/router/service.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package router

import (
"context"

"google.golang.org/protobuf/runtime/protoiface"
)

// Service embeds a QueryRouterService and MessageRouterService.
// Each router allows to invoke messages and queries via the corresponding router.
type Service interface {
QueryRouterService() Router
MessageRouterService() Router
}

// Router is the interface that wraps the basic methods for a router.
type Router interface {
// CanInvoke returns an error if the given request cannot be invoked.
CanInvoke(ctx context.Context, req protoiface.MessageV1) error
// InvokeTyped execute a message or query. It should be used when the called knows the type of the response.
InvokeTyped(ctx context.Context, req, res protoiface.MessageV1) error
// InvokeUntyped execute a message or query. It should be used when the called doesn't know the type of the response.
InvokeUntyped(ctx context.Context, req protoiface.MessageV1) (res protoiface.MessageV1, err error)
}
1 change: 1 addition & 0 deletions runtime/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ type App struct {
amino *codec.LegacyAmino
baseAppOptions []BaseAppOption
msgServiceRouter *baseapp.MsgServiceRouter
grpcQueryRouter *baseapp.GRPCQueryRouter
appConfig *appv1alpha1.Config
logger log.Logger
// initChainer is the init chainer function defined by the app config.
Expand Down
1 change: 1 addition & 0 deletions runtime/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ func (a *AppBuilder) Build(db dbm.DB, traceStore io.Writer, baseAppOptions ...fu

bApp := baseapp.NewBaseApp(a.app.config.AppName, a.app.logger, db, nil, baseAppOptions...)
bApp.SetMsgServiceRouter(a.app.msgServiceRouter)
bApp.SetGRPCQueryRouter(a.app.grpcQueryRouter)
bApp.SetCommitMultiStoreTracer(traceStore)
bApp.SetVersion(version.Version)
bApp.SetInterfaceRegistry(a.app.interfaceRegistry)
Expand Down
35 changes: 31 additions & 4 deletions runtime/environment.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,44 @@ import (
"cosmossdk.io/core/appmodule"
"cosmossdk.io/core/store"
"cosmossdk.io/log"

"github.com/cosmos/cosmos-sdk/baseapp"
)

// NewEnvironment creates a new environment for the application
// if memstoreservice is needed, it can be added to the environment: environment.MemStoreService = memstoreservice
func NewEnvironment(kvService store.KVStoreService, logger log.Logger) appmodule.Environment {
return appmodule.Environment{
// For setting custom services that aren't set by default, use the EnvOption
// Note: Depinject always provide an environment with all services (mandatory and optional)
func NewEnvironment(
kvService store.KVStoreService,
logger log.Logger,
opts ...EnvOption,
) appmodule.Environment {
env := appmodule.Environment{
Logger: logger,
EventService: EventService{},
HeaderService: HeaderService{},
BranchService: BranchService{},
GasService: GasService{},
KVStoreService: kvService,
Logger: logger,
}

for _, opt := range opts {
opt(&env)
}

return env
}

type EnvOption func(*appmodule.Environment)

func EnvWithRouterService(queryServiceRouter *baseapp.GRPCQueryRouter, msgServiceRouter *baseapp.MsgServiceRouter) EnvOption {
return func(env *appmodule.Environment) {
env.RouterService = NewRouterService(env.KVStoreService, queryServiceRouter, msgServiceRouter)
}
}

func EnvWithMemStoreService(memStoreService store.MemoryStoreService) EnvOption {
return func(env *appmodule.Environment) {
env.MemStoreService = memStoreService
}
}
29 changes: 21 additions & 8 deletions runtime/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,6 @@ func init() {
ProvideMemoryStoreKey,
ProvideGenesisTxHandler,
ProvideEnvironment,
ProvideMemoryStoreService,
ProvideTransientStoreService,
ProvideModuleManager,
ProvideAppVersionModifier,
Expand All @@ -82,6 +81,7 @@ func ProvideApp(interfaceRegistry codectypes.InterfaceRegistry) (
*codec.LegacyAmino,
*AppBuilder,
*baseapp.MsgServiceRouter,
*baseapp.GRPCQueryRouter,
appmodule.AppModule,
protodesc.Resolver,
protoregistry.MessageTypeResolver,
Expand All @@ -104,16 +104,18 @@ func ProvideApp(interfaceRegistry codectypes.InterfaceRegistry) (

cdc := codec.NewProtoCodec(interfaceRegistry)
msgServiceRouter := baseapp.NewMsgServiceRouter()
grpcQueryRouter := baseapp.NewGRPCQueryRouter()
app := &App{
storeKeys: nil,
interfaceRegistry: interfaceRegistry,
cdc: cdc,
amino: amino,
msgServiceRouter: msgServiceRouter,
grpcQueryRouter: grpcQueryRouter,
}
appBuilder := &AppBuilder{app}

return cdc, amino, appBuilder, msgServiceRouter, appModule{app}, protoFiles, protoTypes, nil
return cdc, amino, appBuilder, msgServiceRouter, grpcQueryRouter, appModule{app}, protoFiles, protoTypes, nil
}

type AppInputs struct {
Expand Down Expand Up @@ -212,15 +214,26 @@ func ProvideGenesisTxHandler(appBuilder *AppBuilder) genesis.TxHandler {
return appBuilder.app
}

func ProvideEnvironment(config *runtimev1alpha1.Module, key depinject.ModuleKey, app *AppBuilder, logger log.Logger) (store.KVStoreService, appmodule.Environment) {
func ProvideEnvironment(
logger log.Logger,
config *runtimev1alpha1.Module,
key depinject.ModuleKey,
app *AppBuilder,
msgServiceRouter *baseapp.MsgServiceRouter,
queryServiceRouter *baseapp.GRPCQueryRouter,
) (store.KVStoreService, store.MemoryStoreService, appmodule.Environment) {
storeKey := ProvideKVStoreKey(config, key, app)
kvService := kvStoreService{key: storeKey}
return kvService, NewEnvironment(kvService, logger)
}

func ProvideMemoryStoreService(key depinject.ModuleKey, app *AppBuilder) store.MemoryStoreService {
storeKey := ProvideMemoryStoreKey(key, app)
return memStoreService{key: storeKey}
memStoreKey := ProvideMemoryStoreKey(key, app)
memStoreService := memStoreService{key: memStoreKey}

return kvService, memStoreService, NewEnvironment(
kvService,
logger,
EnvWithRouterService(queryServiceRouter, msgServiceRouter),
EnvWithMemStoreService(memStoreService),
)
}

func ProvideTransientStoreService(key depinject.ModuleKey, app *AppBuilder) store.TransientStoreService {
Expand Down
Loading

0 comments on commit cfd426f

Please sign in to comment.