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

feat(runtime): message router service #19571

Merged
merged 20 commits into from
Mar 4, 2024
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).
julienrbrt marked this conversation as resolved.
Show resolved Hide resolved
* (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
tac0turtle marked this conversation as resolved.
Show resolved Hide resolved
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
julienrbrt marked this conversation as resolved.
Show resolved Hide resolved
// 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
Loading