From aceadb0b7ea5d4424c0879475edcfed2636a2e56 Mon Sep 17 00:00:00 2001 From: Aaron Craelius Date: Fri, 27 Jan 2023 12:14:56 -0500 Subject: [PATCH] feat: support core API genesis in module manager (#14582) Co-authored-by: Facundo Medica Co-authored-by: Facundo Medica <14063057+facundomedica@users.noreply.github.com> Co-authored-by: Marko --- testutil/mock/types_mock_appmodule.go | 105 +++++++++++ types/module/core_module.go | 163 +++++++++++++++++ types/module/genesis.go | 1 + types/module/mock_appmodule_test.go | 13 +- types/module/module.go | 67 +++++-- types/module/module_test.go | 252 +++++++++++++++++++++++--- 6 files changed, 558 insertions(+), 43 deletions(-) create mode 100644 types/module/core_module.go create mode 100644 types/module/genesis.go diff --git a/testutil/mock/types_mock_appmodule.go b/testutil/mock/types_mock_appmodule.go index 06e12df03c9f..ce0aff5b86a3 100644 --- a/testutil/mock/types_mock_appmodule.go +++ b/testutil/mock/types_mock_appmodule.go @@ -5,9 +5,11 @@ package mock import ( + context "context" json "encoding/json" reflect "reflect" + appmodule "cosmossdk.io/core/appmodule" client "github.com/cosmos/cosmos-sdk/client" codec "github.com/cosmos/cosmos-sdk/codec" types "github.com/cosmos/cosmos-sdk/codec/types" @@ -239,3 +241,106 @@ func (mr *MockAppModuleWithAllExtensionsMockRecorder) ValidateGenesis(arg0, arg1 mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValidateGenesis", reflect.TypeOf((*MockAppModuleWithAllExtensions)(nil).ValidateGenesis), arg0, arg1, arg2) } + +// MockCoreAppModule is a mock of CoreAppModule interface. +type MockCoreAppModule struct { + ctrl *gomock.Controller + recorder *MockCoreAppModuleMockRecorder +} + +// MockCoreAppModuleMockRecorder is the mock recorder for MockCoreAppModule. +type MockCoreAppModuleMockRecorder struct { + mock *MockCoreAppModule +} + +// NewMockCoreAppModule creates a new mock instance. +func NewMockCoreAppModule(ctrl *gomock.Controller) *MockCoreAppModule { + mock := &MockCoreAppModule{ctrl: ctrl} + mock.recorder = &MockCoreAppModuleMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockCoreAppModule) EXPECT() *MockCoreAppModuleMockRecorder { + return m.recorder +} + +// DefaultGenesis mocks base method. +func (m *MockCoreAppModule) DefaultGenesis(arg0 appmodule.GenesisTarget) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DefaultGenesis", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// DefaultGenesis indicates an expected call of DefaultGenesis. +func (mr *MockCoreAppModuleMockRecorder) DefaultGenesis(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DefaultGenesis", reflect.TypeOf((*MockCoreAppModule)(nil).DefaultGenesis), arg0) +} + +// ExportGenesis mocks base method. +func (m *MockCoreAppModule) ExportGenesis(arg0 context.Context, arg1 appmodule.GenesisTarget) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ExportGenesis", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// ExportGenesis indicates an expected call of ExportGenesis. +func (mr *MockCoreAppModuleMockRecorder) ExportGenesis(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExportGenesis", reflect.TypeOf((*MockCoreAppModule)(nil).ExportGenesis), arg0, arg1) +} + +// InitGenesis mocks base method. +func (m *MockCoreAppModule) InitGenesis(arg0 context.Context, arg1 appmodule.GenesisSource) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "InitGenesis", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// InitGenesis indicates an expected call of InitGenesis. +func (mr *MockCoreAppModuleMockRecorder) InitGenesis(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InitGenesis", reflect.TypeOf((*MockCoreAppModule)(nil).InitGenesis), arg0, arg1) +} + +// IsAppModule mocks base method. +func (m *MockCoreAppModule) IsAppModule() { + m.ctrl.T.Helper() + m.ctrl.Call(m, "IsAppModule") +} + +// IsAppModule indicates an expected call of IsAppModule. +func (mr *MockCoreAppModuleMockRecorder) IsAppModule() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsAppModule", reflect.TypeOf((*MockCoreAppModule)(nil).IsAppModule)) +} + +// IsOnePerModuleType mocks base method. +func (m *MockCoreAppModule) IsOnePerModuleType() { + m.ctrl.T.Helper() + m.ctrl.Call(m, "IsOnePerModuleType") +} + +// IsOnePerModuleType indicates an expected call of IsOnePerModuleType. +func (mr *MockCoreAppModuleMockRecorder) IsOnePerModuleType() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsOnePerModuleType", reflect.TypeOf((*MockCoreAppModule)(nil).IsOnePerModuleType)) +} + +// ValidateGenesis mocks base method. +func (m *MockCoreAppModule) ValidateGenesis(arg0 appmodule.GenesisSource) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ValidateGenesis", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// ValidateGenesis indicates an expected call of ValidateGenesis. +func (mr *MockCoreAppModuleMockRecorder) ValidateGenesis(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValidateGenesis", reflect.TypeOf((*MockCoreAppModule)(nil).ValidateGenesis), arg0) +} diff --git a/types/module/core_module.go b/types/module/core_module.go new file mode 100644 index 000000000000..d924deeba77d --- /dev/null +++ b/types/module/core_module.go @@ -0,0 +1,163 @@ +package module + +import ( + "encoding/json" + + "cosmossdk.io/core/appmodule" + "cosmossdk.io/core/genesis" + "github.com/grpc-ecosystem/grpc-gateway/runtime" + "github.com/spf13/cobra" + abci "github.com/tendermint/tendermint/abci/types" + + storetypes "cosmossdk.io/store/types" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/codec" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +var ( + _ AppModuleBasic = coreAppModuleBasicAdapator{} + _ HasGenesis = coreAppModuleBasicAdapator{} +) + +// CoreAppModuleBasicAdaptor wraps the core API module as an AppModule that this version +// of the SDK can use. +func CoreAppModuleBasicAdaptor(name string, module appmodule.AppModule) AppModuleBasic { + return coreAppModuleBasicAdapator{ + name: name, + module: module, + } +} + +type coreAppModuleBasicAdapator struct { + name string + module appmodule.AppModule +} + +// DefaultGenesis implements HasGenesis +func (c coreAppModuleBasicAdapator) DefaultGenesis(codec.JSONCodec) json.RawMessage { + if mod, ok := c.module.(appmodule.HasGenesis); ok { + target := genesis.RawJSONTarget{} + err := mod.DefaultGenesis(target.Target()) + if err != nil { + panic(err) + } + + res, err := target.JSON() + if err != nil { + panic(err) + } + + return res + } + return nil +} + +// ValidateGenesis implements HasGenesis +func (c coreAppModuleBasicAdapator) ValidateGenesis(cdc codec.JSONCodec, txConfig client.TxEncodingConfig, bz json.RawMessage) error { + if mod, ok := c.module.(appmodule.HasGenesis); ok { + source, err := genesis.SourceFromRawJSON(bz) + if err != nil { + return err + } + + if err := mod.ValidateGenesis(source); err != nil { + return err + } + } + + return nil +} + +// ExportGenesis implements HasGenesis +func (c coreAppModuleBasicAdapator) ExportGenesis(ctx sdk.Context, cdc codec.JSONCodec) json.RawMessage { + if module, ok := c.module.(appmodule.HasGenesis); ok { + ctx := ctx.WithGasMeter(storetypes.NewInfiniteGasMeter()) // avoid race conditions + target := genesis.RawJSONTarget{} + err := module.ExportGenesis(ctx, target.Target()) + if err != nil { + panic(err) + } + + rawJSON, err := target.JSON() + if err != nil { + panic(err) + } + + return rawJSON + } + return nil +} + +// InitGenesis implements HasGenesis +func (c coreAppModuleBasicAdapator) InitGenesis(ctx sdk.Context, _ codec.JSONCodec, bz json.RawMessage) []abci.ValidatorUpdate { + if module, ok := c.module.(appmodule.HasGenesis); ok { + // core API genesis + source, err := genesis.SourceFromRawJSON(bz) + if err != nil { + panic(err) + } + + err = module.InitGenesis(ctx, source) + if err != nil { + panic(err) + } + } + return nil +} + +// Name implements AppModuleBasic +func (c coreAppModuleBasicAdapator) Name() string { + return c.name +} + +// GetQueryCmd implements AppModuleBasic +func (c coreAppModuleBasicAdapator) GetQueryCmd() *cobra.Command { + if mod, ok := c.module.(interface { + GetQueryCmd() *cobra.Command + }); ok { + return mod.GetQueryCmd() + } + + return nil +} + +// GetTxCmd implements AppModuleBasic +func (c coreAppModuleBasicAdapator) GetTxCmd() *cobra.Command { + if mod, ok := c.module.(interface { + GetTxCmd() *cobra.Command + }); ok { + return mod.GetTxCmd() + } + + return nil +} + +// RegisterGRPCGatewayRoutes implements AppModuleBasic +func (c coreAppModuleBasicAdapator) RegisterGRPCGatewayRoutes(ctx client.Context, mux *runtime.ServeMux) { + if mod, ok := c.module.(interface { + RegisterGRPCGatewayRoutes(context client.Context, mux *runtime.ServeMux) + }); ok { + mod.RegisterGRPCGatewayRoutes(ctx, mux) + } +} + +// RegisterInterfaces implements AppModuleBasic +func (c coreAppModuleBasicAdapator) RegisterInterfaces(registry codectypes.InterfaceRegistry) { + if mod, ok := c.module.(interface { + RegisterInterfaces(registry codectypes.InterfaceRegistry) + }); ok { + mod.RegisterInterfaces(registry) + } +} + +// RegisterLegacyAminoCodec implements AppModuleBasic +func (c coreAppModuleBasicAdapator) RegisterLegacyAminoCodec(amino *codec.LegacyAmino) { + if mod, ok := c.module.(interface { + RegisterLegacyAminoCodec(amino *codec.LegacyAmino) + }); ok { + mod.RegisterLegacyAminoCodec(amino) + } +} diff --git a/types/module/genesis.go b/types/module/genesis.go new file mode 100644 index 000000000000..b0b78bfd7a25 --- /dev/null +++ b/types/module/genesis.go @@ -0,0 +1 @@ +package module diff --git a/types/module/mock_appmodule_test.go b/types/module/mock_appmodule_test.go index e16363f1fde1..94d8c466b396 100644 --- a/types/module/mock_appmodule_test.go +++ b/types/module/mock_appmodule_test.go @@ -1,6 +1,10 @@ package module_test -import "github.com/cosmos/cosmos-sdk/types/module" +import ( + "cosmossdk.io/core/appmodule" + + "github.com/cosmos/cosmos-sdk/types/module" +) // AppModuleWithAllExtensions is solely here for the purpose of generating // mocks to be used in module tests. @@ -13,3 +17,10 @@ type AppModuleWithAllExtensions interface { module.BeginBlockAppModule module.EndBlockAppModule } + +// CoreAppModule is solely here for the purpose of generating +// mocks to be used in module tests. +type CoreAppModule interface { + appmodule.AppModule + appmodule.HasGenesis +} diff --git a/types/module/module.go b/types/module/module.go index 80eed9d05432..72b24881c941 100644 --- a/types/module/module.go +++ b/types/module/module.go @@ -34,6 +34,7 @@ import ( "sort" "cosmossdk.io/core/appmodule" + "cosmossdk.io/core/genesis" "github.com/grpc-ecosystem/grpc-gateway/runtime" "github.com/spf13/cobra" abci "github.com/tendermint/tendermint/abci/types" @@ -43,7 +44,7 @@ import ( "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/codec" - codectypes "github.com/cosmos/cosmos-sdk/codec/types" + "github.com/cosmos/cosmos-sdk/codec/types" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" ) @@ -52,7 +53,7 @@ import ( type AppModuleBasic interface { HasName RegisterLegacyAminoCodec(*codec.LegacyAmino) - RegisterInterfaces(codectypes.InterfaceRegistry) + RegisterInterfaces(types.InterfaceRegistry) // client functionality RegisterGRPCGatewayRoutes(client.Context, *runtime.ServeMux) @@ -93,7 +94,7 @@ func (bm BasicManager) RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) { } // RegisterInterfaces registers all module interface types -func (bm BasicManager) RegisterInterfaces(registry codectypes.InterfaceRegistry) { +func (bm BasicManager) RegisterInterfaces(registry types.InterfaceRegistry) { for _, m := range bm { m.RegisterInterfaces(registry) } @@ -101,21 +102,22 @@ func (bm BasicManager) RegisterInterfaces(registry codectypes.InterfaceRegistry) // DefaultGenesis provides default genesis information for all modules func (bm BasicManager) DefaultGenesis(cdc codec.JSONCodec) map[string]json.RawMessage { - genesis := make(map[string]json.RawMessage) + genesisData := make(map[string]json.RawMessage) for _, b := range bm { if mod, ok := b.(HasGenesisBasics); ok { - genesis[b.Name()] = mod.DefaultGenesis(cdc) + genesisData[b.Name()] = mod.DefaultGenesis(cdc) } } - return genesis + return genesisData } // ValidateGenesis performs genesis state validation for all modules -func (bm BasicManager) ValidateGenesis(cdc codec.JSONCodec, txEncCfg client.TxEncodingConfig, genesis map[string]json.RawMessage) error { +func (bm BasicManager) ValidateGenesis(cdc codec.JSONCodec, txEncCfg client.TxEncodingConfig, genesisData map[string]json.RawMessage) error { for _, b := range bm { + // first check if the module is an adapted Core API Module if mod, ok := b.(HasGenesisBasics); ok { - if err := mod.ValidateGenesis(cdc, txEncCfg, genesis[b.Name()]); err != nil { + if err := mod.ValidateGenesis(cdc, txEncCfg, genesisData[b.Name()]); err != nil { return err } } @@ -284,6 +286,9 @@ func NewManagerFromMap(moduleMap map[string]appmodule.AppModule) *Manager { modulesStr = append(modulesStr, name) } + // Sort the modules by name. Given that we are using a map above we can't guarantee the order. + sort.Strings(modulesStr) + return &Manager{ Modules: simpleModuleMap, OrderInitGenesis: modulesStr, @@ -297,6 +302,10 @@ func NewManagerFromMap(moduleMap map[string]appmodule.AppModule) *Manager { func (m *Manager) SetOrderInitGenesis(moduleNames ...string) { m.assertNoForgottenModules("SetOrderInitGenesis", moduleNames, func(moduleName string) bool { module := m.Modules[moduleName] + if _, hasGenesis := module.(appmodule.HasGenesis); hasGenesis { + return !hasGenesis + } + _, hasGenesis := module.(HasGenesis) return !hasGenesis }) @@ -307,6 +316,10 @@ func (m *Manager) SetOrderInitGenesis(moduleNames ...string) { func (m *Manager) SetOrderExportGenesis(moduleNames ...string) { m.assertNoForgottenModules("SetOrderExportGenesis", moduleNames, func(moduleName string) bool { module := m.Modules[moduleName] + if _, hasGenesis := module.(appmodule.HasGenesis); hasGenesis { + return !hasGenesis + } + _, hasGenesis := module.(HasGenesis) return !hasGenesis }) @@ -371,9 +384,22 @@ func (m *Manager) InitGenesis(ctx sdk.Context, cdc codec.JSONCodec, genesisData continue } - if module, ok := m.Modules[moduleName].(HasGenesis); ok { + mod := m.Modules[moduleName] + // we might get an adapted module, a native core API module or a legacy module + if module, ok := mod.(appmodule.HasGenesis); ok { ctx.Logger().Debug("running initialization for module", "module", moduleName) + // core API genesis + source, err := genesis.SourceFromRawJSON(genesisData[moduleName]) + if err != nil { + panic(err) + } + err = module.InitGenesis(ctx, source) + if err != nil { + panic(err) + } + } else if module, ok := mod.(HasGenesis); ok { + ctx.Logger().Debug("running initialization for module", "module", moduleName) moduleValUpdates := module.InitGenesis(ctx, cdc, genesisData[moduleName]) // use these validator updates if provided, the module manager assumes @@ -407,7 +433,6 @@ func (m *Manager) ExportGenesisForModules(ctx sdk.Context, cdc codec.JSONCodec, if len(modulesToExport) == 0 { modulesToExport = m.OrderExportGenesis } - // verify modules exists in app, so that we don't panic in the middle of an export if err := m.checkModulesExists(modulesToExport); err != nil { panic(err) @@ -415,11 +440,29 @@ func (m *Manager) ExportGenesisForModules(ctx sdk.Context, cdc codec.JSONCodec, channels := make(map[string]chan json.RawMessage) for _, moduleName := range modulesToExport { - if module, ok := m.Modules[moduleName].(HasGenesis); ok { + mod := m.Modules[moduleName] + if module, ok := mod.(appmodule.HasGenesis); ok { + // core API genesis channels[moduleName] = make(chan json.RawMessage) - go func(module HasGenesis, ch chan json.RawMessage) { + go func(module appmodule.HasGenesis, ch chan json.RawMessage) { ctx := ctx.WithGasMeter(storetypes.NewInfiniteGasMeter()) // avoid race conditions + target := genesis.RawJSONTarget{} + err := module.ExportGenesis(ctx, target.Target()) + if err != nil { + panic(err) + } + + rawJSON, err := target.JSON() + if err != nil { + panic(err) + } + ch <- rawJSON + }(module, channels[moduleName]) + } else if module, ok := mod.(HasGenesis); ok { + channels[moduleName] = make(chan json.RawMessage) + go func(module HasGenesis, ch chan json.RawMessage) { + ctx := ctx.WithGasMeter(storetypes.NewInfiniteGasMeter()) // avoid race conditions ch <- module.ExportGenesis(ctx, cdc) }(module, channels[moduleName]) } diff --git a/types/module/module_test.go b/types/module/module_test.go index 94c0a54280d7..59787b578fc9 100644 --- a/types/module/module_test.go +++ b/types/module/module_test.go @@ -1,10 +1,13 @@ package module_test import ( + "context" "encoding/json" "errors" + "io" "testing" + "cosmossdk.io/core/appmodule" "github.com/golang/mock/gomock" "github.com/spf13/cobra" "github.com/stretchr/testify/require" @@ -28,30 +31,51 @@ func TestBasicManager(t *testing.T) { interfaceRegistry := types.NewInterfaceRegistry() cdc := codec.NewProtoCodec(interfaceRegistry) - wantDefaultGenesis := map[string]json.RawMessage{"mockAppModuleBasic1": json.RawMessage(``)} + // Test with a legacy module, a mock core module that doesn't return anything, + // and a core module defined in this file + expDefaultGenesis := map[string]json.RawMessage{ + "mockAppModuleBasic1": json.RawMessage(``), + "mockCoreAppModule2": json.RawMessage(`null`), + "mockCoreAppModule3": json.RawMessage(`{ + "someField": "someKey" +}`), + } + // legacy module mockAppModuleBasic1 := mock.NewMockAppModuleWithAllExtensions(mockCtrl) - mockAppModuleBasic1.EXPECT().Name().AnyTimes().Return("mockAppModuleBasic1") mockAppModuleBasic1.EXPECT().DefaultGenesis(gomock.Eq(cdc)).Times(1).Return(json.RawMessage(``)) - mockAppModuleBasic1.EXPECT().ValidateGenesis(gomock.Eq(cdc), gomock.Eq(nil), gomock.Eq(wantDefaultGenesis["mockAppModuleBasic1"])).Times(1).Return(errFoo) + // Allow ValidateGenesis to be called any times because other module can fail before this one is called. + mockAppModuleBasic1.EXPECT().ValidateGenesis(gomock.Eq(cdc), gomock.Eq(nil), gomock.Eq(expDefaultGenesis["mockAppModuleBasic1"])).AnyTimes().Return(nil) mockAppModuleBasic1.EXPECT().RegisterLegacyAminoCodec(gomock.Eq(legacyAmino)).Times(1) mockAppModuleBasic1.EXPECT().RegisterInterfaces(gomock.Eq(interfaceRegistry)).Times(1) mockAppModuleBasic1.EXPECT().GetTxCmd().Times(1).Return(nil) mockAppModuleBasic1.EXPECT().GetQueryCmd().Times(1).Return(nil) - mm := module.NewBasicManager(mockAppModuleBasic1) - require.Equal(t, mm["mockAppModuleBasic1"], mockAppModuleBasic1) + // mock core API module + mockCoreAppModule2 := mock.NewMockCoreAppModule(mockCtrl) + mockCoreAppModule2.EXPECT().DefaultGenesis(gomock.Any()).AnyTimes().Return(nil) + mockCoreAppModule2.EXPECT().ValidateGenesis(gomock.Any()).AnyTimes().Return(nil) + mockAppModule2 := module.CoreAppModuleBasicAdaptor("mockCoreAppModule2", mockCoreAppModule2) + + // mock core API module (but all methods are implemented) + mockCoreAppModule3 := module.CoreAppModuleBasicAdaptor("mockCoreAppModule3", MockCoreAppModule{}) + + mm := module.NewBasicManager(mockAppModuleBasic1, mockAppModule2, mockCoreAppModule3) + + require.Equal(t, mockAppModuleBasic1, mm["mockAppModuleBasic1"]) + require.Equal(t, mockAppModule2, mm["mockCoreAppModule2"]) + require.Equal(t, mockCoreAppModule3, mm["mockCoreAppModule3"]) mm.RegisterLegacyAminoCodec(legacyAmino) mm.RegisterInterfaces(interfaceRegistry) - require.Equal(t, wantDefaultGenesis, mm.DefaultGenesis(cdc)) + require.Equal(t, expDefaultGenesis, mm.DefaultGenesis(cdc)) var data map[string]string require.Equal(t, map[string]string(nil), data) - require.True(t, errors.Is(errFoo, mm.ValidateGenesis(cdc, nil, wantDefaultGenesis))) + require.ErrorIs(t, mm.ValidateGenesis(cdc, nil, expDefaultGenesis), errFoo) mockCmd := &cobra.Command{Use: "root"} mm.AddTxCommands(mockCmd) @@ -59,7 +83,7 @@ func TestBasicManager(t *testing.T) { mm.AddQueryCommands(mockCmd) // validate genesis returns nil - require.Nil(t, module.NewBasicManager().ValidateGenesis(cdc, nil, wantDefaultGenesis)) + require.Nil(t, module.NewBasicManager().ValidateGenesis(cdc, nil, expDefaultGenesis)) } func TestGenesisOnlyAppModule(t *testing.T) { @@ -79,28 +103,29 @@ func TestManagerOrderSetters(t *testing.T) { t.Cleanup(mockCtrl.Finish) mockAppModule1 := mock.NewMockAppModule(mockCtrl) mockAppModule2 := mock.NewMockAppModule(mockCtrl) + mockAppModule3 := mock.NewMockCoreAppModule(mockCtrl) mockAppModule1.EXPECT().Name().Times(2).Return("module1") mockAppModule2.EXPECT().Name().Times(2).Return("module2") - mm := module.NewManager(mockAppModule1, mockAppModule2) + mm := module.NewManager(mockAppModule1, mockAppModule2, module.CoreAppModuleBasicAdaptor("module3", mockAppModule3)) require.NotNil(t, mm) - require.Equal(t, 2, len(mm.Modules)) + require.Equal(t, 3, len(mm.Modules)) - require.Equal(t, []string{"module1", "module2"}, mm.OrderInitGenesis) - mm.SetOrderInitGenesis("module2", "module1") - require.Equal(t, []string{"module2", "module1"}, mm.OrderInitGenesis) + require.Equal(t, []string{"module1", "module2", "module3"}, mm.OrderInitGenesis) + mm.SetOrderInitGenesis("module2", "module1", "module3") + require.Equal(t, []string{"module2", "module1", "module3"}, mm.OrderInitGenesis) - require.Equal(t, []string{"module1", "module2"}, mm.OrderExportGenesis) - mm.SetOrderExportGenesis("module2", "module1") - require.Equal(t, []string{"module2", "module1"}, mm.OrderExportGenesis) + require.Equal(t, []string{"module1", "module2", "module3"}, mm.OrderExportGenesis) + mm.SetOrderExportGenesis("module2", "module1", "module3") + require.Equal(t, []string{"module2", "module1", "module3"}, mm.OrderExportGenesis) - require.Equal(t, []string{"module1", "module2"}, mm.OrderBeginBlockers) - mm.SetOrderBeginBlockers("module2", "module1") - require.Equal(t, []string{"module2", "module1"}, mm.OrderBeginBlockers) + require.Equal(t, []string{"module1", "module2", "module3"}, mm.OrderBeginBlockers) + mm.SetOrderBeginBlockers("module2", "module1", "module3") + require.Equal(t, []string{"module2", "module1", "module3"}, mm.OrderBeginBlockers) - require.Equal(t, []string{"module1", "module2"}, mm.OrderEndBlockers) - mm.SetOrderEndBlockers("module2", "module1") - require.Equal(t, []string{"module2", "module1"}, mm.OrderEndBlockers) + require.Equal(t, []string{"module1", "module2", "module3"}, mm.OrderEndBlockers) + mm.SetOrderEndBlockers("module2", "module1", "module3") + require.Equal(t, []string{"module2", "module1", "module3"}, mm.OrderEndBlockers) } func TestManager_RegisterInvariants(t *testing.T) { @@ -109,11 +134,13 @@ func TestManager_RegisterInvariants(t *testing.T) { mockAppModule1 := mock.NewMockAppModuleWithAllExtensions(mockCtrl) mockAppModule2 := mock.NewMockAppModuleWithAllExtensions(mockCtrl) + mockAppModule3 := mock.NewMockCoreAppModule(mockCtrl) mockAppModule1.EXPECT().Name().Times(2).Return("module1") mockAppModule2.EXPECT().Name().Times(2).Return("module2") - mm := module.NewManager(mockAppModule1, mockAppModule2) + // TODO: This is not working for Core API modules yet + mm := module.NewManager(mockAppModule1, mockAppModule2, module.CoreAppModuleBasicAdaptor("mockAppModule3", mockAppModule3)) require.NotNil(t, mm) - require.Equal(t, 2, len(mm.Modules)) + require.Equal(t, 3, len(mm.Modules)) // test RegisterInvariants mockInvariantRegistry := mock.NewMockInvariantRegistry(mockCtrl) @@ -128,11 +155,13 @@ func TestManager_RegisterQueryServices(t *testing.T) { mockAppModule1 := mock.NewMockAppModuleWithAllExtensions(mockCtrl) mockAppModule2 := mock.NewMockAppModuleWithAllExtensions(mockCtrl) + mockAppModule3 := mock.NewMockCoreAppModule(mockCtrl) mockAppModule1.EXPECT().Name().Times(2).Return("module1") mockAppModule2.EXPECT().Name().Times(2).Return("module2") - mm := module.NewManager(mockAppModule1, mockAppModule2) + // TODO: This is not working for Core API modules yet + mm := module.NewManager(mockAppModule1, mockAppModule2, module.CoreAppModuleBasicAdaptor("mockAppModule3", mockAppModule3)) require.NotNil(t, mm) - require.Equal(t, 2, len(mm.Modules)) + require.Equal(t, 3, len(mm.Modules)) msgRouter := mock.NewMockServer(mockCtrl) queryRouter := mock.NewMockServer(mockCtrl) @@ -151,11 +180,12 @@ func TestManager_InitGenesis(t *testing.T) { mockAppModule1 := mock.NewMockAppModuleWithAllExtensions(mockCtrl) mockAppModule2 := mock.NewMockAppModuleWithAllExtensions(mockCtrl) + mockAppModule3 := mock.NewMockCoreAppModule(mockCtrl) mockAppModule1.EXPECT().Name().Times(2).Return("module1") mockAppModule2.EXPECT().Name().Times(2).Return("module2") - mm := module.NewManager(mockAppModule1, mockAppModule2) + mm := module.NewManager(mockAppModule1, mockAppModule2, module.CoreAppModuleBasicAdaptor("module3", mockAppModule3)) require.NotNil(t, mm) - require.Equal(t, 2, len(mm.Modules)) + require.Equal(t, 3, len(mm.Modules)) ctx := sdk.NewContext(nil, tmproto.Header{}, false, log.NewNopLogger()) interfaceRegistry := types.NewInterfaceRegistry() @@ -170,10 +200,19 @@ func TestManager_InitGenesis(t *testing.T) { genesisData = map[string]json.RawMessage{ "module1": json.RawMessage(`{"key": "value"}`), "module2": json.RawMessage(`{"key": "value"}`), + "module3": json.RawMessage(`{"key": "value"}`), } + + // panic because more than one module returns validator set updates mockAppModule1.EXPECT().InitGenesis(gomock.Eq(ctx), gomock.Eq(cdc), gomock.Eq(genesisData["module1"])).Times(1).Return([]abci.ValidatorUpdate{{}}) mockAppModule2.EXPECT().InitGenesis(gomock.Eq(ctx), gomock.Eq(cdc), gomock.Eq(genesisData["module2"])).Times(1).Return([]abci.ValidatorUpdate{{}}) require.Panics(t, func() { mm.InitGenesis(ctx, cdc, genesisData) }) + + // happy path + mockAppModule1.EXPECT().InitGenesis(gomock.Eq(ctx), gomock.Eq(cdc), gomock.Eq(genesisData["module1"])).Times(1).Return([]abci.ValidatorUpdate{{}}) + mockAppModule2.EXPECT().InitGenesis(gomock.Eq(ctx), gomock.Eq(cdc), gomock.Eq(genesisData["module2"])).Times(1).Return([]abci.ValidatorUpdate{}) + mockAppModule3.EXPECT().InitGenesis(gomock.Eq(ctx), gomock.Any()).Times(1).Return(nil) + mm.InitGenesis(ctx, cdc, genesisData) } func TestManager_ExportGenesis(t *testing.T) { @@ -182,11 +221,12 @@ func TestManager_ExportGenesis(t *testing.T) { mockAppModule1 := mock.NewMockAppModuleWithAllExtensions(mockCtrl) mockAppModule2 := mock.NewMockAppModuleWithAllExtensions(mockCtrl) + mockCoreAppModule := MockCoreAppModule{} mockAppModule1.EXPECT().Name().Times(2).Return("module1") mockAppModule2.EXPECT().Name().Times(2).Return("module2") - mm := module.NewManager(mockAppModule1, mockAppModule2) + mm := module.NewManager(mockAppModule1, mockAppModule2, module.CoreAppModuleBasicAdaptor("mockCoreAppModule", mockCoreAppModule)) require.NotNil(t, mm) - require.Equal(t, 2, len(mm.Modules)) + require.Equal(t, 3, len(mm.Modules)) ctx := sdk.NewContext(nil, tmproto.Header{}, false, log.NewNopLogger()) interfaceRegistry := types.NewInterfaceRegistry() @@ -197,7 +237,11 @@ func TestManager_ExportGenesis(t *testing.T) { want := map[string]json.RawMessage{ "module1": json.RawMessage(`{"key1": "value1"}`), "module2": json.RawMessage(`{"key2": "value2"}`), + "mockCoreAppModule": json.RawMessage(`{ + "someField": "someKey" +}`), } + require.Equal(t, want, mm.ExportGenesis(ctx, cdc)) require.Equal(t, want, mm.ExportGenesisForModules(ctx, cdc, []string{})) require.Equal(t, map[string]json.RawMessage{"module1": json.RawMessage(`{"key1": "value1"}`)}, mm.ExportGenesisForModules(ctx, cdc, []string{"module1"})) @@ -251,3 +295,151 @@ func TestManager_EndBlock(t *testing.T) { mockAppModule2.EXPECT().EndBlock(gomock.Any(), gomock.Eq(req)).Times(1).Return([]abci.ValidatorUpdate{{}}) require.Panics(t, func() { mm.EndBlock(sdk.Context{}, req) }) } + +// Core API exclusive tests +func TestCoreAPIManager(t *testing.T) { + mockCtrl := gomock.NewController(t) + module1 := mock.NewMockCoreAppModule(mockCtrl) + module2 := MockCoreAppModule{} + mm := module.NewManagerFromMap(map[string]appmodule.AppModule{ + "module1": module1, + "module2": module2, + }) + + require.NotNil(t, mm) + require.Equal(t, 2, len(mm.Modules)) + require.Equal(t, module1, mm.Modules["module1"]) + require.Equal(t, module2, mm.Modules["module2"]) +} + +func TestCoreAPIManager_InitGenesis(t *testing.T) { + mockCtrl := gomock.NewController(t) + t.Cleanup(mockCtrl.Finish) + + mockAppModule1 := mock.NewMockCoreAppModule(mockCtrl) + mm := module.NewManagerFromMap(map[string]appmodule.AppModule{"module1": mockAppModule1}) + require.NotNil(t, mm) + require.Equal(t, 1, len(mm.Modules)) + + ctx := sdk.NewContext(nil, tmproto.Header{}, false, log.NewNopLogger()) + interfaceRegistry := types.NewInterfaceRegistry() + cdc := codec.NewProtoCodec(interfaceRegistry) + genesisData := map[string]json.RawMessage{"module1": json.RawMessage(`{"key": "value"}`)} + + // this should panic since the validator set is empty even after init genesis + mockAppModule1.EXPECT().InitGenesis(gomock.Eq(ctx), gomock.Any()).Times(1).Return(nil) + require.Panics(t, func() { mm.InitGenesis(ctx, cdc, genesisData) }) + + // TODO: add happy path test. We are not returning any validator updates +} + +func TestCoreAPIManager_ExportGenesis(t *testing.T) { + mockAppModule1 := MockCoreAppModule{} + mockAppModule2 := MockCoreAppModule{} + mm := module.NewManagerFromMap(map[string]appmodule.AppModule{ + "module1": mockAppModule1, + "module2": mockAppModule2, + }) + require.NotNil(t, mm) + require.Equal(t, 2, len(mm.Modules)) + + ctx := sdk.NewContext(nil, tmproto.Header{}, false, log.NewNopLogger()) + interfaceRegistry := types.NewInterfaceRegistry() + cdc := codec.NewProtoCodec(interfaceRegistry) + want := map[string]json.RawMessage{ + "module1": json.RawMessage(`{ + "someField": "someKey" +}`), + "module2": json.RawMessage(`{ + "someField": "someKey" +}`), + } + + require.Equal(t, want, mm.ExportGenesis(ctx, cdc)) + require.Equal(t, want, mm.ExportGenesisForModules(ctx, cdc, []string{})) + require.Equal(t, map[string]json.RawMessage{"module1": want["module1"]}, mm.ExportGenesisForModules(ctx, cdc, []string{"module1"})) + require.NotEqual(t, map[string]json.RawMessage{"module1": want["module1"]}, mm.ExportGenesisForModules(ctx, cdc, []string{"module2"})) + + require.Panics(t, func() { + mm.ExportGenesisForModules(ctx, cdc, []string{"module1", "modulefoo"}) + }) +} + +func TestCoreAPIManagerOrderSetters(t *testing.T) { + mockCtrl := gomock.NewController(t) + t.Cleanup(mockCtrl.Finish) + mockAppModule1 := mock.NewMockCoreAppModule(mockCtrl) + mockAppModule2 := mock.NewMockCoreAppModule(mockCtrl) + mockAppModule3 := mock.NewMockCoreAppModule(mockCtrl) + + mm := module.NewManagerFromMap( + map[string]appmodule.AppModule{ + "module1": mockAppModule1, + "module2": mockAppModule2, + "module3": mockAppModule3, + }) + require.NotNil(t, mm) + require.Equal(t, 3, len(mm.Modules)) + + require.Equal(t, []string{"module1", "module2", "module3"}, mm.OrderInitGenesis) + mm.SetOrderInitGenesis("module2", "module1", "module3") + require.Equal(t, []string{"module2", "module1", "module3"}, mm.OrderInitGenesis) + + require.Equal(t, []string{"module1", "module2", "module3"}, mm.OrderExportGenesis) + mm.SetOrderExportGenesis("module2", "module1", "module3") + require.Equal(t, []string{"module2", "module1", "module3"}, mm.OrderExportGenesis) + + require.Equal(t, []string{"module1", "module2", "module3"}, mm.OrderBeginBlockers) + mm.SetOrderBeginBlockers("module2", "module1", "module3") + require.Equal(t, []string{"module2", "module1", "module3"}, mm.OrderBeginBlockers) + + require.Equal(t, []string{"module1", "module2", "module3"}, mm.OrderEndBlockers) + mm.SetOrderEndBlockers("module2", "module1", "module3") + require.Equal(t, []string{"module2", "module1", "module3"}, mm.OrderEndBlockers) +} + +// MockCoreAppModule allows us to test functions like DefaultGenesis +type MockCoreAppModule struct{} + +func (MockCoreAppModule) IsOnePerModuleType() {} +func (MockCoreAppModule) IsAppModule() {} +func (MockCoreAppModule) DefaultGenesis(target appmodule.GenesisTarget) error { + someFieldWriter, err := target("someField") + if err != nil { + return err + } + someFieldWriter.Write([]byte(`"someKey"`)) + return someFieldWriter.Close() +} + +func (MockCoreAppModule) ValidateGenesis(src appmodule.GenesisSource) error { + rdr, err := src("someField") + if err != nil { + return err + } + data, err := io.ReadAll(rdr) + if err != nil { + return err + } + + // this check will always fail, but it's just an example + if string(data) != `"dummy validation"` { + return errFoo + } + + return nil +} +func (MockCoreAppModule) InitGenesis(context.Context, appmodule.GenesisSource) error { return nil } +func (MockCoreAppModule) ExportGenesis(ctx context.Context, target appmodule.GenesisTarget) error { + wrt, err := target("someField") + if err != nil { + return err + } + wrt.Write([]byte(`"someKey"`)) + return wrt.Close() +} + +var ( + _ appmodule.AppModule = MockCoreAppModule{} + _ appmodule.HasGenesis = MockCoreAppModule{} +)