Skip to content
Merged
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
3 changes: 3 additions & 0 deletions app/seidb.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ const (
FlagSCHistoricalProofBurst = "state-commit.sc-historical-proof-burst"
FlagSCWriteMode = "state-commit.sc-write-mode"
FlagSCReadMode = "state-commit.sc-read-mode"
FlagSCEnableLatticeHash = "state-commit.sc-enable-lattice-hash"

// SS Store configs
FlagSSEnable = "state-store.ss-enable"
Expand Down Expand Up @@ -120,6 +121,8 @@ func parseSCConfigs(appOpts servertypes.AppOptions) config.StateCommitConfig {
scConfig.ReadMode = parsedRM
}

scConfig.EnableLatticeHash = cast.ToBool(appOpts.Get(FlagSCEnableLatticeHash))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Setting EnableLatticeHash = true with WriteMode = CosmosOnlyWrite is a silent no-op because evmCommitter is nil. The Validate() method can catch this

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.

True, but I think this is probably fine, we don't have to panic here. EnableLatticeHash only take effects when FlatKV is being used, which is expected.


if v := appOpts.Get(FlagSCHistoricalProofMaxInFlight); v != nil {
scConfig.HistoricalProofMaxInFlight = cast.ToInt(v)
}
Expand Down
2 changes: 2 additions & 0 deletions app/seidb_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ func (t TestSeiDBAppOpts) Get(s string) interface{} {
return defaultSCConfig.MemIAVLConfig.SnapshotPrefetchThreshold
case FlagSCSnapshotWriteRateMBps:
return defaultSCConfig.MemIAVLConfig.SnapshotWriteRateMBps
case FlagSCEnableLatticeHash:
return defaultSCConfig.EnableLatticeHash
case FlagSSEnable:
return defaultSSConfig.Enable
case FlagSSBackend:
Expand Down
9 changes: 5 additions & 4 deletions sei-cosmos/server/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -406,10 +406,11 @@ func GetConfig(v *viper.Viper) (Config, error) {
SnapshotDirectory: v.GetString("state-sync.snapshot-directory"),
},
StateCommit: config.StateCommitConfig{
Enable: v.GetBool("state-commit.sc-enable"),
Directory: v.GetString("state-commit.sc-directory"),
WriteMode: config.WriteMode(v.GetString("state-commit.sc-write-mode")),
ReadMode: config.ReadMode(v.GetString("state-commit.sc-read-mode")),
Enable: v.GetBool("state-commit.sc-enable"),
Directory: v.GetString("state-commit.sc-directory"),
WriteMode: config.WriteMode(v.GetString("state-commit.sc-write-mode")),
ReadMode: config.ReadMode(v.GetString("state-commit.sc-read-mode")),
EnableLatticeHash: v.GetBool("state-commit.sc-enable-lattice-hash"),
MemIAVLConfig: memiavl.Config{
AsyncCommitBuffer: v.GetInt("state-commit.sc-async-commit-buffer"),
SnapshotKeepRecent: v.GetUint32("state-commit.sc-keep-recent"),
Expand Down
22 changes: 13 additions & 9 deletions sei-db/config/sc_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,15 @@ type StateCommitConfig struct {
// WriteMode defines the write routing mode for EVM data
// Valid values: cosmos_only, dual_write, split_write, evm_only
// defaults to cosmos_only
WriteMode WriteMode `mapstructure:"write_mode"`
WriteMode WriteMode `mapstructure:"write-mode"`

// ReadMode defines the read routing mode for EVM data
// Valid values: cosmos_only, evm_first, split_read
// defaults to cosmos_only
ReadMode ReadMode `mapstructure:"read_mode"`
ReadMode ReadMode `mapstructure:"read-mode"`

// EnableLatticeHash controls whether lattice hash will be participating in final app hash or not
EnableLatticeHash bool `mapstructure:"enable-lattice-hash"`

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Data format:

EnableLatticeHash bool mapstructure:"enable-lattice-hash"

other fields:
WriteMode WriteMode mapstructure:"write_mode"

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.

Good catch, will fix


// MemIAVLConfig is the configuration for the MemIAVL (Cosmos) backend
MemIAVLConfig memiavl.Config
Expand All @@ -59,11 +62,12 @@ type StateCommitConfig struct {
// DefaultStateCommitConfig returns the default StateCommitConfig
func DefaultStateCommitConfig() StateCommitConfig {
return StateCommitConfig{
Enable: true,
WriteMode: CosmosOnlyWrite,
ReadMode: CosmosOnlyRead,
MemIAVLConfig: memiavl.DefaultConfig(),
FlatKVConfig: flatkv.DefaultConfig(),
Enable: true,
WriteMode: CosmosOnlyWrite,
ReadMode: CosmosOnlyRead,
EnableLatticeHash: false,
MemIAVLConfig: memiavl.DefaultConfig(),
FlatKVConfig: flatkv.DefaultConfig(),

HistoricalProofMaxInFlight: DefaultSCHistoricalProofMaxInFlight,
HistoricalProofRateLimit: DefaultSCHistoricalProofRateLimit,
Expand All @@ -74,10 +78,10 @@ func DefaultStateCommitConfig() StateCommitConfig {
// Validate checks if the StateCommitConfig is valid
func (c StateCommitConfig) Validate() error {
if !c.WriteMode.IsValid() {
return fmt.Errorf("invalid write_mode: %s", c.WriteMode)
return fmt.Errorf("invalid write-mode: %s", c.WriteMode)
}
if !c.ReadMode.IsValid() {
return fmt.Errorf("invalid read_mode: %s", c.ReadMode)
return fmt.Errorf("invalid read-mode: %s", c.ReadMode)
}
return nil
}
4 changes: 4 additions & 0 deletions sei-db/config/toml.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ sc-write-mode = "{{ .StateCommit.WriteMode }}"
# 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
36 changes: 32 additions & 4 deletions sei-db/state_db/sc/composite/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -219,16 +219,44 @@ func (cs *CompositeCommitStore) GetEarliestVersion() (int64, error) {
return cs.cosmosCommitter.GetEarliestVersion()
}

// appendEvmLatticeHash returns a new CommitInfo with the EVM lattice hash
// appended, without mutating the original. Returns the original unchanged
// when lattice hashing is disabled.
func (cs *CompositeCommitStore) appendEvmLatticeHash(ci *proto.CommitInfo, evmHash []byte) *proto.CommitInfo {
if !cs.config.EnableLatticeHash {
return ci
}
combined := make([]proto.StoreInfo, len(ci.StoreInfos)+1)
copy(combined, ci.StoreInfos)
combined[len(combined)-1] = proto.StoreInfo{
Name: "evm_lattice",
CommitId: proto.CommitID{
Version: ci.Version,
Hash: evmHash,
},
}
return &proto.CommitInfo{
Version: ci.Version,
StoreInfos: combined,
}
}

// WorkingCommitInfo returns the working commit info
func (cs *CompositeCommitStore) WorkingCommitInfo() *proto.CommitInfo {
// TODO: Need to combine hash for cosmos and evm
return cs.cosmosCommitter.WorkingCommitInfo()
ci := cs.cosmosCommitter.WorkingCommitInfo()
if cs.evmCommitter != nil {
return cs.appendEvmLatticeHash(ci, cs.evmCommitter.RootHash())
}
return ci
}

// LastCommitInfo returns the last commit info
func (cs *CompositeCommitStore) LastCommitInfo() *proto.CommitInfo {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

some code duplication between WorkingCommitInfo and LastCommitInfo, consider extract a helper?

@yzang2019 yzang2019 Mar 9, 2026 •

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.

Will look into that, good suggestion!

// TODO: Need to combine hash for cosmos and evm
return cs.cosmosCommitter.LastCommitInfo()
ci := cs.cosmosCommitter.LastCommitInfo()
if cs.evmCommitter != nil {
return cs.appendEvmLatticeHash(ci, cs.evmCommitter.CommittedRootHash())
}
return ci
}

// GetChildStoreByName returns the underlying child store by module name.
Expand Down
144 changes: 144 additions & 0 deletions sei-db/state_db/sc/composite/store_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (

"github.com/stretchr/testify/require"

"github.com/sei-protocol/sei-chain/sei-db/common/evm"
"github.com/sei-protocol/sei-chain/sei-db/common/logger"
"github.com/sei-protocol/sei-chain/sei-db/config"
"github.com/sei-protocol/sei-chain/sei-db/proto"
Expand Down Expand Up @@ -153,6 +154,149 @@ func TestWorkingAndLastCommitInfo(t *testing.T) {
require.Equal(t, int64(1), lastInfo.Version)
}

func TestLatticeHashCommitInfo(t *testing.T) {
addr := [20]byte{0xAA}
slot := [32]byte{0xBB}
evmStorageKey := evm.BuildMemIAVLEVMKey(evm.EVMKeyStorage, append(addr[:], slot[:]...))

makeChangesets := func(round byte) []*proto.NamedChangeSet {
return []*proto.NamedChangeSet{
{
Name: "test",
Changeset: iavl.ChangeSet{
Pairs: []*iavl.KVPair{
{Key: []byte("key"), Value: []byte{round}},
},
},
},
{
Name: EVMStoreName,
Changeset: iavl.ChangeSet{
Pairs: []*iavl.KVPair{
{Key: evmStorageKey, Value: []byte{round}},
},
},
},
}
}

tests := []struct {
name string
writeMode config.WriteMode
enableLattice bool
expectLattice bool
}{
{"CosmosOnly/lattice_off", config.CosmosOnlyWrite, false, false},
{"CosmosOnly/lattice_on", config.CosmosOnlyWrite, true, false},
{"DualWrite/lattice_off", config.DualWrite, false, false},
{"DualWrite/lattice_on", config.DualWrite, true, true},
{"SplitWrite/lattice_off", config.SplitWrite, false, false},
{"SplitWrite/lattice_on", config.SplitWrite, true, true},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
dir := t.TempDir()
cfg := config.DefaultStateCommitConfig()
cfg.WriteMode = tt.writeMode
cfg.EnableLatticeHash = tt.enableLattice

cs := NewCompositeCommitStore(t.Context(), dir, logger.NewNopLogger(), cfg)
cs.Initialize([]string{"test", EVMStoreName})
_, err := cs.LoadVersion(0, false)
require.NoError(t, err)
defer cs.Close()

var prevLastHash []byte

for round := byte(1); round <= 3; round++ {
require.NoError(t, cs.ApplyChangeSets(makeChangesets(round)))

// --- Working commit info ---
expectedCosmos := cs.cosmosCommitter.WorkingCommitInfo()
var expectedEvmHash []byte
if tt.expectLattice {
expectedEvmHash = cs.evmCommitter.RootHash()
}

workingInfo := cs.WorkingCommitInfo()
cosmosCount := len(expectedCosmos.StoreInfos)
if tt.expectLattice {
require.Equal(t, cosmosCount+1, len(workingInfo.StoreInfos))
} else {
require.Equal(t, cosmosCount, len(workingInfo.StoreInfos))
}
for i, si := range expectedCosmos.StoreInfos {
require.Equal(t, si.Name, workingInfo.StoreInfos[i].Name)
require.Equal(t, si.CommitId.Hash, workingInfo.StoreInfos[i].CommitId.Hash)
}
if tt.expectLattice {
entry := workingInfo.StoreInfos[len(workingInfo.StoreInfos)-1]
require.Equal(t, "evm_lattice", entry.Name)
require.Equal(t, expectedEvmHash, entry.CommitId.Hash)
require.Equal(t, workingInfo.Version, entry.CommitId.Version)

// Verify no duplicate names — important for app hash merkle tree
names := make(map[string]int)
for _, si := range workingInfo.StoreInfos {
names[si.Name]++
}
for name, count := range names {
require.Equal(t, 1, count, "duplicate store name %q in WorkingCommitInfo", name)
}
}

// --- Commit ---
_, err = cs.Commit()
require.NoError(t, err)

// --- Last commit info ---
expectedCosmosLast := cs.cosmosCommitter.LastCommitInfo()
var expectedEvmCommitted []byte
if tt.expectLattice {
expectedEvmCommitted = cs.evmCommitter.CommittedRootHash()
require.Equal(t, expectedEvmHash, expectedEvmCommitted)
}

lastInfo := cs.LastCommitInfo()
require.Equal(t, int64(round), lastInfo.Version)
cosmosLastCount := len(expectedCosmosLast.StoreInfos)
if tt.expectLattice {
require.Equal(t, cosmosLastCount+1, len(lastInfo.StoreInfos))
} else {
require.Equal(t, cosmosLastCount, len(lastInfo.StoreInfos))
}
for i, si := range expectedCosmosLast.StoreInfos {
require.Equal(t, si.Name, lastInfo.StoreInfos[i].Name)
require.Equal(t, si.CommitId.Hash, lastInfo.StoreInfos[i].CommitId.Hash)
}
if tt.expectLattice {
entry := lastInfo.StoreInfos[len(lastInfo.StoreInfos)-1]
require.Equal(t, "evm_lattice", entry.Name)
require.Equal(t, expectedEvmCommitted, entry.CommitId.Hash)
require.Equal(t, lastInfo.Version, entry.CommitId.Version)

// Verify no duplicate names — important for app hash merkle tree
names := make(map[string]int)
for _, si := range lastInfo.StoreInfos {
names[si.Name]++
}
for name, count := range names {
require.Equal(t, 1, count, "duplicate store name %q in LastCommitInfo", name)
}

// Hash must change between rounds since data differs
if prevLastHash != nil {
require.NotEqual(t, prevLastHash, entry.CommitId.Hash,
"lattice hash should change across commits")
}
prevLastHash = entry.CommitId.Hash
}
}
})
}
}

func TestRollback(t *testing.T) {
dir := t.TempDir()
cfg := config.DefaultStateCommitConfig()
Expand Down
3 changes: 3 additions & 0 deletions sei-db/state_db/sc/flatkv/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ type Store interface {
// raw LtHash vector.
RootHash() []byte

// CommittedRootHash returns the 32-byte checksum of the last committed LtHash.
CommittedRootHash() []byte

// Version returns the latest committed version.
Version() int64

Expand Down
6 changes: 6 additions & 0 deletions sei-db/state_db/sc/flatkv/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,12 @@ func (s *CommitStore) RootHash() []byte {
return checksum[:]
}

// CommittedRootHash returns the Blake3-256 digest of the last committed LtHash.
func (s *CommitStore) CommittedRootHash() []byte {
checksum := s.committedLtHash.Checksum()
return checksum[:]
}

func (s *CommitStore) Importer(version int64) (types.Importer, error) {
// rootmulti.Restore closes the store before creating an importer.
// Reopen the DBs so the importer can write data.
Expand Down
Loading