Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 50 additions & 11 deletions sei-db/state_db/bench/wrappers/db_implementations.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,9 @@ func openSSComposite(dir string, cfg config.StateStoreConfig) (*ssComposite.Comp
}

func newSSCompositeStateStore(dbDir string, ssConfig *config.StateStoreConfig) (DBWrapper, error) {
if ssConfig == nil {
ssConfig = DefaultBenchStateStoreConfig()
}
fmt.Printf("Opening composite state store from directory %s\n", dbDir)
store, err := openSSComposite(dbDir, *ssConfig)
if err != nil {
Expand All @@ -139,6 +142,9 @@ func newCombinedCompositeDualSSComposite(
dbDir string,
ssConfig *config.StateStoreConfig,
) (DBWrapper, error) {
if ssConfig == nil {
ssConfig = DefaultBenchStateStoreConfig()
}

fmt.Printf("Opening CompositeDual (SC) + Composite (SS) from directory %s\n", dbDir)
sc, err := newCompositeCommitStore(ctx, filepath.Join(dbDir, "sc"), sctypes.TestOnlyDualWrite)
Expand All @@ -153,35 +159,68 @@ func newCombinedCompositeDualSSComposite(
return NewCombinedWrapper(sc, ss), nil
}

// backendConfig converts the untyped config NewDBImpl is handed into the one its backend expects.
//
// A nil config is ordinary rather than exceptional: runBenchmark passes nil for every backend, and
// each constructor supplies its own default. So nil is passed straight through, and only a config of
// the wrong type is an error. Asserting without this — dbConfig.(*T) on a nil interface — panics
// before the constructor can apply that default, which is how three bench backends came to crash at
// startup.
func backendConfig[T any](dbType DBType, dbConfig any) (*T, error) {
if dbConfig == nil {
return nil, nil
}
typed, ok := dbConfig.(*T)
if !ok {
var want T
return nil, fmt.Errorf("invalid %s config type %T, want *%T", dbType, dbConfig, want)
}
return typed, nil
}

// NewDBImpl instantiates a new empty DBWrapper based on the given DBType.
func NewDBImpl(ctx context.Context, dbType DBType, dataDir string, dbConfig any) (DBWrapper, error) {
switch dbType {
case NoOp:
return NewNoOpWrapper(), nil
case MemIAVL:
memiavlCfg, ok := dbConfig.(*memiavl.Config)
if dbConfig != nil && !ok {
return nil, fmt.Errorf("invalid MemIAVL config type %T", dbConfig)
cfg, err := backendConfig[memiavl.Config](dbType, dbConfig)
if err != nil {
return nil, err
}
return newMemIAVLCommitStore(dataDir, memiavlCfg)
return newMemIAVLCommitStore(dataDir, cfg)
case FlatKV:
flatKVConfig, ok := dbConfig.(*flatkvConfig.Config)
if dbConfig != nil && !ok {
return nil, fmt.Errorf("invalid FlatKV config type %T", dbConfig)
cfg, err := backendConfig[flatkvConfig.Config](dbType, dbConfig)
if err != nil {
return nil, err
}
return newFlatKVCommitStore(ctx, dataDir, flatKVConfig)
return newFlatKVCommitStore(ctx, dataDir, cfg)
case CompositeDual:
return newCompositeCommitStore(ctx, dataDir, sctypes.TestOnlyDualWrite)
case CompositeSplit:
return newCompositeCommitStore(ctx, dataDir, sctypes.EVMMigrated)
case CompositeCosmos:
return newCompositeCommitStore(ctx, dataDir, sctypes.MemiavlOnly)
case SSComposite:
return newSSCompositeStateStore(dataDir, dbConfig.(*config.StateStoreConfig))
cfg, err := backendConfig[config.StateStoreConfig](dbType, dbConfig)
if err != nil {
return nil, err
}
return newSSCompositeStateStore(dataDir, cfg)
case SSHistoricalOffload:
return newSSHistoricalOffloadStateStore(ctx, dataDir, dbConfig.(*HistoricalOffloadConfig))
// No default: the stream needs brokers only the caller knows, so a missing config is
// reported by HistoricalOffloadConfig.Validate rather than invented here.
cfg, err := backendConfig[HistoricalOffloadConfig](dbType, dbConfig)
if err != nil {
return nil, err
}
return newSSHistoricalOffloadStateStore(ctx, dataDir, cfg)
case CompositeDual_SSComposite:
return newCombinedCompositeDualSSComposite(ctx, dataDir, dbConfig.(*config.StateStoreConfig))
cfg, err := backendConfig[config.StateStoreConfig](dbType, dbConfig)
if err != nil {
return nil, err
}
return newCombinedCompositeDualSSComposite(ctx, dataDir, cfg)
default:
return nil, fmt.Errorf("unsupported DB type: %s", dbType)
}
Expand Down
33 changes: 26 additions & 7 deletions sei-db/state_db/bench/wrappers/wrappers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -192,15 +192,34 @@ func TestNoOpWrapperTracksVersionWithoutReadsOrWrites(t *testing.T) {
require.Equal(t, int64(9), version)
}

func TestNewDBImplFlatKVUsesDefaultConfigWhenNil(t *testing.T) {
wrapper, err := NewDBImpl(t.Context(), FlatKV, t.TempDir(), nil)
require.NoError(t, err)
require.NoError(t, wrapper.Close())
// runBenchmark passes nil for every backend, so each of these opened with a panic before their
// config was made optional.
func TestNewDBImplUsesDefaultConfigWhenNil(t *testing.T) {
for _, dbType := range []DBType{MemIAVL, FlatKV, SSComposite, CompositeDual_SSComposite} {
t.Run(string(dbType), func(t *testing.T) {
wrapper, err := NewDBImpl(t.Context(), dbType, t.TempDir(), nil)
require.NoError(t, err)
require.NoError(t, wrapper.Close())
})
}
}

func TestNewDBImplFlatKVRejectsInvalidConfigType(t *testing.T) {
wrapper, err := NewDBImpl(t.Context(), FlatKV, t.TempDir(), "invalid")
// The offload stream needs brokers that only the caller knows, so this backend has no default to
// fall back on and must say so rather than panic.
func TestNewDBImplSSHistoricalOffloadReportsMissingConfig(t *testing.T) {
wrapper, err := NewDBImpl(t.Context(), SSHistoricalOffload, t.TempDir(), nil)
require.Error(t, err)
require.Nil(t, wrapper)
require.ErrorContains(t, err, "invalid FlatKV config type string")
require.ErrorContains(t, err, "historical offload config is required")
}

func TestNewDBImplRejectsInvalidConfigType(t *testing.T) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[suggestion] The two tables cover the nil path and the wrong-type path, but nothing asserts the third branch of backendConfig — that a correctly-typed non-nil config is passed through and actually honored rather than silently replaced by the backend default. A small case (e.g. open SSComposite with a *config.StateStoreConfig whose EVMSplit/AsyncWriteBuffer differs from DefaultBenchStateStoreConfig(), and assert the store reflects it) would lock in the behavior cryptosim.go:146 depends on when the user supplies a config.

for _, dbType := range []DBType{MemIAVL, FlatKV, SSComposite, SSHistoricalOffload, CompositeDual_SSComposite} {
t.Run(string(dbType), func(t *testing.T) {
wrapper, err := NewDBImpl(t.Context(), dbType, t.TempDir(), "invalid")
require.Error(t, err)
require.Nil(t, wrapper)
require.ErrorContains(t, err, "invalid "+string(dbType)+" config type string")
})
}
}
Loading