Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs(app/module): README #3483

Merged
merged 2 commits into from
May 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 20 additions & 24 deletions app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,12 +161,13 @@ type App struct {
ScopedTransferKeeper capabilitykeeper.ScopedKeeper // This keeper is public for test purposes
ScopedICAHostKeeper capabilitykeeper.ScopedKeeper // This keeper is public for test purposes

mm *module.Manager
manager *module.Manager
configurator module.Configurator
// upgradeHeightV2 is used as a coordination mechanism for the height-based
// upgrade from v1 to v2.
upgradeHeightV2 int64
// used to define what messages are accepted for a given app version
// MsgGateKeeper is used to define which messages are accepted for a given
// app version.
MsgGateKeeper *ante.MsgVersioningGateKeeper
}

Expand All @@ -187,20 +188,19 @@ func New(
baseAppOptions ...func(*baseapp.BaseApp),
) *App {
appCodec := encodingConfig.Codec
cdc := encodingConfig.Amino
interfaceRegistry := encodingConfig.InterfaceRegistry

bApp := baseapp.NewBaseApp(Name, logger, db, encodingConfig.TxConfig.TxDecoder(), baseAppOptions...)
bApp.SetCommitMultiStoreTracer(traceStore)
bApp.SetVersion(version.Version)
bApp.SetInterfaceRegistry(interfaceRegistry)
baseApp := baseapp.NewBaseApp(Name, logger, db, encodingConfig.TxConfig.TxDecoder(), baseAppOptions...)
baseApp.SetCommitMultiStoreTracer(traceStore)
baseApp.SetVersion(version.Version)
baseApp.SetInterfaceRegistry(interfaceRegistry)

keys := sdk.NewKVStoreKeys(allStoreKeys()...)
tkeys := sdk.NewTransientStoreKeys(paramstypes.TStoreKey)
memKeys := sdk.NewMemoryStoreKeys(capabilitytypes.MemStoreKey)

app := &App{
BaseApp: bApp,
BaseApp: baseApp,
appCodec: appCodec,
interfaceRegistry: interfaceRegistry,
txConfig: encodingConfig.TxConfig,
Expand All @@ -212,28 +212,24 @@ func New(
upgradeHeightV2: upgradeHeightV2,
}

app.ParamsKeeper = initParamsKeeper(appCodec, cdc, keys[paramstypes.StoreKey], tkeys[paramstypes.TStoreKey])
app.ParamsKeeper = initParamsKeeper(appCodec, encodingConfig.Amino, keys[paramstypes.StoreKey], tkeys[paramstypes.TStoreKey])

// set the BaseApp's parameter store
bApp.SetParamStore(app.ParamsKeeper.Subspace(baseapp.Paramspace).WithKeyTable(paramstypes.ConsensusParamsKeyTable()))
baseApp.SetParamStore(app.ParamsKeeper.Subspace(baseapp.Paramspace).WithKeyTable(paramstypes.ConsensusParamsKeyTable()))

// add capability keeper and ScopeToModule for ibc module
app.CapabilityKeeper = capabilitykeeper.NewKeeper(appCodec, keys[capabilitytypes.StoreKey], memKeys[capabilitytypes.MemStoreKey])

// grant capabilities for the ibc and ibc-transfer modules
app.ScopedIBCKeeper = app.CapabilityKeeper.ScopeToModule(ibchost.ModuleName)
app.ScopedTransferKeeper = app.CapabilityKeeper.ScopeToModule(ibctransfertypes.ModuleName)
app.ScopedICAHostKeeper = app.CapabilityKeeper.ScopeToModule(icahosttypes.SubModuleName)

// add keepers
app.AccountKeeper = authkeeper.NewAccountKeeper(
appCodec, keys[authtypes.StoreKey], app.GetSubspace(authtypes.ModuleName), authtypes.ProtoBaseAccount, maccPerms, sdk.GetConfig().GetBech32AccountAddrPrefix(),
)
app.BankKeeper = bankkeeper.NewBaseKeeper(
appCodec, keys[banktypes.StoreKey], app.AccountKeeper, app.GetSubspace(banktypes.ModuleName), app.ModuleAccountAddrs(),
)
app.AuthzKeeper = authzkeeper.NewKeeper(
keys[authzkeeper.StoreKey], appCodec, bApp.MsgServiceRouter(), app.AccountKeeper,
keys[authzkeeper.StoreKey], appCodec, baseApp.MsgServiceRouter(), app.AccountKeeper,
)
stakingKeeper := stakingkeeper.NewKeeper(
appCodec, keys[stakingtypes.StoreKey], app.AccountKeeper, app.BankKeeper, app.GetSubspace(stakingtypes.ModuleName),
Expand Down Expand Up @@ -364,7 +360,7 @@ func New(
app.BankKeeper,
&stakingKeeper,
govRouter,
bApp.MsgServiceRouter(),
baseApp.MsgServiceRouter(),
govtypes.DefaultConfig(),
)

Expand Down Expand Up @@ -397,9 +393,9 @@ func New(
app.QueryRouter().AddRoute(proof.TxInclusionQueryPath, proof.QueryTxInclusionProof)
app.QueryRouter().AddRoute(proof.ShareInclusionQueryPath, proof.QueryShareInclusionProof)

app.mm.RegisterInvariants(&app.CrisisKeeper)
app.manager.RegisterInvariants(&app.CrisisKeeper)
app.configurator = module.NewConfigurator(app.appCodec, app.MsgServiceRouter(), app.GRPCQueryRouter())
app.mm.RegisterServices(app.configurator)
app.manager.RegisterServices(app.configurator)

// extract the accepted message list from the configurator and create a gatekeeper
// which will be used both as the antehandler and as part of the circuit breaker in
Expand Down Expand Up @@ -448,12 +444,12 @@ func (app *App) Name() string { return app.BaseApp.Name() }

// BeginBlocker application updates every begin block
func (app *App) BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock) abci.ResponseBeginBlock {
return app.mm.BeginBlock(ctx, req)
return app.manager.BeginBlock(ctx, req)
}

// EndBlocker executes application updates at the end of every block.
func (app *App) EndBlocker(ctx sdk.Context, req abci.RequestEndBlock) abci.ResponseEndBlock {
res := app.mm.EndBlock(ctx, req)
res := app.manager.EndBlock(ctx, req)
currentVersion := app.AppVersion()
// For v1 only we upgrade using a agreed upon height known ahead of time
if currentVersion == v1 {
Expand Down Expand Up @@ -503,7 +499,7 @@ func (app *App) migrateCommitStore(fromVersion, toVersion uint64) (baseapp.Store
// migrateModules performs migrations on existing modules that have registered migrations
// between versions and initializes the state of new modules for the specified app version.
func (app *App) migrateModules(ctx sdk.Context, fromVersion, toVersion uint64) error {
return app.mm.RunMigrations(ctx, app.configurator, fromVersion, toVersion)
return app.manager.RunMigrations(ctx, app.configurator, fromVersion, toVersion)
}

// Info implements the ABCI interface. This method is a wrapper around baseapp's
Expand Down Expand Up @@ -583,8 +579,8 @@ func (app *App) InitChainer(ctx sdk.Context, req abci.RequestInitChain) abci.Res
panic(err)
}

app.UpgradeKeeper.SetModuleVersionMap(ctx, app.mm.GetVersionMap(req.ConsensusParams.Version.AppVersion))
return app.mm.InitGenesis(ctx, app.appCodec, genesisState, req.ConsensusParams.Version.AppVersion)
app.UpgradeKeeper.SetModuleVersionMap(ctx, app.manager.GetVersionMap(req.ConsensusParams.Version.AppVersion))
return app.manager.InitGenesis(ctx, app.appCodec, genesisState, req.ConsensusParams.Version.AppVersion)
}

// LoadHeight loads a particular height
Expand All @@ -595,7 +591,7 @@ func (app *App) LoadHeight(height int64) error {
// SupportedVersions returns all the state machines that the
// application supports
func (app *App) SupportedVersions() []uint64 {
return app.mm.SupportedVersions()
return app.manager.SupportedVersions()
}

// versionedKeys returns a map from moduleName to KV store key for the given app
Expand Down
2 changes: 1 addition & 1 deletion app/export.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ func (app *App) ExportAppStateAndValidators(forZeroHeight bool, jailAllowedAddrs
app.prepForZeroHeightGenesis(ctx, jailAllowedAddrs)
}

genState := app.mm.ExportGenesis(ctx, app.appCodec, app.AppVersion())
genState := app.manager.ExportGenesis(ctx, app.appCodec, app.AppVersion())
appState, err := json.MarshalIndent(genState, "", " ")
if err != nil {
return servertypes.ExportedApp{}, err
Expand Down
3 changes: 3 additions & 0 deletions app/module/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# app/module

This directory contains a module manager (`Manager`) and configurator (`Configurator`) that enables the application to add or remove modules during app version changes.
5 changes: 3 additions & 2 deletions app/module/configurator.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/cosmos/cosmos-sdk/types/module"
)

// Configurator implements the module.Configurator interface.
var _ module.Configurator = Configurator{}

// Configurator is a struct used at startup to register all the message and
Expand Down Expand Up @@ -60,12 +61,12 @@ func (c Configurator) GetAcceptedMessages() map[uint64]map[string]struct{} {
return c.acceptedMessages
}

// QueryServer implements the Configurator.QueryServer method
// QueryServer implements the Configurator.QueryServer method.
func (c Configurator) QueryServer() pbgrpc.Server {
return c.queryServer
}

// RegisterMigration implements the Configurator.RegisterMigration method
// RegisterMigration implements the Configurator.RegisterMigration method.
func (c Configurator) RegisterMigration(moduleName string, fromVersion uint64, handler module.MigrationHandler) error {
if fromVersion == 0 {
return sdkerrors.ErrInvalidVersion.Wrap("module migration versions should start at 1")
Expand Down
111 changes: 84 additions & 27 deletions app/module/configurator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,38 +13,95 @@ import (
"github.com/cosmos/cosmos-sdk/tests/mocks"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/tendermint/tendermint/libs/log"
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
dbm "github.com/tendermint/tm-db"
)

func TestConfiguratorRegistersAllMessageTypes(t *testing.T) {
mockCtrl := gomock.NewController(t)
t.Cleanup(mockCtrl.Finish)
mockServer := mocks.NewMockServer(mockCtrl)
mockServer.EXPECT().RegisterService(gomock.Any(), gomock.Any()).Times(2).Return()
cdc := encoding.MakeConfig(app.ModuleEncodingRegisters...)
configurator := module.NewConfigurator(cdc.Codec, mockServer, mockServer)

storeKey := sdk.NewKVStoreKey(signaltypes.StoreKey)

db := dbm.NewMemDB()
stateStore := store.NewCommitMultiStore(db)
stateStore.MountStoreWithDB(storeKey, storetypes.StoreTypeIAVL, db)
require.NoError(t, stateStore.LoadLatestVersion())

keeper := signal.NewKeeper(storeKey, nil)
upgradeModule := signal.NewAppModule(keeper)
mm, err := module.NewManager([]module.VersionedModule{
{Module: upgradeModule, FromVersion: 2, ToVersion: 2},
func TestConfigurator(t *testing.T) {
t.Run("registers all accepted messages", func(t *testing.T) {
mockCtrl := gomock.NewController(t)
t.Cleanup(mockCtrl.Finish)

mockServer := mocks.NewMockServer(mockCtrl)
mockServer.EXPECT().RegisterService(gomock.Any(), gomock.Any()).Times(2).Return()

config := encoding.MakeConfig(app.ModuleEncodingRegisters...)
configurator := module.NewConfigurator(config.Codec, mockServer, mockServer)
storeKey := sdk.NewKVStoreKey(signaltypes.StoreKey)

db := dbm.NewMemDB()
stateStore := store.NewCommitMultiStore(db)
stateStore.MountStoreWithDB(storeKey, storetypes.StoreTypeIAVL, db)
require.NoError(t, stateStore.LoadLatestVersion())

keeper := signal.NewKeeper(storeKey, nil)
require.NotNil(t, keeper)
upgradeModule := signal.NewAppModule(keeper)
manager, err := module.NewManager([]module.VersionedModule{
{Module: upgradeModule, FromVersion: 2, ToVersion: 2},
})
require.NoError(t, err)
require.NotNil(t, manager)

manager.RegisterServices(configurator)
acceptedMessages := configurator.GetAcceptedMessages()
assert.Equal(t, map[uint64]map[string]struct{}{
2: {
"/celestia.signal.v1.MsgSignalVersion": {},
"/celestia.signal.v1.MsgTryUpgrade": {},
},
}, acceptedMessages)
})
require.NoError(t, err)
require.NotNil(t, mm)

mm.RegisterServices(configurator)
acceptedMessages := configurator.GetAcceptedMessages()
require.Equal(t, map[uint64]map[string]struct{}{
2: {"/celestia.signal.v1.MsgSignalVersion": {}, "/celestia.signal.v1.MsgTryUpgrade": {}},
}, acceptedMessages)
t.Run("register migration", func(t *testing.T) {
mockCtrl := gomock.NewController(t)
t.Cleanup(mockCtrl.Finish)

mockAppModule1 := mocks.NewMockAppModule(mockCtrl)
mockAppModule2 := mocks.NewMockAppModule(mockCtrl)
mockAppModule3 := mocks.NewMockAppModule(mockCtrl)

require.NotNil(t, keeper)
mockAppModule1.EXPECT().Name().Return("testModule").AnyTimes()
mockAppModule2.EXPECT().Name().Return("testModule").AnyTimes()
mockAppModule3.EXPECT().Name().Return("differentModule").AnyTimes()
mockAppModule1.EXPECT().ConsensusVersion().Return(uint64(1)).AnyTimes()
mockAppModule2.EXPECT().ConsensusVersion().Return(uint64(2)).AnyTimes()
mockAppModule3.EXPECT().ConsensusVersion().Return(uint64(5)).AnyTimes()
mockAppModule3.EXPECT().InitGenesis(gomock.Any(), gomock.Any(), gomock.Any()).Times(1).Return(nil)
mockAppModule3.EXPECT().DefaultGenesis(gomock.Any()).Return(nil)

manager, err := module.NewManager([]module.VersionedModule{
// this is an existing module that gets updated in v2
{Module: mockAppModule1, FromVersion: 1, ToVersion: 1},
{Module: mockAppModule2, FromVersion: 2, ToVersion: 3},
// This is a new module that gets added in v2
{Module: mockAppModule3, FromVersion: 2, ToVersion: 2},
})
require.NoError(t, err)
require.NotNil(t, manager)

mockServer := mocks.NewMockServer(mockCtrl)
config := encoding.MakeConfig(app.ModuleEncodingRegisters...)

isCalled := false
configurator := module.NewConfigurator(config.Codec, mockServer, mockServer)
err = configurator.RegisterMigration("testModule", 1, func(_ sdk.Context) error {
isCalled = true
return nil
})
require.NoError(t, err)

err = manager.RunMigrations(sdk.NewContext(nil, tmproto.Header{}, false, log.NewNopLogger()), configurator, 1, 2)
require.NoError(t, err)
require.True(t, isCalled)

supportedVersions := manager.SupportedVersions()
require.Len(t, supportedVersions, 3)
require.Contains(t, supportedVersions, uint64(1))
require.Contains(t, supportedVersions, uint64(2))
require.Contains(t, supportedVersions, uint64(3))
})
}
26 changes: 14 additions & 12 deletions app/module/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@ import (
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
)

// Manager defines a module manager that provides the high level utility for managing and executing
// operations for a group of modules. This implemention maps the state machine version to different
// versions of the module. It also provides a way to run migrations between different versions of a
// module.
// Manager defines a module manager that provides the high level utility for
// managing and executing operations for a group of modules. This implementation
// was originally inspired by the module manager defined in Cosmos SDK but this
// implemention maps the state machine version to different versions of the
// module. It also provides a way to run migrations between different versions
// of a module.
type Manager struct {
// versionedModules is a map from app version -> module name -> module.
versionedModules map[uint64]map[string]sdkmodule.AppModule
Expand All @@ -39,7 +41,7 @@ type Manager struct {

// NewManager returns a new Manager object.
func NewManager(modules []VersionedModule) (*Manager, error) {
moduleMap := make(map[uint64]map[string]sdkmodule.AppModule)
versionedModules := make(map[uint64]map[string]sdkmodule.AppModule)
allModules := make([]sdkmodule.AppModule, len(modules))
modulesStr := make([]string, 0, len(modules))
uniqueModuleVersions := make(map[string]map[uint64][2]uint64)
Expand All @@ -53,13 +55,13 @@ func NewManager(modules []VersionedModule) (*Manager, error) {
return nil, sdkerrors.ErrLogic.Wrapf("FromVersion cannot be greater than ToVersion for module %s", module.Module.Name())
}
for version := module.FromVersion; version <= module.ToVersion; version++ {
if moduleMap[version] == nil {
moduleMap[version] = make(map[string]sdkmodule.AppModule)
if versionedModules[version] == nil {
versionedModules[version] = make(map[string]sdkmodule.AppModule)
}
if _, exists := moduleMap[version][name]; exists {
if _, exists := versionedModules[version][name]; exists {
return nil, sdkerrors.ErrLogic.Wrapf("Two different modules with domain %s are registered with the same version %d", name, version)
}
moduleMap[version][module.Module.Name()] = module.Module
versionedModules[version][module.Module.Name()] = module.Module
}
allModules[idx] = module.Module
modulesStr = append(modulesStr, name)
Expand All @@ -68,11 +70,11 @@ func NewManager(modules []VersionedModule) (*Manager, error) {
}
uniqueModuleVersions[name][moduleVersion] = [2]uint64{module.FromVersion, module.ToVersion}
}
firstVersion := slices.Min(getKeys(moduleMap))
lastVersion := slices.Max(getKeys(moduleMap))
firstVersion := slices.Min(getKeys(versionedModules))
lastVersion := slices.Max(getKeys(versionedModules))

m := &Manager{
versionedModules: moduleMap,
versionedModules: versionedModules,
uniqueModuleVersions: uniqueModuleVersions,
allModules: allModules,
firstVersion: firstVersion,
Expand Down
Loading
Loading