diff --git a/server/migrate_store.go b/server/migrate_store.go new file mode 100644 index 0000000000..32be232493 --- /dev/null +++ b/server/migrate_store.go @@ -0,0 +1,94 @@ +package server + +import ( + "errors" + "fmt" + "os" + + "github.com/cometbft/cometbft/node" + "github.com/spf13/cobra" + + "github.com/cosmos/cosmos-sdk/client/flags" + serverconfig "github.com/cosmos/cosmos-sdk/server/config" + "github.com/cosmos/cosmos-sdk/server/types" + "github.com/cosmos/cosmos-sdk/store/rootmulti" + storetypes "github.com/cosmos/cosmos-sdk/store/types" +) + +// tmpMigratingDir is a temporary directory to facilitate the migration. +const tmpMigratingDir = "data-migrating" + +// NewMigrateStoreCmd creates a command to migrate multistore from IAVL stores to plain DB stores to enable fast node. +func NewMigrateStoreCmd(appCreator types.AppCreator, defaultNodeHome string) *cobra.Command { + cmd := &cobra.Command{ + Use: "migrate-store", + Short: "migrate application db to use plain db stores instead of IAVL stores", + Long: ` +To run a fast node, plain DB store type is needed. To convert a normal full node to a fast full node, +we need to migrate the underlying stores. With this command, the old application db will be backed up, +the new application db will use plain DB store types. +`, + RunE: func(cmd *cobra.Command, args []string) error { + ctx := GetServerContextFromCmd(cmd) + cfg := ctx.Config + home := cfg.RootDir + db, err := openDB(home, GetAppDBBackend(ctx.Viper)) + if err != nil { + return err + } + newDb, err := openDBWithDataDir(home, tmpMigratingDir, GetAppDBBackend(ctx.Viper)) + if err != nil { + return err + } + config, err := serverconfig.GetConfig(ctx.Viper) + if err != nil { + return err + } + genDocProvider := node.DefaultGenesisDocProviderFunc(ctx.Config) + genDoc, err := genDocProvider() + if err != nil { + return err + } + app := appCreator(ctx.Logger, db, nil, genDoc.ChainID, &config, ctx.Viper) + + if err = app.CommitMultiStore().LoadLatestVersion(); err != nil { + return err + } + rs, ok := app.CommitMultiStore().(*rootmulti.Store) + if !ok { + return errors.New("cannot convert store to root multi store") + } + + if err = rs.MigrateStores(storetypes.StoreTypeDB, newDb); err != nil { + return err + } + version, err := rootmulti.MigrateCommitInfos(db, newDb) + if err != nil { + return err + } + fmt.Printf("Multi root store is captured at version %d \n", version) + + _ = db.Close() + _ = newDb.Close() + + applicationPath := fmt.Sprintf("%s%c%s%c%s", home, os.PathSeparator, "data", os.PathSeparator, "application.db") + applicationBackupPath := fmt.Sprintf("%s%c%s%c%s", home, os.PathSeparator, "data", os.PathSeparator, "application.db-backup") + applicationMigratePath := fmt.Sprintf("%s%c%s%c%s", home, os.PathSeparator, tmpMigratingDir, os.PathSeparator, "application.db") + if err = os.Rename(applicationPath, applicationBackupPath); err != nil { + return err + } + if err = os.Rename(applicationMigratePath, applicationPath); err != nil { + return err + } + fmt.Printf("Application db is replaced and the old one is backup %s\n", applicationBackupPath) + + _ = os.Remove(applicationMigratePath) + fmt.Printf("Migrate application db done, please update app.toml and config.toml to use fastnode feature") + + return nil + }, + } + + cmd.Flags().String(flags.FlagHome, defaultNodeHome, "The application home directory") + return cmd +} diff --git a/server/util.go b/server/util.go index 7a7f89ef8f..28845a094a 100644 --- a/server/util.go +++ b/server/util.go @@ -323,6 +323,7 @@ func AddCommands(rootCmd *cobra.Command, defaultNodeHome string, appCreator type ExportCmd(appExport, defaultNodeHome), version.NewVersionCommand(), NewRollbackCmd(appCreator, defaultNodeHome), + NewMigrateStoreCmd(appCreator, defaultNodeHome), ) } @@ -432,6 +433,11 @@ func openDB(rootDir string, backendType dbm.BackendType, opts ...*dbm.NewDatabas return dbm.NewDB("application", backendType, dataDir, opts...) } +func openDBWithDataDir(rootDir, subDir string, backendType dbm.BackendType, opts ...*dbm.NewDatabaseOption) (dbm.DB, error) { + dataDir := filepath.Join(rootDir, subDir) + return dbm.NewDB("application", backendType, dataDir, opts...) +} + func openTraceWriter(traceWriterFile string) (w io.WriteCloser, err error) { if traceWriterFile == "" { return diff --git a/store/rootmulti/store.go b/store/rootmulti/store.go index ef7a5dbc16..9c523fe0e8 100644 --- a/store/rootmulti/store.go +++ b/store/rootmulti/store.go @@ -136,6 +136,31 @@ func (rs *Store) GetStoreType() types.StoreType { return types.StoreTypeMulti } +// MigrateStores will migrate stores to another type in another db. +func (rs *Store) MigrateStores(targetType types.StoreType, newDb dbm.DB) error { + if targetType != types.StoreTypeDB { + return errors.New("only StoreTypeDB is supported") + } + + for key, store := range rs.stores { + switch store.GetStoreType() { + case types.StoreTypeIAVL: + rs.logger.Info("Migrating IAVL store", "store name", key.Name()) + iterator := store.Iterator(nil, nil) + for ; iterator.Valid(); iterator.Next() { + prefixKey := append([]byte("s/k:"+key.Name()+"/"), iterator.Key()...) + if err := newDb.SetSync(prefixKey, iterator.Value()); err != nil { + return err + } + } + _ = iterator.Close() + default: + + } + } + return nil +} + // MountStoreWithDB implements CommitMultiStore. func (rs *Store) MountStoreWithDB(key types.StoreKey, typ types.StoreType, db dbm.DB) { if key == nil { @@ -1249,3 +1274,34 @@ func flushLatestVersion(batch dbm.Batch, version int64) { batch.Set([]byte(latestVersionKey), bz) } + +// MigrateCommitInfos will migrate commit infos to another db. +func MigrateCommitInfos(oldDb, newDb dbm.DB) (int64, error) { + bz, err := oldDb.Get([]byte(latestVersionKey)) + if err != nil { + return 0, errors.New("fail to read the latest version") + } + if err = newDb.SetSync([]byte(latestVersionKey), bz); err != nil { + return 0, err + } + + version := GetLatestVersion(oldDb) + bz, err = oldDb.Get([]byte(fmt.Sprintf(commitInfoKeyFmt, version))) + if bz == nil { + return 0, errors.New("fail to read the latest commit info") + } + if err != nil { + return 0, err + } + if err = newDb.SetSync([]byte(fmt.Sprintf(commitInfoKeyFmt, version)), bz); err != nil { + return 0, err + } + + // ignore the errors for saving old commit info + bz, _ = oldDb.Get([]byte(fmt.Sprintf(commitInfoKeyFmt, version-1))) + if bz != nil { + _ = newDb.SetSync([]byte(fmt.Sprintf(commitInfoKeyFmt, version-1)), bz) + } + + return version, nil +}