diff --git a/app/receipt_store_config.go b/app/receipt_store_config.go index 8d2acfacf2..96591e46ec 100644 --- a/app/receipt_store_config.go +++ b/app/receipt_store_config.go @@ -1,9 +1,9 @@ package app import ( - "path/filepath" - seidbconfig "github.com/sei-protocol/sei-chain/sei-db/config" + + "github.com/sei-protocol/sei-chain/sei-db/common/utils" ) const ( @@ -20,7 +20,7 @@ func readReceiptStoreConfig(homePath string, appOpts seidbconfig.AppOptions) (se return receiptConfig, err } if receiptConfig.DBDirectory == "" { - receiptConfig.DBDirectory = filepath.Join(homePath, "data", "receipt.db") + receiptConfig.DBDirectory = utils.GetReceiptStorePath(homePath, receiptConfig.Backend) } return receiptConfig, nil } diff --git a/app/seidb_test.go b/app/seidb_test.go index 8504f32848..504f6a6163 100644 --- a/app/seidb_test.go +++ b/app/seidb_test.go @@ -155,7 +155,8 @@ func TestReadReceiptStoreConfigUsesDefaultDirectoryWhenUnset(t *testing.T) { homePath := t.TempDir() receiptConfig, err := readReceiptStoreConfig(homePath, mapAppOpts{}) require.NoError(t, err) - assert.Equal(t, filepath.Join(homePath, "data", "receipt.db"), receiptConfig.DBDirectory) + // New nodes (no legacy data/receipt.db) get the new ledger/ layout with backend + assert.Equal(t, filepath.Join(homePath, "data", "ledger", "receipt", "pebbledb"), receiptConfig.DBDirectory) } // TestFullAppPathWithParquetReceiptStore exercises the full app.New path with rs-backend = "parquet" diff --git a/sei-db/common/utils/path.go b/sei-db/common/utils/path.go index aad7207e19..a8d64e7852 100644 --- a/sei-db/common/utils/path.go +++ b/sei-db/common/utils/path.go @@ -1,13 +1,75 @@ package utils -import "path/filepath" +import ( + "os" + "path/filepath" +) -func GetCommitStorePath(homePath string) string { - return filepath.Join(homePath, "data", "committer.db") +// DirExists returns true if path exists and is a directory. +func DirExists(path string) bool { + info, err := os.Stat(path) + return err == nil && info.IsDir() } +// FileExists returns true if path exists and is a regular file. +func FileExists(path string) bool { + info, err := os.Stat(path) + return err == nil && !info.IsDir() +} + +// GetCosmosSCStorePath returns the path for the memiavl state commitment store. +// New nodes use data/state_commit/memiavl; existing nodes with data/committer.db +// continue using the legacy path for backward compatibility. +func GetCosmosSCStorePath(homePath string) string { + legacyPath := filepath.Join(homePath, "data", "committer.db") + if DirExists(legacyPath) { + return legacyPath + } + return filepath.Join(homePath, "data", "state_commit", "memiavl") +} + +// GetFlatKVPath returns the path for the FlatKV EVM commit store. +// New nodes use data/state_commit/flatkv; existing nodes with data/flatkv +// continue using the legacy path for backward compatibility. +func GetFlatKVPath(homePath string) string { + legacyPath := filepath.Join(homePath, "data", "flatkv") + if DirExists(legacyPath) { + return legacyPath + } + return filepath.Join(homePath, "data", "state_commit", "flatkv") +} + +// GetStateStorePath returns the path for the Cosmos state store (SS). +// New nodes use data/state_store/cosmos/{backend}; existing nodes with +// data/{backend} continue using the legacy path for backward compatibility. func GetStateStorePath(homePath string, backend string) string { - return filepath.Join(homePath, "data", backend) + legacyPath := filepath.Join(homePath, "data", backend) + if DirExists(legacyPath) { + return legacyPath + } + return filepath.Join(homePath, "data", "state_store", "cosmos", backend) +} + +// GetEVMStateStorePath returns the path for the EVM state store. +// New nodes use data/state_store/evm/{backend}; existing nodes with +// data/evm_ss continue using the legacy path for backward compatibility. +func GetEVMStateStorePath(homePath string, backend string) string { + legacyPath := filepath.Join(homePath, "data", "evm_ss") + if DirExists(legacyPath) { + return legacyPath + } + return filepath.Join(homePath, "data", "state_store", "evm", backend) +} + +// GetReceiptStorePath returns the path for the receipt store. +// New nodes use data/ledger/receipt/{backend}; existing nodes with +// data/receipt.db continue using the legacy path for backward compatibility. +func GetReceiptStorePath(homePath string, backend string) string { + legacyPath := filepath.Join(homePath, "data", "receipt.db") + if DirExists(legacyPath) { + return legacyPath + } + return filepath.Join(homePath, "data", "ledger", "receipt", backend) } func GetChangelogPath(dbPath string) string { diff --git a/sei-db/common/utils/path_test.go b/sei-db/common/utils/path_test.go new file mode 100644 index 0000000000..bbd8daffc1 --- /dev/null +++ b/sei-db/common/utils/path_test.go @@ -0,0 +1,192 @@ +package utils + +import ( + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestDirExists(t *testing.T) { + dir := t.TempDir() + assert.True(t, DirExists(dir)) + assert.False(t, DirExists(filepath.Join(dir, "nonexistent"))) + + f := filepath.Join(dir, "file.txt") + require.NoError(t, os.WriteFile(f, []byte("hi"), 0644)) + assert.False(t, DirExists(f), "regular file should not match DirExists") +} + +func TestFileExists(t *testing.T) { + dir := t.TempDir() + assert.False(t, FileExists(dir), "directory should not match FileExists") + assert.False(t, FileExists(filepath.Join(dir, "nonexistent"))) + + f := filepath.Join(dir, "file.txt") + require.NoError(t, os.WriteFile(f, []byte("hi"), 0644)) + assert.True(t, FileExists(f)) +} + +// --- GetCosmosSCStorePath --- + +func TestGetCosmosSCStorePath_NewNode(t *testing.T) { + home := t.TempDir() + got := GetCosmosSCStorePath(home) + assert.Equal(t, filepath.Join(home, "data", "state_commit", "memiavl"), got) +} + +func TestGetCosmosSCStorePath_LegacyExists(t *testing.T) { + home := t.TempDir() + legacy := filepath.Join(home, "data", "committer.db") + require.NoError(t, os.MkdirAll(legacy, 0755)) + + got := GetCosmosSCStorePath(home) + assert.Equal(t, legacy, got) +} + +// --- GetFlatKVPath --- + +func TestGetFlatKVPath_NewNode(t *testing.T) { + home := t.TempDir() + got := GetFlatKVPath(home) + assert.Equal(t, filepath.Join(home, "data", "state_commit", "flatkv"), got) +} + +func TestGetFlatKVPath_LegacyExists(t *testing.T) { + home := t.TempDir() + legacy := filepath.Join(home, "data", "flatkv") + require.NoError(t, os.MkdirAll(legacy, 0755)) + + got := GetFlatKVPath(home) + assert.Equal(t, legacy, got) +} + +// --- GetStateStorePath --- + +func TestGetStateStorePath_NewNode_Pebble(t *testing.T) { + home := t.TempDir() + got := GetStateStorePath(home, "pebbledb") + assert.Equal(t, filepath.Join(home, "data", "state_store", "cosmos", "pebbledb"), got) +} + +func TestGetStateStorePath_NewNode_RocksDB(t *testing.T) { + home := t.TempDir() + got := GetStateStorePath(home, "rocksdb") + assert.Equal(t, filepath.Join(home, "data", "state_store", "cosmos", "rocksdb"), got) +} + +func TestGetStateStorePath_LegacyExists(t *testing.T) { + home := t.TempDir() + legacy := filepath.Join(home, "data", "pebbledb") + require.NoError(t, os.MkdirAll(legacy, 0755)) + + got := GetStateStorePath(home, "pebbledb") + assert.Equal(t, legacy, got) +} + +func TestGetStateStorePath_LegacyForDifferentBackend(t *testing.T) { + home := t.TempDir() + // Legacy rocksdb dir exists but we ask for pebbledb — no legacy match + require.NoError(t, os.MkdirAll(filepath.Join(home, "data", "rocksdb"), 0755)) + + got := GetStateStorePath(home, "pebbledb") + assert.Equal(t, filepath.Join(home, "data", "state_store", "cosmos", "pebbledb"), got) +} + +// --- GetEVMStateStorePath --- + +func TestGetEVMStateStorePath_NewNode_Pebble(t *testing.T) { + home := t.TempDir() + got := GetEVMStateStorePath(home, "pebbledb") + assert.Equal(t, filepath.Join(home, "data", "state_store", "evm", "pebbledb"), got) +} + +func TestGetEVMStateStorePath_NewNode_RocksDB(t *testing.T) { + home := t.TempDir() + got := GetEVMStateStorePath(home, "rocksdb") + assert.Equal(t, filepath.Join(home, "data", "state_store", "evm", "rocksdb"), got) +} + +func TestGetEVMStateStorePath_LegacyExists(t *testing.T) { + home := t.TempDir() + legacy := filepath.Join(home, "data", "evm_ss") + require.NoError(t, os.MkdirAll(legacy, 0755)) + + got := GetEVMStateStorePath(home, "pebbledb") + assert.Equal(t, legacy, got) +} + +// --- GetReceiptStorePath --- + +func TestGetReceiptStorePath_NewNode_Pebble(t *testing.T) { + home := t.TempDir() + got := GetReceiptStorePath(home, "pebbledb") + assert.Equal(t, filepath.Join(home, "data", "ledger", "receipt", "pebbledb"), got) +} + +func TestGetReceiptStorePath_NewNode_Parquet(t *testing.T) { + home := t.TempDir() + got := GetReceiptStorePath(home, "parquet") + assert.Equal(t, filepath.Join(home, "data", "ledger", "receipt", "parquet"), got) +} + +func TestGetReceiptStorePath_LegacyExists(t *testing.T) { + home := t.TempDir() + legacy := filepath.Join(home, "data", "receipt.db") + require.NoError(t, os.MkdirAll(legacy, 0755)) + + got := GetReceiptStorePath(home, "pebbledb") + assert.Equal(t, legacy, got) +} + +// --- GetChangelogPath (unchanged, but verify) --- + +func TestGetChangelogPath(t *testing.T) { + assert.Equal(t, "/foo/bar/changelog", GetChangelogPath("/foo/bar")) +} + +// --- Edge: new path already has data (second run of new node) --- + +func TestGetCosmosSCStorePath_NewDataAlreadyExists(t *testing.T) { + home := t.TempDir() + newPath := filepath.Join(home, "data", "state_commit", "memiavl") + require.NoError(t, os.MkdirAll(newPath, 0755)) + + got := GetCosmosSCStorePath(home) + assert.Equal(t, newPath, got, "should use new path when legacy is absent even if new path already exists") +} + +func TestGetStateStorePath_NewDataAlreadyExists(t *testing.T) { + home := t.TempDir() + newPath := filepath.Join(home, "data", "state_store", "cosmos", "pebbledb") + require.NoError(t, os.MkdirAll(newPath, 0755)) + + got := GetStateStorePath(home, "pebbledb") + assert.Equal(t, newPath, got, "should use new path when legacy is absent even if new path already exists") +} + +// --- Edge: both legacy and new exist (legacy wins) --- + +func TestGetCosmosSCStorePath_BothExist(t *testing.T) { + home := t.TempDir() + legacy := filepath.Join(home, "data", "committer.db") + require.NoError(t, os.MkdirAll(legacy, 0755)) + newPath := filepath.Join(home, "data", "state_commit", "memiavl") + require.NoError(t, os.MkdirAll(newPath, 0755)) + + got := GetCosmosSCStorePath(home) + assert.Equal(t, legacy, got, "legacy should take precedence when both exist") +} + +func TestGetReceiptStorePath_BothExist(t *testing.T) { + home := t.TempDir() + legacy := filepath.Join(home, "data", "receipt.db") + require.NoError(t, os.MkdirAll(legacy, 0755)) + newPath := filepath.Join(home, "data", "ledger", "receipt", "pebbledb") + require.NoError(t, os.MkdirAll(newPath, 0755)) + + got := GetReceiptStorePath(home, "pebbledb") + assert.Equal(t, legacy, got, "legacy should take precedence when both exist") +} diff --git a/sei-db/config/ss_config.go b/sei-db/config/ss_config.go index 1cda8d2ae0..097d8fec3f 100644 --- a/sei-db/config/ss_config.go +++ b/sei-db/config/ss_config.go @@ -74,7 +74,7 @@ type StateStoreConfig struct { ReadMode ReadMode `mapstructure:"read-mode"` // EVMDBDirectory defines the directory for EVM state store db files. - // If not set, defaults to /data/evm_ss + // If not set, defaults to /data/state_store/evm/{backend} EVMDBDirectory string `mapstructure:"evm-db-directory"` } diff --git a/sei-db/config/toml.go b/sei-db/config/toml.go index 67bec034d1..9d191ef471 100644 --- a/sei-db/config/toml.go +++ b/sei-db/config/toml.go @@ -126,7 +126,7 @@ const ReceiptStoreConfigTemplate = ` # defaults to pebbledb rs-backend = "{{ .ReceiptStore.Backend }}" -# Defines the receipt store directory. If unset, defaults to /data/receipt.db +# Defines the receipt store directory. If unset, defaults to /data/ledger/receipt/{backend} db-directory = "{{ .ReceiptStore.DBDirectory }}" # AsyncWriteBuffer defines the async queue length for commits to be applied to receipt store. diff --git a/sei-db/state_db/sc/composite/store.go b/sei-db/state_db/sc/composite/store.go index 9edb242db2..c50fb0574d 100644 --- a/sei-db/state_db/sc/composite/store.go +++ b/sei-db/state_db/sc/composite/store.go @@ -6,10 +6,10 @@ import ( "context" "fmt" "math" - "path/filepath" commonerrors "github.com/sei-protocol/sei-chain/sei-db/common/errors" commonevm "github.com/sei-protocol/sei-chain/sei-db/common/evm" + "github.com/sei-protocol/sei-chain/sei-db/common/utils" "github.com/sei-protocol/sei-chain/sei-db/config" "github.com/sei-protocol/sei-chain/sei-db/proto" "github.com/sei-protocol/sei-chain/sei-db/state_db/sc/flatkv" @@ -72,7 +72,7 @@ func NewCompositeCommitStore( // Initialize FlatKV store struct if write mode requires it // Note: DB is NOT opened here, will be opened in LoadVersion if cfg.WriteMode == config.DualWrite || cfg.WriteMode == config.SplitWrite { - cfg.FlatKVConfig.DataDir = filepath.Join(homeDir, "data", "flatkv") + cfg.FlatKVConfig.DataDir = utils.GetFlatKVPath(homeDir) var err error store.evmCommitter, err = flatkv.NewCommitStore(ctx, &cfg.FlatKVConfig) if err != nil { diff --git a/sei-db/state_db/sc/composite/store_test.go b/sei-db/state_db/sc/composite/store_test.go index 025920eb70..a6dda2b954 100644 --- a/sei-db/state_db/sc/composite/store_test.go +++ b/sei-db/state_db/sc/composite/store_test.go @@ -10,6 +10,7 @@ import ( errorutils "github.com/sei-protocol/sei-chain/sei-db/common/errors" "github.com/sei-protocol/sei-chain/sei-db/common/evm" "github.com/sei-protocol/sei-chain/sei-db/common/metrics" + "github.com/sei-protocol/sei-chain/sei-db/common/utils" "github.com/sei-protocol/sei-chain/sei-db/config" "github.com/sei-protocol/sei-chain/sei-db/proto" "github.com/sei-protocol/sei-chain/sei-db/state_db/sc/flatkv" @@ -715,8 +716,9 @@ func TestReconcileVersionsAfterCrash(t *testing.T) { // Simulate crash: rollback FlatKV to version 2 independently, leaving // cosmos at version 3. This mirrors a crash after cosmos Commit but // before FlatKV Commit completes. + flatkvCfg := cfg.FlatKVConfig - flatkvCfg.DataDir = dir + "/data/flatkv" + flatkvCfg.DataDir = utils.GetFlatKVPath(dir) evmStore, err := flatkv.NewCommitStore(t.Context(), &flatkvCfg) require.NoError(t, err) _, err = evmStore.LoadVersion(0, false) @@ -776,7 +778,7 @@ func TestReconcileVersionsThenContinueCommitting(t *testing.T) { // Simulate crash: roll FlatKV back to version 2. flatkvCfg := cfg.FlatKVConfig - flatkvCfg.DataDir = dir + "/data/flatkv" + flatkvCfg.DataDir = utils.GetFlatKVPath(dir) evmStore, err := flatkv.NewCommitStore(t.Context(), &flatkvCfg) require.NoError(t, err) _, err = evmStore.LoadVersion(0, false) @@ -873,7 +875,7 @@ func TestReconcileVersionsCosmosAheadByMultiple(t *testing.T) { // Rollback FlatKV to version 3 (simulating 2 lost commits) flatkvCfg := cfg.FlatKVConfig - flatkvCfg.DataDir = dir + "/data/flatkv" + flatkvCfg.DataDir = utils.GetFlatKVPath(dir) evmStore, err := flatkv.NewCommitStore(t.Context(), &flatkvCfg) require.NoError(t, err) _, err = evmStore.LoadVersion(0, false) diff --git a/sei-db/state_db/sc/memiavl/store.go b/sei-db/state_db/sc/memiavl/store.go index 7667772b9f..32d9ffc8cb 100644 --- a/sei-db/state_db/sc/memiavl/store.go +++ b/sei-db/state_db/sc/memiavl/store.go @@ -19,7 +19,7 @@ type CommitStore struct { } func NewCommitStore(homeDir string, config Config) *CommitStore { - commitDBPath := utils.GetCommitStorePath(homeDir) + commitDBPath := utils.GetCosmosSCStorePath(homeDir) opts := Options{ Config: config, // Embed the config directly Dir: commitDBPath, diff --git a/sei-db/state_db/ss/composite/store.go b/sei-db/state_db/ss/composite/store.go index 41b3c137e9..65d00a8969 100644 --- a/sei-db/state_db/ss/composite/store.go +++ b/sei-db/state_db/ss/composite/store.go @@ -2,7 +2,6 @@ package composite import ( "fmt" - "path/filepath" "sync" commonevm "github.com/sei-protocol/sei-chain/sei-db/common/evm" @@ -59,7 +58,7 @@ func NewCompositeStateStore( if ssConfig.EVMEnabled() { evmDir := ssConfig.EVMDBDirectory if evmDir == "" { - evmDir = filepath.Join(homeDir, "data", "evm_ss") + evmDir = utils.GetEVMStateStorePath(homeDir, ssConfig.Backend) } evmStore, err := evm.NewEVMStateStore(evmDir, ssConfig) diff --git a/sei-tendermint/cmd/tendermint/commands/reindex_event.go b/sei-tendermint/cmd/tendermint/commands/reindex_event.go index e3b33f3d81..72dc2817ff 100644 --- a/sei-tendermint/cmd/tendermint/commands/reindex_event.go +++ b/sei-tendermint/cmd/tendermint/commands/reindex_event.go @@ -149,23 +149,23 @@ func loadEventSinks(cfg *tmcfg.Config) ([]indexer.EventSink, error) { func loadStateAndBlockStore(cfg *tmcfg.Config) (*store.BlockStore, state.Store, error) { dbType := dbm.BackendType(cfg.DBBackend) - if !os.FileExists(filepath.Join(cfg.DBDir(), "blockstore.db")) { - return nil, nil, fmt.Errorf("no blockstore found in %v", cfg.DBDir()) + blockstoreDir := tmcfg.ResolveDBDir("blockstore", cfg.DBDir()) + if !os.FileExists(filepath.Join(blockstoreDir, "blockstore.db")) { + return nil, nil, fmt.Errorf("no blockstore found in %v", blockstoreDir) } - // Get BlockStore - blockStoreDB, err := dbm.NewDB("blockstore", dbType, cfg.DBDir()) + blockStoreDB, err := dbm.NewDB("blockstore", dbType, blockstoreDir) if err != nil { return nil, nil, err } blockStore := store.NewBlockStore(blockStoreDB) - if !os.FileExists(filepath.Join(cfg.DBDir(), "state.db")) { - return nil, nil, fmt.Errorf("no blockstore found in %v", cfg.DBDir()) + stateDir := tmcfg.ResolveDBDir("state", cfg.DBDir()) + if !os.FileExists(filepath.Join(stateDir, "state.db")) { + return nil, nil, fmt.Errorf("no state store found in %v", stateDir) } - // Get StateStore - stateDB, err := dbm.NewDB("state", dbType, cfg.DBDir()) + stateDB, err := dbm.NewDB("state", dbType, stateDir) if err != nil { return nil, nil, err } diff --git a/sei-tendermint/cmd/tendermint/commands/reset.go b/sei-tendermint/cmd/tendermint/commands/reset.go index 7e994b9ee7..280415ee54 100644 --- a/sei-tendermint/cmd/tendermint/commands/reset.go +++ b/sei-tendermint/cmd/tendermint/commands/reset.go @@ -110,53 +110,34 @@ func ResetAll(dbDir, privValKeyFile, privValStateFile string, keyType string, ho return ResetFilePV(filepath.Join(homeDir, privValKeyFile), filepath.Join(homeDir, privValStateFile), keyType) } -// ResetState removes all blocks, tendermint state, indexed transactions and evidence. -func ResetState(dbDir string) error { - blockdb := filepath.Join(dbDir, "blockstore.db") - state := filepath.Join(dbDir, "state.db") - wal := filepath.Join(dbDir, "cs.wal") - evidence := filepath.Join(dbDir, "evidence.db") - txIndex := filepath.Join(dbDir, "tx_index.db") - - if tmos.FileExists(blockdb) { - if err := os.RemoveAll(blockdb); err == nil { - logger.Info("Removed all blockstore.db", "dir", blockdb) +// removeIfExists removes a path if it exists, logging the result. +func removeIfExists(path, label string) { + if tmos.FileExists(path) { + if err := os.RemoveAll(path); err == nil { + logger.Info("Removed "+label, "dir", path) } else { - logger.Error("error removing all blockstore.db", "dir", blockdb, "err", err) - } - } - - if tmos.FileExists(state) { - if err := os.RemoveAll(state); err == nil { - logger.Info("Removed all state.db", "dir", state) - } else { - logger.Error("error removing all state.db", "dir", state, "err", err) - } - } - - if tmos.FileExists(wal) { - if err := os.RemoveAll(wal); err == nil { - logger.Info("Removed all cs.wal", "dir", wal) - } else { - logger.Error("error removing all cs.wal", "dir", wal, "err", err) - } - } - - if tmos.FileExists(evidence) { - if err := os.RemoveAll(evidence); err == nil { - logger.Info("Removed all evidence.db", "dir", evidence) - } else { - logger.Error("error removing all evidence.db", "dir", evidence, "err", err) + logger.Error("error removing "+label, "dir", path, "err", err) } } +} - if tmos.FileExists(txIndex) { - if err := os.RemoveAll(txIndex); err == nil { - logger.Info("Removed tx_index.db", "dir", txIndex) - } else { - logger.Error("error removing tx_index.db", "dir", txIndex, "err", err) - } - } +// ResetState removes all blocks, tendermint state, indexed transactions and evidence. +// It handles both the legacy flat layout and the new subdirectory layout. +func ResetState(dbDir string) error { + // Legacy paths (flat under data/) + removeIfExists(filepath.Join(dbDir, "blockstore.db"), "blockstore.db") + removeIfExists(filepath.Join(dbDir, "state.db"), "state.db") + removeIfExists(filepath.Join(dbDir, "cs.wal"), "cs.wal") + removeIfExists(filepath.Join(dbDir, "evidence.db"), "evidence.db") + removeIfExists(filepath.Join(dbDir, "tx_index.db"), "tx_index.db") + + // New paths (subdirectory layout — all tendermint DBs under data/tendermint/) + removeIfExists(filepath.Join(dbDir, "tendermint", "blockstore.db"), "tendermint/blockstore.db") + removeIfExists(filepath.Join(dbDir, "tendermint", "tx_index.db"), "tendermint/tx_index.db") + removeIfExists(filepath.Join(dbDir, "tendermint", "state.db"), "tendermint/state.db") + removeIfExists(filepath.Join(dbDir, "tendermint", "cs.wal"), "tendermint/cs.wal") + removeIfExists(filepath.Join(dbDir, "tendermint", "evidence.db"), "tendermint/evidence.db") + removeIfExists(filepath.Join(dbDir, "tendermint", "peerstore.db"), "tendermint/peerstore.db") return tmos.EnsureDir(dbDir, 0700) } @@ -189,12 +170,21 @@ func ResetFilePV(privValKeyFile, privValStateFile string, keyType string) error return nil } -// ResetPeerStore removes the peer store containing all information used by the tendermint networking layer -// In the case of a reset, new peers will need to be set either via the config or through the discovery mechanism +// ResetPeerStore removes the peer store containing all information used by the tendermint networking layer. +// In the case of a reset, new peers will need to be set either via the config or through the discovery mechanism. +// It checks both legacy (data/peerstore.db) and new (data/tendermint/peerstore.db) locations. func ResetPeerStore(dbDir string) error { - peerstore := filepath.Join(dbDir, "peerstore.db") - if tmos.FileExists(peerstore) { - return os.RemoveAll(peerstore) + legacy := filepath.Join(dbDir, "peerstore.db") + if tmos.FileExists(legacy) { + if err := os.RemoveAll(legacy); err != nil { + return err + } + } + newPath := filepath.Join(dbDir, "tendermint", "peerstore.db") + if tmos.FileExists(newPath) { + if err := os.RemoveAll(newPath); err != nil { + return err + } } return nil } diff --git a/sei-tendermint/cmd/tendermint/commands/reset_test.go b/sei-tendermint/cmd/tendermint/commands/reset_test.go index 95587f5163..ab804a1331 100644 --- a/sei-tendermint/cmd/tendermint/commands/reset_test.go +++ b/sei-tendermint/cmd/tendermint/commands/reset_test.go @@ -96,3 +96,108 @@ func initTestFiles(t *testing.T, config *cfg.Config) { require.NoError(t, err) require.NoError(t, pv.Save()) } + +// createDirs is a test helper that creates the given directories under base. +func createDirs(t *testing.T, base string, dirs []string) { + t.Helper() + for _, d := range dirs { + require.NoError(t, os.MkdirAll(filepath.Join(base, d), 0o755)) + } +} + +// legacyDBs are the flat data/ DB directories that ResetState should remove. +var legacyDBs = []string{ + "blockstore.db", "state.db", "cs.wal", "evidence.db", "tx_index.db", +} + +// newTendermintDBs are the data/tendermint/ DB directories that ResetState should remove. +var newTendermintDBs = []string{ + "tendermint/blockstore.db", "tendermint/state.db", "tendermint/cs.wal", + "tendermint/evidence.db", "tendermint/tx_index.db", "tendermint/peerstore.db", +} + +func TestResetState_LegacyLayout(t *testing.T) { + dbDir := t.TempDir() + createDirs(t, dbDir, legacyDBs) + + for _, d := range legacyDBs { + require.DirExists(t, filepath.Join(dbDir, d)) + } + + require.NoError(t, ResetState(dbDir)) + + for _, d := range legacyDBs { + require.NoDirExists(t, filepath.Join(dbDir, d)) + } + require.DirExists(t, dbDir) +} + +func TestResetState_NewLayout(t *testing.T) { + dbDir := t.TempDir() + createDirs(t, dbDir, newTendermintDBs) + + for _, d := range newTendermintDBs { + require.DirExists(t, filepath.Join(dbDir, d)) + } + + require.NoError(t, ResetState(dbDir)) + + for _, d := range newTendermintDBs { + require.NoDirExists(t, filepath.Join(dbDir, d)) + } + require.DirExists(t, dbDir) +} + +func TestResetState_BothLayouts(t *testing.T) { + dbDir := t.TempDir() + all := append(legacyDBs, newTendermintDBs...) + createDirs(t, dbDir, all) + + require.NoError(t, ResetState(dbDir)) + + for _, d := range all { + require.NoDirExists(t, filepath.Join(dbDir, d)) + } + require.DirExists(t, dbDir) +} + +func TestResetState_EmptyDir(t *testing.T) { + dbDir := t.TempDir() + require.NoError(t, ResetState(dbDir)) + require.DirExists(t, dbDir) +} + +func TestResetPeerStore_LegacyOnly(t *testing.T) { + dbDir := t.TempDir() + legacy := filepath.Join(dbDir, "peerstore.db") + require.NoError(t, os.MkdirAll(legacy, 0o755)) + + require.NoError(t, ResetPeerStore(dbDir)) + require.NoDirExists(t, legacy) +} + +func TestResetPeerStore_NewOnly(t *testing.T) { + dbDir := t.TempDir() + newPath := filepath.Join(dbDir, "tendermint", "peerstore.db") + require.NoError(t, os.MkdirAll(newPath, 0o755)) + + require.NoError(t, ResetPeerStore(dbDir)) + require.NoDirExists(t, newPath) +} + +func TestResetPeerStore_BothLayouts(t *testing.T) { + dbDir := t.TempDir() + legacy := filepath.Join(dbDir, "peerstore.db") + newPath := filepath.Join(dbDir, "tendermint", "peerstore.db") + require.NoError(t, os.MkdirAll(legacy, 0o755)) + require.NoError(t, os.MkdirAll(newPath, 0o755)) + + require.NoError(t, ResetPeerStore(dbDir)) + require.NoDirExists(t, legacy) + require.NoDirExists(t, newPath) +} + +func TestResetPeerStore_NeitherExists(t *testing.T) { + dbDir := t.TempDir() + require.NoError(t, ResetPeerStore(dbDir)) +} diff --git a/sei-tendermint/config/config.go b/sei-tendermint/config/config.go index e8c6e767c0..1110561b8f 100644 --- a/sei-tendermint/config/config.go +++ b/sei-tendermint/config/config.go @@ -1154,7 +1154,7 @@ type ConsensusConfig struct { // DefaultConsensusConfig returns a default configuration for the consensus service func DefaultConsensusConfig() *ConsensusConfig { return &ConsensusConfig{ - WalPath: filepath.Join(defaultDataDir, "cs.wal", "wal"), + WalPath: filepath.Join(defaultDataDir, "tendermint", "cs.wal", "wal"), CreateEmptyBlocks: true, CreateEmptyBlocksInterval: 0 * time.Second, PeerGossipSleepDuration: 100 * time.Millisecond, @@ -1181,8 +1181,23 @@ func (cfg *ConsensusConfig) WaitForTxs() bool { return !cfg.CreateEmptyBlocks || cfg.CreateEmptyBlocksInterval > 0 } -// WalFile returns the full path to the write-ahead log file +// WalFile returns the full path to the write-ahead log file. +// When either the old default (data/cs.wal/wal) or the new default +// (data/tendermint/cs.wal/wal) is configured, the directory is chosen +// automatically: legacy data/cs.wal/ is used when it exists on disk, +// otherwise data/tendermint/cs.wal/ is used. Custom or absolute paths +// are returned as-is. func (cfg *ConsensusConfig) WalFile() string { + oldDefault := filepath.Join(defaultDataDir, "cs.wal", "wal") + newDefault := filepath.Join(defaultDataDir, "tendermint", "cs.wal", "wal") + + if cfg.WalPath == oldDefault || cfg.WalPath == newDefault { + legacyDir := filepath.Join(rootify(defaultDataDir, cfg.RootDir), "cs.wal") + if dirExists(legacyDir) { + return filepath.Join(legacyDir, "wal") + } + return filepath.Join(rootify(defaultDataDir, cfg.RootDir), "tendermint", "cs.wal", "wal") + } return rootify(cfg.WalPath, cfg.RootDir) } diff --git a/sei-tendermint/config/config_test.go b/sei-tendermint/config/config_test.go index 82fdd6606e..77db8be3f5 100644 --- a/sei-tendermint/config/config_test.go +++ b/sei-tendermint/config/config_test.go @@ -1,6 +1,8 @@ package config import ( + "os" + "path/filepath" "reflect" "testing" "time" @@ -164,3 +166,89 @@ func TestP2PConfigValidateBasic(t *testing.T) { reflect.ValueOf(cfg).Elem().FieldByName(fieldName).SetInt(0) } } + +// --- WalFile legacy fallback tests --- + +func TestWalFile_NewDefault_NoLegacy(t *testing.T) { + root := t.TempDir() + cfg := DefaultConsensusConfig() + cfg.RootDir = root + + expected := filepath.Join(root, "data", "tendermint", "cs.wal", "wal") + assert.Equal(t, expected, cfg.WalFile(), + "new node with new default should use data/tendermint/cs.wal/wal") +} + +func TestWalFile_NewDefault_LegacyExists(t *testing.T) { + root := t.TempDir() + legacyDir := filepath.Join(root, "data", "cs.wal") + require.NoError(t, os.MkdirAll(legacyDir, 0755)) + + cfg := DefaultConsensusConfig() + cfg.RootDir = root + + expected := filepath.Join(legacyDir, "wal") + assert.Equal(t, expected, cfg.WalFile(), + "should fall back to legacy cs.wal when it exists on disk") +} + +func TestWalFile_OldDefault_NoLegacy(t *testing.T) { + root := t.TempDir() + cfg := DefaultConsensusConfig() + cfg.RootDir = root + cfg.WalPath = filepath.Join("data", "cs.wal", "wal") + + expected := filepath.Join(root, "data", "tendermint", "cs.wal", "wal") + assert.Equal(t, expected, cfg.WalFile(), + "old default in config.toml on a new node should redirect to new path") +} + +func TestWalFile_OldDefault_LegacyExists(t *testing.T) { + root := t.TempDir() + legacyDir := filepath.Join(root, "data", "cs.wal") + require.NoError(t, os.MkdirAll(legacyDir, 0755)) + + cfg := DefaultConsensusConfig() + cfg.RootDir = root + cfg.WalPath = filepath.Join("data", "cs.wal", "wal") + + expected := filepath.Join(legacyDir, "wal") + assert.Equal(t, expected, cfg.WalFile(), + "old default in config.toml with legacy data should use legacy path") +} + +func TestWalFile_CustomPath(t *testing.T) { + root := t.TempDir() + cfg := DefaultConsensusConfig() + cfg.RootDir = root + cfg.WalPath = "/custom/wal/path" + + assert.Equal(t, "/custom/wal/path", cfg.WalFile(), + "absolute custom path should be returned unchanged") +} + +func TestWalFile_CustomRelativePath(t *testing.T) { + root := t.TempDir() + cfg := DefaultConsensusConfig() + cfg.RootDir = root + cfg.WalPath = filepath.Join("data", "mywal", "wal") + + expected := filepath.Join(root, "data", "mywal", "wal") + assert.Equal(t, expected, cfg.WalFile(), + "non-default custom relative path should be resolved normally") +} + +func TestWalFile_BothExist_LegacyWins(t *testing.T) { + root := t.TempDir() + legacyDir := filepath.Join(root, "data", "cs.wal") + require.NoError(t, os.MkdirAll(legacyDir, 0755)) + newDir := filepath.Join(root, "data", "tendermint", "cs.wal") + require.NoError(t, os.MkdirAll(newDir, 0755)) + + cfg := DefaultConsensusConfig() + cfg.RootDir = root + + expected := filepath.Join(legacyDir, "wal") + assert.Equal(t, expected, cfg.WalFile(), + "legacy should win when both locations exist") +} diff --git a/sei-tendermint/config/db.go b/sei-tendermint/config/db.go index 3f851b4e6c..4d4bd92ef5 100644 --- a/sei-tendermint/config/db.go +++ b/sei-tendermint/config/db.go @@ -1,6 +1,9 @@ package config import ( + "os" + "path/filepath" + dbm "github.com/tendermint/tm-db" ) @@ -14,8 +17,42 @@ type DBContext struct { type DBProvider func(*DBContext) (dbm.DB, error) // DefaultDBProvider returns a database using the DBBackend and DBDir -// specified in the Config. +// specified in the Config. It routes each DB to its appropriate +// subdirectory under the data folder, with backward compatibility +// for existing nodes that have data in the legacy flat layout. func DefaultDBProvider(ctx *DBContext) (dbm.DB, error) { dbType := dbm.BackendType(ctx.Config.DBBackend) - return dbm.NewDB(ctx.ID, dbType, ctx.Config.DBDir()) + dbDir := ResolveDBDir(ctx.ID, ctx.Config.DBDir()) + return dbm.NewDB(ctx.ID, dbType, dbDir) +} + +// dbSubDir returns the new subdirectory for a given DB identifier. +func dbSubDir(dbID string) string { + switch dbID { + case "blockstore", "tx_index", "state", "evidence", "peerstore": + return "tendermint" + default: + return "" + } +} + +// ResolveDBDir returns the directory in which the given DB should be opened. +// If legacy data exists directly under baseDir (e.g. baseDir/blockstore.db), +// baseDir is returned for backward compatibility. Otherwise the new +// subdirectory layout (e.g. baseDir/ledger) is used. +func ResolveDBDir(dbID string, baseDir string) string { + subDir := dbSubDir(dbID) + if subDir == "" { + return baseDir + } + legacyPath := filepath.Join(baseDir, dbID+".db") + if dirExists(legacyPath) { + return baseDir + } + return filepath.Join(baseDir, subDir) +} + +func dirExists(path string) bool { + info, err := os.Stat(path) + return err == nil && info.IsDir() } diff --git a/sei-tendermint/config/db_test.go b/sei-tendermint/config/db_test.go new file mode 100644 index 0000000000..ba275e6729 --- /dev/null +++ b/sei-tendermint/config/db_test.go @@ -0,0 +1,120 @@ +package config + +import ( + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// --- ResolveDBDir --- + +func TestResolveDBDir_NewNode_Blockstore(t *testing.T) { + base := t.TempDir() + got := ResolveDBDir("blockstore", base) + assert.Equal(t, filepath.Join(base, "tendermint"), got) +} + +func TestResolveDBDir_NewNode_TxIndex(t *testing.T) { + base := t.TempDir() + got := ResolveDBDir("tx_index", base) + assert.Equal(t, filepath.Join(base, "tendermint"), got) +} + +func TestResolveDBDir_NewNode_State(t *testing.T) { + base := t.TempDir() + got := ResolveDBDir("state", base) + assert.Equal(t, filepath.Join(base, "tendermint"), got) +} + +func TestResolveDBDir_NewNode_Evidence(t *testing.T) { + base := t.TempDir() + got := ResolveDBDir("evidence", base) + assert.Equal(t, filepath.Join(base, "tendermint"), got) +} + +func TestResolveDBDir_NewNode_Peerstore(t *testing.T) { + base := t.TempDir() + got := ResolveDBDir("peerstore", base) + assert.Equal(t, filepath.Join(base, "tendermint"), got) +} + +func TestResolveDBDir_LegacyBlockstore(t *testing.T) { + base := t.TempDir() + require.NoError(t, os.MkdirAll(filepath.Join(base, "blockstore.db"), 0755)) + + got := ResolveDBDir("blockstore", base) + assert.Equal(t, base, got, "should return base dir when legacy blockstore.db exists") +} + +func TestResolveDBDir_LegacyState(t *testing.T) { + base := t.TempDir() + require.NoError(t, os.MkdirAll(filepath.Join(base, "state.db"), 0755)) + + got := ResolveDBDir("state", base) + assert.Equal(t, base, got, "should return base dir when legacy state.db exists") +} + +func TestResolveDBDir_LegacyTxIndex(t *testing.T) { + base := t.TempDir() + require.NoError(t, os.MkdirAll(filepath.Join(base, "tx_index.db"), 0755)) + + got := ResolveDBDir("tx_index", base) + assert.Equal(t, base, got, "should return base dir when legacy tx_index.db exists") +} + +func TestResolveDBDir_LegacyEvidence(t *testing.T) { + base := t.TempDir() + require.NoError(t, os.MkdirAll(filepath.Join(base, "evidence.db"), 0755)) + + got := ResolveDBDir("evidence", base) + assert.Equal(t, base, got, "should return base dir when legacy evidence.db exists") +} + +func TestResolveDBDir_LegacyPeerstore(t *testing.T) { + base := t.TempDir() + require.NoError(t, os.MkdirAll(filepath.Join(base, "peerstore.db"), 0755)) + + got := ResolveDBDir("peerstore", base) + assert.Equal(t, base, got, "should return base dir when legacy peerstore.db exists") +} + +func TestResolveDBDir_UnknownID(t *testing.T) { + base := t.TempDir() + got := ResolveDBDir("something_unknown", base) + assert.Equal(t, base, got, "unknown DB IDs should fall through to base dir") +} + +func TestResolveDBDir_NewDataAlreadyExists(t *testing.T) { + base := t.TempDir() + newDir := filepath.Join(base, "tendermint", "blockstore.db") + require.NoError(t, os.MkdirAll(newDir, 0755)) + + got := ResolveDBDir("blockstore", base) + assert.Equal(t, filepath.Join(base, "tendermint"), got, + "should use new path on subsequent runs when legacy is absent") +} + +func TestResolveDBDir_BothExist_LegacyWins(t *testing.T) { + base := t.TempDir() + require.NoError(t, os.MkdirAll(filepath.Join(base, "state.db"), 0755)) + require.NoError(t, os.MkdirAll(filepath.Join(base, "tendermint", "state.db"), 0755)) + + got := ResolveDBDir("state", base) + assert.Equal(t, base, got, "legacy should win when both exist") +} + +// Ensure that one DB having legacy data does not affect resolution of another DB. +func TestResolveDBDir_IndependentResolution(t *testing.T) { + base := t.TempDir() + // blockstore has legacy data, but state does not + require.NoError(t, os.MkdirAll(filepath.Join(base, "blockstore.db"), 0755)) + + gotBlock := ResolveDBDir("blockstore", base) + gotState := ResolveDBDir("state", base) + + assert.Equal(t, base, gotBlock, "blockstore should resolve to legacy") + assert.Equal(t, filepath.Join(base, "tendermint"), gotState, "state should resolve to new path") +}