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 15 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
27 changes: 19 additions & 8 deletions golang/cosmos/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,13 +137,16 @@ 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 = "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 +676,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 +706,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{
swingsetkeeper.SwingStoreArtifactModeDebug: true,
swingsetkeeper.SwingStoreArtifactModeOperational: true,
swingsetkeeper.SwingStoreArtifactModeSkip: true,
mhofman marked this conversation as resolved.
Show resolved Hide resolved
}

// 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,
swingsetkeeper.SwingStoreArtifactModeOperational,
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 skip mode doesn't perform any swing store export
// There is no point in creating the export directory
if swingStoreExportMode != swingsetkeeper.SwingStoreArtifactModeSkip {
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 == swingsetkeeper.SwingStoreArtifactModeSkip {
// 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 == swingsetkeeper.SwingStoreArtifactModeSkip || OnExportHook == nil) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@mhofman since OnExportHook currently points to launchVM, is it okay to bypass this hook in case swing-store export is not needed? Or do you see us adding cosmic side future hook changes?

Copy link
Member

Choose a reason for hiding this comment

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

I think that's fine, but I'd like @michaelfig to confirm, especially with his upcoming changed for split brains. Basically export only needs to launch the VM if it will export the swing-store, which is decided by a command line config. Since OnExportHook is unconditionally set or not based on the entrypoint used, the only option we have is to ignore it here.

Copy link
Member

Choose a reason for hiding this comment

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

Btw, my brain still struggles with distributing negations.

Suggested change
if !(swingStoreExportMode == swingsetkeeper.SwingStoreArtifactModeSkip || OnExportHook == nil) {
if swingStoreExportMode != swingsetkeeper.SwingStoreArtifactModeSkip && 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
133 changes: 97 additions & 36 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 Down Expand Up @@ -130,36 +130,57 @@ 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,
Params: k.GetParams(ctx),
State: k.GetState(ctx),
SwingStoreExportData: nil,
SwingStoreExportDataHash: "",
mhofman marked this conversation as resolved.
Show resolved Hide resolved
}

exportDataMode := keeper.SwingStoreExportDataModeSkip
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 == keeper.SwingStoreArtifactModeDebug {
exportDataMode = keeper.SwingStoreExportDataModeAll
snapshotHeight = 0
}

err = keeper.WaitUntilSwingStoreExportDone()
if err != nil {
panic(err)
}
if swingStoreExportMode != keeper.SwingStoreArtifactModeSkip {
eventHandler := swingStoreGenesisEventHandler{
exportDir: swingStoreExportDir,
snapshotHeight: snapshotHeight,
swingStore: k.GetSwingStore(ctx),
hasher: sha256.New(),
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: swingStoreExportMode,
mhofman marked this conversation as resolved.
Show resolved Hide resolved
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", eventHandler.hasher.Sum(nil))
}

return gs
}
Expand All @@ -169,32 +190,72 @@ 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 == keeper.SwingStoreArtifactModeDebug && eventHandler.snapshotHeight == 0) ||
eventHandler.snapshotHeight == provider.BlockHeight) {
mhofman marked this conversation as resolved.
Show resolved Hide resolved
return fmt.Errorf("snapshot block height (%d) doesn't match requested height (%d)", provider.BlockHeight, eventHandler.snapshotHeight)
}

artifactsEnded := false

var getExportDataReader = func() (agoric.KVEntryReader, error) {
exportDataIterator := eventHandler.swingStore.Iterator(nil, nil)
kvReader := agoric.NewKVIteratorReader(exportDataIterator)
eventHandler.hasher.Reset()
encoder := json.NewEncoder(eventHandler.hasher)
encoder.SetEscapeHTML(false)

return agoric.NewKVHookingReader(kvReader, func(entry agoric.KVEntry) error {
return encoder.Encode(entry)
}, func() error {
return nil
}), nil
}

artifactsProvider := keeper.SwingStoreExportProvider{
mhofman marked this conversation as resolved.
Show resolved Hide resolved
GetExportDataReader: func() (agoric.KVEntryReader, error) {
exportDataIterator := eventHandler.swingStore.Iterator(nil, nil)
kvReader := agoric.NewKVIteratorReader(exportDataIterator)
eventHandler.hasher.Reset()
encoder := json.NewEncoder(eventHandler.hasher)
encoder.SetEscapeHTML(false)
GetExportDataReader: getExportDataReader,
ReadNextArtifact: func() (types.SwingStoreArtifact, error) {
var (
artifact types.SwingStoreArtifact
err error
encodedExportData bytes.Buffer
)

return agoric.NewKVHookingReader(kvReader, func(entry agoric.KVEntry) error {
return encoder.Encode(entry)
}, func() error {
return nil
}), nil
if !artifactsEnded {
artifact, err = provider.ReadNextArtifact()
} else {
return types.SwingStoreArtifact{}, io.EOF
}

if err == io.EOF {
artifactsEnded = true
if eventHandler.exportMode == keeper.SwingStoreArtifactModeDebug {
exportDataReader, _ := getExportDataReader()
mhofman marked this conversation as resolved.
Show resolved Hide resolved

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
},
ReadNextArtifact: provider.ReadNextArtifact,
}

return keeper.WriteSwingStoreExportToDirectory(artifactsProvider, eventHandler.exportDir)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ type swingStoreRestoreExportAction struct {
const (
// SwingStoreArtifactModeNone means that no artifacts are part of the
// export / import.
SwingStoreArtifactModeNone = "none"
SwingStoreArtifactModeSkip = "skip"
mhofman marked this conversation as resolved.
Show resolved Hide resolved

// SwingStoreArtifactModeOperational represents the minimal set of artifacts
// needed to operate a node.
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