From 6112a8b0fc5482e7930f5aa092a340dcef1a2a66 Mon Sep 17 00:00:00 2001 From: Usman Saleem <57341641+usmanmani1122@users.noreply.github.com> Date: Thu, 21 Nov 2024 10:36:27 +0500 Subject: [PATCH] `agd export` option for diagnosis (#10344) closes: #8420 ref: #8152 ## Description This PR adds a new optional flag `--swing-store-export-mode` to the `agd export` command which can be used to specify the nature of swing-store export ### Security Considerations None ### Scaling Considerations None ### Documentation Considerations An optional flag `--swing-store-export-mode` option is now supported for `agd export` to specify the kind of swing store export required: - `operational` (default) option will retain the existing behavior and export the swing-store artifacts using the latest height - `skip` option will not export any swing-store artifact - `debug` option will export all swing-store artifacts, starting from height `0` ### Testing Considerations Tested manually ### Upgrade Considerations None --- golang/cosmos/app/app.go | 33 ++++++-- golang/cosmos/daemon/cmd/root.go | 63 +++++++++++---- golang/cosmos/x/swingset/genesis.go | 120 +++++++++++++++++++++++----- golang/cosmos/x/swingset/module.go | 19 ++++- 4 files changed, 189 insertions(+), 46 deletions(-) diff --git a/golang/cosmos/app/app.go b/golang/cosmos/app/app.go index e5dc4a3c4fe..6f2d7d225b9 100644 --- a/golang/cosmos/app/app.go +++ b/golang/cosmos/app/app.go @@ -137,13 +137,22 @@ import ( const appName = "agoric" -// FlagSwingStoreExportDir defines the config flag used to specify where a -// genesis swing-store export is expected. For start from genesis, the default -// value is config/swing-store in the home directory. For genesis export, the -// value is always a "swing-store" directory sibling to the exported -// genesis.json file. -// TODO: document this flag in config, likely alongside the genesis path -const FlagSwingStoreExportDir = "swing-store-export-dir" +const ( + // FlagSwingStoreExportDir defines the config flag used to specify where a + // genesis swing-store export is expected. For start from genesis, the default + // value is config/swing-store in the home directory. For genesis export, the + // value is always a "swing-store" directory sibling to the exported + // genesis.json file. + // TODO: document this flag in config, likely alongside the genesis path + FlagSwingStoreExportDir = "swing-store-export-dir" + // FlagSwingStoreExportMode defines the export mode for the swing store + // Alongside the default mode `operational`, there are two other modes + // + // 1- `skip` mode will skip the swing store export altogether + // + // 2- `debug` mode will export all the available store + FlagSwingStoreExportMode = "swing-store-export-mode" +) var ( // DefaultNodeHome default home directories for the application daemon @@ -673,6 +682,7 @@ func NewAgoricApp( app.EvidenceKeeper = *evidenceKeeper swingStoreExportDir := cast.ToString(appOpts.Get(FlagSwingStoreExportDir)) + swingStoreExportMode := cast.ToString(appOpts.Get(FlagSwingStoreExportMode)) // NOTE: Any module instantiated in the module manager that is later modified // must be passed by reference here. @@ -702,7 +712,14 @@ func NewAgoricApp( icaModule, packetforward.NewAppModule(app.PacketForwardKeeper), vstorage.NewAppModule(app.VstorageKeeper), - swingset.NewAppModule(app.SwingSetKeeper, &app.SwingStoreExportsHandler, setBootstrapNeeded, app.ensureControllerInited, swingStoreExportDir), + swingset.NewAppModule( + app.SwingSetKeeper, + &app.SwingStoreExportsHandler, + setBootstrapNeeded, + app.ensureControllerInited, + swingStoreExportDir, + swingStoreExportMode, + ), vibcModule, vbankModule, vtransferModule, diff --git a/golang/cosmos/daemon/cmd/root.go b/golang/cosmos/daemon/cmd/root.go index 367cea44c07..07a46217348 100644 --- a/golang/cosmos/daemon/cmd/root.go +++ b/golang/cosmos/daemon/cmd/root.go @@ -386,6 +386,12 @@ const ( ExportedSwingStoreDirectoryName = "swing-store" ) +var allowedSwingSetExportModes = map[string]bool{ + swingset.SwingStoreExportModeDebug: true, + swingset.SwingStoreExportModeOperational: true, + swingset.SwingStoreExportModeSkip: true, +} + // extendCosmosExportCommand monkey-patches the "export" command added by // cosmos-sdk to add a required "export-dir" command-line flag, and create the // genesis export in the specified directory if the VM is running. @@ -396,31 +402,52 @@ func extendCosmosExportCommand(cmd *cobra.Command) { panic(err) } + var keys []string + for key := range allowedSwingSetExportModes { + keys = append(keys, key) + } + + cmd.Flags().String( + gaia.FlagSwingStoreExportMode, + swingset.SwingStoreExportModeOperational, + fmt.Sprintf( + "The mode for swingstore export (%s)", + strings.Join(keys, " | "), + ), + ) + originalRunE := cmd.RunE extendedRunE := func(cmd *cobra.Command, args []string) error { serverCtx := server.GetServerContextFromCmd(cmd) exportDir, _ := cmd.Flags().GetString(FlagExportDir) + swingStoreExportMode, _ := cmd.Flags().GetString(gaia.FlagSwingStoreExportMode) + err := os.MkdirAll(exportDir, os.ModePerm) if err != nil { return err } genesisPath := filepath.Join(exportDir, ExportedGenesisFileName) - swingStoreExportPath := filepath.Join(exportDir, ExportedSwingStoreDirectoryName) - err = os.MkdirAll(swingStoreExportPath, os.ModePerm) - if err != nil { - return err + // Since none mode doesn't perform any swing store export + // There is no point in creating the export directory + if swingStoreExportMode != swingset.SwingStoreExportModeSkip { + swingStoreExportPath := filepath.Join(exportDir, ExportedSwingStoreDirectoryName) + + err = os.MkdirAll(swingStoreExportPath, os.ModePerm) + if err != nil { + return err + } + // We unconditionally set FlagSwingStoreExportDir as for export, it makes + // little sense for users to control this location separately, and we don't + // want to override any swing-store artifacts that may be associated to the + // current genesis. + serverCtx.Viper.Set(gaia.FlagSwingStoreExportDir, swingStoreExportPath) } - // We unconditionally set FlagSwingStoreExportDir as for export, it makes - // little sense for users to control this location separately, and we don't - // want to override any swing-store artifacts that may be associated to the - // current genesis. - serverCtx.Viper.Set(gaia.FlagSwingStoreExportDir, swingStoreExportPath) - if hasVMController(serverCtx) { + if hasVMController(serverCtx) || swingStoreExportMode == swingset.SwingStoreExportModeSkip { // Capture the export in the genesisPath. // This will fail if a genesis.json already exists in the export-dir genesisFile, err := os.OpenFile( @@ -453,7 +480,16 @@ func (ac appCreator) appExport( jailAllowedAddrs []string, appOpts servertypes.AppOptions, ) (servertypes.ExportedApp, error) { - if OnExportHook != nil { + swingStoreExportMode, ok := appOpts.Get(gaia.FlagSwingStoreExportMode).(string) + if !(ok && allowedSwingSetExportModes[swingStoreExportMode]) { + return servertypes.ExportedApp{}, fmt.Errorf( + "export mode '%s' is not supported", + swingStoreExportMode, + ) + } + + // We don't have to launch VM in case the swing store export is not required + if swingStoreExportMode != swingset.SwingStoreExportModeSkip && OnExportHook != nil { if err := OnExportHook(ac.agdServer, logger, appOpts); err != nil { return servertypes.ExportedApp{}, err } @@ -464,10 +500,7 @@ func (ac appCreator) appExport( return servertypes.ExportedApp{}, errors.New("application home is not set") } - var loadLatest bool - if height == -1 { - loadLatest = true - } + loadLatest := height == -1 gaiaApp := gaia.NewAgoricApp( ac.sender, ac.agdServer, diff --git a/golang/cosmos/x/swingset/genesis.go b/golang/cosmos/x/swingset/genesis.go index 56c6e06e5ba..e8308e6e94e 100644 --- a/golang/cosmos/x/swingset/genesis.go +++ b/golang/cosmos/x/swingset/genesis.go @@ -1,13 +1,13 @@ package swingset import ( - // "os" "bytes" "crypto/sha256" "encoding/hex" "encoding/json" "fmt" "hash" + "io" "strings" agoric "github.com/Agoric/agoric-sdk/golang/cosmos/types" @@ -16,6 +16,20 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) +const ( + // SwingStoreExportModeSkip indicates swing store data should be + // excluded from the export. + SwingStoreExportModeSkip = "skip" + + // SwingStoreExportModeOperational (default) indicates export should + // have the minimal set of artifacts needed to operate a node. + SwingStoreExportModeOperational = "operational" + + // SwingStoreExportModeDebug indicates export should have the maximal + // set of artifacts available in the JS swing-store. + SwingStoreExportModeDebug = "debug" +) + func ValidateGenesis(data *types.GenesisState) error { if data == nil { return fmt.Errorf("swingset genesis data cannot be nil") @@ -130,36 +144,59 @@ func InitGenesis(ctx sdk.Context, k Keeper, swingStoreExportsHandler *SwingStore return false } -func ExportGenesis(ctx sdk.Context, k Keeper, swingStoreExportsHandler *SwingStoreExportsHandler, swingStoreExportDir string) *types.GenesisState { +func ExportGenesis( + ctx sdk.Context, + k Keeper, + swingStoreExportsHandler *SwingStoreExportsHandler, + swingStoreExportDir string, + swingStoreExportMode string, +) *types.GenesisState { gs := &types.GenesisState{ Params: k.GetParams(ctx), State: k.GetState(ctx), SwingStoreExportData: nil, } + // This will only be used in non skip mode + artifactMode := swingStoreExportMode + exportDataMode := keeper.SwingStoreExportDataModeSkip + hasher := sha256.New() snapshotHeight := uint64(ctx.BlockHeight()) - eventHandler := swingStoreGenesisEventHandler{exportDir: swingStoreExportDir, snapshotHeight: snapshotHeight, swingStore: k.GetSwingStore(ctx), hasher: sha256.New()} - - err := swingStoreExportsHandler.InitiateExport( - // The export will fail if the export of a historical height was requested - snapshotHeight, - eventHandler, - keeper.SwingStoreExportOptions{ - ArtifactMode: keeper.SwingStoreArtifactModeOperational, - ExportDataMode: keeper.SwingStoreExportDataModeSkip, - }, - ) - if err != nil { - panic(err) + if swingStoreExportMode == SwingStoreExportModeDebug { + exportDataMode = keeper.SwingStoreExportDataModeAll + snapshotHeight = 0 } - err = keeper.WaitUntilSwingStoreExportDone() - if err != nil { - panic(err) + if swingStoreExportMode != SwingStoreExportModeSkip { + eventHandler := swingStoreGenesisEventHandler{ + exportDir: swingStoreExportDir, + snapshotHeight: snapshotHeight, + swingStore: k.GetSwingStore(ctx), + hasher: hasher, + exportMode: swingStoreExportMode, + } + + err := swingStoreExportsHandler.InitiateExport( + // The export will fail if the export of a historical height was requested outside of debug mode + snapshotHeight, + eventHandler, + keeper.SwingStoreExportOptions{ + ArtifactMode: artifactMode, + ExportDataMode: exportDataMode, + }, + ) + if err != nil { + panic(err) + } + + err = keeper.WaitUntilSwingStoreExportDone() + if err != nil { + panic(err) + } } - gs.SwingStoreExportDataHash = fmt.Sprintf("sha256:%x", eventHandler.hasher.Sum(nil)) + gs.SwingStoreExportDataHash = fmt.Sprintf("sha256:%x", hasher.Sum(nil)) return gs } @@ -169,6 +206,7 @@ type swingStoreGenesisEventHandler struct { snapshotHeight uint64 swingStore sdk.KVStore hasher hash.Hash + exportMode string } func (eventHandler swingStoreGenesisEventHandler) OnExportStarted(height uint64, retrieveSwingStoreExport func() error) error { @@ -176,10 +214,12 @@ func (eventHandler swingStoreGenesisEventHandler) OnExportStarted(height uint64, } func (eventHandler swingStoreGenesisEventHandler) OnExportRetrieved(provider keeper.SwingStoreExportProvider) error { - if eventHandler.snapshotHeight != provider.BlockHeight { + if eventHandler.exportMode != SwingStoreExportModeDebug && eventHandler.snapshotHeight != provider.BlockHeight { return fmt.Errorf("snapshot block height (%d) doesn't match requested height (%d)", provider.BlockHeight, eventHandler.snapshotHeight) } + artifactsEnded := false + artifactsProvider := keeper.SwingStoreExportProvider{ GetExportDataReader: func() (agoric.KVEntryReader, error) { exportDataIterator := eventHandler.swingStore.Iterator(nil, nil) @@ -194,7 +234,45 @@ func (eventHandler swingStoreGenesisEventHandler) OnExportRetrieved(provider kee return nil }), nil }, - ReadNextArtifact: provider.ReadNextArtifact, + ReadNextArtifact: func() (types.SwingStoreArtifact, error) { + var ( + artifact types.SwingStoreArtifact + err error + encodedExportData bytes.Buffer + ) + + if !artifactsEnded { + artifact, err = provider.ReadNextArtifact() + } else { + return types.SwingStoreArtifact{}, io.EOF + } + + if err == io.EOF { + artifactsEnded = true + if eventHandler.exportMode == SwingStoreExportModeDebug { + exportDataReader, _ := provider.GetExportDataReader() + + defer exportDataReader.Close() + + err = agoric.EncodeKVEntryReaderToJsonl( + exportDataReader, + &encodedExportData, + ) + if err == nil { + artifact = types.SwingStoreArtifact{ + Data: encodedExportData.Bytes(), + Name: keeper.UntrustedExportDataArtifactName, + } + } + } + } + + return artifact, err + }, + } + + if eventHandler.exportMode == SwingStoreExportModeDebug { + artifactsProvider.BlockHeight = provider.BlockHeight } return keeper.WriteSwingStoreExportToDirectory(artifactsProvider, eventHandler.exportDir) diff --git a/golang/cosmos/x/swingset/module.go b/golang/cosmos/x/swingset/module.go index c8c646313f6..6a3465e88bf 100644 --- a/golang/cosmos/x/swingset/module.go +++ b/golang/cosmos/x/swingset/module.go @@ -80,10 +80,18 @@ type AppModule struct { setBootstrapNeeded func() ensureControllerInited func(sdk.Context) swingStoreExportDir string + swingStoreExportMode string } // NewAppModule creates a new AppModule Object -func NewAppModule(k Keeper, swingStoreExportsHandler *SwingStoreExportsHandler, setBootstrapNeeded func(), ensureControllerInited func(sdk.Context), swingStoreExportDir string) AppModule { +func NewAppModule( + k Keeper, + swingStoreExportsHandler *SwingStoreExportsHandler, + setBootstrapNeeded func(), + ensureControllerInited func(sdk.Context), + swingStoreExportDir string, + swingStoreExportMode string, +) AppModule { am := AppModule{ AppModuleBasic: AppModuleBasic{}, keeper: k, @@ -91,6 +99,7 @@ func NewAppModule(k Keeper, swingStoreExportsHandler *SwingStoreExportsHandler, setBootstrapNeeded: setBootstrapNeeded, ensureControllerInited: ensureControllerInited, swingStoreExportDir: swingStoreExportDir, + swingStoreExportMode: swingStoreExportMode, } return am } @@ -173,6 +182,12 @@ func (am AppModule) InitGenesis(ctx sdk.Context, cdc codec.JSONCodec, data json. func (am AppModule) ExportGenesis(ctx sdk.Context, cdc codec.JSONCodec) json.RawMessage { am.checkSwingStoreExportSetup() - gs := ExportGenesis(ctx, am.keeper, am.swingStoreExportsHandler, am.swingStoreExportDir) + gs := ExportGenesis( + ctx, + am.keeper, + am.swingStoreExportsHandler, + am.swingStoreExportDir, + am.swingStoreExportMode, + ) return cdc.MustMarshalJSON(gs) }