diff --git a/CHANGELOG.md b/CHANGELOG.md index 1383e5cab6b0..7d081463bebf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -47,6 +47,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 index bdcff50a9bc5..dfaf45f7f916 100644 --- a/client/pruning/main.go +++ b/client/pruning/main.go @@ -1,84 +1,74 @@ package pruning import ( - "errors" "fmt" + "os" "path/filepath" - dbm "github.com/cosmos/cosmos-db" "github.com/spf13/cobra" "github.com/spf13/viper" - corestore "cosmossdk.io/core/store" - "cosmossdk.io/log" - pruningtypes "cosmossdk.io/store/pruning/types" - "cosmossdk.io/store/rootmulti" - "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/version" + "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" -// Cmd prunes the sdk root multi store history versions based on the pruning options +// PruningCmd prunes the sdk root multi store history versions based on the pruning options // specified by command flags. -func Cmd[T servertypes.Application](appCreator servertypes.AppCreator[T]) *cobra.Command { +func PruningCmd(appCreator servertypes.AppCreator) *cobra.Command { cmd := &cobra.Command{ - Use: "prune [pruning-method]", + 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' argument or alternatively with '--pruning-keep-recent' - -- 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' - -Note: When the --app-db-backend flag is not specified, the default backend type is 'goleveldb'. -Supported app-db-backend types include 'goleveldb', 'rocksdb', 'pebbledb'.`, - Example: fmt.Sprintf("%s prune custom --pruning-keep-recent 100 --app-db-backend 'goleveldb'", version.AppName), - Args: cobra.RangeArgs(0, 1), - RunE: func(cmd *cobra.Command, args []string) error { - // bind flags to the Context's Viper so we can get pruning options. + 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 } - if err := vp.BindPFlags(cmd.PersistentFlags()); err != nil { - return err - } - - // use the first argument if present to set the pruning method - if len(args) > 0 { - vp.Set(server.FlagPruning, args[0]) - } else { - vp.Set(server.FlagPruning, pruningtypes.PruningOptionDefault) - } pruningOptions, err := server.GetPruningOptionsFromFlags(vp) if err != nil { return err } - - cmd.Printf("get pruning options from command flags, strategy: %v, keep-recent: %v\n", - pruningOptions.Strategy, + fmt.Printf("get pruning options from command flags, keep-recent: %v\n", pruningOptions.KeepRecent, ) home := vp.GetString(flags.FlagHome) - db, err := openDB(home, server.GetAppDBBackend(vp)) + db, err := openDB(home) if err != nil { return err } - logger := log.NewLogger(cmd.OutOrStdout()) + logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)) app := appCreator(logger, db, nil, vp) cms := app.CommitMultiStore() rootMultiStore, ok := cms.(*rootmulti.Store) if !ok { - return errors.New("currently only support the pruning of rootmulti.Store type") + return fmt.Errorf("currently only support the pruning of rootmulti.Store type") } latestHeight := rootmulti.GetLatestVersion(db) // valid heights should be greater than 0. @@ -86,21 +76,38 @@ Supported app-db-backend types include 'goleveldb', 'rocksdb', 'pebbledb'.`, return fmt.Errorf("the database has no valid heights to prune, the latest height: %v", latestHeight) } - pruningHeight := latestHeight - int64(pruningOptions.KeepRecent) - cmd.Printf("pruning heights up to %v\n", pruningHeight) + 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], + ) - err = rootMultiStore.PruneStores(pruningHeight) + rootMultiStore.PruneStores(false, pruningHeights) if err != nil { return err } - - cmd.Println("successfully pruned the application root multi stores") + 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`) @@ -108,7 +115,7 @@ Supported app-db-backend types include 'goleveldb', 'rocksdb', 'pebbledb'.`, return cmd } -func openDB(rootDir string, backendType dbm.BackendType) (corestore.KVStoreWithBatch, error) { +func openDB(rootDir string) (dbm.DB, error) { dataDir := filepath.Join(rootDir, "data") - return dbm.NewDB("application", backendType, dataDir) + return sdk.NewLevelDB("application", dataDir) } diff --git a/server/types/app.go b/server/types/app.go index 6da58d7c3a87..47008d1015c3 100644 --- a/server/types/app.go +++ b/server/types/app.go @@ -19,6 +19,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 @@ -57,18 +58,11 @@ type ( // RegisterNodeService registers the node gRPC Query service. RegisterNodeService(client.Context, config.Config) - // CommitMultiStore return the multistore instance - CommitMultiStore() storetypes.CommitMultiStore + // RegisterTendermintService registers the gRPC Query service for tendermint queries. + RegisterTendermintService(clientCtx client.Context) - // SnapshotManager return the snapshot manager - SnapshotManager() *snapshots.Manager - - // ValidatorKeyProvider returns a function that generates a validator key - ValidatorKeyProvider() func() (cmtcrypto.PrivKey, error) - - // Close is called in start cmd to gracefully cleanup resources. - // Must be safe to be called multiple times. - Close() error + // 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 06325325e40b..6347b6ea44b6 100644 --- a/simapp/simd/cmd/root.go +++ b/simapp/simd/cmd/root.go @@ -15,8 +15,11 @@ import ( "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/config" - nodeservice "github.com/cosmos/cosmos-sdk/client/grpc/node" - addresscodec "github.com/cosmos/cosmos-sdk/codec/address" + "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" simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims" sdk "github.com/cosmos/cosmos-sdk/types" @@ -108,7 +111,120 @@ func NewRootCmd() *cobra.Command { }, } - initRootCmd(rootCmd, tempApp.ModuleManager) + customAppTemplate := serverconfig.DefaultConfigTemplate + ` +[wasm] +# This is the maximum sdk gas (wasm and storage) that we allow for any x/wasm "smart" queries +query_gas_limit = 300000 +# This is the number of wasm vm instances we keep cached in memory for speed-up +# Warning: this is currently unstable and may lead to crashes, best to keep for 0 unless testing locally +lru_size = 0` + + return customAppTemplate, customAppConfig +} + +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), + genutilcli.MigrateGenesisCmd(), + genutilcli.GenTxCmd(simapp.ModuleBasics, encodingConfig.TxConfig, banktypes.GenesisBalancesIterator{}, simapp.DefaultNodeHome), + genutilcli.ValidateGenesisCmd(simapp.ModuleBasics), + AddGenesisAccountCmd(simapp.DefaultNodeHome), + tmcli.NewCompletionCmd(rootCmd, true), + testnetCmd(simapp.ModuleBasics, banktypes.GenesisBalancesIterator{}), + debug.Cmd(), + config.Cmd(), + pruning.PruningCmd(a.newApp), + ) + + server.AddCommands(rootCmd, simapp.DefaultNodeHome, a.newApp, a.appExport, addModuleInitFlags) + + // add keybase, auxiliary RPC, query, and tx child commands + rootCmd.AddCommand( + rpc.StatusCommand(), + queryCommand(), + txCommand(), + keys.Commands(simapp.DefaultNodeHome), + ) + + // add rosetta + rootCmd.AddCommand(server.RosettaCommand(encodingConfig.InterfaceRegistry, encodingConfig.Marshaler)) +} + +func addModuleInitFlags(startCmd *cobra.Command) { + crisis.AddModuleInitFlags(startCmd) +} + +func queryCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "query", + Aliases: []string{"q"}, + Short: "Querying subcommands", + DisableFlagParsing: true, + SuggestionsMinimumDistance: 2, + RunE: client.ValidateCmd, + } + + cmd.AddCommand( + authcmd.GetAccountCmd(), + rpc.ValidatorCommand(), + rpc.BlockCommand(), + authcmd.QueryTxsByEventsCmd(), + authcmd.QueryTxCmd(), + ) + + simapp.ModuleBasics.AddQueryCommands(cmd) + cmd.PersistentFlags().String(flags.FlagChainID, "", "The network chain ID") + + return cmd +} + +func txCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "tx", + Short: "Transactions subcommands", + DisableFlagParsing: true, + SuggestionsMinimumDistance: 2, + RunE: client.ValidateCmd, + } + + cmd.AddCommand( + authcmd.GetSignCommand(), + authcmd.GetSignBatchCommand(), + authcmd.GetMultiSignCommand(), + authcmd.GetMultiSignBatchCmd(), + authcmd.GetValidateSignaturesCommand(), + authcmd.GetBroadcastCommand(), + authcmd.GetEncodeCommand(), + authcmd.GetDecodeCommand(), + ) + + simapp.ModuleBasics.AddTxCommands(cmd) + cmd.PersistentFlags().String(flags.FlagChainID, "", "The network chain ID") + + return cmd +} + +type appCreator struct { + encCfg params.EncodingConfig +} + +// newApp is an appCreator +func (a appCreator) newApp(logger log.Logger, db dbm.DB, traceStore io.Writer, appOpts servertypes.AppOptions) servertypes.Application { + var cache sdk.MultiStorePersistentCache + + if cast.ToBool(appOpts.Get(server.FlagInterBlockCache)) { + cache = store.NewCommitKVStoreCacheManager() + } + + skipUpgradeHeights := make(map[int64]bool) + for _, h := range cast.ToIntSlice(appOpts.Get(server.FlagUnsafeSkipUpgrades)) { + skipUpgradeHeights[int64(h)] = true + } // autocli opts customClientTemplate, customClientConfig := initClientConfig() diff --git a/store/rootmulti/store.go b/store/rootmulti/store.go index 9e6693f40beb..6f5abb40f45b 100644 --- a/store/rootmulti/store.go +++ b/store/rootmulti/store.go @@ -444,8 +444,7 @@ func (rs *Store) LastCommitID() types.CommitID { emptyHash := sha256.Sum256([]byte{}) appHash := emptyHash[:] return types.CommitID{ - Version: rs.lastCommitInfo.Version, - Hash: appHash, // set empty apphash to sha256([]byte{}) if hash is nil + Version: GetLatestVersion(rs.db), } } @@ -508,8 +507,10 @@ func (rs *Store) Commit() types.CommitID { } } - // reset the removalMap - rs.removalMap = make(map[types.StoreKey]bool) + // batch prune if the current height is a pruning interval height + if rs.pruningOpts.Interval > 0 && version%int64(rs.pruningOpts.Interval) == 0 { + rs.PruneStores(true, nil) + } if err := rs.handlePruning(version); err != nil { rs.logger.Error( @@ -524,35 +525,33 @@ func (rs *Store) Commit() types.CommitID { } } -// WorkingHash returns the current hash of the store. -// it will be used to get the current app hash before commit. -func (rs *Store) WorkingHash() []byte { - storeInfos := make([]types.StoreInfo, 0, len(rs.stores)) - storeKeys := keysFromStoreKeyMap(rs.stores) +// 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 + } for _, key := range storeKeys { store := rs.stores[key] - if store.GetStoreType() != types.StoreTypeIAVL { - continue - } - - if !rs.removalMap[key] { - si := types.StoreInfo{ - Name: key.Name(), - CommitId: types.CommitID{ - Hash: store.WorkingHash(), - }, + if err := store.(*iavl.Store).DeleteVersions(pruningHeights...); err != nil { + if errCause := errors.Cause(err); errCause != nil && errCause != iavltree.ErrVersionDoesNotExist { + panic(err) + } } storeInfos = append(storeInfos, si) } } - sort.SliceStable(storeInfos, func(i, j int) bool { - return storeInfos[i].Name < storeInfos[j].Name - }) - - return types.CommitInfo{StoreInfos: storeInfos}.Hash() + if clearStorePruningHeihgts { + rs.pruneHeights = make([]int64, 0) + } } // CacheWrap implements CacheWrapper/Store/CommitStore. @@ -1041,14 +1040,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) @@ -1067,16 +1066,7 @@ type storeParams struct { initialVersion uint64 } -func newStoreParams(key types.StoreKey, db corestore.KVStoreWithBatch, typ types.StoreType, initialVersion uint64) storeParams { - return storeParams{ - key: key, - db: db, - typ: typ, - initialVersion: initialVersion, - } -} - -func GetLatestVersion(db corestore.KVStoreWithBatch) int64 { +func GetLatestVersion(db dbm.DB) int64 { bz, err := db.Get([]byte(latestVersionKey)) if err != nil { panic(err)