Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

agd export option for diagnosis #10344

Merged
merged 21 commits into from
Nov 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 25 additions & 8 deletions golang/cosmos/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
mhofman marked this conversation as resolved.
Show resolved Hide resolved
)

var (
// DefaultNodeHome default home directories for the application daemon
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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,
Expand Down
63 changes: 48 additions & 15 deletions golang/cosmos/daemon/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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(
Expand Down Expand Up @@ -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
}
Expand All @@ -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,
Expand Down
120 changes: 99 additions & 21 deletions golang/cosmos/x/swingset/genesis.go
Original file line number Diff line number Diff line change
@@ -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"
Expand All @@ -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")
Expand Down Expand Up @@ -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
}
Expand All @@ -169,17 +206,20 @@ type swingStoreGenesisEventHandler struct {
snapshotHeight uint64
swingStore sdk.KVStore
hasher hash.Hash
exportMode string
}

func (eventHandler swingStoreGenesisEventHandler) OnExportStarted(height uint64, retrieveSwingStoreExport func() error) error {
return retrieveSwingStoreExport()
}

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{
mhofman marked this conversation as resolved.
Show resolved Hide resolved
GetExportDataReader: func() (agoric.KVEntryReader, error) {
exportDataIterator := eventHandler.swingStore.Iterator(nil, nil)
Expand All @@ -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,
}
}
Comment on lines +257 to +266
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It really is a bit unfortunate we have to read, decode, re-encode it all in memory before finally writing it out, but streamlining this is definitely out of scope.

}
}

return artifact, err
},
}

if eventHandler.exportMode == SwingStoreExportModeDebug {
artifactsProvider.BlockHeight = provider.BlockHeight
}

return keeper.WriteSwingStoreExportToDirectory(artifactsProvider, eventHandler.exportDir)
Expand Down
19 changes: 17 additions & 2 deletions golang/cosmos/x/swingset/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,17 +80,26 @@ 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,
swingStoreExportsHandler: swingStoreExportsHandler,
setBootstrapNeeded: setBootstrapNeeded,
ensureControllerInited: ensureControllerInited,
swingStoreExportDir: swingStoreExportDir,
swingStoreExportMode: swingStoreExportMode,
}
return am
}
Expand Down Expand Up @@ -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)
}
Loading