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
17 changes: 9 additions & 8 deletions app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,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 @@ -437,10 +438,6 @@ func NewShentuApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest
// If evidence needs to be handled for the app, set routes in router here and seal
app.evidenceKeeper = *evidenceKeeper

// Add empty upgrade handler to bump to 0.44.0
cfg := module.NewConfigurator(appCodec, app.MsgServiceRouter(), app.GRPCQueryRouter())
app.setUpgradeHandler(cfg)

/**** Module Options ****/

// NOTE: Any module instantiated in the module manager that is
Expand Down Expand Up @@ -524,7 +521,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 @@ -552,8 +551,6 @@ func NewShentuApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest
app.MountTransientStores(tkeys)
app.MountMemoryStores(memKeys)

app.SetInitChainer(app.InitChainer)
app.SetBeginBlocker(app.BeginBlocker)
// The AnteHandler handles signature verification and transaction pre-processing
anteHandler, err := ante.NewAnteHandler(
ante.HandlerOptions{
Expand All @@ -568,8 +565,12 @@ func NewShentuApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest
tmos.Exit(err.Error())
}
app.SetAnteHandler(anteHandler)
app.SetInitChainer(app.InitChainer)
app.SetBeginBlocker(app.BeginBlocker)
app.SetEndBlocker(app.EndBlocker)

app.setUpgradeHandler()

if loadLatest {
if err := app.LoadLatestVersion(); err != nil {
tmos.Exit(err.Error())
Expand Down
55 changes: 54 additions & 1 deletion app/upgrade_handler.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,60 @@
package app

import (
"fmt"
storetypes "github.com/cosmos/cosmos-sdk/store/types"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/module"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
"github.com/cosmos/cosmos-sdk/x/authz"
sdkauthz "github.com/cosmos/cosmos-sdk/x/authz"
"github.com/cosmos/cosmos-sdk/x/feegrant"
sdkfeegrant "github.com/cosmos/cosmos-sdk/x/feegrant"
upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types"
ibcconnectiontypes "github.com/cosmos/ibc-go/modules/core/03-connection/types"
)

func (app ShentuApp) setUpgradeHandler(cfg module.Configurator) {}
const upgradeName = "Shentu-v230"

func (app ShentuApp) setUpgradeHandler() {
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

temp, err := app.mm.RunMigrations(ctx, app.configurator, fromVM)

if err != nil {
return temp, err
}

authVM := make(map[string]uint64)
authVM[authtypes.ModuleName] = 1

_, err = app.mm.RunMigrations(ctx, app.configurator, authVM)
return temp, err
},
)

upgradeInfo, err := app.upgradeKeeper.ReadUpgradeInfoFromDisk()
if err != nil {
panic(fmt.Sprintf("failed to read upgrade info from disk %s", err))
}

if upgradeInfo.Name == upgradeName && !app.upgradeKeeper.IsSkipHeight(upgradeInfo.Height) {
storeUpgrades := storetypes.StoreUpgrades{
Added: []string{authz.ModuleName, feegrant.ModuleName},
}

// configure store loader that checks if version == upgradeHeight and applies store upgrades
app.SetStoreLoader(upgradetypes.UpgradeStoreLoader(upgradeInfo.Height, &storeUpgrades))
}
}
24 changes: 2 additions & 22 deletions proto/shentu/gov/v1alpha1/gov.proto
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ message GenesisState {
// starting_proposal_id is the ID of the starting proposal.
uint64 starting_proposal_id = 1 [(gogoproto.moretags) = "yaml:\"starting_proposal_id\""];
// deposits defines all the deposits present at genesis.
repeated Deposit deposits = 2 [(gogoproto.castrepeated) = "Deposits", (gogoproto.nullable) = false];
repeated cosmos.gov.v1beta1.Deposit deposits = 2 [(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 All @@ -36,16 +36,6 @@ message GenesisState {
TallyParams tally_params = 7 [(gogoproto.nullable) = false, (gogoproto.moretags) = "yaml:\"tally_params\""];
}

// Deposit defines an amount deposited by an account address to an active
// proposal.
message Deposit {
option (gogoproto.goproto_getters) = false;
option (gogoproto.equal) = false;

cosmos.gov.v1beta1.Deposit deposit = 1 [(gogoproto.embed) = true];
string tx_hash = 2 [ (gogoproto.moretags) = "yaml:\"txhash\"" ];
}

// DepositParams defines the params for deposits on governance proposals.
message DepositParams {
// Minimum deposit for a proposal to enter voting period.
Expand Down Expand Up @@ -129,13 +119,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\"" ];
}
8 changes: 4 additions & 4 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 Expand Up @@ -158,7 +158,7 @@ message QueryDepositRequest {
// QueryDepositResponse is the response type for the Query/Deposit RPC method.
message QueryDepositResponse {
// deposit defines the requested deposit.
Deposit deposit = 1 [(gogoproto.nullable) = false];
cosmos.gov.v1beta1.Deposit deposit = 1 [(gogoproto.nullable) = false];
}

// QueryDepositsRequest is the request type for the Query/Deposits RPC method.
Expand All @@ -172,7 +172,7 @@ message QueryDepositsRequest {

// QueryDepositsResponse is the response type for the Query/Deposits RPC method.
message QueryDepositsResponse {
repeated Deposit deposits = 1 [(gogoproto.nullable) = false];
repeated cosmos.gov.v1beta1.Deposit deposits = 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
Loading