InitGenesis in migrations when fromVersion==0 (#9007)
* InitGenesis in migrations when fromVersion==0 * Add test * Fix test * Fix comment * cdc=>codec * Don't break Configurator * Remove method * Add comments * Add more comments * Update types/module/module.go Co-authored-by: technicallyty <48813565+technicallyty@users.noreply.github.com> * Update types/module/module.go Co-authored-by: technicallyty <48813565+technicallyty@users.noreply.github.com> * Update types/module/module.go Co-authored-by: technicallyty <48813565+technicallyty@users.noreply.github.com> Co-authored-by: technicallyty <48813565+technicallyty@users.noreply.github.com>
This commit is contained in:
parent
e3a0148bf6
commit
5d13b1fc63
|
@ -339,7 +339,7 @@ func NewSimApp(
|
||||||
|
|
||||||
app.mm.RegisterInvariants(&app.CrisisKeeper)
|
app.mm.RegisterInvariants(&app.CrisisKeeper)
|
||||||
app.mm.RegisterRoutes(app.Router(), app.QueryRouter(), encodingConfig.Amino)
|
app.mm.RegisterRoutes(app.Router(), app.QueryRouter(), encodingConfig.Amino)
|
||||||
app.configurator = module.NewConfigurator(app.MsgServiceRouter(), app.GRPCQueryRouter())
|
app.configurator = module.NewConfigurator(app.appCodec, app.MsgServiceRouter(), app.GRPCQueryRouter())
|
||||||
app.mm.RegisterServices(app.configurator)
|
app.mm.RegisterServices(app.configurator)
|
||||||
|
|
||||||
// add test gRPC service for testing gRPC queries in isolation
|
// add test gRPC service for testing gRPC queries in isolation
|
||||||
|
@ -536,22 +536,6 @@ func (app *SimApp) RegisterTendermintService(clientCtx client.Context) {
|
||||||
tmservice.RegisterTendermintService(app.BaseApp.GRPCQueryRouter(), clientCtx, app.interfaceRegistry)
|
tmservice.RegisterTendermintService(app.BaseApp.GRPCQueryRouter(), clientCtx, app.interfaceRegistry)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RunMigrations performs in-place store migrations for all modules. This
|
|
||||||
// function MUST be only called by x/upgrade UpgradeHandler.
|
|
||||||
//
|
|
||||||
// `migrateFromVersions` is a map of moduleName to fromVersion (unit64), where
|
|
||||||
// fromVersion denotes the version from which we should migrate the module, the
|
|
||||||
// target version being the module's latest ConsensusVersion.
|
|
||||||
//
|
|
||||||
// Example:
|
|
||||||
// cfg := module.NewConfigurator(...)
|
|
||||||
// app.UpgradeKeeper.SetUpgradeHandler("store-migration", func(ctx sdk.Context, plan upgradetypes.Plan, vm module.VersionMap) {
|
|
||||||
// return app.RunMigrations(ctx, vm)
|
|
||||||
// })
|
|
||||||
func (app *SimApp) RunMigrations(ctx sdk.Context, migrateFromVersions module.VersionMap) (module.VersionMap, error) {
|
|
||||||
return app.mm.RunMigrations(ctx, app.configurator, migrateFromVersions)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RegisterSwaggerAPI registers swagger route with API Server
|
// RegisterSwaggerAPI registers swagger route with API Server
|
||||||
func RegisterSwaggerAPI(ctx client.Context, rtr *mux.Router) {
|
func RegisterSwaggerAPI(ctx client.Context, rtr *mux.Router) {
|
||||||
statikFS, err := fs.New()
|
statikFS, err := fs.New()
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/golang/mock/gomock"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
abci "github.com/tendermint/tendermint/abci/types"
|
abci "github.com/tendermint/tendermint/abci/types"
|
||||||
"github.com/tendermint/tendermint/libs/log"
|
"github.com/tendermint/tendermint/libs/log"
|
||||||
|
@ -12,11 +13,13 @@ import (
|
||||||
dbm "github.com/tendermint/tm-db"
|
dbm "github.com/tendermint/tm-db"
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/baseapp"
|
"github.com/cosmos/cosmos-sdk/baseapp"
|
||||||
|
"github.com/cosmos/cosmos-sdk/tests/mocks"
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
"github.com/cosmos/cosmos-sdk/types/module"
|
"github.com/cosmos/cosmos-sdk/types/module"
|
||||||
"github.com/cosmos/cosmos-sdk/x/auth"
|
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||||
"github.com/cosmos/cosmos-sdk/x/auth/vesting"
|
"github.com/cosmos/cosmos-sdk/x/auth/vesting"
|
||||||
"github.com/cosmos/cosmos-sdk/x/authz"
|
"github.com/cosmos/cosmos-sdk/x/authz"
|
||||||
|
"github.com/cosmos/cosmos-sdk/x/bank"
|
||||||
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
|
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
|
||||||
"github.com/cosmos/cosmos-sdk/x/capability"
|
"github.com/cosmos/cosmos-sdk/x/capability"
|
||||||
"github.com/cosmos/cosmos-sdk/x/crisis"
|
"github.com/cosmos/cosmos-sdk/x/crisis"
|
||||||
|
@ -80,7 +83,7 @@ func TestRunMigrations(t *testing.T) {
|
||||||
bApp.SetCommitMultiStoreTracer(nil)
|
bApp.SetCommitMultiStoreTracer(nil)
|
||||||
bApp.SetInterfaceRegistry(encCfg.InterfaceRegistry)
|
bApp.SetInterfaceRegistry(encCfg.InterfaceRegistry)
|
||||||
app.BaseApp = bApp
|
app.BaseApp = bApp
|
||||||
app.configurator = module.NewConfigurator(app.MsgServiceRouter(), app.GRPCQueryRouter())
|
app.configurator = module.NewConfigurator(app.appCodec, app.MsgServiceRouter(), app.GRPCQueryRouter())
|
||||||
|
|
||||||
// We register all modules on the Configurator, except x/bank. x/bank will
|
// We register all modules on the Configurator, except x/bank. x/bank will
|
||||||
// serve as the test subject on which we run the migration tests.
|
// serve as the test subject on which we run the migration tests.
|
||||||
|
@ -160,8 +163,8 @@ func TestRunMigrations(t *testing.T) {
|
||||||
// Run migrations only for bank. That's why we put the initial
|
// Run migrations only for bank. That's why we put the initial
|
||||||
// version for bank as 1, and for all other modules, we put as
|
// version for bank as 1, and for all other modules, we put as
|
||||||
// their latest ConsensusVersion.
|
// their latest ConsensusVersion.
|
||||||
_, err = app.RunMigrations(
|
_, err = app.mm.RunMigrations(
|
||||||
app.NewContext(true, tmproto.Header{Height: app.LastBlockHeight()}),
|
app.NewContext(true, tmproto.Header{Height: app.LastBlockHeight()}), app.configurator,
|
||||||
module.VersionMap{
|
module.VersionMap{
|
||||||
"bank": 1,
|
"bank": 1,
|
||||||
"auth": auth.AppModule{}.ConsensusVersion(),
|
"auth": auth.AppModule{}.ConsensusVersion(),
|
||||||
|
@ -185,12 +188,58 @@ func TestRunMigrations(t *testing.T) {
|
||||||
require.EqualError(t, err, tc.expRunErrMsg)
|
require.EqualError(t, err, tc.expRunErrMsg)
|
||||||
} else {
|
} else {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
// Make sure bank's migration is called.
|
||||||
require.Equal(t, tc.expCalled, called)
|
require.Equal(t, tc.expCalled, called)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestInitGenesisOnMigration(t *testing.T) {
|
||||||
|
db := dbm.NewMemDB()
|
||||||
|
encCfg := MakeTestEncodingConfig()
|
||||||
|
logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout))
|
||||||
|
app := NewSimApp(logger, db, nil, true, map[int64]bool{}, DefaultNodeHome, 0, encCfg, EmptyAppOptions{})
|
||||||
|
ctx := app.NewContext(true, tmproto.Header{Height: app.LastBlockHeight()})
|
||||||
|
|
||||||
|
// Create a mock module. This module will serve as the new module we're
|
||||||
|
// adding during a migration.
|
||||||
|
mockCtrl := gomock.NewController(t)
|
||||||
|
t.Cleanup(mockCtrl.Finish)
|
||||||
|
mockModule := mocks.NewMockAppModule(mockCtrl)
|
||||||
|
mockDefaultGenesis := json.RawMessage(`{"key": "value"}`)
|
||||||
|
mockModule.EXPECT().DefaultGenesis(gomock.Eq(app.appCodec)).Times(1).Return(mockDefaultGenesis)
|
||||||
|
mockModule.EXPECT().InitGenesis(gomock.Eq(ctx), gomock.Eq(app.appCodec), gomock.Eq(mockDefaultGenesis)).Times(1).Return(nil)
|
||||||
|
|
||||||
|
app.mm.Modules["mock"] = mockModule
|
||||||
|
|
||||||
|
// Run migrations only for "mock" module. That's why we put the initial
|
||||||
|
// version for bank as 0 (to run its InitGenesis), and for all other
|
||||||
|
// modules, we put their latest ConsensusVersion to skip migrations.
|
||||||
|
_, err := app.mm.RunMigrations(ctx, app.configurator,
|
||||||
|
module.VersionMap{
|
||||||
|
"mock": 0,
|
||||||
|
"bank": bank.AppModule{}.ConsensusVersion(),
|
||||||
|
"auth": auth.AppModule{}.ConsensusVersion(),
|
||||||
|
"authz": authz.AppModule{}.ConsensusVersion(),
|
||||||
|
"staking": staking.AppModule{}.ConsensusVersion(),
|
||||||
|
"mint": mint.AppModule{}.ConsensusVersion(),
|
||||||
|
"distribution": distribution.AppModule{}.ConsensusVersion(),
|
||||||
|
"slashing": slashing.AppModule{}.ConsensusVersion(),
|
||||||
|
"gov": gov.AppModule{}.ConsensusVersion(),
|
||||||
|
"params": params.AppModule{}.ConsensusVersion(),
|
||||||
|
"upgrade": upgrade.AppModule{}.ConsensusVersion(),
|
||||||
|
"vesting": vesting.AppModule{}.ConsensusVersion(),
|
||||||
|
"feegrant": feegrant.AppModule{}.ConsensusVersion(),
|
||||||
|
"evidence": evidence.AppModule{}.ConsensusVersion(),
|
||||||
|
"crisis": crisis.AppModule{}.ConsensusVersion(),
|
||||||
|
"genutil": genutil.AppModule{}.ConsensusVersion(),
|
||||||
|
"capability": capability.AppModule{}.ConsensusVersion(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
func TestUpgradeStateOnGenesis(t *testing.T) {
|
func TestUpgradeStateOnGenesis(t *testing.T) {
|
||||||
encCfg := MakeTestEncodingConfig()
|
encCfg := MakeTestEncodingConfig()
|
||||||
db := dbm.NewMemDB()
|
db := dbm.NewMemDB()
|
||||||
|
|
|
@ -3,6 +3,7 @@ package module
|
||||||
import (
|
import (
|
||||||
"github.com/gogo/protobuf/grpc"
|
"github.com/gogo/protobuf/grpc"
|
||||||
|
|
||||||
|
"github.com/cosmos/cosmos-sdk/codec"
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
||||||
)
|
)
|
||||||
|
@ -34,6 +35,7 @@ type Configurator interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
type configurator struct {
|
type configurator struct {
|
||||||
|
cdc codec.Marshaler
|
||||||
msgServer grpc.Server
|
msgServer grpc.Server
|
||||||
queryServer grpc.Server
|
queryServer grpc.Server
|
||||||
|
|
||||||
|
@ -42,8 +44,9 @@ type configurator struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewConfigurator returns a new Configurator instance
|
// NewConfigurator returns a new Configurator instance
|
||||||
func NewConfigurator(msgServer grpc.Server, queryServer grpc.Server) Configurator {
|
func NewConfigurator(cdc codec.Marshaler, msgServer grpc.Server, queryServer grpc.Server) Configurator {
|
||||||
return configurator{
|
return configurator{
|
||||||
|
cdc: cdc,
|
||||||
msgServer: msgServer,
|
msgServer: msgServer,
|
||||||
queryServer: queryServer,
|
queryServer: queryServer,
|
||||||
migrations: map[string]map[uint64]MigrationHandler{},
|
migrations: map[string]map[uint64]MigrationHandler{},
|
||||||
|
|
|
@ -337,7 +337,51 @@ type MigrationHandler func(sdk.Context) error
|
||||||
// version from which we should perform the migration for each module.
|
// version from which we should perform the migration for each module.
|
||||||
type VersionMap map[string]uint64
|
type VersionMap map[string]uint64
|
||||||
|
|
||||||
// RunMigrations performs in-place store migrations for all modules.
|
// RunMigrations performs in-place store migrations for all modules. This
|
||||||
|
// function MUST be called insde an x/upgrade UpgradeHandler.
|
||||||
|
//
|
||||||
|
// Recall that in an upgrade handler, the `fromVM` VersionMap is retrieved from
|
||||||
|
// x/upgrade's store, and the function needs to return the target VersionMap
|
||||||
|
// that will in turn be persisted to the x/upgrade's store. In general,
|
||||||
|
// returning RunMigrations should be enough:
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
// cfg := module.NewConfigurator(...)
|
||||||
|
// app.UpgradeKeeper.SetUpgradeHandler("my-plan", func(ctx sdk.Context, plan upgradetypes.Plan, fromVM module.VersionMap) (module.VersionMap, error) {
|
||||||
|
// return app.mm.RunMigrations(ctx, cfg, fromVM)
|
||||||
|
// })
|
||||||
|
//
|
||||||
|
// Internally, RunMigrations will perform the following steps:
|
||||||
|
// - create an `updatedVM` VersionMap of module with their latest ConsensusVersion
|
||||||
|
// - make a diff of `fromVM` and `udpatedVM`, and for each module:
|
||||||
|
// - if the module's `fromVM` version is less than its `updatedVM` version,
|
||||||
|
// then run in-place store migrations for that module between those versions.
|
||||||
|
// - if the module's `fromVM` is 0 (which means that it's a new module,
|
||||||
|
// because it was not in the previous x/upgrade's store), then run
|
||||||
|
// `InitGenesis` on that module.
|
||||||
|
// - return the `updatedVM` to be persisted in the x/upgrade's store.
|
||||||
|
//
|
||||||
|
// As an app developer, if you wish to skip running InitGenesis for your new
|
||||||
|
// module "foo", you need to manually pass a `fromVM` argument to this function
|
||||||
|
// foo's module version set to its latest ConsensusVersion. That way, the diff
|
||||||
|
// between the function's `fromVM` and `udpatedVM` will be empty, hence not
|
||||||
|
// running anything for foo.
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
// cfg := module.NewConfigurator(...)
|
||||||
|
// app.UpgradeKeeper.SetUpgradeHandler("my-plan", func(ctx sdk.Context, plan upgradetypes.Plan, fromVM module.VersionMap) (module.VersionMap, error) {
|
||||||
|
// // Assume "foo" is a new module.
|
||||||
|
// // `fromVM` is fetched from existing x/upgrade store. Since foo didn't exist
|
||||||
|
// // before this upgrade, `fromVM["foo"] == 0`, and RunMigration will by default
|
||||||
|
// // run InitGenesis on foo.
|
||||||
|
// // To skip running foo's InitGenesis, you need set `fromVM`'s foo to its latest
|
||||||
|
// // consensus version:
|
||||||
|
// fromVM["foo"] = foo.AppModule{}.ConsensusVersion()
|
||||||
|
//
|
||||||
|
// return app.mm.RunMigrations(ctx, cfg, fromVM)
|
||||||
|
// })
|
||||||
|
//
|
||||||
|
// Please also refer to docs/core/upgrade.md for more information.
|
||||||
func (m Manager) RunMigrations(ctx sdk.Context, cfg Configurator, fromVM VersionMap) (VersionMap, error) {
|
func (m Manager) RunMigrations(ctx sdk.Context, cfg Configurator, fromVM VersionMap) (VersionMap, error) {
|
||||||
c, ok := cfg.(configurator)
|
c, ok := cfg.(configurator)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
@ -349,13 +393,34 @@ func (m Manager) RunMigrations(ctx sdk.Context, cfg Configurator, fromVM Version
|
||||||
fromVersion := fromVM[moduleName]
|
fromVersion := fromVM[moduleName]
|
||||||
toVersion := module.ConsensusVersion()
|
toVersion := module.ConsensusVersion()
|
||||||
|
|
||||||
// only run migrations when the from version is > 0
|
// Only run migrations when the fromVersion is > 0, or run InitGenesis
|
||||||
// from version will be 0 when a new module is added and migrations shouldn't be run in this case
|
// if fromVersion == 0.
|
||||||
|
//
|
||||||
|
// fromVersion will be 0 in two cases:
|
||||||
|
// 1. If a new module is added. In this case we run InitGenesis with an
|
||||||
|
// empty genesis state.
|
||||||
|
// 2. If the app developer is running in-place store migrations for the
|
||||||
|
// first time. In this case, it is the app developer's responsibility
|
||||||
|
// to set their module's fromVersions to a version that suits them.
|
||||||
if fromVersion > 0 {
|
if fromVersion > 0 {
|
||||||
err := c.runModuleMigrations(ctx, moduleName, fromVersion, toVersion)
|
err := c.runModuleMigrations(ctx, moduleName, fromVersion, toVersion)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
cfgtor, ok := cfg.(configurator)
|
||||||
|
if !ok {
|
||||||
|
// Currently, the only implementator of Configurator (the interface)
|
||||||
|
// is configurator (the struct).
|
||||||
|
return nil, sdkerrors.Wrapf(sdkerrors.ErrInvalidType, "expected %T, got %T", configurator{}, cfg)
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleValUpdates := module.InitGenesis(ctx, cfgtor.cdc, module.DefaultGenesis(cfgtor.cdc))
|
||||||
|
// The module manager assumes only one module will update the
|
||||||
|
// validator set, and that it will not be by a new module.
|
||||||
|
if len(moduleValUpdates) > 0 {
|
||||||
|
return nil, sdkerrors.Wrapf(sdkerrors.ErrLogic, "validator InitGenesis updates already set by a previous module")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updatedVM[moduleName] = toVersion
|
updatedVM[moduleName] = toVersion
|
||||||
|
|
|
@ -179,7 +179,9 @@ func TestManager_RegisterQueryServices(t *testing.T) {
|
||||||
|
|
||||||
msgRouter := mocks.NewMockServer(mockCtrl)
|
msgRouter := mocks.NewMockServer(mockCtrl)
|
||||||
queryRouter := mocks.NewMockServer(mockCtrl)
|
queryRouter := mocks.NewMockServer(mockCtrl)
|
||||||
cfg := module.NewConfigurator(msgRouter, queryRouter)
|
interfaceRegistry := types.NewInterfaceRegistry()
|
||||||
|
cdc := codec.NewProtoCodec(interfaceRegistry)
|
||||||
|
cfg := module.NewConfigurator(cdc, msgRouter, queryRouter)
|
||||||
mockAppModule1.EXPECT().RegisterServices(cfg).Times(1)
|
mockAppModule1.EXPECT().RegisterServices(cfg).Times(1)
|
||||||
mockAppModule2.EXPECT().RegisterServices(cfg).Times(1)
|
mockAppModule2.EXPECT().RegisterServices(cfg).Times(1)
|
||||||
|
|
||||||
|
|
|
@ -5,5 +5,22 @@ import (
|
||||||
"github.com/cosmos/cosmos-sdk/types/module"
|
"github.com/cosmos/cosmos-sdk/types/module"
|
||||||
)
|
)
|
||||||
|
|
||||||
// UpgradeHandler specifies the type of function that is called when an upgrade is applied
|
// UpgradeHandler specifies the type of function that is called when an upgrade
|
||||||
type UpgradeHandler func(ctx sdk.Context, plan Plan, vm module.VersionMap) (module.VersionMap, error)
|
// is applied.
|
||||||
|
//
|
||||||
|
// `fromVM` is a VersionMap of moduleName to fromVersion (unit64), where
|
||||||
|
// fromVersion denotes the version from which we should migrate the module, the
|
||||||
|
// target version being the module's latest version in the return VersionMap,
|
||||||
|
// let's call it `toVM`.
|
||||||
|
//
|
||||||
|
// `fromVM` is retrieved from x/upgrade's store, whereas `toVM` is chosen
|
||||||
|
// arbitrarily by the app developer (and persisted to x/upgrade's store right
|
||||||
|
// after the upgrade handler runs). In general, `toVM` should map all modules
|
||||||
|
// to their latest ConsensusVersion so that x/upgrade can track each module's
|
||||||
|
// latest ConsensusVersion; `fromVM` can be left as-is, but can also be
|
||||||
|
// modified inside the upgrade handler, e.g. to skip running InitGenesis or
|
||||||
|
// migrations for certain modules when calling the `module.Manager#RunMigrations`
|
||||||
|
// function.
|
||||||
|
//
|
||||||
|
// Please also refer to docs/core/upgrade.md for more information.
|
||||||
|
type UpgradeHandler func(ctx sdk.Context, plan Plan, fromVM module.VersionMap) (module.VersionMap, error)
|
||||||
|
|
Loading…
Reference in New Issue