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

InitGenesis in migrations when fromVersion==0 #9007

Merged
merged 14 commits into from
Apr 2, 2021
18 changes: 1 addition & 17 deletions simapp/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,7 @@ func NewSimApp(

app.mm.RegisterInvariants(&app.CrisisKeeper)
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())
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After the Configurator/module.Manager refactor, this would be the only place that might need to be updated by app developers (or maybe even not). The Configurator interface itself is not modified for now, to allow more flexibility on our side.

app.mm.RegisterServices(app.configurator)

// add test gRPC service for testing gRPC queries in isolation
Expand Down Expand Up @@ -536,22 +536,6 @@ func (app *SimApp) RegisterTendermintService(clientCtx client.Context) {
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
func RegisterSwaggerAPI(ctx client.Context, rtr *mux.Router) {
statikFS, err := fs.New()
Expand Down
55 changes: 52 additions & 3 deletions simapp/app_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,21 @@ import (
"os"
"testing"

"github.com/golang/mock/gomock"
"github.com/stretchr/testify/require"
abci "github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/libs/log"
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
dbm "github.com/tendermint/tm-db"

"github.com/cosmos/cosmos-sdk/baseapp"
"github.com/cosmos/cosmos-sdk/tests/mocks"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/module"
"github.com/cosmos/cosmos-sdk/x/auth"
"github.com/cosmos/cosmos-sdk/x/auth/vesting"
"github.com/cosmos/cosmos-sdk/x/authz"
"github.com/cosmos/cosmos-sdk/x/bank"
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
"github.com/cosmos/cosmos-sdk/x/capability"
"github.com/cosmos/cosmos-sdk/x/crisis"
Expand Down Expand Up @@ -80,7 +83,7 @@ func TestRunMigrations(t *testing.T) {
bApp.SetCommitMultiStoreTracer(nil)
bApp.SetInterfaceRegistry(encCfg.InterfaceRegistry)
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
// serve as the test subject on which we run the migration tests.
Expand Down Expand Up @@ -160,8 +163,8 @@ func TestRunMigrations(t *testing.T) {
// 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
// their latest ConsensusVersion.
_, err = app.RunMigrations(
app.NewContext(true, tmproto.Header{Height: app.LastBlockHeight()}),
_, err = app.mm.RunMigrations(
app.NewContext(true, tmproto.Header{Height: app.LastBlockHeight()}), app.configurator,
module.VersionMap{
"bank": 1,
"auth": auth.AppModule{}.ConsensusVersion(),
Expand All @@ -185,12 +188,58 @@ func TestRunMigrations(t *testing.T) {
require.EqualError(t, err, tc.expRunErrMsg)
} else {
require.NoError(t, err)
// Make sure bank's migration is 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) {
encCfg := MakeTestEncodingConfig()
db := dbm.NewMemDB()
Expand Down
5 changes: 4 additions & 1 deletion types/module/configurator.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package module
import (
"github.com/gogo/protobuf/grpc"

"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
)
Expand Down Expand Up @@ -34,6 +35,7 @@ type Configurator interface {
}

type configurator struct {
cdc codec.Marshaler
msgServer grpc.Server
queryServer grpc.Server

Expand All @@ -42,8 +44,9 @@ type configurator struct {
}

// 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{
cdc: cdc,
msgServer: msgServer,
queryServer: queryServer,
migrations: map[string]map[uint64]MigrationHandler{},
Expand Down
71 changes: 68 additions & 3 deletions types/module/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -337,7 +337,51 @@ type MigrationHandler func(sdk.Context) error
// version from which we should perform the migration for each module.
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.
amaury1093 marked this conversation as resolved.
Show resolved Hide resolved
// - 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) {
c, ok := cfg.(configurator)
if !ok {
Expand All @@ -349,13 +393,34 @@ func (m Manager) RunMigrations(ctx sdk.Context, cfg Configurator, fromVM Version
fromVersion := fromVM[moduleName]
toVersion := module.ConsensusVersion()

// only run migrations when the from version is > 0
// from version will be 0 when a new module is added and migrations shouldn't be run in this case
// Only run migrations when the fromVersion is > 0, or run InitGenesis
// 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 {
err := c.runModuleMigrations(ctx, moduleName, fromVersion, toVersion)
if err != nil {
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
Expand Down
4 changes: 3 additions & 1 deletion types/module/module_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,9 @@ func TestManager_RegisterQueryServices(t *testing.T) {

msgRouter := 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)
mockAppModule2.EXPECT().RegisterServices(cfg).Times(1)

Expand Down
21 changes: 19 additions & 2 deletions x/upgrade/types/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,22 @@ import (
"github.com/cosmos/cosmos-sdk/types/module"
)

// UpgradeHandler specifies the type of function that is called when an upgrade is applied
type UpgradeHandler func(ctx sdk.Context, plan Plan, vm module.VersionMap) (module.VersionMap, error)
// UpgradeHandler specifies the type of function that is called when an upgrade
// 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)