From 17327614779a66b8e6cb9161236255db14e6ba19 Mon Sep 17 00:00:00 2001 From: jay tseng Date: Fri, 9 Sep 2022 15:53:27 -0400 Subject: [PATCH 01/17] separate import/export/valadate app state to files --- client/flags/flags.go | 1 + server/export.go | 33 +++++-- simapp/app.go | 28 ++++++ simapp/export.go | 19 +++- simapp/simd/cmd/root.go | 1 + types/module/module.go | 120 +++++++++++++++++++++-- types/module/module_test.go | 10 +- x/authz/client/testutil/grpc.go | 4 +- x/genutil/client/cli/validate_genesis.go | 64 ++++++++++-- 9 files changed, 247 insertions(+), 33 deletions(-) diff --git a/client/flags/flags.go b/client/flags/flags.go index faf7c16de0ea..439f14b86106 100644 --- a/client/flags/flags.go +++ b/client/flags/flags.go @@ -77,6 +77,7 @@ const ( FlagTip = "tip" FlagAux = "aux" FlagOutput = tmcli.OutputFlag + FlagGenesisFilePath = "genesis-path" // Tendermint logging flags FlagLogLevel = "log_level" diff --git a/server/export.go b/server/export.go index 80415679a709..64f999ca77a7 100644 --- a/server/export.go +++ b/server/export.go @@ -5,6 +5,7 @@ package server import ( "fmt" "os" + "path/filepath" "github.com/spf13/cobra" tmjson "github.com/tendermint/tendermint/libs/json" @@ -34,6 +35,11 @@ func ExportCmd(appExporter types.AppExporter, defaultNodeHome string) *cobra.Com homeDir, _ := cmd.Flags().GetString(flags.FlagHome) config.SetRoot(homeDir) + genesisExportPath, err := cmd.Flags().GetString(flags.FlagGenesisFilePath) + if err != nil { + return err + } + if _, err := os.Stat(config.GenesisFile()); os.IsNotExist(err) { return err } @@ -96,17 +102,23 @@ func ExportCmd(appExporter types.AppExporter, defaultNodeHome string) *cobra.Com }, } - // NOTE: Tendermint uses a custom JSON decoder for GenesisDoc - // (except for stuff inside AppState). Inside AppState, we're free - // to encode as protobuf or amino. - encoded, err := tmjson.Marshal(doc) - if err != nil { - return err - } + if len(genesisExportPath) > 0 { + if err := doc.SaveAs(filepath.Join(genesisExportPath, "genesis", "genesis.json")); err != nil { + return err + } + } else { + // NOTE: Tendermint uses a custom JSON decoder for GenesisDoc + // (except for stuff inside AppState). Inside AppState, we're free + // to encode as protobuf or amino. + encoded, err := tmjson.Marshal(doc) + if err != nil { + return err + } - cmd.SetOut(cmd.OutOrStdout()) - cmd.SetErr(cmd.OutOrStderr()) - cmd.Println(string(sdk.MustSortJSON(encoded))) + cmd.SetOut(cmd.OutOrStdout()) + cmd.SetErr(cmd.OutOrStderr()) + cmd.Println(string(sdk.MustSortJSON(encoded))) + } return nil }, } @@ -116,6 +128,7 @@ func ExportCmd(appExporter types.AppExporter, defaultNodeHome string) *cobra.Com cmd.Flags().Bool(FlagForZeroHeight, false, "Export state to start at height zero (perform preproccessing)") cmd.Flags().StringSlice(FlagJailAllowedAddrs, []string{}, "Comma-separated list of operator addresses of jailed validators to unjail") cmd.Flags().StringSlice(FlagModulesToExport, []string{}, "Comma-separated list of modules to export. If empty, will export all modules") + cmd.Flags().String(flags.FlagGenesisFilePath, "", "Export state to the designated file path") return cmd } diff --git a/simapp/app.go b/simapp/app.go index 44a2f5e6e110..b461737fb252 100644 --- a/simapp/app.go +++ b/simapp/app.go @@ -3,12 +3,15 @@ package simapp import ( + "bytes" _ "embed" + "encoding/json" "fmt" "io" "os" "path/filepath" + "github.com/spf13/cast" abci "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/libs/log" dbm "github.com/tendermint/tm-db" @@ -17,6 +20,7 @@ import ( "github.com/cosmos/cosmos-sdk/baseapp" "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/flags" "github.com/cosmos/cosmos-sdk/codec" codectypes "github.com/cosmos/cosmos-sdk/codec/types" "github.com/cosmos/cosmos-sdk/runtime" @@ -166,6 +170,12 @@ type SimApp struct { // simulation manager sm *module.SimulationManager + + // the root folder of the app config and data + homepath string + + // the path for the genesis state exporting + exportpath string } func init() { @@ -239,6 +249,12 @@ func NewSimApp( } app.App = appBuilder.Build(logger, db, traceStore, baseAppOptions...) + app.homepath = cast.ToString(appOpts.Get(flags.FlagHome)) + + ep := cast.ToString(appOpts.Get(flags.FlagGenesisFilePath)) + if len(ep) > 0 { + app.exportpath = filepath.Join(ep, "genesis") + } // configure state listening capabilities using AppOptions // we are doing nothing with the returned streamingServices and waitGroup in this case @@ -309,6 +325,18 @@ func (app *SimApp) Name() string { return app.BaseApp.Name() } // InitChainer application update at chain initialization func (app *SimApp) InitChainer(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain { + jsonObj := make(map[string]json.RawMessage) + jsonObj["module_genesis_state"] = []byte("true") + loadAppStateFromFolder, _ := json.Marshal(jsonObj) + var genesisState GenesisState + if bytes.Equal(loadAppStateFromFolder, req.AppStateBytes) { + app.ModuleManager.SetGenesisPath(filepath.Join(app.homepath, "config", "genesis")) + } else { + if err := json.Unmarshal(req.AppStateBytes, &genesisState); err != nil { + panic(err) + } + } + app.UpgradeKeeper.SetModuleVersionMap(ctx, app.ModuleManager.GetVersionMap()) return app.App.InitChainer(ctx, req) } diff --git a/simapp/export.go b/simapp/export.go index 96e841064a5a..84bad04ace23 100644 --- a/simapp/export.go +++ b/simapp/export.go @@ -28,12 +28,27 @@ func (app *SimApp) ExportAppStateAndValidators(forZeroHeight bool, jailAllowedAd app.prepForZeroHeightGenesis(ctx, jailAllowedAddrs) } - genState := app.ModuleManager.ExportGenesisForModules(ctx, app.appCodec, modulesToExport) - appState, err := json.MarshalIndent(genState, "", " ") + app.ModuleManager.SetGenesisPath(app.exportpath) + genState, err := app.ModuleManager.ExportGenesisForModules(ctx, app.appCodec, modulesToExport) if err != nil { return servertypes.ExportedApp{}, err } + var appState []byte + if app.exportpath != "" { + jsonObj := make(map[string]json.RawMessage) + jsonObj["module_genesis_state"] = []byte("true") + appState, err = json.MarshalIndent(jsonObj, "", " ") + if err != nil { + return servertypes.ExportedApp{}, err + } + } else { + appState, err = json.MarshalIndent(genState, "", " ") + if err != nil { + return servertypes.ExportedApp{}, err + } + } + validators, err := staking.WriteValidators(ctx, app.StakingKeeper) return servertypes.ExportedApp{ AppState: appState, diff --git a/simapp/simd/cmd/root.go b/simapp/simd/cmd/root.go index 7590217c7083..3ee974a7bbaa 100644 --- a/simapp/simd/cmd/root.go +++ b/simapp/simd/cmd/root.go @@ -176,6 +176,7 @@ func initRootCmd(rootCmd *cobra.Command, encodingConfig params.EncodingConfig) { genutilcli.CollectGenTxsCmd(banktypes.GenesisBalancesIterator{}, simapp.DefaultNodeHome, gentxModule.GenTxValidator), genutilcli.MigrateGenesisCmd(), + genutilcli.GenTxCmd(simapp.ModuleBasics, encodingConfig.TxConfig, banktypes.GenesisBalancesIterator{}, simapp.DefaultNodeHome), genutilcli.ValidateGenesisCmd(simapp.ModuleBasics), diff --git a/types/module/module.go b/types/module/module.go index 45356e239915..1c1c4e62eae3 100644 --- a/types/module/module.go +++ b/types/module/module.go @@ -31,6 +31,8 @@ package module import ( "encoding/json" "fmt" + "os" + "path/filepath" "sort" "github.com/grpc-ecosystem/grpc-gateway/runtime" @@ -218,6 +220,7 @@ type Manager struct { OrderBeginBlockers []string OrderEndBlockers []string OrderMigrations []string + GenesisPath string } // NewManager creates a new Manager object @@ -235,6 +238,7 @@ func NewManager(modules ...AppModule) *Manager { OrderExportGenesis: modulesStr, OrderBeginBlockers: modulesStr, OrderEndBlockers: modulesStr, + GenesisPath: "", } } @@ -291,13 +295,34 @@ func (m *Manager) RegisterServices(cfg Configurator) { func (m *Manager) InitGenesis(ctx sdk.Context, cdc codec.JSONCodec, genesisData map[string]json.RawMessage) abci.ResponseInitChain { var validatorUpdates []abci.ValidatorUpdate ctx.Logger().Info("initializing blockchain state from genesis.json") + initWithPath := (len(m.GenesisPath) != 0) for _, moduleName := range m.OrderInitGenesis { - if genesisData[moduleName] == nil { + if (!initWithPath && genesisData[moduleName] == nil) || + (initWithPath && m.Modules[moduleName] == nil) { continue } - ctx.Logger().Debug("running initialization for module", "module", moduleName) + ctx.Logger().Info("running initialization for module", "module", moduleName) - moduleValUpdates := m.Modules[moduleName].InitGenesis(ctx, cdc, genesisData[moduleName]) + var moduleValUpdates []abci.ValidatorUpdate + if initWithPath { + modulePath := filepath.Join(m.GenesisPath, moduleName) + ctx.Logger().Info("loading module genesis state from", "path", modulePath) + + f, err := OpenGenesisModuleFile(modulePath, moduleName) + if err != nil { + panic(fmt.Sprintf("failed to open genesis file from module %s: %v", moduleName, err)) + } + defer f.Close() + + bz, err := FileRead(f) + if err != nil { + panic(fmt.Sprintf("failed to read the genesis state from file: %v", err)) + } + + moduleValUpdates = m.Modules[moduleName].InitGenesis(ctx, cdc, bz) + } else { + moduleValUpdates = m.Modules[moduleName].InitGenesis(ctx, cdc, genesisData[moduleName]) + } // use these validator updates if provided, the module manager assumes // only one module will update the validator set @@ -320,31 +345,56 @@ func (m *Manager) InitGenesis(ctx sdk.Context, cdc codec.JSONCodec, genesisData } // ExportGenesis performs export genesis functionality for modules -func (m *Manager) ExportGenesis(ctx sdk.Context, cdc codec.JSONCodec) map[string]json.RawMessage { +func (m *Manager) ExportGenesis(ctx sdk.Context, cdc codec.JSONCodec) (map[string]json.RawMessage, error) { return m.ExportGenesisForModules(ctx, cdc, []string{}) } // ExportGenesisForModules performs export genesis functionality for modules -func (m *Manager) ExportGenesisForModules(ctx sdk.Context, cdc codec.JSONCodec, modulesToExport []string) map[string]json.RawMessage { +// There are 3 ways to export the module's genesis state +// a. export whole modules to file given the export path, separate by module name +// b. export whole modules +// c. export designated modules from the modulesToExport argument +func (m *Manager) ExportGenesisForModules(ctx sdk.Context, cdc codec.JSONCodec, modulesToExport []string) (map[string]json.RawMessage, error) { genesisData := make(map[string]json.RawMessage) + + // a. + if len(m.GenesisPath) > 0 { + for _, moduleName := range m.OrderExportGenesis { + modulePath := filepath.Join(m.GenesisPath, moduleName) + fmt.Printf("exporting module: %s,path: %s\n", moduleName, modulePath) + + bz := m.Modules[moduleName].ExportGenesis(ctx, cdc) + f, err := CreateGenesisExportFile(modulePath, moduleName) + if err != nil { + return nil, err + } + defer f.Close() + + if err := FileWrite(f, bz); err != nil { + return nil, fmt.Errorf("ExportGenesis to file failed, module=%s err=%v", moduleName, err) + } + } + } + + // b. if len(modulesToExport) == 0 { for _, moduleName := range m.OrderExportGenesis { genesisData[moduleName] = m.Modules[moduleName].ExportGenesis(ctx, cdc) } - return genesisData + return genesisData, nil } + // c. // verify modules exists in app, so that we don't panic in the middle of an export if err := m.checkModulesExists(modulesToExport); err != nil { panic(err) } - for _, moduleName := range modulesToExport { genesisData[moduleName] = m.Modules[moduleName].ExportGenesis(ctx, cdc) } - return genesisData + return genesisData, nil } // checkModulesExists verifies that all modules in the list exist in the app @@ -547,6 +597,11 @@ func (m *Manager) ModuleNames() []string { return maps.Keys(m.Modules) } +// SetGenesisPath sets the genesis binaries export/import path. +func (m *Manager) SetGenesisPath(path string) { + m.GenesisPath = path +} + // DefaultMigrationsOrder returns a default migrations order: ascending alphabetical by module name, // except x/auth which will run last, see: // https://github.com/cosmos/cosmos-sdk/issues/10591 @@ -567,3 +622,52 @@ func DefaultMigrationsOrder(modules []string) []string { } return out } + +func CreateGenesisExportFile(exportPath string, moduleName string) (*os.File, error) { + if err := os.MkdirAll(exportPath, 0o700); err != nil { + return nil, fmt.Errorf("failed to create directory: %w", err) + } + + fp := filepath.Join(exportPath, fmt.Sprintf("genesis_%s.bin", moduleName)) + f, err := os.Create(fp) + if err != nil { + return nil, fmt.Errorf("failed to create file: %w", err) + } + + return f, nil +} + +func OpenGenesisModuleFile(importPath string, moduleName string) (*os.File, error) { + fp := filepath.Join(importPath, fmt.Sprintf("genesis_%s.bin", moduleName)) + f, err := os.OpenFile(fp, os.O_RDONLY, 0o600) + if err != nil { + return nil, fmt.Errorf("failed to open file: %w", err) + } + + return f, nil +} + +func FileWrite(f *os.File, bz []byte) error { + if n, err := f.Write(bz); err != nil { + return fmt.Errorf("failed to write genesis file: %w", err) + } else if n != len(bz) { + return fmt.Errorf("genesis file was not fully written: %w", err) + } + return nil +} + +func FileRead(f *os.File) ([]byte, error) { + fi, err := f.Stat() + if err != nil { + return nil, fmt.Errorf("failed to stat file: %w", err) + } + + bz := make([]byte, fi.Size()) + if n, err := f.Read(bz); err != nil { + return nil, fmt.Errorf("failed to read genesis file: %w", err) + } else if n != int(fi.Size()) { + return nil, fmt.Errorf("couldn't read entire genesis file, read: %d, file size: %d", n, fi.Size()) + } + + return bz, nil +} diff --git a/types/module/module_test.go b/types/module/module_test.go index dd6ad46b88e8..4d81572f56b8 100644 --- a/types/module/module_test.go +++ b/types/module/module_test.go @@ -198,8 +198,14 @@ func TestManager_ExportGenesis(t *testing.T) { "module1": json.RawMessage(`{"key1": "value1"}`), "module2": json.RawMessage(`{"key2": "value2"}`), } - require.Equal(t, want, mm.ExportGenesis(ctx, cdc)) - require.Equal(t, want, mm.ExportGenesisForModules(ctx, cdc, []string{})) + + actual, err := mm.ExportGenesis(ctx, cdc) + require.NoError(t, err) + require.Equal(t, want, actual) + + actual, err = mm.ExportGenesisForModules(ctx, cdc, []string{}) + require.NoError(t, err) + require.Equal(t, want, actual) require.Panics(t, func() { mm.ExportGenesisForModules(ctx, cdc, []string{"module1", "modulefoo"}) diff --git a/x/authz/client/testutil/grpc.go b/x/authz/client/testutil/grpc.go index 4a3b5e793d65..3adaad380f2d 100644 --- a/x/authz/client/testutil/grpc.go +++ b/x/authz/client/testutil/grpc.go @@ -186,7 +186,7 @@ func (s *IntegrationTestSuite) TestQueryGranterGrantsGRPC() { }, { "no authorizations found", - fmt.Sprintf("%s/cosmos/authz/v1beta1/grants/granter/%s", val.APIAddress, grantee.String()), + fmt.Sprintf("%s/cosmos/authz/v1beta1/grants/granter/%s", val.APIAddress, string(grantee)), false, "", 0, @@ -245,7 +245,7 @@ func (s *IntegrationTestSuite) TestQueryGranteeGrantsGRPC() { }, { "valid query", - fmt.Sprintf("%s/cosmos/authz/v1beta1/grants/grantee/%s", val.APIAddress, grantee.String()), + fmt.Sprintf("%s/cosmos/authz/v1beta1/grants/grantee/%s", val.APIAddress, string(grantee)), false, "", 1, diff --git a/x/genutil/client/cli/validate_genesis.go b/x/genutil/client/cli/validate_genesis.go index 519a5ae01f94..f5dba8de6c3e 100644 --- a/x/genutil/client/cli/validate_genesis.go +++ b/x/genutil/client/cli/validate_genesis.go @@ -1,13 +1,16 @@ package cli import ( + "bytes" "encoding/json" "fmt" + "path/filepath" "github.com/spf13/cobra" tmtypes "github.com/tendermint/tendermint/types" "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/flags" "github.com/cosmos/cosmos-sdk/server" "github.com/cosmos/cosmos-sdk/types/module" ) @@ -16,7 +19,7 @@ const chainUpgradeGuide = "https://github.com/cosmos/cosmos-sdk/blob/main/UPGRAD // ValidateGenesisCmd takes a genesis file, and makes sure that it is valid. func ValidateGenesisCmd(mbm module.BasicManager) *cobra.Command { - return &cobra.Command{ + cmd := &cobra.Command{ Use: "validate-genesis [file]", Args: cobra.RangeArgs(0, 1), Short: "validates the genesis file at the default location or at the location passed as an arg", @@ -34,24 +37,67 @@ func ValidateGenesisCmd(mbm module.BasicManager) *cobra.Command { genesis = args[0] } - genDoc, err := validateGenDoc(genesis) + genesisFilePath, err := cmd.Flags().GetString(flags.FlagGenesisFilePath) if err != nil { return err } - var genState map[string]json.RawMessage - if err = json.Unmarshal(genDoc.AppState, &genState); err != nil { - return fmt.Errorf("error unmarshalling genesis doc %s: %s", genesis, err.Error()) - } + if len(genesisFilePath) > 0 { + genDoc, err := validateGenDoc(filepath.Join(genesisFilePath, "genesis.json")) + if err != nil { + return err + } + + jsonObj := make(map[string]json.RawMessage) + jsonObj["module_genesis_state"] = []byte("true") + loadAppStateFromFolder, _ := json.Marshal(jsonObj) + + if bytes.Equal(genDoc.AppState, loadAppStateFromFolder) { + return fmt.Errorf("genesisAppState is not equal to expectedAppState, expect: %v, actual: %v", loadAppStateFromFolder, genDoc.AppState) + } + + for _, b := range mbm { + f, err := module.OpenGenesisModuleFile(filepath.Join(genesisFilePath, b.Name()), b.Name()) + if err != nil { + return err + } + defer f.Close() + + bz, err := module.FileRead(f) + if err != nil { + return err + } + + if err = b.ValidateGenesis(cdc, clientCtx.TxConfig, bz); err != nil { + return fmt.Errorf("error validating genesis state in module %s: %v", b.Name(), err.Error()) + } + } - if err = mbm.ValidateGenesis(cdc, clientCtx.TxConfig, genState); err != nil { - return fmt.Errorf("error validating genesis file %s: %s", genesis, err.Error()) + fmt.Printf("The genesis in %s is valid\n", genesisFilePath) + } else { + genDoc, err := validateGenDoc(genesis) + if err != nil { + return err + } + + var genState map[string]json.RawMessage + if err = json.Unmarshal(genDoc.AppState, &genState); err != nil { + return fmt.Errorf("error unmarshalling genesis doc %s: %s", genesis, err.Error()) + } + + if err = mbm.ValidateGenesis(cdc, clientCtx.TxConfig, genState); err != nil { + 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.Printf("File at %s is a valid genesis file\n", genesis) return nil }, } + + cmd.Flags().String(flags.FlagGenesisFilePath, "", "the file path of app genesis states") + + return cmd } // validateGenDoc reads a genesis file and validates that it is a correct From 11845378f1790d1a3b72c5db978496fd38e4640f Mon Sep 17 00:00:00 2001 From: jay tseng Date: Fri, 9 Sep 2022 15:55:59 -0400 Subject: [PATCH 02/17] remove extra line --- simapp/simd/cmd/root.go | 1 - 1 file changed, 1 deletion(-) diff --git a/simapp/simd/cmd/root.go b/simapp/simd/cmd/root.go index 3ee974a7bbaa..7590217c7083 100644 --- a/simapp/simd/cmd/root.go +++ b/simapp/simd/cmd/root.go @@ -176,7 +176,6 @@ func initRootCmd(rootCmd *cobra.Command, encodingConfig params.EncodingConfig) { genutilcli.CollectGenTxsCmd(banktypes.GenesisBalancesIterator{}, simapp.DefaultNodeHome, gentxModule.GenTxValidator), genutilcli.MigrateGenesisCmd(), - genutilcli.GenTxCmd(simapp.ModuleBasics, encodingConfig.TxConfig, banktypes.GenesisBalancesIterator{}, simapp.DefaultNodeHome), genutilcli.ValidateGenesisCmd(simapp.ModuleBasics), From 900467e06d8bc5631e62d6101de8597c6f5fda9b Mon Sep 17 00:00:00 2001 From: jay tseng Date: Mon, 12 Sep 2022 17:16:33 -0400 Subject: [PATCH 03/17] make genesis state export able to split into mutliple files --- simapp/app.go | 1 - types/module/module.go | 113 ++++++++++++++++------- x/genutil/client/cli/validate_genesis.go | 8 +- 3 files changed, 82 insertions(+), 40 deletions(-) diff --git a/simapp/app.go b/simapp/app.go index b461737fb252..12a211a711ff 100644 --- a/simapp/app.go +++ b/simapp/app.go @@ -250,7 +250,6 @@ func NewSimApp( app.App = appBuilder.Build(logger, db, traceStore, baseAppOptions...) app.homepath = cast.ToString(appOpts.Get(flags.FlagHome)) - ep := cast.ToString(appOpts.Get(flags.FlagGenesisFilePath)) if len(ep) > 0 { app.exportpath = filepath.Join(ep, "genesis") diff --git a/types/module/module.go b/types/module/module.go index 1c1c4e62eae3..4941d6b3d3f3 100644 --- a/types/module/module.go +++ b/types/module/module.go @@ -29,8 +29,11 @@ needlessly defining many placeholder functions package module import ( + "bytes" "encoding/json" "fmt" + "io/ioutil" + "math" "os" "path/filepath" "sort" @@ -308,13 +311,7 @@ func (m *Manager) InitGenesis(ctx sdk.Context, cdc codec.JSONCodec, genesisData modulePath := filepath.Join(m.GenesisPath, moduleName) ctx.Logger().Info("loading module genesis state from", "path", modulePath) - f, err := OpenGenesisModuleFile(modulePath, moduleName) - if err != nil { - panic(fmt.Sprintf("failed to open genesis file from module %s: %v", moduleName, err)) - } - defer f.Close() - - bz, err := FileRead(f) + bz, err := FileRead(modulePath, moduleName) if err != nil { panic(fmt.Sprintf("failed to read the genesis state from file: %v", err)) } @@ -364,13 +361,7 @@ func (m *Manager) ExportGenesisForModules(ctx sdk.Context, cdc codec.JSONCodec, fmt.Printf("exporting module: %s,path: %s\n", moduleName, modulePath) bz := m.Modules[moduleName].ExportGenesis(ctx, cdc) - f, err := CreateGenesisExportFile(modulePath, moduleName) - if err != nil { - return nil, err - } - defer f.Close() - - if err := FileWrite(f, bz); err != nil { + if err := fileWrite(modulePath, moduleName, bz); err != nil { return nil, fmt.Errorf("ExportGenesis to file failed, module=%s err=%v", moduleName, err) } } @@ -623,12 +614,12 @@ func DefaultMigrationsOrder(modules []string) []string { return out } -func CreateGenesisExportFile(exportPath string, moduleName string) (*os.File, error) { +func createExportFile(exportPath string, moduleName string, index int) (*os.File, error) { if err := os.MkdirAll(exportPath, 0o700); err != nil { return nil, fmt.Errorf("failed to create directory: %w", err) } - fp := filepath.Join(exportPath, fmt.Sprintf("genesis_%s.bin", moduleName)) + fp := filepath.Join(exportPath, fmt.Sprintf("genesis_%s_%d.bin", moduleName, index)) f, err := os.Create(fp) if err != nil { return nil, fmt.Errorf("failed to create file: %w", err) @@ -637,8 +628,8 @@ func CreateGenesisExportFile(exportPath string, moduleName string) (*os.File, er return f, nil } -func OpenGenesisModuleFile(importPath string, moduleName string) (*os.File, error) { - fp := filepath.Join(importPath, fmt.Sprintf("genesis_%s.bin", moduleName)) +func openModuleStateFile(importPath string, moduleName string, index int) (*os.File, error) { + fp := filepath.Join(importPath, fmt.Sprintf("genesis_%s_%d.bin", moduleName, index)) f, err := os.OpenFile(fp, os.O_RDONLY, 0o600) if err != nil { return nil, fmt.Errorf("failed to open file: %w", err) @@ -647,27 +638,85 @@ func OpenGenesisModuleFile(importPath string, moduleName string) (*os.File, erro return f, nil } -func FileWrite(f *os.File, bz []byte) error { - if n, err := f.Write(bz); err != nil { - return fmt.Errorf("failed to write genesis file: %w", err) - } else if n != len(bz) { - return fmt.Errorf("genesis file was not fully written: %w", err) +const stateChunkSize = 100000000 // 100 MB + +// byteChunk returns the chunk at a given index from the full byte slice. +func byteChunk(bz []byte, index int) []byte { + start := index * stateChunkSize + end := (index + 1) * stateChunkSize + switch { + case start >= len(bz): + return nil + case end >= len(bz): + return bz[start:] + default: + return bz[start:end] + } +} + +// byteChunks calculates the number of chunks in the byte slice. +func byteChunks(bz []byte) int { + return int(math.Ceil(float64(len(bz)) / float64(stateChunkSize))) +} + +// fileWrite writes the module's genesis state into files, each file containing +// maximum 100 MB of data +func fileWrite(modulePath, moduleName string, bz []byte) error { + chunks := byteChunks(bz) + // if the genesis state is empty, still create a new file to write nothing + if chunks == 0 { + chunks++ } + totalWritten := 0 + for i := 0; i < chunks; i++ { + f, err := createExportFile(modulePath, moduleName, i) + if err != nil { + return err + } + defer f.Close() + + n, err := f.Write(byteChunk(bz, i)) + if err != nil { + return fmt.Errorf("failed to write genesis file: %w", err) + } + totalWritten += n + } + + if totalWritten != len(bz) { + return fmt.Errorf("genesis file was not fully written: written %d/ total %d", totalWritten, len(bz)) + } + return nil } -func FileRead(f *os.File) ([]byte, error) { - fi, err := f.Stat() +// FileRead reads the module's genesus state given the file path and the module name, returns json encoded +// data +func FileRead(modulePath string, moduleName string) ([]byte, error) { + files, err := ioutil.ReadDir(modulePath) if err != nil { - return nil, fmt.Errorf("failed to stat file: %w", err) + return nil, fmt.Errorf("failed to read folder from %s: %w", modulePath, err) } - bz := make([]byte, fi.Size()) - if n, err := f.Read(bz); err != nil { - return nil, fmt.Errorf("failed to read genesis file: %w", err) - } else if n != int(fi.Size()) { - return nil, fmt.Errorf("couldn't read entire genesis file, read: %d, file size: %d", n, fi.Size()) + var buf bytes.Buffer + for i := 0; i < len(files); i++ { + f, err := openModuleStateFile(modulePath, moduleName, i) + if err != nil { + panic(fmt.Sprintf("failed to open genesis file from module %s: %v", moduleName, err)) + } + defer f.Close() + + fi, err := f.Stat() + if err != nil { + return nil, fmt.Errorf("failed to stat file: %w", err) + } + + n, err := buf.ReadFrom(f) + if err != nil { + return nil, fmt.Errorf("failed to read file %s: %w", f.Name(), err) + } else if n != fi.Size() { + return nil, fmt.Errorf("couldn't read entire file: %s, read: %d, file size: %d", f.Name(), n, fi.Size()) + } } - return bz, nil + return buf.Bytes(), nil } diff --git a/x/genutil/client/cli/validate_genesis.go b/x/genutil/client/cli/validate_genesis.go index f5dba8de6c3e..4fe86e56894d 100644 --- a/x/genutil/client/cli/validate_genesis.go +++ b/x/genutil/client/cli/validate_genesis.go @@ -57,13 +57,7 @@ func ValidateGenesisCmd(mbm module.BasicManager) *cobra.Command { } for _, b := range mbm { - f, err := module.OpenGenesisModuleFile(filepath.Join(genesisFilePath, b.Name()), b.Name()) - if err != nil { - return err - } - defer f.Close() - - bz, err := module.FileRead(f) + bz, err := module.FileRead(filepath.Join(genesisFilePath, b.Name()), b.Name()) if err != nil { return err } From e793b3ac6b94967249d366cf1b3db9ecf060f96c Mon Sep 17 00:00:00 2001 From: jay tseng Date: Tue, 13 Sep 2022 11:49:42 -0400 Subject: [PATCH 04/17] fix file close in loops --- types/module/module.go | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/types/module/module.go b/types/module/module.go index 4941d6b3d3f3..5dfbb8d77a08 100644 --- a/types/module/module.go +++ b/types/module/module.go @@ -673,9 +673,9 @@ func fileWrite(modulePath, moduleName string, bz []byte) error { if err != nil { return err } - defer f.Close() n, err := f.Write(byteChunk(bz, i)) + f.Close() if err != nil { return fmt.Errorf("failed to write genesis file: %w", err) } @@ -699,22 +699,27 @@ func FileRead(modulePath string, moduleName string) ([]byte, error) { var buf bytes.Buffer for i := 0; i < len(files); i++ { - f, err := openModuleStateFile(modulePath, moduleName, i) - if err != nil { - panic(fmt.Sprintf("failed to open genesis file from module %s: %v", moduleName, err)) - } - defer f.Close() + if err := func() error { + f, err := openModuleStateFile(modulePath, moduleName, i) + if err != nil { + panic(fmt.Sprintf("failed to open genesis file from module %s: %v", moduleName, err)) + } + defer f.Close() - fi, err := f.Stat() - if err != nil { - return nil, fmt.Errorf("failed to stat file: %w", err) - } + fi, err := f.Stat() + if err != nil { + return fmt.Errorf("failed to stat file: %w", err) + } - n, err := buf.ReadFrom(f) - if err != nil { - return nil, fmt.Errorf("failed to read file %s: %w", f.Name(), err) - } else if n != fi.Size() { - return nil, fmt.Errorf("couldn't read entire file: %s, read: %d, file size: %d", f.Name(), n, fi.Size()) + n, err := buf.ReadFrom(f) + if err != nil { + return fmt.Errorf("failed to read file %s: %w", f.Name(), err) + } else if n != fi.Size() { + return fmt.Errorf("couldn't read entire file: %s, read: %d, file size: %d", f.Name(), n, fi.Size()) + } + return nil + }(); err != nil { + return nil, err } } From a4ecfec484718d2323f32b8dc0e5f9610dc1a4f9 Mon Sep 17 00:00:00 2001 From: jay tseng Date: Tue, 13 Sep 2022 12:08:18 -0400 Subject: [PATCH 05/17] fix non-determinism function --- types/module/module.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/types/module/module.go b/types/module/module.go index 5dfbb8d77a08..c1b47b4554ca 100644 --- a/types/module/module.go +++ b/types/module/module.go @@ -33,7 +33,6 @@ import ( "encoding/json" "fmt" "io/ioutil" - "math" "os" "path/filepath" "sort" @@ -656,7 +655,12 @@ func byteChunk(bz []byte, index int) []byte { // byteChunks calculates the number of chunks in the byte slice. func byteChunks(bz []byte) int { - return int(math.Ceil(float64(len(bz)) / float64(stateChunkSize))) + bzs := len(bz) + if bzs%stateChunkSize == 0 { + return bzs / stateChunkSize + } + + return bzs/stateChunkSize + 1 } // fileWrite writes the module's genesis state into files, each file containing From b2e8d03305b92f122957448c78f1c6a0ae4a6be2 Mon Sep 17 00:00:00 2001 From: jay tseng Date: Tue, 13 Sep 2022 16:34:07 -0400 Subject: [PATCH 06/17] fix empty validator when calling InitChainer first time --- runtime/app.go | 4 ++++ simapp/app.go | 14 +++++++------- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/runtime/app.go b/runtime/app.go index 5cc714e8ade6..9be4192c7401 100644 --- a/runtime/app.go +++ b/runtime/app.go @@ -121,6 +121,10 @@ func (a *App) EndBlocker(ctx sdk.Context, req abci.RequestEndBlock) abci.Respons // InitChainer initializes the chain. func (a *App) InitChainer(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain { + if len(a.ModuleManager.GenesisPath) > 0 { + return a.ModuleManager.InitGenesis(ctx, a.cdc, nil) + } + var genesisState map[string]json.RawMessage if err := json.Unmarshal(req.AppStateBytes, &genesisState); err != nil { panic(err) diff --git a/simapp/app.go b/simapp/app.go index 12a211a711ff..b27eb1be522a 100644 --- a/simapp/app.go +++ b/simapp/app.go @@ -327,13 +327,13 @@ func (app *SimApp) InitChainer(ctx sdk.Context, req abci.RequestInitChain) abci. jsonObj := make(map[string]json.RawMessage) jsonObj["module_genesis_state"] = []byte("true") loadAppStateFromFolder, _ := json.Marshal(jsonObj) - var genesisState GenesisState - if bytes.Equal(loadAppStateFromFolder, req.AppStateBytes) { - app.ModuleManager.SetGenesisPath(filepath.Join(app.homepath, "config", "genesis")) - } else { - if err := json.Unmarshal(req.AppStateBytes, &genesisState); err != nil { - panic(err) - } + buf := bytes.NewBuffer(nil) + if err := json.Compact(buf, req.AppStateBytes); err != nil { + panic(err) + } + + if bytes.Equal(loadAppStateFromFolder, buf.Bytes()) { + app.App.ModuleManager.SetGenesisPath(filepath.Join(app.homepath, "config", "genesis")) } app.UpgradeKeeper.SetModuleVersionMap(ctx, app.ModuleManager.GetVersionMap()) From 61080bf51be4ebcb17885d9dcd717870289ea0ef Mon Sep 17 00:00:00 2001 From: jay tseng Date: Mon, 19 Sep 2022 16:45:33 -0400 Subject: [PATCH 07/17] fix lint --- types/module/module.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/types/module/module.go b/types/module/module.go index c1b47b4554ca..26a45d57b62c 100644 --- a/types/module/module.go +++ b/types/module/module.go @@ -32,7 +32,6 @@ import ( "bytes" "encoding/json" "fmt" - "io/ioutil" "os" "path/filepath" "sort" @@ -696,7 +695,7 @@ func fileWrite(modulePath, moduleName string, bz []byte) error { // FileRead reads the module's genesus state given the file path and the module name, returns json encoded // data func FileRead(modulePath string, moduleName string) ([]byte, error) { - files, err := ioutil.ReadDir(modulePath) + files, err := os.ReadDir(modulePath) if err != nil { return nil, fmt.Errorf("failed to read folder from %s: %w", modulePath, err) } From 757cd482c0082e6bf30719d037711cd827c6b8e8 Mon Sep 17 00:00:00 2001 From: jay tseng Date: Wed, 28 Sep 2022 15:30:50 -0400 Subject: [PATCH 08/17] fix gosec errors --- simapp/app.go | 5 ++++- types/module/module.go | 21 +++++++++++++++++---- x/genutil/client/cli/validate_genesis.go | 13 ++++++++----- 3 files changed, 29 insertions(+), 10 deletions(-) diff --git a/simapp/app.go b/simapp/app.go index b27eb1be522a..5a28c61ef422 100644 --- a/simapp/app.go +++ b/simapp/app.go @@ -326,7 +326,10 @@ func (app *SimApp) Name() string { return app.BaseApp.Name() } func (app *SimApp) InitChainer(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain { jsonObj := make(map[string]json.RawMessage) jsonObj["module_genesis_state"] = []byte("true") - loadAppStateFromFolder, _ := json.Marshal(jsonObj) + loadAppStateFromFolder, err := json.Marshal(jsonObj) + if err != nil { + panic(err) + } buf := bytes.NewBuffer(nil) if err := json.Compact(buf, req.AppStateBytes); err != nil { panic(err) diff --git a/types/module/module.go b/types/module/module.go index 26a45d57b62c..608324c96691 100644 --- a/types/module/module.go +++ b/types/module/module.go @@ -617,7 +617,7 @@ func createExportFile(exportPath string, moduleName string, index int) (*os.File return nil, fmt.Errorf("failed to create directory: %w", err) } - fp := filepath.Join(exportPath, fmt.Sprintf("genesis_%s_%d.bin", moduleName, index)) + fp := filepath.Join(filepath.Clean(exportPath), fmt.Sprintf("genesis_%s_%d.bin", moduleName, index)) f, err := os.Create(fp) if err != nil { return nil, fmt.Errorf("failed to create file: %w", err) @@ -628,7 +628,7 @@ func createExportFile(exportPath string, moduleName string, index int) (*os.File func openModuleStateFile(importPath string, moduleName string, index int) (*os.File, error) { fp := filepath.Join(importPath, fmt.Sprintf("genesis_%s_%d.bin", moduleName, index)) - f, err := os.OpenFile(fp, os.O_RDONLY, 0o600) + f, err := os.OpenFile(filepath.Clean(fp), os.O_RDONLY, 0o600) if err != nil { return nil, fmt.Errorf("failed to open file: %w", err) } @@ -677,8 +677,15 @@ func fileWrite(modulePath, moduleName string, bz []byte) error { return err } + defer func() error { + err := f.Close() + if err != nil { + return fmt.Errorf("failed to close file: %w", err) + } + return nil + }() + n, err := f.Write(byteChunk(bz, i)) - f.Close() if err != nil { return fmt.Errorf("failed to write genesis file: %w", err) } @@ -707,7 +714,13 @@ func FileRead(modulePath string, moduleName string) ([]byte, error) { if err != nil { panic(fmt.Sprintf("failed to open genesis file from module %s: %v", moduleName, err)) } - defer f.Close() + defer func() error { + err := f.Close() + if err != nil { + return fmt.Errorf("failed to close file: %w", err) + } + return nil + }() fi, err := f.Stat() if err != nil { diff --git a/x/genutil/client/cli/validate_genesis.go b/x/genutil/client/cli/validate_genesis.go index 4fe86e56894d..5830755885c5 100644 --- a/x/genutil/client/cli/validate_genesis.go +++ b/x/genutil/client/cli/validate_genesis.go @@ -50,20 +50,23 @@ func ValidateGenesisCmd(mbm module.BasicManager) *cobra.Command { jsonObj := make(map[string]json.RawMessage) jsonObj["module_genesis_state"] = []byte("true") - loadAppStateFromFolder, _ := json.Marshal(jsonObj) + loadAppStateFromFolder, err := json.Marshal(jsonObj) + if err != nil { + return fmt.Errorf("cannot marshal the module_genesis_state object, err :%v", err) + } if bytes.Equal(genDoc.AppState, loadAppStateFromFolder) { return fmt.Errorf("genesisAppState is not equal to expectedAppState, expect: %v, actual: %v", loadAppStateFromFolder, genDoc.AppState) } - for _, b := range mbm { - bz, err := module.FileRead(filepath.Join(genesisFilePath, b.Name()), b.Name()) + for _, appModule := range mbm { + bz, err := module.FileRead(filepath.Join(genesisFilePath, appModule.Name()), appModule.Name()) if err != nil { return err } - if err = b.ValidateGenesis(cdc, clientCtx.TxConfig, bz); err != nil { - return fmt.Errorf("error validating genesis state in module %s: %v", b.Name(), err.Error()) + if err = appModule.ValidateGenesis(cdc, clientCtx.TxConfig, bz); err != nil { + return fmt.Errorf("error validating genesis state in module %s: %v", appModule.Name(), err.Error()) } } From 1f672e68cbd86705789deeb7c4f96e06308805d5 Mon Sep 17 00:00:00 2001 From: jay tseng Date: Wed, 28 Sep 2022 16:44:47 -0400 Subject: [PATCH 09/17] fix legacy app --- simapp/app_legacy.go | 34 ++++++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/simapp/app_legacy.go b/simapp/app_legacy.go index 36940f250abe..9432079fec2d 100644 --- a/simapp/app_legacy.go +++ b/simapp/app_legacy.go @@ -3,6 +3,7 @@ package simapp import ( + "bytes" "encoding/json" "fmt" "io" @@ -195,6 +196,12 @@ type SimApp struct { // module configurator configurator module.Configurator + + // the root folder of the app config and data + homepath string + + // the path for the genesis state exporting + exportpath string } func init() { @@ -258,6 +265,12 @@ func NewSimApp( memKeys: memKeys, } + app.homepath = cast.ToString(appOpts.Get(flags.FlagHome)) + ep := cast.ToString(appOpts.Get(flags.FlagGenesisFilePath)) + if len(ep) > 0 { + app.exportpath = filepath.Join(ep, "genesis") + } + app.ParamsKeeper = initParamsKeeper(appCodec, legacyAmino, keys[paramstypes.StoreKey], tkeys[paramstypes.TStoreKey]) // set the BaseApp's parameter store @@ -544,10 +557,27 @@ func (a *SimApp) Configurator() module.Configurator { // InitChainer application update at chain initialization func (app *SimApp) InitChainer(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain { - var genesisState GenesisState - if err := json.Unmarshal(req.AppStateBytes, &genesisState); err != nil { + jsonObj := make(map[string]json.RawMessage) + jsonObj["module_genesis_state"] = []byte("true") + loadAppStateFromFolder, err := json.Marshal(jsonObj) + if err != nil { + panic(err) + } + + buf := bytes.NewBuffer(nil) + if err := json.Compact(buf, req.AppStateBytes); err != nil { panic(err) } + + var genesisState GenesisState + if bytes.Equal(loadAppStateFromFolder, buf.Bytes()) { + app.ModuleManager.SetGenesisPath(filepath.Join(app.homepath, "config", "genesis")) + } else { + if err := json.Unmarshal(req.AppStateBytes, &genesisState); err != nil { + panic(err) + } + } + app.UpgradeKeeper.SetModuleVersionMap(ctx, app.ModuleManager.GetVersionMap()) return app.ModuleManager.InitGenesis(ctx, app.appCodec, genesisState) } From c199b2b7e5d70733904bd3389fc49a63de9fe86a Mon Sep 17 00:00:00 2001 From: jay tseng Date: Thu, 29 Sep 2022 16:32:35 -0400 Subject: [PATCH 10/17] add type.module tests --- types/module/module.go | 26 ++--- types/module/module_test.go | 223 ++++++++++++++++++++++++++++++++++++ 2 files changed, 236 insertions(+), 13 deletions(-) diff --git a/types/module/module.go b/types/module/module.go index 608324c96691..00b0555101c3 100644 --- a/types/module/module.go +++ b/types/module/module.go @@ -359,7 +359,7 @@ func (m *Manager) ExportGenesisForModules(ctx sdk.Context, cdc codec.JSONCodec, fmt.Printf("exporting module: %s,path: %s\n", moduleName, modulePath) bz := m.Modules[moduleName].ExportGenesis(ctx, cdc) - if err := fileWrite(modulePath, moduleName, bz); err != nil { + if err := FileWrite(modulePath, moduleName, bz); err != nil { return nil, fmt.Errorf("ExportGenesis to file failed, module=%s err=%v", moduleName, err) } } @@ -612,7 +612,7 @@ func DefaultMigrationsOrder(modules []string) []string { return out } -func createExportFile(exportPath string, moduleName string, index int) (*os.File, error) { +func CreateExportFile(exportPath string, moduleName string, index int) (*os.File, error) { if err := os.MkdirAll(exportPath, 0o700); err != nil { return nil, fmt.Errorf("failed to create directory: %w", err) } @@ -626,7 +626,7 @@ func createExportFile(exportPath string, moduleName string, index int) (*os.File return f, nil } -func openModuleStateFile(importPath string, moduleName string, index int) (*os.File, error) { +func OpenModuleStateFile(importPath string, moduleName string, index int) (*os.File, error) { fp := filepath.Join(importPath, fmt.Sprintf("genesis_%s_%d.bin", moduleName, index)) f, err := os.OpenFile(filepath.Clean(fp), os.O_RDONLY, 0o600) if err != nil { @@ -636,12 +636,12 @@ func openModuleStateFile(importPath string, moduleName string, index int) (*os.F return f, nil } -const stateChunkSize = 100000000 // 100 MB +const StateChunkSize = 100000000 // 100 MB // byteChunk returns the chunk at a given index from the full byte slice. func byteChunk(bz []byte, index int) []byte { - start := index * stateChunkSize - end := (index + 1) * stateChunkSize + start := index * StateChunkSize + end := (index + 1) * StateChunkSize switch { case start >= len(bz): return nil @@ -655,16 +655,16 @@ func byteChunk(bz []byte, index int) []byte { // byteChunks calculates the number of chunks in the byte slice. func byteChunks(bz []byte) int { bzs := len(bz) - if bzs%stateChunkSize == 0 { - return bzs / stateChunkSize + if bzs%StateChunkSize == 0 { + return bzs / StateChunkSize } - return bzs/stateChunkSize + 1 + return bzs/StateChunkSize + 1 } -// fileWrite writes the module's genesis state into files, each file containing +// FileWrite writes the module's genesis state into files, each file containing // maximum 100 MB of data -func fileWrite(modulePath, moduleName string, bz []byte) error { +func FileWrite(modulePath, moduleName string, bz []byte) error { chunks := byteChunks(bz) // if the genesis state is empty, still create a new file to write nothing if chunks == 0 { @@ -672,7 +672,7 @@ func fileWrite(modulePath, moduleName string, bz []byte) error { } totalWritten := 0 for i := 0; i < chunks; i++ { - f, err := createExportFile(modulePath, moduleName, i) + f, err := CreateExportFile(modulePath, moduleName, i) if err != nil { return err } @@ -710,7 +710,7 @@ func FileRead(modulePath string, moduleName string) ([]byte, error) { var buf bytes.Buffer for i := 0; i < len(files); i++ { if err := func() error { - f, err := openModuleStateFile(modulePath, moduleName, i) + f, err := OpenModuleStateFile(modulePath, moduleName, i) if err != nil { panic(fmt.Sprintf("failed to open genesis file from module %s: %v", moduleName, err)) } diff --git a/types/module/module_test.go b/types/module/module_test.go index 4d81572f56b8..5f10351dafd1 100644 --- a/types/module/module_test.go +++ b/types/module/module_test.go @@ -1,8 +1,11 @@ package module_test import ( + "bytes" "encoding/json" "errors" + "fmt" + "path/filepath" "testing" "github.com/golang/mock/gomock" @@ -255,3 +258,223 @@ func TestManager_EndBlock(t *testing.T) { mockAppModule2.EXPECT().EndBlock(gomock.Any(), gomock.Eq(req)).Times(1).Return([]abci.ValidatorUpdate{{}}) require.Panics(t, func() { mm.EndBlock(sdk.Context{}, req) }) } + +func TestModule_CreateExportFile(t *testing.T) { + tmp := t.TempDir() + mod := "test" + index := 0 + + f1, err := module.CreateExportFile(tmp, mod, index) + require.NoError(t, err) + defer f1.Close() + + fname := filepath.Join(filepath.Clean(tmp), fmt.Sprintf("genesis_%s_%d.bin", mod, index)) + require.Equal(t, fname, f1.Name()) + + n, err := f1.WriteString("123") + require.NoError(t, err) + require.Equal(t, len("123"), n) + + // if we create the same export file again, the original will be truncated. + f2, err := module.CreateExportFile(tmp, mod, index) + require.NoError(t, err) + require.Equal(t, f1.Name(), f2.Name()) + defer f2.Close() + + fs, err := f2.Stat() + require.NoError(t, err) + require.Equal(t, int64(0), fs.Size()) +} + +func TestModule_OpenModuleStateFile(t *testing.T) { + tmp := t.TempDir() + mod := "test" + index := 0 + + fp1, err := module.CreateExportFile(tmp, mod, index) + require.NoError(t, err) + defer fp1.Close() + + fp2, err := module.OpenModuleStateFile(tmp, mod, index) + require.NoError(t, err) + defer fp2.Close() + + fp1Stat, err := fp1.Stat() + require.NoError(t, err) + + fp2Stat, err := fp2.Stat() + require.NoError(t, err) + + require.Equal(t, fp1Stat, fp2Stat) + + // should failed to file request file + _, err = module.OpenModuleStateFile(tmp, mod, index+1) + require.ErrorContains(t, err, "failed to open file") +} + +func TestManager_FileWrite(t *testing.T) { + tmp := t.TempDir() + mod := "test" + + // write empty state to file, will still create a file + err := module.FileWrite(tmp, mod, []byte{}) + require.NoError(t, err) + + fp, err := module.OpenModuleStateFile(tmp, mod, 0) + require.NoError(t, err) + defer fp.Close() + + fs, err := fp.Stat() + require.NoError(t, err) + require.Equal(t, int64(0), fs.Size()) + + // write bytes with maximum state chunk size, should only write 1 file + bz := make([]byte, module.StateChunkSize) + err = module.FileWrite(tmp, mod, bz) + require.NoError(t, err) + + fp0, err := module.OpenModuleStateFile(tmp, mod, 0) + require.NoError(t, err) + defer fp0.Close() + + var buf bytes.Buffer + n, err := buf.ReadFrom(fp0) + require.NoError(t, err) + require.Equal(t, int64(module.StateChunkSize), n) + require.True(t, bytes.Equal(bz, buf.Bytes())) + + // write bytes larger than maximum state chunk size, should create multiple files + bz = append(bz, []byte{1}...) + err = module.FileWrite(tmp, mod, bz) + require.NoError(t, err) + + // open the first file, read the content, and verify + fp0, err = module.OpenModuleStateFile(tmp, mod, 0) + require.NoError(t, err) + defer fp0.Close() + + buf.Reset() + n, err = buf.ReadFrom(fp0) + require.NoError(t, err) + require.Equal(t, int64(module.StateChunkSize), n) + require.True(t, bytes.Equal(bz[:module.StateChunkSize], buf.Bytes())) + + // open the second file, read the content, and verify + fp1, err := module.OpenModuleStateFile(tmp, mod, 1) + require.NoError(t, err) + defer fp1.Close() + + buf.Reset() + n, err = buf.ReadFrom(fp1) + require.NoError(t, err) + require.Equal(t, int64(1), n) + require.True(t, bytes.Equal(bz[module.StateChunkSize:], buf.Bytes())) +} + +func TestManager_FileRead(t *testing.T) { + tmp := t.TempDir() + mod := "test" + bz := make([]byte, module.StateChunkSize+1) + bz[module.StateChunkSize] = byte(1) + + err := module.FileWrite(tmp, mod, bz) + require.NoError(t, err) + + bzRead, err := module.FileRead(tmp, mod) + require.NoError(t, err) + require.True(t, bytes.Equal(bz, bzRead)) +} + +func TestManager_InitGenesisWithPath(t *testing.T) { + tmp := t.TempDir() + mockCtrl := gomock.NewController(t) + t.Cleanup(mockCtrl.Finish) + + mod1 := "module1" + mod2 := "module2" + mockAppModule1 := mock.NewMockAppModule(mockCtrl) + mockAppModule2 := mock.NewMockAppModule(mockCtrl) + mockAppModule1.EXPECT().Name().Times(2).Return(mod1) + mockAppModule2.EXPECT().Name().Times(2).Return(mod2) + mm := module.NewManager(mockAppModule1, mockAppModule2) + require.NotNil(t, mm) + require.Equal(t, 2, len(mm.Modules)) + + ctx := sdk.NewContext(nil, tmproto.Header{}, false, log.NewNopLogger()) + interfaceRegistry := types.NewInterfaceRegistry() + cdc := codec.NewProtoCodec(interfaceRegistry) + + appGenesisState := map[string]json.RawMessage{ + mod1: json.RawMessage(`{"key": "value1"}`), + mod2: json.RawMessage(`{"key": "value2"}`), + } + vs := []abci.ValidatorUpdate{{}} + + mockAppModule1.EXPECT().InitGenesis( + gomock.Eq(ctx), gomock.Eq(cdc), gomock.Eq(appGenesisState[mod1])).Times(1).Return(vs) + mockAppModule2.EXPECT().InitGenesis( + gomock.Eq(ctx), gomock.Eq(cdc), gomock.Eq(appGenesisState[mod2])).Times(1).Return([]abci.ValidatorUpdate{}) + + // we assume the genesis state has been exported to the module folders + err := module.FileWrite(filepath.Join(tmp, mod1), mod1, appGenesisState[mod1]) + require.NoError(t, err) + + err = module.FileWrite(filepath.Join(tmp, mod2), mod2, appGenesisState[mod2]) + require.NoError(t, err) + + // set the file import path + mm.SetGenesisPath(tmp) + var res abci.ResponseInitChain + require.NotPanics(t, func() { + res = mm.InitGenesis(ctx, cdc, nil) + }) + + // check the final import status + require.Equal(t, res, abci.ResponseInitChain{Validators: vs}) +} + +func TestManager_ExportGenesisWithPath(t *testing.T) { + tmp := t.TempDir() + mockCtrl := gomock.NewController(t) + t.Cleanup(mockCtrl.Finish) + + mod1 := "module1" + mod2 := "module2" + mockAppModule1 := mock.NewMockAppModule(mockCtrl) + mockAppModule2 := mock.NewMockAppModule(mockCtrl) + mockAppModule1.EXPECT().Name().Times(2).Return(mod1) + mockAppModule2.EXPECT().Name().Times(2).Return(mod2) + mm := module.NewManager(mockAppModule1, mockAppModule2) + require.NotNil(t, mm) + require.Equal(t, 2, len(mm.Modules)) + + ctx := sdk.Context{} + interfaceRegistry := types.NewInterfaceRegistry() + cdc := codec.NewProtoCodec(interfaceRegistry) + + appGenesisState := map[string]json.RawMessage{ + mod1: json.RawMessage(`{"key": "value1"}`), + mod2: json.RawMessage(`{"key": "value2"}`), + } + + // set the export state in each mock modules + mockAppModule1.EXPECT().ExportGenesis(gomock.Eq(ctx), gomock.Eq(cdc)).Times(1).Return(appGenesisState[mod1]) + mockAppModule2.EXPECT().ExportGenesis(gomock.Eq(ctx), gomock.Eq(cdc)).Times(1).Return(appGenesisState[mod2]) + + // assign the export path + mm.SetGenesisPath(tmp) + + // run actual genesis state export + actual, err := mm.ExportGenesis(ctx, cdc) + require.NoError(t, err) + require.Equal(t, make(map[string]json.RawMessage), actual) + + // check the state has been exported to the correct file path and verify the data + bz, err := module.FileRead(filepath.Join(tmp, mod1), mod1) + require.NoError(t, err) + require.Equal(t, appGenesisState[mod1], json.RawMessage(bz)) + + bz, err = module.FileRead(filepath.Join(tmp, mod2), mod2) + require.NoError(t, err) + require.Equal(t, appGenesisState[mod2], json.RawMessage(bz)) +} From e00801853abf40d8af9c1d65e2e3cb3a74f0b827 Mon Sep 17 00:00:00 2001 From: jay tseng Date: Mon, 3 Oct 2022 11:53:10 -0400 Subject: [PATCH 11/17] add sim test --- simapp/sim_test.go | 194 ++++++++++++++++++++++++++++++--------------- 1 file changed, 131 insertions(+), 63 deletions(-) diff --git a/simapp/sim_test.go b/simapp/sim_test.go index 46dc9a6b3cdf..36d47f6a96ce 100644 --- a/simapp/sim_test.go +++ b/simapp/sim_test.go @@ -20,6 +20,7 @@ import ( storetypes "github.com/cosmos/cosmos-sdk/store/types" "github.com/cosmos/cosmos-sdk/baseapp" + servertypes "github.com/cosmos/cosmos-sdk/server/types" "github.com/cosmos/cosmos-sdk/store" simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims" sdk "github.com/cosmos/cosmos-sdk/types" @@ -132,32 +133,8 @@ func TestAppImportExport(t *testing.T) { app := NewSimApp(logger, db, nil, true, appOptions, fauxMerkleModeOpt) require.Equal(t, "SimApp", app.Name()) - // Run randomized simulation - _, simParams, simErr := simulation.SimulateFromSeed( - t, - os.Stdout, - app.BaseApp, - AppStateFn(app.AppCodec(), app.SimulationManager()), - simtypes.RandomAccounts, // Replace with own random account function if using keys other than secp256k1 - simtestutil.SimulationOperations(app, app.AppCodec(), config), - ModuleAccountAddrs(), - config, - app.AppCodec(), - ) - - // export state and simParams before the simulation error is checked - err = simtestutil.CheckExportSimulation(app, config, simParams) - require.NoError(t, err) - require.NoError(t, simErr) - - if config.Commit { - simtestutil.PrintStats(db) - } - - fmt.Printf("exporting genesis...\n") - - exported, err := app.ExportAppStateAndValidators(false, []string{}, []string{}) - require.NoError(t, err) + // Run randomized simulation and export the genesis state + exported := randomSimulation(t, app, config, db) fmt.Printf("importing genesis...\n") @@ -187,43 +164,7 @@ func TestAppImportExport(t *testing.T) { } }() - ctxA := app.NewContext(true, tmproto.Header{Height: app.LastBlockHeight()}) - ctxB := newApp.NewContext(true, tmproto.Header{Height: app.LastBlockHeight()}) - newApp.ModuleManager.InitGenesis(ctxB, app.AppCodec(), genesisState) - newApp.StoreConsensusParams(ctxB, exported.ConsensusParams) - - fmt.Printf("comparing stores...\n") - - storeKeysPrefixes := []StoreKeysPrefixes{ - {app.GetKey(authtypes.StoreKey), newApp.GetKey(authtypes.StoreKey), [][]byte{}}, - { - app.GetKey(stakingtypes.StoreKey), newApp.GetKey(stakingtypes.StoreKey), - [][]byte{ - stakingtypes.UnbondingQueueKey, stakingtypes.RedelegationQueueKey, stakingtypes.ValidatorQueueKey, - stakingtypes.HistoricalInfoKey, stakingtypes.UnbondingIDKey, stakingtypes.UnbondingIndexKey, stakingtypes.UnbondingTypeKey, stakingtypes.ValidatorUpdatesKey, - }, - }, // ordering may change but it doesn't matter - {app.GetKey(slashingtypes.StoreKey), newApp.GetKey(slashingtypes.StoreKey), [][]byte{}}, - {app.GetKey(minttypes.StoreKey), newApp.GetKey(minttypes.StoreKey), [][]byte{}}, - {app.GetKey(distrtypes.StoreKey), newApp.GetKey(distrtypes.StoreKey), [][]byte{}}, - {app.GetKey(banktypes.StoreKey), newApp.GetKey(banktypes.StoreKey), [][]byte{banktypes.BalancesPrefix}}, - {app.GetKey(paramtypes.StoreKey), newApp.GetKey(paramtypes.StoreKey), [][]byte{}}, - {app.GetKey(govtypes.StoreKey), newApp.GetKey(govtypes.StoreKey), [][]byte{}}, - {app.GetKey(evidencetypes.StoreKey), newApp.GetKey(evidencetypes.StoreKey), [][]byte{}}, - {app.GetKey(capabilitytypes.StoreKey), newApp.GetKey(capabilitytypes.StoreKey), [][]byte{}}, - {app.GetKey(authzkeeper.StoreKey), newApp.GetKey(authzkeeper.StoreKey), [][]byte{authzkeeper.GrantKey, authzkeeper.GrantQueuePrefix}}, - } - - for _, skp := range storeKeysPrefixes { - storeA := ctxA.KVStore(skp.A) - storeB := ctxB.KVStore(skp.B) - - failedKVAs, failedKVBs := sdk.DiffKVStores(storeA, storeB, skp.Prefixes) - require.Equal(t, len(failedKVAs), len(failedKVBs), "unequal sets of key-values to compare") - - fmt.Printf("compared %d different key/value pairs between %s and %s\n", len(failedKVAs), skp.A, skp.B) - require.Equal(t, 0, len(failedKVAs), simtestutil.GetSimulationLog(skp.A.Name(), app.SimulationManager().StoreDecoders, failedKVAs, failedKVBs)) - } + checkStoresAfterInitGenesis(t, app, newApp, exported.ConsensusParams, genesisState) } func TestAppSimulationAfterImport(t *testing.T) { @@ -381,3 +322,130 @@ func TestAppStateDeterminism(t *testing.T) { } } } + +func TestAppImportExportWithAppStatePath(t *testing.T) { + config := simcli.NewConfigFromFlags() + config.ChainID = SimAppChainID + + db, dir, logger, skip, err := simtestutil.SetupSimulation(config, "leveldb-app-sim", "Simulation", simcli.FlagVerboseValue, simcli.FlagEnabledValue) + if skip { + t.Skip("skipping application import/export simulation") + } + require.NoError(t, err, "simulation setup failed") + + defer func() { + require.NoError(t, db.Close()) + require.NoError(t, os.RemoveAll(dir)) + }() + + tmp := t.TempDir() + + appOptions := make(simtestutil.AppOptionsMap, 0) + appOptions[flags.FlagHome] = DefaultNodeHome + appOptions[server.FlagInvCheckPeriod] = simcli.FlagPeriodValue + appOptions[flags.FlagGenesisFilePath] = tmp + + app := NewSimApp(logger, db, nil, true, appOptions, fauxMerkleModeOpt) + require.Equal(t, "SimApp", app.Name()) + + // Run randomized simulation and export the genesis state + exported := randomSimulation(t, app, config, db) + + fmt.Printf("importing genesis...\n") + + newDB, newDir, _, _, err := simtestutil.SetupSimulation(config, "leveldb-app-sim-2", "Simulation-2", simcli.FlagVerboseValue, simcli.FlagEnabledValue) + require.NoError(t, err, "simulation setup failed") + + defer func() { + require.NoError(t, newDB.Close()) + require.NoError(t, os.RemoveAll(newDir)) + }() + + newApp := NewSimApp(log.NewNopLogger(), newDB, nil, true, appOptions, fauxMerkleModeOpt) + require.Equal(t, "SimApp", newApp.Name()) + + defer func() { + if r := recover(); r != nil { + err := fmt.Sprintf("%v", r) + if !strings.Contains(err, "validator set is empty after InitGenesis") { + panic(r) + } + logger.Info("Skipping simulation as all validators have been unbonded") + logger.Info("err", err, "stacktrace", string(debug.Stack())) + } + }() + + newApp.ModuleManager.SetGenesisPath(tmp) + checkStoresAfterInitGenesis(t, app, newApp, exported.ConsensusParams, nil) +} + +func randomSimulation(t *testing.T, app *SimApp, config simtypes.Config, db dbm.DB) servertypes.ExportedApp { + _, simParams, simErr := simulation.SimulateFromSeed( + t, + os.Stdout, + app.BaseApp, + AppStateFn(app.AppCodec(), app.SimulationManager()), + simtypes.RandomAccounts, // Replace with own random account function if using keys other than secp256k1 + simtestutil.SimulationOperations(app, app.AppCodec(), config), + ModuleAccountAddrs(), + config, + app.AppCodec(), + ) + + // export state and simParams before the simulation error is checked + err := simtestutil.CheckExportSimulation(app, config, simParams) + require.NoError(t, err) + require.NoError(t, simErr) + + if config.Commit { + simtestutil.PrintStats(db) + } + + fmt.Printf("exporting genesis...\n") + + exported, err := app.ExportAppStateAndValidators(false, []string{}, []string{}) + require.NoError(t, err) + + return exported +} + +func checkStoresAfterInitGenesis(t *testing.T, app *SimApp, newApp *SimApp, consParams *tmproto.ConsensusParams, gs GenesisState) { + ctxA := app.NewContext(true, tmproto.Header{Height: app.LastBlockHeight()}) + ctxB := newApp.NewContext(true, tmproto.Header{Height: app.LastBlockHeight()}) + + newApp.ModuleManager.InitGenesis(ctxB, app.AppCodec(), gs) + newApp.StoreConsensusParams(ctxB, consParams) + + fmt.Printf("comparing stores...\n") + + storeKeysPrefixes := []StoreKeysPrefixes{ + {app.GetKey(authtypes.StoreKey), newApp.GetKey(authtypes.StoreKey), [][]byte{}}, + { + app.GetKey(stakingtypes.StoreKey), newApp.GetKey(stakingtypes.StoreKey), + [][]byte{ + stakingtypes.UnbondingQueueKey, stakingtypes.RedelegationQueueKey, stakingtypes.ValidatorQueueKey, + stakingtypes.HistoricalInfoKey, stakingtypes.UnbondingIDKey, stakingtypes.UnbondingIndexKey, stakingtypes.UnbondingTypeKey, stakingtypes.ValidatorUpdatesKey, + }, + }, // ordering may change but it doesn't matter + {app.GetKey(slashingtypes.StoreKey), newApp.GetKey(slashingtypes.StoreKey), [][]byte{}}, + {app.GetKey(minttypes.StoreKey), newApp.GetKey(minttypes.StoreKey), [][]byte{}}, + {app.GetKey(distrtypes.StoreKey), newApp.GetKey(distrtypes.StoreKey), [][]byte{}}, + {app.GetKey(banktypes.StoreKey), newApp.GetKey(banktypes.StoreKey), [][]byte{banktypes.BalancesPrefix}}, + {app.GetKey(paramtypes.StoreKey), newApp.GetKey(paramtypes.StoreKey), [][]byte{}}, + {app.GetKey(govtypes.StoreKey), newApp.GetKey(govtypes.StoreKey), [][]byte{}}, + {app.GetKey(evidencetypes.StoreKey), newApp.GetKey(evidencetypes.StoreKey), [][]byte{}}, + {app.GetKey(capabilitytypes.StoreKey), newApp.GetKey(capabilitytypes.StoreKey), [][]byte{}}, + {app.GetKey(authzkeeper.StoreKey), newApp.GetKey(authzkeeper.StoreKey), [][]byte{authzkeeper.GrantKey, authzkeeper.GrantQueuePrefix}}, + } + + for _, skp := range storeKeysPrefixes { + storeA := ctxA.KVStore(skp.A) + storeB := ctxB.KVStore(skp.B) + + failedKVAs, failedKVBs := sdk.DiffKVStores(storeA, storeB, skp.Prefixes) + require.Equal(t, len(failedKVAs), len(failedKVBs), "unequal sets of key-values to compare") + + fmt.Printf("compared %d different key/value pairs between %s and %s\n", len(failedKVAs), skp.A, skp.B) + require.Equal(t, 0, len(failedKVAs), simtestutil.GetSimulationLog(skp.A.Name(), app.SimulationManager().StoreDecoders, failedKVAs, failedKVBs)) + } +} From a0013d275870263647d527e7bf9832dcfa66c7e5 Mon Sep 17 00:00:00 2001 From: jay tseng Date: Thu, 6 Oct 2022 11:41:01 -0400 Subject: [PATCH 12/17] fix rebase error --- types/module/module.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/types/module/module.go b/types/module/module.go index 00b0555101c3..7c2f719196dd 100644 --- a/types/module/module.go +++ b/types/module/module.go @@ -363,6 +363,8 @@ func (m *Manager) ExportGenesisForModules(ctx sdk.Context, cdc codec.JSONCodec, return nil, fmt.Errorf("ExportGenesis to file failed, module=%s err=%v", moduleName, err) } } + + return genesisData, nil } // b. From e8d56643c1f1089a4fd02c4aab3c83548e1d85f3 Mon Sep 17 00:00:00 2001 From: jay tseng Date: Thu, 6 Oct 2022 12:16:14 -0400 Subject: [PATCH 13/17] add function comment --- types/module/module.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/types/module/module.go b/types/module/module.go index 7c2f719196dd..2855208f70a9 100644 --- a/types/module/module.go +++ b/types/module/module.go @@ -614,6 +614,7 @@ func DefaultMigrationsOrder(modules []string) []string { return out } +// CreateExportFile creates new file for exporting the module genesus state func CreateExportFile(exportPath string, moduleName string, index int) (*os.File, error) { if err := os.MkdirAll(exportPath, 0o700); err != nil { return nil, fmt.Errorf("failed to create directory: %w", err) @@ -628,6 +629,7 @@ func CreateExportFile(exportPath string, moduleName string, index int) (*os.File return f, nil } +// OpenModuleStateFile opens the genesis state file given the path, module name, and file index func OpenModuleStateFile(importPath string, moduleName string, index int) (*os.File, error) { fp := filepath.Join(importPath, fmt.Sprintf("genesis_%s_%d.bin", moduleName, index)) f, err := os.OpenFile(filepath.Clean(fp), os.O_RDONLY, 0o600) From 17d2f06af85f81ba71603e5ababc9078544135f3 Mon Sep 17 00:00:00 2001 From: jay tseng Date: Wed, 12 Oct 2022 16:06:34 -0400 Subject: [PATCH 14/17] simplify implementation --- client/flags/flags.go | 1 - runtime/types.go | 7 ++- server/export.go | 20 ++++---- server/types/app.go | 2 +- simapp/app.go | 15 ++---- simapp/app_legacy.go | 16 ++---- simapp/app_test.go | 2 +- simapp/export.go | 18 +++++-- simapp/sim_test.go | 15 +++--- simapp/simd/cmd/root.go | 3 +- tests/e2e/server/export_test.go | 2 +- testutil/sims/simulation_helpers.go | 2 +- types/module/module.go | 63 +++++++++++++----------- types/module/module_test.go | 10 ++-- x/genutil/client/cli/validate_genesis.go | 18 +++++-- 15 files changed, 103 insertions(+), 91 deletions(-) diff --git a/client/flags/flags.go b/client/flags/flags.go index 439f14b86106..faf7c16de0ea 100644 --- a/client/flags/flags.go +++ b/client/flags/flags.go @@ -77,7 +77,6 @@ const ( FlagTip = "tip" FlagAux = "aux" FlagOutput = tmcli.OutputFlag - FlagGenesisFilePath = "genesis-path" // Tendermint logging flags FlagLogLevel = "log_level" diff --git a/runtime/types.go b/runtime/types.go index fb7c65d82f62..f463676416dd 100644 --- a/runtime/types.go +++ b/runtime/types.go @@ -32,7 +32,12 @@ type AppI interface { LoadHeight(height int64) error // Exports the state of the application for a genesis file. - ExportAppStateAndValidators(forZeroHeight bool, jailAllowedAddrs []string, modulesToExport []string) (types.ExportedApp, error) + ExportAppStateAndValidators( + forZeroHeight bool, + jailAllowedAddrs []string, + modulesToExport []string, + splitModules bool, + ) (types.ExportedApp, error) // Helper for the simulation framework. SimulationManager() *module.SimulationManager diff --git a/server/export.go b/server/export.go index 64f999ca77a7..fdffb14bf339 100644 --- a/server/export.go +++ b/server/export.go @@ -21,6 +21,7 @@ const ( FlagForZeroHeight = "for-zero-height" FlagJailAllowedAddrs = "jail-allowed-addrs" FlagModulesToExport = "modules-to-export" + FlagSplitModules = "split-modules" ) // ExportCmd dumps app state to JSON. @@ -35,11 +36,6 @@ func ExportCmd(appExporter types.AppExporter, defaultNodeHome string) *cobra.Com homeDir, _ := cmd.Flags().GetString(flags.FlagHome) config.SetRoot(homeDir) - genesisExportPath, err := cmd.Flags().GetString(flags.FlagGenesisFilePath) - if err != nil { - return err - } - if _, err := os.Stat(config.GenesisFile()); os.IsNotExist(err) { return err } @@ -73,8 +69,9 @@ func ExportCmd(appExporter types.AppExporter, defaultNodeHome string) *cobra.Com forZeroHeight, _ := cmd.Flags().GetBool(FlagForZeroHeight) jailAllowedAddrs, _ := cmd.Flags().GetStringSlice(FlagJailAllowedAddrs) modulesToExport, _ := cmd.Flags().GetStringSlice(FlagModulesToExport) + splitModules, _ := cmd.Flags().GetBool(FlagSplitModules) - exported, err := appExporter(serverCtx.Logger, db, traceWriter, height, forZeroHeight, jailAllowedAddrs, serverCtx.Viper, modulesToExport) + exported, err := appExporter(serverCtx.Logger, db, traceWriter, height, forZeroHeight, jailAllowedAddrs, serverCtx.Viper, modulesToExport, splitModules) if err != nil { return fmt.Errorf("error exporting state: %v", err) } @@ -102,8 +99,13 @@ func ExportCmd(appExporter types.AppExporter, defaultNodeHome string) *cobra.Com }, } - if len(genesisExportPath) > 0 { - if err := doc.SaveAs(filepath.Join(genesisExportPath, "genesis", "genesis.json")); err != nil { + if splitModules { + wd, err := os.Getwd() + if err != nil { + return err + } + + if err := doc.SaveAs(filepath.Join(wd, "genesis", "genesis.json")); err != nil { return err } } else { @@ -128,7 +130,7 @@ func ExportCmd(appExporter types.AppExporter, defaultNodeHome string) *cobra.Com cmd.Flags().Bool(FlagForZeroHeight, false, "Export state to start at height zero (perform preproccessing)") cmd.Flags().StringSlice(FlagJailAllowedAddrs, []string{}, "Comma-separated list of operator addresses of jailed validators to unjail") cmd.Flags().StringSlice(FlagModulesToExport, []string{}, "Comma-separated list of modules to export. If empty, will export all modules") - cmd.Flags().String(flags.FlagGenesisFilePath, "", "Export state to the designated file path") + cmd.Flags().Bool(FlagSplitModules, false, "Export module state and store it as separate file(s) in genesis/[module] folder") return cmd } diff --git a/server/types/app.go b/server/types/app.go index b8cedff4fb9e..d8ec845acb1f 100644 --- a/server/types/app.go +++ b/server/types/app.go @@ -83,5 +83,5 @@ type ( // AppExporter is a function that dumps all app state to // JSON-serializable structure and returns the current validator set. - AppExporter func(log.Logger, dbm.DB, io.Writer, int64, bool, []string, AppOptions, []string) (ExportedApp, error) + AppExporter func(log.Logger, dbm.DB, io.Writer, int64, bool, []string, AppOptions, []string, bool) (ExportedApp, error) ) diff --git a/simapp/app.go b/simapp/app.go index 5a28c61ef422..c6ebabc80b8d 100644 --- a/simapp/app.go +++ b/simapp/app.go @@ -171,11 +171,7 @@ type SimApp struct { // simulation manager sm *module.SimulationManager - // the root folder of the app config and data - homepath string - - // the path for the genesis state exporting - exportpath string + appOpts servertypes.AppOptions } func init() { @@ -249,11 +245,7 @@ func NewSimApp( } app.App = appBuilder.Build(logger, db, traceStore, baseAppOptions...) - app.homepath = cast.ToString(appOpts.Get(flags.FlagHome)) - ep := cast.ToString(appOpts.Get(flags.FlagGenesisFilePath)) - if len(ep) > 0 { - app.exportpath = filepath.Join(ep, "genesis") - } + app.appOpts = appOpts // configure state listening capabilities using AppOptions // we are doing nothing with the returned streamingServices and waitGroup in this case @@ -336,7 +328,8 @@ func (app *SimApp) InitChainer(ctx sdk.Context, req abci.RequestInitChain) abci. } if bytes.Equal(loadAppStateFromFolder, buf.Bytes()) { - app.App.ModuleManager.SetGenesisPath(filepath.Join(app.homepath, "config", "genesis")) + homepath := cast.ToString(app.appOpts.Get(flags.FlagHome)) + app.App.ModuleManager.GenesisPath = filepath.Join(homepath, "config", "genesis") } app.UpgradeKeeper.SetModuleVersionMap(ctx, app.ModuleManager.GetVersionMap()) diff --git a/simapp/app_legacy.go b/simapp/app_legacy.go index 9432079fec2d..6f77768db55c 100644 --- a/simapp/app_legacy.go +++ b/simapp/app_legacy.go @@ -197,11 +197,7 @@ type SimApp struct { // module configurator configurator module.Configurator - // the root folder of the app config and data - homepath string - - // the path for the genesis state exporting - exportpath string + appOpts servertypes.AppOptions } func init() { @@ -263,12 +259,7 @@ func NewSimApp( keys: keys, tkeys: tkeys, memKeys: memKeys, - } - - app.homepath = cast.ToString(appOpts.Get(flags.FlagHome)) - ep := cast.ToString(appOpts.Get(flags.FlagGenesisFilePath)) - if len(ep) > 0 { - app.exportpath = filepath.Join(ep, "genesis") + appOpts: appOpts, } app.ParamsKeeper = initParamsKeeper(appCodec, legacyAmino, keys[paramstypes.StoreKey], tkeys[paramstypes.TStoreKey]) @@ -571,7 +562,8 @@ func (app *SimApp) InitChainer(ctx sdk.Context, req abci.RequestInitChain) abci. var genesisState GenesisState if bytes.Equal(loadAppStateFromFolder, buf.Bytes()) { - app.ModuleManager.SetGenesisPath(filepath.Join(app.homepath, "config", "genesis")) + homepath := cast.ToString(app.appOpts.Get(flags.FlagHome)) + app.ModuleManager.GenesisPath = filepath.Join(homepath, "config", "genesis") } else { if err := json.Unmarshal(req.AppStateBytes, &genesisState); err != nil { panic(err) diff --git a/simapp/app_test.go b/simapp/app_test.go index af92cbe077a0..4683f1be1098 100644 --- a/simapp/app_test.go +++ b/simapp/app_test.go @@ -60,7 +60,7 @@ func TestSimAppExportAndBlockedAddrs(t *testing.T) { logger2 := log.NewTMLogger(log.NewSyncWriter(os.Stdout)) // Making a new app object with the db, so that initchain hasn't been called app2 := NewSimApp(logger2, db, nil, true, simtestutil.NewAppOptionsWithFlagHome(DefaultNodeHome)) - _, err := app2.ExportAppStateAndValidators(false, []string{}, []string{}) + _, err := app2.ExportAppStateAndValidators(false, []string{}, []string{}, false) require.NoError(t, err, "ExportAppStateAndValidators should not have an error") } diff --git a/simapp/export.go b/simapp/export.go index 84bad04ace23..2cd25cbbb593 100644 --- a/simapp/export.go +++ b/simapp/export.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" "log" + "os" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" @@ -16,7 +17,12 @@ import ( // ExportAppStateAndValidators exports the state of the application for a genesis // file. -func (app *SimApp) ExportAppStateAndValidators(forZeroHeight bool, jailAllowedAddrs []string, modulesToExport []string) (servertypes.ExportedApp, error) { +func (app *SimApp) ExportAppStateAndValidators( + forZeroHeight bool, + jailAllowedAddrs []string, + modulesToExport []string, + splitModules bool, +) (servertypes.ExportedApp, error) { // as if they could withdraw from the start of the next block ctx := app.NewContext(true, tmproto.Header{Height: app.LastBlockHeight()}) @@ -28,14 +34,18 @@ func (app *SimApp) ExportAppStateAndValidators(forZeroHeight bool, jailAllowedAd app.prepForZeroHeightGenesis(ctx, jailAllowedAddrs) } - app.ModuleManager.SetGenesisPath(app.exportpath) - genState, err := app.ModuleManager.ExportGenesisForModules(ctx, app.appCodec, modulesToExport) + wd, err := os.Getwd() + if err != nil { + return servertypes.ExportedApp{}, err + } + app.ModuleManager.GenesisPath = wd + genState, err := app.ModuleManager.ExportGenesisForModules(ctx, app.appCodec, modulesToExport, splitModules) if err != nil { return servertypes.ExportedApp{}, err } var appState []byte - if app.exportpath != "" { + if splitModules { jsonObj := make(map[string]json.RawMessage) jsonObj["module_genesis_state"] = []byte("true") appState, err = json.MarshalIndent(jsonObj, "", " ") diff --git a/simapp/sim_test.go b/simapp/sim_test.go index 36d47f6a96ce..0ca5cb584072 100644 --- a/simapp/sim_test.go +++ b/simapp/sim_test.go @@ -134,7 +134,7 @@ func TestAppImportExport(t *testing.T) { require.Equal(t, "SimApp", app.Name()) // Run randomized simulation and export the genesis state - exported := randomSimulation(t, app, config, db) + exported := randomSimulation(t, app, config, db, false) fmt.Printf("importing genesis...\n") @@ -218,7 +218,7 @@ func TestAppSimulationAfterImport(t *testing.T) { fmt.Printf("exporting genesis...\n") - exported, err := app.ExportAppStateAndValidators(true, []string{}, []string{}) + exported, err := app.ExportAppStateAndValidators(true, []string{}, []string{}, false) require.NoError(t, err) fmt.Printf("importing genesis...\n") @@ -341,15 +341,14 @@ func TestAppImportExportWithAppStatePath(t *testing.T) { tmp := t.TempDir() appOptions := make(simtestutil.AppOptionsMap, 0) - appOptions[flags.FlagHome] = DefaultNodeHome + appOptions[flags.FlagHome] = tmp appOptions[server.FlagInvCheckPeriod] = simcli.FlagPeriodValue - appOptions[flags.FlagGenesisFilePath] = tmp app := NewSimApp(logger, db, nil, true, appOptions, fauxMerkleModeOpt) require.Equal(t, "SimApp", app.Name()) // Run randomized simulation and export the genesis state - exported := randomSimulation(t, app, config, db) + exported := randomSimulation(t, app, config, db, true) fmt.Printf("importing genesis...\n") @@ -375,11 +374,11 @@ func TestAppImportExportWithAppStatePath(t *testing.T) { } }() - newApp.ModuleManager.SetGenesisPath(tmp) + newApp.ModuleManager.GenesisPath = tmp checkStoresAfterInitGenesis(t, app, newApp, exported.ConsensusParams, nil) } -func randomSimulation(t *testing.T, app *SimApp, config simtypes.Config, db dbm.DB) servertypes.ExportedApp { +func randomSimulation(t *testing.T, app *SimApp, config simtypes.Config, db dbm.DB, splitModule bool) servertypes.ExportedApp { _, simParams, simErr := simulation.SimulateFromSeed( t, os.Stdout, @@ -403,7 +402,7 @@ func randomSimulation(t *testing.T, app *SimApp, config simtypes.Config, db dbm. fmt.Printf("exporting genesis...\n") - exported, err := app.ExportAppStateAndValidators(false, []string{}, []string{}) + exported, err := app.ExportAppStateAndValidators(false, []string{}, []string{}, splitModule) require.NoError(t, err) return exported diff --git a/simapp/simd/cmd/root.go b/simapp/simd/cmd/root.go index 7590217c7083..b105e5aae40a 100644 --- a/simapp/simd/cmd/root.go +++ b/simapp/simd/cmd/root.go @@ -319,6 +319,7 @@ func appExport( jailAllowedAddrs []string, appOpts servertypes.AppOptions, modulesToExport []string, + splitModules bool, ) (servertypes.ExportedApp, error) { var simApp *simapp.SimApp @@ -348,5 +349,5 @@ func appExport( simApp = simapp.NewSimApp(logger, db, traceStore, true, appOpts) } - return simApp.ExportAppStateAndValidators(forZeroHeight, jailAllowedAddrs, modulesToExport) + return simApp.ExportAppStateAndValidators(forZeroHeight, jailAllowedAddrs, modulesToExport, splitModules) } diff --git a/tests/e2e/server/export_test.go b/tests/e2e/server/export_test.go index b994d87706f8..4b77cbd3c366 100644 --- a/tests/e2e/server/export_test.go +++ b/tests/e2e/server/export_test.go @@ -169,7 +169,7 @@ func setupApp(t *testing.T, tempDir string) (*simapp.SimApp, context.Context, *t simApp = simapp.NewSimApp(logger, db, nil, true, appOptions) } - return simApp.ExportAppStateAndValidators(forZeroHeight, jailAllowedAddrs, modulesToExport) + return simApp.ExportAppStateAndValidators(forZeroHeight, jailAllowedAddrs, modulesToExport, false) }, tempDir) ctx := context.Background() diff --git a/testutil/sims/simulation_helpers.go b/testutil/sims/simulation_helpers.go index 4132007c0906..fc0ffeabbc4e 100644 --- a/testutil/sims/simulation_helpers.go +++ b/testutil/sims/simulation_helpers.go @@ -72,7 +72,7 @@ func SimulationOperations(app runtime.AppI, cdc codec.JSONCodec, config simtypes func CheckExportSimulation(app runtime.AppI, config simtypes.Config, params simtypes.Params) error { if config.ExportStatePath != "" { fmt.Println("exporting app state...") - exported, err := app.ExportAppStateAndValidators(false, nil, nil) + exported, err := app.ExportAppStateAndValidators(false, nil, nil, false) if err != nil { return err } diff --git a/types/module/module.go b/types/module/module.go index 2855208f70a9..7da991602a81 100644 --- a/types/module/module.go +++ b/types/module/module.go @@ -341,48 +341,45 @@ func (m *Manager) InitGenesis(ctx sdk.Context, cdc codec.JSONCodec, genesisData // ExportGenesis performs export genesis functionality for modules func (m *Manager) ExportGenesis(ctx sdk.Context, cdc codec.JSONCodec) (map[string]json.RawMessage, error) { - return m.ExportGenesisForModules(ctx, cdc, []string{}) + return m.ExportGenesisForModules(ctx, cdc, []string{}, false) } // ExportGenesisForModules performs export genesis functionality for modules -// There are 3 ways to export the module's genesis state -// a. export whole modules to file given the export path, separate by module name -// b. export whole modules -// c. export designated modules from the modulesToExport argument -func (m *Manager) ExportGenesisForModules(ctx sdk.Context, cdc codec.JSONCodec, modulesToExport []string) (map[string]json.RawMessage, error) { +func (m *Manager) ExportGenesisForModules( + ctx sdk.Context, + cdc codec.JSONCodec, + modulesToExport []string, + splitModules bool, +) (map[string]json.RawMessage, error) { genesisData := make(map[string]json.RawMessage) - // a. - if len(m.GenesisPath) > 0 { + if len(modulesToExport) == 0 { for _, moduleName := range m.OrderExportGenesis { - modulePath := filepath.Join(m.GenesisPath, moduleName) - fmt.Printf("exporting module: %s,path: %s\n", moduleName, modulePath) + if splitModules { + if err := m.exportModuleStateToFile(ctx, cdc, moduleName); err != nil { + return nil, err + } + } else { + genesisData[moduleName] = m.Modules[moduleName].ExportGenesis(ctx, cdc) - bz := m.Modules[moduleName].ExportGenesis(ctx, cdc) - if err := FileWrite(modulePath, moduleName, bz); err != nil { - return nil, fmt.Errorf("ExportGenesis to file failed, module=%s err=%v", moduleName, err) } } - return genesisData, nil } - // b. - if len(modulesToExport) == 0 { - for _, moduleName := range m.OrderExportGenesis { - genesisData[moduleName] = m.Modules[moduleName].ExportGenesis(ctx, cdc) - } - - return genesisData, nil - } - - // c. // verify modules exists in app, so that we don't panic in the middle of an export if err := m.checkModulesExists(modulesToExport); err != nil { panic(err) } + for _, moduleName := range modulesToExport { - genesisData[moduleName] = m.Modules[moduleName].ExportGenesis(ctx, cdc) + if splitModules { + if err := m.exportModuleStateToFile(ctx, cdc, moduleName); err != nil { + return nil, err + } + } else { + genesisData[moduleName] = m.Modules[moduleName].ExportGenesis(ctx, cdc) + } } return genesisData, nil @@ -588,11 +585,6 @@ func (m *Manager) ModuleNames() []string { return maps.Keys(m.Modules) } -// SetGenesisPath sets the genesis binaries export/import path. -func (m *Manager) SetGenesisPath(path string) { - m.GenesisPath = path -} - // DefaultMigrationsOrder returns a default migrations order: ascending alphabetical by module name, // except x/auth which will run last, see: // https://github.com/cosmos/cosmos-sdk/issues/10591 @@ -745,3 +737,14 @@ func FileRead(modulePath string, moduleName string) ([]byte, error) { return buf.Bytes(), nil } + +func (m *Manager) exportModuleStateToFile(ctx sdk.Context, cdc codec.JSONCodec, moduleName string) error { + modulePath := filepath.Join(m.GenesisPath, moduleName) + fmt.Printf("exporting module: %s,path: %s\n", moduleName, modulePath) + + bz := m.Modules[moduleName].ExportGenesis(ctx, cdc) + if err := FileWrite(modulePath, moduleName, bz); err != nil { + return fmt.Errorf("ExportGenesis to file failed, module=%s err=%v", moduleName, err) + } + return nil +} diff --git a/types/module/module_test.go b/types/module/module_test.go index 5f10351dafd1..1bddf1b4ef66 100644 --- a/types/module/module_test.go +++ b/types/module/module_test.go @@ -206,12 +206,12 @@ func TestManager_ExportGenesis(t *testing.T) { require.NoError(t, err) require.Equal(t, want, actual) - actual, err = mm.ExportGenesisForModules(ctx, cdc, []string{}) + actual, err = mm.ExportGenesisForModules(ctx, cdc, []string{}, false) require.NoError(t, err) require.Equal(t, want, actual) require.Panics(t, func() { - mm.ExportGenesisForModules(ctx, cdc, []string{"module1", "modulefoo"}) + mm.ExportGenesisForModules(ctx, cdc, []string{"module1", "modulefoo"}, false) }) } @@ -423,7 +423,7 @@ func TestManager_InitGenesisWithPath(t *testing.T) { require.NoError(t, err) // set the file import path - mm.SetGenesisPath(tmp) + mm.GenesisPath = tmp var res abci.ResponseInitChain require.NotPanics(t, func() { res = mm.InitGenesis(ctx, cdc, nil) @@ -462,10 +462,10 @@ func TestManager_ExportGenesisWithPath(t *testing.T) { mockAppModule2.EXPECT().ExportGenesis(gomock.Eq(ctx), gomock.Eq(cdc)).Times(1).Return(appGenesisState[mod2]) // assign the export path - mm.SetGenesisPath(tmp) + mm.GenesisPath = tmp // run actual genesis state export - actual, err := mm.ExportGenesis(ctx, cdc) + actual, err := mm.ExportGenesisForModules(ctx, cdc, []string{}, true) require.NoError(t, err) require.Equal(t, make(map[string]json.RawMessage), actual) diff --git a/x/genutil/client/cli/validate_genesis.go b/x/genutil/client/cli/validate_genesis.go index 5830755885c5..374ab517c70d 100644 --- a/x/genutil/client/cli/validate_genesis.go +++ b/x/genutil/client/cli/validate_genesis.go @@ -4,18 +4,21 @@ import ( "bytes" "encoding/json" "fmt" + "os" "path/filepath" "github.com/spf13/cobra" tmtypes "github.com/tendermint/tendermint/types" "github.com/cosmos/cosmos-sdk/client" - "github.com/cosmos/cosmos-sdk/client/flags" "github.com/cosmos/cosmos-sdk/server" "github.com/cosmos/cosmos-sdk/types/module" ) -const chainUpgradeGuide = "https://github.com/cosmos/cosmos-sdk/blob/main/UPGRADING.md" +const ( + chainUpgradeGuide = "https://github.com/cosmos/cosmos-sdk/blob/main/UPGRADING.md" + FlagValidateSplitModules = "validate-split-modules" +) // ValidateGenesisCmd takes a genesis file, and makes sure that it is valid. func ValidateGenesisCmd(mbm module.BasicManager) *cobra.Command { @@ -37,12 +40,17 @@ func ValidateGenesisCmd(mbm module.BasicManager) *cobra.Command { genesis = args[0] } - genesisFilePath, err := cmd.Flags().GetString(flags.FlagGenesisFilePath) + splitModules, err := cmd.Flags().GetBool(FlagValidateSplitModules) if err != nil { return err } - if len(genesisFilePath) > 0 { + if splitModules { + wd, err := os.Getwd() + if err != nil { + return err + } + genesisFilePath := filepath.Join(wd, "genesis") genDoc, err := validateGenDoc(filepath.Join(genesisFilePath, "genesis.json")) if err != nil { return err @@ -92,7 +100,7 @@ func ValidateGenesisCmd(mbm module.BasicManager) *cobra.Command { }, } - cmd.Flags().String(flags.FlagGenesisFilePath, "", "the file path of app genesis states") + cmd.Flags().Bool(FlagValidateSplitModules, false, "validate the modules' genesis state in appHome/genesis folder") return cmd } From 596598a1ba32e0b782f1ea1edd8889a13325d991 Mon Sep 17 00:00:00 2001 From: jay tseng Date: Wed, 12 Oct 2022 16:49:27 -0400 Subject: [PATCH 15/17] fix e2e tests --- tests/e2e/server/export_test.go | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/tests/e2e/server/export_test.go b/tests/e2e/server/export_test.go index 4b77cbd3c366..fc1f44d244a8 100644 --- a/tests/e2e/server/export_test.go +++ b/tests/e2e/server/export_test.go @@ -157,7 +157,16 @@ func setupApp(t *testing.T, tempDir string) (*simapp.SimApp, context.Context, *t app.Commit() cmd := server.ExportCmd( - func(_ log.Logger, _ dbm.DB, _ io.Writer, height int64, forZeroHeight bool, jailAllowedAddrs []string, appOptions types.AppOptions, modulesToExport []string) (types.ExportedApp, error) { + func( + _ log.Logger, + _ dbm.DB, + _ io.Writer, + height int64, + forZeroHeight bool, + jailAllowedAddrs []string, + appOptions types.AppOptions, + modulesToExport []string, + splitModules bool) (types.ExportedApp, error) { var simApp *simapp.SimApp if height != -1 { simApp = simapp.NewSimApp(logger, db, nil, false, appOptions) @@ -169,7 +178,7 @@ func setupApp(t *testing.T, tempDir string) (*simapp.SimApp, context.Context, *t simApp = simapp.NewSimApp(logger, db, nil, true, appOptions) } - return simApp.ExportAppStateAndValidators(forZeroHeight, jailAllowedAddrs, modulesToExport, false) + return simApp.ExportAppStateAndValidators(forZeroHeight, jailAllowedAddrs, modulesToExport, splitModules) }, tempDir) ctx := context.Background() From 9ddb7292ef4539127e76b791a91bd9996f718835 Mon Sep 17 00:00:00 2001 From: jay tseng Date: Wed, 12 Oct 2022 16:51:24 -0400 Subject: [PATCH 16/17] fix file close in the for loop --- types/module/module.go | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/types/module/module.go b/types/module/module.go index 7da991602a81..0022c40dafab 100644 --- a/types/module/module.go +++ b/types/module/module.go @@ -668,24 +668,29 @@ func FileWrite(modulePath, moduleName string, bz []byte) error { } totalWritten := 0 for i := 0; i < chunks; i++ { - f, err := CreateExportFile(modulePath, moduleName, i) - if err != nil { - return err - } + if err := func() error { + f, err := CreateExportFile(modulePath, moduleName, i) + if err != nil { + return err + } + + defer func() error { + err := f.Close() + if err != nil { + return fmt.Errorf("failed to close file: %w", err) + } + return nil + }() - defer func() error { - err := f.Close() + n, err := f.Write(byteChunk(bz, i)) if err != nil { - return fmt.Errorf("failed to close file: %w", err) + return fmt.Errorf("failed to write genesis file: %w", err) } + totalWritten += n return nil - }() - - n, err := f.Write(byteChunk(bz, i)) - if err != nil { - return fmt.Errorf("failed to write genesis file: %w", err) + }(); err != nil { + return err } - totalWritten += n } if totalWritten != len(bz) { From 12c9fad7aa5f8799c8ed3903bc4ec6d93d6bfdf7 Mon Sep 17 00:00:00 2001 From: jay tseng Date: Wed, 12 Oct 2022 16:56:52 -0400 Subject: [PATCH 17/17] update wording --- x/genutil/client/cli/validate_genesis.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x/genutil/client/cli/validate_genesis.go b/x/genutil/client/cli/validate_genesis.go index 374ab517c70d..f3bb2e9d18b6 100644 --- a/x/genutil/client/cli/validate_genesis.go +++ b/x/genutil/client/cli/validate_genesis.go @@ -100,7 +100,7 @@ func ValidateGenesisCmd(mbm module.BasicManager) *cobra.Command { }, } - cmd.Flags().Bool(FlagValidateSplitModules, false, "validate the modules' genesis state in appHome/genesis folder") + cmd.Flags().Bool(FlagValidateSplitModules, false, "validate the modules' genesis state in current working path") return cmd }