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
2 changes: 1 addition & 1 deletion sei-cosmos/storev2/rootmulti/store_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import (
)

func TestLastCommitID(t *testing.T) {
store := NewStore(t.TempDir(), config.StateCommitConfig{}, config.StateStoreConfig{}, []string{})
store := NewStore(t.TempDir(), config.DefaultStateCommitConfig(), config.StateStoreConfig{}, []string{})
require.Equal(t, types.CommitID{}, store.LastCommitID())
}

Expand Down
16 changes: 9 additions & 7 deletions sei-db/config/sc_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,13 +62,12 @@ type StateCommitConfig struct {
// DefaultStateCommitConfig returns the default StateCommitConfig
func DefaultStateCommitConfig() StateCommitConfig {
return StateCommitConfig{
Enable: true,
WriteMode: CosmosOnlyWrite,
ReadMode: CosmosOnlyRead,
EnableLatticeHash: false,
MemIAVLConfig: memiavl.DefaultConfig(),
FlatKVConfig: flatkv.DefaultConfig(),

Enable: true,
WriteMode: CosmosOnlyWrite,
ReadMode: CosmosOnlyRead,
EnableLatticeHash: false,
MemIAVLConfig: memiavl.DefaultConfig(),
FlatKVConfig: flatkv.DefaultConfig(),
HistoricalProofMaxInFlight: DefaultSCHistoricalProofMaxInFlight,
HistoricalProofRateLimit: DefaultSCHistoricalProofRateLimit,
HistoricalProofBurst: DefaultSCHistoricalProofBurst,
Expand All @@ -83,5 +82,8 @@ func (c StateCommitConfig) Validate() error {
if !c.ReadMode.IsValid() {
return fmt.Errorf("invalid read-mode: %s", c.ReadMode)
}
if c.WriteMode == SplitWrite && !c.EnableLatticeHash {
return fmt.Errorf("lattice hash must be enabled when using split_write mode")
}
return nil
}
14 changes: 0 additions & 14 deletions sei-db/config/toml.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,6 @@ sc-enable = {{ .StateCommit.Enable }}
# Defines the SC store directory, if not explicitly set, default to application home directory
sc-directory = "{{ .StateCommit.Directory }}"

# WriteMode defines how EVM data writes are routed between backends.
# Valid values: cosmos_only, dual_write, split_write, evm_only
# defaults to cosmos_only
sc-write-mode = "{{ .StateCommit.WriteMode }}"

# ReadMode defines how EVM data reads are routed between backends.
# Valid values: cosmos_only, evm_first, split_read
# defaults to cosmos_only
sc-read-mode = "{{ .StateCommit.ReadMode }}"

# EnableLatticeHash controls whether the FlatKV lattice hash participates
# in the final app hash. Default: false.
sc-enable-lattice-hash = {{ .StateCommit.EnableLatticeHash }}

# Max concurrent historical proof queries (RPC /store path)
sc-historical-proof-max-inflight = {{ .StateCommit.HistoricalProofMaxInFlight }}

Expand Down
22 changes: 12 additions & 10 deletions sei-db/config/toml_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,6 @@ func TestStateCommitConfigTemplate(t *testing.T) {
// Verify key config values are present in output
require.Contains(t, output, "[state-commit]", "Missing state-commit section")
require.Contains(t, output, "sc-enable = true", "Missing or incorrect sc-enable")
require.Contains(t, output, `sc-write-mode = "cosmos_only"`, "Missing or incorrect sc-write-mode")
require.Contains(t, output, `sc-read-mode = "cosmos_only"`, "Missing or incorrect sc-read-mode")

// Verify MemIAVLConfig fields are accessible
require.Contains(t, output, "sc-async-commit-buffer =", "Missing sc-async-commit-buffer")
Expand Down Expand Up @@ -252,22 +250,26 @@ func TestParseReadMode(t *testing.T) {
// TestStateCommitConfigValidate verifies config validation works
func TestStateCommitConfigValidate(t *testing.T) {
tests := []struct {
name string
writeMode WriteMode
readMode ReadMode
hasError bool
name string
writeMode WriteMode
readMode ReadMode
enableLatticeHash bool
hasError bool
}{
{"valid cosmos_only", CosmosOnlyWrite, CosmosOnlyRead, false},
{"valid dual_write", DualWrite, EVMFirstRead, false},
{"invalid write mode", WriteMode("invalid"), CosmosOnlyRead, true},
{"invalid read mode", CosmosOnlyWrite, ReadMode("invalid"), true},
{"valid cosmos_only", CosmosOnlyWrite, CosmosOnlyRead, false, false},
{"valid dual_write", DualWrite, EVMFirstRead, false, false},
{"valid split_write with lattice", SplitWrite, SplitRead, true, false},
{"split_write without lattice", SplitWrite, SplitRead, false, true},
{"invalid write mode", WriteMode("invalid"), CosmosOnlyRead, false, true},
{"invalid read mode", CosmosOnlyWrite, ReadMode("invalid"), false, true},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
cfg := DefaultStateCommitConfig()
cfg.WriteMode = tc.writeMode
cfg.ReadMode = tc.readMode
cfg.EnableLatticeHash = tc.enableLatticeHash

err := cfg.Validate()
if tc.hasError {
Expand Down
55 changes: 55 additions & 0 deletions sei-db/state_db/sc/composite/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ func NewCompositeCommitStore(
homeDir string,
cfg config.StateCommitConfig,
) *CompositeCommitStore {
if err := cfg.Validate(); err != nil {
panic(fmt.Sprintf("invalid state commit config: %s", err))
}

// Always initialize the Cosmos backend (creates struct only, not opened)
cosmosCommitter := memiavl.NewCommitStore(homeDir, cfg.MemIAVLConfig)

Expand Down Expand Up @@ -136,6 +140,16 @@ func (cs *CompositeCommitStore) LoadVersion(targetVersion int64, readOnly bool)
if err != nil {
return nil, fmt.Errorf("failed to load FlatKV version: %w", err)
}

// When loading latest (targetVersion==0), a crash between the
// sequential cosmos and EVM commits can leave the backends at
// different versions. Detect the mismatch and roll the ahead
// backend back so both restart from a consistent point.
if targetVersion == 0 {
if err := cs.reconcileVersions(); err != nil {
return nil, err
}
}
}

return cs, nil
Expand Down Expand Up @@ -216,6 +230,47 @@ func (cs *CompositeCommitStore) Commit() (int64, error) {
return cosmosVersion, nil
}

// reconcileVersions checks whether the cosmos and EVM backends are at the
// same version after loading latest. A crash between the sequential Commit
// calls can leave one backend one version ahead. When a mismatch is found
// and both backends have committed at least once (version > 0), the ahead
// backend is rolled back to the behind version. Rollback truncates the WAL
// so the correction survives subsequent restarts.
func (cs *CompositeCommitStore) reconcileVersions() error {
cosmosVer := cs.cosmosCommitter.Version()
evmVer := cs.evmCommitter.Version()
if cosmosVer == evmVer {
return nil
}

// Skip reconciliation when either backend is at version 0 (fresh
// initialization / migration), since that is not a crash artifact.
if cosmosVer == 0 || evmVer == 0 {
return nil
}

minVer := cosmosVer
if evmVer < minVer {
minVer = evmVer
}

logger.Warn("version mismatch between cosmos and EVM after loading latest, rolling back to consistent version",
"cosmosVersion", cosmosVer, "evmVersion", evmVer, "reconciledVersion", minVer)

if cosmosVer > minVer {
if err := cs.cosmosCommitter.Rollback(minVer); err != nil {
return fmt.Errorf("failed to rollback cosmos to reconciled version %d: %w", minVer, err)
}
}
if evmVer > minVer {
if err := cs.evmCommitter.Rollback(minVer); err != nil {
return fmt.Errorf("failed to rollback EVM to reconciled version %d: %w", minVer, err)
}
}

return nil
}

// Version returns the current version
func (cs *CompositeCommitStore) Version() int64 {
if cs.cosmosCommitter != nil {
Expand Down
Loading
Loading