diff --git a/CHANGELOG.md b/CHANGELOG.md index feaca9011e7b..7289e013bc98 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -46,6 +46,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ * [#12970](https://github.com/cosmos/cosmos-sdk/pull/12970) Bump Tendermint to `v0.34.21` and IAVL to `v0.19.1`. * [#12693](https://github.com/cosmos/cosmos-sdk/pull/12693) Make sure the order of each node is consistent when emitting proto events. * (simapp) [#13107](https://github.com/cosmos/cosmos-sdk/pull/13107) Call `SetIAVLCacheSize` with the configured value in simapp. +* (cli) [#12742](https://github.com/cosmos/cosmos-sdk/pull/12742) Add the `prune` CLI cmd to manually prune app store history versions based on the pruning options. ### Bug Fixes diff --git a/client/pruning/main.go b/client/pruning/main.go new file mode 100644 index 000000000000..dfaf45f7f916 --- /dev/null +++ b/client/pruning/main.go @@ -0,0 +1,121 @@ +package pruning + +import ( + "fmt" + "os" + "path/filepath" + + "github.com/spf13/cobra" + "github.com/spf13/viper" + + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/cosmos/cosmos-sdk/server" + servertypes "github.com/cosmos/cosmos-sdk/server/types" + "github.com/cosmos/cosmos-sdk/store/rootmulti" + storetypes "github.com/cosmos/cosmos-sdk/store/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/tendermint/tendermint/libs/log" + dbm "github.com/tendermint/tm-db" +) + +const FlagAppDBBackend = "app-db-backend" + +// PruningCmd prunes the sdk root multi store history versions based on the pruning options +// specified by command flags. +func PruningCmd(appCreator servertypes.AppCreator) *cobra.Command { + cmd := &cobra.Command{ + Use: "prune", + Short: "Prune app history states by keeping the recent heights and deleting old heights", + Long: `Prune app history states by keeping the recent heights and deleting old heights. + The pruning option is provided via the '--pruning' flag or alternatively with '--pruning-keep-recent' + + For '--pruning' the options are as follows: + + default: the last 362880 states are kept + nothing: all historic states will be saved, nothing will be deleted (i.e. archiving node) + everything: 2 latest states will be kept + custom: allow pruning options to be manually specified through 'pruning-keep-recent'. + besides pruning options, database home directory and database backend type should also be specified via flags + '--home' and '--app-db-backend'. + valid app-db-backend type includes 'goleveldb', 'cleveldb', 'rocksdb', 'boltdb', and 'badgerdb'. + `, + Example: `prune --home './' --app-db-backend 'goleveldb' --pruning 'custom' --pruning-keep-recent 100 -- + pruning-keep-every 10, --pruning-interval 10`, + RunE: func(cmd *cobra.Command, _ []string) error { + vp := viper.New() + + // Bind flags to the Context's Viper so we can get pruning options. + if err := vp.BindPFlags(cmd.Flags()); err != nil { + return err + } + pruningOptions, err := server.GetPruningOptionsFromFlags(vp) + if err != nil { + return err + } + fmt.Printf("get pruning options from command flags, keep-recent: %v\n", + pruningOptions.KeepRecent, + ) + + home := vp.GetString(flags.FlagHome) + db, err := openDB(home) + if err != nil { + return err + } + + logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)) + app := appCreator(logger, db, nil, vp) + cms := app.CommitMultiStore() + + rootMultiStore, ok := cms.(*rootmulti.Store) + if !ok { + return fmt.Errorf("currently only support the pruning of rootmulti.Store type") + } + latestHeight := rootmulti.GetLatestVersion(db) + // valid heights should be greater than 0. + if latestHeight <= 0 { + return fmt.Errorf("the database has no valid heights to prune, the latest height: %v", latestHeight) + } + + var pruningHeights []int64 + for height := int64(1); height < latestHeight; height++ { + if height < latestHeight-int64(pruningOptions.KeepRecent) { + pruningHeights = append(pruningHeights, height) + } + } + if len(pruningHeights) == 0 { + fmt.Printf("no heights to prune\n") + return nil + } + fmt.Printf( + "pruning heights start from %v, end at %v\n", + pruningHeights[0], + pruningHeights[len(pruningHeights)-1], + ) + + rootMultiStore.PruneStores(false, pruningHeights) + if err != nil { + return err + } + fmt.Printf("successfully pruned the application root multi stores\n") + return nil + }, + } + + cmd.Flags().String(flags.FlagHome, "", "The database home directory") + cmd.Flags().String(FlagAppDBBackend, "", "The type of database for application and snapshots databases") + cmd.Flags().String(server.FlagPruning, storetypes.PruningOptionDefault, "Pruning strategy (default|nothing|everything|custom)") + cmd.Flags().Uint64(server.FlagPruningKeepRecent, 0, "Number of recent heights to keep on disk (ignored if pruning is not 'custom')") + cmd.Flags().Uint64(server.FlagPruningKeepEvery, 0, + `Offset heights to keep on disk after 'keep-every' (ignored if pruning is not 'custom'), + this is not used by this command but kept for compatibility with the complete pruning options`) + cmd.Flags().Uint64(server.FlagPruningInterval, 10, + `Height interval at which pruned heights are removed from disk (ignored if pruning is not 'custom'), + this is not used by this command but kept for compatibility with the complete pruning options`) + + return cmd +} + +func openDB(rootDir string) (dbm.DB, error) { + dataDir := filepath.Join(rootDir, "data") + return sdk.NewLevelDB("application", dataDir) +} diff --git a/server/types/app.go b/server/types/app.go index 467f627c605f..f4c59afea24a 100644 --- a/server/types/app.go +++ b/server/types/app.go @@ -15,6 +15,7 @@ import ( "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/server/api" "github.com/cosmos/cosmos-sdk/server/config" + sdk "github.com/cosmos/cosmos-sdk/types" ) // ServerStartTime defines the time duration that the server need to stay running after startup @@ -51,6 +52,9 @@ type ( // RegisterTendermintService registers the gRPC Query service for tendermint queries. RegisterTendermintService(clientCtx client.Context) + + // CommitMultiStore Returns the multistore instance + CommitMultiStore() sdk.CommitMultiStore } // AppCreator is a function that allows us to lazily initialize an diff --git a/simapp/simd/cmd/root.go b/simapp/simd/cmd/root.go index 3189241f9b8d..8f3e951199e1 100644 --- a/simapp/simd/cmd/root.go +++ b/simapp/simd/cmd/root.go @@ -19,6 +19,7 @@ import ( "github.com/cosmos/cosmos-sdk/client/debug" "github.com/cosmos/cosmos-sdk/client/flags" "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/server" servertypes "github.com/cosmos/cosmos-sdk/server/types" @@ -141,6 +142,7 @@ func initRootCmd(rootCmd *cobra.Command, encodingConfig params.EncodingConfig) { cfg := sdk.GetConfig() cfg.Seal() + a := appCreator{encodingConfig} rootCmd.AddCommand( genutilcli.InitCmd(simapp.ModuleBasics, simapp.DefaultNodeHome), genutilcli.CollectGenTxsCmd(banktypes.GenesisBalancesIterator{}, simapp.DefaultNodeHome), @@ -152,9 +154,9 @@ func initRootCmd(rootCmd *cobra.Command, encodingConfig params.EncodingConfig) { testnetCmd(simapp.ModuleBasics, banktypes.GenesisBalancesIterator{}), debug.Cmd(), config.Cmd(), + pruning.PruningCmd(a.newApp), ) - a := appCreator{encodingConfig} server.AddCommands(rootCmd, simapp.DefaultNodeHome, a.newApp, a.appExport, addModuleInitFlags) // add keybase, auxiliary RPC, query, and tx child commands diff --git a/store/rootmulti/store.go b/store/rootmulti/store.go index 1f8ef097d0f2..9de85d0c270d 100644 --- a/store/rootmulti/store.go +++ b/store/rootmulti/store.go @@ -156,7 +156,7 @@ func (rs *Store) GetStores() map[types.StoreKey]types.CommitKVStore { // LoadLatestVersionAndUpgrade implements CommitMultiStore func (rs *Store) LoadLatestVersionAndUpgrade(upgrades *types.StoreUpgrades) error { - ver := getLatestVersion(rs.db) + ver := GetLatestVersion(rs.db) return rs.loadVersion(ver, upgrades) } @@ -167,7 +167,7 @@ func (rs *Store) LoadVersionAndUpgrade(ver int64, upgrades *types.StoreUpgrades) // LoadLatestVersion implements CommitMultiStore. func (rs *Store) LoadLatestVersion() error { - ver := getLatestVersion(rs.db) + ver := GetLatestVersion(rs.db) return rs.loadVersion(ver, nil) } @@ -378,7 +378,7 @@ func (rs *Store) ListeningEnabled(key types.StoreKey) bool { func (rs *Store) LastCommitID() types.CommitID { if rs.lastCommitInfo == nil { return types.CommitID{ - Version: getLatestVersion(rs.db), + Version: GetLatestVersion(rs.db), } } @@ -420,7 +420,7 @@ func (rs *Store) Commit() types.CommitID { // batch prune if the current height is a pruning interval height if rs.pruningOpts.Interval > 0 && version%int64(rs.pruningOpts.Interval) == 0 { - rs.pruneStores() + rs.PruneStores(true, nil) } flushMetadata(rs.db, version, rs.lastCommitInfo, rs.pruneHeights) @@ -431,9 +431,14 @@ func (rs *Store) Commit() types.CommitID { } } -// pruneStores will batch delete a list of heights from each mounted sub-store. -// Afterwards, pruneHeights is reset. -func (rs *Store) pruneStores() { +// PruneStores will batch delete a list of heights from each mounted sub-store. +// If clearStorePruningHeihgts is true, store's pruneHeights is appended to the +// pruningHeights and reset after finishing pruning. +func (rs *Store) PruneStores(clearStorePruningHeihgts bool, pruningHeights []int64) { + if clearStorePruningHeihgts { + pruningHeights = append(pruningHeights, rs.pruneHeights...) + } + if len(rs.pruneHeights) == 0 { return } @@ -444,7 +449,7 @@ func (rs *Store) pruneStores() { // it to get the underlying IAVL store. store = rs.GetCommitKVStore(key) - if err := store.(*iavl.Store).DeleteVersions(rs.pruneHeights...); err != nil { + if err := store.(*iavl.Store).DeleteVersions(pruningHeights...); err != nil { if errCause := errors.Cause(err); errCause != nil && errCause != iavltree.ErrVersionDoesNotExist { panic(err) } @@ -452,7 +457,9 @@ func (rs *Store) pruneStores() { } } - rs.pruneHeights = make([]int64, 0) + if clearStorePruningHeihgts { + rs.pruneHeights = make([]int64, 0) + } } // CacheWrap implements CacheWrapper/Store/CommitStore. @@ -900,14 +907,14 @@ func (rs *Store) RollbackToVersion(target int64) int64 { if target < 0 { panic("Negative rollback target") } - current := getLatestVersion(rs.db) + current := GetLatestVersion(rs.db) if target >= current { return current } for ; current > target; current-- { rs.pruneHeights = append(rs.pruneHeights, current) } - rs.pruneStores() + rs.PruneStores(true, nil) // update latest height bz, err := gogotypes.StdInt64Marshal(current) @@ -926,7 +933,7 @@ type storeParams struct { initialVersion uint64 } -func getLatestVersion(db dbm.DB) int64 { +func GetLatestVersion(db dbm.DB) int64 { bz, err := db.Get([]byte(latestVersionKey)) if err != nil { panic(err)