From cfad122ec8018914a8c50cc5793820d9b4693b7b Mon Sep 17 00:00:00 2001 From: Jeremy Wei Date: Fri, 6 Mar 2026 08:39:08 -0500 Subject: [PATCH 1/7] app toml controls receipt --- app/app.go | 3 +-- app/seidb.go | 11 +++++++++++ app/seidb_test.go | 18 ++++++++++++++++++ cmd/seid/cmd/app_config.go | 26 ++++++++++++++------------ cmd/seid/cmd/init_test.go | 24 ++++++++++++++++++++++++ cmd/seid/cmd/root.go | 1 + sei-db/config/receipt_config.go | 4 ++-- 7 files changed, 71 insertions(+), 16 deletions(-) diff --git a/app/app.go b/app/app.go index 85e844d6a6..68621f9778 100644 --- a/app/app.go +++ b/app/app.go @@ -118,7 +118,6 @@ import ( gigautils "github.com/sei-protocol/sei-chain/giga/executor/utils" "github.com/sei-protocol/sei-chain/precompiles" putils "github.com/sei-protocol/sei-chain/precompiles/utils" - ssconfig "github.com/sei-protocol/sei-chain/sei-db/config" "github.com/sei-protocol/sei-chain/sei-ibc-go/modules/apps/transfer" ibctransferkeeper "github.com/sei-protocol/sei-chain/sei-ibc-go/modules/apps/transfer/keeper" ibctransfertypes "github.com/sei-protocol/sei-chain/sei-ibc-go/modules/apps/transfer/types" @@ -679,7 +678,7 @@ func New( ) receiptStorePath := filepath.Join(homePath, "data", "receipt.db") - receiptConfig := ssconfig.DefaultReceiptStoreConfig() + receiptConfig := parseReceiptConfigs(appOpts) receiptConfig.DBDirectory = receiptStorePath receiptConfig.KeepRecent = cast.ToInt(appOpts.Get(server.FlagMinRetainBlocks)) if app.receiptStore == nil { diff --git a/app/seidb.go b/app/seidb.go index 448de9960f..6b8e70c223 100644 --- a/app/seidb.go +++ b/app/seidb.go @@ -40,6 +40,9 @@ const ( FlagSSPruneInterval = "state-store.ss-prune-interval" FlagSSImportNumWorkers = "state-store.ss-import-num-workers" + // Receipt store configs + FlagRSBackend = "receipt-store.rs-backend" + // EVM SS optimization (embedded in SS config, controlled via write/read mode) FlagEVMSSDirectory = "state-store.evm-ss-db-directory" FlagEVMSSWriteMode = "state-store.evm-ss-write-mode" @@ -162,6 +165,14 @@ func parseSSConfigs(appOpts servertypes.AppOptions) config.StateStoreConfig { return ssConfig } +func parseReceiptConfigs(appOpts servertypes.AppOptions) config.ReceiptStoreConfig { + receiptConfig := config.DefaultReceiptStoreConfig() + if backend := cast.ToString(appOpts.Get(FlagRSBackend)); backend != "" { + receiptConfig.Backend = backend + } + return receiptConfig +} + func validateConfigs(appOpts servertypes.AppOptions) { scEnabled := cast.ToBool(appOpts.Get(FlagSCEnable)) ssEnabled := cast.ToBool(appOpts.Get(FlagSSEnable)) diff --git a/app/seidb_test.go b/app/seidb_test.go index 94c7db5257..bca3e7c351 100644 --- a/app/seidb_test.go +++ b/app/seidb_test.go @@ -46,6 +46,8 @@ func (t TestSeiDBAppOpts) Get(s string) interface{} { return defaultSSConfig.PruneIntervalSeconds case FlagSSImportNumWorkers: return defaultSSConfig.ImportNumWorkers + case FlagRSBackend: + return config.DefaultReceiptStoreConfig().Backend case FlagEVMSSDirectory: return defaultSSConfig.EVMDBDirectory case FlagEVMSSWriteMode: @@ -61,8 +63,10 @@ func TestNewDefaultConfig(t *testing.T) { appOpts := TestSeiDBAppOpts{} scConfig := parseSCConfigs(appOpts) ssConfig := parseSSConfigs(appOpts) + receiptConfig := parseReceiptConfigs(appOpts) assert.Equal(t, scConfig, config.DefaultStateCommitConfig()) assert.Equal(t, ssConfig, config.DefaultStateStoreConfig()) + assert.Equal(t, receiptConfig, config.DefaultReceiptStoreConfig()) } type mapAppOpts map[string]interface{} @@ -85,3 +89,17 @@ func TestParseSCConfigs_HistoricalProofFlags(t *testing.T) { assert.Equal(t, 12.5, scConfig.HistoricalProofRateLimit) assert.Equal(t, 3, scConfig.HistoricalProofBurst) } + +func TestParseReceiptConfigs_DefaultsToPebbleWhenUnset(t *testing.T) { + receiptConfig := parseReceiptConfigs(mapAppOpts{}) + assert.Equal(t, config.DefaultReceiptStoreConfig(), receiptConfig) +} + +func TestParseReceiptConfigs_UsesConfiguredBackend(t *testing.T) { + receiptConfig := parseReceiptConfigs(mapAppOpts{ + FlagRSBackend: "parquet", + }) + assert.Equal(t, "parquet", receiptConfig.Backend) + assert.Equal(t, config.DefaultReceiptStoreConfig().AsyncWriteBuffer, receiptConfig.AsyncWriteBuffer) + assert.Equal(t, config.DefaultReceiptStoreConfig().KeepRecent, receiptConfig.KeepRecent) +} diff --git a/cmd/seid/cmd/app_config.go b/cmd/seid/cmd/app_config.go index 2ed7d4c3ae..22305f00cf 100644 --- a/cmd/seid/cmd/app_config.go +++ b/cmd/seid/cmd/app_config.go @@ -22,23 +22,25 @@ type WASMConfig struct { type CustomAppConfig struct { srvconfig.Config - StateCommit seidbconfig.StateCommitConfig `mapstructure:"state-commit"` - StateStore seidbconfig.StateStoreConfig `mapstructure:"state-store"` - WASM WASMConfig `mapstructure:"wasm"` - EVM evmrpcconfig.Config `mapstructure:"evm"` - GigaExecutor gigaconfig.Config `mapstructure:"giga_executor"` - ETHReplay replay.Config `mapstructure:"eth_replay"` - ETHBlockTest blocktest.Config `mapstructure:"eth_block_test"` - EvmQuery querier.Config `mapstructure:"evm_query"` - LightInvariance seiapp.LightInvarianceConfig `mapstructure:"light_invariance"` + StateCommit seidbconfig.StateCommitConfig `mapstructure:"state-commit"` + StateStore seidbconfig.StateStoreConfig `mapstructure:"state-store"` + ReceiptStore seidbconfig.ReceiptStoreConfig `mapstructure:"receipt-store"` + WASM WASMConfig `mapstructure:"wasm"` + EVM evmrpcconfig.Config `mapstructure:"evm"` + GigaExecutor gigaconfig.Config `mapstructure:"giga_executor"` + ETHReplay replay.Config `mapstructure:"eth_replay"` + ETHBlockTest blocktest.Config `mapstructure:"eth_block_test"` + EvmQuery querier.Config `mapstructure:"evm_query"` + LightInvariance seiapp.LightInvarianceConfig `mapstructure:"light_invariance"` } // NewCustomAppConfig creates a CustomAppConfig with the given base config and EVM config func NewCustomAppConfig(baseConfig *srvconfig.Config, evmConfig evmrpcconfig.Config) CustomAppConfig { return CustomAppConfig{ - Config: *baseConfig, - StateCommit: seidbconfig.DefaultStateCommitConfig(), - StateStore: seidbconfig.DefaultStateStoreConfig(), + Config: *baseConfig, + StateCommit: seidbconfig.DefaultStateCommitConfig(), + StateStore: seidbconfig.DefaultStateStoreConfig(), + ReceiptStore: seidbconfig.DefaultReceiptStoreConfig(), WASM: WASMConfig{ QueryGasLimit: 300000, LruSize: 1, diff --git a/cmd/seid/cmd/init_test.go b/cmd/seid/cmd/init_test.go index 5c4f50abd4..417bf8da93 100644 --- a/cmd/seid/cmd/init_test.go +++ b/cmd/seid/cmd/init_test.go @@ -1,11 +1,13 @@ package cmd import ( + "bytes" "encoding/json" "fmt" "os" "path/filepath" "testing" + "text/template" "github.com/sei-protocol/sei-chain/app" "github.com/sei-protocol/sei-chain/app/genesis" @@ -149,6 +151,7 @@ func TestInitModeConfiguration(t *testing.T) { // Build custom template with all sections customAppTemplate := srvconfig.ManualConfigTemplate + seidbconfig.StateCommitConfigTemplate + seidbconfig.StateStoreConfigTemplate + + seidbconfig.ReceiptStoreConfigTemplate + srvconfig.AutoManagedConfigTemplate // Simplified - just need the pruning config srvconfig.SetConfigTemplate(customAppTemplate) @@ -167,10 +170,31 @@ func TestInitModeConfiguration(t *testing.T) { if tt.validateApp != nil { tt.validateApp(t, configDir) } + + v := viper.New() + v.SetConfigFile(appTomlPath) + err = v.ReadInConfig() + require.NoError(t, err) + require.Equal(t, "pebbledb", v.GetString("receipt-store.rs-backend")) }) } } +func TestInitAppConfigIncludesReceiptStoreDefaults(t *testing.T) { + customAppTemplate, customAppConfig := initAppConfig() + + tmpl, err := template.New("app").Parse(customAppTemplate) + require.NoError(t, err) + + var buf bytes.Buffer + err = tmpl.Execute(&buf, customAppConfig) + require.NoError(t, err) + + output := buf.String() + require.Contains(t, output, "[receipt-store]") + require.Contains(t, output, `rs-backend = "pebbledb"`) +} + // TestInitModeFlag verifies the mode flag validation func TestInitModeFlag(t *testing.T) { validModes := []string{"validator", "full", "seed", "archive"} diff --git a/cmd/seid/cmd/root.go b/cmd/seid/cmd/root.go index 235f98f0f1..00625f9e11 100644 --- a/cmd/seid/cmd/root.go +++ b/cmd/seid/cmd/root.go @@ -430,6 +430,7 @@ func initAppConfig() (string, interface{}) { customAppTemplate := serverconfig.ManualConfigTemplate + seidbconfig.StateCommitConfigTemplate + seidbconfig.StateStoreConfigTemplate + + seidbconfig.ReceiptStoreConfigTemplate + evmrpcconfig.ConfigTemplate + gigaconfig.ConfigTemplate + serverconfig.AutoManagedConfigTemplate + ` diff --git a/sei-db/config/receipt_config.go b/sei-db/config/receipt_config.go index 87b540251e..a6974eb1b0 100644 --- a/sei-db/config/receipt_config.go +++ b/sei-db/config/receipt_config.go @@ -7,8 +7,8 @@ type ReceiptStoreConfig struct { // default to empty DBDirectory string `mapstructure:"db-directory"` - // Backend defines the backend database used for receipt-store - // Supported backends: pebbledb, rocksdb + // Backend defines the backend database used for receipt-store. + // Supported backends: pebbledb (aka pebble), parquet // defaults to pebbledb Backend string `mapstructure:"backend"` From 9bee1ac80ec6a633ecf8b73e015242135b841a2c Mon Sep 17 00:00:00 2001 From: Jeremy Wei Date: Mon, 9 Mar 2026 10:09:05 -0400 Subject: [PATCH 2/7] use ReadConfig --- app/app.go | 6 +++++- app/seidb.go | 11 ---------- app/seidb_test.go | 22 +++++++++++++++----- sei-db/config/receipt_config.go | 36 +++++++++++++++++++++++++++++++++ 4 files changed, 58 insertions(+), 17 deletions(-) diff --git a/app/app.go b/app/app.go index 68621f9778..5506f1724d 100644 --- a/app/app.go +++ b/app/app.go @@ -112,6 +112,7 @@ import ( "github.com/sei-protocol/sei-chain/evmrpc" evmrpcconfig "github.com/sei-protocol/sei-chain/evmrpc/config" gigaexecutor "github.com/sei-protocol/sei-chain/giga/executor" + seidbconfig "github.com/sei-protocol/sei-chain/sei-db/config" gigaconfig "github.com/sei-protocol/sei-chain/giga/executor/config" gigalib "github.com/sei-protocol/sei-chain/giga/executor/lib" gigaprecompiles "github.com/sei-protocol/sei-chain/giga/executor/precompiles" @@ -678,7 +679,10 @@ func New( ) receiptStorePath := filepath.Join(homePath, "data", "receipt.db") - receiptConfig := parseReceiptConfigs(appOpts) + receiptConfig, err := seidbconfig.ReadReceiptConfig(appOpts) + if err != nil { + panic(fmt.Sprintf("error reading receipt store config: %s", err)) + } receiptConfig.DBDirectory = receiptStorePath receiptConfig.KeepRecent = cast.ToInt(appOpts.Get(server.FlagMinRetainBlocks)) if app.receiptStore == nil { diff --git a/app/seidb.go b/app/seidb.go index 6b8e70c223..448de9960f 100644 --- a/app/seidb.go +++ b/app/seidb.go @@ -40,9 +40,6 @@ const ( FlagSSPruneInterval = "state-store.ss-prune-interval" FlagSSImportNumWorkers = "state-store.ss-import-num-workers" - // Receipt store configs - FlagRSBackend = "receipt-store.rs-backend" - // EVM SS optimization (embedded in SS config, controlled via write/read mode) FlagEVMSSDirectory = "state-store.evm-ss-db-directory" FlagEVMSSWriteMode = "state-store.evm-ss-write-mode" @@ -165,14 +162,6 @@ func parseSSConfigs(appOpts servertypes.AppOptions) config.StateStoreConfig { return ssConfig } -func parseReceiptConfigs(appOpts servertypes.AppOptions) config.ReceiptStoreConfig { - receiptConfig := config.DefaultReceiptStoreConfig() - if backend := cast.ToString(appOpts.Get(FlagRSBackend)); backend != "" { - receiptConfig.Backend = backend - } - return receiptConfig -} - func validateConfigs(appOpts servertypes.AppOptions) { scEnabled := cast.ToBool(appOpts.Get(FlagSCEnable)) ssEnabled := cast.ToBool(appOpts.Get(FlagSSEnable)) diff --git a/app/seidb_test.go b/app/seidb_test.go index bca3e7c351..9b986a09a4 100644 --- a/app/seidb_test.go +++ b/app/seidb_test.go @@ -46,7 +46,7 @@ func (t TestSeiDBAppOpts) Get(s string) interface{} { return defaultSSConfig.PruneIntervalSeconds case FlagSSImportNumWorkers: return defaultSSConfig.ImportNumWorkers - case FlagRSBackend: + case "receipt-store.rs-backend": return config.DefaultReceiptStoreConfig().Backend case FlagEVMSSDirectory: return defaultSSConfig.EVMDBDirectory @@ -63,7 +63,8 @@ func TestNewDefaultConfig(t *testing.T) { appOpts := TestSeiDBAppOpts{} scConfig := parseSCConfigs(appOpts) ssConfig := parseSSConfigs(appOpts) - receiptConfig := parseReceiptConfigs(appOpts) + receiptConfig, err := config.ReadReceiptConfig(appOpts) + assert.NoError(t, err) assert.Equal(t, scConfig, config.DefaultStateCommitConfig()) assert.Equal(t, ssConfig, config.DefaultStateStoreConfig()) assert.Equal(t, receiptConfig, config.DefaultReceiptStoreConfig()) @@ -91,15 +92,26 @@ func TestParseSCConfigs_HistoricalProofFlags(t *testing.T) { } func TestParseReceiptConfigs_DefaultsToPebbleWhenUnset(t *testing.T) { - receiptConfig := parseReceiptConfigs(mapAppOpts{}) + receiptConfig, err := config.ReadReceiptConfig(mapAppOpts{}) + assert.NoError(t, err) assert.Equal(t, config.DefaultReceiptStoreConfig(), receiptConfig) } func TestParseReceiptConfigs_UsesConfiguredBackend(t *testing.T) { - receiptConfig := parseReceiptConfigs(mapAppOpts{ - FlagRSBackend: "parquet", + receiptConfig, err := config.ReadReceiptConfig(mapAppOpts{ + "receipt-store.rs-backend": "parquet", }) + assert.NoError(t, err) assert.Equal(t, "parquet", receiptConfig.Backend) assert.Equal(t, config.DefaultReceiptStoreConfig().AsyncWriteBuffer, receiptConfig.AsyncWriteBuffer) assert.Equal(t, config.DefaultReceiptStoreConfig().KeepRecent, receiptConfig.KeepRecent) } + +func TestParseReceiptConfigs_RejectsInvalidBackend(t *testing.T) { + _, err := config.ReadReceiptConfig(mapAppOpts{ + "receipt-store.rs-backend": "rocksdb", + }) + assert.Error(t, err) + assert.Contains(t, err.Error(), "unsupported receipt-store backend") + assert.Contains(t, err.Error(), "rocksdb") +} diff --git a/sei-db/config/receipt_config.go b/sei-db/config/receipt_config.go index a6974eb1b0..b091bb5cc0 100644 --- a/sei-db/config/receipt_config.go +++ b/sei-db/config/receipt_config.go @@ -1,5 +1,22 @@ package config +import ( + "fmt" + "strings" + + "github.com/spf13/cast" +) + +// AppOptions is a minimal interface for reading config (e.g. from Viper). +// Implemented by sei-cosmos server/types.AppOptions; defined here to avoid import cycles. +type AppOptions interface { + Get(string) interface{} +} + +const ( + flagRSBackend = "receipt-store.rs-backend" +) + // ReceiptStoreConfig defines configuration for the receipt store database. type ReceiptStoreConfig struct { // DBDirectory defines the directory to store the receipt store db files @@ -43,3 +60,22 @@ func DefaultReceiptStoreConfig() ReceiptStoreConfig { UseDefaultComparer: false, } } + +// ReadReceiptConfig reads receipt store config from app options (e.g. TOML / Viper). +func ReadReceiptConfig(opts AppOptions) (ReceiptStoreConfig, error) { + cfg := DefaultReceiptStoreConfig() + if v := opts.Get(flagRSBackend); v != nil { + backend, err := cast.ToStringE(v) + if err != nil { + return cfg, err + } + backend = strings.ToLower(strings.TrimSpace(backend)) + switch backend { + case "pebbledb", "pebble", "parquet": + cfg.Backend = backend + default: + return cfg, fmt.Errorf("unsupported receipt-store backend %q; supported: pebbledb, parquet", backend) + } + } + return cfg, nil +} From 65f3222d617af0e915ccc29abc037c64d71ef070 Mon Sep 17 00:00:00 2001 From: Jeremy Wei Date: Mon, 9 Mar 2026 10:19:41 -0400 Subject: [PATCH 3/7] add unit test to open up parquet --- app/seidb_test.go | 13 +++++ app/test_helpers.go | 59 +++++++++++++++++++++-- sei-db/ledger_db/receipt/receipt_store.go | 19 ++++++++ 3 files changed, 88 insertions(+), 3 deletions(-) diff --git a/app/seidb_test.go b/app/seidb_test.go index 9b986a09a4..576a3bd358 100644 --- a/app/seidb_test.go +++ b/app/seidb_test.go @@ -4,7 +4,9 @@ import ( "testing" "github.com/sei-protocol/sei-chain/sei-db/config" + "github.com/sei-protocol/sei-chain/sei-db/ledger_db/receipt" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) type TestSeiDBAppOpts struct { @@ -115,3 +117,14 @@ func TestParseReceiptConfigs_RejectsInvalidBackend(t *testing.T) { assert.Contains(t, err.Error(), "unsupported receipt-store backend") assert.Contains(t, err.Error(), "rocksdb") } + +// TestFullAppPathWithParquetReceiptStore exercises the full app.New path with rs-backend = "parquet" +// and asserts the parquet receipt store is actually instantiated (not pebble). +func TestFullAppPathWithParquetReceiptStore(t *testing.T) { + app := SetupWithScReceiptFromOpts(t, false, false, TestAppOpts{ + UseSc: true, + ReceiptBackend: "parquet", + }) + require.NotNil(t, app.receiptStore, "receipt store should be created") + assert.Equal(t, "parquet", receipt.BackendTypeName(app.receiptStore), "receipt store backend should be parquet") +} diff --git a/app/test_helpers.go b/app/test_helpers.go index c9d5320548..86f660d177 100644 --- a/app/test_helpers.go +++ b/app/test_helpers.go @@ -76,9 +76,10 @@ func (t TestTx) GetGasEstimate() uint64 { } type TestAppOpts struct { - UseSc bool - EnableGiga bool - EnableGigaOCC bool + UseSc bool + EnableGiga bool + EnableGigaOCC bool + ReceiptBackend string // e.g. "parquet" to use parquet receipt store; empty = default (pebble) } func (t TestAppOpts) Get(s string) interface{} { @@ -99,6 +100,9 @@ func (t TestAppOpts) Get(s string) interface{} { if s == gigaconfig.FlagOCCEnabled { return t.EnableGigaOCC } + if s == "receipt-store.rs-backend" && t.ReceiptBackend != "" { + return t.ReceiptBackend + } // Disable EVM HTTP and WebSocket servers in tests to avoid port conflicts // when multiple tests run in parallel (all would try to bind to port 8545) if s == "evm.http_enabled" || s == "evm.ws_enabled" { @@ -472,6 +476,55 @@ func SetupWithDB(tb testing.TB, db dbm.DB, isCheckTx bool, enableEVMCustomPrecom return res } +// SetupWithScReceiptFromOpts is like SetupWithSc but does not inject a receipt store via AppOption. +// The receipt store is created inside New() from testAppOpts (e.g. testAppOpts.ReceiptBackend = "parquet"). +// Use this to test the full app path with rs-backend from config. +func SetupWithScReceiptFromOpts(t *testing.T, isCheckTx bool, enableEVMCustomPrecompiles bool, testAppOpts TestAppOpts, baseAppOptions ...func(*bam.BaseApp)) (res *App) { + db := dbm.NewMemDB() + encodingConfig := MakeEncodingConfig() + cdc := encodingConfig.Marshaler + + res = New( + log.NewNopLogger(), + db, + nil, + true, + map[int64]bool{}, + t.TempDir(), + 1, + enableEVMCustomPrecompiles, + config.TestConfig(), + encodingConfig, + wasm.EnableAllProposals, + testAppOpts, + EmptyWasmOpts, + nil, // no options: receipt store is created from testAppOpts inside New() + baseAppOptions..., + ) + if !isCheckTx { + genesisState := NewDefaultGenesisState(cdc) + stateBytes, err := json.MarshalIndent(genesisState, "", " ") + if err != nil { + panic(err) + } + + defer func() { _ = recover() }() + + _, err = res.InitChain( + context.Background(), &abci.RequestInitChain{ + Validators: []abci.ValidatorUpdate{}, + ConsensusParams: DefaultConsensusParams, + AppStateBytes: stateBytes, + }, + ) + if err != nil { + panic(err) + } + } + + return res +} + func SetupWithSc(t *testing.T, isCheckTx bool, enableEVMCustomPrecompiles bool, testAppOpts TestAppOpts, baseAppOptions ...func(*bam.BaseApp)) (res *App) { db := dbm.NewMemDB() encodingConfig := MakeEncodingConfig() diff --git a/sei-db/ledger_db/receipt/receipt_store.go b/sei-db/ledger_db/receipt/receipt_store.go index 8776a21a92..30e37c58d2 100644 --- a/sei-db/ledger_db/receipt/receipt_store.go +++ b/sei-db/ledger_db/receipt/receipt_store.go @@ -83,6 +83,25 @@ func NewReceiptStore(log dbLogger.Logger, config dbconfig.ReceiptStoreConfig, st return newCachedReceiptStore(backend), nil } +// BackendTypeName returns the backend implementation name ("parquet" or "pebble") for testing. +// Returns "" if store is nil or the backend type is unknown. +func BackendTypeName(store ReceiptStore) string { + if store == nil { + return "" + } + if c, ok := store.(*cachedReceiptStore); ok { + store = c.backend + } + switch store.(type) { + case *parquetReceiptStore: + return receiptBackendParquet + case *receiptStore: + return receiptBackendPebble + default: + return "unknown" + } +} + func newReceiptBackend(log dbLogger.Logger, config dbconfig.ReceiptStoreConfig, storeKey sdk.StoreKey) (ReceiptStore, error) { if log == nil { log = dbLogger.NewNopLogger() From a8dd9bf7a1934b024a266f335470b6b13fe9199c Mon Sep 17 00:00:00 2001 From: Jeremy Wei Date: Mon, 9 Mar 2026 10:22:59 -0400 Subject: [PATCH 4/7] fix rs-backend --- sei-db/config/receipt_config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sei-db/config/receipt_config.go b/sei-db/config/receipt_config.go index b091bb5cc0..30f734e846 100644 --- a/sei-db/config/receipt_config.go +++ b/sei-db/config/receipt_config.go @@ -27,7 +27,7 @@ type ReceiptStoreConfig struct { // Backend defines the backend database used for receipt-store. // Supported backends: pebbledb (aka pebble), parquet // defaults to pebbledb - Backend string `mapstructure:"backend"` + Backend string `mapstructure:"rs-backend"` // AsyncWriteBuffer defines the async queue length for commits to be applied to receipt store // Set <= 0 for synchronous writes. From 0fa9f96baca5d7108eafcf86de151db56eb6ab0c Mon Sep 17 00:00:00 2001 From: Jeremy Wei Date: Mon, 9 Mar 2026 15:06:04 -0400 Subject: [PATCH 5/7] Add receipt store configuration options and tests This commit introduces additional configuration options for the receipt store, including `db-directory`, `async-write-buffer`, `keep-recent`, and `prune-interval-seconds`. The changes include updates to the TOML configuration template, the receipt store configuration struct, and the associated reading logic. Unit tests have been added to ensure that these new configuration options are correctly parsed and utilized, as well as to verify that the receipt store behaves as expected with the new settings. This enhances the flexibility and usability of the receipt store configuration. --- app/app.go | 7 +-- app/receipt_store_config.go | 18 ++++++++ app/seidb_test.go | 37 +++++++++++++++ cmd/seid/cmd/init_test.go | 9 ++++ sei-db/config/receipt_config.go | 42 +++++++++++++---- sei-db/config/toml.go | 18 ++++++++ sei-db/config/toml_test.go | 33 +++++++++++++ sei-db/ledger_db/parquet/store_config_test.go | 13 ++++++ .../ledger_db/receipt/parquet_store_test.go | 46 +++++++++++++++++-- sei-db/ledger_db/receipt/receipt_store.go | 1 + 10 files changed, 206 insertions(+), 18 deletions(-) create mode 100644 app/receipt_store_config.go diff --git a/app/app.go b/app/app.go index 5506f1724d..69cf7c4e96 100644 --- a/app/app.go +++ b/app/app.go @@ -35,7 +35,6 @@ import ( "github.com/sei-protocol/sei-chain/sei-cosmos/codec" "github.com/sei-protocol/sei-chain/sei-cosmos/codec/types" cryptotypes "github.com/sei-protocol/sei-chain/sei-cosmos/crypto/types" - "github.com/sei-protocol/sei-chain/sei-cosmos/server" "github.com/sei-protocol/sei-chain/sei-cosmos/server/api" "github.com/sei-protocol/sei-chain/sei-cosmos/server/config" servertypes "github.com/sei-protocol/sei-chain/sei-cosmos/server/types" @@ -112,7 +111,6 @@ import ( "github.com/sei-protocol/sei-chain/evmrpc" evmrpcconfig "github.com/sei-protocol/sei-chain/evmrpc/config" gigaexecutor "github.com/sei-protocol/sei-chain/giga/executor" - seidbconfig "github.com/sei-protocol/sei-chain/sei-db/config" gigaconfig "github.com/sei-protocol/sei-chain/giga/executor/config" gigalib "github.com/sei-protocol/sei-chain/giga/executor/lib" gigaprecompiles "github.com/sei-protocol/sei-chain/giga/executor/precompiles" @@ -678,13 +676,10 @@ func New( wasmOpts..., ) - receiptStorePath := filepath.Join(homePath, "data", "receipt.db") - receiptConfig, err := seidbconfig.ReadReceiptConfig(appOpts) + receiptConfig, err := readReceiptStoreConfig(homePath, appOpts) if err != nil { panic(fmt.Sprintf("error reading receipt store config: %s", err)) } - receiptConfig.DBDirectory = receiptStorePath - receiptConfig.KeepRecent = cast.ToInt(appOpts.Get(server.FlagMinRetainBlocks)) if app.receiptStore == nil { receiptStore, err := receipt.NewReceiptStore(logger, receiptConfig, keys[evmtypes.StoreKey]) if err != nil { diff --git a/app/receipt_store_config.go b/app/receipt_store_config.go new file mode 100644 index 0000000000..a87c18980e --- /dev/null +++ b/app/receipt_store_config.go @@ -0,0 +1,18 @@ +package app + +import ( + "path/filepath" + + seidbconfig "github.com/sei-protocol/sei-chain/sei-db/config" +) + +func readReceiptStoreConfig(homePath string, appOpts seidbconfig.AppOptions) (seidbconfig.ReceiptStoreConfig, error) { + receiptConfig, err := seidbconfig.ReadReceiptConfig(appOpts) + if err != nil { + return receiptConfig, err + } + if receiptConfig.DBDirectory == "" { + receiptConfig.DBDirectory = filepath.Join(homePath, "data", "receipt.db") + } + return receiptConfig, nil +} diff --git a/app/seidb_test.go b/app/seidb_test.go index 576a3bd358..ec7b08e1a1 100644 --- a/app/seidb_test.go +++ b/app/seidb_test.go @@ -1,8 +1,10 @@ package app import ( + "path/filepath" "testing" + "github.com/sei-protocol/sei-chain/sei-cosmos/server" "github.com/sei-protocol/sei-chain/sei-db/config" "github.com/sei-protocol/sei-chain/sei-db/ledger_db/receipt" "github.com/stretchr/testify/assert" @@ -109,6 +111,22 @@ func TestParseReceiptConfigs_UsesConfiguredBackend(t *testing.T) { assert.Equal(t, config.DefaultReceiptStoreConfig().KeepRecent, receiptConfig.KeepRecent) } +func TestParseReceiptConfigs_UsesConfiguredValues(t *testing.T) { + receiptConfig, err := config.ReadReceiptConfig(mapAppOpts{ + "receipt-store.db-directory": "/tmp/custom-receipt-db", + "receipt-store.rs-backend": "parquet", + "receipt-store.async-write-buffer": 7, + "receipt-store.keep-recent": 42, + "receipt-store.prune-interval-seconds": 9, + }) + assert.NoError(t, err) + assert.Equal(t, "/tmp/custom-receipt-db", receiptConfig.DBDirectory) + assert.Equal(t, "parquet", receiptConfig.Backend) + assert.Equal(t, 7, receiptConfig.AsyncWriteBuffer) + assert.Equal(t, 42, receiptConfig.KeepRecent) + assert.Equal(t, 9, receiptConfig.PruneIntervalSeconds) +} + func TestParseReceiptConfigs_RejectsInvalidBackend(t *testing.T) { _, err := config.ReadReceiptConfig(mapAppOpts{ "receipt-store.rs-backend": "rocksdb", @@ -118,6 +136,25 @@ func TestParseReceiptConfigs_RejectsInvalidBackend(t *testing.T) { assert.Contains(t, err.Error(), "rocksdb") } +func TestReadReceiptStoreConfigUsesConfiguredValues(t *testing.T) { + homePath := t.TempDir() + receiptConfig, err := readReceiptStoreConfig(homePath, mapAppOpts{ + "receipt-store.db-directory": "/tmp/custom-receipt-db", + "receipt-store.keep-recent": 5, + server.FlagMinRetainBlocks: 100, + }) + require.NoError(t, err) + assert.Equal(t, "/tmp/custom-receipt-db", receiptConfig.DBDirectory) + assert.Equal(t, 5, receiptConfig.KeepRecent) +} + +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) +} + // TestFullAppPathWithParquetReceiptStore exercises the full app.New path with rs-backend = "parquet" // and asserts the parquet receipt store is actually instantiated (not pebble). func TestFullAppPathWithParquetReceiptStore(t *testing.T) { diff --git a/cmd/seid/cmd/init_test.go b/cmd/seid/cmd/init_test.go index 417bf8da93..06fd629bc1 100644 --- a/cmd/seid/cmd/init_test.go +++ b/cmd/seid/cmd/init_test.go @@ -176,6 +176,10 @@ func TestInitModeConfiguration(t *testing.T) { err = v.ReadInConfig() require.NoError(t, err) require.Equal(t, "pebbledb", v.GetString("receipt-store.rs-backend")) + require.Equal(t, "", v.GetString("receipt-store.db-directory")) + require.Equal(t, seidbconfig.DefaultReceiptStoreConfig().AsyncWriteBuffer, v.GetInt("receipt-store.async-write-buffer")) + require.Equal(t, seidbconfig.DefaultReceiptStoreConfig().KeepRecent, v.GetInt("receipt-store.keep-recent")) + require.Equal(t, seidbconfig.DefaultReceiptStoreConfig().PruneIntervalSeconds, v.GetInt("receipt-store.prune-interval-seconds")) }) } } @@ -193,6 +197,11 @@ func TestInitAppConfigIncludesReceiptStoreDefaults(t *testing.T) { output := buf.String() require.Contains(t, output, "[receipt-store]") require.Contains(t, output, `rs-backend = "pebbledb"`) + require.Contains(t, output, `db-directory = ""`) + require.Contains(t, output, "async-write-buffer =") + require.Contains(t, output, "keep-recent =") + require.Contains(t, output, "prune-interval-seconds =") + require.NotContains(t, output, "use-default-comparer") } // TestInitModeFlag verifies the mode flag validation diff --git a/sei-db/config/receipt_config.go b/sei-db/config/receipt_config.go index 30f734e846..1bf066dcfb 100644 --- a/sei-db/config/receipt_config.go +++ b/sei-db/config/receipt_config.go @@ -14,7 +14,11 @@ type AppOptions interface { } const ( - flagRSBackend = "receipt-store.rs-backend" + flagRSDBDirectory = "receipt-store.db-directory" + flagRSBackend = "receipt-store.rs-backend" + flagRSAsyncWriteBuffer = "receipt-store.async-write-buffer" + flagRSKeepRecent = "receipt-store.keep-recent" + flagRSPruneIntervalSeconds = "receipt-store.prune-interval-seconds" ) // ReceiptStoreConfig defines configuration for the receipt store database. @@ -30,6 +34,7 @@ type ReceiptStoreConfig struct { Backend string `mapstructure:"rs-backend"` // AsyncWriteBuffer defines the async queue length for commits to be applied to receipt store + // Applies only to the pebbledb backend. // Set <= 0 for synchronous writes. // defaults to 100 AsyncWriteBuffer int `mapstructure:"async-write-buffer"` @@ -42,12 +47,6 @@ type ReceiptStoreConfig struct { // PruneIntervalSeconds defines the interval in seconds to trigger pruning // default to every 600 seconds PruneIntervalSeconds int `mapstructure:"prune-interval-seconds"` - - // UseDefaultComparer uses Pebble's default lexicographic byte comparer instead of - // the custom MVCCComparer. This is NOT backwards compatible with existing databases - // that were created with MVCCComparer - only use this for NEW databases. - // defaults to false (use MVCCComparer for backwards compatibility) - UseDefaultComparer bool `mapstructure:"use-default-comparer"` } // DefaultReceiptStoreConfig returns the default ReceiptStoreConfig @@ -57,13 +56,19 @@ func DefaultReceiptStoreConfig() ReceiptStoreConfig { AsyncWriteBuffer: DefaultSSAsyncBuffer, KeepRecent: DefaultSSKeepRecent, PruneIntervalSeconds: DefaultSSPruneInterval, - UseDefaultComparer: false, } } // ReadReceiptConfig reads receipt store config from app options (e.g. TOML / Viper). func ReadReceiptConfig(opts AppOptions) (ReceiptStoreConfig, error) { cfg := DefaultReceiptStoreConfig() + if v := opts.Get(flagRSDBDirectory); v != nil { + dbDirectory, err := cast.ToStringE(v) + if err != nil { + return cfg, err + } + cfg.DBDirectory = strings.TrimSpace(dbDirectory) + } if v := opts.Get(flagRSBackend); v != nil { backend, err := cast.ToStringE(v) if err != nil { @@ -77,5 +82,26 @@ func ReadReceiptConfig(opts AppOptions) (ReceiptStoreConfig, error) { return cfg, fmt.Errorf("unsupported receipt-store backend %q; supported: pebbledb, parquet", backend) } } + if v := opts.Get(flagRSAsyncWriteBuffer); v != nil { + asyncWriteBuffer, err := cast.ToIntE(v) + if err != nil { + return cfg, err + } + cfg.AsyncWriteBuffer = asyncWriteBuffer + } + if v := opts.Get(flagRSKeepRecent); v != nil { + keepRecent, err := cast.ToIntE(v) + if err != nil { + return cfg, err + } + cfg.KeepRecent = keepRecent + } + if v := opts.Get(flagRSPruneIntervalSeconds); v != nil { + pruneIntervalSeconds, err := cast.ToIntE(v) + if err != nil { + return cfg, err + } + cfg.PruneIntervalSeconds = pruneIntervalSeconds + } return cfg, nil } diff --git a/sei-db/config/toml.go b/sei-db/config/toml.go index 8d972cf97f..7588e7b434 100644 --- a/sei-db/config/toml.go +++ b/sei-db/config/toml.go @@ -135,6 +135,24 @@ const ReceiptStoreConfigTemplate = ` # Supported backends: pebble (aka pebbledb), parquet # defaults to pebbledb rs-backend = "{{ .ReceiptStore.Backend }}" + +# Defines the receipt store directory. If unset, defaults to /data/receipt.db +db-directory = "{{ .ReceiptStore.DBDirectory }}" + +# AsyncWriteBuffer defines the async queue length for commits to be applied to receipt store. +# Applies only when rs-backend = "pebbledb"; parquet ignores this setting. +# Set <= 0 for synchronous writes. +# defaults to 100 +async-write-buffer = {{ .ReceiptStore.AsyncWriteBuffer }} + +# KeepRecent defines the number of versions to keep in receipt store +# Setting it to 0 means keep everything. +# Default to keep the last 100,000 blocks +keep-recent = {{ .ReceiptStore.KeepRecent }} + +# PruneIntervalSeconds defines the interval in seconds to trigger pruning. +# defaults to 600 seconds +prune-interval-seconds = {{ .ReceiptStore.PruneIntervalSeconds }} ` // DefaultConfigTemplate combines both templates for backward compatibility diff --git a/sei-db/config/toml_test.go b/sei-db/config/toml_test.go index d35307766f..09a175850c 100644 --- a/sei-db/config/toml_test.go +++ b/sei-db/config/toml_test.go @@ -89,6 +89,36 @@ func TestStateStoreConfigTemplate(t *testing.T) { require.Contains(t, output, "ss-import-num-workers =", "Missing ss-import-num-workers") } +// TestReceiptStoreConfigTemplate verifies that all field paths in the receipt-store TOML template +// are valid and can be resolved against the actual config struct. +func TestReceiptStoreConfigTemplate(t *testing.T) { + type TemplateConfig struct { + ReceiptStore ReceiptStoreConfig + } + + cfg := TemplateConfig{ + ReceiptStore: DefaultReceiptStoreConfig(), + } + + tmpl, err := template.New("receipt").Parse(ReceiptStoreConfigTemplate) + require.NoError(t, err, "Failed to parse ReceiptStoreConfigTemplate") + + var buf bytes.Buffer + err = tmpl.Execute(&buf, cfg) + require.NoError(t, err, "Failed to execute ReceiptStoreConfigTemplate - field path mismatch detected") + + output := buf.String() + + require.Contains(t, output, "[receipt-store]", "Missing receipt-store section") + require.Contains(t, output, `rs-backend = "pebbledb"`, "Missing or incorrect rs-backend") + require.Contains(t, output, `db-directory = ""`, "Missing or incorrect db-directory") + require.Contains(t, output, "async-write-buffer =", "Missing async-write-buffer") + require.Contains(t, output, "keep-recent =", "Missing keep-recent") + require.Contains(t, output, "prune-interval-seconds =", "Missing prune-interval-seconds") + require.Contains(t, output, `Applies only when rs-backend = "pebbledb"`, "Missing pebble-only async-write-buffer note") + require.NotContains(t, output, "use-default-comparer", "use-default-comparer should not be in receipt-store template") +} + // TestDefaultConfigTemplate verifies the combined template works correctly func TestDefaultConfigTemplate(t *testing.T) { type TemplateConfig struct { @@ -116,6 +146,9 @@ func TestDefaultConfigTemplate(t *testing.T) { require.Contains(t, output, "[state-commit]") require.Contains(t, output, "[state-store]") require.Contains(t, output, "[receipt-store]") + require.Contains(t, output, "async-write-buffer =") + require.Contains(t, output, "keep-recent =") + require.Contains(t, output, "prune-interval-seconds =") } // TestWriteModeValues verifies WriteMode enum values match template output diff --git a/sei-db/ledger_db/parquet/store_config_test.go b/sei-db/ledger_db/parquet/store_config_test.go index 2fba4afb66..b7d1d8b07f 100644 --- a/sei-db/ledger_db/parquet/store_config_test.go +++ b/sei-db/ledger_db/parquet/store_config_test.go @@ -65,6 +65,19 @@ func TestNewStoreUsesDefaultIntervalsWhenUnset(t *testing.T) { require.Equal(t, defaultMaxBlocksPerFile, store.CacheRotateInterval()) } +func TestNewStorePreservesKeepRecentAndPruneIntervalSettings(t *testing.T) { + store, err := NewStore(dbLogger.NewNopLogger(), StoreConfig{ + DBDirectory: t.TempDir(), + KeepRecent: 123, + PruneIntervalSeconds: 9, + }) + require.NoError(t, err) + t.Cleanup(func() { _ = store.Close() }) + + require.Equal(t, int64(123), store.config.KeepRecent) + require.Equal(t, int64(9), store.config.PruneIntervalSeconds) +} + func TestPruneOldFilesKeepsTrackingOnDeleteFailure(t *testing.T) { store, err := NewStore(dbLogger.NewNopLogger(), StoreConfig{ DBDirectory: t.TempDir(), diff --git a/sei-db/ledger_db/receipt/parquet_store_test.go b/sei-db/ledger_db/receipt/parquet_store_test.go index 78e3f8a669..794fc6f81b 100644 --- a/sei-db/ledger_db/receipt/parquet_store_test.go +++ b/sei-db/ledger_db/receipt/parquet_store_test.go @@ -5,6 +5,7 @@ import ( "os" "path/filepath" "testing" + "time" "github.com/ethereum/go-ethereum/common" ethtypes "github.com/ethereum/go-ethereum/core/types" @@ -183,12 +184,38 @@ func TestParquetReceiptStoreWALReplay(t *testing.T) { require.Equal(t, receipt.TxHashHex, got.TxHashHex) } +func TestParquetReceiptStoreUsesConfiguredDirectory(t *testing.T) { + ctx, storeKey := newTestContext() + cfg := dbconfig.DefaultReceiptStoreConfig() + cfg.Backend = "parquet" + cfg.DBDirectory = t.TempDir() + + store, err := NewReceiptStore(dbLogger.NewNopLogger(), cfg, storeKey) + require.NoError(t, err) + + txHash := common.HexToHash("0x31") + receipt := makeTestReceipt(txHash, 11, 0, common.HexToAddress("0x401"), nil) + require.NoError(t, store.SetReceipts(ctx.WithBlockHeight(11), []ReceiptRecord{ + {TxHash: txHash, Receipt: receipt}, + })) + require.NoError(t, store.Close()) + + receiptFiles, err := filepath.Glob(filepath.Join(cfg.DBDirectory, "receipts_*.parquet")) + require.NoError(t, err) + require.NotEmpty(t, receiptFiles, "receipt parquet files should be written under the configured db directory") + + logFiles, err := filepath.Glob(filepath.Join(cfg.DBDirectory, "logs_*.parquet")) + require.NoError(t, err) + require.NotEmpty(t, logFiles, "log parquet files should be written under the configured db directory") +} + func TestParquetFilePruning(t *testing.T) { ctx, storeKey := newTestContext() cfg := dbconfig.DefaultReceiptStoreConfig() cfg.Backend = "parquet" cfg.DBDirectory = t.TempDir() cfg.KeepRecent = 600 // Keep 600 blocks, so files with blocks < (latest - 600) get pruned + cfg.PruneIntervalSeconds = 1 store, err := NewReceiptStore(dbLogger.NewNopLogger(), cfg, storeKey) require.NoError(t, err) @@ -209,20 +236,31 @@ func TestParquetFilePruning(t *testing.T) { // Close to flush all files require.NoError(t, store.Close()) - // Verify we have at least 2 receipt and log files + // Verify we have enough receipt and log files for pruning to remove at least one pair. receiptFiles, err := filepath.Glob(filepath.Join(cfg.DBDirectory, "receipts_*.parquet")) require.NoError(t, err) - require.GreaterOrEqual(t, len(receiptFiles), 2, "should have at least 2 receipt files") + require.GreaterOrEqual(t, len(receiptFiles), 3, "should have at least 3 receipt files") logFiles, err := filepath.Glob(filepath.Join(cfg.DBDirectory, "logs_*.parquet")) require.NoError(t, err) - require.GreaterOrEqual(t, len(logFiles), 2, "should have at least 2 log files") + require.GreaterOrEqual(t, len(logFiles), 3, "should have at least 3 log files") + + initialReceiptFileCount := len(receiptFiles) + initialLogFileCount := len(logFiles) - // Reopen store - pruning will run in background + // Reopen store - pruning runs immediately on startup and then periodically afterward. store, err = NewReceiptStore(dbLogger.NewNopLogger(), cfg, storeKey) require.NoError(t, err) t.Cleanup(func() { _ = store.Close() }) + require.Eventually(t, func() bool { + currentReceiptFiles, err := filepath.Glob(filepath.Join(cfg.DBDirectory, "receipts_*.parquet")) + require.NoError(t, err) + currentLogFiles, err := filepath.Glob(filepath.Join(cfg.DBDirectory, "logs_*.parquet")) + require.NoError(t, err) + return len(currentReceiptFiles) < initialReceiptFileCount && len(currentLogFiles) < initialLogFileCount + }, 5*time.Second, 100*time.Millisecond, "configured keep-recent and prune-interval should trigger parquet file pruning") + // Verify we can still query recent data txHash := common.BigToHash(common.Big1.SetUint64(1400)) _, err = store.GetReceiptFromStore(ctx, txHash) diff --git a/sei-db/ledger_db/receipt/receipt_store.go b/sei-db/ledger_db/receipt/receipt_store.go index 30e37c58d2..250ac3ee30 100644 --- a/sei-db/ledger_db/receipt/receipt_store.go +++ b/sei-db/ledger_db/receipt/receipt_store.go @@ -117,6 +117,7 @@ func newReceiptBackend(log dbLogger.Logger, config dbconfig.ReceiptStoreConfig, case receiptBackendPebble: ssConfig := dbconfig.DefaultStateStoreConfig() ssConfig.DBDirectory = config.DBDirectory + ssConfig.AsyncWriteBuffer = config.AsyncWriteBuffer ssConfig.KeepRecent = config.KeepRecent if config.PruneIntervalSeconds > 0 { ssConfig.PruneIntervalSeconds = config.PruneIntervalSeconds From 28ae4007144534eb819a5fbafc109534e6e4bd2b Mon Sep 17 00:00:00 2001 From: Jeremy Wei Date: Mon, 9 Mar 2026 15:27:34 -0400 Subject: [PATCH 6/7] fix --- app/receipt_store_config.go | 8 ++++++ app/seidb_test.go | 25 ++++++++++--------- app/test_helpers.go | 2 +- .../ledger_db/receipt/parquet_store_test.go | 8 ++++-- 4 files changed, 28 insertions(+), 15 deletions(-) diff --git a/app/receipt_store_config.go b/app/receipt_store_config.go index a87c18980e..8d2acfacf2 100644 --- a/app/receipt_store_config.go +++ b/app/receipt_store_config.go @@ -6,6 +6,14 @@ import ( seidbconfig "github.com/sei-protocol/sei-chain/sei-db/config" ) +const ( + receiptStoreBackendKey = "receipt-store.rs-backend" + receiptStoreDBDirectoryKey = "receipt-store.db-directory" + receiptStoreAsyncWriteBufferKey = "receipt-store.async-write-buffer" + receiptStoreKeepRecentKey = "receipt-store.keep-recent" + receiptStorePruneIntervalSecondsKey = "receipt-store.prune-interval-seconds" +) + func readReceiptStoreConfig(homePath string, appOpts seidbconfig.AppOptions) (seidbconfig.ReceiptStoreConfig, error) { receiptConfig, err := seidbconfig.ReadReceiptConfig(appOpts) if err != nil { diff --git a/app/seidb_test.go b/app/seidb_test.go index ec7b08e1a1..fa91e1436e 100644 --- a/app/seidb_test.go +++ b/app/seidb_test.go @@ -17,6 +17,7 @@ type TestSeiDBAppOpts struct { func (t TestSeiDBAppOpts) Get(s string) interface{} { defaultSCConfig := config.DefaultStateCommitConfig() defaultSSConfig := config.DefaultStateStoreConfig() + defaultReceiptConfig := config.DefaultReceiptStoreConfig() switch s { case FlagSCEnable: return defaultSCConfig.Enable @@ -50,8 +51,8 @@ func (t TestSeiDBAppOpts) Get(s string) interface{} { return defaultSSConfig.PruneIntervalSeconds case FlagSSImportNumWorkers: return defaultSSConfig.ImportNumWorkers - case "receipt-store.rs-backend": - return config.DefaultReceiptStoreConfig().Backend + case receiptStoreBackendKey: + return defaultReceiptConfig.Backend case FlagEVMSSDirectory: return defaultSSConfig.EVMDBDirectory case FlagEVMSSWriteMode: @@ -103,7 +104,7 @@ func TestParseReceiptConfigs_DefaultsToPebbleWhenUnset(t *testing.T) { func TestParseReceiptConfigs_UsesConfiguredBackend(t *testing.T) { receiptConfig, err := config.ReadReceiptConfig(mapAppOpts{ - "receipt-store.rs-backend": "parquet", + receiptStoreBackendKey: "parquet", }) assert.NoError(t, err) assert.Equal(t, "parquet", receiptConfig.Backend) @@ -113,11 +114,11 @@ func TestParseReceiptConfigs_UsesConfiguredBackend(t *testing.T) { func TestParseReceiptConfigs_UsesConfiguredValues(t *testing.T) { receiptConfig, err := config.ReadReceiptConfig(mapAppOpts{ - "receipt-store.db-directory": "/tmp/custom-receipt-db", - "receipt-store.rs-backend": "parquet", - "receipt-store.async-write-buffer": 7, - "receipt-store.keep-recent": 42, - "receipt-store.prune-interval-seconds": 9, + receiptStoreDBDirectoryKey: "/tmp/custom-receipt-db", + receiptStoreBackendKey: "parquet", + receiptStoreAsyncWriteBufferKey: 7, + receiptStoreKeepRecentKey: 42, + receiptStorePruneIntervalSecondsKey: 9, }) assert.NoError(t, err) assert.Equal(t, "/tmp/custom-receipt-db", receiptConfig.DBDirectory) @@ -129,7 +130,7 @@ func TestParseReceiptConfigs_UsesConfiguredValues(t *testing.T) { func TestParseReceiptConfigs_RejectsInvalidBackend(t *testing.T) { _, err := config.ReadReceiptConfig(mapAppOpts{ - "receipt-store.rs-backend": "rocksdb", + receiptStoreBackendKey: "rocksdb", }) assert.Error(t, err) assert.Contains(t, err.Error(), "unsupported receipt-store backend") @@ -139,9 +140,9 @@ func TestParseReceiptConfigs_RejectsInvalidBackend(t *testing.T) { func TestReadReceiptStoreConfigUsesConfiguredValues(t *testing.T) { homePath := t.TempDir() receiptConfig, err := readReceiptStoreConfig(homePath, mapAppOpts{ - "receipt-store.db-directory": "/tmp/custom-receipt-db", - "receipt-store.keep-recent": 5, - server.FlagMinRetainBlocks: 100, + receiptStoreDBDirectoryKey: "/tmp/custom-receipt-db", + receiptStoreKeepRecentKey: 5, + server.FlagMinRetainBlocks: 100, }) require.NoError(t, err) assert.Equal(t, "/tmp/custom-receipt-db", receiptConfig.DBDirectory) diff --git a/app/test_helpers.go b/app/test_helpers.go index 86f660d177..8785e8c568 100644 --- a/app/test_helpers.go +++ b/app/test_helpers.go @@ -100,7 +100,7 @@ func (t TestAppOpts) Get(s string) interface{} { if s == gigaconfig.FlagOCCEnabled { return t.EnableGigaOCC } - if s == "receipt-store.rs-backend" && t.ReceiptBackend != "" { + if s == receiptStoreBackendKey && t.ReceiptBackend != "" { return t.ReceiptBackend } // Disable EVM HTTP and WebSocket servers in tests to avoid port conflicts diff --git a/sei-db/ledger_db/receipt/parquet_store_test.go b/sei-db/ledger_db/receipt/parquet_store_test.go index 794fc6f81b..6ba6142f4c 100644 --- a/sei-db/ledger_db/receipt/parquet_store_test.go +++ b/sei-db/ledger_db/receipt/parquet_store_test.go @@ -5,7 +5,6 @@ import ( "os" "path/filepath" "testing" - "time" "github.com/ethereum/go-ethereum/common" ethtypes "github.com/ethereum/go-ethereum/core/types" @@ -248,6 +247,11 @@ func TestParquetFilePruning(t *testing.T) { initialReceiptFileCount := len(receiptFiles) initialLogFileCount := len(logFiles) + const ( + pruneTimeout = 5_000_000_000 + prunePollInterval = 100_000_000 + ) + // Reopen store - pruning runs immediately on startup and then periodically afterward. store, err = NewReceiptStore(dbLogger.NewNopLogger(), cfg, storeKey) require.NoError(t, err) @@ -259,7 +263,7 @@ func TestParquetFilePruning(t *testing.T) { currentLogFiles, err := filepath.Glob(filepath.Join(cfg.DBDirectory, "logs_*.parquet")) require.NoError(t, err) return len(currentReceiptFiles) < initialReceiptFileCount && len(currentLogFiles) < initialLogFileCount - }, 5*time.Second, 100*time.Millisecond, "configured keep-recent and prune-interval should trigger parquet file pruning") + }, pruneTimeout, prunePollInterval, "configured keep-recent and prune-interval should trigger parquet file pruning") // Verify we can still query recent data txHash := common.BigToHash(common.Big1.SetUint64(1400)) From 76566dc6c3097e5493970a864d0353d03e26b75a Mon Sep 17 00:00:00 2001 From: Jeremy Wei Date: Tue, 10 Mar 2026 13:36:44 -0400 Subject: [PATCH 7/7] error on backend used for config not rs-backend --- sei-db/config/receipt_config.go | 4 ++++ sei-db/config/receipt_config_test.go | 23 +++++++++++++++++++++++ 2 files changed, 27 insertions(+) create mode 100644 sei-db/config/receipt_config_test.go diff --git a/sei-db/config/receipt_config.go b/sei-db/config/receipt_config.go index 1bf066dcfb..0d2887ed91 100644 --- a/sei-db/config/receipt_config.go +++ b/sei-db/config/receipt_config.go @@ -16,6 +16,7 @@ type AppOptions interface { const ( flagRSDBDirectory = "receipt-store.db-directory" flagRSBackend = "receipt-store.rs-backend" + flagRSMisnamedBackend = "receipt-store.backend" flagRSAsyncWriteBuffer = "receipt-store.async-write-buffer" flagRSKeepRecent = "receipt-store.keep-recent" flagRSPruneIntervalSeconds = "receipt-store.prune-interval-seconds" @@ -62,6 +63,9 @@ func DefaultReceiptStoreConfig() ReceiptStoreConfig { // ReadReceiptConfig reads receipt store config from app options (e.g. TOML / Viper). func ReadReceiptConfig(opts AppOptions) (ReceiptStoreConfig, error) { cfg := DefaultReceiptStoreConfig() + if v := opts.Get(flagRSMisnamedBackend); v != nil { + return cfg, fmt.Errorf("unsupported receipt-store config key %q; use %q instead", flagRSMisnamedBackend, flagRSBackend) + } if v := opts.Get(flagRSDBDirectory); v != nil { dbDirectory, err := cast.ToStringE(v) if err != nil { diff --git a/sei-db/config/receipt_config_test.go b/sei-db/config/receipt_config_test.go new file mode 100644 index 0000000000..68af52c524 --- /dev/null +++ b/sei-db/config/receipt_config_test.go @@ -0,0 +1,23 @@ +package config + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +type mapAppOpts map[string]interface{} + +func (m mapAppOpts) Get(key string) interface{} { + return m[key] +} + +func TestReadReceiptConfigRejectsMisnamedBackendKey(t *testing.T) { + _, err := ReadReceiptConfig(mapAppOpts{ + "receipt-store.backend": "parquet", + }) + + require.Error(t, err) + require.ErrorContains(t, err, "receipt-store.backend") + require.ErrorContains(t, err, "receipt-store.rs-backend") +}