Skip to content

Commit

Permalink
feat: improve genesis migration command (#15679)
Browse files Browse the repository at this point in the history
## Description

Closes: #5041
ref: cosmos/gaia#1950 (comment)

---

### Author Checklist

*All items are required. Please add a note to the item if the item is not applicable and
please add links to any relevant follow up issues.*

I have...

* [ ] included the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title
* [ ] added `!` to the type prefix if API or client breaking change
* [ ] targeted the correct branch (see [PR Targeting](https://github.com/cosmos/cosmos-sdk/blob/main/CONTRIBUTING.md#pr-targeting))
* [ ] provided a link to the relevant issue or specification
* [ ] followed the guidelines for [building modules](https://github.com/cosmos/cosmos-sdk/blob/main/docs/docs/building-modules)
* [ ] included the necessary unit and integration [tests](https://github.com/cosmos/cosmos-sdk/blob/main/CONTRIBUTING.md#testing)
* [ ] added a changelog entry to `CHANGELOG.md`
* [ ] included comments for [documenting Go code](https://blog.golang.org/godoc)
* [ ] updated the relevant documentation or specification
* [ ] reviewed "Files changed" and left comments if necessary
* [ ] confirmed all CI checks have passed

### Reviewers Checklist

*All items are required. Please add a note if the item is not applicable and please add
your handle next to the items reviewed if you only reviewed selected items.*

I have...

* [ ] confirmed the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title
* [ ] confirmed `!` in the type prefix if API or client breaking change
* [ ] confirmed all author checklist items have been addressed 
* [ ] reviewed state machine logic
* [ ] reviewed API design and naming
* [ ] reviewed documentation is accurate
* [ ] reviewed tests and test coverage
* [ ] manually tested (if applicable)
  • Loading branch information
julienrbrt authored Apr 4, 2023
1 parent 445dc8a commit d0d1f5c
Show file tree
Hide file tree
Showing 11 changed files with 71 additions and 67 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ Ref: https://keepachangelog.com/en/1.0.0/

### Features

* (x/genutil) [#15679](https://github.com/cosmos/cosmos-sdk/pull/15679) Allow applications to specify a custom genesis migration function for the `genesis migrate` command.
* (client) [#15458](https://github.com/cosmos/cosmos-sdk/pull/15458) Add a `CmdContext` field to client.Context initialized to cobra command's context.
* (core) [#15133](https://github.com/cosmos/cosmos-sdk/pull/15133) Implement RegisterServices in the module manager.
* (x/gov) [#14373](https://github.com/cosmos/cosmos-sdk/pull/14373) Add new proto field `constitution` of type `string` to gov module genesis state, which allows chain builders to lay a strong foundation by specifying purpose.
Expand Down Expand Up @@ -99,6 +100,7 @@ Ref: https://keepachangelog.com/en/1.0.0/

### API Breaking Changes

* (x/genutil) [#15679](https://github.com/cosmos/cosmos-sdk/pull/15679) `MigrateGenesisCmd` now takes a `MigrationMap` instead of having the SDK genesis migration hardcoded.
* (client) [#15673](https://github.com/cosmos/cosmos-sdk/pull/15673) Move `client/keys.OutputFormatJSON` and `client/keys.OutputFormatText` to `client/flags` package.
* (x/nft) [#15588](https://github.com/cosmos/cosmos-sdk/pull/15588) `NewKeeper` now takes a `KVStoreService` instead of a `StoreKey` and methods in the `Keeper` now take a `context.Context` instead of a `sdk.Context`.
* (x/auth) [#15520](https://github.com/cosmos/cosmos-sdk/pull/15520) `NewAccountKeeper` now takes a `KVStoreService` instead of a `StoreKey` and methods in the `Keeper` now take a `context.Context` instead of a `sdk.Context`.
Expand Down
2 changes: 1 addition & 1 deletion simapp/simd/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ func addModuleInitFlags(startCmd *cobra.Command) {

// genesisCommand builds genesis-related `simd genesis` command. Users may provide application specific commands as a parameter
func genesisCommand(encodingConfig params.EncodingConfig, cmds ...*cobra.Command) *cobra.Command {
cmd := genutilcli.GenesisCoreCommand(encodingConfig.TxConfig, simapp.ModuleBasics, simapp.DefaultNodeHome)
cmd := genutilcli.Commands(encodingConfig.TxConfig, simapp.ModuleBasics, simapp.DefaultNodeHome)

for _, subCmd := range cmds {
cmd.AddCommand(subCmd)
Expand Down
9 changes: 9 additions & 0 deletions x/genutil/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,19 @@ Migrate genesis to a specified target (SDK) version.
simd genesis migrate [target-version]
```

:::tip
The `migrate` command is extensible and takes a `MigrationMap`. This map is a mapping of target versions to genesis migrations functions.
When not using the default `MigrationMap`, it is recommended to still call the default `MigrationMap` corresponding the SDK version of the chain and prepend/append your own genesis migrations.
:::

#### validate-genesis

Validates the genesis file at the default location or at the location passed as an argument.

```shell
simd genesis validate-genesis
```

:::warning
Validate genesis only validates if the genesis is valid at the **current application binary**. For validating a genesis from a previous version of the application, use the `migrate` command to migrate the genesis to the current version.
:::
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,20 @@ import (
"github.com/spf13/cobra"
)

// GenesisCoreCommand adds core sdk's sub-commands into genesis command:
// -> gentx, migrate, collect-gentxs, validate-genesis, add-genesis-account
// GenesisCoreCommand adds core sdk's sub-commands into genesis command.
// Deprecated: use Commands instead.
func GenesisCoreCommand(txConfig client.TxConfig, moduleBasics module.BasicManager, defaultNodeHome string) *cobra.Command {
return Commands(txConfig, moduleBasics, defaultNodeHome)
}

// Commands adds core sdk's sub-commands into genesis command.
func Commands(txConfig client.TxConfig, moduleBasics module.BasicManager, defaultNodeHome string) *cobra.Command {
return CommandsWithCustomMigrationMap(txConfig, moduleBasics, defaultNodeHome, MigrationMap)
}

// CommandsWithCustomMigrationMap adds core sdk's sub-commands into genesis command with custom migration map.
// This custom migration map can be used by the application to add its own migration map.
func CommandsWithCustomMigrationMap(txConfig client.TxConfig, moduleBasics module.BasicManager, defaultNodeHome string, migrationMap genutiltypes.MigrationMap) *cobra.Command {
cmd := &cobra.Command{
Use: "genesis",
Short: "Application's genesis-related subcommands",
Expand All @@ -23,11 +34,9 @@ func GenesisCoreCommand(txConfig client.TxConfig, moduleBasics module.BasicManag
gentxModule := moduleBasics[genutiltypes.ModuleName].(genutil.AppModuleBasic)

cmd.AddCommand(
GenTxCmd(moduleBasics, txConfig,
banktypes.GenesisBalancesIterator{}, defaultNodeHome),
MigrateGenesisCmd(),
CollectGenTxsCmd(banktypes.GenesisBalancesIterator{}, defaultNodeHome,
gentxModule.GenTxValidator),
GenTxCmd(moduleBasics, txConfig, banktypes.GenesisBalancesIterator{}, defaultNodeHome),
MigrateGenesisCmd(migrationMap),
CollectGenTxsCmd(banktypes.GenesisBalancesIterator{}, defaultNodeHome, gentxModule.GenTxValidator),
ValidateGenesisCmd(moduleBasics),
AddGenesisAccountCmd(defaultNodeHome),
)
Expand Down
56 changes: 21 additions & 35 deletions x/genutil/client/cli/migrate.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"encoding/json"
"fmt"
"sort"
"strings"
"time"

"github.com/spf13/cobra"
Expand All @@ -20,47 +21,35 @@ import (

const flagGenesisTime = "genesis-time"

// Allow applications to extend and modify the migration process.
//
// Ref: https://github.com/cosmos/cosmos-sdk/issues/5041
var migrationMap = types.MigrationMap{
// MigrationMap is a map of SDK versions to their respective genesis migration functions.
var MigrationMap = types.MigrationMap{
"v0.43": v043.Migrate, // NOTE: v0.43, v0.44 and v0.45 are genesis compatible.
"v0.46": v046.Migrate,
"v0.47": v047.Migrate,
}

// GetMigrationCallback returns a MigrationCallback for a given version.
func GetMigrationCallback(version string) types.MigrationCallback {
return migrationMap[version]
}

// GetMigrationVersions get all migration version in a sorted slice.
func GetMigrationVersions() []string {
versions := maps.Keys(migrationMap)
sort.Strings(versions)

return versions
}

// MigrateGenesisCmd returns a command to execute genesis state migration.
func MigrateGenesisCmd() *cobra.Command {
// Applications should pass their own migration map to this function.
// When the application migration includes a SDK migration, the Cosmos SDK migration function should as well be called.
func MigrateGenesisCmd(migrations types.MigrationMap) *cobra.Command {
cmd := &cobra.Command{
Use: "migrate [target-version] [genesis-file]",
Short: "Migrate genesis to a specified target version",
Long: fmt.Sprintf(`Migrate the source genesis into the target version and print to STDOUT.
Example:
$ %s migrate v0.36 /path/to/genesis.json --chain-id=cosmoshub-3 --genesis-time=2019-04-22T17:00:00Z
`, version.AppName),
Args: cobra.ExactArgs(2),
Use: "migrate [target-version] [genesis-file]",
Short: "Migrate genesis to a specified target version",
Long: "Migrate the source genesis into the target version and print to STDOUT",
Example: fmt.Sprintf("%s migrate v0.47 /path/to/genesis.json --chain-id=cosmoshub-3 --genesis-time=2019-04-22T17:00:00Z", version.AppName),
Args: cobra.ExactArgs(2),
RunE: func(cmd *cobra.Command, args []string) error {
clientCtx := client.GetClientContextFromCmd(cmd)

var err error

target := args[0]
importGenesis := args[1]
migrationFunc, ok := migrations[target]
if !ok || migrationFunc == nil {
versions := maps.Keys(migrations)
sort.Strings(versions)
return fmt.Errorf("unknown migration function for version: %s (supported versions %s)", target, strings.Join(versions, ", "))
}

importGenesis := args[1]
appGenesis, err := types.AppGenesisFromFile(importGenesis)
if err != nil {
return err
Expand All @@ -83,14 +72,11 @@ $ %s migrate v0.36 /path/to/genesis.json --chain-id=cosmoshub-3 --genesis-time=2
return fmt.Errorf("failed to JSON unmarshal initial genesis state: %w", err)
}

migrationFunc := GetMigrationCallback(target)
if migrationFunc == nil {
return fmt.Errorf("unknown migration function for version: %s", target)
newGenState, err := migrationFunc(initialState, clientCtx)
if err != nil {
return fmt.Errorf("failed to migrate genesis state: %w", err)
}

// TODO: handler error from migrationFunc call
newGenState := migrationFunc(initialState, clientCtx)

appGenesis.AppState, err = json.Marshal(newGenState)
if err != nil {
return fmt.Errorf("failed to JSON marshal migrated genesis state: %w", err)
Expand Down
22 changes: 11 additions & 11 deletions x/genutil/client/cli/migrate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,10 @@ import (
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/testutil"
clitestutil "github.com/cosmos/cosmos-sdk/testutil/cli"
moduletestutil "github.com/cosmos/cosmos-sdk/types/module/testutil"
"github.com/cosmos/cosmos-sdk/x/genutil/client/cli"
)

func TestGetMigrationCallback(t *testing.T) {
for _, version := range cli.GetMigrationVersions() {
require.NotNil(t, cli.GetMigrationCallback(version))
}
}

func TestMigrateGenesis(t *testing.T) {
testCases := []struct {
name string
Expand All @@ -28,9 +23,9 @@ func TestMigrateGenesis(t *testing.T) {
check func(jsonOut string)
}{
{
"migrate 0.37 to 0.42",
"migrate 0.37 to 0.43",
v037Exported,
"v0.42",
"v0.43",
true, "make sure that you have correctly migrated all CometBFT consensus params", func(_ string) {},
},
{
Expand All @@ -42,7 +37,7 @@ func TestMigrateGenesis(t *testing.T) {
return string(bz)
}(),
"v0.10",
true, "unknown migration function for version: v0.10", func(_ string) {},
true, "unknown migration function for version: v0.10 (supported versions v0.43, v0.46, v0.47)", func(_ string) {},
},
{
"invalid target version",
Expand All @@ -53,15 +48,20 @@ func TestMigrateGenesis(t *testing.T) {
return string(bz)
}(),
"v0.10",
true, "unknown migration function for version: v0.10", func(_ string) {},
true, "unknown migration function for version: v0.10 (supported versions v0.43, v0.46, v0.47)", func(_ string) {},
},
}

for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
genesisFile := testutil.WriteToNewTempFile(t, tc.genesis)
jsonOutput, err := clitestutil.ExecTestCLICmd(client.Context{}, cli.MigrateGenesisCmd(), []string{tc.target, genesisFile.Name()})
jsonOutput, err := clitestutil.ExecTestCLICmd(
// the codec does not contain any modules so that genutil does not bring unnecessary dependencies in the test
client.Context{Codec: moduletestutil.MakeTestEncodingConfig().Codec},
cli.MigrateGenesisCmd(cli.MigrationMap),
[]string{tc.target, genesisFile.Name()},
)
if tc.expErr {
require.Contains(t, err.Error(), tc.expErrMsg)
} else {
Expand Down
2 changes: 1 addition & 1 deletion x/genutil/client/cli/validate_genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ func ValidateGenesisCmd(mbm module.BasicManager) *cobra.Command {
return fmt.Errorf("error validating genesis file %s: %s", genesis, err.Error())
}

fmt.Printf("File at %s is a valid genesis file\n", genesis)
fmt.Fprintf(cmd.OutOrStdout(), "File at %s is a valid genesis file\n", genesis)
return nil
},
}
Expand Down
4 changes: 2 additions & 2 deletions x/genutil/migrations/v043/migrate.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (
)

// Migrate migrates exported state from v0.40 to a v0.43 genesis state.
func Migrate(appState types.AppMap, clientCtx client.Context) types.AppMap {
func Migrate(appState types.AppMap, clientCtx client.Context) (types.AppMap, error) {
// Migrate x/gov.
if appState[v1gov.ModuleName] != nil {
// unmarshal relative source genesis application state
Expand Down Expand Up @@ -40,5 +40,5 @@ func Migrate(appState types.AppMap, clientCtx client.Context) types.AppMap {
appState[v2bank.ModuleName] = clientCtx.Codec.MustMarshalJSON(v2bank.MigrateJSON(&oldBankState))
}

return appState
return appState, nil
}
8 changes: 4 additions & 4 deletions x/genutil/migrations/v046/migrate.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (
)

// Migrate migrates exported state from v0.43 to a v0.46 genesis state.
func Migrate(appState types.AppMap, clientCtx client.Context) types.AppMap {
func Migrate(appState types.AppMap, clientCtx client.Context) (types.AppMap, error) {
// Migrate x/gov.
if appState[v2gov.ModuleName] != nil {
// unmarshal relative source genesis application state
Expand All @@ -26,7 +26,7 @@ func Migrate(appState types.AppMap, clientCtx client.Context) types.AppMap {
// the respective key.
new, err := v3gov.MigrateJSON(&old)
if err != nil {
panic(err)
return nil, err
}
appState[v3gov.ModuleName] = clientCtx.Codec.MustMarshalJSON(new)
}
Expand All @@ -44,10 +44,10 @@ func Migrate(appState types.AppMap, clientCtx client.Context) types.AppMap {
// the respective key.
new, err := stakingv3.MigrateJSON(old)
if err != nil {
panic(err)
return nil, err
}
appState[stakingv3.ModuleName] = clientCtx.Codec.MustMarshalJSON(&new)
}

return appState
return appState, nil
}
6 changes: 3 additions & 3 deletions x/genutil/migrations/v047/migrate.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import (
)

// Migrate migrates exported state from v0.46 to a v0.47 genesis state.
func Migrate(appState types.AppMap, clientCtx client.Context) types.AppMap {
func Migrate(appState types.AppMap, clientCtx client.Context) (types.AppMap, error) {
// Migrate x/bank.
bankState := appState[banktypes.ModuleName]
if len(bankState) > 0 {
Expand All @@ -37,7 +37,7 @@ func Migrate(appState types.AppMap, clientCtx client.Context) types.AppMap {
// set the x/gov genesis state with new state.
new, err := v4gov.MigrateJSON(&old)
if err != nil {
panic(err)
return nil, err
}
appState[v4gov.ModuleName] = clientCtx.Codec.MustMarshalJSON(new)
}
Expand All @@ -58,5 +58,5 @@ func Migrate(appState types.AppMap, clientCtx client.Context) types.AppMap {
appState[v1distr.ModuleName] = clientCtx.Codec.MustMarshalJSON(newDistState)
}

return appState
return appState, nil
}
4 changes: 1 addition & 3 deletions x/genutil/types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,7 @@ type (

// MigrationCallback converts a genesis map from the previous version to the
// targeted one.
//
// TODO: MigrationCallback should also return an error upon failure.
MigrationCallback func(AppMap, client.Context) AppMap
MigrationCallback func(AppMap, client.Context) (AppMap, error)

// MigrationMap defines a mapping from a version to a MigrationCallback.
MigrationMap map[string]MigrationCallback
Expand Down

0 comments on commit d0d1f5c

Please sign in to comment.