From 50fb51f556f36fc30827a46042153acbcd95addf Mon Sep 17 00:00:00 2001 From: HuangYi Date: Tue, 9 May 2023 10:56:40 +0800 Subject: [PATCH] feat: add local snapshots management commands - `appd snapshots list` list local snapshots - `appd snapshots restore` restore from a local snapshot --- CHANGELOG.md | 1 + client/snapshot/cmd.go | 20 +++++++++++++++ client/snapshot/list.go | 34 +++++++++++++++++++++++++ client/snapshot/restore.go | 51 ++++++++++++++++++++++++++++++++++++++ go.mod | 1 + go.sum | 3 ++- server/types/app.go | 4 +++ server/util.go | 30 ++++++++++++++-------- simapp/simd/cmd/root.go | 2 ++ simapp/simd/cmd/root_v2.go | 2 ++ store/snapshots/manager.go | 9 +++++++ 11 files changed, 146 insertions(+), 11 deletions(-) create mode 100644 client/snapshot/cmd.go create mode 100644 client/snapshot/list.go create mode 100644 client/snapshot/restore.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 4bb3c5911f0e..b8a4d0906176 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -107,6 +107,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ * (x/bank) [#15764](https://github.com/cosmos/cosmos-sdk/pull/15764) Speedup x/bank InitGenesis * (x/auth) [#15867](https://github.com/cosmos/cosmos-sdk/pull/15867) Support better logging for signature verification failure. * (types/query) [#16041](https://github.com/cosmos/cosmos-sdk/pull/16041) change pagination max limit to a variable in order to be modifed by application devs +* (store) [#]() Add local snapshots management commands. ### State Machine Breaking diff --git a/client/snapshot/cmd.go b/client/snapshot/cmd.go new file mode 100644 index 000000000000..bc6dc05bf2b7 --- /dev/null +++ b/client/snapshot/cmd.go @@ -0,0 +1,20 @@ +package snapshot + +import ( + servertypes "github.com/cosmos/cosmos-sdk/server/types" + "github.com/spf13/cobra" +) + +// Cmd returns the snapshots group command +func Cmd(appCreator servertypes.AppCreator) *cobra.Command { + cmd := &cobra.Command{ + Use: "snapshots", + Short: "Manage local snapshots", + Long: "Manage local snapshots", + } + cmd.AddCommand( + ListSnapshotsCmd(appCreator), + RestoreSnapshotCmd(appCreator), + ) + return cmd +} diff --git a/client/snapshot/list.go b/client/snapshot/list.go new file mode 100644 index 000000000000..9ab8b829bf9d --- /dev/null +++ b/client/snapshot/list.go @@ -0,0 +1,34 @@ +package snapshot + +import ( + "fmt" + + "github.com/cosmos/cosmos-sdk/server" + servertypes "github.com/cosmos/cosmos-sdk/server/types" + "github.com/spf13/cobra" +) + +// ListSnapshotsCmd returns the command to list local snapshots +func ListSnapshotsCmd(appCreator servertypes.AppCreator) *cobra.Command { + return &cobra.Command{ + Use: "list", + Short: "List snapshots", + Long: "List snapshots", + RunE: func(cmd *cobra.Command, args []string) error { + ctx := server.GetServerContextFromCmd(cmd) + snapshotStore, err := server.GetSnapshotStore(ctx.Viper) + if err != nil { + return err + } + snapshots, err := snapshotStore.List() + if err != nil { + return fmt.Errorf("failed to list snapshots: %w", err) + } + for _, snapshot := range snapshots { + fmt.Println("height:", snapshot.Height, "format:", snapshot.Format, "chunks:", snapshot.Chunks) + } + + return nil + }, + } +} diff --git a/client/snapshot/restore.go b/client/snapshot/restore.go new file mode 100644 index 000000000000..1c19502a4f7d --- /dev/null +++ b/client/snapshot/restore.go @@ -0,0 +1,51 @@ +package snapshot + +import ( + "path/filepath" + "strconv" + + "cosmossdk.io/log" + "github.com/spf13/cobra" + + dbm "github.com/cosmos/cosmos-db" + "github.com/cosmos/cosmos-sdk/server" + servertypes "github.com/cosmos/cosmos-sdk/server/types" +) + +// RestoreSnapshotCmd returns a command to restore a snapshot +func RestoreSnapshotCmd(appCreator servertypes.AppCreator) *cobra.Command { + cmd := &cobra.Command{ + Use: "restore ", + Short: "Restore app state from local snapshot", + Long: "Restore app state from local snapshot", + RunE: func(cmd *cobra.Command, args []string) error { + ctx := server.GetServerContextFromCmd(cmd) + + height, err := strconv.ParseUint(args[0], 10, 64) + if err != nil { + return err + } + format, err := strconv.ParseUint(args[1], 10, 32) + if err != nil { + return err + } + + home := ctx.Config.RootDir + db, err := openDB(home, server.GetAppDBBackend(ctx.Viper)) + if err != nil { + return err + } + logger := log.NewLogger(cmd.OutOrStdout()) + app := appCreator(logger, db, nil, ctx.Viper) + + sm := app.SnapshotManager() + return sm.RestoreLocalSnapshot(height, uint32(format)) + }, + } + return cmd +} + +func openDB(rootDir string, backendType dbm.BackendType) (dbm.DB, error) { + dataDir := filepath.Join(rootDir, "data") + return dbm.NewDB("application", backendType, dataDir) +} diff --git a/go.mod b/go.mod index 6eb7b1594a08..864bf1a46ecc 100644 --- a/go.mod +++ b/go.mod @@ -130,6 +130,7 @@ require ( github.com/mitchellh/go-testing-interface v1.14.1 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mtibben/percent v0.2.1 // indirect + github.com/nxadm/tail v1.4.8 // indirect github.com/oklog/run v1.1.0 // indirect github.com/pelletier/go-toml/v2 v2.0.7 // indirect github.com/petermattis/goid v0.0.0-20230317030725-371a4b8eda08 // indirect diff --git a/go.sum b/go.sum index 9e8a25733938..b92e53003390 100644 --- a/go.sum +++ b/go.sum @@ -636,8 +636,9 @@ github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxzi github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= diff --git a/server/types/app.go b/server/types/app.go index 3b51fe1920ee..1af0b0bf57ec 100644 --- a/server/types/app.go +++ b/server/types/app.go @@ -5,6 +5,7 @@ import ( "io" "cosmossdk.io/log" + "cosmossdk.io/store/snapshots" storetypes "cosmossdk.io/store/types" abci "github.com/cometbft/cometbft/abci/types" cmtproto "github.com/cometbft/cometbft/proto/tendermint/types" @@ -54,6 +55,9 @@ type ( // CommitMultiStore return the multistore instance CommitMultiStore() storetypes.CommitMultiStore + + // Return the snapshot manager + SnapshotManager() *snapshots.Manager } // AppCreator is a function that allows us to lazily initialize an diff --git a/server/util.go b/server/util.go index bd1b80371ffa..4481ee119722 100644 --- a/server/util.go +++ b/server/util.go @@ -468,16 +468,7 @@ func DefaultBaseappOptions(appOpts types.AppOptions) []func(*baseapp.BaseApp) { chainID = appGenesis.ChainID } - snapshotDir := filepath.Join(homeDir, "data", "snapshots") - if err = os.MkdirAll(snapshotDir, os.ModePerm); err != nil { - panic(fmt.Errorf("failed to create snapshots directory: %w", err)) - } - - snapshotDB, err := dbm.NewDB("metadata", GetAppDBBackend(appOpts), snapshotDir) - if err != nil { - panic(err) - } - snapshotStore, err := snapshots.NewStore(snapshotDB, snapshotDir) + snapshotStore, err := GetSnapshotStore(appOpts) if err != nil { panic(err) } @@ -508,3 +499,22 @@ func DefaultBaseappOptions(appOpts types.AppOptions) []func(*baseapp.BaseApp) { baseapp.SetChainID(chainID), } } + +func GetSnapshotStore(appOpts types.AppOptions) (*snapshots.Store, error) { + homeDir := cast.ToString(appOpts.Get(flags.FlagHome)) + snapshotDir := filepath.Join(homeDir, "data", "snapshots") + if err := os.MkdirAll(snapshotDir, os.ModePerm); err != nil { + return nil, fmt.Errorf("failed to create snapshots directory: %w", err) + } + + snapshotDB, err := dbm.NewDB("metadata", GetAppDBBackend(appOpts), snapshotDir) + if err != nil { + return nil, err + } + snapshotStore, err := snapshots.NewStore(snapshotDB, snapshotDir) + if err != nil { + return nil, err + } + + return snapshotStore, nil +} diff --git a/simapp/simd/cmd/root.go b/simapp/simd/cmd/root.go index bf287893e864..ce7ba3171682 100644 --- a/simapp/simd/cmd/root.go +++ b/simapp/simd/cmd/root.go @@ -24,6 +24,7 @@ import ( "github.com/cosmos/cosmos-sdk/client/keys" "github.com/cosmos/cosmos-sdk/client/pruning" "github.com/cosmos/cosmos-sdk/client/rpc" + "github.com/cosmos/cosmos-sdk/client/snapshot" "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/server" serverconfig "github.com/cosmos/cosmos-sdk/server/config" @@ -191,6 +192,7 @@ func initRootCmd(rootCmd *cobra.Command, encodingConfig params.EncodingConfig, b debug.Cmd(), confixcmd.ConfigCommand(), pruning.Cmd(newApp), + snapshot.Cmd(newApp), ) server.AddCommands(rootCmd, simapp.DefaultNodeHome, newApp, appExport, addModuleInitFlags) diff --git a/simapp/simd/cmd/root_v2.go b/simapp/simd/cmd/root_v2.go index db8c0298afce..bfc8079414a9 100644 --- a/simapp/simd/cmd/root_v2.go +++ b/simapp/simd/cmd/root_v2.go @@ -25,6 +25,7 @@ import ( "github.com/cosmos/cosmos-sdk/client/keys" "github.com/cosmos/cosmos-sdk/client/pruning" "github.com/cosmos/cosmos-sdk/client/rpc" + "github.com/cosmos/cosmos-sdk/client/snapshot" "github.com/cosmos/cosmos-sdk/codec" codectypes "github.com/cosmos/cosmos-sdk/codec/types" "github.com/cosmos/cosmos-sdk/server" @@ -207,6 +208,7 @@ func initRootCmd( debug.Cmd(), confixcmd.ConfigCommand(), pruning.Cmd(newApp), + snapshot.Cmd(newApp), ) server.AddCommands(rootCmd, simapp.DefaultNodeHome, newApp, appExport, addModuleInitFlags) diff --git a/store/snapshots/manager.go b/store/snapshots/manager.go index 90e980d4d016..05988a70c969 100644 --- a/store/snapshots/manager.go +++ b/store/snapshots/manager.go @@ -418,6 +418,15 @@ func (m *Manager) RestoreChunk(chunk []byte) (bool, error) { return false, nil } +// RestoreLocalSnapshot restores app state from a local snapshot. +func (m *Manager) RestoreLocalSnapshot(height uint64, format uint32) error { + snapshot, ch, err := m.store.Load(height, format) + if err != nil { + return err + } + return m.restoreSnapshot(*snapshot, ch) +} + // sortedExtensionNames sort extension names for deterministic iteration. func (m *Manager) sortedExtensionNames() []string { names := make([]string, 0, len(m.extensions))