From 7624ca749ff9db9a9092b252262fb46877fbfe5c Mon Sep 17 00:00:00 2001 From: yzang2019 Date: Mon, 9 Mar 2026 07:28:55 -0700 Subject: [PATCH 1/2] Add config to enable lattice hash --- app/seidb.go | 3 + app/seidb_test.go | 2 + sei-cosmos/server/config/config.go | 9 +- sei-db/config/sc_config.go | 14 ++- sei-db/config/toml.go | 4 + sei-db/state_db/sc/composite/store.go | 45 +++++++- sei-db/state_db/sc/composite/store_test.go | 118 +++++++++++++++++++++ sei-db/state_db/sc/flatkv/api.go | 3 + sei-db/state_db/sc/flatkv/store.go | 6 ++ 9 files changed, 191 insertions(+), 13 deletions(-) diff --git a/app/seidb.go b/app/seidb.go index 448de9960f..ed1fdb7353 100644 --- a/app/seidb.go +++ b/app/seidb.go @@ -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" @@ -120,6 +121,8 @@ func parseSCConfigs(appOpts servertypes.AppOptions) config.StateCommitConfig { scConfig.ReadMode = parsedRM } + scConfig.EnableLatticeHash = cast.ToBool(appOpts.Get(FlagSCEnableLatticeHash)) + if v := appOpts.Get(FlagSCHistoricalProofMaxInFlight); v != nil { scConfig.HistoricalProofMaxInFlight = cast.ToInt(v) } diff --git a/app/seidb_test.go b/app/seidb_test.go index 94c7db5257..0969eb6d69 100644 --- a/app/seidb_test.go +++ b/app/seidb_test.go @@ -32,6 +32,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: diff --git a/sei-cosmos/server/config/config.go b/sei-cosmos/server/config/config.go index 7e9b2e383a..b3ac1b746c 100644 --- a/sei-cosmos/server/config/config.go +++ b/sei-cosmos/server/config/config.go @@ -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"), diff --git a/sei-db/config/sc_config.go b/sei-db/config/sc_config.go index b32995a77d..c0871029bc 100644 --- a/sei-db/config/sc_config.go +++ b/sei-db/config/sc_config.go @@ -39,6 +39,9 @@ type StateCommitConfig struct { // defaults to cosmos_only 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"` + // MemIAVLConfig is the configuration for the MemIAVL (Cosmos) backend MemIAVLConfig memiavl.Config @@ -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, diff --git a/sei-db/config/toml.go b/sei-db/config/toml.go index 8d972cf97f..e20cac5ca0 100644 --- a/sei-db/config/toml.go +++ b/sei-db/config/toml.go @@ -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 }} diff --git a/sei-db/state_db/sc/composite/store.go b/sei-db/state_db/sc/composite/store.go index 4d750d4437..55544239b1 100644 --- a/sei-db/state_db/sc/composite/store.go +++ b/sei-db/state_db/sc/composite/store.go @@ -221,14 +221,51 @@ func (cs *CompositeCommitStore) GetEarliestVersion() (int64, error) { // 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() + commitInfo := cs.cosmosCommitter.WorkingCommitInfo() + version := commitInfo.Version + if cs.evmCommitter != nil && cs.config.EnableLatticeHash { + hash := cs.evmCommitter.RootHash() + evmStoreInfo := proto.StoreInfo{ + Name: "evm", + CommitId: proto.CommitID{ + Version: version, + Hash: hash, + }, + } + combined := make([]proto.StoreInfo, len(commitInfo.StoreInfos)+1) + copy(combined, commitInfo.StoreInfos) + combined[len(combined)-1] = evmStoreInfo + commitInfo = &proto.CommitInfo{ + Version: version, + StoreInfos: combined, + } + } + return commitInfo } // LastCommitInfo returns the last commit info func (cs *CompositeCommitStore) LastCommitInfo() *proto.CommitInfo { - // TODO: Need to combine hash for cosmos and evm - return cs.cosmosCommitter.LastCommitInfo() + lastCommitInfo := cs.cosmosCommitter.LastCommitInfo() + if cs.evmCommitter != nil && cs.config.EnableLatticeHash { + version := lastCommitInfo.Version + hash := cs.evmCommitter.CommittedRootHash() + evmStoreInfo := proto.StoreInfo{ + Name: "evm", + CommitId: proto.CommitID{ + Version: version, + Hash: hash, + }, + } + // Copy to avoid mutating memiavl's internal lastCommitInfo + combined := make([]proto.StoreInfo, len(lastCommitInfo.StoreInfos)+1) + copy(combined, lastCommitInfo.StoreInfos) + combined[len(combined)-1] = evmStoreInfo + lastCommitInfo = &proto.CommitInfo{ + Version: version, + StoreInfos: combined, + } + } + return lastCommitInfo } // GetChildStoreByName returns the underlying child store by module name. diff --git a/sei-db/state_db/sc/composite/store_test.go b/sei-db/state_db/sc/composite/store_test.go index e4ed33ebe7..296921aecd 100644 --- a/sei-db/state_db/sc/composite/store_test.go +++ b/sei-db/state_db/sc/composite/store_test.go @@ -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" @@ -153,6 +154,123 @@ func TestWorkingAndLastCommitInfo(t *testing.T) { require.Equal(t, int64(1), lastInfo.Version) } +func TestEnableLatticeHash(t *testing.T) { + evmStorageKey := evm.BuildMemIAVLEVMKey(evm.EVMKeyStorage, + append(make([]byte, 20), make([]byte, 32)...)) + + makeChangesets := func() []*proto.NamedChangeSet { + return []*proto.NamedChangeSet{ + { + Name: "test", + Changeset: iavl.ChangeSet{ + Pairs: []*iavl.KVPair{ + {Key: []byte("key"), Value: []byte("value")}, + }, + }, + }, + { + Name: EVMStoreName, + Changeset: iavl.ChangeSet{ + Pairs: []*iavl.KVPair{ + {Key: evmStorageKey, Value: []byte{0x42}}, + }, + }, + }, + } + } + + t.Run("disabled uses cosmos-only hash", func(t *testing.T) { + dir := t.TempDir() + cfg := config.DefaultStateCommitConfig() + cfg.WriteMode = config.DualWrite + cfg.EnableLatticeHash = false + + 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() + + require.NoError(t, cs.ApplyChangeSets(makeChangesets())) + + // Compute expected hashes from cosmos committer + expectedWorking := cs.cosmosCommitter.WorkingCommitInfo() + + workingInfo := cs.WorkingCommitInfo() + require.Equal(t, expectedWorking.Version, workingInfo.Version) + require.Equal(t, len(expectedWorking.StoreInfos), len(workingInfo.StoreInfos)) + for i, si := range expectedWorking.StoreInfos { + require.Equal(t, si.Name, workingInfo.StoreInfos[i].Name) + require.Equal(t, si.CommitId.Hash, workingInfo.StoreInfos[i].CommitId.Hash) + } + + _, err = cs.Commit() + require.NoError(t, err) + + expectedLast := cs.cosmosCommitter.LastCommitInfo() + + lastInfo := cs.LastCommitInfo() + require.Equal(t, expectedLast.Version, lastInfo.Version) + require.Equal(t, len(expectedLast.StoreInfos), len(lastInfo.StoreInfos)) + for i, si := range expectedLast.StoreInfos { + require.Equal(t, si.Name, lastInfo.StoreInfos[i].Name) + require.Equal(t, si.CommitId.Hash, lastInfo.StoreInfos[i].CommitId.Hash) + } + }) + + t.Run("enabled appends flatkv lattice hash", func(t *testing.T) { + dir := t.TempDir() + cfg := config.DefaultStateCommitConfig() + cfg.WriteMode = config.DualWrite + cfg.EnableLatticeHash = true + + 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() + + require.NoError(t, cs.ApplyChangeSets(makeChangesets())) + + // Compute expected hashes before calling composite methods + expectedCosmosWorking := cs.cosmosCommitter.WorkingCommitInfo() + expectedEvmWorkingHash := cs.evmCommitter.RootHash() + require.Len(t, expectedEvmWorkingHash, 32) + + workingInfo := cs.WorkingCommitInfo() + require.Equal(t, len(expectedCosmosWorking.StoreInfos)+1, len(workingInfo.StoreInfos)) + for i, si := range expectedCosmosWorking.StoreInfos { + require.Equal(t, si.Name, workingInfo.StoreInfos[i].Name) + require.Equal(t, si.CommitId.Hash, workingInfo.StoreInfos[i].CommitId.Hash) + } + latticeEntry := workingInfo.StoreInfos[len(workingInfo.StoreInfos)-1] + require.Equal(t, "evm", latticeEntry.Name) + require.Equal(t, expectedEvmWorkingHash, latticeEntry.CommitId.Hash) + require.Equal(t, workingInfo.Version, latticeEntry.CommitId.Version) + + _, err = cs.Commit() + require.NoError(t, err) + + // Compute expected committed hashes + expectedCosmosLast := cs.cosmosCommitter.LastCommitInfo() + expectedEvmCommittedHash := cs.evmCommitter.CommittedRootHash() + require.Len(t, expectedEvmCommittedHash, 32) + require.Equal(t, expectedEvmWorkingHash, expectedEvmCommittedHash, + "committed hash should equal working hash after commit") + + lastInfo := cs.LastCommitInfo() + require.Equal(t, len(expectedCosmosLast.StoreInfos)+1, 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) + } + lastLatticeEntry := lastInfo.StoreInfos[len(lastInfo.StoreInfos)-1] + require.Equal(t, "evm", lastLatticeEntry.Name) + require.Equal(t, expectedEvmCommittedHash, lastLatticeEntry.CommitId.Hash) + require.Equal(t, lastInfo.Version, lastLatticeEntry.CommitId.Version) + }) +} + func TestRollback(t *testing.T) { dir := t.TempDir() cfg := config.DefaultStateCommitConfig() diff --git a/sei-db/state_db/sc/flatkv/api.go b/sei-db/state_db/sc/flatkv/api.go index dce5fc4c01..3bc76e9785 100644 --- a/sei-db/state_db/sc/flatkv/api.go +++ b/sei-db/state_db/sc/flatkv/api.go @@ -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 diff --git a/sei-db/state_db/sc/flatkv/store.go b/sei-db/state_db/sc/flatkv/store.go index 5dabbbba1c..a833e60f18 100644 --- a/sei-db/state_db/sc/flatkv/store.go +++ b/sei-db/state_db/sc/flatkv/store.go @@ -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. From 25491f6e02af7f036e7f6ac939e06ba5177c3383 Mon Sep 17 00:00:00 2001 From: yzang2019 Date: Mon, 9 Mar 2026 08:43:27 -0700 Subject: [PATCH 2/2] Address comments --- sei-db/config/sc_config.go | 8 +- sei-db/state_db/sc/composite/store.go | 69 +++---- sei-db/state_db/sc/composite/store_test.go | 216 ++++++++++++--------- 3 files changed, 155 insertions(+), 138 deletions(-) diff --git a/sei-db/config/sc_config.go b/sei-db/config/sc_config.go index c0871029bc..830cc7ab48 100644 --- a/sei-db/config/sc_config.go +++ b/sei-db/config/sc_config.go @@ -32,12 +32,12 @@ 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"` @@ -78,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 } diff --git a/sei-db/state_db/sc/composite/store.go b/sei-db/state_db/sc/composite/store.go index 55544239b1..d0e8b5a4c6 100644 --- a/sei-db/state_db/sc/composite/store.go +++ b/sei-db/state_db/sc/composite/store.go @@ -219,53 +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 { - commitInfo := cs.cosmosCommitter.WorkingCommitInfo() - version := commitInfo.Version - if cs.evmCommitter != nil && cs.config.EnableLatticeHash { - hash := cs.evmCommitter.RootHash() - evmStoreInfo := proto.StoreInfo{ - Name: "evm", - CommitId: proto.CommitID{ - Version: version, - Hash: hash, - }, - } - combined := make([]proto.StoreInfo, len(commitInfo.StoreInfos)+1) - copy(combined, commitInfo.StoreInfos) - combined[len(combined)-1] = evmStoreInfo - commitInfo = &proto.CommitInfo{ - Version: version, - StoreInfos: combined, - } + ci := cs.cosmosCommitter.WorkingCommitInfo() + if cs.evmCommitter != nil { + return cs.appendEvmLatticeHash(ci, cs.evmCommitter.RootHash()) } - return commitInfo + return ci } // LastCommitInfo returns the last commit info func (cs *CompositeCommitStore) LastCommitInfo() *proto.CommitInfo { - lastCommitInfo := cs.cosmosCommitter.LastCommitInfo() - if cs.evmCommitter != nil && cs.config.EnableLatticeHash { - version := lastCommitInfo.Version - hash := cs.evmCommitter.CommittedRootHash() - evmStoreInfo := proto.StoreInfo{ - Name: "evm", - CommitId: proto.CommitID{ - Version: version, - Hash: hash, - }, - } - // Copy to avoid mutating memiavl's internal lastCommitInfo - combined := make([]proto.StoreInfo, len(lastCommitInfo.StoreInfos)+1) - copy(combined, lastCommitInfo.StoreInfos) - combined[len(combined)-1] = evmStoreInfo - lastCommitInfo = &proto.CommitInfo{ - Version: version, - StoreInfos: combined, - } + ci := cs.cosmosCommitter.LastCommitInfo() + if cs.evmCommitter != nil { + return cs.appendEvmLatticeHash(ci, cs.evmCommitter.CommittedRootHash()) } - return lastCommitInfo + return ci } // GetChildStoreByName returns the underlying child store by module name. diff --git a/sei-db/state_db/sc/composite/store_test.go b/sei-db/state_db/sc/composite/store_test.go index 296921aecd..551b505a05 100644 --- a/sei-db/state_db/sc/composite/store_test.go +++ b/sei-db/state_db/sc/composite/store_test.go @@ -154,17 +154,18 @@ func TestWorkingAndLastCommitInfo(t *testing.T) { require.Equal(t, int64(1), lastInfo.Version) } -func TestEnableLatticeHash(t *testing.T) { - evmStorageKey := evm.BuildMemIAVLEVMKey(evm.EVMKeyStorage, - append(make([]byte, 20), make([]byte, 32)...)) +func TestLatticeHashCommitInfo(t *testing.T) { + addr := [20]byte{0xAA} + slot := [32]byte{0xBB} + evmStorageKey := evm.BuildMemIAVLEVMKey(evm.EVMKeyStorage, append(addr[:], slot[:]...)) - makeChangesets := func() []*proto.NamedChangeSet { + makeChangesets := func(round byte) []*proto.NamedChangeSet { return []*proto.NamedChangeSet{ { Name: "test", Changeset: iavl.ChangeSet{ Pairs: []*iavl.KVPair{ - {Key: []byte("key"), Value: []byte("value")}, + {Key: []byte("key"), Value: []byte{round}}, }, }, }, @@ -172,103 +173,128 @@ func TestEnableLatticeHash(t *testing.T) { Name: EVMStoreName, Changeset: iavl.ChangeSet{ Pairs: []*iavl.KVPair{ - {Key: evmStorageKey, Value: []byte{0x42}}, + {Key: evmStorageKey, Value: []byte{round}}, }, }, }, } } - t.Run("disabled uses cosmos-only hash", func(t *testing.T) { - dir := t.TempDir() - cfg := config.DefaultStateCommitConfig() - cfg.WriteMode = config.DualWrite - cfg.EnableLatticeHash = false - - 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() - - require.NoError(t, cs.ApplyChangeSets(makeChangesets())) - - // Compute expected hashes from cosmos committer - expectedWorking := cs.cosmosCommitter.WorkingCommitInfo() - - workingInfo := cs.WorkingCommitInfo() - require.Equal(t, expectedWorking.Version, workingInfo.Version) - require.Equal(t, len(expectedWorking.StoreInfos), len(workingInfo.StoreInfos)) - for i, si := range expectedWorking.StoreInfos { - require.Equal(t, si.Name, workingInfo.StoreInfos[i].Name) - require.Equal(t, si.CommitId.Hash, workingInfo.StoreInfos[i].CommitId.Hash) - } - - _, err = cs.Commit() - require.NoError(t, err) - - expectedLast := cs.cosmosCommitter.LastCommitInfo() - - lastInfo := cs.LastCommitInfo() - require.Equal(t, expectedLast.Version, lastInfo.Version) - require.Equal(t, len(expectedLast.StoreInfos), len(lastInfo.StoreInfos)) - for i, si := range expectedLast.StoreInfos { - require.Equal(t, si.Name, lastInfo.StoreInfos[i].Name) - require.Equal(t, si.CommitId.Hash, lastInfo.StoreInfos[i].CommitId.Hash) - } - }) - - t.Run("enabled appends flatkv lattice hash", func(t *testing.T) { - dir := t.TempDir() - cfg := config.DefaultStateCommitConfig() - cfg.WriteMode = config.DualWrite - cfg.EnableLatticeHash = true - - 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() - - require.NoError(t, cs.ApplyChangeSets(makeChangesets())) - - // Compute expected hashes before calling composite methods - expectedCosmosWorking := cs.cosmosCommitter.WorkingCommitInfo() - expectedEvmWorkingHash := cs.evmCommitter.RootHash() - require.Len(t, expectedEvmWorkingHash, 32) - - workingInfo := cs.WorkingCommitInfo() - require.Equal(t, len(expectedCosmosWorking.StoreInfos)+1, len(workingInfo.StoreInfos)) - for i, si := range expectedCosmosWorking.StoreInfos { - require.Equal(t, si.Name, workingInfo.StoreInfos[i].Name) - require.Equal(t, si.CommitId.Hash, workingInfo.StoreInfos[i].CommitId.Hash) - } - latticeEntry := workingInfo.StoreInfos[len(workingInfo.StoreInfos)-1] - require.Equal(t, "evm", latticeEntry.Name) - require.Equal(t, expectedEvmWorkingHash, latticeEntry.CommitId.Hash) - require.Equal(t, workingInfo.Version, latticeEntry.CommitId.Version) - - _, err = cs.Commit() - require.NoError(t, err) + 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}, + } - // Compute expected committed hashes - expectedCosmosLast := cs.cosmosCommitter.LastCommitInfo() - expectedEvmCommittedHash := cs.evmCommitter.CommittedRootHash() - require.Len(t, expectedEvmCommittedHash, 32) - require.Equal(t, expectedEvmWorkingHash, expectedEvmCommittedHash, - "committed hash should equal working hash after commit") - - lastInfo := cs.LastCommitInfo() - require.Equal(t, len(expectedCosmosLast.StoreInfos)+1, 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) - } - lastLatticeEntry := lastInfo.StoreInfos[len(lastInfo.StoreInfos)-1] - require.Equal(t, "evm", lastLatticeEntry.Name) - require.Equal(t, expectedEvmCommittedHash, lastLatticeEntry.CommitId.Hash) - require.Equal(t, lastInfo.Version, lastLatticeEntry.CommitId.Version) - }) + 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) {