Skip to content

Commit

Permalink
Merge pull request #8152 from Agoric/mhofman/6527-genesis-with-swing-…
Browse files Browse the repository at this point in the history
…store

feat(x/swingset): include swing-store in genesis
  • Loading branch information
mhofman committed Aug 22, 2023
2 parents a820d2c + 4223dee commit d2975a7
Show file tree
Hide file tree
Showing 12 changed files with 314 additions and 90 deletions.
11 changes: 10 additions & 1 deletion golang/cosmos/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,14 @@ 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"

var (
// DefaultNodeHome default home directories for the application daemon
DefaultNodeHome string
Expand Down Expand Up @@ -588,6 +596,7 @@ func NewAgoricApp(
app.EvidenceKeeper = *evidenceKeeper

skipGenesisInvariants := cast.ToBool(appOpts.Get(crisis.FlagSkipGenesisInvariants))
swingStoreExportDir := cast.ToString(appOpts.Get(FlagSwingStoreExportDir))

// NOTE: Any module instantiated in the module manager that is later modified
// must be passed by reference here.
Expand Down Expand Up @@ -617,7 +626,7 @@ func NewAgoricApp(
transferModule,
icaModule,
vstorage.NewAppModule(app.VstorageKeeper),
swingset.NewAppModule(app.SwingSetKeeper, setBootstrapNeeded, app.ensureControllerInited),
swingset.NewAppModule(app.SwingSetKeeper, &app.SwingStoreExportsHandler, setBootstrapNeeded, app.ensureControllerInited, swingStoreExportDir),
vibcModule,
vbankModule,
lienModule,
Expand Down
7 changes: 5 additions & 2 deletions golang/cosmos/cmd/agd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (

func main() {
// We need to delegate to our default app for running the actual chain.
daemoncmd.OnStartHook = func(logger log.Logger) {
launchVM := func(logger log.Logger) {
args := []string{"ag-chain-cosmos", "--home", gaia.DefaultNodeHome}
args = append(args, os.Args[1:]...)

Expand All @@ -22,12 +22,15 @@ func main() {
panic(lookErr)
}

logger.Info("Start chain delegating to JS executable", "binary", binary, "args", args)
logger.Info("agd delegating to JS executable", "binary", binary, "args", args)
execErr := syscall.Exec(binary, args, os.Environ())
if execErr != nil {
panic(execErr)
}
}

daemoncmd.OnStartHook = launchVM
daemoncmd.OnExportHook = launchVM

daemon.RunWithController(nil)
}
96 changes: 93 additions & 3 deletions golang/cosmos/daemon/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
genutilcli "github.com/cosmos/cosmos-sdk/x/genutil/client/cli"
"github.com/spf13/cast"
"github.com/spf13/cobra"
"github.com/spf13/viper"
tmcli "github.com/tendermint/tendermint/libs/cli"
"github.com/tendermint/tendermint/libs/log"
dbm "github.com/tendermint/tm-db"
Expand All @@ -40,7 +41,8 @@ import (
type Sender func(needReply bool, str string) (string, error)

var AppName = "agd"
var OnStartHook func(log.Logger)
var OnStartHook func(logger log.Logger)
var OnExportHook func(logger log.Logger)

// NewRootCmd creates a new root command for simd. It is called once in the
// main function.
Expand Down Expand Up @@ -133,6 +135,14 @@ func initRootCmd(sender Sender, rootCmd *cobra.Command, encodingConfig params.En
}
server.AddCommands(rootCmd, gaia.DefaultNodeHome, ac.newApp, ac.appExport, addModuleInitFlags)

hasVMController := sender != nil
for _, command := range rootCmd.Commands() {
if command.Name() == "export" {
extendCosmosExportCommand(command, hasVMController)
break
}
}

// add keybase, auxiliary RPC, query, and tx child commands
rootCmd.AddCommand(
rpc.StatusCommand(),
Expand Down Expand Up @@ -232,7 +242,16 @@ func (ac appCreator) newApp(
panic(err)
}

snapshotDir := filepath.Join(cast.ToString(appOpts.Get(flags.FlagHome)), "data", "snapshots")
homePath := cast.ToString(appOpts.Get(flags.FlagHome))

// Set a default value for FlagSwingStoreExportDir based on the homePath
// in case we need to InitGenesis with swing-store data
viper, ok := appOpts.(*viper.Viper)
if ok && cast.ToString(appOpts.Get(gaia.FlagSwingStoreExportDir)) == "" {
viper.Set(gaia.FlagSwingStoreExportDir, filepath.Join(homePath, "config", ExportedSwingStoreDirectoryName))
}

snapshotDir := filepath.Join(homePath, "data", "snapshots")
snapshotDB, err := sdk.NewLevelDB("metadata", snapshotDir)
if err != nil {
panic(err)
Expand All @@ -245,7 +264,7 @@ func (ac appCreator) newApp(
return gaia.NewAgoricApp(
ac.sender,
logger, db, traceStore, true, skipUpgradeHeights,
cast.ToString(appOpts.Get(flags.FlagHome)),
homePath,
cast.ToUint(appOpts.Get(server.FlagInvCheckPeriod)),
ac.encCfg,
appOpts,
Expand All @@ -263,6 +282,74 @@ func (ac appCreator) newApp(
)
}

const (
// FlagExportDir is the command-line flag for the "export" command specifying
// where the output of the export should be placed. It contains both the
// items names below: the genesis file, and a directory containing the
// exported swing-store artifacts
FlagExportDir = "export-dir"
// ExportedGenesisFileName is the file name used to save the genesis in the export-dir
ExportedGenesisFileName = "genesis.json"
// ExportedSwingStoreDirectoryName is the directory name used to save the swing-store
// export (artifacts only) in the export-dir
ExportedSwingStoreDirectoryName = "swing-store"
)

// 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.
func extendCosmosExportCommand(cmd *cobra.Command, hasVMController bool) {
cmd.Flags().String(FlagExportDir, "", "The directory where to create the genesis export")
err := cmd.MarkFlagRequired(FlagExportDir)
if err != nil {
panic(err)
}

originalRunE := cmd.RunE

extendedRunE := func(cmd *cobra.Command, args []string) error {
serverCtx := server.GetServerContextFromCmd(cmd)

exportDir, _ := cmd.Flags().GetString(FlagExportDir)
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
}
// 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)

// This will fail is a genesis.json already exists in the export-dir
genesisFile, err := os.OpenFile(genesisPath, os.O_CREATE|os.O_EXCL|os.O_WRONLY, os.ModePerm)
if err != nil {
return err
}
defer genesisFile.Close()

cmd.SetOut(genesisFile)

return originalRunE(cmd, args)
}

// Only modify the command handler when we have a VM controller to handle
// the full export logic. Otherwise, appExport will just exec the VM program
// (OnExportHook), which will result in re-entering this flow with the VM
// controller set.
if hasVMController {
cmd.RunE = extendedRunE
}
}

func (ac appCreator) appExport(
logger log.Logger,
db dbm.DB,
Expand All @@ -272,6 +359,9 @@ func (ac appCreator) appExport(
jailAllowedAddrs []string,
appOpts servertypes.AppOptions,
) (servertypes.ExportedApp, error) {
if OnExportHook != nil {
OnExportHook(logger)
}

homePath, ok := appOpts.Get(flags.FlagHome).(string)
if !ok || homePath == "" {
Expand Down
103 changes: 88 additions & 15 deletions golang/cosmos/x/swingset/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
// "os"
"fmt"

agoric "github.com/Agoric/agoric-sdk/golang/cosmos/types"
"github.com/Agoric/agoric-sdk/golang/cosmos/x/swingset/keeper"
"github.com/Agoric/agoric-sdk/golang/cosmos/x/swingset/types"
sdk "github.com/cosmos/cosmos-sdk/types"
)
Expand All @@ -28,33 +30,58 @@ func DefaultGenesisState() *types.GenesisState {

// InitGenesis initializes the (Cosmos-side) SwingSet state from the GenesisState.
// Returns whether the app should send a bootstrap action to the controller.
func InitGenesis(ctx sdk.Context, keeper Keeper, data *types.GenesisState) bool {
keeper.SetParams(ctx, data.GetParams())
keeper.SetState(ctx, data.GetState())
func InitGenesis(ctx sdk.Context, k Keeper, swingStoreExportsHandler *SwingStoreExportsHandler, swingStoreExportDir string, data *types.GenesisState) bool {
k.SetParams(ctx, data.GetParams())
k.SetState(ctx, data.GetState())

swingStoreExportData := data.GetSwingStoreExportData()
if len(swingStoreExportData) > 0 {
// See https://github.com/Agoric/agoric-sdk/issues/6527
panic("genesis with swing-store state not implemented")
if len(swingStoreExportData) == 0 {
return true
}

// TODO: bootstrap only if not restoring swing-store from genesis state
return true
artifactProvider, err := keeper.OpenSwingStoreExportDirectory(swingStoreExportDir)
if err != nil {
panic(err)
}

swingStore := k.GetSwingStore(ctx)

for _, entry := range swingStoreExportData {
swingStore.Set([]byte(entry.Key), []byte(entry.Value))
}

snapshotHeight := uint64(ctx.BlockHeight())

getExportDataReader := func() (agoric.KVEntryReader, error) {
exportDataIterator := swingStore.Iterator(nil, nil)
return agoric.NewKVIteratorReader(exportDataIterator), nil
}

err = swingStoreExportsHandler.RestoreExport(
keeper.SwingStoreExportProvider{
BlockHeight: snapshotHeight,
GetExportDataReader: getExportDataReader,
ReadNextArtifact: artifactProvider.ReadNextArtifact,
},
keeper.SwingStoreRestoreOptions{
ArtifactMode: keeper.SwingStoreArtifactModeReplay,
ExportDataMode: keeper.SwingStoreExportDataModeAll,
},
)
if err != nil {
panic(err)
}

return false
}

func ExportGenesis(ctx sdk.Context, k Keeper) *types.GenesisState {
func ExportGenesis(ctx sdk.Context, k Keeper, swingStoreExportsHandler *SwingStoreExportsHandler, swingStoreExportDir string) *types.GenesisState {
gs := &types.GenesisState{
Params: k.GetParams(ctx),
State: k.GetState(ctx),
SwingStoreExportData: []*types.SwingStoreExportDataEntry{},
}

// Only export the swing-store shadow copy for now
// TODO:
// - perform state-sync export with check blockHeight (figure out how to
// handle export of historical height),
// - include swing-store artifacts in genesis state
// See https://github.com/Agoric/agoric-sdk/issues/6527
exportDataIterator := k.GetSwingStore(ctx).Iterator(nil, nil)
defer exportDataIterator.Close()
for ; exportDataIterator.Valid(); exportDataIterator.Next() {
Expand All @@ -64,5 +91,51 @@ func ExportGenesis(ctx sdk.Context, k Keeper) *types.GenesisState {
}
gs.SwingStoreExportData = append(gs.SwingStoreExportData, &entry)
}

snapshotHeight := uint64(ctx.BlockHeight())

err := swingStoreExportsHandler.InitiateExport(
// The export will fail if the export of a historical height was requested
snapshotHeight,
swingStoreGenesisEventHandler{exportDir: swingStoreExportDir, snapshotHeight: snapshotHeight},
// The export will fail if the swing-store does not contain all replay artifacts
keeper.SwingStoreExportOptions{
ArtifactMode: keeper.SwingStoreArtifactModeReplay,
ExportDataMode: keeper.SwingStoreExportDataModeSkip,
},
)
if err != nil {
panic(err)
}

err = keeper.WaitUntilSwingStoreExportDone()
if err != nil {
panic(err)
}

return gs
}

type swingStoreGenesisEventHandler struct {
exportDir string
snapshotHeight uint64
}

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 {
return fmt.Errorf("snapshot block height (%d) doesn't match requested height (%d)", provider.BlockHeight, eventHandler.snapshotHeight)
}

artifactsProvider := keeper.SwingStoreExportProvider{
GetExportDataReader: func() (agoric.KVEntryReader, error) {
return nil, nil
},
ReadNextArtifact: provider.ReadNextArtifact,
}

return keeper.WriteSwingStoreExportToDirectory(artifactsProvider, eventHandler.exportDir)
}
Loading

0 comments on commit d2975a7

Please sign in to comment.