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/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 80415679a709..fdffb14bf339 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" @@ -20,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. @@ -67,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) } @@ -96,17 +99,28 @@ 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 splitModules { + wd, err := os.Getwd() + if err != nil { + return err + } - cmd.SetOut(cmd.OutOrStdout()) - cmd.SetErr(cmd.OutOrStderr()) - cmd.Println(string(sdk.MustSortJSON(encoded))) + if err := doc.SaveAs(filepath.Join(wd, "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))) + } return nil }, } @@ -116,6 +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().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 44a2f5e6e110..c6ebabc80b8d 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,8 @@ type SimApp struct { // simulation manager sm *module.SimulationManager + + appOpts servertypes.AppOptions } func init() { @@ -239,6 +245,7 @@ func NewSimApp( } app.App = appBuilder.Build(logger, db, traceStore, baseAppOptions...) + app.appOpts = appOpts // configure state listening capabilities using AppOptions // we are doing nothing with the returned streamingServices and waitGroup in this case @@ -309,6 +316,22 @@ 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, err := json.Marshal(jsonObj) + if 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()) { + homepath := cast.ToString(app.appOpts.Get(flags.FlagHome)) + app.App.ModuleManager.GenesisPath = filepath.Join(homepath, "config", "genesis") + } + app.UpgradeKeeper.SetModuleVersionMap(ctx, app.ModuleManager.GetVersionMap()) return app.App.InitChainer(ctx, req) } diff --git a/simapp/app_legacy.go b/simapp/app_legacy.go index 36940f250abe..6f77768db55c 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,8 @@ type SimApp struct { // module configurator configurator module.Configurator + + appOpts servertypes.AppOptions } func init() { @@ -256,6 +259,7 @@ func NewSimApp( keys: keys, tkeys: tkeys, memKeys: memKeys, + appOpts: appOpts, } app.ParamsKeeper = initParamsKeeper(appCodec, legacyAmino, keys[paramstypes.StoreKey], tkeys[paramstypes.TStoreKey]) @@ -544,10 +548,28 @@ 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()) { + 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) + } + } + app.UpgradeKeeper.SetModuleVersionMap(ctx, app.ModuleManager.GetVersionMap()) return app.ModuleManager.InitGenesis(ctx, app.appCodec, genesisState) } 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 96e841064a5a..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,11 +34,30 @@ func (app *SimApp) ExportAppStateAndValidators(forZeroHeight bool, jailAllowedAd app.prepForZeroHeightGenesis(ctx, jailAllowedAddrs) } - genState := app.ModuleManager.ExportGenesisForModules(ctx, app.appCodec, modulesToExport) - appState, err := json.MarshalIndent(genState, "", " ") + 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 splitModules { + 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{ diff --git a/simapp/sim_test.go b/simapp/sim_test.go index 46dc9a6b3cdf..0ca5cb584072 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, false) 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) { @@ -277,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") @@ -381,3 +322,129 @@ 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] = tmp + appOptions[server.FlagInvCheckPeriod] = simcli.FlagPeriodValue + + 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, true) + + 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.GenesisPath = tmp + checkStoresAfterInitGenesis(t, app, newApp, exported.ConsensusParams, nil) +} + +func randomSimulation(t *testing.T, app *SimApp, config simtypes.Config, db dbm.DB, splitModule bool) 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{}, splitModule) + 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)) + } +} 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..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) + return simApp.ExportAppStateAndValidators(forZeroHeight, jailAllowedAddrs, modulesToExport, splitModules) }, 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 45356e239915..0022c40dafab 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" + "os" + "path/filepath" "sort" "github.com/grpc-ecosystem/grpc-gateway/runtime" @@ -218,6 +221,7 @@ type Manager struct { OrderBeginBlockers []string OrderEndBlockers []string OrderMigrations []string + GenesisPath string } // NewManager creates a new Manager object @@ -235,6 +239,7 @@ func NewManager(modules ...AppModule) *Manager { OrderExportGenesis: modulesStr, OrderBeginBlockers: modulesStr, OrderEndBlockers: modulesStr, + GenesisPath: "", } } @@ -291,13 +296,28 @@ 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) + + bz, err := FileRead(modulePath, moduleName) + 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,19 +340,31 @@ 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 { - return m.ExportGenesisForModules(ctx, cdc, []string{}) +func (m *Manager) ExportGenesis(ctx sdk.Context, cdc codec.JSONCodec) (map[string]json.RawMessage, error) { + return m.ExportGenesisForModules(ctx, cdc, []string{}, false) } // ExportGenesisForModules performs export genesis functionality for modules -func (m *Manager) ExportGenesisForModules(ctx sdk.Context, cdc codec.JSONCodec, modulesToExport []string) map[string]json.RawMessage { +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) + if len(modulesToExport) == 0 { for _, moduleName := range m.OrderExportGenesis { - 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 + } + } + return genesisData, nil } // verify modules exists in app, so that we don't panic in the middle of an export @@ -341,10 +373,16 @@ func (m *Manager) ExportGenesisForModules(ctx sdk.Context, cdc codec.JSONCodec, } 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 + return genesisData, nil } // checkModulesExists verifies that all modules in the list exist in the app @@ -567,3 +605,151 @@ 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) + } + + 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) + } + + 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) + if err != nil { + return nil, fmt.Errorf("failed to open file: %w", err) + } + + return f, nil +} + +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 { + 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 +// 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++ { + 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 + }() + + n, err := f.Write(byteChunk(bz, i)) + if err != nil { + return fmt.Errorf("failed to write genesis file: %w", err) + } + totalWritten += n + return nil + }(); err != nil { + return err + } + } + + if totalWritten != len(bz) { + return fmt.Errorf("genesis file was not fully written: written %d/ total %d", totalWritten, len(bz)) + } + + return nil +} + +// 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 := os.ReadDir(modulePath) + if err != nil { + return nil, fmt.Errorf("failed to read folder from %s: %w", modulePath, err) + } + + var buf bytes.Buffer + for i := 0; i < len(files); i++ { + 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 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 { + return fmt.Errorf("failed to stat file: %w", err) + } + + 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 + } + } + + 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 dd6ad46b88e8..1bddf1b4ef66 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" @@ -198,11 +201,17 @@ 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{}, 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) }) } @@ -249,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.GenesisPath = 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.GenesisPath = tmp + + // run actual genesis state export + actual, err := mm.ExportGenesisForModules(ctx, cdc, []string{}, true) + 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)) +} 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..f3bb2e9d18b6 100644 --- a/x/genutil/client/cli/validate_genesis.go +++ b/x/genutil/client/cli/validate_genesis.go @@ -1,8 +1,11 @@ package cli import ( + "bytes" "encoding/json" "fmt" + "os" + "path/filepath" "github.com/spf13/cobra" tmtypes "github.com/tendermint/tendermint/types" @@ -12,11 +15,14 @@ import ( "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 { - 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 +40,69 @@ func ValidateGenesisCmd(mbm module.BasicManager) *cobra.Command { genesis = args[0] } - genDoc, err := validateGenDoc(genesis) + splitModules, err := cmd.Flags().GetBool(FlagValidateSplitModules) 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 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 + } + + jsonObj := make(map[string]json.RawMessage) + jsonObj["module_genesis_state"] = []byte("true") + 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 _, appModule := range mbm { + bz, err := module.FileRead(filepath.Join(genesisFilePath, appModule.Name()), appModule.Name()) + if err != nil { + return err + } + + 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()) + } + } - 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().Bool(FlagValidateSplitModules, false, "validate the modules' genesis state in current working path") + + return cmd } // validateGenDoc reads a genesis file and validates that it is a correct