mirror of
https://github.com/0glabs/0g-chain.git
synced 2024-11-10 10:05:18 +00:00
cli: refactor shard command & add recovery options (#1837)
* expose SkipLoadLatest override via AppOpts * add --force-app-version option to shard command * refactor sharding of application.db * refactor sharding of blockstore & state.db * add --only-cometbft-state flag * add comment divisions * update usage doc * prevent infinite loop during cometbft rollback
This commit is contained in:
parent
2a1e9a6631
commit
f5384a1f11
@ -31,6 +31,7 @@ import (
|
|||||||
const (
|
const (
|
||||||
flagMempoolEnableAuth = "mempool.enable-authentication"
|
flagMempoolEnableAuth = "mempool.enable-authentication"
|
||||||
flagMempoolAuthAddresses = "mempool.authorized-addresses"
|
flagMempoolAuthAddresses = "mempool.authorized-addresses"
|
||||||
|
flagSkipLoadLatest = "skip-load-latest"
|
||||||
)
|
)
|
||||||
|
|
||||||
// appCreator holds functions used by the sdk server to control the kava app.
|
// appCreator holds functions used by the sdk server to control the kava app.
|
||||||
@ -101,10 +102,15 @@ func (ac appCreator) newApp(
|
|||||||
chainID = appGenesis.ChainID
|
chainID = appGenesis.ChainID
|
||||||
}
|
}
|
||||||
|
|
||||||
|
skipLoadLatest := false
|
||||||
|
if appOpts.Get(flagSkipLoadLatest) != nil {
|
||||||
|
skipLoadLatest = cast.ToBool(appOpts.Get(flagSkipLoadLatest))
|
||||||
|
}
|
||||||
|
|
||||||
return app.NewApp(
|
return app.NewApp(
|
||||||
logger, db, homeDir, traceStore, ac.encodingConfig,
|
logger, db, homeDir, traceStore, ac.encodingConfig,
|
||||||
app.Options{
|
app.Options{
|
||||||
SkipLoadLatest: false,
|
SkipLoadLatest: skipLoadLatest,
|
||||||
SkipUpgradeHeights: skipUpgradeHeights,
|
SkipUpgradeHeights: skipUpgradeHeights,
|
||||||
SkipGenesisInvariants: cast.ToBool(appOpts.Get(crisis.FlagSkipGenesisInvariants)),
|
SkipGenesisInvariants: cast.ToBool(appOpts.Get(crisis.FlagSkipGenesisInvariants)),
|
||||||
InvariantCheckPeriod: cast.ToUint(appOpts.Get(server.FlagInvCheckPeriod)),
|
InvariantCheckPeriod: cast.ToUint(appOpts.Get(server.FlagInvCheckPeriod)),
|
||||||
|
@ -4,6 +4,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/kava-labs/kava/app"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
dbm "github.com/cometbft/cometbft-db"
|
dbm "github.com/cometbft/cometbft-db"
|
||||||
@ -26,6 +27,8 @@ const (
|
|||||||
flagShardStartBlock = "start"
|
flagShardStartBlock = "start"
|
||||||
flagShardEndBlock = "end"
|
flagShardEndBlock = "end"
|
||||||
flagShardOnlyAppState = "only-app-state"
|
flagShardOnlyAppState = "only-app-state"
|
||||||
|
flagShardForceAppVersion = "force-app-version"
|
||||||
|
flagShardOnlyCometbftState = "only-cometbft-state"
|
||||||
// TODO: --preserve flag for creating & operating on a copy?
|
// TODO: --preserve flag for creating & operating on a copy?
|
||||||
|
|
||||||
// allow using -1 to mean "latest" (perform no rollbacks)
|
// allow using -1 to mean "latest" (perform no rollbacks)
|
||||||
@ -34,7 +37,7 @@ const (
|
|||||||
|
|
||||||
func newShardCmd(opts ethermintserver.StartOptions) *cobra.Command {
|
func newShardCmd(opts ethermintserver.StartOptions) *cobra.Command {
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "shard --home <path-to-home-dir> --start <start-block> --end <end-block> [--only-app-state]",
|
Use: "shard --home <path-to-home-dir> --start <start-block> --end <end-block> [--only-app-state] [--only-cometbft-state] [--force-app-version <app-version>]",
|
||||||
Short: "Strip all blocks from the database outside of a given range",
|
Short: "Strip all blocks from the database outside of a given range",
|
||||||
Long: `shard opens a local kava home directory's databases and removes all blocks outside a range defined by --start and --end. The range is inclusive of the end block.
|
Long: `shard opens a local kava home directory's databases and removes all blocks outside a range defined by --start and --end. The range is inclusive of the end block.
|
||||||
|
|
||||||
@ -42,8 +45,14 @@ It works by first rolling back the latest state to the block before the end bloc
|
|||||||
|
|
||||||
Setting the end block to -1 signals to keep the latest block (no rollbacks).
|
Setting the end block to -1 signals to keep the latest block (no rollbacks).
|
||||||
|
|
||||||
|
The application.db can be loaded at a particular height via the --force-app-version option. This is useful if the sharding process is prematurely terminated while the application.db is being sharded.
|
||||||
|
|
||||||
The --only-app-state flag can be used to skip the pruning of the blockstore and cometbft state. This matches the functionality of the cosmos-sdk's "prune" command. Note that rolled back blocks will still affect all stores.
|
The --only-app-state flag can be used to skip the pruning of the blockstore and cometbft state. This matches the functionality of the cosmos-sdk's "prune" command. Note that rolled back blocks will still affect all stores.
|
||||||
|
|
||||||
|
Similarly, the --only-cometbft-state flag skips pruning app state. This can be useful if the shard command is prematurely terminated during the shard process.
|
||||||
|
|
||||||
|
The shard command only flags the iavl tree nodes for deletion. Actual removal from the databases will be performed when each database is compacted.
|
||||||
|
|
||||||
WARNING: this is a destructive action.`,
|
WARNING: this is a destructive action.`,
|
||||||
Example: `Create a 1M block data shard (keeps blocks kava 1,000,000 to 2,000,000)
|
Example: `Create a 1M block data shard (keeps blocks kava 1,000,000 to 2,000,000)
|
||||||
$ kava shard --home path/to/.kava --start 1000000 --end 2000000
|
$ kava shard --home path/to/.kava --start 1000000 --end 2000000
|
||||||
@ -54,7 +63,9 @@ $ kava shard --home path/to/.kava --start 5000000 --end -1
|
|||||||
Prune first 1M blocks _without_ affecting blockstore or cometBFT state:
|
Prune first 1M blocks _without_ affecting blockstore or cometBFT state:
|
||||||
$ kava shard --home path/to/.kava --start 1000000 --end -1 --only-app-state`,
|
$ kava shard --home path/to/.kava --start 1000000 --end -1 --only-app-state`,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
// read & validate flags
|
//////////////////////////
|
||||||
|
// parse & validate flags
|
||||||
|
//////////////////////////
|
||||||
startBlock, err := cmd.Flags().GetInt64(flagShardStartBlock)
|
startBlock, err := cmd.Flags().GetInt64(flagShardStartBlock)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -70,16 +81,23 @@ $ kava shard --home path/to/.kava --start 1000000 --end -1 --only-app-state`,
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
forceAppVersion, err := cmd.Flags().GetInt64(flagShardForceAppVersion)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
onlyCometbftState, err := cmd.Flags().GetBool(flagShardOnlyCometbftState)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
clientCtx := client.GetClientContextFromCmd(cmd)
|
clientCtx := client.GetClientContextFromCmd(cmd)
|
||||||
|
|
||||||
ctx := server.GetServerContextFromCmd(cmd)
|
ctx := server.GetServerContextFromCmd(cmd)
|
||||||
ctx.Config.SetRoot(clientCtx.HomeDir)
|
ctx.Config.SetRoot(clientCtx.HomeDir)
|
||||||
|
|
||||||
//////////////////////////////
|
////////////////////////
|
||||||
// Rollback state to endBlock
|
// manage db connection
|
||||||
//////////////////////////////
|
////////////////////////
|
||||||
|
|
||||||
// connect to database
|
// connect to database
|
||||||
db, err := opts.DBOpener(ctx.Viper, clientCtx.HomeDir, server.GetAppDBBackend(ctx.Viper))
|
db, err := opts.DBOpener(ctx.Viper, clientCtx.HomeDir, server.GetAppDBBackend(ctx.Viper))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -93,16 +111,83 @@ $ kava shard --home path/to/.kava --start 1000000 --end -1 --only-app-state`,
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
///////////////////
|
||||||
|
// load multistore
|
||||||
|
///////////////////
|
||||||
|
// create app in order to load the multistore
|
||||||
|
// skip loading the latest version so the desired height can be manually loaded
|
||||||
|
ctx.Viper.Set("skip-load-latest", true)
|
||||||
|
|
||||||
|
app := opts.AppCreator(ctx.Logger, db, nil, ctx.Viper).(*app.App)
|
||||||
|
if forceAppVersion == shardEndBlockLatest {
|
||||||
|
if err := app.LoadLatestVersion(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err := app.LoadVersion(forceAppVersion); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
// get the multistore
|
// get the multistore
|
||||||
app := opts.AppCreator(ctx.Logger, db, nil, ctx.Viper)
|
|
||||||
cms := app.CommitMultiStore()
|
cms := app.CommitMultiStore()
|
||||||
multistore, ok := cms.(*rootmulti.Store)
|
multistore, ok := cms.(*rootmulti.Store)
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("only sharding of rootmulti.Store type is supported")
|
return fmt.Errorf("only sharding of rootmulti.Store type is supported")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
////////////////////////
|
||||||
|
// shard application.db
|
||||||
|
////////////////////////
|
||||||
|
if !onlyCometbftState {
|
||||||
|
if err := shardApplicationDb(multistore, startBlock, endBlock); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fmt.Printf("[%s] skipping sharding of application.db\n", flagShardOnlyCometbftState)
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////////////
|
||||||
|
// shard blockstore.db & state.db
|
||||||
|
//////////////////////////////////
|
||||||
|
// open block store & cometbft state
|
||||||
|
blockStore, stateStore, err := openCometBftDbs(ctx.Config)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to open cometbft dbs: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !onlyAppState {
|
||||||
|
if err := shardCometBftDbs(blockStore, stateStore, startBlock, endBlock); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fmt.Printf("[%s] skipping sharding of blockstore.db and state.db\n", flagShardOnlyAppState)
|
||||||
|
fmt.Printf("blockstore contains blocks %d - %d\n", blockStore.Base(), blockStore.Height())
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.Flags().String(flags.FlagHome, opts.DefaultNodeHome, "The application home directory")
|
||||||
|
cmd.Flags().Int64(flagShardStartBlock, 1, "Start block of data shard (inclusive)")
|
||||||
|
cmd.Flags().Int64(flagShardEndBlock, 0, "End block of data shard (inclusive)")
|
||||||
|
cmd.Flags().Bool(flagShardOnlyAppState, false, "Skip pruning of blockstore & cometbft state")
|
||||||
|
cmd.Flags().Bool(flagShardOnlyCometbftState, false, "Skip pruning of application state")
|
||||||
|
cmd.Flags().Int64(flagShardForceAppVersion, shardEndBlockLatest, "Instead of loading latest, force set the version of the multistore that is loaded")
|
||||||
|
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
// shardApplicationDb prunes the multistore up to startBlock and rolls it back to endBlock
|
||||||
|
func shardApplicationDb(multistore *rootmulti.Store, startBlock, endBlock int64) error {
|
||||||
|
//////////////////////////////
|
||||||
|
// Rollback state to endBlock
|
||||||
|
//////////////////////////////
|
||||||
// handle desired endblock being latest
|
// handle desired endblock being latest
|
||||||
latest := multistore.LatestVersion()
|
latest := multistore.LastCommitID().Version
|
||||||
|
if latest == 0 {
|
||||||
|
return fmt.Errorf("failed to find latest height >0")
|
||||||
|
}
|
||||||
fmt.Printf("latest height: %d\n", latest)
|
fmt.Printf("latest height: %d\n", latest)
|
||||||
if endBlock == shardEndBlockLatest {
|
if endBlock == shardEndBlockLatest {
|
||||||
endBlock = latest
|
endBlock = latest
|
||||||
@ -114,52 +199,19 @@ $ kava shard --home path/to/.kava --start 1000000 --end -1 --only-app-state`,
|
|||||||
return fmt.Errorf("data does not contain end block (%d): latest version is %d", endBlock, latest)
|
return fmt.Errorf("data does not contain end block (%d): latest version is %d", endBlock, latest)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("pruning data in %s down to heights %d - %d (%d blocks)\n", clientCtx.HomeDir, startBlock, endBlock, shardSize)
|
fmt.Printf("pruning data down to heights %d - %d (%d blocks)\n", startBlock, endBlock, shardSize)
|
||||||
|
|
||||||
// set pruning options to prevent no-ops from `PruneStores`
|
// set pruning options to prevent no-ops from `PruneStores`
|
||||||
multistore.SetPruning(pruningtypes.PruningOptions{KeepRecent: uint64(shardSize), Interval: 0})
|
multistore.SetPruning(pruningtypes.PruningOptions{KeepRecent: uint64(shardSize), Interval: 0})
|
||||||
|
|
||||||
// rollback application state
|
// rollback application state
|
||||||
if err = multistore.RollbackToVersion(endBlock); err != nil {
|
if err := multistore.RollbackToVersion(endBlock); err != nil {
|
||||||
return fmt.Errorf("failed to rollback application state: %s", err)
|
return fmt.Errorf("failed to rollback application state: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// open block store & cometbft state
|
|
||||||
blockStore, stateStore, err := openCometBftDbs(ctx.Config)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to open cometbft dbs: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// prep for outputting progress repeatedly to same line
|
|
||||||
needsRollback := endBlock < latest
|
|
||||||
progress := "rolling back blockstore & cometbft state to height %d"
|
|
||||||
numChars := len(fmt.Sprintf(progress, latest))
|
|
||||||
clearLine := fmt.Sprintf("\r%s\r", strings.Repeat(" ", numChars))
|
|
||||||
printRollbackProgress := func(h int64) {
|
|
||||||
fmt.Print(clearLine)
|
|
||||||
fmt.Printf(progress, h)
|
|
||||||
}
|
|
||||||
|
|
||||||
// rollback tendermint db
|
|
||||||
height := latest
|
|
||||||
for height > endBlock {
|
|
||||||
printRollbackProgress(height - 1)
|
|
||||||
height, _, err = tmstate.Rollback(blockStore, stateStore, true)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to rollback tendermint state: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if needsRollback {
|
|
||||||
fmt.Println()
|
|
||||||
} else {
|
|
||||||
fmt.Printf("latest store height is already %d\n", latest)
|
|
||||||
}
|
|
||||||
|
|
||||||
//////////////////////////////
|
//////////////////////////////
|
||||||
// Prune blocks to startBlock
|
// Prune blocks to startBlock
|
||||||
//////////////////////////////
|
//////////////////////////////
|
||||||
|
|
||||||
// enumerate all heights to prune
|
// enumerate all heights to prune
|
||||||
pruneHeights := make([]int64, 0, latest-shardSize)
|
pruneHeights := make([]int64, 0, latest-shardSize)
|
||||||
for i := int64(1); i < startBlock; i++ {
|
for i := int64(1); i < startBlock; i++ {
|
||||||
@ -174,11 +226,58 @@ $ kava shard --home path/to/.kava --start 1000000 --end -1 --only-app-state`,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// shardCometBftDbs shrinks blockstore.db & state.db down to the desired block range
|
||||||
|
func shardCometBftDbs(blockStore *store.BlockStore, stateStore tmstate.Store, startBlock, endBlock int64) error {
|
||||||
|
var err error
|
||||||
|
latest := blockStore.Height()
|
||||||
|
if endBlock == shardEndBlockLatest {
|
||||||
|
endBlock = latest
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////////
|
||||||
|
// Rollback state to endBlock
|
||||||
|
//////////////////////////////
|
||||||
|
// prep for outputting progress repeatedly to same line
|
||||||
|
needsRollback := endBlock < latest
|
||||||
|
progress := "rolling back blockstore & cometbft state to height %d"
|
||||||
|
numChars := len(fmt.Sprintf(progress, latest))
|
||||||
|
clearLine := fmt.Sprintf("\r%s\r", strings.Repeat(" ", numChars))
|
||||||
|
printRollbackProgress := func(h int64) {
|
||||||
|
fmt.Print(clearLine)
|
||||||
|
fmt.Printf(progress, h)
|
||||||
|
}
|
||||||
|
|
||||||
|
// rollback tendermint db
|
||||||
|
height := latest
|
||||||
|
for height > endBlock {
|
||||||
|
beforeRollbackHeight := height
|
||||||
|
printRollbackProgress(height - 1)
|
||||||
|
height, _, err = tmstate.Rollback(blockStore, stateStore, true)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to rollback cometbft state: %w", err)
|
||||||
|
}
|
||||||
|
if beforeRollbackHeight == height {
|
||||||
|
return fmt.Errorf("attempting to rollback cometbft state height %d failed (no rollback performed)", height)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if needsRollback {
|
||||||
|
fmt.Println()
|
||||||
|
} else {
|
||||||
|
fmt.Printf("latest store height is already %d\n", latest)
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////////
|
||||||
|
// Prune blocks to startBlock
|
||||||
|
//////////////////////////////
|
||||||
// get starting block of block store
|
// get starting block of block store
|
||||||
baseBlock := blockStore.Base()
|
baseBlock := blockStore.Base()
|
||||||
|
|
||||||
// only prune if data exists, otherwise blockStore.PruneBlocks will panic
|
// only prune if data exists, otherwise blockStore.PruneBlocks will panic
|
||||||
if !onlyAppState && baseBlock < startBlock {
|
if baseBlock < startBlock {
|
||||||
// prune block store
|
// prune block store
|
||||||
fmt.Printf("pruning block store from %d - %d\n", baseBlock, startBlock)
|
fmt.Printf("pruning block store from %d - %d\n", baseBlock, startBlock)
|
||||||
if _, err := blockStore.PruneBlocks(startBlock); err != nil {
|
if _, err := blockStore.PruneBlocks(startBlock); err != nil {
|
||||||
@ -194,18 +293,7 @@ $ kava shard --home path/to/.kava --start 1000000 --end -1 --only-app-state`,
|
|||||||
fmt.Printf("blockstore and cometbft state begins at block %d\n", baseBlock)
|
fmt.Printf("blockstore and cometbft state begins at block %d\n", baseBlock)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: db compaction
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd.Flags().String(flags.FlagHome, opts.DefaultNodeHome, "The application home directory")
|
|
||||||
cmd.Flags().Int64(flagShardStartBlock, 1, "Start block of data shard (inclusive)")
|
|
||||||
cmd.Flags().Int64(flagShardEndBlock, 0, "End block of data shard (inclusive)")
|
|
||||||
cmd.Flags().Bool(flagShardOnlyAppState, false, "Skip pruning of blockstore & cometbft state")
|
|
||||||
|
|
||||||
return cmd
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// inspired by https://github.com/Kava-Labs/cometbft/blob/277b0853db3f67865a55aa1c54f59790b5f591be/node/node.go#L234
|
// inspired by https://github.com/Kava-Labs/cometbft/blob/277b0853db3f67865a55aa1c54f59790b5f591be/node/node.go#L234
|
||||||
|
Loading…
Reference in New Issue
Block a user