feat: Implement RegisterServices for CoreAPI in module manager (#15133)

Co-authored-by: Marko <marbar3778@yahoo.com>
This commit is contained in:
Facundo Medica
2023-03-02 14:22:20 -03:00
committed by GitHub
co-authored by Marko
parent da238c5555
commit 14ba0248b8
9 changed files with 173 additions and 38 deletions
+1
View File
@@ -39,6 +39,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
### Features
* (core) [#15133](https://github.com/cosmos/cosmos-sdk/pull/15133) Implement RegisterServices in the module manager.
* (x/gov) [#14373](https://github.com/cosmos/cosmos-sdk/pull/14057) Add new proto field `constitution` of type `string` to gov module genesis state, which allows chain builders to lay a strong foundation by specifying purpose.
* (x/genutil) [#15301](https://github.com/cosmos/cosmos-sdk/pull/15031) Add application genesis. The genesis is now entirely managed by the application and passed to CometBFT at note instantiation. Functions that were taking a `cmttypes.GenesisDoc{}` now takes a `genutiltypes.AppGenesis{}`.
* (cli) [#14659](https://github.com/cosmos/cosmos-sdk/pull/14659) Added ability to query blocks by events with queries directly passed to Tendermint, which will allow for full query operator support, e.g. `>`.
+4 -1
View File
@@ -44,7 +44,10 @@ func (a *AppBuilder) Build(
a.app.BaseApp = bApp
a.app.configurator = module.NewConfigurator(a.app.cdc, a.app.MsgServiceRouter(), a.app.GRPCQueryRouter())
a.app.ModuleManager.RegisterServices(a.app.configurator)
err := a.app.ModuleManager.RegisterServices(a.app.configurator)
if err != nil {
panic(err)
}
return a.app
}
+66 -21
View File
@@ -4,8 +4,14 @@ import (
"context"
autocliv1 "cosmossdk.io/api/cosmos/autocli/v1"
cosmosmsg "cosmossdk.io/api/cosmos/msg/v1"
"cosmossdk.io/core/appmodule"
gogogrpc "github.com/cosmos/gogoproto/grpc"
"github.com/cosmos/gogoproto/proto"
"google.golang.org/grpc"
protobuf "google.golang.org/protobuf/proto"
"google.golang.org/protobuf/reflect/protoreflect"
"google.golang.org/protobuf/reflect/protoregistry"
"github.com/cosmos/cosmos-sdk/types/module"
)
@@ -35,32 +41,48 @@ func ExtractAutoCLIOptions(appModules map[string]interface{}) map[string]*autocl
AutoCLIOptions() *autocliv1.ModuleOptions
}); ok {
moduleOptions[modName] = autoCliMod.AutoCLIOptions()
} else if mod, ok := mod.(module.HasServices); ok {
// try to auto-discover options based on the last msg and query
// services registered for the module
cfg := &autocliConfigurator{}
continue
}
cfg := &autocliConfigurator{}
// try to auto-discover options based on the last msg and query
// services registered for the module
if mod, ok := mod.(module.HasServices); ok {
mod.RegisterServices(cfg)
modOptions := &autocliv1.ModuleOptions{}
haveServices := false
}
if cfg.msgServer.serviceName != "" {
haveServices = true
modOptions.Tx = &autocliv1.ServiceCommandDescriptor{
Service: cfg.msgServer.serviceName,
}
if mod, ok := mod.(appmodule.HasServices); ok {
err := mod.RegisterServices(cfg)
if err != nil {
panic(err)
}
}
if cfg.queryServer.serviceName != "" {
haveServices = true
modOptions.Query = &autocliv1.ServiceCommandDescriptor{
Service: cfg.queryServer.serviceName,
}
}
// check for errors in the configurator
if cfg.Error() != nil {
panic(cfg.Error())
}
if haveServices {
moduleOptions[modName] = modOptions
haveServices := false
modOptions := &autocliv1.ModuleOptions{}
if cfg.msgServer.serviceName != "" {
haveServices = true
modOptions.Tx = &autocliv1.ServiceCommandDescriptor{
Service: cfg.msgServer.serviceName,
}
}
if cfg.queryServer.serviceName != "" {
haveServices = true
modOptions.Query = &autocliv1.ServiceCommandDescriptor{
Service: cfg.queryServer.serviceName,
}
}
if haveServices {
moduleOptions[modName] = modOptions
}
}
return moduleOptions
}
@@ -73,10 +95,14 @@ func (a AutoCLIQueryService) AppOptions(context.Context, *autocliv1.AppOptionsRe
// autocliConfigurator allows us to call RegisterServices and introspect the services
type autocliConfigurator struct {
msgServer autocliServiceRegistrar
queryServer autocliServiceRegistrar
msgServer autocliServiceRegistrar
queryServer autocliServiceRegistrar
registryCache *protoregistry.Files
err error
}
var _ module.Configurator = &autocliConfigurator{}
func (a *autocliConfigurator) MsgServer() gogogrpc.Server { return &a.msgServer }
func (a *autocliConfigurator) QueryServer() gogogrpc.Server { return &a.queryServer }
@@ -85,6 +111,25 @@ func (a *autocliConfigurator) RegisterMigration(string, uint64, module.Migration
return nil
}
func (a *autocliConfigurator) RegisterService(sd *grpc.ServiceDesc, ss interface{}) {
if a.registryCache == nil {
a.registryCache, a.err = proto.MergedRegistry()
}
desc, err := a.registryCache.FindDescriptorByName(protoreflect.FullName(sd.ServiceName))
if err != nil {
a.err = err
return
}
if protobuf.HasExtension(desc.Options(), cosmosmsg.E_Service) {
a.msgServer.RegisterService(sd, ss)
} else {
a.queryServer.RegisterService(sd, ss)
}
}
func (a *autocliConfigurator) Error() error { return nil }
// autocliServiceRegistrar is used to capture the service name for registered services
type autocliServiceRegistrar struct {
serviceName string
+4 -1
View File
@@ -453,7 +453,10 @@ func NewSimApp(
app.ModuleManager.RegisterInvariants(app.CrisisKeeper)
app.configurator = module.NewConfigurator(app.appCodec, app.MsgServiceRouter(), app.GRPCQueryRouter())
app.ModuleManager.RegisterServices(app.configurator)
err := app.ModuleManager.RegisterServices(app.configurator)
if err != nil {
panic(err)
}
// RegisterUpgradeHandlers is used for registering any on-chain upgrades.
// Make sure it's called after `app.ModuleManager` and `app.configurator` are set.
+8
View File
@@ -14,6 +14,7 @@ import (
"github.com/golang/mock/gomock"
"github.com/stretchr/testify/require"
"cosmossdk.io/core/appmodule"
"cosmossdk.io/log"
feegrantmodule "cosmossdk.io/x/feegrant/module"
"github.com/cosmos/cosmos-sdk/baseapp"
@@ -97,6 +98,13 @@ func TestRunMigrations(t *testing.T) {
if mod, ok := mod.(module.HasServices); ok {
mod.RegisterServices(configurator)
}
if mod, ok := mod.(appmodule.HasServices); ok {
err := mod.RegisterServices(configurator)
require.NoError(t, err)
}
require.NoError(t, configurator.Error())
}
// Initialize the chain
+45 -8
View File
@@ -3,9 +3,14 @@ package module
import (
"fmt"
"github.com/cosmos/gogoproto/grpc"
cosmosmsg "cosmossdk.io/api/cosmos/msg/v1"
errorsmod "cosmossdk.io/errors"
"github.com/cosmos/gogoproto/grpc"
"github.com/cosmos/gogoproto/proto"
googlegrpc "google.golang.org/grpc"
protobuf "google.golang.org/protobuf/proto"
"google.golang.org/protobuf/reflect/protoreflect"
"google.golang.org/protobuf/reflect/protoregistry"
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
@@ -17,6 +22,11 @@ import (
// support module object capabilities isolation as described in
// https://github.com/cosmos/cosmos-sdk/issues/7093
type Configurator interface {
grpc.Server
// Error returns the last error encountered during RegisterService.
Error() error
// MsgServer returns a grpc.Server instance which allows registering services
// that will handle TxBody.messages in transactions. These Msg's WILL NOT
// be exposed as gRPC services.
@@ -45,11 +55,38 @@ type configurator struct {
// migrations is a map of moduleName -> fromVersion -> migration script handler
migrations map[string]map[uint64]MigrationHandler
registryCache *protoregistry.Files
err error
}
// RegisterService implements the grpc.Server interface.
func (c *configurator) RegisterService(sd *googlegrpc.ServiceDesc, ss interface{}) {
if c.registryCache == nil {
c.registryCache, c.err = proto.MergedRegistry()
}
desc, err := c.registryCache.FindDescriptorByName(protoreflect.FullName(sd.ServiceName))
if err != nil {
c.err = err
return
}
if protobuf.HasExtension(desc.Options(), cosmosmsg.E_Service) {
c.msgServer.RegisterService(sd, ss)
} else {
c.queryServer.RegisterService(sd, ss)
}
}
// Error returns the last error encountered during RegisterService.
func (c *configurator) Error() error {
return c.err
}
// NewConfigurator returns a new Configurator instance
func NewConfigurator(cdc codec.Codec, msgServer grpc.Server, queryServer grpc.Server) Configurator {
return configurator{
return &configurator{
cdc: cdc,
msgServer: msgServer,
queryServer: queryServer,
@@ -57,20 +94,20 @@ func NewConfigurator(cdc codec.Codec, msgServer grpc.Server, queryServer grpc.Se
}
}
var _ Configurator = configurator{}
var _ Configurator = &configurator{}
// MsgServer implements the Configurator.MsgServer method
func (c configurator) MsgServer() grpc.Server {
func (c *configurator) MsgServer() grpc.Server {
return c.msgServer
}
// QueryServer implements the Configurator.QueryServer method
func (c configurator) QueryServer() grpc.Server {
func (c *configurator) QueryServer() grpc.Server {
return c.queryServer
}
// RegisterMigration implements the Configurator.RegisterMigration method
func (c configurator) RegisterMigration(moduleName string, fromVersion uint64, handler MigrationHandler) error {
func (c *configurator) RegisterMigration(moduleName string, fromVersion uint64, handler MigrationHandler) error {
if fromVersion == 0 {
return errorsmod.Wrap(sdkerrors.ErrInvalidVersion, "module migration versions should start at 1")
}
@@ -90,7 +127,7 @@ func (c configurator) RegisterMigration(moduleName string, fromVersion uint64, h
// runModuleMigrations runs all in-place store migrations for one given module from a
// version to another version.
func (c configurator) runModuleMigrations(ctx sdk.Context, moduleName string, fromVersion, toVersion uint64) error {
func (c *configurator) runModuleMigrations(ctx sdk.Context, moduleName string, fromVersion, toVersion uint64) error {
// No-op if toVersion is the initial version or if the version is unchanged.
if toVersion <= 1 || fromVersion == toVersion {
return nil
+11
View File
@@ -20,6 +20,7 @@ import (
var (
_ AppModuleBasic = coreAppModuleBasicAdapator{}
_ HasGenesis = coreAppModuleBasicAdapator{}
_ HasServices = coreAppModuleBasicAdapator{}
)
// CoreAppModuleBasicAdaptor wraps the core API module as an AppModule that this version
@@ -161,3 +162,13 @@ func (c coreAppModuleBasicAdapator) RegisterLegacyAminoCodec(amino *codec.Legacy
mod.RegisterLegacyAminoCodec(amino)
}
}
// RegisterServices implements HasServices
func (c coreAppModuleBasicAdapator) RegisterServices(cfg Configurator) {
if module, ok := c.module.(appmodule.HasServices); ok {
err := module.RegisterServices(cfg)
if err != nil {
panic(err)
}
}
}
+16 -3
View File
@@ -368,12 +368,25 @@ func (m *Manager) RegisterInvariants(ir sdk.InvariantRegistry) {
}
// RegisterServices registers all module services
func (m *Manager) RegisterServices(cfg Configurator) {
func (m *Manager) RegisterServices(cfg Configurator) error {
for _, module := range m.Modules {
if module, ok := module.(HasServices); ok {
module.RegisterServices(cfg)
}
if module, ok := module.(appmodule.HasServices); ok {
err := module.RegisterServices(cfg)
if err != nil {
return err
}
}
if cfg.Error() != nil {
return cfg.Error()
}
}
return nil
}
// InitGenesis performs init genesis functionality for modules. Exactly one
@@ -586,9 +599,9 @@ type VersionMap map[string]uint64
//
// Please also refer to docs/core/upgrade.md for more information.
func (m Manager) RunMigrations(ctx sdk.Context, cfg Configurator, fromVM VersionMap) (VersionMap, error) {
c, ok := cfg.(configurator)
c, ok := cfg.(*configurator)
if !ok {
return nil, errorsmod.Wrapf(sdkerrors.ErrInvalidType, "expected %T, got %T", configurator{}, cfg)
return nil, errorsmod.Wrapf(sdkerrors.ErrInvalidType, "expected %T, got %T", &configurator{}, cfg)
}
modules := m.OrderMigrations
if modules == nil {
+18 -4
View File
@@ -14,12 +14,14 @@ import (
"github.com/golang/mock/gomock"
"github.com/spf13/cobra"
"github.com/stretchr/testify/require"
"google.golang.org/grpc"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/codec/types"
"github.com/cosmos/cosmos-sdk/testutil/mock"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/module"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
)
var errFoo = errors.New("dummy")
@@ -155,7 +157,7 @@ func TestManager_RegisterQueryServices(t *testing.T) {
mockAppModule1 := mock.NewMockAppModuleWithAllExtensions(mockCtrl)
mockAppModule2 := mock.NewMockAppModuleWithAllExtensions(mockCtrl)
mockAppModule3 := mock.NewMockCoreAppModule(mockCtrl)
mockAppModule3 := MockCoreAppModule{}
mockAppModule1.EXPECT().Name().Times(2).Return("module1")
mockAppModule2.EXPECT().Name().Times(2).Return("module2")
// TODO: This is not working for Core API modules yet
@@ -164,14 +166,17 @@ func TestManager_RegisterQueryServices(t *testing.T) {
require.Equal(t, 3, len(mm.Modules))
msgRouter := mock.NewMockServer(mockCtrl)
msgRouter.EXPECT().RegisterService(gomock.Any(), gomock.Any()).Times(1)
queryRouter := mock.NewMockServer(mockCtrl)
queryRouter.EXPECT().RegisterService(gomock.Any(), gomock.Any()).Times(1)
interfaceRegistry := types.NewInterfaceRegistry()
cdc := codec.NewProtoCodec(interfaceRegistry)
cfg := module.NewConfigurator(cdc, msgRouter, queryRouter)
mockAppModule1.EXPECT().RegisterServices(cfg).Times(1)
mockAppModule2.EXPECT().RegisterServices(cfg).Times(1)
mm.RegisterServices(cfg)
require.NotPanics(t, func() { mm.RegisterServices(cfg) })
}
func TestManager_InitGenesis(t *testing.T) {
@@ -484,6 +489,14 @@ func TestCoreAPIManager_EndBlock(t *testing.T) {
// MockCoreAppModule allows us to test functions like DefaultGenesis
type MockCoreAppModule struct{}
// RegisterServices implements appmodule.HasServices
func (MockCoreAppModule) RegisterServices(reg grpc.ServiceRegistrar) error {
// Use Auth's service definitions as a placeholder
authtypes.RegisterQueryServer(reg, &authtypes.UnimplementedQueryServer{})
authtypes.RegisterMsgServer(reg, &authtypes.UnimplementedMsgServer{})
return nil
}
func (MockCoreAppModule) IsOnePerModuleType() {}
func (MockCoreAppModule) IsAppModule() {}
func (MockCoreAppModule) DefaultGenesis(target appmodule.GenesisTarget) error {
@@ -523,6 +536,7 @@ func (MockCoreAppModule) ExportGenesis(ctx context.Context, target appmodule.Gen
}
var (
_ appmodule.AppModule = MockCoreAppModule{}
_ appmodule.HasGenesis = MockCoreAppModule{}
_ appmodule.AppModule = MockCoreAppModule{}
_ appmodule.HasGenesis = MockCoreAppModule{}
_ appmodule.HasServices = MockCoreAppModule{}
)