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
34 changes: 33 additions & 1 deletion sei-db/state_db/sc/composite/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -444,7 +444,8 @@ func (cs *CompositeCommitStore) adoptPersistedWriteMode() error {
}

// resolveCurrentWriteMode sets cs.currentWriteMode after the backends have been
// opened. For a fixed configured mode this is a copy; for types.Auto the
// opened. For a fixed configured mode this is a copy once
// requirePinnedModeMatchesMigration accepts it; for types.Auto the
// mode is derived from the migration metadata persisted in flatkv. A nil
// flatkv under types.Auto means the backend was never materialized
// (lazy-open found no directory), which is definitionally MemiavlOnly.
Expand All @@ -462,6 +463,9 @@ func (cs *CompositeCommitStore) adoptPersistedWriteMode() error {
// anyway.
func (cs *CompositeCommitStore) resolveCurrentWriteMode(closeIdleFlatKV bool) error {
if cs.config.WriteMode != types.Auto {
if err := cs.requirePinnedModeMatchesMigration(); err != nil {
return err
}
cs.storeWriteMode(cs.config.WriteMode)
return nil
}
Expand All @@ -486,6 +490,34 @@ func (cs *CompositeCommitStore) resolveCurrentWriteMode(closeIdleFlatKV bool) er
return nil
}

// requirePinnedModeMatchesMigration returns an error when the configured
// steady-state write mode disagrees with the migration state persisted in
// flatkv. A store whose history began in flatkv carries no migration
// metadata and derives MemiavlOnly, so that state is accepted for any pin.
func (cs *CompositeCommitStore) requirePinnedModeMatchesMigration() error {
pinned := cs.config.WriteMode
switch pinned {
case types.EVMMigrated, types.AllMigratedButBank, types.FlatKVOnly:
default:
return nil
}
flatKV := cs.loadFlatKV()
if flatKV == nil {
return nil
}
persisted, err := migration.DeriveWriteMode(flatKV)
if err != nil {
return fmt.Errorf("failed to derive persisted write mode: %w", err)
}
if persisted == pinned || persisted == types.MemiavlOnly {

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

suggestion — (Also raised by codex.) Accepting MemiavlOnly for every pin also lets through a store with real memiavl history and no migration, such as a pinned EVMMigrated or AllMigratedButBank node whose memiavl is at version > 0 while flatkv derives MemiavlOnly (freshly seeded, or rolled back below activation). Such a node then runs steady-state routing over a flatkv that never received the migrated data. Those two pins already open memiavl, so memIAVL.Version() > 0 combined with a MemiavlOnly derivation is evidence you can refuse on; FlatKVOnly never opens memiavl and would need some other provenance signal.

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.

@seidroid can you approve the PR

@seidroid seidroid Bot Oct 7, 2026 •

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Claude finished @alexander-sei's task in 12s —— View job


Approval request

I can't approve this PR. For security reasons this integration can't submit formal GitHub PR reviews, including approvals. All I can do is reply in this comment. The FAQ has more on this and possible workarounds.

Some context that may help:

  • You already approved it. Your review from 17:00:58Z is still there, so this PR has one approval. If branch protection on release/v6.7 needs more, a second maintainer has to approve it.
  • My last review recommended approval. It found nothing blocking: 0 blocking and 3 non-blocking items. All three are about behaviour this change inherits:
    1. A refused pinned Rollback returns an error after both backends have already been rolled back on disk.
    2. Read-only and export views at heights where a migration was in flight now fail under a pin, and no test covers this.
    3. Accepting MemiavlOnly for every pin lets through a pinned node that has memiavl history but no migration (store.go:512).

None of these stops a backport that matches #4484. They are better handled as follow-ups on main.

return nil
}
return fmt.Errorf(
"write mode is pinned to %q but the persisted migration state is %q; "+
"set state-commit.sc-write-mode-enable-auto = true to follow the migration",
pinned, persisted)
}

// loadFlatKV returns the flatKV backend, or nil when none is open.
func (cs *CompositeCommitStore) loadFlatKV() flatkv.Store {
if store := cs.flatKV.Load(); store != nil {
Expand Down
88 changes: 88 additions & 0 deletions sei-db/state_db/sc/composite/store_migration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1537,3 +1537,91 @@ func TestMigrateEVMPausedMidMigrationHashesEveryWrite(t *testing.T) {
require.NotEqual(t, lattice, hashOf(cs, "evm_lattice"),
"after a restart, a paused write to flatkv must still change the lattice hash")
}

// pinnedConfig returns a test config pinned to mode.
func pinnedConfig(mode types.WriteMode) config.StateCommitConfig {
cfg := autoConfig()
cfg.WriteMode = mode
return cfg
}

// loadLatestError opens the store at dir with cfg and returns the LoadLatest error.
func loadLatestError(t *testing.T, dir string, cfg config.StateCommitConfig) error {
t.Helper()
cs, err := NewCompositeCommitStore(t.Context(), dir, cfg)
require.NoError(t, err)
require.NoError(t, cs.Initialize([]string{keys.BankStoreKey, keys.EVMStoreKey}))
err = cs.LoadLatest()
_ = cs.Close()
return err
}

// TestComposite_PinnedFlatKVOnly_RefusesRollbackIntoBankMigration rolls a store pinned to FlatKVOnly back
// below the bank migration's completion. The rollback and every later pinned open must fail, and reopening
// in Auto must recover the canonical state at the target and reproduce the rolled-back blocks.
func TestComposite_PinnedFlatKVOnly_RefusesRollbackIntoBankMigration(t *testing.T) {
dir := t.TempDir()
workload := newMigrationWorkload(0xB0A7)
const batch = 8

cs := openAutoStore(t, dir, batch)
walkToMigrateBank(t, cs, workload)
target := commitRecorded(t, cs, workload.generateBlock(0, 2, 1, 0, 2))
rolledBack := commitUntilAtMigrationVersion(t, cs, workload, migration.Version3_FlatKVOnly, 800)
require.NoError(t, cs.Close())

pinned := pinnedConfig(types.FlatKVOnly)
cs = openAutoStoreWithConfig(t, dir, pinned, batch)
require.ErrorContains(t, cs.Rollback(target.info.Version), `persisted migration state is "migrate_bank"`)
require.NoError(t, cs.Close())
require.ErrorContains(t, loadLatestError(t, dir, pinned), `persisted migration state is "migrate_bank"`)

cs = openAutoStore(t, dir, batch)
defer func() { _ = cs.Close() }()
require.Equal(t, types.MigrateBank, cs.loadWriteMode())
requireCommitInfoEqual(t, target.info, cs.LastCommitInfo(), "commit info after reopening in Auto")
replayRecorded(t, cs, rolledBack, "replay after reopening in Auto")
}

// TestComposite_PinnedSteadyStateModes_RefuseInFlightMigration opens a store whose EVM migration is in
// flight with each steady-state pin. Every pin must refuse to load.
func TestComposite_PinnedSteadyStateModes_RefuseInFlightMigration(t *testing.T) {
dir := t.TempDir()
workload := newMigrationWorkload(0xE7E8)

cs := openAutoStore(t, dir, 8)
for i := 0; i < 12; i++ {
commitRecorded(t, cs, workload.generateBlock(6, 0, 0, 4, 0))
}
require.NoError(t, cs.SetWriteMode(types.MigrateEVM))
commitRecorded(t, cs, workload.generateBlock(2, 2, 1, 0, 0))
require.NoError(t, cs.Close())

for _, mode := range []types.WriteMode{types.EVMMigrated, types.AllMigratedButBank, types.FlatKVOnly} {
require.ErrorContains(t, loadLatestError(t, dir, pinnedConfig(mode)),
`persisted migration state is "migrate_evm"`, "pinned %s", mode)
}

cs = openAutoStore(t, dir, 8)
defer func() { _ = cs.Close() }()
require.Equal(t, types.MigrateEVM, cs.loadWriteMode(), "sanity: the migration is still in flight")
}

// TestComposite_PinnedFlatKVOnly_GenesisStoreReopensAndRollsBack checks that a store pinned to FlatKVOnly
// from genesis, which has no migration metadata, still reopens and rolls back.
func TestComposite_PinnedFlatKVOnly_GenesisStoreReopensAndRollsBack(t *testing.T) {
dir := t.TempDir()
workload := newMigrationWorkload(0x6E5)
pinned := pinnedConfig(types.FlatKVOnly)

cs := openAutoStoreWithConfig(t, dir, pinned, 0)
runBlocks(t, cs, workload, 5)
require.NoError(t, cs.Close())

cs = openAutoStoreWithConfig(t, dir, pinned, 0)
defer func() { _ = cs.Close() }()
require.Equal(t, int64(5), cs.Version())
require.NoError(t, cs.Rollback(3))
require.Equal(t, int64(3), cs.Version())
require.Equal(t, types.FlatKVOnly, cs.loadWriteMode())
}
Loading