Skip to content

Commit

Permalink
InitGenesis in migrations when fromVersion==0 (#9007)
Browse files Browse the repository at this point in the history
* 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>
  • Loading branch information
amaury1093 and technicallyty authored Apr 2, 2021
1 parent e3a0148 commit 5d13b1f
Show file tree
Hide file tree
Showing 6 changed files with 147 additions and 27 deletions.
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())
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.
// - 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)

0 comments on commit 5d13b1f

Please sign in to comment.