Skip to content

Commit cce2bed

Browse files
authored
Merge pull request #51 from sei-protocol/feat/sc-write-mode-enable-auto
feat: add storage.state_commit.write_mode_enable_auto
2 parents fe8f4a5 + a0a6fcf commit cce2bed

5 files changed

Lines changed: 115 additions & 5 deletions

File tree

config.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,14 @@ type StateCommitConfig struct {
333333
WriteMode WriteMode `toml:"write_mode"`
334334
ReadMode ReadMode `toml:"read_mode"`
335335

336+
// WriteModeEnableAuto controls whether the binary derives its write mode
337+
// from committed migration state instead of honoring WriteMode. Nil leaves
338+
// the key out of the rendered app.toml, which the binary reads as enabled.
339+
// Set it to false to pin a node to its explicit WriteMode; such a node does
340+
// not follow a governance-driven migration and diverges once the chain
341+
// migrates, which is what a reserve node is for.
342+
WriteModeEnableAuto *bool `toml:"write_mode_enable_auto"`
343+
336344
MemIAVL MemIAVLConfig `toml:"memiavl"`
337345
}
338346

config_test.go

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
package seiconfig
22

33
import (
4+
"bytes"
45
"os"
56
"path/filepath"
67
"strings"
78
"testing"
89
"time"
10+
11+
"github.com/BurntSushi/toml"
912
)
1013

1114
const testRPCAddr = "tcp://0.0.0.0:26657"
@@ -445,6 +448,85 @@ func TestApplyOverrides_Bool(t *testing.T) {
445448
}
446449
}
447450

451+
// TestWriteModeEnableAuto_Tristate covers the three states a reserve node
452+
// depends on: absent so the binary's default applies, explicitly false to pin
453+
// the node to its own write mode, and explicitly true.
454+
func TestWriteModeEnableAuto_Tristate(t *testing.T) {
455+
encode := func(t *testing.T, cfg *SeiConfig) string {
456+
t.Helper()
457+
var buf bytes.Buffer
458+
if err := toml.NewEncoder(&buf).Encode(cfg.toLegacyApp()); err != nil {
459+
t.Fatalf("encoding app.toml: %v", err)
460+
}
461+
return buf.String()
462+
}
463+
464+
t.Run("unset omits the key", func(t *testing.T) {
465+
cfg := Default()
466+
if cfg.Storage.StateCommit.WriteModeEnableAuto != nil {
467+
t.Fatal("default should leave WriteModeEnableAuto unset")
468+
}
469+
if got := encode(t, cfg); strings.Contains(got, "sc-write-mode-enable-auto") {
470+
t.Error("unset value must not render the key, so the binary keeps its own default")
471+
}
472+
})
473+
474+
t.Run("override to false renders false", func(t *testing.T) {
475+
cfg := Default()
476+
if err := ApplyOverrides(cfg, map[string]string{
477+
"storage.state_commit.write_mode_enable_auto": "false",
478+
}); err != nil {
479+
t.Fatalf("ApplyOverrides: %v", err)
480+
}
481+
got := cfg.Storage.StateCommit.WriteModeEnableAuto
482+
if got == nil || *got {
483+
t.Fatalf("WriteModeEnableAuto: got %v, want pointer to false", got)
484+
}
485+
if out := encode(t, cfg); !strings.Contains(out, "sc-write-mode-enable-auto = false") {
486+
t.Error("an explicit false must render; omitempty on a pointer drops only nil")
487+
}
488+
})
489+
490+
t.Run("override to true renders true", func(t *testing.T) {
491+
cfg := Default()
492+
if err := ApplyOverrides(cfg, map[string]string{
493+
"storage.state_commit.write_mode_enable_auto": "true",
494+
}); err != nil {
495+
t.Fatalf("ApplyOverrides: %v", err)
496+
}
497+
got := cfg.Storage.StateCommit.WriteModeEnableAuto
498+
if got == nil || !*got {
499+
t.Fatalf("WriteModeEnableAuto: got %v, want pointer to true", got)
500+
}
501+
if out := encode(t, cfg); !strings.Contains(out, "sc-write-mode-enable-auto = true") {
502+
t.Error("an explicit true must render")
503+
}
504+
})
505+
}
506+
507+
// TestWriteModeEnableAuto_RoundTrip checks that reading back a rendered
508+
// app.toml preserves the pin, so a re-render does not silently unpin the node.
509+
func TestWriteModeEnableAuto_RoundTrip(t *testing.T) {
510+
for _, want := range []*bool{nil, boolPtr(false), boolPtr(true)} {
511+
cfg := Default()
512+
cfg.Storage.StateCommit.WriteModeEnableAuto = want
513+
514+
back := fromLegacy(cfg.toLegacyTendermint(), cfg.toLegacyApp())
515+
got := back.Storage.StateCommit.WriteModeEnableAuto
516+
517+
switch {
518+
case want == nil && got != nil:
519+
t.Errorf("unset round-tripped to %v", *got)
520+
case want != nil && got == nil:
521+
t.Errorf("%v round-tripped to unset", *want)
522+
case want != nil && got != nil && *want != *got:
523+
t.Errorf("round-trip: got %v, want %v", *got, *want)
524+
}
525+
}
526+
}
527+
528+
func boolPtr(b bool) *bool { return &b }
529+
448530
func TestApplyOverrides_Uint(t *testing.T) {
449531
cfg := Default()
450532
if err := ApplyOverrides(cfg, map[string]string{

enrichments.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,9 @@ func DefaultEnrichments() map[string][]FieldOption {
219219
"storage.state_commit.write_mode": {
220220
WithDescription("EVM write routing: cosmos_only, dual_write, split_write, evm_only."),
221221
},
222+
"storage.state_commit.write_mode_enable_auto": {
223+
WithDescription("Derive the write mode from committed migration state instead of honoring write_mode. Unset means enabled. Set false to pin a node, which then does not follow a governance-driven migration."),
224+
},
222225
"storage.state_commit.read_mode": {
223226
WithDescription("EVM read routing: cosmos_only, evm_first, split_read."),
224227
},

legacy.go

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,10 @@ type legacyStateCommit struct {
261261
AsyncCommitBuffer int `toml:"sc-async-commit-buffer"`
262262
WriteMode string `toml:"sc-write-mode,omitempty"`
263263
ReadMode string `toml:"sc-read-mode,omitempty"`
264+
// omitempty on a pointer omits only nil, so an explicit false still
265+
// renders. A bare bool could not express the pin: omitempty would drop
266+
// false, and without omitempty every node would emit the key.
267+
WriteModeEnableAuto *bool `toml:"sc-write-mode-enable-auto,omitempty"`
264268

265269
KeepRecent uint32 `toml:"sc-keep-recent"`
266270
SnapshotInterval uint32 `toml:"sc-snapshot-interval"`
@@ -589,6 +593,7 @@ func (cfg *SeiConfig) toLegacyApp() legacyAppConfig {
589593
AsyncCommitBuffer: cfg.Storage.StateCommit.AsyncCommitBuffer,
590594
WriteMode: string(cfg.Storage.StateCommit.WriteMode),
591595
ReadMode: string(cfg.Storage.StateCommit.ReadMode),
596+
WriteModeEnableAuto: cfg.Storage.StateCommit.WriteModeEnableAuto,
592597
KeepRecent: cfg.Storage.StateCommit.MemIAVL.SnapshotKeepRecent,
593598
SnapshotInterval: cfg.Storage.StateCommit.MemIAVL.SnapshotInterval,
594599
SnapshotMinTimeInterval: cfg.Storage.StateCommit.MemIAVL.SnapshotMinTimeInterval,
@@ -849,11 +854,12 @@ func fromLegacy(tm legacyTendermintConfig, app legacyAppConfig) *SeiConfig {
849854
CompactionInterval: app.CompactionInterval,
850855
IAVLDisableFastNode: app.IAVLDisableFastNode,
851856
StateCommit: StateCommitConfig{
852-
Enable: app.StateCommit.Enable,
853-
Directory: app.StateCommit.Directory,
854-
AsyncCommitBuffer: app.StateCommit.AsyncCommitBuffer,
855-
WriteMode: WriteMode(app.StateCommit.WriteMode),
856-
ReadMode: ReadMode(app.StateCommit.ReadMode),
857+
Enable: app.StateCommit.Enable,
858+
Directory: app.StateCommit.Directory,
859+
AsyncCommitBuffer: app.StateCommit.AsyncCommitBuffer,
860+
WriteMode: WriteMode(app.StateCommit.WriteMode),
861+
ReadMode: ReadMode(app.StateCommit.ReadMode),
862+
WriteModeEnableAuto: app.StateCommit.WriteModeEnableAuto,
857863
MemIAVL: MemIAVLConfig{
858864
SnapshotKeepRecent: app.StateCommit.KeepRecent,
859865
SnapshotInterval: app.StateCommit.SnapshotInterval,

resolve.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,17 @@ func setFieldByPath(cfg *SeiConfig, path string, value string) error {
119119
}
120120

121121
func setReflectValue(v reflect.Value, s string) error {
122+
// A pointer field is the tri-state form: nil renders as an absent key, so
123+
// the binary's own default applies. Allocate before setting, otherwise an
124+
// override on such a field reaches the type switch as a pointer and is
125+
// rejected as unsupported.
126+
if v.Kind() == reflect.Ptr {
127+
if v.IsNil() {
128+
v.Set(reflect.New(v.Type().Elem()))
129+
}
130+
return setReflectValue(v.Elem(), s)
131+
}
132+
122133
if v.Type() == reflect.TypeFor[Duration]() {
123134
var d Duration
124135
if err := d.UnmarshalText([]byte(s)); err != nil {

0 commit comments

Comments
 (0)