diff --git a/CHANGELOG.md b/CHANGELOG.md index 1bec30fd4a41..e98de25afdf5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -87,6 +87,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ * [#13651](https://github.com/cosmos/cosmos-sdk/pull/13651) Update `server/config/config.GetConfig` function. * [#13781](https://github.com/cosmos/cosmos-sdk/pull/13781) Remove `client/keys.KeysCdc`. * [#13803](https://github.com/cosmos/cosmos-sdk/pull/13803) Add an error log if iavl set operation failed. +* [#13802](https://github.com/cosmos/cosmos-sdk/pull/13802) Add --output-document flag to the export CLI command to allow writing genesis state to a file. ### State Machine Breaking diff --git a/server/export.go b/server/export.go index 80415679a709..0bdf518417e7 100644 --- a/server/export.go +++ b/server/export.go @@ -20,6 +20,7 @@ const ( FlagForZeroHeight = "for-zero-height" FlagJailAllowedAddrs = "jail-allowed-addrs" FlagModulesToExport = "modules-to-export" + FlagOutputDocument = "output-document" ) // ExportCmd dumps app state to JSON. @@ -67,6 +68,7 @@ func ExportCmd(appExporter types.AppExporter, defaultNodeHome string) *cobra.Com forZeroHeight, _ := cmd.Flags().GetBool(FlagForZeroHeight) jailAllowedAddrs, _ := cmd.Flags().GetStringSlice(FlagJailAllowedAddrs) modulesToExport, _ := cmd.Flags().GetStringSlice(FlagModulesToExport) + outputDocument, _ := cmd.Flags().GetString(FlagOutputDocument) exported, err := appExporter(serverCtx.Logger, db, traceWriter, height, forZeroHeight, jailAllowedAddrs, serverCtx.Viper, modulesToExport) if err != nil { @@ -106,7 +108,21 @@ func ExportCmd(appExporter types.AppExporter, defaultNodeHome string) *cobra.Com cmd.SetOut(cmd.OutOrStdout()) cmd.SetErr(cmd.OutOrStderr()) - cmd.Println(string(sdk.MustSortJSON(encoded))) + out := sdk.MustSortJSON(encoded) + + if outputDocument == "" { + cmd.Println(string(out)) + return nil + } + + var exportedGenDoc tmtypes.GenesisDoc + if err = tmjson.Unmarshal(out, &exportedGenDoc); err != nil { + return err + } + if err = exportedGenDoc.SaveAs(outputDocument); err != nil { + return err + } + return nil }, } @@ -116,6 +132,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(FlagOutputDocument, "", "Exported state is written to the given file instead of STDOUT") return cmd } diff --git a/tests/e2e/server/export_test.go b/tests/e2e/server/export_test.go index b994d87706f8..46a2e1d9fbd8 100644 --- a/tests/e2e/server/export_test.go +++ b/tests/e2e/server/export_test.go @@ -122,6 +122,47 @@ func TestExportCmd_Height(t *testing.T) { } } +func TestExportCmd_Output(t *testing.T) { + testCases := []struct { + name string + flags []string + outputDocument string + }{ + { + "should export state to the specified file", + []string{ + fmt.Sprintf("--%s=%s", server.FlagOutputDocument, "foobar.json"), + }, + "foobar.json", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + tempDir := t.TempDir() + _, ctx, _, cmd := setupApp(t, tempDir) + + output := &bytes.Buffer{} + cmd.SetOut(output) + args := append(tc.flags, fmt.Sprintf("--%s=%s", flags.FlagHome, tempDir)) + cmd.SetArgs(args) + require.NoError(t, cmd.ExecuteContext(ctx)) + + var exportedGenDoc tmtypes.GenesisDoc + f, err := os.ReadFile(tc.outputDocument) + if err != nil { + t.Fatalf("error reading exported genesis doc: %s", err) + } + require.NoError(t, tmjson.Unmarshal(f, &exportedGenDoc)) + + // Cleanup + if err = os.Remove(tc.outputDocument); err != nil { + t.Fatalf("error removing exported genesis doc: %s", err) + } + }) + } +} + func setupApp(t *testing.T, tempDir string) (*simapp.SimApp, context.Context, *tmtypes.GenesisDoc, *cobra.Command) { t.Helper()