diff --git a/app/seidb.go b/app/seidb.go index ce6c725515..debe108df2 100644 --- a/app/seidb.go +++ b/app/seidb.go @@ -41,9 +41,10 @@ const ( FlagSSImportNumWorkers = "state-store.ss-import-num-workers" // 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" - FlagEVMSSReadMode = "state-store.evm-ss-read-mode" + FlagEVMSSDirectory = "state-store.evm-ss-db-directory" + FlagEVMSSWriteMode = "state-store.evm-ss-write-mode" + FlagEVMSSReadMode = "state-store.evm-ss-read-mode" + FlagEVMSSSeparateDBs = "state-store.evm-ss-separate-dbs" // Other configs FlagSnapshotInterval = "state-sync.snapshot-interval" @@ -69,7 +70,10 @@ func SetupSeiDB( } if ssConfig.EVMEnabled() { logger.Info("SeiDB EVM StateStore optimization is enabled", - "writeMode", ssConfig.WriteMode, "readMode", ssConfig.ReadMode) + "writeMode", ssConfig.WriteMode, + "readMode", ssConfig.ReadMode, + "separateDBs", ssConfig.SeparateEVMSubDBs, + ) } validateConfigs(appOpts) gigaExecutorConfig, err := gigaconfig.ReadConfig(appOpts) @@ -146,6 +150,7 @@ func parseSSConfigs(appOpts servertypes.AppOptions) config.StateStoreConfig { // EVM optimization fields (embedded in SS config) ssConfig.EVMDBDirectory = cast.ToString(appOpts.Get(FlagEVMSSDirectory)) + ssConfig.SeparateEVMSubDBs = cast.ToBool(appOpts.Get(FlagEVMSSSeparateDBs)) if wm := cast.ToString(appOpts.Get(FlagEVMSSWriteMode)); wm != "" { parsedWM, err := config.ParseWriteMode(wm) if err != nil { diff --git a/app/seidb_test.go b/app/seidb_test.go index 8504f32848..e97c99f5ae 100644 --- a/app/seidb_test.go +++ b/app/seidb_test.go @@ -61,6 +61,8 @@ func (t TestSeiDBAppOpts) Get(s string) interface{} { return "" // empty means use default case FlagEVMSSReadMode: return "" // empty means use default + case FlagEVMSSSeparateDBs: + return defaultSSConfig.SeparateEVMSubDBs } return nil } @@ -98,6 +100,24 @@ func TestParseSCConfigs_HistoricalProofFlags(t *testing.T) { assert.Equal(t, 3, scConfig.HistoricalProofBurst) } +func TestParseSSConfigs_EVMFlags(t *testing.T) { + appOpts := mapAppOpts{ + FlagSSEnable: true, + FlagEVMSSDirectory: "/tmp/evm-ss", + FlagEVMSSWriteMode: string(config.SplitWrite), + FlagEVMSSReadMode: string(config.SplitRead), + FlagEVMSSSeparateDBs: true, + FlagSSAsyncWriterBuffer: 0, + } + + ssConfig := parseSSConfigs(appOpts) + assert.True(t, ssConfig.Enable) + assert.Equal(t, "/tmp/evm-ss", ssConfig.EVMDBDirectory) + assert.Equal(t, config.SplitWrite, ssConfig.WriteMode) + assert.Equal(t, config.SplitRead, ssConfig.ReadMode) + assert.True(t, ssConfig.SeparateEVMSubDBs) +} + func TestParseReceiptConfigs_DefaultsToPebbleWhenUnset(t *testing.T) { receiptConfig, err := config.ReadReceiptConfig(mapAppOpts{}) assert.NoError(t, err) diff --git a/docker/localnode/config/app.toml b/docker/localnode/config/app.toml index 9a5bab2960..570bb2b15c 100644 --- a/docker/localnode/config/app.toml +++ b/docker/localnode/config/app.toml @@ -269,6 +269,12 @@ ss-prune-interval = 60 # defaults to 1 ss-import-num-workers = 1 +# EVM state-store DB directory and routing mode controls. +evm-ss-db-directory = "" +evm-ss-write-mode = "cosmos_only" +evm-ss-read-mode = "cosmos_only" +evm-ss-separate-dbs = false + [evm] # EnableTestAPI enables the EVM test API diff --git a/docker/rpcnode/config/app.toml b/docker/rpcnode/config/app.toml index 8a9cda2067..2b943c6dd1 100644 --- a/docker/rpcnode/config/app.toml +++ b/docker/rpcnode/config/app.toml @@ -257,6 +257,12 @@ ss-prune-interval = 60 # defaults to 1 ss-import-num-workers = 1 +# EVM state-store DB directory and routing mode controls. +evm-ss-db-directory = "" +evm-ss-write-mode = "cosmos_only" +evm-ss-read-mode = "cosmos_only" +evm-ss-separate-dbs = false + [evm] # EnableTestAPI enables the EVM test API diff --git a/sei-cosmos/server/config/config.go b/sei-cosmos/server/config/config.go index b3ac1b746c..1d3372910a 100644 --- a/sei-cosmos/server/config/config.go +++ b/sei-cosmos/server/config/config.go @@ -428,6 +428,10 @@ func GetConfig(v *viper.Viper) (Config, error) { KeepRecent: v.GetInt("state-store.ss-keep-recent"), PruneIntervalSeconds: v.GetInt("state-store.ss-prune-interval"), ImportNumWorkers: v.GetInt("state-store.ss-import-num-workers"), + WriteMode: config.WriteMode(v.GetString("state-store.evm-ss-write-mode")), + ReadMode: config.ReadMode(v.GetString("state-store.evm-ss-read-mode")), + EVMDBDirectory: v.GetString("state-store.evm-ss-db-directory"), + SeparateEVMSubDBs: v.GetBool("state-store.evm-ss-separate-dbs"), }, Genesis: GenesisConfig{ StreamImport: v.GetBool("genesis.stream-import"), diff --git a/sei-cosmos/server/config/config_test.go b/sei-cosmos/server/config/config_test.go index 0b7fad8c20..729db4aa08 100644 --- a/sei-cosmos/server/config/config_test.go +++ b/sei-cosmos/server/config/config_test.go @@ -332,6 +332,10 @@ func TestGetConfigStateStore(t *testing.T) { v.Set("state-store.ss-keep-recent", 50000) v.Set("state-store.ss-prune-interval", 1200) v.Set("state-store.ss-import-num-workers", 4) + v.Set("state-store.evm-ss-db-directory", "/custom/evm/ss/path") + v.Set("state-store.evm-ss-write-mode", "split_write") + v.Set("state-store.evm-ss-read-mode", "split_read") + v.Set("state-store.evm-ss-separate-dbs", true) cfg, err := GetConfig(v) require.NoError(t, err) @@ -344,6 +348,10 @@ func TestGetConfigStateStore(t *testing.T) { require.Equal(t, 50000, cfg.StateStore.KeepRecent) require.Equal(t, 1200, cfg.StateStore.PruneIntervalSeconds) require.Equal(t, 4, cfg.StateStore.ImportNumWorkers) + require.Equal(t, "/custom/evm/ss/path", cfg.StateStore.EVMDBDirectory) + require.Equal(t, seidbconfig.SplitWrite, cfg.StateStore.WriteMode) + require.Equal(t, seidbconfig.SplitRead, cfg.StateStore.ReadMode) + require.True(t, cfg.StateStore.SeparateEVMSubDBs) } func TestDefaultStateCommitConfig(t *testing.T) { @@ -367,4 +375,7 @@ func TestDefaultStateStoreConfig(t *testing.T) { require.Equal(t, seidbconfig.DefaultSSKeepRecent, cfg.StateStore.KeepRecent) require.Equal(t, seidbconfig.DefaultSSPruneInterval, cfg.StateStore.PruneIntervalSeconds) require.Equal(t, seidbconfig.DefaultSSImportWorkers, cfg.StateStore.ImportNumWorkers) + require.Equal(t, seidbconfig.CosmosOnlyWrite, cfg.StateStore.WriteMode) + require.Equal(t, seidbconfig.CosmosOnlyRead, cfg.StateStore.ReadMode) + require.False(t, cfg.StateStore.SeparateEVMSubDBs) } diff --git a/sei-db/config/ss_config.go b/sei-db/config/ss_config.go index 1cda8d2ae0..f218bb1da7 100644 --- a/sei-db/config/ss_config.go +++ b/sei-db/config/ss_config.go @@ -76,6 +76,12 @@ type StateStoreConfig struct { // EVMDBDirectory defines the directory for EVM state store db files. // If not set, defaults to /data/evm_ss EVMDBDirectory string `mapstructure:"evm-db-directory"` + + // SeparateEVMSubDBs controls whether EVM data is physically split across + // per-type databases. When false (default), all EVM data stays in one DB. + // When true, data is routed to separate DBs by EVM key family while + // preserving the same logical store key and full key encoding inside each DB. + SeparateEVMSubDBs bool `mapstructure:"evm-separate-dbs"` } // EVMEnabled returns true if EVM state stores should be opened. @@ -106,5 +112,6 @@ func DefaultStateStoreConfig() StateStoreConfig { UseDefaultComparer: false, WriteMode: CosmosOnlyWrite, ReadMode: CosmosOnlyRead, + SeparateEVMSubDBs: false, } } diff --git a/sei-db/config/toml.go b/sei-db/config/toml.go index 723832d0d6..750b73580a 100644 --- a/sei-db/config/toml.go +++ b/sei-db/config/toml.go @@ -126,6 +126,23 @@ ss-prune-interval = {{ .StateStore.PruneIntervalSeconds }} # ImportNumWorkers defines the concurrency for state sync import # defaults to 1 ss-import-num-workers = {{ .StateStore.ImportNumWorkers }} + +# EVMDBDirectory defines the directory for the optional EVM state-store DB(s). +# If unset, defaults to /data/evm_ss when EVM SS is enabled. +evm-ss-db-directory = "{{ .StateStore.EVMDBDirectory }}" + +# WriteMode controls how EVM data writes are routed. +# Supported values: "cosmos_only", "dual_write", "split_write" +evm-ss-write-mode = "{{ .StateStore.WriteMode }}" + +# ReadMode controls how EVM data reads are routed. +# Supported values: "cosmos_only", "evm_first", "split_read" +evm-ss-read-mode = "{{ .StateStore.ReadMode }}" + +# SeparateEVMSubDBs controls whether EVM data is split across per-type DBs. +# When false, all EVM data stays in one DB using the current unified layout. +# When true, data is routed to separate DBs while preserving the same evm key prefix format. +evm-ss-separate-dbs = {{ .StateStore.SeparateEVMSubDBs }} ` // ReceiptStoreConfigTemplate defines the configuration template for receipt-store diff --git a/sei-db/config/toml_test.go b/sei-db/config/toml_test.go index 09a175850c..cb614fe868 100644 --- a/sei-db/config/toml_test.go +++ b/sei-db/config/toml_test.go @@ -87,6 +87,10 @@ func TestStateStoreConfigTemplate(t *testing.T) { require.Contains(t, output, "ss-keep-recent =", "Missing ss-keep-recent") require.Contains(t, output, "ss-prune-interval =", "Missing ss-prune-interval") require.Contains(t, output, "ss-import-num-workers =", "Missing ss-import-num-workers") + require.Contains(t, output, `evm-ss-db-directory = ""`, "Missing evm-ss-db-directory") + require.Contains(t, output, `evm-ss-write-mode = "cosmos_only"`, "Missing or incorrect evm-ss-write-mode") + require.Contains(t, output, `evm-ss-read-mode = "cosmos_only"`, "Missing or incorrect evm-ss-read-mode") + require.Contains(t, output, "evm-ss-separate-dbs = false", "Missing or incorrect evm-ss-separate-dbs") } // TestReceiptStoreConfigTemplate verifies that all field paths in the receipt-store TOML template diff --git a/sei-db/state_db/ss/composite/store.go b/sei-db/state_db/ss/composite/store.go index 41b3c137e9..cbdcbad8a9 100644 --- a/sei-db/state_db/ss/composite/store.go +++ b/sei-db/state_db/ss/composite/store.go @@ -68,7 +68,12 @@ func NewCompositeStateStore( return nil, fmt.Errorf("failed to create EVM store: %w", err) } cs.evmStore = evmStore - logger.Info("EVM state store enabled", "dir", evmDir, "writeMode", ssConfig.WriteMode, "readMode", ssConfig.ReadMode) + logger.Info("EVM state store enabled", + "dir", evmDir, + "writeMode", ssConfig.WriteMode, + "readMode", ssConfig.ReadMode, + "separateDBs", ssConfig.SeparateEVMSubDBs, + ) } changelogPath := utils.GetChangelogPath(dbHome) diff --git a/sei-db/state_db/ss/evm/db_test.go b/sei-db/state_db/ss/evm/db_test.go index 1a31a4b05f..85e2a7c632 100644 --- a/sei-db/state_db/ss/evm/db_test.go +++ b/sei-db/state_db/ss/evm/db_test.go @@ -1,6 +1,8 @@ package evm import ( + "os" + "path/filepath" "testing" "github.com/stretchr/testify/require" @@ -9,6 +11,7 @@ import ( "github.com/sei-protocol/sei-chain/sei-db/config" "github.com/sei-protocol/sei-chain/sei-db/db_engine/types" "github.com/sei-protocol/sei-chain/sei-db/proto" + "github.com/sei-protocol/sei-chain/sei-db/state_db/ss/backend" iavl "github.com/sei-protocol/sei-chain/sei-iavl" ) @@ -29,6 +32,81 @@ func openTestStore(t *testing.T) types.StateStore { return store } +func TestEVMStateStoreDefaultUsesUnifiedDB(t *testing.T) { + dir := t.TempDir() + cfg := testConfig() + + store, err := NewEVMStateStore(dir, cfg) + require.NoError(t, err) + t.Cleanup(func() { _ = store.Close() }) + + require.False(t, store.separateDBs) + require.Len(t, store.managedDBs, 1) + + for _, storeType := range AllEVMStoreTypes() { + require.Same(t, store.managedDBs[0], store.subDBs[storeType]) + _, err := os.Stat(filepath.Join(dir, StoreTypeName(storeType))) + require.ErrorIs(t, err, os.ErrNotExist) + } +} + +func TestEVMStateStoreSeparatedPreservesUnifiedKeyLayout(t *testing.T) { + dir := t.TempDir() + cfg := testConfig() + cfg.SeparateEVMSubDBs = true + + store, err := NewEVMStateStore(dir, cfg) + require.NoError(t, err) + + addr := make([]byte, 20) + addr[0] = 0x11 + slot := make([]byte, 32) + slot[0] = 0x22 + nonceKey := append([]byte{0x0a}, addr...) + storageKey := append([]byte{0x03}, append(addr, slot...)...) + + cs := []*proto.NamedChangeSet{ + { + Name: EVMStoreKey, + Changeset: iavl.ChangeSet{ + Pairs: []*iavl.KVPair{ + {Key: nonceKey, Value: []byte{0x09}}, + {Key: storageKey, Value: []byte("slot_value")}, + }, + }, + }, + } + require.NoError(t, store.ApplyChangesetSync(7, cs)) + require.NoError(t, store.SetLatestVersion(7)) + require.NoError(t, store.Close()) + + opener := backend.ResolveBackend(cfg.Backend) + + nonceDir := filepath.Join(dir, StoreTypeName(StoreNonce)) + nonceDB, err := opener(nonceDir, subDBConfig(cfg, nonceDir)) + require.NoError(t, err) + defer nonceDB.Close() + + nonceVal, err := nonceDB.Get(EVMStoreKey, 7, nonceKey) + require.NoError(t, err) + require.Equal(t, []byte{0x09}, nonceVal) + require.Equal(t, int64(7), nonceDB.GetLatestVersion()) + + _, strippedNonceKey := commonevm.ParseEVMKey(nonceKey) + rewrittenNonceVal, err := nonceDB.Get(StoreTypeName(StoreNonce), 7, strippedNonceKey) + require.NoError(t, err) + require.Nil(t, rewrittenNonceVal, "separated DB should preserve evm store key and full key layout") + + storageDir := filepath.Join(dir, StoreTypeName(StoreStorage)) + storageDB, err := opener(storageDir, subDBConfig(cfg, storageDir)) + require.NoError(t, err) + defer storageDB.Close() + + storageVal, err := storageDB.Get(EVMStoreKey, 7, storageKey) + require.NoError(t, err) + require.Equal(t, []byte("slot_value"), storageVal) +} + // verifyStateStoreInterface ensures EVMStateStore satisfies db_engine.StateStore. func TestEVMStateStoreImplementsInterface(t *testing.T) { var _ types.StateStore = (*EVMStateStore)(nil) diff --git a/sei-db/state_db/ss/evm/store.go b/sei-db/state_db/ss/evm/store.go index a084a8a548..4925da5949 100644 --- a/sei-db/state_db/ss/evm/store.go +++ b/sei-db/state_db/ss/evm/store.go @@ -13,38 +13,51 @@ import ( iavl "github.com/sei-protocol/sei-chain/sei-iavl" ) -// Compile-time check: EVMStateStore implements db_engine.StateStore. var _ types.StateStore = (*EVMStateStore)(nil) -// EVMStateStore manages 5 StateStore instances (one per EVM sub-type) -// and implements db_engine.StateStore. Each sub-DB is the raw MVCC DB engine -// (PebbleDB or RocksDB via build tags). -// -// Key parsing (commonevm.ParseEVMKey) and sub-DB routing are encapsulated here, -// so callers (CompositeStateStore) pass standard StateStore calls with raw EVM keys. +// EVMStateStore manages either a single MVCC DB for all EVM data or one DB per +// EVM sub-type, depending on config. In both modes, the logical store key and +// key encoding remain unchanged. type EVMStateStore struct { - subDBs map[EVMStoreType]types.StateStore - dir string + subDBs map[EVMStoreType]types.StateStore + managedDBs []types.StateStore + dir string + separateDBs bool } -// NewEVMStateStore opens 5 StateStore instances (one per EVM sub-type) using the -// backend resolved from ssConfig.Backend (PebbleDB by default, RocksDB with build tag). +// NewEVMStateStore opens either a single unified MVCC DB for all EVM state +// or one MVCC DB per EVM sub-type. func NewEVMStateStore(dir string, ssConfig config.StateStoreConfig) (*EVMStateStore, error) { opener := backend.ResolveBackend(ssConfig.Backend) store := &EVMStateStore{ - subDBs: make(map[EVMStoreType]types.StateStore, NumEVMStoreTypes), - dir: dir, + subDBs: make(map[EVMStoreType]types.StateStore, NumEVMStoreTypes), + dir: dir, + separateDBs: ssConfig.SeparateEVMSubDBs, } - for _, storeType := range AllEVMStoreTypes() { - dbDir := filepath.Join(dir, StoreTypeName(storeType)) - subCfg := subDBConfig(ssConfig, dbDir) - db, err := opener(dbDir, subCfg) - if err != nil { - _ = store.Close() - return nil, fmt.Errorf("failed to open EVM MVCC DB for %s: %w", StoreTypeName(storeType), err) + if ssConfig.SeparateEVMSubDBs { + for _, storeType := range AllEVMStoreTypes() { + dbDir := filepath.Join(dir, StoreTypeName(storeType)) + subCfg := subDBConfig(ssConfig, dbDir) + db, err := opener(dbDir, subCfg) + if err != nil { + _ = store.Close() + return nil, fmt.Errorf("failed to open EVM MVCC DB for %s: %w", StoreTypeName(storeType), err) + } + store.subDBs[storeType] = db + store.managedDBs = append(store.managedDBs, db) } + return store, nil + } + + cfg := subDBConfig(ssConfig, dir) + db, err := opener(dir, cfg) + if err != nil { + return nil, fmt.Errorf("failed to open unified EVM MVCC DB: %w", err) + } + store.managedDBs = append(store.managedDBs, db) + for _, storeType := range AllEVMStoreTypes() { store.subDBs[storeType] = db } @@ -58,37 +71,43 @@ func subDBConfig(parent config.StateStoreConfig, dbDir string) config.StateStore return cfg } -func (s *EVMStateStore) routeKey(key []byte) (types.StateStore, string, []byte) { - storeType, strippedKey := commonevm.ParseEVMKey(key) +func (s *EVMStateStore) primaryDB() types.StateStore { + if len(s.managedDBs) == 0 { + return nil + } + return s.managedDBs[0] +} + +func (s *EVMStateStore) routeKey(key []byte) types.StateStore { + storeType, _ := commonevm.ParseEVMKey(key) if storeType == StoreEmpty { - return nil, "", nil + return nil } - db := s.subDBs[storeType] - return db, StoreTypeName(storeType), strippedKey + return s.subDBs[storeType] } func (s *EVMStateStore) Get(_ string, version int64, key []byte) ([]byte, error) { - db, subStoreKey, strippedKey := s.routeKey(key) + db := s.routeKey(key) if db == nil { return nil, nil } - return db.Get(subStoreKey, version, strippedKey) + return db.Get(EVMStoreKey, version, key) } func (s *EVMStateStore) Has(_ string, version int64, key []byte) (bool, error) { - db, subStoreKey, strippedKey := s.routeKey(key) + db := s.routeKey(key) if db == nil { return false, nil } - return db.Has(subStoreKey, version, strippedKey) + return db.Has(EVMStoreKey, version, key) } func (s *EVMStateStore) Iterator(_ string, _ int64, _, _ []byte) (types.DBIterator, error) { - return nil, fmt.Errorf("EVMStateStore: cross-type iteration not supported; use individual sub-DB or Cosmos_SS") + return nil, fmt.Errorf("EVMStateStore: cross-type iteration not supported; use Cosmos_SS") } func (s *EVMStateStore) ReverseIterator(_ string, _ int64, _, _ []byte) (types.DBIterator, error) { - return nil, fmt.Errorf("EVMStateStore: cross-type reverse iteration not supported; use individual sub-DB or Cosmos_SS") + return nil, fmt.Errorf("EVMStateStore: cross-type reverse iteration not supported; use Cosmos_SS") } func (s *EVMStateStore) RawIterate(_ string, _ func([]byte, []byte, int64) bool) (bool, error) { @@ -97,7 +116,7 @@ func (s *EVMStateStore) RawIterate(_ string, _ func([]byte, []byte, int64) bool) func (s *EVMStateStore) GetLatestVersion() int64 { var minVersion int64 = -1 - for _, db := range s.subDBs { + for _, db := range s.managedDBs { if v := db.GetLatestVersion(); minVersion < 0 || v < minVersion { minVersion = v } @@ -109,7 +128,7 @@ func (s *EVMStateStore) GetLatestVersion() int64 { } func (s *EVMStateStore) SetLatestVersion(version int64) error { - for _, db := range s.subDBs { + for _, db := range s.managedDBs { if err := db.SetLatestVersion(version); err != nil { return err } @@ -119,7 +138,7 @@ func (s *EVMStateStore) SetLatestVersion(version int64) error { func (s *EVMStateStore) GetEarliestVersion() int64 { var minVersion int64 = -1 - for _, db := range s.subDBs { + for _, db := range s.managedDBs { if v := db.GetEarliestVersion(); minVersion < 0 || v < minVersion { minVersion = v } @@ -131,7 +150,7 @@ func (s *EVMStateStore) GetEarliestVersion() int64 { } func (s *EVMStateStore) SetEarliestVersion(version int64, ignoreVersion bool) error { - for _, db := range s.subDBs { + for _, db := range s.managedDBs { if err := db.SetEarliestVersion(version, ignoreVersion); err != nil { return err } @@ -140,6 +159,18 @@ func (s *EVMStateStore) SetEarliestVersion(version int64, ignoreVersion bool) er } func (s *EVMStateStore) ApplyChangesetSync(version int64, changesets []*proto.NamedChangeSet) error { + if !s.separateDBs { + db := s.primaryDB() + if db == nil { + return nil + } + evmChangesets := filterEVMChangesets(changesets) + if len(evmChangesets) == 0 { + return nil + } + return db.ApplyChangesetSync(version, evmChangesets) + } + grouped := s.groupBySubType(changesets) if len(grouped) == 0 { return nil @@ -148,6 +179,18 @@ func (s *EVMStateStore) ApplyChangesetSync(version int64, changesets []*proto.Na } func (s *EVMStateStore) ApplyChangesetAsync(version int64, changesets []*proto.NamedChangeSet) error { + if !s.separateDBs { + db := s.primaryDB() + if db == nil { + return nil + } + evmChangesets := filterEVMChangesets(changesets) + if len(evmChangesets) == 0 { + return nil + } + return db.ApplyChangesetAsync(version, evmChangesets) + } + grouped := s.groupBySubType(changesets) if len(grouped) == 0 { return nil @@ -162,12 +205,12 @@ func (s *EVMStateStore) groupBySubType(changesets []*proto.NamedChangeSet) map[E continue } for _, kvPair := range cs.Changeset.Pairs { - storeType, strippedKey := commonevm.ParseEVMKey(kvPair.Key) + storeType, _ := commonevm.ParseEVMKey(kvPair.Key) if storeType == StoreEmpty { continue } grouped[storeType] = append(grouped[storeType], &iavl.KVPair{ - Key: strippedKey, + Key: kvPair.Key, Value: kvPair.Value, Delete: kvPair.Delete, }) @@ -210,10 +253,9 @@ func (s *EVMStateStore) applyToSubDB(storeType EVMStoreType, version int64, pair if db == nil { return nil } - subStoreKey := StoreTypeName(storeType) cs := []*proto.NamedChangeSet{ { - Name: subStoreKey, + Name: EVMStoreKey, Changeset: iavl.ChangeSet{Pairs: pairs}, }, } @@ -223,8 +265,24 @@ func (s *EVMStateStore) applyToSubDB(storeType EVMStoreType, version int64, pair return db.ApplyChangesetSync(version, cs) } -// Import parses incoming snapshot nodes, routes EVM keys to sub-DBs via ApplyChangesetSync. func (s *EVMStateStore) Import(version int64, ch <-chan types.SnapshotNode) error { + if !s.separateDBs { + db := s.primaryDB() + if db == nil { + return nil + } + filtered := make(chan types.SnapshotNode, 100) + go func() { + defer close(filtered) + for node := range ch { + if node.StoreKey == EVMStoreKey { + filtered <- node + } + } + }() + return db.Import(version, filtered) + } + const flushThreshold = 10000 grouped := make(map[EVMStoreType][]*iavl.KVPair, NumEVMStoreTypes) pending := 0 @@ -242,12 +300,12 @@ func (s *EVMStateStore) Import(version int64, ch <-chan types.SnapshotNode) erro } for node := range ch { - storeType, strippedKey := commonevm.ParseEVMKey(node.Key) + storeType, _ := commonevm.ParseEVMKey(node.Key) if storeType == StoreEmpty { continue } grouped[storeType] = append(grouped[storeType], &iavl.KVPair{ - Key: strippedKey, + Key: node.Key, Value: node.Value, }) pending++ @@ -257,20 +315,18 @@ func (s *EVMStateStore) Import(version int64, ch <-chan types.SnapshotNode) erro } } } - return flush() } -// Prune removes old versions from all sub-DBs in parallel. func (s *EVMStateStore) Prune(version int64) error { - if len(s.subDBs) == 0 { + if len(s.managedDBs) == 0 { return nil } var wg sync.WaitGroup - errCh := make(chan error, len(s.subDBs)) + errCh := make(chan error, len(s.managedDBs)) - for _, db := range s.subDBs { + for _, db := range s.managedDBs { wg.Add(1) go func(db types.StateStore) { defer wg.Done() @@ -291,10 +347,20 @@ func (s *EVMStateStore) Prune(version int64) error { func (s *EVMStateStore) Close() error { var lastErr error - for _, db := range s.subDBs { + for _, db := range s.managedDBs { if err := db.Close(); err != nil { lastErr = err } } return lastErr } + +func filterEVMChangesets(changesets []*proto.NamedChangeSet) []*proto.NamedChangeSet { + filtered := make([]*proto.NamedChangeSet, 0, len(changesets)) + for _, cs := range changesets { + if cs.Name == EVMStoreKey { + filtered = append(filtered, cs) + } + } + return filtered +} diff --git a/sei-db/state_db/ss/evm/types.go b/sei-db/state_db/ss/evm/types.go index c67077b719..8c2413ea0c 100644 --- a/sei-db/state_db/ss/evm/types.go +++ b/sei-db/state_db/ss/evm/types.go @@ -11,7 +11,7 @@ const EVMStoreKey = commonevm.EVMStoreKey // Alias to EVMKeyKind from common/evm - use commonevm.ParseEVMKey for routing. type EVMStoreType = commonevm.EVMKeyKind -// NumEVMStoreTypes is the number of active EVM store types with separate DBs. +// NumEVMStoreTypes is the number of active EVM store key namespaces. // Used for pre-allocating maps. Types: Nonce, CodeHash, Code, Storage, Legacy. const NumEVMStoreTypes = 5