Skip to content

Commit 705de11

Browse files
bdchathamclaude
andauthored
feat: v1→v2 config schema migration for seid v6.5 write mode rename (#25)
* feat: v1→v2 config schema migration for seid v6.5 write mode rename seid v6.5 (sei-chain commit 0412e4e84) replaced the WriteMode enum to model the FlatKV migration lifecycle. cosmos_only → memiavl_only is now the breaking change that crashes any node provisioned with the old default. Changes: - types.go: add v2 WriteMode constants (memiavl_only, migrate_evm, evm_migrated, migrate_all_but_bank, all_migrated_but_bank, migrate_bank, flatkv_only, test_only_dual_write); update IsValid() to v2 values; keep v1 constants as Deprecated for migration reference - defaults.go: change StateCommit and StateStore WriteMode defaults from WriteModeCosmosOnly → WriteModeMemiavlOnly - config.go: bump CurrentVersion 1 → 2 - migrate.go: register v1→v2 migration (renames cosmos_only, dual_write, split_write in both StateCommit and StateStore WriteMode); add SeidVersionForSchema map so each schema version is traceable to its seid version boundary - config_test.go: update TestWriteMode_Validity for v2 semantics Fixes: #24 Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * test: add v1→v2 migration round-trip and edge case coverage Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * fix: address cross-review findings on v2 write mode migration - types.go: remove WriteModeEVMOnly — never a real deployed value, had no migration target (would fail IsValid() after migration) - migrate.go: delete SeidVersionForSchema exported var (YAGNI — no programmatic consumer); fold version mapping into doc comment - validate.go: add SeverityWarning for test_only_dual_write — valid per IsValid() to match seid's own parser, but explicitly flagged since sei-chain marks it "CRITICAL: never deploy to production" Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
1 parent 2d4ff04 commit 705de11

7 files changed

Lines changed: 144 additions & 22 deletions

File tree

config.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ package seiconfig
88
import "runtime"
99

1010
// CurrentVersion is the config schema version produced by this library.
11-
const CurrentVersion = 1
11+
const CurrentVersion = 2
1212

1313
// DefaultSnapshotInterval is the default Tendermint state-sync snapshot
1414
// creation interval (in blocks) used when snapshot generation is enabled.

config_test.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -739,8 +739,11 @@ func TestNodeMode_IsFullnodeType(t *testing.T) {
739739
}
740740

741741
func TestWriteMode_Validity(t *testing.T) {
742-
if !WriteModeCosmosOnly.IsValid() {
743-
t.Error("cosmos_only should be valid")
742+
if !WriteModeMemiavlOnly.IsValid() {
743+
t.Error("memiavl_only should be valid")
744+
}
745+
if WriteModeCosmosOnly.IsValid() {
746+
t.Error("cosmos_only should not be valid in v2 (deprecated — use migration)")
744747
}
745748
if WriteMode("invalid").IsValid() {
746749
t.Error("'invalid' should not be valid")

defaults.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ func baseDefaults() *SeiConfig {
117117
IAVLDisableFastNode: true,
118118
StateCommit: StateCommitConfig{
119119
Enable: true,
120-
WriteMode: WriteModeCosmosOnly,
120+
WriteMode: WriteModeMemiavlOnly,
121121
ReadMode: ReadModeCosmosOnly,
122122
},
123123
StateStore: StateStoreConfig{
@@ -128,7 +128,7 @@ func baseDefaults() *SeiConfig {
128128
PruneIntervalSeconds: 600,
129129
ImportNumWorkers: 1,
130130
KeepLastVersion: true,
131-
WriteMode: WriteModeCosmosOnly,
131+
WriteMode: WriteModeMemiavlOnly,
132132
ReadMode: ReadModeCosmosOnly,
133133
},
134134
ReceiptStore: ReceiptStoreConfig{

migrate.go

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -175,22 +175,33 @@ type AppliedMigration struct {
175175
// Default migration registry
176176
// ---------------------------------------------------------------------------
177177

178-
// DefaultMigrations returns the set of all known migrations for the sei-config
179-
// schema. Currently empty since v1 is the initial version — migrations will be
180-
// added here as the schema evolves.
178+
// DefaultMigrations returns all known migrations for the sei-config schema.
181179
//
182-
// Example of a future migration:
180+
// Schema version to seid version mapping:
183181
//
184-
// Migration{
185-
// FromVersion: 1,
186-
// ToVersion: 2,
187-
// Description: "Rename evm.checktx_timeout to evm.check_tx_timeout",
188-
// Migrate: func(cfg *SeiConfig) error {
189-
// // Field was renamed; value is preserved by the struct.
190-
// cfg.Version = 2
191-
// return nil
192-
// },
193-
// }
182+
// v1 → seid < v6.5 (cosmos_only write mode, legacy EVM routing)
183+
// v2 → seid ≥ v6.5 (memiavl_only write mode, FlatKV migration scheme)
194184
func DefaultMigrations() []Migration {
195-
return []Migration{}
185+
return []Migration{
186+
{
187+
FromVersion: 1,
188+
ToVersion: 2,
189+
Description: "seid v6.5: rename WriteMode values to FlatKV migration scheme (cosmos_only→memiavl_only, dual_write→migrate_evm, split_write→evm_migrated)",
190+
Migrate: func(cfg *SeiConfig) error {
191+
rename := map[WriteMode]WriteMode{
192+
WriteModeCosmosOnly: WriteModeMemiavlOnly,
193+
WriteModeDualWrite: WriteModeMigrateEVM,
194+
WriteModeSplitWrite: WriteModeEVMMigrated,
195+
}
196+
if m, ok := rename[cfg.Storage.StateCommit.WriteMode]; ok {
197+
cfg.Storage.StateCommit.WriteMode = m
198+
}
199+
if m, ok := rename[cfg.Storage.StateStore.WriteMode]; ok {
200+
cfg.Storage.StateStore.WriteMode = m
201+
}
202+
cfg.Version = 2
203+
return nil
204+
},
205+
},
206+
}
196207
}

migrate_test.go

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -377,3 +377,96 @@ func TestDefaultMigrations_Valid(t *testing.T) {
377377
t.Fatalf("DefaultMigrations failed to register: %v", err)
378378
}
379379
}
380+
381+
// v1ToV2Migration returns the v1→v2 migration from DefaultMigrations for tests
382+
// that exercise the rename transform directly (bypassing post-migration
383+
// validation, which rejects unknown/deprecated WriteMode values).
384+
func v1ToV2Migration(t *testing.T) Migration {
385+
t.Helper()
386+
for _, m := range DefaultMigrations() {
387+
if m.FromVersion == 1 && m.ToVersion == 2 {
388+
return m
389+
}
390+
}
391+
t.Fatal("DefaultMigrations missing v1→v2 migration")
392+
return Migration{}
393+
}
394+
395+
// TestMigrateConfig_WriteModeRoundTrip runs the real v1→v2 migration through
396+
// the registry pipeline (including post-migration validation) and asserts the
397+
// deprecated cosmos_only write mode is renamed to memiavl_only in both stores.
398+
func TestMigrateConfig_WriteModeRoundTrip(t *testing.T) {
399+
r, err := NewMigrationRegistry(DefaultMigrations()...)
400+
if err != nil {
401+
t.Fatalf("NewMigrationRegistry: %v", err)
402+
}
403+
404+
cfg := DefaultForMode(ModeFull)
405+
cfg.Version = 1
406+
cfg.Storage.StateCommit.WriteMode = WriteModeCosmosOnly
407+
cfg.Storage.StateStore.WriteMode = WriteModeCosmosOnly
408+
409+
result, err := r.MigrateConfig(cfg, 2)
410+
if err != nil {
411+
t.Fatalf("MigrateConfig: %v", err)
412+
}
413+
414+
if cfg.Version != 2 {
415+
t.Errorf("version: got %d, want 2", cfg.Version)
416+
}
417+
if cfg.Storage.StateCommit.WriteMode != WriteModeMemiavlOnly {
418+
t.Errorf("state_commit.write_mode: got %q, want %q",
419+
cfg.Storage.StateCommit.WriteMode, WriteModeMemiavlOnly)
420+
}
421+
if cfg.Storage.StateStore.WriteMode != WriteModeMemiavlOnly {
422+
t.Errorf("state_store.write_mode: got %q, want %q",
423+
cfg.Storage.StateStore.WriteMode, WriteModeMemiavlOnly)
424+
}
425+
if len(result.Applied) != 1 {
426+
t.Fatalf("applied migrations: got %d, want 1", len(result.Applied))
427+
}
428+
}
429+
430+
// TestV1ToV2_WriteModeRename covers every deprecated v1 write mode and asserts
431+
// it maps to the expected v2 value. The unknown-value case asserts pass-through
432+
// (the migration only renames known values; validation handles the rest).
433+
func TestV1ToV2_WriteModeRename(t *testing.T) {
434+
m := v1ToV2Migration(t)
435+
436+
tests := []struct {
437+
name string
438+
in WriteMode
439+
want WriteMode
440+
}{
441+
{"cosmos_only renames to memiavl_only", WriteModeCosmosOnly, WriteModeMemiavlOnly},
442+
{"dual_write renames to migrate_evm", WriteModeDualWrite, WriteModeMigrateEVM},
443+
{"split_write renames to evm_migrated", WriteModeSplitWrite, WriteModeEVMMigrated},
444+
{"already-v2 memiavl_only is preserved", WriteModeMemiavlOnly, WriteModeMemiavlOnly},
445+
{"unknown value passes through unchanged", WriteMode("future_mode"), WriteMode("future_mode")},
446+
}
447+
448+
for _, tc := range tests {
449+
t.Run(tc.name, func(t *testing.T) {
450+
cfg := DefaultForMode(ModeFull)
451+
cfg.Version = 1
452+
cfg.Storage.StateCommit.WriteMode = tc.in
453+
cfg.Storage.StateStore.WriteMode = tc.in
454+
455+
if err := m.Migrate(cfg); err != nil {
456+
t.Fatalf("Migrate: %v", err)
457+
}
458+
459+
if cfg.Version != 2 {
460+
t.Errorf("version: got %d, want 2", cfg.Version)
461+
}
462+
if cfg.Storage.StateCommit.WriteMode != tc.want {
463+
t.Errorf("state_commit.write_mode: got %q, want %q",
464+
cfg.Storage.StateCommit.WriteMode, tc.want)
465+
}
466+
if cfg.Storage.StateStore.WriteMode != tc.want {
467+
t.Errorf("state_store.write_mode: got %q, want %q",
468+
cfg.Storage.StateStore.WriteMode, tc.want)
469+
}
470+
})
471+
}
472+
}

types.go

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,15 +69,27 @@ func Dur(d time.Duration) Duration {
6969
type WriteMode string
7070

7171
const (
72+
// v2 write modes — FlatKV migration lifecycle (sei-chain ≥ v6.5).
73+
WriteModeMemiavlOnly WriteMode = "memiavl_only"
74+
WriteModeMigrateEVM WriteMode = "migrate_evm"
75+
WriteModeEVMMigrated WriteMode = "evm_migrated"
76+
WriteModeMigrateAllButBank WriteMode = "migrate_all_but_bank"
77+
WriteModeAllMigratedButBank WriteMode = "all_migrated_but_bank"
78+
WriteModeMigrateBank WriteMode = "migrate_bank"
79+
WriteModeFlatKVOnly WriteMode = "flatkv_only"
80+
WriteModeTestOnlyDualWrite WriteMode = "test_only_dual_write"
81+
82+
// Deprecated: v1 write modes, accepted only during v1→v2 migration.
7283
WriteModeCosmosOnly WriteMode = "cosmos_only"
7384
WriteModeDualWrite WriteMode = "dual_write"
7485
WriteModeSplitWrite WriteMode = "split_write"
75-
WriteModeEVMOnly WriteMode = "evm_only"
7686
)
7787

7888
func (m WriteMode) IsValid() bool {
7989
switch m {
80-
case WriteModeCosmosOnly, WriteModeDualWrite, WriteModeSplitWrite, WriteModeEVMOnly:
90+
case WriteModeMemiavlOnly, WriteModeMigrateEVM, WriteModeEVMMigrated,
91+
WriteModeMigrateAllButBank, WriteModeAllMigratedButBank,
92+
WriteModeMigrateBank, WriteModeFlatKVOnly, WriteModeTestOnlyDualWrite:
8193
return true
8294
default:
8395
return false

validate.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,9 @@ func validateStorage(r *ValidationResult, cfg *SeiConfig) {
260260
if sc.WriteMode != "" && !sc.WriteMode.IsValid() {
261261
r.addError("storage.state_commit.write_mode", fmt.Sprintf("invalid write_mode: %q", sc.WriteMode))
262262
}
263+
if sc.WriteMode == WriteModeTestOnlyDualWrite {
264+
r.addWarning("storage.state_commit.write_mode", "test_only_dual_write must not be used in production")
265+
}
263266
if sc.ReadMode != "" && !sc.ReadMode.IsValid() {
264267
r.addError("storage.state_commit.read_mode", fmt.Sprintf("invalid read_mode: %q", sc.ReadMode))
265268
}

0 commit comments

Comments
 (0)