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

Implement in-place store migration #334

Merged
merged 14 commits into from
Nov 29, 2021
28 changes: 25 additions & 3 deletions app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ import (
ibctransfertypes "github.com/cosmos/ibc-go/modules/apps/transfer/types"
ibc "github.com/cosmos/ibc-go/modules/core"
ibcclient "github.com/cosmos/ibc-go/modules/core/02-client"
ibcconnectiontypes "github.com/cosmos/ibc-go/modules/core/03-connection/types"
porttypes "github.com/cosmos/ibc-go/modules/core/05-port/types"
ibchost "github.com/cosmos/ibc-go/modules/core/24-host"
ibckeeper "github.com/cosmos/ibc-go/modules/core/keeper"
Expand Down Expand Up @@ -111,7 +112,8 @@ import (

const (
// AppName specifies the global application name.
AppName = "Shentu"
AppName = "Shentu"
upgradeName = "Shentu-upgrade"

// DefaultKeyPass for certik node daemon.
DefaultKeyPass = "12345678"
Expand Down Expand Up @@ -212,7 +214,8 @@ type ShentuApp struct {
mm *module.Manager

// simulation manager
sm *module.SimulationManager
sm *module.SimulationManager
configurator module.Configurator
}

// NewShentuApp returns a reference to an initialized ShentuApp.
Expand Down Expand Up @@ -524,7 +527,9 @@ func NewShentuApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest
)

app.mm.RegisterRoutes(app.Router(), app.QueryRouter(), encodingConfig.Amino)
app.mm.RegisterServices(cfg)

app.configurator = module.NewConfigurator(app.appCodec, app.MsgServiceRouter(), app.GRPCQueryRouter())
app.mm.RegisterServices(app.configurator)

app.sm = module.NewSimulationManager(
auth.NewAppModule(appCodec, app.authKeeper, app.accountKeeper, app.bankKeeper, app.certKeeper, authsims.RandomGenesisAccounts),
Expand Down Expand Up @@ -570,6 +575,23 @@ func NewShentuApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest
app.SetAnteHandler(anteHandler)
app.SetEndBlocker(app.EndBlocker)

app.upgradeKeeper.SetUpgradeHandler(
upgradeName,
func(ctx sdk.Context, _ upgradetypes.Plan, _ module.VersionMap) (module.VersionMap, error) {
app.ibcKeeper.ConnectionKeeper.SetParams(ctx, ibcconnectiontypes.DefaultParams())

fromVM := make(map[string]uint64)
for moduleName := range app.mm.Modules {
fromVM[moduleName] = 1
}
// override versions for _new_ modules as to not skip InitGenesis
fromVM[sdkauthz.ModuleName] = 0
fromVM[sdkfeegrant.ModuleName] = 0

return app.mm.RunMigrations(ctx, app.configurator, fromVM)
},
)

if loadLatest {
if err := app.LoadLatestVersion(); err != nil {
tmos.Exit(err.Error())
Expand Down
12 changes: 1 addition & 11 deletions proto/shentu/gov/v1alpha1/gov.proto
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ message GenesisState {
// deposits defines all the deposits present at genesis.
repeated Deposit deposits = 2 [(gogoproto.castrepeated) = "Deposits", (gogoproto.nullable) = false];
// votes defines all the votes present at genesis.
repeated Vote votes = 3 [(gogoproto.castrepeated) = "Votes", (gogoproto.nullable) = false];
repeated cosmos.gov.v1beta1.Vote votes = 3 [(gogoproto.nullable) = false];
// proposals defines all the proposals present at genesis.
repeated Proposal proposals = 4 [(gogoproto.castrepeated) = "Proposals", (gogoproto.nullable) = false];
// params defines all the paramaters of related to deposit.
Expand Down Expand Up @@ -129,13 +129,3 @@ enum ProposalStatus {
// failed.
PROPOSAL_STATUS_FAILED = 6 [(gogoproto.enumvalue_customname) = "StatusFailed"];
}

// Vote defines a vote on a governance proposal.
// A Vote consists of a proposal ID, the voter, and the vote option.
message Vote {
option (gogoproto.goproto_stringer) = false;
option (gogoproto.equal) = false;

cosmos.gov.v1beta1.Vote deposit = 1 [(gogoproto.embed) = true];
string tx_hash = 2 [ (gogoproto.moretags) = "yaml:\"txhash\"" ];
}
4 changes: 2 additions & 2 deletions proto/shentu/gov/v1alpha1/query.proto
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ message QueryVoteRequest {
// QueryVoteResponse is the response type for the Query/Vote RPC method.
message QueryVoteResponse {
// vote defined the queried vote.
Vote vote = 1 [(gogoproto.nullable) = false];
cosmos.gov.v1beta1.Vote vote = 1 [(gogoproto.nullable) = false];
}

// QueryVotesRequest is the request type for the Query/Votes RPC method.
Expand All @@ -120,7 +120,7 @@ message QueryVotesRequest {
// QueryVotesResponse is the response type for the Query/Votes RPC method.
message QueryVotesResponse {
// votes defined the queried votes.
repeated Vote votes = 1 [(gogoproto.nullable) = false];
repeated cosmos.gov.v1beta1.Vote votes = 1 [(gogoproto.nullable) = false];

// pagination defines the pagination in the response.
cosmos.base.query.v1beta1.PageResponse pagination = 2;
Expand Down
167 changes: 167 additions & 0 deletions simapp/migration_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
package simapp

import (
"os"
"testing"

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/test-go/testify/require"

"github.com/cosmos/cosmos-sdk/baseapp"
"github.com/cosmos/cosmos-sdk/simapp"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/module"
"github.com/cosmos/cosmos-sdk/x/auth/vesting"
authzmodule "github.com/cosmos/cosmos-sdk/x/authz/module"
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
"github.com/cosmos/cosmos-sdk/x/capability"
"github.com/cosmos/cosmos-sdk/x/crisis"
"github.com/cosmos/cosmos-sdk/x/evidence"
feegrantmodule "github.com/cosmos/cosmos-sdk/x/feegrant/module"
"github.com/cosmos/cosmos-sdk/x/genutil"
sdkparams "github.com/cosmos/cosmos-sdk/x/params"
"github.com/cosmos/cosmos-sdk/x/upgrade"
"github.com/cosmos/ibc-go/modules/apps/transfer"

"github.com/certikfoundation/shentu/v2/x/auth"
"github.com/certikfoundation/shentu/v2/x/cert"
"github.com/certikfoundation/shentu/v2/x/cvm"
"github.com/certikfoundation/shentu/v2/x/distribution"
"github.com/certikfoundation/shentu/v2/x/gov"
"github.com/certikfoundation/shentu/v2/x/mint"
"github.com/certikfoundation/shentu/v2/x/oracle"
"github.com/certikfoundation/shentu/v2/x/shield"
"github.com/certikfoundation/shentu/v2/x/slashing"
"github.com/certikfoundation/shentu/v2/x/staking"
)

func TestRunMigrations(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, simapp.EmptyAppOptions{})

// Create a new baseapp and configurator for the purpose of this test.
bApp := baseapp.NewBaseApp(appName, logger, db, encCfg.TxConfig.TxDecoder())
bApp.SetCommitMultiStoreTracer(nil)
bApp.SetInterfaceRegistry(encCfg.InterfaceRegistry)
app.BaseApp = bApp
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.
//
// The loop below is the same as calling `RegisterServices` on
// ModuleManager, except that we skip x/bank.
for _, module := range app.mm.Modules {
if module.Name() == banktypes.ModuleName {
continue
}

module.RegisterServices(app.configurator)
}

// Initialize the chain
app.InitChain(abci.RequestInitChain{})
app.Commit()

testCases := []struct {
name string
moduleName string
forVersion uint64
expRegErr bool // errors while registering migration
expRegErrMsg string
expRunErr bool // errors while running migration
expRunErrMsg string
expCalled int
}{
{
"cannot register migration for version 0",
"bank", 0,
true, "module migration versions should start at 1: invalid version", false, "", 0,
},
{
"throws error on RunMigrations if no migration registered for bank",
"", 1,
false, "", true, "no migrations found for module bank: not found", 0,
},
{
"can register and run migration handler for x/bank",
"bank", 1,
false, "", false, "", 1,
},
{
"cannot register migration handler for same module & forVersion",
"bank", 1,
true, "another migration for module bank and version 1 already exists: internal logic error", false, "", 0,
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
var err error

// Since it's very hard to test actual in-place store migrations in
// tests (due to the difficulty of maintaining multiple versions of a
// module), we're just testing here that the migration logic is
// called.
called := 0

if tc.moduleName != "" {
// Register migration for module from version `forVersion` to `forVersion+1`.
err = app.configurator.RegisterMigration(tc.moduleName, tc.forVersion, func(sdk.Context) error {
called++

return nil
})

if tc.expRegErr {
require.EqualError(t, err, tc.expRegErrMsg)

return
}
}
require.NoError(t, err)

// 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.mm.RunMigrations(
app.NewContext(true, tmproto.Header{Height: app.LastBlockHeight()}), app.configurator,
module.VersionMap{
"bank": 1,
"cert": cert.AppModule{}.ConsensusVersion(),
"cvm": cvm.AppModule{}.ConsensusVersion(),
"oracle": oracle.AppModule{}.ConsensusVersion(),
"shield": shield.AppModule{}.ConsensusVersion(),
"auth": auth.AppModule{}.ConsensusVersion(),
"authz": authzmodule.AppModule{}.ConsensusVersion(),
"staking": staking.AppModule{}.ConsensusVersion(),
"mint": mint.AppModule{}.ConsensusVersion(),
"distribution": distribution.AppModule{}.ConsensusVersion(),
"slashing": slashing.AppModule{}.ConsensusVersion(),
"gov": gov.AppModule{}.ConsensusVersion(),
"params": sdkparams.AppModule{}.ConsensusVersion(),
"upgrade": upgrade.AppModule{}.ConsensusVersion(),
"vesting": vesting.AppModule{}.ConsensusVersion(),
"feegrant": feegrantmodule.AppModule{}.ConsensusVersion(),
"evidence": evidence.AppModule{}.ConsensusVersion(),
"crisis": crisis.AppModule{}.ConsensusVersion(),
"genutil": genutil.AppModule{}.ConsensusVersion(),
"capability": capability.AppModule{}.ConsensusVersion(),
"transfer": transfer.AppModule{}.ConsensusVersion(),
hacheigriega marked this conversation as resolved.
Show resolved Hide resolved
},
)
if tc.expRunErr {
require.EqualError(t, err, tc.expRunErrMsg)
} else {
require.NoError(t, err)
// Make sure bank's migration is called.
require.Equal(t, tc.expCalled, called)
}
})
}
}
7 changes: 5 additions & 2 deletions simapp/simapp.go
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,9 @@ type SimApp struct {

// simulation manager
sm *module.SimulationManager

// module configurator
configurator module.Configurator
}

// NewSimApp returns a reference to an initialized SimApp.
Expand Down Expand Up @@ -460,7 +463,6 @@ func NewSimApp(
// If evidence needs to be handled for the app, set routes in router here and seal
app.EvidenceKeeper = *evidenceKeeper

cfg := module.NewConfigurator(appCodec, app.MsgServiceRouter(), app.GRPCQueryRouter())
/**** Module Options ****/

// NOTE: we may consider parsing `appOpts` inside module constructors. For the moment
Expand Down Expand Up @@ -545,7 +547,8 @@ func NewSimApp(

app.mm.RegisterInvariants(&app.CrisisKeeper)
app.mm.RegisterRoutes(app.Router(), app.QueryRouter(), encodingConfig.Amino)
app.mm.RegisterServices(cfg)
app.configurator = module.NewConfigurator(app.appCodec, app.MsgServiceRouter(), app.GRPCQueryRouter())
app.mm.RegisterServices(app.configurator)

app.sm = module.NewSimulationManager(
auth.NewAppModule(appCodec, app.AuthKeeper, app.AccountKeeper, app.BankKeeper, app.CertKeeper, authsims.RandomGenesisAccounts),
Expand Down
44 changes: 44 additions & 0 deletions x/auth/keeper/migrations.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package keeper

import (
"github.com/gogo/protobuf/grpc"

sdk "github.com/cosmos/cosmos-sdk/types"
v043 "github.com/cosmos/cosmos-sdk/x/auth/legacy/v043"
sdktypes "github.com/cosmos/cosmos-sdk/x/auth/types"

"github.com/certikfoundation/shentu/v2/x/auth/types"
)

// Migrator is a struct for handling in-place store migrations.
type Migrator struct {
keeper types.AccountKeeper
queryServer grpc.Server
}

// NewMigrator returns a new Migrator.
func NewMigrator(keeper Keeper, queryServer grpc.Server) Migrator {
return Migrator{keeper: keeper.ak, queryServer: queryServer}
}

// Migrate1to2 migrates from version 1 to 2.
func (m Migrator) Migrate1to2(ctx sdk.Context) error {
var iterErr error

m.keeper.IterateAccounts(ctx, func(account sdktypes.AccountI) (stop bool) {
wb, err := v043.MigrateAccount(ctx, account, m.queryServer)
if err != nil {
iterErr = err
return true
}

if wb == nil {
return false
}

m.keeper.SetAccount(ctx, wb)
return false
})

return iterErr
}
8 changes: 7 additions & 1 deletion x/auth/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,12 @@ func (am AppModule) LegacyQuerierHandler(cdc *codec.LegacyAmino) sdk.Querier {
func (am AppModule) RegisterServices(cfg module.Configurator) {
types.RegisterMsgServer(cfg.MsgServer(), keeper.NewMsgServerImpl(am.keeper))
authtypes.RegisterQueryServer(cfg.QueryServer(), am.authKeeper)

m := keeper.NewMigrator(am.keeper, cfg.QueryServer())
err := cfg.RegisterMigration(types.ModuleName, 1, m.Migrate1to2)
if err != nil {
panic(err)
}
}

// InitGenesis performs genesis initialization for the auth module. It returns no validator updates.
Expand All @@ -150,7 +156,7 @@ func (am AppModule) ExportGenesis(ctx sdk.Context, cdc codec.JSONCodec) json.Raw
}

// ConsensusVersion implements AppModule/ConsensusVersion.
func (AppModule) ConsensusVersion() uint64 { return 1 }
func (am AppModule) ConsensusVersion() uint64 { return am.cosmosAppModule.ConsensusVersion() }

// BeginBlock returns the begin blocker for the auth module.
func (am AppModule) BeginBlock(ctx sdk.Context, rbb abci.RequestBeginBlock) {
Expand Down
2 changes: 2 additions & 0 deletions x/auth/types/expected_keepers.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,6 @@ type CertKeeper interface {
type AccountKeeper interface {
GetAccount(ctx sdk.Context, addr sdk.AccAddress) types.AccountI
SetAccount(ctx sdk.Context, acc types.AccountI)

IterateAccounts(ctx sdk.Context, cb func(account types.AccountI) (stop bool))
}
2 changes: 1 addition & 1 deletion x/bank/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ func (am AppModule) ExportGenesis(ctx sdk.Context, cdc codec.JSONCodec) json.Raw
}

// ConsensusVersion implements AppModule/ConsensusVersion.
func (AppModule) ConsensusVersion() uint64 { return 1 }
func (am AppModule) ConsensusVersion() uint64 { return am.cosmosAppModule.ConsensusVersion() }

// BeginBlock returns the begin blocker for the bank module.
func (am AppModule) BeginBlock(ctx sdk.Context, rbb abci.RequestBeginBlock) {
Expand Down
2 changes: 1 addition & 1 deletion x/crisis/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ func (am AppModule) ExportGenesis(ctx sdk.Context, cdc codec.JSONCodec) json.Raw
}

// ConsensusVersion implements AppModule/ConsensusVersion.
func (AppModule) ConsensusVersion() uint64 { return 1 }
func (am AppModule) ConsensusVersion() uint64 { return 1 }

// BeginBlock performs a no-op.
func (AppModule) BeginBlock(_ sdk.Context, _ abci.RequestBeginBlock) {}
Expand Down
Loading