Skip to content

Commit

Permalink
Implement in-place store migration (shentufoundation#334)
Browse files Browse the repository at this point in the history
* implement in-place store migration

* fix consensus versions and add a test

* update unit test

* lint

* remove tx hash from gov votes and add migration

* remove tx hash from gov type Deposit

* unit test fix

* update upgrade name

* separate migration logic from app.go

* reorder upgrade handler and register legacy types for migration

* APP: Fix Auth module MVA in-place migration (shentufoundation#344)

* module level migration WIP

* add auth migration again after first migration

* changelog

* Update CHANGELOG.md

Co-authored-by: yoongbok-lee <52583590+yoongbok-lee@users.noreply.github.com>
  • Loading branch information
2 people authored and priyanshiiit committed Dec 1, 2021
1 parent 8efc823 commit d4b4428
Show file tree
Hide file tree
Showing 40 changed files with 3,528 additions and 872 deletions.
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ Ref: https://keepachangelog.com/en/1.0.0/
### API Breaking Changes
### State Machine Breaking Changes
* (app) [\#326](https://github.com/certikfoundation/shentu/pull/326) Bump Cosmos SDK to v0.44.3.
* (app) [\#334](https://github.com/certikfoundation/shentu/pull/334) Implement in-store migration from v2.2.0 to v2.3.0.
* (x/gov) [\#334](https://github.com/certikfoundation/shentu/pull/334) `TxHash` field has been removed from `Vote` and `Deposit` types.

### Features
* (app) [\#326](https://github.com/certikfoundation/shentu/pull/326) Add `authz` and `feegrant` modules.
Expand All @@ -55,7 +57,8 @@ Ref: https://keepachangelog.com/en/1.0.0/
* (x/shield) [\#326](https://github.com/certikfoundation/shentu/pull/326) Add checks for expired entries in shield purchase.
* (x/gov) [\#331](https://github.com/certikfoundation/shentu/pull/331) Fix gov tally logic.

## [v2.1.0] - 08-09-2021

## [v2.1.0] - 09-03-2021

Version 2.1.0 re-enables endblocker in the staking module, and bumps SDK to 0.42.9 for necessary query route.

Expand Down
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(),
},
)
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)
}
})
}
}
Loading

0 comments on commit d4b4428

Please sign in to comment.