From 1f285297739df649e7a75a10e0516c33f3cb77b7 Mon Sep 17 00:00:00 2001 From: Julien Robert Date: Mon, 26 Feb 2024 21:46:29 +0100 Subject: [PATCH 01/18] feat: message router service --- core/appmodule/environment.go | 14 +++++++++----- core/gas/{meter.go => service.go} | 0 core/go.mod | 3 +++ core/go.sum | 5 +++++ core/router/service.go | 9 +++++++++ runtime/router.go | 30 ++++++++++++++++++++++++++++++ 6 files changed, 56 insertions(+), 5 deletions(-) rename core/gas/{meter.go => service.go} (100%) create mode 100644 core/router/service.go create mode 100644 runtime/router.go diff --git a/core/appmodule/environment.go b/core/appmodule/environment.go index 17ab778191c0..5e3ec6a74965 100644 --- a/core/appmodule/environment.go +++ b/core/appmodule/environment.go @@ -5,17 +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 + MessageRouterService router.Service + KVStoreService store.KVStoreService MemStoreService store.MemoryStoreService - Logger log.Logger } diff --git a/core/gas/meter.go b/core/gas/service.go similarity index 100% rename from core/gas/meter.go rename to core/gas/service.go diff --git a/core/go.mod b/core/go.mod index e7bf2f319d5e..e732129de93a 100644 --- a/core/go.mod +++ b/core/go.mod @@ -4,6 +4,7 @@ go 1.20 require ( cosmossdk.io/log v1.3.1 + github.com/cosmos/gogoproto v1.4.11 github.com/stretchr/testify v1.8.4 google.golang.org/grpc v1.62.0 google.golang.org/protobuf v1.32.0 @@ -12,6 +13,7 @@ require ( require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/golang/protobuf v1.5.3 // indirect + github.com/google/go-cmp v0.6.0 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect @@ -19,6 +21,7 @@ require ( github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rogpeppe/go-internal v1.11.0 // indirect github.com/rs/zerolog v1.32.0 // indirect + golang.org/x/exp v0.0.0-20230811145659-89c5cff77bcb // indirect golang.org/x/net v0.21.0 // indirect golang.org/x/sys v0.17.0 // indirect golang.org/x/text v0.14.0 // indirect diff --git a/core/go.sum b/core/go.sum index 105975b5b061..57133f587985 100644 --- a/core/go.sum +++ b/core/go.sum @@ -1,6 +1,8 @@ cosmossdk.io/log v1.3.1 h1:UZx8nWIkfbbNEWusZqzAx3ZGvu54TZacWib3EzUYmGI= cosmossdk.io/log v1.3.1/go.mod h1:2/dIomt8mKdk6vl3OWJcPk2be3pGOS8OQaLUM/3/tCM= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/cosmos/gogoproto v1.4.11 h1:LZcMHrx4FjUgrqQSWeaGC1v/TeuVFqSLa43CC6aWR2g= +github.com/cosmos/gogoproto v1.4.11/go.mod h1:/g39Mh8m17X8Q/GDEs5zYTSNaNnInBSohtaxzQnYq1Y= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -10,6 +12,7 @@ github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= @@ -36,6 +39,8 @@ github.com/rs/zerolog v1.32.0 h1:keLypqrlIjaFsbmJOBdB/qvyF8KEtCWHwobLp5l/mQ0= github.com/rs/zerolog v1.32.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +golang.org/x/exp v0.0.0-20230811145659-89c5cff77bcb h1:mIKbk8weKhSeLH2GmUTrvx8CjkyJmnU1wFmg59CUjFA= +golang.org/x/exp v0.0.0-20230811145659-89c5cff77bcb/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/core/router/service.go b/core/router/service.go new file mode 100644 index 000000000000..7d50df7a5b69 --- /dev/null +++ b/core/router/service.go @@ -0,0 +1,9 @@ +package router + +import gogoproto "github.com/cosmos/gogoproto/proto" + +// Service is the interface that wraps the basic methods for a router service. +type Service interface { + Handler(msg gogoproto.Message) Service + HandlerByTypeURL(typeURL string) Service +} diff --git a/runtime/router.go b/runtime/router.go new file mode 100644 index 000000000000..3060f3f91344 --- /dev/null +++ b/runtime/router.go @@ -0,0 +1,30 @@ +package runtime + +import ( + "cosmossdk.io/core/router" + "cosmossdk.io/core/store" + "github.com/cosmos/cosmos-sdk/baseapp" + "github.com/cosmos/gogoproto/proto" +) + +func NewMsgRouterService(storeService store.KVStoreService, router baseapp.MessageRouter) router.Service { + return &msgRouterService{ + storeService: storeService, + router: router, + } +} + +type msgRouterService struct { + storeService store.KVStoreService + router baseapp.MessageRouter +} + +// Handler implements router.Service. +func (m *msgRouterService) Handler(msg proto.Message) router.Service { + return m.router.Handler(msg) +} + +// HandlerByTypeURL implements router.Service. +func (*msgRouterService) HandlerByTypeURL(typeURL string) router.Service { + panic("unimplemented") +} From c8622b32830cdb8c44edeafbd6bd148c0f477b3b Mon Sep 17 00:00:00 2001 From: Julien Robert Date: Mon, 26 Feb 2024 23:46:33 +0100 Subject: [PATCH 02/18] updates --- core/router/service.go | 10 +++++++--- runtime/environment.go | 34 ++++++++++++++++++++++++++++++---- runtime/module.go | 17 ++++++++++------- runtime/router.go | 25 +++++++++++++++++-------- testutil/integration/router.go | 2 +- 5 files changed, 65 insertions(+), 23 deletions(-) diff --git a/core/router/service.go b/core/router/service.go index 7d50df7a5b69..fcf5c0c44599 100644 --- a/core/router/service.go +++ b/core/router/service.go @@ -1,9 +1,13 @@ package router -import gogoproto "github.com/cosmos/gogoproto/proto" +import ( + "context" + + "google.golang.org/protobuf/runtime/protoiface" +) // Service is the interface that wraps the basic methods for a router service. type Service interface { - Handler(msg gogoproto.Message) Service - HandlerByTypeURL(typeURL string) Service + InvokeTyped(ctx context.Context, req, res protoiface.MessageV1) error + // InvokeUntyped(ctx context.Context, req protoiface.MessageV1) (res protoiface.MessageV1, err error) } diff --git a/runtime/environment.go b/runtime/environment.go index 2a592363eb6d..d314e479c031 100644 --- a/runtime/environment.go +++ b/runtime/environment.go @@ -4,17 +4,43 @@ 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 EnvWithMessageRouterService(msgServiceRouter *baseapp.MsgServiceRouter) EnvOption { + return func(env *appmodule.Environment) { + env.MessageRouterService = NewMsgRouterService(env.KVStoreService, msgServiceRouter) + } +} + +func EnvWithMemStoreService(memStoreService store.MemoryStoreService) EnvOption { + return func(env *appmodule.Environment) { + env.MemStoreService = memStoreService } } diff --git a/runtime/module.go b/runtime/module.go index e00063df8642..328dea4bb49e 100644 --- a/runtime/module.go +++ b/runtime/module.go @@ -67,7 +67,6 @@ func init() { ProvideMemoryStoreKey, ProvideGenesisTxHandler, ProvideEnvironment, - ProvideMemoryStoreService, ProvideTransientStoreService, ProvideModuleManager, ProvideAppVersionModifier, @@ -212,15 +211,19 @@ 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) (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, + EnvWithMessageRouterService(msgServiceRouter), + EnvWithMemStoreService(memStoreService), + ) } func ProvideTransientStoreService(key depinject.ModuleKey, app *AppBuilder) store.TransientStoreService { diff --git a/runtime/router.go b/runtime/router.go index 3060f3f91344..d951dee4bd14 100644 --- a/runtime/router.go +++ b/runtime/router.go @@ -1,13 +1,17 @@ package runtime import ( + "context" + "cosmossdk.io/core/router" "cosmossdk.io/core/store" "github.com/cosmos/cosmos-sdk/baseapp" "github.com/cosmos/gogoproto/proto" + protov2 "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/runtime/protoiface" ) -func NewMsgRouterService(storeService store.KVStoreService, router baseapp.MessageRouter) router.Service { +func NewMsgRouterService(storeService store.KVStoreService, router *baseapp.MsgServiceRouter) router.Service { return &msgRouterService{ storeService: storeService, router: router, @@ -16,15 +20,20 @@ func NewMsgRouterService(storeService store.KVStoreService, router baseapp.Messa type msgRouterService struct { storeService store.KVStoreService - router baseapp.MessageRouter + router *baseapp.MsgServiceRouter } -// Handler implements router.Service. -func (m *msgRouterService) Handler(msg proto.Message) router.Service { - return m.router.Handler(msg) +// InvokeTyped implements router.Service. +func (m *msgRouterService) InvokeTyped(ctx context.Context, req protoiface.MessageV1, res protoiface.MessageV1) error { + messageName := msgTypeURL(req) + return m.router.HybridHandlerByMsgName(messageName)(ctx, req, res) } -// HandlerByTypeURL implements router.Service. -func (*msgRouterService) HandlerByTypeURL(typeURL string) router.Service { - panic("unimplemented") +// msgTypeURL returns the TypeURL of a `sdk.Msg`. +func msgTypeURL(msg proto.Message) string { + if m, ok := msg.(protov2.Message); ok { + return "/" + string(m.ProtoReflect().Descriptor().FullName()) + } + + return "/" + proto.MessageName(msg) } diff --git a/testutil/integration/router.go b/testutil/integration/router.go index 2784118eb2a2..6f4e2a647638 100644 --- a/testutil/integration/router.go +++ b/testutil/integration/router.go @@ -81,7 +81,7 @@ func NewIntegrationApp( if keys[consensusparamtypes.StoreKey] != nil { // set baseApp param store - consensusParamsKeeper := consensusparamkeeper.NewKeeper(appCodec, runtime.NewEnvironment(runtime.NewKVStoreService(keys[consensusparamtypes.StoreKey]), log.NewNopLogger()), authtypes.NewModuleAddress("gov").String()) + consensusParamsKeeper := consensusparamkeeper.NewKeeper(appCodec, runtime.NewEnvironment(runtime.NewKVStoreService(keys[consensusparamtypes.StoreKey]), log.NewNopLogger(), runtime.EnvWithMessageRouterService(router)), authtypes.NewModuleAddress("gov").String()) bApp.SetParamStore(consensusParamsKeeper.ParamsStore) if err := bApp.LoadLatestVersion(); err != nil { From cf6ff8a91ad97f39f5b874a5bbfd786381a6ae43 Mon Sep 17 00:00:00 2001 From: Julien Robert Date: Tue, 27 Feb 2024 00:03:20 +0100 Subject: [PATCH 03/18] `make lint-fix` --- runtime/environment.go | 1 + runtime/router.go | 10 ++++++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/runtime/environment.go b/runtime/environment.go index d314e479c031..da4f1e9ba13f 100644 --- a/runtime/environment.go +++ b/runtime/environment.go @@ -4,6 +4,7 @@ import ( "cosmossdk.io/core/appmodule" "cosmossdk.io/core/store" "cosmossdk.io/log" + "github.com/cosmos/cosmos-sdk/baseapp" ) diff --git a/runtime/router.go b/runtime/router.go index d951dee4bd14..64669b177de9 100644 --- a/runtime/router.go +++ b/runtime/router.go @@ -3,12 +3,14 @@ package runtime import ( "context" - "cosmossdk.io/core/router" - "cosmossdk.io/core/store" - "github.com/cosmos/cosmos-sdk/baseapp" "github.com/cosmos/gogoproto/proto" protov2 "google.golang.org/protobuf/proto" "google.golang.org/protobuf/runtime/protoiface" + + "cosmossdk.io/core/router" + "cosmossdk.io/core/store" + + "github.com/cosmos/cosmos-sdk/baseapp" ) func NewMsgRouterService(storeService store.KVStoreService, router *baseapp.MsgServiceRouter) router.Service { @@ -24,7 +26,7 @@ type msgRouterService struct { } // InvokeTyped implements router.Service. -func (m *msgRouterService) InvokeTyped(ctx context.Context, req protoiface.MessageV1, res protoiface.MessageV1) error { +func (m *msgRouterService) InvokeTyped(ctx context.Context, req, res protoiface.MessageV1) error { messageName := msgTypeURL(req) return m.router.HybridHandlerByMsgName(messageName)(ctx, req, res) } From 3c550b23763bfcecd85bc44bde9072047786a13b Mon Sep 17 00:00:00 2001 From: Julien Robert Date: Tue, 27 Feb 2024 08:48:10 +0100 Subject: [PATCH 04/18] updates --- core/router/service.go | 2 +- runtime/router.go | 40 +++++++++++++++++++++++++++++++++++++--- 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/core/router/service.go b/core/router/service.go index fcf5c0c44599..669d43db2c1d 100644 --- a/core/router/service.go +++ b/core/router/service.go @@ -9,5 +9,5 @@ import ( // Service is the interface that wraps the basic methods for a router service. type Service interface { InvokeTyped(ctx context.Context, req, res protoiface.MessageV1) error - // InvokeUntyped(ctx context.Context, req protoiface.MessageV1) (res protoiface.MessageV1, err error) + InvokeUntyped(ctx context.Context, req protoiface.MessageV1) (res protoiface.MessageV1, err error) } diff --git a/runtime/router.go b/runtime/router.go index 64669b177de9..baebea58ecca 100644 --- a/runtime/router.go +++ b/runtime/router.go @@ -2,9 +2,12 @@ package runtime import ( "context" + "fmt" "github.com/cosmos/gogoproto/proto" protov2 "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/reflect/protoreflect" + "google.golang.org/protobuf/reflect/protoregistry" "google.golang.org/protobuf/runtime/protoiface" "cosmossdk.io/core/router" @@ -17,18 +20,49 @@ func NewMsgRouterService(storeService store.KVStoreService, router *baseapp.MsgS return &msgRouterService{ storeService: storeService, router: router, + resolver: protoregistry.GlobalTypes, } } type msgRouterService struct { storeService store.KVStoreService router *baseapp.MsgServiceRouter + resolver protoregistry.MessageTypeResolver } // InvokeTyped implements router.Service. -func (m *msgRouterService) InvokeTyped(ctx context.Context, req, res protoiface.MessageV1) error { - messageName := msgTypeURL(req) - return m.router.HybridHandlerByMsgName(messageName)(ctx, req, res) +func (m *msgRouterService) InvokeTyped(ctx context.Context, msg, msgResp protoiface.MessageV1) error { + messageName := msgTypeURL(msg) + handler := m.router.HybridHandlerByMsgName(messageName) + if handler == nil { + return fmt.Errorf("unknown message: %s", messageName) + } + + return handler(ctx, msg, msgResp) +} + +// InvokeUntyped implements router.Service. +func (m *msgRouterService) InvokeUntyped(ctx context.Context, msg protoiface.MessageV1) (protoiface.MessageV1, error) { + messageName := msgTypeURL(msg) + respName := m.router.ResponseNameByRequestName(messageName) + if respName == "" { + return nil, fmt.Errorf("could not find response type for message %T", msg) + } + + // get response type + resp, err := m.resolver.FindMessageByName(protoreflect.FullName(respName)) + if err != nil { + return nil, err + } + + handler := m.router.HybridHandlerByMsgName(messageName) + if handler == nil { + return nil, fmt.Errorf("unknown message: %s", messageName) + } + + msgResp := resp.New().Interface().(protoiface.MessageV1) + err = handler(ctx, msg, msgResp) + return msgResp, err } // msgTypeURL returns the TypeURL of a `sdk.Msg`. From 9700f787e7269a7ecf7d39be1e66d29400c1262d Mon Sep 17 00:00:00 2001 From: Julien Robert Date: Tue, 27 Feb 2024 12:47:48 +0100 Subject: [PATCH 05/18] updates --- CHANGELOG.md | 1 + baseapp/msg_service_router.go | 3 +++ core/CHANGELOG.md | 3 ++- runtime/router.go | 26 ++++++++++++++------------ 4 files changed, 20 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e9939c7bd8d..c5d15e6869f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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. diff --git a/baseapp/msg_service_router.go b/baseapp/msg_service_router.go index fe4488d2673c..22f76cbafa3c 100644 --- a/baseapp/msg_service_router.go +++ b/baseapp/msg_service_router.go @@ -24,6 +24,9 @@ import ( type MessageRouter interface { Handler(msg sdk.Msg) MsgServiceHandler HandlerByTypeURL(typeURL string) MsgServiceHandler + + ResponseNameByRequestName(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. diff --git a/core/CHANGELOG.md b/core/CHANGELOG.md index 27ee7b8be825..49a3e692f14d 100644 --- a/core/CHANGELOG.md +++ b/core/CHANGELOG.md @@ -42,7 +42,8 @@ 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 - +* [#19571](https://github.com/cosmos/cosmos-sdk/pull/19571) Add `router.Service` and add it in `appmodule.Environment` + ### API Breaking Changes * [#18857](https://github.com/cosmos/cosmos-sdk/pull/18857) Moved `FormatCoins` to `x/tx`. diff --git a/runtime/router.go b/runtime/router.go index baebea58ecca..f690e213d54e 100644 --- a/runtime/router.go +++ b/runtime/router.go @@ -3,10 +3,10 @@ package runtime import ( "context" "fmt" + "reflect" "github.com/cosmos/gogoproto/proto" protov2 "google.golang.org/protobuf/proto" - "google.golang.org/protobuf/reflect/protoreflect" "google.golang.org/protobuf/reflect/protoregistry" "google.golang.org/protobuf/runtime/protoiface" @@ -16,7 +16,7 @@ import ( "github.com/cosmos/cosmos-sdk/baseapp" ) -func NewMsgRouterService(storeService store.KVStoreService, router *baseapp.MsgServiceRouter) router.Service { +func NewMsgRouterService(storeService store.KVStoreService, router baseapp.MessageRouter) router.Service { return &msgRouterService{ storeService: storeService, router: router, @@ -26,22 +26,24 @@ func NewMsgRouterService(storeService store.KVStoreService, router *baseapp.MsgS type msgRouterService struct { storeService store.KVStoreService - router *baseapp.MsgServiceRouter + router baseapp.MessageRouter resolver protoregistry.MessageTypeResolver } -// InvokeTyped implements router.Service. -func (m *msgRouterService) InvokeTyped(ctx context.Context, msg, msgResp protoiface.MessageV1) error { +// InvokeTyped execute a message and fill-in a response. +// The response must be known and passed as a parameter. +// Use InvokeUntyped if the response type is not known. +func (m *msgRouterService) InvokeTyped(ctx context.Context, msg, resp protoiface.MessageV1) error { messageName := msgTypeURL(msg) handler := m.router.HybridHandlerByMsgName(messageName) if handler == nil { return fmt.Errorf("unknown message: %s", messageName) } - return handler(ctx, msg, msgResp) + return handler(ctx, msg, resp) } -// InvokeUntyped implements router.Service. +// InvokeUntyped execute a message and returns a response. func (m *msgRouterService) InvokeUntyped(ctx context.Context, msg protoiface.MessageV1) (protoiface.MessageV1, error) { messageName := msgTypeURL(msg) respName := m.router.ResponseNameByRequestName(messageName) @@ -50,18 +52,18 @@ func (m *msgRouterService) InvokeUntyped(ctx context.Context, msg protoiface.Mes } // get response type - resp, err := m.resolver.FindMessageByName(protoreflect.FullName(respName)) - if err != nil { - return nil, err + typ := proto.MessageType(respName) + if typ == nil { + return nil, fmt.Errorf("no message type found for %s", respName) } + msgResp := reflect.New(typ.Elem()).Interface().(protoiface.MessageV1) handler := m.router.HybridHandlerByMsgName(messageName) if handler == nil { return nil, fmt.Errorf("unknown message: %s", messageName) } - msgResp := resp.New().Interface().(protoiface.MessageV1) - err = handler(ctx, msg, msgResp) + err := handler(ctx, msg, msgResp) return msgResp, err } From c7b8a8a12190c878ca2ef79f32ca7aef68c49632 Mon Sep 17 00:00:00 2001 From: Julien Robert Date: Tue, 27 Feb 2024 12:49:11 +0100 Subject: [PATCH 06/18] nit --- runtime/router.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/router.go b/runtime/router.go index f690e213d54e..03e73f5fdf1d 100644 --- a/runtime/router.go +++ b/runtime/router.go @@ -67,7 +67,7 @@ func (m *msgRouterService) InvokeUntyped(ctx context.Context, msg protoiface.Mes return msgResp, err } -// msgTypeURL returns the TypeURL of a `sdk.Msg`. +// msgTypeURL returns the TypeURL of a proto message. func msgTypeURL(msg proto.Message) string { if m, ok := msg.(protov2.Message); ok { return "/" + string(m.ProtoReflect().Descriptor().FullName()) From 37c08d2fad678970f8dfb16606a7723e93e80715 Mon Sep 17 00:00:00 2001 From: Julien Robert Date: Tue, 27 Feb 2024 15:28:48 +0100 Subject: [PATCH 07/18] feedback --- baseapp/msg_service_router.go | 6 +++--- runtime/router.go | 5 ++++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/baseapp/msg_service_router.go b/baseapp/msg_service_router.go index 22f76cbafa3c..ddbd3f8fe153 100644 --- a/baseapp/msg_service_router.go +++ b/baseapp/msg_service_router.go @@ -25,7 +25,7 @@ type MessageRouter interface { Handler(msg sdk.Msg) MsgServiceHandler HandlerByTypeURL(typeURL string) MsgServiceHandler - ResponseNameByRequestName(msgName string) string + ResponseNameByRequestName(requestName string) string HybridHandlerByMsgName(msgName string) func(ctx context.Context, req, resp protoiface.MessageV1) error } @@ -93,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) ResponseNameByRequestName(requestName string) string { + return msr.responseByRequest[requestName] } func (msr *MsgServiceRouter) registerHybridHandler(sd *grpc.ServiceDesc, method grpc.MethodDesc, handler interface{}) error { diff --git a/runtime/router.go b/runtime/router.go index 03e73f5fdf1d..b2237be3d592 100644 --- a/runtime/router.go +++ b/runtime/router.go @@ -56,7 +56,10 @@ func (m *msgRouterService) InvokeUntyped(ctx context.Context, msg protoiface.Mes if typ == nil { return nil, fmt.Errorf("no message type found for %s", respName) } - msgResp := reflect.New(typ.Elem()).Interface().(protoiface.MessageV1) + msgResp, ok := reflect.New(typ.Elem()).Interface().(protoiface.MessageV1) + if !ok { + return nil, fmt.Errorf("could not create response message %s", respName) + } handler := m.router.HybridHandlerByMsgName(messageName) if handler == nil { From ad4ec723229401e8477f522ab1bc36eb7f9e993a Mon Sep 17 00:00:00 2001 From: Julien Robert Date: Thu, 29 Feb 2024 10:04:05 +0100 Subject: [PATCH 08/18] go docs --- core/router/service.go | 1 + runtime/router.go | 4 +--- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/core/router/service.go b/core/router/service.go index 669d43db2c1d..43cc7e8fb9b8 100644 --- a/core/router/service.go +++ b/core/router/service.go @@ -7,6 +7,7 @@ import ( ) // Service is the interface that wraps the basic methods for a router service. +// This service allows to invoke messages and queries via a message router. type Service interface { InvokeTyped(ctx context.Context, req, res protoiface.MessageV1) error InvokeUntyped(ctx context.Context, req protoiface.MessageV1) (res protoiface.MessageV1, err error) diff --git a/runtime/router.go b/runtime/router.go index b2237be3d592..9b629d18a5e3 100644 --- a/runtime/router.go +++ b/runtime/router.go @@ -7,7 +7,6 @@ import ( "github.com/cosmos/gogoproto/proto" protov2 "google.golang.org/protobuf/proto" - "google.golang.org/protobuf/reflect/protoregistry" "google.golang.org/protobuf/runtime/protoiface" "cosmossdk.io/core/router" @@ -16,18 +15,17 @@ import ( "github.com/cosmos/cosmos-sdk/baseapp" ) +// NewMsgRouterService creates a router.Service which allows to invoke messages and queries using the msg router. func NewMsgRouterService(storeService store.KVStoreService, router baseapp.MessageRouter) router.Service { return &msgRouterService{ storeService: storeService, router: router, - resolver: protoregistry.GlobalTypes, } } type msgRouterService struct { storeService store.KVStoreService router baseapp.MessageRouter - resolver protoregistry.MessageTypeResolver } // InvokeTyped execute a message and fill-in a response. From 3a986faddfa920e670c2ee085f40790f746a3999 Mon Sep 17 00:00:00 2001 From: Julien Robert Date: Thu, 29 Feb 2024 10:39:17 +0100 Subject: [PATCH 09/18] add test --- client/grpc_query_test.go | 2 +- runtime/router_test.go | 42 ++++++++++++++++++++++++++++++++++++++ x/counter/depinject.go | 9 +++----- x/counter/keeper/keeper.go | 12 +++++------ 4 files changed, 52 insertions(+), 13 deletions(-) create mode 100644 runtime/router_test.go diff --git a/client/grpc_query_test.go b/client/grpc_query_test.go index 7aaf78a85445..a493fc548ff1 100644 --- a/client/grpc_query_test.go +++ b/client/grpc_query_test.go @@ -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) } diff --git a/runtime/router_test.go b/runtime/router_test.go new file mode 100644 index 000000000000..db6e88527f53 --- /dev/null +++ b/runtime/router_test.go @@ -0,0 +1,42 @@ +package runtime_test + +import ( + "context" + "testing" + + "cosmossdk.io/log" + storetypes "cosmossdk.io/store/types" + "github.com/cosmos/cosmos-sdk/baseapp" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + "github.com/cosmos/cosmos-sdk/runtime" + counterkeeper "github.com/cosmos/cosmos-sdk/x/counter/keeper" + countertypes "github.com/cosmos/cosmos-sdk/x/counter/types" + "github.com/stretchr/testify/require" +) + +func TestMsgRouterService(t *testing.T) { + router := baseapp.NewMsgServiceRouter() + interfaceRegistry := codectypes.NewInterfaceRegistry() + router.SetInterfaceRegistry(interfaceRegistry) + key := storetypes.NewKVStoreKey(countertypes.StoreKey) + storeService := runtime.NewKVStoreService(key) + counterKeeper := counterkeeper.NewKeeper(runtime.NewEnvironment(storeService, log.NewNopLogger())) + countertypes.RegisterInterfaces(interfaceRegistry) + countertypes.RegisterMsgServer(router, counterKeeper) + countertypes.RegisterQueryServer(router, counterKeeper) + + routerService := runtime.NewMsgRouterService(storeService, router) + + resp, err := routerService.InvokeUntyped(context.Background(), &countertypes.MsgIncreaseCounter{ + Signer: "cosmos1", + Count: 42, + }) + require.NoError(t, err) + require.NotNil(t, resp) + + resp = &countertypes.QueryGetCountResponse{} + err = routerService.InvokeTyped(context.Background(), &countertypes.QueryGetCountRequest{}, &countertypes.QueryGetCountResponse{}) + require.NoError(t, err) + require.NotNil(t, resp) + require.Equal(t, &countertypes.QueryGetCountResponse{TotalCount: 42}, resp) +} diff --git a/x/counter/depinject.go b/x/counter/depinject.go index a1297170602b..4cbe20acf4a2 100644 --- a/x/counter/depinject.go +++ b/x/counter/depinject.go @@ -3,8 +3,6 @@ package counter import ( modulev1 "cosmossdk.io/api/cosmos/counter/module/v1" "cosmossdk.io/core/appmodule" - "cosmossdk.io/core/event" - storetypes "cosmossdk.io/core/store" "cosmossdk.io/depinject" "cosmossdk.io/depinject/appconfig" @@ -26,9 +24,8 @@ func init() { type ModuleInputs struct { depinject.In - Config *modulev1.Module - StoreService storetypes.KVStoreService - EventManager event.Service + Config *modulev1.Module + Environment appmodule.Environment } type ModuleOutputs struct { @@ -39,7 +36,7 @@ type ModuleOutputs struct { } func ProvideModule(in ModuleInputs) ModuleOutputs { - k := keeper.NewKeeper(in.StoreService, in.EventManager) + k := keeper.NewKeeper(in.Environment) m := NewAppModule(k) return ModuleOutputs{ diff --git a/x/counter/keeper/keeper.go b/x/counter/keeper/keeper.go index 6d7fd422e8c3..f03b978ad1e4 100644 --- a/x/counter/keeper/keeper.go +++ b/x/counter/keeper/keeper.go @@ -9,8 +9,8 @@ import ( "google.golang.org/grpc/status" "cosmossdk.io/collections" + "cosmossdk.io/core/appmodule" "cosmossdk.io/core/event" - storetypes "cosmossdk.io/core/store" "github.com/cosmos/cosmos-sdk/x/counter/types" ) @@ -18,15 +18,15 @@ import ( var StoreKey = "Counter" type Keeper struct { - event event.Service + env appmodule.Environment CountStore collections.Item[int64] } -func NewKeeper(storeService storetypes.KVStoreService, em event.Service) Keeper { - sb := collections.NewSchemaBuilder(storeService) +func NewKeeper(env appmodule.Environment) Keeper { + sb := collections.NewSchemaBuilder(env.KVStoreService) return Keeper{ - event: em, + env: env, CountStore: collections.NewItem(sb, collections.NewPrefix(0), "count", collections.Int64Value), } } @@ -67,7 +67,7 @@ func (k Keeper) IncreaseCount(ctx context.Context, msg *types.MsgIncreaseCounter return nil, err } - if err := k.event.EventManager(ctx).EmitKV( + if err := k.env.EventService.EventManager(ctx).EmitKV( "increase_counter", event.NewAttribute("signer", msg.Signer), event.NewAttribute("new count", fmt.Sprint(num+msg.Count))); err != nil { From e8f8d24e35511b4fc7fd046211483f60f4fed436 Mon Sep 17 00:00:00 2001 From: Julien Robert Date: Thu, 29 Feb 2024 10:44:44 +0100 Subject: [PATCH 10/18] updates --- core/router/service.go | 2 ++ runtime/router.go | 2 +- runtime/router_test.go | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/core/router/service.go b/core/router/service.go index 43cc7e8fb9b8..634e18064649 100644 --- a/core/router/service.go +++ b/core/router/service.go @@ -9,6 +9,8 @@ import ( // Service is the interface that wraps the basic methods for a router service. // This service allows to invoke messages and queries via a message router. type Service interface { + // 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) } diff --git a/runtime/router.go b/runtime/router.go index 9b629d18a5e3..48e6d24294da 100644 --- a/runtime/router.go +++ b/runtime/router.go @@ -46,7 +46,7 @@ func (m *msgRouterService) InvokeUntyped(ctx context.Context, msg protoiface.Mes messageName := msgTypeURL(msg) respName := m.router.ResponseNameByRequestName(messageName) if respName == "" { - return nil, fmt.Errorf("could not find response type for message %T", msg) + return nil, fmt.Errorf("could not find response type for message %s (%T)", messageName, msg) } // get response type diff --git a/runtime/router_test.go b/runtime/router_test.go index db6e88527f53..8c2a36540f45 100644 --- a/runtime/router_test.go +++ b/runtime/router_test.go @@ -23,7 +23,7 @@ func TestMsgRouterService(t *testing.T) { counterKeeper := counterkeeper.NewKeeper(runtime.NewEnvironment(storeService, log.NewNopLogger())) countertypes.RegisterInterfaces(interfaceRegistry) countertypes.RegisterMsgServer(router, counterKeeper) - countertypes.RegisterQueryServer(router, counterKeeper) + // countertypes.RegisterQueryServer(router, counterKeeper) routerService := runtime.NewMsgRouterService(storeService, router) From 64a92c437d84efff74b24a592b5c62ee5d09167b Mon Sep 17 00:00:00 2001 From: Julien Robert Date: Thu, 29 Feb 2024 11:01:30 +0100 Subject: [PATCH 11/18] updates --- runtime/router_test.go | 60 +++++++++++++++++++++++++++++++++--------- 1 file changed, 47 insertions(+), 13 deletions(-) diff --git a/runtime/router_test.go b/runtime/router_test.go index 8c2a36540f45..dd9bec43c4d8 100644 --- a/runtime/router_test.go +++ b/runtime/router_test.go @@ -1,14 +1,16 @@ package runtime_test import ( - "context" "testing" + bankv1beta1 "cosmossdk.io/api/cosmos/bank/v1beta1" + counterv1 "cosmossdk.io/api/cosmos/counter/v1" "cosmossdk.io/log" storetypes "cosmossdk.io/store/types" "github.com/cosmos/cosmos-sdk/baseapp" codectypes "github.com/cosmos/cosmos-sdk/codec/types" "github.com/cosmos/cosmos-sdk/runtime" + "github.com/cosmos/cosmos-sdk/testutil" counterkeeper "github.com/cosmos/cosmos-sdk/x/counter/keeper" countertypes "github.com/cosmos/cosmos-sdk/x/counter/types" "github.com/stretchr/testify/require" @@ -23,20 +25,52 @@ func TestMsgRouterService(t *testing.T) { counterKeeper := counterkeeper.NewKeeper(runtime.NewEnvironment(storeService, log.NewNopLogger())) countertypes.RegisterInterfaces(interfaceRegistry) countertypes.RegisterMsgServer(router, counterKeeper) - // countertypes.RegisterQueryServer(router, counterKeeper) + countertypes.RegisterQueryServer(router, counterKeeper) routerService := runtime.NewMsgRouterService(storeService, router) + testCtx := testutil.DefaultContextWithDB(t, key, storetypes.NewTransientStoreKey("transient_test")) - resp, err := routerService.InvokeUntyped(context.Background(), &countertypes.MsgIncreaseCounter{ - Signer: "cosmos1", - Count: 42, + t.Run("invalid msg", func(t *testing.T) { + _, err := routerService.InvokeUntyped(testCtx.Ctx, &bankv1beta1.MsgSend{}) + require.ErrorContains(t, err, "could not find response type for message /cosmos.bank.v1beta1.MsgSend") + }) + + t.Run("valid msg (proto v1)", func(t *testing.T) { + resp, err := routerService.InvokeUntyped(testCtx.Ctx, &countertypes.MsgIncreaseCounter{ + Signer: "cosmos1", + Count: 42, + }) + require.NoError(t, err) + require.NotNil(t, resp) + }) + + t.Run("valid msg (proto v2)", func(t *testing.T) { + resp, err := routerService.InvokeUntyped(testCtx.Ctx, &counterv1.MsgIncreaseCounter{ + Signer: "cosmos1", + Count: 42, + }) + require.NoError(t, err) + require.NotNil(t, resp) + }) + + t.Run("invalid query", func(t *testing.T) { + err := routerService.InvokeTyped(testCtx.Ctx, &bankv1beta1.QueryBalanceRequest{}, &bankv1beta1.QueryBalanceResponse{}) + require.ErrorContains(t, err, "unknown message: /cosmos.bank.v1beta1.QueryBalanceRequest") + }) + + t.Run("valid query (proto v1)", func(t *testing.T) { + resp := &countertypes.QueryGetCountResponse{} + err := routerService.InvokeTyped(testCtx.Ctx, &countertypes.QueryGetCountRequest{}, resp) + require.NoError(t, err) + require.NotNil(t, resp) + require.Equal(t, &countertypes.QueryGetCountResponse{TotalCount: 42}, resp) + }) + + t.Run("valid query (proto v2)", func(t *testing.T) { + resp := &counterv1.QueryGetCountResponse{} + err := routerService.InvokeTyped(testCtx.Ctx, &counterv1.QueryGetCountRequest{}, resp) + require.NoError(t, err) + require.NotNil(t, resp) + require.Equal(t, &counterv1.QueryGetCountResponse{TotalCount: 42}, resp) }) - require.NoError(t, err) - require.NotNil(t, resp) - - resp = &countertypes.QueryGetCountResponse{} - err = routerService.InvokeTyped(context.Background(), &countertypes.QueryGetCountRequest{}, &countertypes.QueryGetCountResponse{}) - require.NoError(t, err) - require.NotNil(t, resp) - require.Equal(t, &countertypes.QueryGetCountResponse{TotalCount: 42}, resp) } From 092f902d64f49b9a5629cbbc0cafcec69138019a Mon Sep 17 00:00:00 2001 From: Julien Robert Date: Thu, 29 Feb 2024 11:42:28 +0100 Subject: [PATCH 12/18] lint --- runtime/router_test.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/runtime/router_test.go b/runtime/router_test.go index dd9bec43c4d8..ad276cb2a15d 100644 --- a/runtime/router_test.go +++ b/runtime/router_test.go @@ -3,17 +3,19 @@ package runtime_test import ( "testing" + "github.com/stretchr/testify/require" + bankv1beta1 "cosmossdk.io/api/cosmos/bank/v1beta1" counterv1 "cosmossdk.io/api/cosmos/counter/v1" "cosmossdk.io/log" storetypes "cosmossdk.io/store/types" + "github.com/cosmos/cosmos-sdk/baseapp" codectypes "github.com/cosmos/cosmos-sdk/codec/types" "github.com/cosmos/cosmos-sdk/runtime" "github.com/cosmos/cosmos-sdk/testutil" counterkeeper "github.com/cosmos/cosmos-sdk/x/counter/keeper" countertypes "github.com/cosmos/cosmos-sdk/x/counter/types" - "github.com/stretchr/testify/require" ) func TestMsgRouterService(t *testing.T) { From dee1a224e71b51a808b704223de40bfcdd3e8aab Mon Sep 17 00:00:00 2001 From: Julien Robert Date: Thu, 29 Feb 2024 11:52:33 +0100 Subject: [PATCH 13/18] fix test only --- runtime/router_test.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/runtime/router_test.go b/runtime/router_test.go index ad276cb2a15d..dd7029dc822f 100644 --- a/runtime/router_test.go +++ b/runtime/router_test.go @@ -24,13 +24,14 @@ func TestMsgRouterService(t *testing.T) { router.SetInterfaceRegistry(interfaceRegistry) key := storetypes.NewKVStoreKey(countertypes.StoreKey) storeService := runtime.NewKVStoreService(key) + testCtx := testutil.DefaultContextWithDB(t, key, storetypes.NewTransientStoreKey("transient_test")) + queryRouter := baseapp.NewQueryServerTestHelper(testCtx.Ctx, interfaceRegistry) counterKeeper := counterkeeper.NewKeeper(runtime.NewEnvironment(storeService, log.NewNopLogger())) countertypes.RegisterInterfaces(interfaceRegistry) countertypes.RegisterMsgServer(router, counterKeeper) - countertypes.RegisterQueryServer(router, counterKeeper) + countertypes.RegisterQueryServer(queryRouter, counterKeeper) routerService := runtime.NewMsgRouterService(storeService, router) - testCtx := testutil.DefaultContextWithDB(t, key, storetypes.NewTransientStoreKey("transient_test")) t.Run("invalid msg", func(t *testing.T) { _, err := routerService.InvokeUntyped(testCtx.Ctx, &bankv1beta1.MsgSend{}) From d84802d19e0be1044c30308b77a163228384959a Mon Sep 17 00:00:00 2001 From: Julien Robert Date: Fri, 1 Mar 2024 12:35:22 +0100 Subject: [PATCH 14/18] updates --- baseapp/baseapp.go | 6 +-- baseapp/grpcrouter.go | 17 ++++++- baseapp/grpcserver.go | 3 -- baseapp/msg_service_router.go | 12 ++--- baseapp/options.go | 10 +++++ core/appmodule/environment.go | 10 ++--- core/router/service.go | 8 +++- runtime/app.go | 1 + runtime/builder.go | 1 + runtime/environment.go | 4 +- runtime/module.go | 16 +++++-- runtime/router.go | 83 ++++++++++++++++++++++++++++++----- runtime/router_test.go | 75 +++++++++++++++++++++++-------- x/accounts/keeper.go | 4 +- 14 files changed, 193 insertions(+), 57 deletions(-) diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index 7f7ef4ac5279..672e5a6dea30 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -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. diff --git a/baseapp/grpcrouter.go b/baseapp/grpcrouter.go index a1acc0b9cf2b..b12c3bd6ac3a 100644 --- a/baseapp/grpcrouter.go +++ b/baseapp/grpcrouter.go @@ -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. @@ -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{}, } } @@ -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 } diff --git a/baseapp/grpcserver.go b/baseapp/grpcserver.go index 2d11818a462c..b1befabe9e86 100644 --- a/baseapp/grpcserver.go +++ b/baseapp/grpcserver.go @@ -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 diff --git a/baseapp/msg_service_router.go b/baseapp/msg_service_router.go index ddbd3f8fe153..21ba4371e426 100644 --- a/baseapp/msg_service_router.go +++ b/baseapp/msg_service_router.go @@ -25,7 +25,7 @@ type MessageRouter interface { Handler(msg sdk.Msg) MsgServiceHandler HandlerByTypeURL(typeURL string) MsgServiceHandler - ResponseNameByRequestName(requestName string) string + ResponseNameByMsgName(msgName string) string HybridHandlerByMsgName(msgName string) func(ctx context.Context, req, resp protoiface.MessageV1) error } @@ -34,7 +34,7 @@ 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 } @@ -45,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, } } @@ -93,8 +93,8 @@ func (msr *MsgServiceRouter) HybridHandlerByMsgName(msgName string) func(ctx con return msr.hybridHandlers[msgName] } -func (msr *MsgServiceRouter) ResponseNameByRequestName(requestName string) string { - return msr.responseByRequest[requestName] +func (msr *MsgServiceRouter) ResponseNameByMsgName(msgName string) string { + return msr.responseByMsgName[msgName] } func (msr *MsgServiceRouter) registerHybridHandler(sd *grpc.ServiceDesc, method grpc.MethodDesc, handler interface{}) error { @@ -112,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 diff --git a/baseapp/options.go b/baseapp/options.go index a99762da5e51..5acc6811dbe3 100644 --- a/baseapp/options.go +++ b/baseapp/options.go @@ -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 +} diff --git a/core/appmodule/environment.go b/core/appmodule/environment.go index 5e3ec6a74965..40ee12b2f398 100644 --- a/core/appmodule/environment.go +++ b/core/appmodule/environment.go @@ -14,11 +14,11 @@ import ( type Environment struct { Logger log.Logger - BranchService branch.Service - EventService event.Service - GasService gas.Service - HeaderService header.Service - MessageRouterService router.Service + BranchService branch.Service + EventService event.Service + GasService gas.Service + HeaderService header.Service + RouterService router.Router KVStoreService store.KVStoreService MemStoreService store.MemoryStoreService diff --git a/core/router/service.go b/core/router/service.go index 634e18064649..5a544d6a029f 100644 --- a/core/router/service.go +++ b/core/router/service.go @@ -6,8 +6,14 @@ import ( "google.golang.org/protobuf/runtime/protoiface" ) +// Router embeds a QueryRouterService and MessageRouterService. +// Each service allows to invoke messages and queries via the corresponding router. +type Router interface { + QueryRouterService() Service + MessageRouterService() Service +} + // Service is the interface that wraps the basic methods for a router service. -// This service allows to invoke messages and queries via a message router. type Service interface { // 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 diff --git a/runtime/app.go b/runtime/app.go index 268f3c90f8d6..72f80ad2c1d8 100644 --- a/runtime/app.go +++ b/runtime/app.go @@ -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. diff --git a/runtime/builder.go b/runtime/builder.go index 805b814a4956..c96af6ecd6ee 100644 --- a/runtime/builder.go +++ b/runtime/builder.go @@ -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) diff --git a/runtime/environment.go b/runtime/environment.go index da4f1e9ba13f..38500ecb6b35 100644 --- a/runtime/environment.go +++ b/runtime/environment.go @@ -34,9 +34,9 @@ func NewEnvironment( type EnvOption func(*appmodule.Environment) -func EnvWithMessageRouterService(msgServiceRouter *baseapp.MsgServiceRouter) EnvOption { +func EnvWithRouterService(queryServiceRouter *baseapp.GRPCQueryRouter, msgServiceRouter *baseapp.MsgServiceRouter) EnvOption { return func(env *appmodule.Environment) { - env.MessageRouterService = NewMsgRouterService(env.KVStoreService, msgServiceRouter) + env.RouterService = NewRouterService(env.KVStoreService, queryServiceRouter, msgServiceRouter) } } diff --git a/runtime/module.go b/runtime/module.go index 328dea4bb49e..2c0dd6e58671 100644 --- a/runtime/module.go +++ b/runtime/module.go @@ -81,6 +81,7 @@ func ProvideApp(interfaceRegistry codectypes.InterfaceRegistry) ( *codec.LegacyAmino, *AppBuilder, *baseapp.MsgServiceRouter, + *baseapp.GRPCQueryRouter, appmodule.AppModule, protodesc.Resolver, protoregistry.MessageTypeResolver, @@ -103,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 { @@ -211,7 +214,14 @@ func ProvideGenesisTxHandler(appBuilder *AppBuilder) genesis.TxHandler { return appBuilder.app } -func ProvideEnvironment(logger log.Logger, config *runtimev1alpha1.Module, key depinject.ModuleKey, app *AppBuilder, msgServiceRouter *baseapp.MsgServiceRouter) (store.KVStoreService, store.MemoryStoreService, 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} @@ -221,7 +231,7 @@ func ProvideEnvironment(logger log.Logger, config *runtimev1alpha1.Module, key d return kvService, memStoreService, NewEnvironment( kvService, logger, - EnvWithMessageRouterService(msgServiceRouter), + EnvWithRouterService(queryServiceRouter, msgServiceRouter), EnvWithMemStoreService(memStoreService), ) } diff --git a/runtime/router.go b/runtime/router.go index 48e6d24294da..fe5925b4f7d3 100644 --- a/runtime/router.go +++ b/runtime/router.go @@ -15,14 +15,39 @@ import ( "github.com/cosmos/cosmos-sdk/baseapp" ) -// NewMsgRouterService creates a router.Service which allows to invoke messages and queries using the msg router. -func NewMsgRouterService(storeService store.KVStoreService, router baseapp.MessageRouter) router.Service { - return &msgRouterService{ - storeService: storeService, - router: router, +// NewRouterService creates a router.Service which allows to invoke messages and queries using the msg router. +func NewRouterService(storeService store.KVStoreService, queryRouter *baseapp.GRPCQueryRouter, msgRouter baseapp.MessageRouter) router.Router { + return &routerService{ + queryRouterService: &queryRouterService{ + storeService: storeService, // TODO: this will be used later on as authenticating modules before routing + router: queryRouter, + }, + msgRouterService: &msgRouterService{ + storeService: storeService, // TODO: this will be used later on as authenticating modules before routing + router: msgRouter, + }, } } +var _ router.Router = (*routerService)(nil) + +type routerService struct { + queryRouterService router.Service + msgRouterService router.Service +} + +// MessageRouterService implements router.Router. +func (r *routerService) MessageRouterService() router.Service { + return r.msgRouterService +} + +// QueryRouterService implements router.Router. +func (r *routerService) QueryRouterService() router.Service { + return r.queryRouterService +} + +var _ router.Service = (*msgRouterService)(nil) + type msgRouterService struct { storeService store.KVStoreService router baseapp.MessageRouter @@ -33,6 +58,7 @@ type msgRouterService struct { // Use InvokeUntyped if the response type is not known. func (m *msgRouterService) InvokeTyped(ctx context.Context, msg, resp protoiface.MessageV1) error { messageName := msgTypeURL(msg) + handler := m.router.HybridHandlerByMsgName(messageName) if handler == nil { return fmt.Errorf("unknown message: %s", messageName) @@ -44,7 +70,7 @@ func (m *msgRouterService) InvokeTyped(ctx context.Context, msg, resp protoiface // InvokeUntyped execute a message and returns a response. func (m *msgRouterService) InvokeUntyped(ctx context.Context, msg protoiface.MessageV1) (protoiface.MessageV1, error) { messageName := msgTypeURL(msg) - respName := m.router.ResponseNameByRequestName(messageName) + respName := m.router.ResponseNameByMsgName(messageName) if respName == "" { return nil, fmt.Errorf("could not find response type for message %s (%T)", messageName, msg) } @@ -59,13 +85,48 @@ func (m *msgRouterService) InvokeUntyped(ctx context.Context, msg protoiface.Mes return nil, fmt.Errorf("could not create response message %s", respName) } - handler := m.router.HybridHandlerByMsgName(messageName) - if handler == nil { - return nil, fmt.Errorf("unknown message: %s", messageName) + return msgResp, m.InvokeTyped(ctx, msg, msgResp) +} + +var _ router.Service = (*queryRouterService)(nil) + +type queryRouterService struct { + storeService store.KVStoreService + router *baseapp.GRPCQueryRouter +} + +// InvokeTyped execute a message and fill-in a response. +// The response must be known and passed as a parameter. +// Use InvokeUntyped if the response type is not known. +func (m *queryRouterService) InvokeTyped(ctx context.Context, req, resp protoiface.MessageV1) error { + messageName := msgTypeURL(req) + handlers := m.router.HybridHandlerByRequestName(messageName) + if len(handlers) == 0 { + return fmt.Errorf("unknown request: %s", messageName) + } + + return handlers[0](ctx, req, resp) +} + +// InvokeUntyped execute a message and returns a response. +func (m *queryRouterService) InvokeUntyped(ctx context.Context, req protoiface.MessageV1) (protoiface.MessageV1, error) { + messageName := msgTypeURL(req) + respName := m.router.ResponseNameByRequestName(messageName) + if respName == "" { + return nil, fmt.Errorf("could not find response type for request %s (%T)", messageName, req) + } + + // get response type + typ := proto.MessageType(respName) + if typ == nil { + return nil, fmt.Errorf("no message type found for %s", respName) + } + reqResp, ok := reflect.New(typ.Elem()).Interface().(protoiface.MessageV1) + if !ok { + return nil, fmt.Errorf("could not create response request %s", respName) } - err := handler(ctx, msg, msgResp) - return msgResp, err + return reqResp, m.InvokeTyped(ctx, req, reqResp) } // msgTypeURL returns the TypeURL of a proto message. diff --git a/runtime/router_test.go b/runtime/router_test.go index dd7029dc822f..1cc8073ebef7 100644 --- a/runtime/router_test.go +++ b/runtime/router_test.go @@ -18,28 +18,31 @@ import ( countertypes "github.com/cosmos/cosmos-sdk/x/counter/types" ) -func TestMsgRouterService(t *testing.T) { - router := baseapp.NewMsgServiceRouter() +func TestRouterService(t *testing.T) { interfaceRegistry := codectypes.NewInterfaceRegistry() - router.SetInterfaceRegistry(interfaceRegistry) + msgRouter := baseapp.NewMsgServiceRouter() + msgRouter.SetInterfaceRegistry(interfaceRegistry) + queryRouter := baseapp.NewGRPCQueryRouter() + queryRouter.SetInterfaceRegistry(interfaceRegistry) key := storetypes.NewKVStoreKey(countertypes.StoreKey) storeService := runtime.NewKVStoreService(key) - testCtx := testutil.DefaultContextWithDB(t, key, storetypes.NewTransientStoreKey("transient_test")) - queryRouter := baseapp.NewQueryServerTestHelper(testCtx.Ctx, interfaceRegistry) counterKeeper := counterkeeper.NewKeeper(runtime.NewEnvironment(storeService, log.NewNopLogger())) countertypes.RegisterInterfaces(interfaceRegistry) - countertypes.RegisterMsgServer(router, counterKeeper) + countertypes.RegisterMsgServer(msgRouter, counterKeeper) countertypes.RegisterQueryServer(queryRouter, counterKeeper) - routerService := runtime.NewMsgRouterService(storeService, router) + routerService := runtime.NewRouterService(storeService, queryRouter, msgRouter) + testCtx := testutil.DefaultContextWithDB(t, key, storetypes.NewTransientStoreKey("transient_test")) + + // Messages t.Run("invalid msg", func(t *testing.T) { - _, err := routerService.InvokeUntyped(testCtx.Ctx, &bankv1beta1.MsgSend{}) + _, err := routerService.MessageRouterService().InvokeUntyped(testCtx.Ctx, &bankv1beta1.MsgSend{}) require.ErrorContains(t, err, "could not find response type for message /cosmos.bank.v1beta1.MsgSend") }) - t.Run("valid msg (proto v1)", func(t *testing.T) { - resp, err := routerService.InvokeUntyped(testCtx.Ctx, &countertypes.MsgIncreaseCounter{ + t.Run("invoke untyped: valid msg (proto v1)", func(t *testing.T) { + resp, err := routerService.MessageRouterService().InvokeUntyped(testCtx.Ctx, &countertypes.MsgIncreaseCounter{ Signer: "cosmos1", Count: 42, }) @@ -47,8 +50,8 @@ func TestMsgRouterService(t *testing.T) { require.NotNil(t, resp) }) - t.Run("valid msg (proto v2)", func(t *testing.T) { - resp, err := routerService.InvokeUntyped(testCtx.Ctx, &counterv1.MsgIncreaseCounter{ + t.Run("invoke untyped: valid msg (proto v2)", func(t *testing.T) { + resp, err := routerService.MessageRouterService().InvokeUntyped(testCtx.Ctx, &counterv1.MsgIncreaseCounter{ Signer: "cosmos1", Count: 42, }) @@ -56,22 +59,58 @@ func TestMsgRouterService(t *testing.T) { require.NotNil(t, resp) }) + t.Run("invoke typed: valid msg (proto v1)", func(t *testing.T) { + resp := &countertypes.MsgIncreaseCountResponse{} + err := routerService.MessageRouterService().InvokeTyped(testCtx.Ctx, &countertypes.MsgIncreaseCounter{ + Signer: "cosmos1", + Count: 42, + }, resp) + require.NoError(t, err) + require.NotNil(t, resp) + }) + + t.Run("invoke typed: valid msg (proto v2)", func(t *testing.T) { + resp := &counterv1.MsgIncreaseCountResponse{} + err := routerService.MessageRouterService().InvokeTyped(testCtx.Ctx, &counterv1.MsgIncreaseCounter{ + Signer: "cosmos1", + Count: 42, + }, resp) + require.NoError(t, err) + require.NotNil(t, resp) + }) + + // Queries + t.Run("invalid query", func(t *testing.T) { - err := routerService.InvokeTyped(testCtx.Ctx, &bankv1beta1.QueryBalanceRequest{}, &bankv1beta1.QueryBalanceResponse{}) - require.ErrorContains(t, err, "unknown message: /cosmos.bank.v1beta1.QueryBalanceRequest") + err := routerService.QueryRouterService().InvokeTyped(testCtx.Ctx, &bankv1beta1.QueryBalanceRequest{}, &bankv1beta1.QueryBalanceResponse{}) + require.ErrorContains(t, err, "unknown request: /cosmos.bank.v1beta1.QueryBalanceRequest") }) - t.Run("valid query (proto v1)", func(t *testing.T) { + t.Run("invoke typed: valid query (proto v1)", func(t *testing.T) { resp := &countertypes.QueryGetCountResponse{} - err := routerService.InvokeTyped(testCtx.Ctx, &countertypes.QueryGetCountRequest{}, resp) + err := routerService.QueryRouterService().InvokeTyped(testCtx.Ctx, &countertypes.QueryGetCountRequest{}, resp) require.NoError(t, err) require.NotNil(t, resp) require.Equal(t, &countertypes.QueryGetCountResponse{TotalCount: 42}, resp) }) - t.Run("valid query (proto v2)", func(t *testing.T) { + t.Run("invoke typed: valid query (proto v2)", func(t *testing.T) { resp := &counterv1.QueryGetCountResponse{} - err := routerService.InvokeTyped(testCtx.Ctx, &counterv1.QueryGetCountRequest{}, resp) + err := routerService.QueryRouterService().InvokeTyped(testCtx.Ctx, &counterv1.QueryGetCountRequest{}, resp) + require.NoError(t, err) + require.NotNil(t, resp) + require.Equal(t, &counterv1.QueryGetCountResponse{TotalCount: 42}, resp) + }) + + t.Run("invoke untyped: valid query (proto v1)", func(t *testing.T) { + resp, err := routerService.QueryRouterService().InvokeUntyped(testCtx.Ctx, &countertypes.QueryGetCountRequest{}) + require.NoError(t, err) + require.NotNil(t, resp) + require.Equal(t, &countertypes.QueryGetCountResponse{TotalCount: 42}, resp) + }) + + t.Run("invoke untyped: valid query (proto v2)", func(t *testing.T) { + resp, err := routerService.QueryRouterService().InvokeUntyped(testCtx.Ctx, &counterv1.QueryGetCountRequest{}) require.NoError(t, err) require.NotNil(t, resp) require.Equal(t, &counterv1.QueryGetCountResponse{TotalCount: 42}, resp) diff --git a/x/accounts/keeper.go b/x/accounts/keeper.go index 58afcd2b47df..d366c828c27d 100644 --- a/x/accounts/keeper.go +++ b/x/accounts/keeper.go @@ -104,9 +104,9 @@ type Keeper struct { // deps coming from the runtime environment appmodule.Environment addressCodec address.Codec - msgRouter MsgRouter + msgRouter MsgRouter // todo use env signerProvider SignerProvider - queryRouter QueryRouter + queryRouter QueryRouter // todo use env makeSendCoinsMsg coinsTransferMsgFunc logger log.Logger From 59f1727f33b9ad2ec68c1d3fb245e6c638396cba Mon Sep 17 00:00:00 2001 From: Julien Robert Date: Fri, 1 Mar 2024 12:47:14 +0100 Subject: [PATCH 15/18] updates --- core/go.mod | 3 --- core/go.sum | 5 ----- testutil/integration/router.go | 8 ++++---- 3 files changed, 4 insertions(+), 12 deletions(-) diff --git a/core/go.mod b/core/go.mod index e732129de93a..e7bf2f319d5e 100644 --- a/core/go.mod +++ b/core/go.mod @@ -4,7 +4,6 @@ go 1.20 require ( cosmossdk.io/log v1.3.1 - github.com/cosmos/gogoproto v1.4.11 github.com/stretchr/testify v1.8.4 google.golang.org/grpc v1.62.0 google.golang.org/protobuf v1.32.0 @@ -13,7 +12,6 @@ require ( require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/golang/protobuf v1.5.3 // indirect - github.com/google/go-cmp v0.6.0 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect @@ -21,7 +19,6 @@ require ( github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rogpeppe/go-internal v1.11.0 // indirect github.com/rs/zerolog v1.32.0 // indirect - golang.org/x/exp v0.0.0-20230811145659-89c5cff77bcb // indirect golang.org/x/net v0.21.0 // indirect golang.org/x/sys v0.17.0 // indirect golang.org/x/text v0.14.0 // indirect diff --git a/core/go.sum b/core/go.sum index 57133f587985..105975b5b061 100644 --- a/core/go.sum +++ b/core/go.sum @@ -1,8 +1,6 @@ cosmossdk.io/log v1.3.1 h1:UZx8nWIkfbbNEWusZqzAx3ZGvu54TZacWib3EzUYmGI= cosmossdk.io/log v1.3.1/go.mod h1:2/dIomt8mKdk6vl3OWJcPk2be3pGOS8OQaLUM/3/tCM= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/cosmos/gogoproto v1.4.11 h1:LZcMHrx4FjUgrqQSWeaGC1v/TeuVFqSLa43CC6aWR2g= -github.com/cosmos/gogoproto v1.4.11/go.mod h1:/g39Mh8m17X8Q/GDEs5zYTSNaNnInBSohtaxzQnYq1Y= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -12,7 +10,6 @@ github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= @@ -39,8 +36,6 @@ github.com/rs/zerolog v1.32.0 h1:keLypqrlIjaFsbmJOBdB/qvyF8KEtCWHwobLp5l/mQ0= github.com/rs/zerolog v1.32.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -golang.org/x/exp v0.0.0-20230811145659-89c5cff77bcb h1:mIKbk8weKhSeLH2GmUTrvx8CjkyJmnU1wFmg59CUjFA= -golang.org/x/exp v0.0.0-20230811145659-89c5cff77bcb/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/testutil/integration/router.go b/testutil/integration/router.go index 6f4e2a647638..dafaa7f9d011 100644 --- a/testutil/integration/router.go +++ b/testutil/integration/router.go @@ -75,13 +75,13 @@ func NewIntegrationApp( return moduleManager.EndBlock(sdkCtx) }) - router := baseapp.NewMsgServiceRouter() - router.SetInterfaceRegistry(interfaceRegistry) - bApp.SetMsgServiceRouter(router) + msgRouter := baseapp.NewMsgServiceRouter() + msgRouter.SetInterfaceRegistry(interfaceRegistry) + bApp.SetMsgServiceRouter(msgRouter) if keys[consensusparamtypes.StoreKey] != nil { // set baseApp param store - consensusParamsKeeper := consensusparamkeeper.NewKeeper(appCodec, runtime.NewEnvironment(runtime.NewKVStoreService(keys[consensusparamtypes.StoreKey]), log.NewNopLogger(), runtime.EnvWithMessageRouterService(router)), authtypes.NewModuleAddress("gov").String()) + consensusParamsKeeper := consensusparamkeeper.NewKeeper(appCodec, runtime.NewEnvironment(runtime.NewKVStoreService(keys[consensusparamtypes.StoreKey]), log.NewNopLogger(), runtime.EnvWithRouterService(baseapp.NewGRPCQueryRouter(), msgRouter)), authtypes.NewModuleAddress("gov").String()) bApp.SetParamStore(consensusParamsKeeper.ParamsStore) if err := bApp.LoadLatestVersion(); err != nil { From c58a9300863fbe877fe5e8146f8eaccc2c17da1b Mon Sep 17 00:00:00 2001 From: Julien Robert Date: Sat, 2 Mar 2024 01:28:20 +0100 Subject: [PATCH 16/18] fixes --- runtime/router.go | 4 ++-- runtime/router_test.go | 34 +++++++++++++--------------------- 2 files changed, 15 insertions(+), 23 deletions(-) diff --git a/runtime/router.go b/runtime/router.go index fe5925b4f7d3..df2a95b4e778 100644 --- a/runtime/router.go +++ b/runtime/router.go @@ -132,8 +132,8 @@ func (m *queryRouterService) InvokeUntyped(ctx context.Context, req protoiface.M // msgTypeURL returns the TypeURL of a proto message. func msgTypeURL(msg proto.Message) string { if m, ok := msg.(protov2.Message); ok { - return "/" + string(m.ProtoReflect().Descriptor().FullName()) + return string(m.ProtoReflect().Descriptor().FullName()) } - return "/" + proto.MessageName(msg) + return proto.MessageName(msg) } diff --git a/runtime/router_test.go b/runtime/router_test.go index 1cc8073ebef7..1c1d3db19edf 100644 --- a/runtime/router_test.go +++ b/runtime/router_test.go @@ -38,7 +38,7 @@ func TestRouterService(t *testing.T) { t.Run("invalid msg", func(t *testing.T) { _, err := routerService.MessageRouterService().InvokeUntyped(testCtx.Ctx, &bankv1beta1.MsgSend{}) - require.ErrorContains(t, err, "could not find response type for message /cosmos.bank.v1beta1.MsgSend") + require.ErrorContains(t, err, "could not find response type for message cosmos.bank.v1beta1.MsgSend") }) t.Run("invoke untyped: valid msg (proto v1)", func(t *testing.T) { @@ -50,15 +50,6 @@ func TestRouterService(t *testing.T) { require.NotNil(t, resp) }) - t.Run("invoke untyped: valid msg (proto v2)", func(t *testing.T) { - resp, err := routerService.MessageRouterService().InvokeUntyped(testCtx.Ctx, &counterv1.MsgIncreaseCounter{ - Signer: "cosmos1", - Count: 42, - }) - require.NoError(t, err) - require.NotNil(t, resp) - }) - t.Run("invoke typed: valid msg (proto v1)", func(t *testing.T) { resp := &countertypes.MsgIncreaseCountResponse{} err := routerService.MessageRouterService().InvokeTyped(testCtx.Ctx, &countertypes.MsgIncreaseCounter{ @@ -83,36 +74,37 @@ func TestRouterService(t *testing.T) { t.Run("invalid query", func(t *testing.T) { err := routerService.QueryRouterService().InvokeTyped(testCtx.Ctx, &bankv1beta1.QueryBalanceRequest{}, &bankv1beta1.QueryBalanceResponse{}) - require.ErrorContains(t, err, "unknown request: /cosmos.bank.v1beta1.QueryBalanceRequest") + require.ErrorContains(t, err, "unknown request: cosmos.bank.v1beta1.QueryBalanceRequest") }) t.Run("invoke typed: valid query (proto v1)", func(t *testing.T) { + _ = counterKeeper.CountStore.Set(testCtx.Ctx, 42) + resp := &countertypes.QueryGetCountResponse{} err := routerService.QueryRouterService().InvokeTyped(testCtx.Ctx, &countertypes.QueryGetCountRequest{}, resp) require.NoError(t, err) require.NotNil(t, resp) - require.Equal(t, &countertypes.QueryGetCountResponse{TotalCount: 42}, resp) + require.Equal(t, int64(42), resp.TotalCount) }) t.Run("invoke typed: valid query (proto v2)", func(t *testing.T) { + _ = counterKeeper.CountStore.Set(testCtx.Ctx, 42) + resp := &counterv1.QueryGetCountResponse{} err := routerService.QueryRouterService().InvokeTyped(testCtx.Ctx, &counterv1.QueryGetCountRequest{}, resp) require.NoError(t, err) require.NotNil(t, resp) - require.Equal(t, &counterv1.QueryGetCountResponse{TotalCount: 42}, resp) + require.Equal(t, int64(42), resp.TotalCount) }) t.Run("invoke untyped: valid query (proto v1)", func(t *testing.T) { - resp, err := routerService.QueryRouterService().InvokeUntyped(testCtx.Ctx, &countertypes.QueryGetCountRequest{}) - require.NoError(t, err) - require.NotNil(t, resp) - require.Equal(t, &countertypes.QueryGetCountResponse{TotalCount: 42}, resp) - }) + _ = counterKeeper.CountStore.Set(testCtx.Ctx, 42) - t.Run("invoke untyped: valid query (proto v2)", func(t *testing.T) { - resp, err := routerService.QueryRouterService().InvokeUntyped(testCtx.Ctx, &counterv1.QueryGetCountRequest{}) + resp, err := routerService.QueryRouterService().InvokeUntyped(testCtx.Ctx, &countertypes.QueryGetCountRequest{}) require.NoError(t, err) require.NotNil(t, resp) - require.Equal(t, &counterv1.QueryGetCountResponse{TotalCount: 42}, resp) + respVal, ok := resp.(*countertypes.QueryGetCountResponse) + require.True(t, ok) + require.Equal(t, int64(42), respVal.TotalCount) }) } From 1caec8112c7556dad0310d421ed3ef6cec6f60a2 Mon Sep 17 00:00:00 2001 From: Julien Robert Date: Sat, 2 Mar 2024 01:33:21 +0100 Subject: [PATCH 17/18] match api --- x/accounts/keeper.go | 4 ++-- x/accounts/utils_test.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/x/accounts/keeper.go b/x/accounts/keeper.go index 328c2de1ac65..870bce8760ce 100644 --- a/x/accounts/keeper.go +++ b/x/accounts/keeper.go @@ -46,7 +46,7 @@ type QueryRouter interface { // MsgRouter represents a router which can be used to route messages to the correct module. type MsgRouter interface { HybridHandlerByMsgName(msgName string) func(ctx context.Context, req, resp implementation.ProtoMsg) error - ResponseNameByRequestName(name string) string + ResponseNameByMsgName(name string) string } // SignerProvider defines an interface used to get the expected sender from a message. @@ -344,7 +344,7 @@ func (k Keeper) sendAnyMessages(ctx context.Context, sender []byte, anyMessages func (k Keeper) sendModuleMessageUntyped(ctx context.Context, sender []byte, msg implementation.ProtoMsg) (implementation.ProtoMsg, error) { // we need to fetch the response type from the request message type. // this is because the response type is not known. - respName := k.msgRouter.ResponseNameByRequestName(implementation.MessageName(msg)) + respName := k.msgRouter.ResponseNameByMsgName(implementation.MessageName(msg)) if respName == "" { return nil, fmt.Errorf("could not find response type for message %T", msg) } diff --git a/x/accounts/utils_test.go b/x/accounts/utils_test.go index dc7ac535052c..5ee67365b6ec 100644 --- a/x/accounts/utils_test.go +++ b/x/accounts/utils_test.go @@ -89,6 +89,6 @@ func (m mockExec) HybridHandlerByMsgName(_ string) func(ctx context.Context, req } } -func (m mockExec) ResponseNameByRequestName(name string) string { +func (m mockExec) ResponseNameByMsgName(name string) string { return name + "Response" } From b3a330a71539b611070cd889283ff12bd89fba98 Mon Sep 17 00:00:00 2001 From: Julien Robert Date: Mon, 4 Mar 2024 10:57:43 +0100 Subject: [PATCH 18/18] add `CanInvoke` --- core/appmodule/v2/environment.go | 2 +- core/router/service.go | 16 +++++---- runtime/router.go | 59 +++++++++++++++++++++++--------- 3 files changed, 52 insertions(+), 25 deletions(-) diff --git a/core/appmodule/v2/environment.go b/core/appmodule/v2/environment.go index 40ee12b2f398..eccc7eb297af 100644 --- a/core/appmodule/v2/environment.go +++ b/core/appmodule/v2/environment.go @@ -18,7 +18,7 @@ type Environment struct { EventService event.Service GasService gas.Service HeaderService header.Service - RouterService router.Router + RouterService router.Service KVStoreService store.KVStoreService MemStoreService store.MemoryStoreService diff --git a/core/router/service.go b/core/router/service.go index 5a544d6a029f..4ab582085b20 100644 --- a/core/router/service.go +++ b/core/router/service.go @@ -6,15 +6,17 @@ import ( "google.golang.org/protobuf/runtime/protoiface" ) -// Router embeds a QueryRouterService and MessageRouterService. -// Each service allows to invoke messages and queries via the corresponding router. -type Router interface { - QueryRouterService() Service - MessageRouterService() Service +// 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 } -// Service is the interface that wraps the basic methods for a router service. -type Service interface { +// 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. diff --git a/runtime/router.go b/runtime/router.go index df2a95b4e778..972949ad8b98 100644 --- a/runtime/router.go +++ b/runtime/router.go @@ -16,7 +16,7 @@ import ( ) // NewRouterService creates a router.Service which allows to invoke messages and queries using the msg router. -func NewRouterService(storeService store.KVStoreService, queryRouter *baseapp.GRPCQueryRouter, msgRouter baseapp.MessageRouter) router.Router { +func NewRouterService(storeService store.KVStoreService, queryRouter *baseapp.GRPCQueryRouter, msgRouter baseapp.MessageRouter) router.Service { return &routerService{ queryRouterService: &queryRouterService{ storeService: storeService, // TODO: this will be used later on as authenticating modules before routing @@ -29,36 +29,46 @@ func NewRouterService(storeService store.KVStoreService, queryRouter *baseapp.GR } } -var _ router.Router = (*routerService)(nil) +var _ router.Service = (*routerService)(nil) type routerService struct { - queryRouterService router.Service - msgRouterService router.Service + queryRouterService router.Router + msgRouterService router.Router } -// MessageRouterService implements router.Router. -func (r *routerService) MessageRouterService() router.Service { +// MessageRouterService implements router.Service. +func (r *routerService) MessageRouterService() router.Router { return r.msgRouterService } -// QueryRouterService implements router.Router. -func (r *routerService) QueryRouterService() router.Service { +// QueryRouterService implements router.Service. +func (r *routerService) QueryRouterService() router.Router { return r.queryRouterService } -var _ router.Service = (*msgRouterService)(nil) +var _ router.Router = (*msgRouterService)(nil) type msgRouterService struct { storeService store.KVStoreService router baseapp.MessageRouter } +// CanInvoke returns an error if the given message cannot be invoked. +func (m *msgRouterService) CanInvoke(ctx context.Context, msg protoiface.MessageV1) error { + messageName := msgTypeURL(msg) + handler := m.router.HybridHandlerByMsgName(messageName) + if handler == nil { + return fmt.Errorf("unknown message: %s", messageName) + } + + return nil +} + // InvokeTyped execute a message and fill-in a response. // The response must be known and passed as a parameter. // Use InvokeUntyped if the response type is not known. func (m *msgRouterService) InvokeTyped(ctx context.Context, msg, resp protoiface.MessageV1) error { messageName := msgTypeURL(msg) - handler := m.router.HybridHandlerByMsgName(messageName) if handler == nil { return fmt.Errorf("unknown message: %s", messageName) @@ -88,21 +98,36 @@ func (m *msgRouterService) InvokeUntyped(ctx context.Context, msg protoiface.Mes return msgResp, m.InvokeTyped(ctx, msg, msgResp) } -var _ router.Service = (*queryRouterService)(nil) +var _ router.Router = (*queryRouterService)(nil) type queryRouterService struct { storeService store.KVStoreService router *baseapp.GRPCQueryRouter } +// CanInvoke returns an error if the given request cannot be invoked. +func (m *queryRouterService) CanInvoke(ctx context.Context, req protoiface.MessageV1) error { + reqName := msgTypeURL(req) + handlers := m.router.HybridHandlerByRequestName(reqName) + if len(handlers) == 0 { + return fmt.Errorf("unknown request: %s", reqName) + } else if len(handlers) > 1 { + return fmt.Errorf("ambiguous request, query have multiple handlers: %s", reqName) + } + + return nil +} + // InvokeTyped execute a message and fill-in a response. // The response must be known and passed as a parameter. // Use InvokeUntyped if the response type is not known. func (m *queryRouterService) InvokeTyped(ctx context.Context, req, resp protoiface.MessageV1) error { - messageName := msgTypeURL(req) - handlers := m.router.HybridHandlerByRequestName(messageName) + reqName := msgTypeURL(req) + handlers := m.router.HybridHandlerByRequestName(reqName) if len(handlers) == 0 { - return fmt.Errorf("unknown request: %s", messageName) + return fmt.Errorf("unknown request: %s", reqName) + } else if len(handlers) > 1 { + return fmt.Errorf("ambiguous request, query have multiple handlers: %s", reqName) } return handlers[0](ctx, req, resp) @@ -110,10 +135,10 @@ func (m *queryRouterService) InvokeTyped(ctx context.Context, req, resp protoifa // InvokeUntyped execute a message and returns a response. func (m *queryRouterService) InvokeUntyped(ctx context.Context, req protoiface.MessageV1) (protoiface.MessageV1, error) { - messageName := msgTypeURL(req) - respName := m.router.ResponseNameByRequestName(messageName) + reqName := msgTypeURL(req) + respName := m.router.ResponseNameByRequestName(reqName) if respName == "" { - return nil, fmt.Errorf("could not find response type for request %s (%T)", messageName, req) + return nil, fmt.Errorf("could not find response type for request %s (%T)", reqName, req) } // get response type