Skip to content

Commit

Permalink
fix: Add MigrationModuleManager to handle migration of upgrade module…
Browse files Browse the repository at this point in the history
… before other modules (#16583)

Co-authored-by: Aleksandr Bezobchuk <alexanderbez@users.noreply.github.com>
Co-authored-by: Julien Robert <julien@rbrt.fr>
(cherry picked from commit 0c1f6fc)
  • Loading branch information
mmsqe authored and mergify[bot] committed Aug 11, 2023
1 parent 2f6573a commit 2878a3c
Show file tree
Hide file tree
Showing 11 changed files with 127 additions and 10 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
* (x/group) [#17146](https://github.com/cosmos/cosmos-sdk/pull/17146) Rename x/group legacy ORM package's error codespace from "orm" to "legacy_orm", preventing collisions with ORM's error codespace "orm".
* (x/bank) [#17170](https://github.com/cosmos/cosmos-sdk/pull/17170) Avoid empty spendable error message on send coins.
* (x/distribution) [#17236](https://github.com/cosmos/cosmos-sdk/pull/17236) Using "validateCommunityTax" in "Params.ValidateBasic", preventing panic when field "CommunityTax" is nil.
* (migrations) [#16583](https://github.com/cosmos/cosmos-sdk/pull/16583) Add `MigrationModuleManager` to handle migration of upgrade module before other modules, ensuring access to the updated context with consensus parameters within the same block that executes the migration.

### API Breaking Changes

Expand Down
2 changes: 1 addition & 1 deletion UPGRADING.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ In this section we describe the changes made in Cosmos SDK' SimApp.

#### Module Assertions

Previously, all modules were required to be set in `OrderBeginBlockers`, `OrderEndBlockers` and `OrderInitGenesis / OrderExportGenesis` in `app.go` / `app_config.go`. This is no longer the case, the assertion has been loosened to only require modules implementing, respectively, the `module.BeginBlockAppModule`, `module.EndBlockAppModule` and `module.HasGenesis` interfaces.
Previously, all modules were required to be set in `OrderBeginBlockers`, `OrderEndBlockers` and `OrderInitGenesis / OrderExportGenesis` in `app.go` / `app_config.go`. This is no longer the case, the assertion has been loosened to only require modules implementing, respectively, the `appmodule.HasBeginBlocker`, `appmodule.HasEndBlocker` and `appmodule.HasGenesis` / `module.HasGenesis` interfaces.

#### Module wiring

Expand Down
27 changes: 26 additions & 1 deletion baseapp/baseapp.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@ type (
StoreLoader func(ms storetypes.CommitMultiStore) error
)

// MigrationModuleManager is the interface that a migration module manager should implement to handle
// the execution of migration logic during the beginning of a block.
type MigrationModuleManager interface {
RunMigrationBeginBlock(ctx sdk.Context) bool
}

const (
execModeCheck execMode = iota // Check a transaction
execModeReCheck // Recheck a (pending) transaction after a commit
Expand Down Expand Up @@ -92,6 +98,9 @@ type BaseApp struct {
// manages snapshots, i.e. dumps of app state at certain intervals
snapshotManager *snapshots.Manager

// manages migrate module
migrationModuleManager MigrationModuleManager

// volatile states:
//
// - checkState is set on InitChain and reset on Commit
Expand Down Expand Up @@ -267,6 +276,11 @@ func (app *BaseApp) SetMsgServiceRouter(msgServiceRouter *MsgServiceRouter) {
app.msgServiceRouter = msgServiceRouter
}

// SetMigrationModuleManager sets the MigrationModuleManager of a BaseApp.
func (app *BaseApp) SetMigrationModuleManager(migrationModuleManager MigrationModuleManager) {
app.migrationModuleManager = migrationModuleManager
}

// MountStores mounts all IAVL or DB stores to the provided keys in the BaseApp
// multistore.
func (app *BaseApp) MountStores(keys ...storetypes.StoreKey) {
Expand Down Expand Up @@ -671,7 +685,18 @@ func (app *BaseApp) beginBlock(req *abci.RequestFinalizeBlock) (sdk.BeginBlock,
)

if app.beginBlocker != nil {
resp, err = app.beginBlocker(app.finalizeBlockState.ctx)
ctx := app.finalizeBlockState.ctx
if app.migrationModuleManager != nil && app.migrationModuleManager.RunMigrationBeginBlock(ctx) {
cp := ctx.ConsensusParams()
// Manager skips this step if Block is non-nil since upgrade module is expected to set this params
// and consensus parameters should not be overwritten.
if cp.Block == nil {
if cp = app.GetConsensusParams(ctx); cp.Block != nil {
ctx = ctx.WithConsensusParams(cp)
}
}
}
resp, err = app.beginBlocker(ctx)
if err != nil {
return resp, err
}
Expand Down
3 changes: 2 additions & 1 deletion docs/docs/building-modules/01-module-manager.md
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,8 @@ The module manager is used throughout the application whenever an action on a co
* `InitGenesis(ctx context.Context, cdc codec.JSONCodec, genesisData map[string]json.RawMessage)`: Calls the [`InitGenesis`](./08-genesis.md#initgenesis) function of each module when the application is first started, in the order defined in `OrderInitGenesis`. Returns an `abci.ResponseInitChain` to the underlying consensus engine, which can contain validator updates.
* `ExportGenesis(ctx context.Context, cdc codec.JSONCodec)`: Calls the [`ExportGenesis`](./08-genesis.md#exportgenesis) function of each module, in the order defined in `OrderExportGenesis`. The export constructs a genesis file from a previously existing state, and is mainly used when a hard-fork upgrade of the chain is required.
* `ExportGenesisForModules(ctx context.Context, cdc codec.JSONCodec, modulesToExport []string)`: Behaves the same as `ExportGenesis`, except takes a list of modules to export.
* `BeginBlock(ctx context.Context) error`: At the beginning of each block, this function is called from [`BaseApp`](../core/00-baseapp.md#beginblock) and, in turn, calls the [`BeginBlock`](./05-beginblock-endblock.md) function of each modules implementing the `BeginBlockAppModule` interface, in the order defined in `OrderBeginBlockers`. It creates a child [context](../core/02-context.md) with an event manager to aggregate [events](../core/08-events.md) emitted from all modules.
* `RunMigrationBeginBlock(ctx sdk.Context) bool`: At the beginning of each block, this function is called from [`BaseApp`](../core/00-baseapp.md#beginblock) and, in turn, calls the [`BeginBlock`](./05-beginblock-endblock.md) function of the upgrade module implementing the `HasBeginBlocker` interface. The function returns a boolean value indicating whether the migration was successfully executed or not.
* `BeginBlock(ctx context.Context) error`: At the beginning of each block, this function is called from [`BaseApp`](../core/00-baseapp.md#beginblock) and, in turn, calls the [`BeginBlock`](./05-beginblock-endblock.md) function of each non-upgrade modules implementing the `HasBeginBlocker` interface, in the order defined in `OrderBeginBlockers`. It creates a child [context](../core/02-context.md) with an event manager to aggregate [events](../core/08-events.md) emitted from non-upgrade modules.
* `EndBlock(ctx context.Context) error`: At the end of each block, this function is called from [`BaseApp`](../core/00-baseapp.md#endblock) and, in turn, calls the [`EndBlock`](./05-beginblock-endblock.md) function of each modules implementing the `HasEndBlocker` interface, in the order defined in `OrderEndBlockers`. It creates a child [context](../core/02-context.md) with an event manager to aggregate [events](../core/08-events.md) emitted from all modules. The function returns an `abci` which contains the aforementioned events, as well as validator set updates (if any).
* `EndBlock(context.Context) ([]abci.ValidatorUpdate, error)`: At the end of each block, this function is called from [`BaseApp`](../core/00-baseapp.md#endblock) and, in turn, calls the [`EndBlock`](./05-beginblock-endblock.md) function of each modules implementing the `HasEndBlocker` interface, in the order defined in `OrderEndBlockers`. It creates a child [context](../core/02-context.md) with an event manager to aggregate [events](../core/08-events.md) emitted from all modules. The function returns an `abci` which contains the aforementioned events, as well as validator set updates (if any).
* `Precommit(ctx context.Context)`: During [`Commit`](../core/00-baseapp.md#commit), this function is called from `BaseApp` immediately before the [`deliverState`](../core/00-baseapp.md#state-updates) is written to the underlying [`rootMultiStore`](../core/04-store.md#commitmultistore) and, in turn calls the `Precommit` function of each modules implementing the `HasPrecommit` interface, in the order defined in `OrderPrecommiters`. It creates a child [context](../core/02-context.md) where the underlying `CacheMultiStore` is that of the newly committed block's [`finalizeblockstate`](../core/00-baseapp.md#state-updates).
Expand Down
2 changes: 1 addition & 1 deletion docs/docs/core/00-baseapp.md
Original file line number Diff line number Diff line change
Expand Up @@ -455,7 +455,7 @@ https://github.com/cosmos/cosmos-sdk/blob/v0.50.0-alpha.0/baseapp/abci.go#L623
This function also resets the [main gas meter](../basics/04-gas-fees.md#main-gas-meter).

* Initialize the [block gas meter](../basics/04-gas-fees.md#block-gas-meter) with the `maxGas` limit. The `gas` consumed within the block cannot go above `maxGas`. This parameter is defined in the application's consensus parameters.
* Run the application's [`beginBlocker()`](../basics/00-app-anatomy.md#beginblocker-and-endblock), which mainly runs the [`BeginBlocker()`](../building-modules/05-beginblock-endblock.md#beginblock) method of each of the application's modules.
* Run the application's [`beginBlocker()`](../basics/00-app-anatomy.md#beginblocker-and-endblock), which mainly runs the [`BeginBlocker()`](../building-modules/05-beginblock-endblock.md#beginblock) method of each of the non-upgrade modules.
* Set the [`VoteInfos`](https://github.com/cometbft/cometbft/blob/v0.37.x/spec/abci/abci++_methods.md#voteinfo) of the application, i.e. the list of validators whose _precommit_ for the previous block was included by the proposer of the current block. This information is carried into the [`Context`](./02-context.md) so that it can be used during transaction execution and EndBlock.

#### Transaction Execution
Expand Down
1 change: 1 addition & 0 deletions runtime/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ func (a *AppBuilder) Build(db dbm.DB, traceStore io.Writer, baseAppOptions ...fu
bApp.SetVersion(version.Version)
bApp.SetInterfaceRegistry(a.app.interfaceRegistry)
bApp.MountStores(a.app.storeKeys...)
bApp.SetMigrationModuleManager(a.app.ModuleManager)

a.app.BaseApp = bApp
a.app.configurator = module.NewConfigurator(a.app.cdc, a.app.MsgServiceRouter(), a.app.GRPCQueryRouter())
Expand Down
1 change: 1 addition & 0 deletions simapp/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,7 @@ func NewSimApp(
consensus.NewAppModule(appCodec, app.ConsensusParamsKeeper),
circuit.NewAppModule(appCodec, app.CircuitKeeper),
)
bApp.SetMigrationModuleManager(app.ModuleManager)

// BasicModuleManager defines the module BasicManager is in charge of setting up basic,
// non-dependant module elements, such as codec registration and genesis verification.
Expand Down
37 changes: 37 additions & 0 deletions testutil/mock/types_mock_appmodule.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

33 changes: 27 additions & 6 deletions types/module/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,13 @@ type HasName interface {
Name() string
}

// UpgradeModule is the extension interface that upgrade module should implement to differentiate
// it from other modules, migration handler need ensure the upgrade module's migration is executed
// before the rest of the modules.
type UpgradeModule interface {
IsUpgradeModule()
}

// HasGenesisBasics is the legacy interface for stateless genesis methods.
type HasGenesisBasics interface {
DefaultGenesis(codec.JSONCodec) json.RawMessage
Expand Down Expand Up @@ -692,17 +699,31 @@ func (m Manager) RunMigrations(ctx context.Context, cfg Configurator, fromVM Ver
return updatedVM, nil
}

// BeginBlock performs begin block functionality for all modules. It creates a
// child context with an event manager to aggregate events emitted from all
// RunMigrationBeginBlock performs begin block functionality for upgrade module.
// It takes the current context as a parameter and returns a boolean value
// indicating whether the migration was successfully executed or not.
func (m *Manager) RunMigrationBeginBlock(ctx sdk.Context) bool {
for _, moduleName := range m.OrderBeginBlockers {
if mod, ok := m.Modules[moduleName].(appmodule.HasBeginBlocker); ok {
if _, ok := mod.(UpgradeModule); ok {
return mod.BeginBlock(ctx) == nil
}
}
}
return false
}

// BeginBlock performs begin block functionality for non-upgrade modules. It creates a
// child context with an event manager to aggregate events emitted from non-upgrade
// modules.
func (m *Manager) BeginBlock(ctx sdk.Context) (sdk.BeginBlock, error) {
ctx = ctx.WithEventManager(sdk.NewEventManager())

for _, moduleName := range m.OrderBeginBlockers {
if module, ok := m.Modules[moduleName].(appmodule.HasBeginBlocker); ok {
err := module.BeginBlock(ctx)
if err != nil {
return sdk.BeginBlock{}, err
if _, ok := module.(UpgradeModule); !ok {
if err := module.BeginBlock(ctx); err != nil {
return sdk.BeginBlock{}, err
}
}
}
}
Expand Down
27 changes: 27 additions & 0 deletions types/module/module_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,33 @@ func TestCoreAPIManagerOrderSetters(t *testing.T) {
require.Equal(t, []string{"module3", "module2", "module1"}, mm.OrderPrecommiters)
}

func TestCoreAPIManager_RunMigrationBeginBlock(t *testing.T) {
mockCtrl := gomock.NewController(t)
t.Cleanup(mockCtrl.Finish)

mockAppModule1 := mock.NewMockCoreAppModule(mockCtrl)
mockAppModule2 := mock.NewMockUpgradeModule(mockCtrl)
mm := module.NewManagerFromMap(map[string]appmodule.AppModule{
"module1": mockAppModule1,
"module2": mockAppModule2,
})
require.NotNil(t, mm)
require.Equal(t, 2, len(mm.Modules))

mockAppModule1.EXPECT().BeginBlock(gomock.Any()).Times(0)
mockAppModule2.EXPECT().BeginBlock(gomock.Any()).Times(1).Return(nil)
success := mm.RunMigrationBeginBlock(sdk.Context{})
require.Equal(t, true, success)

// test false
require.Equal(t, false, module.NewManager().RunMigrationBeginBlock(sdk.Context{}))

// test panic
mockAppModule2.EXPECT().BeginBlock(gomock.Any()).Times(1).Return(errors.New("some error"))
success = mm.RunMigrationBeginBlock(sdk.Context{})
require.Equal(t, false, success)
}

func TestCoreAPIManager_BeginBlock(t *testing.T) {
mockCtrl := gomock.NewController(t)
t.Cleanup(mockCtrl.Finish)
Expand Down
3 changes: 3 additions & 0 deletions x/upgrade/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,9 @@ func (am AppModule) BeginBlock(ctx context.Context) error {
return BeginBlocker(ctx, am.keeper)
}

// IsUpgradeModule implements the module.UpgradeModule interface.
func (am AppModuleBasic) IsUpgradeModule() {}

//
// App Wiring Setup
//
Expand Down

0 comments on commit 2878a3c

Please sign in to comment.