Skip to content
6 changes: 3 additions & 3 deletions app/receipt_store_config.go
Original file line number Diff line number Diff line change
@@ -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 (
Expand All @@ -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
}
3 changes: 2 additions & 1 deletion app/seidb_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
64 changes: 60 additions & 4 deletions sei-db/common/utils/path.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,69 @@
package utils

import "path/filepath"
import (
"os"
"path/filepath"
)

func GetCommitStorePath(homePath string) string {
return filepath.Join(homePath, "data", "committer.db")
// PathExists returns true if the given path exists on disk.
func PathExists(path string) bool {
_, err := os.Stat(path)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Harden this by requiring the type of path? e.g. directory? file etc.

This is racy in that it doesn't atomically check and it repeatedly checks but hopefully the probability of race is low enough that we can accept the race condition as negligible.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make sense, will do

return err == nil
}

// 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 PathExists(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 PathExists(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 PathExists(legacyPath) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: Is there ever a case where an abrupt node failure could cause the creation of an empty directory here?

If so, for robustness i recommend checking if this dir is empty and proceed with legacy path iff it is not empty.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the creation of empty dir happens, then it will continue proceed with the old path, which is fine. I don't think there's any case where it is started with the new path first and then suddenly crash and create an old path

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 PathExists(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 PathExists(legacyPath) {
return legacyPath
}
return filepath.Join(homePath, "data", "ledger", "receipt", backend)
}

func GetChangelogPath(dbPath string) string {
Expand Down
182 changes: 182 additions & 0 deletions sei-db/common/utils/path_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
package utils

import (
"os"
"path/filepath"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestPathExists(t *testing.T) {
dir := t.TempDir()
assert.True(t, PathExists(dir))
assert.False(t, PathExists(filepath.Join(dir, "nonexistent")))

f := filepath.Join(dir, "file.txt")
require.NoError(t, os.WriteFile(f, []byte("hi"), 0644))
assert.True(t, PathExists(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")
}
2 changes: 1 addition & 1 deletion sei-db/config/ss_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 <home>/data/evm_ss
// If not set, defaults to <home>/data/state_store/evm/{backend}
EVMDBDirectory string `mapstructure:"evm-db-directory"`
}

Expand Down
2 changes: 1 addition & 1 deletion sei-db/config/toml.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ const ReceiptStoreConfigTemplate = `
# defaults to pebbledb
rs-backend = "{{ .ReceiptStore.Backend }}"

# Defines the receipt store directory. If unset, defaults to <home>/data/receipt.db
# Defines the receipt store directory. If unset, defaults to <home>/data/ledger/receipt/{backend}
db-directory = "{{ .ReceiptStore.DBDirectory }}"

# AsyncWriteBuffer defines the async queue length for commits to be applied to receipt store.
Expand Down
4 changes: 2 additions & 2 deletions sei-db/state_db/sc/composite/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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 {
Expand Down
8 changes: 5 additions & 3 deletions sei-db/state_db/sc/composite/store_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion sei-db/state_db/sc/memiavl/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
3 changes: 1 addition & 2 deletions sei-db/state_db/ss/composite/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package composite

import (
"fmt"
"path/filepath"
"sync"

commonevm "github.com/sei-protocol/sei-chain/sei-db/common/evm"
Expand Down Expand Up @@ -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)
Expand Down
Loading
Loading