diff --git a/app/config_fuzz_test.go b/app/config_fuzz_test.go index 9d42328eee..a97d73a884 100644 --- a/app/config_fuzz_test.go +++ b/app/config_fuzz_test.go @@ -347,8 +347,8 @@ func FuzzSCWriteMode(f *testing.F) { cfg := parseSCConfigs(opts) - // enable-auto defaults to true and only an explicit key changes it. - effectiveAuto := true + // enable-auto takes its in-code default and only an explicit key changes it. + effectiveAuto := config.DefaultStateCommitConfig().WriteModeEnableAuto if setAuto { effectiveAuto = auto } @@ -507,14 +507,14 @@ func FuzzReadGenesisStreamImport(f *testing.F) { // TestParseSCConfigsAbsentBaseline records what an app.toml with no // [state-commit] section resolves to. It is not the in-code default: the two // unguarded reads clobber Enable to false and Directory to "", and the write mode -// resolves to auto because enable-auto defaults to true. The Enable clobber is the +// follows whatever enable-auto defaults to on this build. The Enable clobber is the // reason a node whose app.toml predates the section refuses to boot — SetupSeiDB // panics on !Enable rather than falling back. func TestParseSCConfigsAbsentBaseline(t *testing.T) { want := config.DefaultStateCommitConfig() want.Enable = false // unguarded read of an absent key want.Directory = "" // unguarded read of an absent key - want.WriteMode = sctypes.Auto + want.WriteMode = wantAbsentAutoWriteMode want.HashLogger.Version = version.Version // stamped from the build, not from config got := parseSCConfigs(configtest.AppOpts{}) @@ -631,7 +631,7 @@ func TestKeyNamesMatchTheRecordedNames(t *testing.T) { // this package and sei-cosmos/server/config, and regenerating only one leaves the other red, so // regenerate both and read both diffs. func TestDefaultsMatchTheRecordedValues(t *testing.T) { - configtest.CheckDefaults(t, "state-commit", config.DefaultStateCommitConfig()) + configtest.CheckDefaults(t, stateCommitRecord, config.DefaultStateCommitConfig()) configtest.CheckDefaults(t, "state-store", config.DefaultStateStoreConfig()) configtest.CheckDefaults(t, "light_invariance", DefaultLightInvarianceConfig) configtest.CheckDefaults(t, "genesis", DefaultGenesisConfig) diff --git a/app/config_register_agreement_test.go b/app/config_register_agreement_test.go index ca84b68182..c9a7f60f39 100644 --- a/app/config_register_agreement_test.go +++ b/app/config_register_agreement_test.go @@ -22,7 +22,7 @@ var whatANodeRunsToday = map[string]string{ FlagSSPruneInterval: "0", FlagSSImportNumWorkers: "0", FlagSCEnable: "false", - FlagSCWriteMode: "auto", + FlagSCWriteMode: scWriteModeANodeRuns, } // whyItMatters says what a node gets today, for the keys where that is worth stating. @@ -42,15 +42,18 @@ var whyItMatters = map[string]string{ // Per mode because the section answers per mode for two of these settings and the reader does not answer // per mode at all. An archive node declares the retention the reader also produces, so that key agrees for // archive and disagrees everywhere else; the store toggle is the reverse. +// +// Every entry ends in scWriteModeDivergence, which holds sc-write-mode on builds where the two sides +// disagree and is empty on the one build where they do not. var theDivergences = map[registry.Mode][]string{ - registry.ModeValidator: {FlagSSBackend, FlagSSAsyncWriterBuffer, FlagSSKeepRecent, - FlagSSPruneInterval, FlagSSImportNumWorkers, FlagSCEnable, FlagSCWriteMode}, - registry.ModeSeed: {FlagSSBackend, FlagSSAsyncWriterBuffer, FlagSSKeepRecent, - FlagSSPruneInterval, FlagSSImportNumWorkers, FlagSCEnable, FlagSCWriteMode}, - registry.ModeFull: {FlagSSEnable, FlagSSBackend, FlagSSAsyncWriterBuffer, FlagSSKeepRecent, - FlagSSPruneInterval, FlagSSImportNumWorkers, FlagSCEnable, FlagSCWriteMode}, - registry.ModeArchive: {FlagSSEnable, FlagSSBackend, FlagSSAsyncWriterBuffer, - FlagSSPruneInterval, FlagSSImportNumWorkers, FlagSCEnable, FlagSCWriteMode}, + registry.ModeValidator: append([]string{FlagSSBackend, FlagSSAsyncWriterBuffer, FlagSSKeepRecent, + FlagSSPruneInterval, FlagSSImportNumWorkers, FlagSCEnable}, scWriteModeDivergence...), + registry.ModeSeed: append([]string{FlagSSBackend, FlagSSAsyncWriterBuffer, FlagSSKeepRecent, + FlagSSPruneInterval, FlagSSImportNumWorkers, FlagSCEnable}, scWriteModeDivergence...), + registry.ModeFull: append([]string{FlagSSEnable, FlagSSBackend, FlagSSAsyncWriterBuffer, FlagSSKeepRecent, + FlagSSPruneInterval, FlagSSImportNumWorkers, FlagSCEnable}, scWriteModeDivergence...), + registry.ModeArchive: append([]string{FlagSSEnable, FlagSSBackend, FlagSSAsyncWriterBuffer, + FlagSSPruneInterval, FlagSSImportNumWorkers, FlagSCEnable}, scWriteModeDivergence...), } // readerValues is what each section's reader produces for a file carrying no keys at all. diff --git a/app/seidb_test.go b/app/seidb_test.go index f089826f2b..6ac968d4ea 100644 --- a/app/seidb_test.go +++ b/app/seidb_test.go @@ -76,10 +76,10 @@ func TestNewDefaultConfig(t *testing.T) { ssConfig := parseSSConfigs(appOpts) receiptConfig, err := config.ReadReceiptConfig(appOpts) assert.NoError(t, err) - // WriteModeEnableAuto defaults to true, so parseSCConfigs resolves the effective - // WriteMode to auto, overriding the fixed-fallback default (memiavl_only). + // parseSCConfigs resolves the effective WriteMode through WriteModeEnableAuto, + // so it is the one field that need not equal the fixed-fallback default. expectedSC := config.DefaultStateCommitConfig() - expectedSC.WriteMode = sctypes.Auto + expectedSC.WriteMode = wantAbsentAutoWriteMode // parseSCConfigs is a raw parse and does not align FlatKV with memIAVL (that // happens in composite.NewCompositeCommitStore), so the parsed config matches // the in-code defaults verbatim apart from the resolved write mode. @@ -169,7 +169,7 @@ func TestParseSCConfigs_LegacyCosmosOnlyWriteMode(t *testing.T) { FlagSCEnable: true, FlagSCWriteMode: "cosmos_only", }) - assert.Equal(t, sctypes.Auto, scConfig.WriteMode) + assert.Equal(t, wantAbsentAutoWriteMode, scConfig.WriteMode) scConfig = parseSCConfigs(mapAppOpts{ FlagSCEnable: true, diff --git a/app/testdata/state-commit.reserve.golden b/app/testdata/state-commit.reserve.golden new file mode 100644 index 0000000000..1627385aca --- /dev/null +++ b/app/testdata/state-commit.reserve.golden @@ -0,0 +1,71 @@ +Enable = bool(true) +Directory = string("") +AsyncCommitBuffer = int(0) +WriteMode = types.WriteMode("memiavl_only") +WriteModeEnableAuto = bool(false) +MemIAVLConfig.AsyncCommitBuffer = int(100) +MemIAVLConfig.SnapshotKeepRecent = uint32(1) +MemIAVLConfig.SnapshotInterval = uint32(10000) +MemIAVLConfig.SnapshotMinTimeInterval = uint32(3600) +MemIAVLConfig.SnapshotWriterLimit = int(4) +MemIAVLConfig.SnapshotPrefetchThreshold = float64(0.8) +MemIAVLConfig.SnapshotWriteRateMBps = int(100) +FlatKVConfig.DataDir = string("") +FlatKVConfig.Fsync = bool(false) +FlatKVConfig.AsyncWriteBuffer = int(0) +FlatKVConfig.SnapshotInterval = uint32(10000) +FlatKVConfig.SnapshotKeepRecent = uint32(1) +FlatKVConfig.ExternalPruning = bool(false) +FlatKVConfig.EnablePebbleMetrics = bool(true) +FlatKVConfig.EnableReadWriteMetrics = bool(false) +FlatKVConfig.AccountDBConfig.DataDir = string("") +FlatKVConfig.AccountDBConfig.EnableMetrics = bool(true) +FlatKVConfig.AccountDBConfig.EnableReadWriteMetrics = bool(false) +FlatKVConfig.AccountDBConfig.MetricsScrapeInterval = time.Duration(10s) +FlatKVConfig.AccountCacheConfig.ShardCount = uint64(8) +FlatKVConfig.AccountCacheConfig.MaxSize = uint64(1073741824) +FlatKVConfig.AccountCacheConfig.EstimatedOverheadPerEntry = uint64(250) +FlatKVConfig.AccountCacheConfig.MetricsName = string("") +FlatKVConfig.AccountCacheConfig.MetricsScrapeInterval = time.Duration(0s) +FlatKVConfig.CodeDBConfig.DataDir = string("") +FlatKVConfig.CodeDBConfig.EnableMetrics = bool(true) +FlatKVConfig.CodeDBConfig.EnableReadWriteMetrics = bool(false) +FlatKVConfig.CodeDBConfig.MetricsScrapeInterval = time.Duration(10s) +FlatKVConfig.CodeCacheConfig.ShardCount = uint64(8) +FlatKVConfig.CodeCacheConfig.MaxSize = uint64(536870912) +FlatKVConfig.CodeCacheConfig.EstimatedOverheadPerEntry = uint64(250) +FlatKVConfig.CodeCacheConfig.MetricsName = string("") +FlatKVConfig.CodeCacheConfig.MetricsScrapeInterval = time.Duration(0s) +FlatKVConfig.StorageDBConfig.DataDir = string("") +FlatKVConfig.StorageDBConfig.EnableMetrics = bool(true) +FlatKVConfig.StorageDBConfig.EnableReadWriteMetrics = bool(false) +FlatKVConfig.StorageDBConfig.MetricsScrapeInterval = time.Duration(10s) +FlatKVConfig.StorageCacheConfig.ShardCount = uint64(8) +FlatKVConfig.StorageCacheConfig.MaxSize = uint64(4294967296) +FlatKVConfig.StorageCacheConfig.EstimatedOverheadPerEntry = uint64(250) +FlatKVConfig.StorageCacheConfig.MetricsName = string("") +FlatKVConfig.StorageCacheConfig.MetricsScrapeInterval = time.Duration(0s) +FlatKVConfig.MiscDBConfig.DataDir = string("") +FlatKVConfig.MiscDBConfig.EnableMetrics = bool(true) +FlatKVConfig.MiscDBConfig.EnableReadWriteMetrics = bool(false) +FlatKVConfig.MiscDBConfig.MetricsScrapeInterval = time.Duration(10s) +FlatKVConfig.MiscCacheConfig.ShardCount = uint64(8) +FlatKVConfig.MiscCacheConfig.MaxSize = uint64(536870912) +FlatKVConfig.MiscCacheConfig.EstimatedOverheadPerEntry = uint64(250) +FlatKVConfig.MiscCacheConfig.MetricsName = string("") +FlatKVConfig.MiscCacheConfig.MetricsScrapeInterval = time.Duration(0s) +FlatKVConfig.ReaderThreadsPerCore = float64(2) +FlatKVConfig.ReaderConstantThreadCount = int(0) +FlatKVConfig.ReaderPoolQueueSize = int(1024) +FlatKVConfig.MiscPoolThreadsPerCore = float64(4) +FlatKVConfig.MiscConstantThreadCount = int(0) +FlatKVConfig.LtHashThreadsPerCore = float64(1) +HistoricalProofMaxInFlight = int(1) +HistoricalProofRateLimit = float64(1) +HistoricalProofBurst = int(1) +HashLogger.Enable = bool(true) +HashLogger.Directory = string("") +HashLogger.BlocksToRetain = uint(0) +HashLogger.TargetFileSize = uint(16777216) +HashLogger.MaxDiskSize = uint(17179869184) +HashLogger.Version = string("") diff --git a/app/write_mode_default_test.go b/app/write_mode_default_test.go new file mode 100644 index 0000000000..759ee3a4a1 --- /dev/null +++ b/app/write_mode_default_test.go @@ -0,0 +1,30 @@ +//go:build !mock_chain_validation + +package app + +import ( + sctypes "github.com/sei-protocol/sei-chain/sei-db/state_db/sc/types" +) + +// The four values below are everything in this package's configuration records +// that moves with sc-write-mode-enable-auto's in-code default. The reserve build +// declares that key false where this one declares it true, so it carries a +// write_mode_mock_chain_validation_test.go stating each of these the other way. + +// stateCommitRecord names this build's [state-commit] defaults record. The two +// builds keep separate records because a shared one would be rewritten by +// whichever build regenerated it last, losing the value the reviewer needed. +const stateCommitRecord = "state-commit" + +// wantAbsentAutoWriteMode is the mode parseSCConfigs resolves memiavl_only to +// when app.toml carries no sc-write-mode-enable-auto key. +const wantAbsentAutoWriteMode = sctypes.Auto + +// scWriteModeDivergence is the tail of every theDivergences entry. sc-write-mode +// belongs on those lists here, because the section declares memiavl_only and a +// node missing the key runs auto instead. +var scWriteModeDivergence = []string{FlagSCWriteMode} + +// scWriteModeANodeRuns is what sc-write-mode resolves to for a file carrying no +// keys at all. +const scWriteModeANodeRuns = "auto" diff --git a/app/write_mode_mock_chain_validation_test.go b/app/write_mode_mock_chain_validation_test.go new file mode 100644 index 0000000000..2501d8c739 --- /dev/null +++ b/app/write_mode_mock_chain_validation_test.go @@ -0,0 +1,22 @@ +//go:build mock_chain_validation + +package app + +import ( + sctypes "github.com/sei-protocol/sei-chain/sei-db/state_db/sc/types" +) + +// See write_mode_default_test.go for what these four values are and why each +// build states them separately. This build declares sc-write-mode-enable-auto +// false, so an explicit sc-write-mode is honored rather than replaced by auto. + +const stateCommitRecord = "state-commit.reserve" + +const wantAbsentAutoWriteMode = sctypes.MemiavlOnly + +// scWriteModeDivergence is empty here. The section declares memiavl_only and a +// node missing the key runs memiavl_only, so this is the one build on which +// sc-write-mode is not a setting whose declared and running values disagree. +var scWriteModeDivergence []string + +const scWriteModeANodeRuns = "memiavl_only" diff --git a/sei-cosmos/server/config/config_fuzz_test.go b/sei-cosmos/server/config/config_fuzz_test.go index 04ef74c8a4..591bafcba2 100644 --- a/sei-cosmos/server/config/config_fuzz_test.go +++ b/sei-cosmos/server/config/config_fuzz_test.go @@ -224,8 +224,8 @@ func FuzzGetConfigGRPCDurationClamps(f *testing.F) { // FuzzGetConfigWriteMode pins GetConfig's own copy of the write-mode resolution. // // The rules match app/seidb.go — always parse, then let sc-write-mode-enable-auto -// (default true, flipped only by an explicit key) decide whether the parsed mode is -// honored — but the mechanism differs: GetConfig returns an error where seidb.go +// (the in-code default, flipped only by an explicit key) decide whether the parsed +// mode is honored — but the mechanism differs: GetConfig returns an error where seidb.go // panics. Both parsers must agree on the resolved mode for a node's store choice // and its reported config to describe the same thing, so the agreement is asserted // against the shared helpers rather than restated. @@ -261,7 +261,7 @@ func FuzzGetConfigWriteMode(f *testing.F) { t.Fatalf("sc-write-mode = %q must parse, got %v", mode, err) } - effectiveAuto := true + effectiveAuto := config.DefaultStateCommitConfig().WriteModeEnableAuto if setAuto { effectiveAuto = auto } @@ -743,7 +743,7 @@ func TestDefaultsMatchTheRecordedValues(t *testing.T) { // the other leaves that other one red. configtest.CheckDefaults(t, "state-sync", DefaultConfig().StateSync) - configtest.CheckDefaults(t, "server_config", DefaultConfig(), + configtest.CheckDefaults(t, serverConfigRecord, DefaultConfig(), configtest.DerivedDefault{ Path: "ConcurrencyWorkers", Want: max(10, min(runtime.NumCPU()*2, 128)), Why: "max(10, min(runtime.NumCPU()*2, 128))", diff --git a/sei-cosmos/server/config/config_test.go b/sei-cosmos/server/config/config_test.go index 38a1abc0b9..554d3665fc 100644 --- a/sei-cosmos/server/config/config_test.go +++ b/sei-cosmos/server/config/config_test.go @@ -640,88 +640,9 @@ func TestGetConfigRejectsInvalidWriteMode(t *testing.T) { require.Contains(t, err.Error(), "bogus_mode") } -// TestGetConfigLegacyMemiavlOnlyResolvesToAuto guards the existing-fleet -// upgrade path: a config written by an older binary carries an explicit -// sc-write-mode = "memiavl_only" but no sc-write-mode-enable-auto key. The absent -// key must default to true so the node resolves to auto and can follow a -// governance-driven migration without any app.toml edit. -func TestGetConfigLegacyMemiavlOnlyResolvesToAuto(t *testing.T) { - v := viper.New() - - v.Set("minimum-gas-prices", DefaultMinGasPrices) - v.Set("telemetry.global-labels", []interface{}{}) - v.Set("state-commit.sc-write-mode", "memiavl_only") - - cfg, err := GetConfig(v) - require.NoError(t, err) - require.True(t, cfg.StateCommit.WriteModeEnableAuto) - require.Equal(t, sctypes.Auto, cfg.StateCommit.WriteMode, - "absent sc-write-mode-enable-auto must default to true and override an explicit memiavl_only") -} - -func TestGetConfigLegacyCosmosOnlyResolvesToAuto(t *testing.T) { - v := viper.New() - - v.Set("minimum-gas-prices", DefaultMinGasPrices) - v.Set("telemetry.global-labels", []interface{}{}) - v.Set("state-commit.sc-write-mode", "cosmos_only") - - cfg, err := GetConfig(v) - require.NoError(t, err) - require.True(t, cfg.StateCommit.WriteModeEnableAuto) - require.Equal(t, sctypes.Auto, cfg.StateCommit.WriteMode, - "v6.4/v6.5 app.toml files with cosmos_only must parse before auto mode is applied") -} - -// TestGetConfigPinnedModeRequiresAutoDisabled verifies that an explicit -// sc-write-mode is only honored when sc-write-mode-enable-auto = false. With auto -// enabled (the default), the explicit mode is ignored and the node runs in auto. -func TestGetConfigPinnedModeRequiresAutoDisabled(t *testing.T) { - for _, mode := range []sctypes.WriteMode{ - sctypes.FlatKVOnly, - sctypes.EVMMigrated, - sctypes.TestOnlyDualWrite, - } { - t.Run(string(mode)+"/auto-disabled-pins", func(t *testing.T) { - v := viper.New() - v.Set("minimum-gas-prices", DefaultMinGasPrices) - v.Set("telemetry.global-labels", []interface{}{}) - v.Set("state-commit.sc-write-mode-enable-auto", false) - v.Set("state-commit.sc-write-mode", string(mode)) - - cfg, err := GetConfig(v) - require.NoError(t, err) - require.False(t, cfg.StateCommit.WriteModeEnableAuto) - require.Equal(t, mode, cfg.StateCommit.WriteMode, - "with auto disabled the explicit mode must be honored as a pin") - }) - - t.Run(string(mode)+"/auto-enabled-overrides", func(t *testing.T) { - v := viper.New() - v.Set("minimum-gas-prices", DefaultMinGasPrices) - v.Set("telemetry.global-labels", []interface{}{}) - v.Set("state-commit.sc-write-mode", string(mode)) - - cfg, err := GetConfig(v) - require.NoError(t, err) - require.True(t, cfg.StateCommit.WriteModeEnableAuto) - require.Equal(t, sctypes.Auto, cfg.StateCommit.WriteMode, - "with auto enabled (default) the explicit mode must be ignored in favor of auto") - }) - } -} - -func TestGetConfigEmptyWriteModeUsesDefault(t *testing.T) { - v := viper.New() - - v.Set("minimum-gas-prices", DefaultMinGasPrices) - v.Set("telemetry.global-labels", []interface{}{}) - - cfg, err := GetConfig(v) - require.NoError(t, err) - require.Equal(t, sctypes.Auto, cfg.StateCommit.WriteMode, - "unset sc-write-mode must fall back to the in-code default") -} +// The write-mode resolution records live in write_mode_default_test.go and its +// mock_chain_validation counterpart: the reserve build defaults +// sc-write-mode-enable-auto the other way, so every resolution differs by build. func TestGetConfigStateStore(t *testing.T) { v := viper.New() @@ -758,17 +679,6 @@ func TestGetConfigStateStore(t *testing.T) { require.True(t, cfg.StateStore.SeparateEVMSubDBs) } -func TestDefaultStateCommitConfig(t *testing.T) { - cfg := DefaultConfig() - - require.True(t, cfg.StateCommit.Enable) - require.Empty(t, cfg.StateCommit.Directory) - // WriteMode is the fixed fallback (memiavl_only); WriteModeEnableAuto - // defaults true, so the effective default after resolution is auto. - require.Equal(t, sctypes.MemiavlOnly, cfg.StateCommit.WriteMode) - require.True(t, cfg.StateCommit.WriteModeEnableAuto) -} - func TestDefaultStateStoreConfig(t *testing.T) { cfg := DefaultConfig() diff --git a/sei-cosmos/server/config/testdata/server_config.reserve.golden b/sei-cosmos/server/config/testdata/server_config.reserve.golden new file mode 100644 index 0000000000..a63756118c --- /dev/null +++ b/sei-cosmos/server/config/testdata/server_config.reserve.golden @@ -0,0 +1,149 @@ +MinGasPrices = string("0.01usei") +Pruning = string("nothing") +PruningKeepRecent = string("0") +PruningKeepEvery = string("0") +PruningInterval = string("0") +HaltHeight = uint64(0) +FreezeHeight = uint64(0) +HaltTime = uint64(0) +MinRetainBlocks = uint64(0) +InterBlockCache = bool(true) +IndexEvents = +CompactionInterval = uint64(0) +ConcurrencyWorkers = +OccEnabled = bool(true) +Telemetry.ServiceName = string("") +Telemetry.Enabled = bool(true) +Telemetry.EnableHostname = bool(false) +Telemetry.EnableHostnameLabel = bool(false) +Telemetry.EnableServiceLabel = bool(false) +Telemetry.PrometheusRetentionTime = int64(7200) +Telemetry.GlobalLabels = +API.Enable = bool(false) +API.Swagger = bool(true) +API.EnableUnsafeCORS = bool(false) +API.Address = string("tcp://0.0.0.0:1317") +API.MaxOpenConnections = uint(1000) +API.RPCReadTimeout = uint(10) +API.RPCWriteTimeout = uint(0) +API.RPCMaxBodyBytes = uint(1000000) +GRPC.Enable = bool(true) +GRPC.Address = string("0.0.0.0:9090") +GRPC.MaxRecvMsgSize = int(4194304) +GRPC.MaxOpenConnections = uint(1000) +GRPC.MaxConnectionIdle = time.Duration(5m0s) +GRPC.MaxConnectionAge = time.Duration(0s) +GRPC.MaxConnectionAgeGrace = time.Duration(0s) +GRPC.KeepaliveTime = time.Duration(2h0m0s) +GRPC.KeepaliveTimeout = time.Duration(20s) +GRPC.KeepaliveMinTime = time.Duration(5m0s) +GRPC.KeepalivePermitWithoutStream = bool(false) +Rosetta.Address = string(":8080") +Rosetta.Blockchain = string("app") +Rosetta.Network = string("network") +Rosetta.Retries = int(3) +Rosetta.Enable = bool(false) +Rosetta.Offline = bool(false) +GRPCWeb.Enable = bool(true) +GRPCWeb.Address = string("0.0.0.0:9091") +GRPCWeb.EnableUnsafeCORS = bool(false) +GRPCWeb.MaxOpenConnections = uint(1000) +Query.DisableLimits = bool(false) +Query.TrustedCIDRs = +Query.MaxLimit = uint64(1000) +Query.MaxOffset = uint64(10000) +Query.MaxIterations = uint64(11000) +StateSync.SnapshotInterval = uint64(0) +StateSync.SnapshotKeepRecent = uint32(2) +StateSync.SnapshotDirectory = string("") +StateCommit.Enable = bool(true) +StateCommit.Directory = string("") +StateCommit.AsyncCommitBuffer = int(0) +StateCommit.WriteMode = types.WriteMode("memiavl_only") +StateCommit.WriteModeEnableAuto = bool(false) +StateCommit.MemIAVLConfig.AsyncCommitBuffer = int(100) +StateCommit.MemIAVLConfig.SnapshotKeepRecent = uint32(1) +StateCommit.MemIAVLConfig.SnapshotInterval = uint32(10000) +StateCommit.MemIAVLConfig.SnapshotMinTimeInterval = uint32(3600) +StateCommit.MemIAVLConfig.SnapshotWriterLimit = int(4) +StateCommit.MemIAVLConfig.SnapshotPrefetchThreshold = float64(0.8) +StateCommit.MemIAVLConfig.SnapshotWriteRateMBps = int(100) +StateCommit.FlatKVConfig.DataDir = string("") +StateCommit.FlatKVConfig.Fsync = bool(false) +StateCommit.FlatKVConfig.AsyncWriteBuffer = int(0) +StateCommit.FlatKVConfig.SnapshotInterval = uint32(10000) +StateCommit.FlatKVConfig.SnapshotKeepRecent = uint32(1) +StateCommit.FlatKVConfig.ExternalPruning = bool(false) +StateCommit.FlatKVConfig.EnablePebbleMetrics = bool(true) +StateCommit.FlatKVConfig.EnableReadWriteMetrics = bool(false) +StateCommit.FlatKVConfig.AccountDBConfig.DataDir = string("") +StateCommit.FlatKVConfig.AccountDBConfig.EnableMetrics = bool(true) +StateCommit.FlatKVConfig.AccountDBConfig.EnableReadWriteMetrics = bool(false) +StateCommit.FlatKVConfig.AccountDBConfig.MetricsScrapeInterval = time.Duration(10s) +StateCommit.FlatKVConfig.AccountCacheConfig.ShardCount = uint64(8) +StateCommit.FlatKVConfig.AccountCacheConfig.MaxSize = uint64(1073741824) +StateCommit.FlatKVConfig.AccountCacheConfig.EstimatedOverheadPerEntry = uint64(250) +StateCommit.FlatKVConfig.AccountCacheConfig.MetricsName = string("") +StateCommit.FlatKVConfig.AccountCacheConfig.MetricsScrapeInterval = time.Duration(0s) +StateCommit.FlatKVConfig.CodeDBConfig.DataDir = string("") +StateCommit.FlatKVConfig.CodeDBConfig.EnableMetrics = bool(true) +StateCommit.FlatKVConfig.CodeDBConfig.EnableReadWriteMetrics = bool(false) +StateCommit.FlatKVConfig.CodeDBConfig.MetricsScrapeInterval = time.Duration(10s) +StateCommit.FlatKVConfig.CodeCacheConfig.ShardCount = uint64(8) +StateCommit.FlatKVConfig.CodeCacheConfig.MaxSize = uint64(536870912) +StateCommit.FlatKVConfig.CodeCacheConfig.EstimatedOverheadPerEntry = uint64(250) +StateCommit.FlatKVConfig.CodeCacheConfig.MetricsName = string("") +StateCommit.FlatKVConfig.CodeCacheConfig.MetricsScrapeInterval = time.Duration(0s) +StateCommit.FlatKVConfig.StorageDBConfig.DataDir = string("") +StateCommit.FlatKVConfig.StorageDBConfig.EnableMetrics = bool(true) +StateCommit.FlatKVConfig.StorageDBConfig.EnableReadWriteMetrics = bool(false) +StateCommit.FlatKVConfig.StorageDBConfig.MetricsScrapeInterval = time.Duration(10s) +StateCommit.FlatKVConfig.StorageCacheConfig.ShardCount = uint64(8) +StateCommit.FlatKVConfig.StorageCacheConfig.MaxSize = uint64(4294967296) +StateCommit.FlatKVConfig.StorageCacheConfig.EstimatedOverheadPerEntry = uint64(250) +StateCommit.FlatKVConfig.StorageCacheConfig.MetricsName = string("") +StateCommit.FlatKVConfig.StorageCacheConfig.MetricsScrapeInterval = time.Duration(0s) +StateCommit.FlatKVConfig.MiscDBConfig.DataDir = string("") +StateCommit.FlatKVConfig.MiscDBConfig.EnableMetrics = bool(true) +StateCommit.FlatKVConfig.MiscDBConfig.EnableReadWriteMetrics = bool(false) +StateCommit.FlatKVConfig.MiscDBConfig.MetricsScrapeInterval = time.Duration(10s) +StateCommit.FlatKVConfig.MiscCacheConfig.ShardCount = uint64(8) +StateCommit.FlatKVConfig.MiscCacheConfig.MaxSize = uint64(536870912) +StateCommit.FlatKVConfig.MiscCacheConfig.EstimatedOverheadPerEntry = uint64(250) +StateCommit.FlatKVConfig.MiscCacheConfig.MetricsName = string("") +StateCommit.FlatKVConfig.MiscCacheConfig.MetricsScrapeInterval = time.Duration(0s) +StateCommit.FlatKVConfig.ReaderThreadsPerCore = float64(2) +StateCommit.FlatKVConfig.ReaderConstantThreadCount = int(0) +StateCommit.FlatKVConfig.ReaderPoolQueueSize = int(1024) +StateCommit.FlatKVConfig.MiscPoolThreadsPerCore = float64(4) +StateCommit.FlatKVConfig.MiscConstantThreadCount = int(0) +StateCommit.FlatKVConfig.LtHashThreadsPerCore = float64(1) +StateCommit.HistoricalProofMaxInFlight = int(1) +StateCommit.HistoricalProofRateLimit = float64(1) +StateCommit.HistoricalProofBurst = int(1) +StateCommit.HashLogger.Enable = bool(true) +StateCommit.HashLogger.Directory = string("") +StateCommit.HashLogger.BlocksToRetain = uint(0) +StateCommit.HashLogger.TargetFileSize = uint(16777216) +StateCommit.HashLogger.MaxDiskSize = uint(17179869184) +StateCommit.HashLogger.Version = string("") +StateStore.Enable = bool(true) +StateStore.DBDirectory = string("") +StateStore.Backend = string("pebbledb") +StateStore.AsyncWriteBuffer = int(100) +StateStore.KeepRecent = int(100000) +StateStore.PruneIntervalSeconds = int(600) +StateStore.ImportNumWorkers = int(1) +StateStore.EnableReadWriteMetrics = bool(false) +StateStore.KeepLastVersion = bool(true) +StateStore.UseDefaultComparer = bool(false) +StateStore.SnapshotEnable = bool(false) +StateStore.SnapshotInterval = int64(0) +StateStore.SnapshotKeepRecent = int(0) +StateStore.SnapshotMinTimeInterval = time.Duration(0s) +StateStore.ExternalPruning = bool(false) +StateStore.EVMSplit = bool(false) +StateStore.EVMDBDirectory = string("") +StateStore.SeparateEVMSubDBs = bool(false) +Genesis.StreamImport = bool(false) +Genesis.GenesisStreamFile = string("") diff --git a/sei-cosmos/server/config/write_mode_default_test.go b/sei-cosmos/server/config/write_mode_default_test.go new file mode 100644 index 0000000000..2ed243260a --- /dev/null +++ b/sei-cosmos/server/config/write_mode_default_test.go @@ -0,0 +1,112 @@ +//go:build !mock_chain_validation + +package config + +import ( + "testing" + + "github.com/spf13/viper" + "github.com/stretchr/testify/require" + + sctypes "github.com/sei-protocol/sei-chain/sei-db/state_db/sc/types" +) + +// serverConfigRecord names this build's defaults record. The two builds declare +// different defaults, so they keep separate records: a shared one would be +// rewritten by whichever build regenerated it last, and the value it lost is the +// one the reviewer needed to see. +const serverConfigRecord = "server_config" + +// TestGetConfigLegacyMemiavlOnlyResolvesToAuto guards the existing-fleet +// upgrade path: a config written by an older binary carries an explicit +// sc-write-mode = "memiavl_only" but no sc-write-mode-enable-auto key. The absent +// key must default to true so the node resolves to auto and can follow a +// governance-driven migration without any app.toml edit. +func TestGetConfigLegacyMemiavlOnlyResolvesToAuto(t *testing.T) { + v := viper.New() + + v.Set("minimum-gas-prices", DefaultMinGasPrices) + v.Set("telemetry.global-labels", []interface{}{}) + v.Set("state-commit.sc-write-mode", "memiavl_only") + + cfg, err := GetConfig(v) + require.NoError(t, err) + require.True(t, cfg.StateCommit.WriteModeEnableAuto) + require.Equal(t, sctypes.Auto, cfg.StateCommit.WriteMode, + "absent sc-write-mode-enable-auto must default to true and override an explicit memiavl_only") +} + +func TestGetConfigLegacyCosmosOnlyResolvesToAuto(t *testing.T) { + v := viper.New() + + v.Set("minimum-gas-prices", DefaultMinGasPrices) + v.Set("telemetry.global-labels", []interface{}{}) + v.Set("state-commit.sc-write-mode", "cosmos_only") + + cfg, err := GetConfig(v) + require.NoError(t, err) + require.True(t, cfg.StateCommit.WriteModeEnableAuto) + require.Equal(t, sctypes.Auto, cfg.StateCommit.WriteMode, + "v6.4/v6.5 app.toml files with cosmos_only must parse before auto mode is applied") +} + +// TestGetConfigPinnedModeRequiresAutoDisabled verifies that an explicit +// sc-write-mode is only honored when sc-write-mode-enable-auto = false. With auto +// enabled (the default), the explicit mode is ignored and the node runs in auto. +func TestGetConfigPinnedModeRequiresAutoDisabled(t *testing.T) { + for _, mode := range []sctypes.WriteMode{ + sctypes.FlatKVOnly, + sctypes.EVMMigrated, + sctypes.TestOnlyDualWrite, + } { + t.Run(string(mode)+"/auto-disabled-pins", func(t *testing.T) { + v := viper.New() + v.Set("minimum-gas-prices", DefaultMinGasPrices) + v.Set("telemetry.global-labels", []interface{}{}) + v.Set("state-commit.sc-write-mode-enable-auto", false) + v.Set("state-commit.sc-write-mode", string(mode)) + + cfg, err := GetConfig(v) + require.NoError(t, err) + require.False(t, cfg.StateCommit.WriteModeEnableAuto) + require.Equal(t, mode, cfg.StateCommit.WriteMode, + "with auto disabled the explicit mode must be honored as a pin") + }) + + t.Run(string(mode)+"/auto-enabled-overrides", func(t *testing.T) { + v := viper.New() + v.Set("minimum-gas-prices", DefaultMinGasPrices) + v.Set("telemetry.global-labels", []interface{}{}) + v.Set("state-commit.sc-write-mode", string(mode)) + + cfg, err := GetConfig(v) + require.NoError(t, err) + require.True(t, cfg.StateCommit.WriteModeEnableAuto) + require.Equal(t, sctypes.Auto, cfg.StateCommit.WriteMode, + "with auto enabled (default) the explicit mode must be ignored in favor of auto") + }) + } +} + +func TestGetConfigEmptyWriteModeUsesDefault(t *testing.T) { + v := viper.New() + + v.Set("minimum-gas-prices", DefaultMinGasPrices) + v.Set("telemetry.global-labels", []interface{}{}) + + cfg, err := GetConfig(v) + require.NoError(t, err) + require.Equal(t, sctypes.Auto, cfg.StateCommit.WriteMode, + "unset sc-write-mode must fall back to the in-code default") +} + +func TestDefaultStateCommitConfig(t *testing.T) { + cfg := DefaultConfig() + + require.True(t, cfg.StateCommit.Enable) + require.Empty(t, cfg.StateCommit.Directory) + // WriteMode is the fixed fallback (memiavl_only); WriteModeEnableAuto + // defaults true, so the effective default after resolution is auto. + require.Equal(t, sctypes.MemiavlOnly, cfg.StateCommit.WriteMode) + require.True(t, cfg.StateCommit.WriteModeEnableAuto) +} diff --git a/sei-cosmos/server/config/write_mode_mock_chain_validation_test.go b/sei-cosmos/server/config/write_mode_mock_chain_validation_test.go new file mode 100644 index 0000000000..1314297d6a --- /dev/null +++ b/sei-cosmos/server/config/write_mode_mock_chain_validation_test.go @@ -0,0 +1,113 @@ +//go:build mock_chain_validation + +package config + +import ( + "testing" + + "github.com/spf13/viper" + "github.com/stretchr/testify/require" + + sctypes "github.com/sei-protocol/sei-chain/sei-db/state_db/sc/types" +) + +// These records mirror write_mode_default_test.go for the reserve build, where +// sc-write-mode-enable-auto defaults to false rather than true. The rule is +// unchanged — an absent key takes the in-code default, and the default decides +// whether an explicit sc-write-mode is honored — so every resolution below is +// the opposite of the stock one for the same input. + +// serverConfigRecord names this build's defaults record, separate from the stock +// build's so both declared values stay readable side by side. +const serverConfigRecord = "server_config.reserve" + +func TestGetConfigLegacyMemiavlOnlyIsPinnedOnAReserveBuild(t *testing.T) { + v := viper.New() + + v.Set("minimum-gas-prices", DefaultMinGasPrices) + v.Set("telemetry.global-labels", []interface{}{}) + v.Set("state-commit.sc-write-mode", "memiavl_only") + + cfg, err := GetConfig(v) + require.NoError(t, err) + require.False(t, cfg.StateCommit.WriteModeEnableAuto) + require.Equal(t, sctypes.MemiavlOnly, cfg.StateCommit.WriteMode, + "absent sc-write-mode-enable-auto must default to false and honor the explicit memiavl_only") +} + +func TestGetConfigLegacyCosmosOnlyIsPinnedOnAReserveBuild(t *testing.T) { + v := viper.New() + + v.Set("minimum-gas-prices", DefaultMinGasPrices) + v.Set("telemetry.global-labels", []interface{}{}) + v.Set("state-commit.sc-write-mode", "cosmos_only") + + cfg, err := GetConfig(v) + require.NoError(t, err) + require.False(t, cfg.StateCommit.WriteModeEnableAuto) + require.Equal(t, sctypes.MemiavlOnly, cfg.StateCommit.WriteMode, + "v6.4/v6.5 app.toml files with cosmos_only must parse to memiavl_only and then be honored") +} + +// TestGetConfigPinnedModeNeedsNoAutoKeyOnAReserveBuild is the inverse of the +// stock TestGetConfigPinnedModeRequiresAutoDisabled: pinning needs no key here, +// and setting the key to true is what un-pins the node. assertReserveNodeAllowed +// refuses to start in that state. +func TestGetConfigPinnedModeNeedsNoAutoKeyOnAReserveBuild(t *testing.T) { + for _, mode := range []sctypes.WriteMode{ + sctypes.FlatKVOnly, + sctypes.EVMMigrated, + sctypes.TestOnlyDualWrite, + } { + t.Run(string(mode)+"/auto-key-absent-pins", func(t *testing.T) { + v := viper.New() + v.Set("minimum-gas-prices", DefaultMinGasPrices) + v.Set("telemetry.global-labels", []interface{}{}) + v.Set("state-commit.sc-write-mode", string(mode)) + + cfg, err := GetConfig(v) + require.NoError(t, err) + require.False(t, cfg.StateCommit.WriteModeEnableAuto) + require.Equal(t, mode, cfg.StateCommit.WriteMode, + "with auto defaulted off the explicit mode must be honored as a pin") + }) + + t.Run(string(mode)+"/auto-enabled-overrides", func(t *testing.T) { + v := viper.New() + v.Set("minimum-gas-prices", DefaultMinGasPrices) + v.Set("telemetry.global-labels", []interface{}{}) + v.Set("state-commit.sc-write-mode-enable-auto", true) + v.Set("state-commit.sc-write-mode", string(mode)) + + cfg, err := GetConfig(v) + require.NoError(t, err) + require.True(t, cfg.StateCommit.WriteModeEnableAuto) + require.Equal(t, sctypes.Auto, cfg.StateCommit.WriteMode, + "an explicit sc-write-mode-enable-auto = true must still win over the default") + }) + } +} + +func TestGetConfigEmptyWriteModeUsesDefaultOnAReserveBuild(t *testing.T) { + v := viper.New() + + v.Set("minimum-gas-prices", DefaultMinGasPrices) + v.Set("telemetry.global-labels", []interface{}{}) + + cfg, err := GetConfig(v) + require.NoError(t, err) + require.Equal(t, sctypes.MemiavlOnly, cfg.StateCommit.WriteMode, + "an app.toml carrying neither key must resolve to a pinned memiavl_only, "+ + "which is what lets a reserve start with no configuration at all") +} + +func TestDefaultStateCommitConfigOnAReserveBuild(t *testing.T) { + cfg := DefaultConfig() + + require.True(t, cfg.StateCommit.Enable) + require.Empty(t, cfg.StateCommit.Directory) + // WriteMode is the same fixed fallback as the stock build; only + // WriteModeEnableAuto moves, and it is what makes that fallback effective. + require.Equal(t, sctypes.MemiavlOnly, cfg.StateCommit.WriteMode) + require.False(t, cfg.StateCommit.WriteModeEnableAuto) +} diff --git a/sei-cosmos/server/reserve_policy_default.go b/sei-cosmos/server/reserve_policy_default.go new file mode 100644 index 0000000000..1bda347baa --- /dev/null +++ b/sei-cosmos/server/reserve_policy_default.go @@ -0,0 +1,10 @@ +//go:build !mock_chain_validation + +package server + +import sctypes "github.com/sei-protocol/sei-chain/sei-db/state_db/sc/types" + +// assertReserveNodeAllowed reports whether a node may start with the given +// Tendermint mode and effective state-commit write mode. Production builds +// accept every combination the configuration itself accepts. +func assertReserveNodeAllowed(string, sctypes.WriteMode) error { return nil } diff --git a/sei-cosmos/server/reserve_policy_default_test.go b/sei-cosmos/server/reserve_policy_default_test.go new file mode 100644 index 0000000000..0403b1e39d --- /dev/null +++ b/sei-cosmos/server/reserve_policy_default_test.go @@ -0,0 +1,41 @@ +//go:build !mock_chain_validation + +package server + +import ( + "testing" + + "github.com/stretchr/testify/require" + + sctypes "github.com/sei-protocol/sei-chain/sei-db/state_db/sc/types" + tmcfg "github.com/sei-protocol/sei-chain/sei-tendermint/config" +) + +// A production build must not refuse any mode/write-mode combination; the +// reserve guard exists only in the mock_chain_validation build. +func TestAssertReserveNodeAllowed_Default_AcceptsEveryCombination(t *testing.T) { + for _, nodeMode := range allNodeModes() { + for _, writeMode := range allSCWriteModes() { + require.NoError(t, assertReserveNodeAllowed(nodeMode, writeMode), + "mode %q with write mode %q must be accepted by a production build", nodeMode, writeMode) + } + } +} + +func allNodeModes() []string { + return []string{tmcfg.ModeFull, tmcfg.ModeValidator, tmcfg.ModeSeed} +} + +func allSCWriteModes() []sctypes.WriteMode { + return []sctypes.WriteMode{ + sctypes.MemiavlOnly, + sctypes.MigrateEVM, + sctypes.EVMMigrated, + sctypes.MigrateAllButBank, + sctypes.AllMigratedButBank, + sctypes.MigrateBank, + sctypes.FlatKVOnly, + sctypes.TestOnlyDualWrite, + sctypes.Auto, + } +} diff --git a/sei-cosmos/server/reserve_policy_mock_chain_validation.go b/sei-cosmos/server/reserve_policy_mock_chain_validation.go new file mode 100644 index 0000000000..69149ddd13 --- /dev/null +++ b/sei-cosmos/server/reserve_policy_mock_chain_validation.go @@ -0,0 +1,46 @@ +//go:build mock_chain_validation + +package server + +import ( + "fmt" + + sctypes "github.com/sei-protocol/sei-chain/sei-db/state_db/sc/types" + tmcfg "github.com/sei-protocol/sei-chain/sei-tendermint/config" +) + +// assertReserveNodeAllowed reports whether a node may start with the given +// Tendermint mode and effective state-commit write mode. This build starts +// only as a non-validator pinned to memiavl_only. +// +// Both conditions are what make the build a reserve rather than a liability. +// The consensus policy compiled in here swallows ErrAppHash and the +// validator-set sentinels, so a validator running it would prevote and +// precommit blocks whose app hash contradicts its own state instead of +// prevoting nil. And a node that is not pinned joins the migration on the +// first block after governance raises the batch size, which spends the reserve +// with nothing to signal that it happened. +// +// Refusing here rather than trusting app.toml costs a restart to discover and +// saves finding out at the first divergent block. +func assertReserveNodeAllowed(nodeMode string, writeMode sctypes.WriteMode) error { + if nodeMode == tmcfg.ModeValidator { + return fmt.Errorf( + "mock_chain_validation builds must not run as a validator: this build "+ + "swallows app-hash and validator-set validation failures, so it cannot "+ + "safely vote; set mode = %q in config.toml", + tmcfg.ModeFull, + ) + } + if writeMode != sctypes.MemiavlOnly { + return fmt.Errorf( + "mock_chain_validation builds must run %[1]q, got %[2]q: set "+ + "state-commit.sc-write-mode = %[1]q and "+ + "state-commit.sc-write-mode-enable-auto = false in app.toml (the latter "+ + "is absent from a generated app.toml and defaults to true, which "+ + "discards the former)", + sctypes.MemiavlOnly, writeMode, + ) + } + return nil +} diff --git a/sei-cosmos/server/reserve_policy_mock_chain_validation_test.go b/sei-cosmos/server/reserve_policy_mock_chain_validation_test.go new file mode 100644 index 0000000000..238291b608 --- /dev/null +++ b/sei-cosmos/server/reserve_policy_mock_chain_validation_test.go @@ -0,0 +1,62 @@ +//go:build mock_chain_validation + +package server + +import ( + "testing" + + "github.com/stretchr/testify/require" + + sctypes "github.com/sei-protocol/sei-chain/sei-db/state_db/sc/types" + tmcfg "github.com/sei-protocol/sei-chain/sei-tendermint/config" +) + +// Exactly one combination may start: a non-validator pinned to memiavl_only. +func TestAssertReserveNodeAllowed_MockChainValidation_Matrix(t *testing.T) { + for _, nodeMode := range allNodeModes() { + for _, writeMode := range allSCWriteModes() { + allowed := nodeMode != tmcfg.ModeValidator && writeMode == sctypes.MemiavlOnly + err := assertReserveNodeAllowed(nodeMode, writeMode) + if allowed { + require.NoError(t, err, "mode %q with write mode %q must be accepted", nodeMode, writeMode) + continue + } + require.Error(t, err, "mode %q with write mode %q must be refused", nodeMode, writeMode) + } + } +} + +// Validator mode is refused whatever the write mode, so a correctly pinned +// validator does not slip through. +func TestAssertReserveNodeAllowed_MockChainValidation_RefusesPinnedValidator(t *testing.T) { + err := assertReserveNodeAllowed(tmcfg.ModeValidator, sctypes.MemiavlOnly) + require.Error(t, err) + require.Contains(t, err.Error(), tmcfg.ModeFull) +} + +// Auto is what a forgotten sc-write-mode-enable-auto resolves to, and it is the +// failure the write-mode half of this guard exists for, so the message must +// name that key. +func TestAssertReserveNodeAllowed_MockChainValidation_ErrorNamesTheAutoKey(t *testing.T) { + err := assertReserveNodeAllowed(tmcfg.ModeFull, sctypes.Auto) + require.Error(t, err) + require.Contains(t, err.Error(), "sc-write-mode-enable-auto") +} + +func allNodeModes() []string { + return []string{tmcfg.ModeFull, tmcfg.ModeValidator, tmcfg.ModeSeed} +} + +func allSCWriteModes() []sctypes.WriteMode { + return []sctypes.WriteMode{ + sctypes.MemiavlOnly, + sctypes.MigrateEVM, + sctypes.EVMMigrated, + sctypes.MigrateAllButBank, + sctypes.AllMigratedButBank, + sctypes.MigrateBank, + sctypes.FlatKVOnly, + sctypes.TestOnlyDualWrite, + sctypes.Auto, + } +} diff --git a/sei-cosmos/server/start.go b/sei-cosmos/server/start.go index ff9e416b75..5f9d437b07 100644 --- a/sei-cosmos/server/start.go +++ b/sei-cosmos/server/start.go @@ -312,6 +312,9 @@ func startInProcess( if err := config.ValidateFreeze(); err != nil { return err } + if err := assertReserveNodeAllowed(cfg.Mode, config.StateCommit.WriteMode); err != nil { + return err + } gRPCOnly := ctx.Viper.GetBool(flagGRPCOnly) if gRPCOnly && config.FreezeHeight > 0 { return errors.New("freeze-height cannot be used with grpc-only mode") diff --git a/sei-db/config/reserve_defaults_default.go b/sei-db/config/reserve_defaults_default.go new file mode 100644 index 0000000000..3d4755a233 --- /dev/null +++ b/sei-db/config/reserve_defaults_default.go @@ -0,0 +1,11 @@ +//go:build !mock_chain_validation + +package config + +// applyReserveDefaults returns the state-commit defaults for this build's node +// role. Production builds have no reserve role and use the defaults unchanged. +func applyReserveDefaults(cfg StateCommitConfig) StateCommitConfig { return cfg } + +// reserveStateCommitConfigTemplate is the app.toml fragment this build adds to +// the [state-commit] section. Production builds add nothing. +const reserveStateCommitConfigTemplate = "" diff --git a/sei-db/config/reserve_defaults_default_test.go b/sei-db/config/reserve_defaults_default_test.go new file mode 100644 index 0000000000..4872de1e3e --- /dev/null +++ b/sei-db/config/reserve_defaults_default_test.go @@ -0,0 +1,31 @@ +//go:build !mock_chain_validation + +package config + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/sei-protocol/sei-chain/sei-db/state_db/sc/types" +) + +func TestDefaultStateCommitConfigWriteMode(t *testing.T) { + cfg := DefaultStateCommitConfig() + // The raw default is the fixed fallback; auto comes from WriteModeEnableAuto + // via ApplyWriteModeAuto at the config-parse boundary. + require.Equal(t, types.MemiavlOnly, cfg.WriteMode) + require.True(t, cfg.WriteModeEnableAuto) +} + +// TestStateCommitConfigTemplateOmitsWriteModeEnableAuto records that a stock +// app.toml carries no sc-write-mode-enable-auto key. An operator who wants to pin +// a mode has to add the key by hand; the key's absence is what makes an unedited +// config resolve to auto. The template does name the key in a comment, so the +// assertion is on assignments in the rendered file rather than on the text. +func TestStateCommitConfigTemplateOmitsWriteModeEnableAuto(t *testing.T) { + require.Empty(t, reserveStateCommitConfigTemplate) + require.NotContains(t, renderedAssignments(t), "sc-write-mode-enable-auto") + require.Contains(t, renderedAssignments(t), "sc-write-mode", + "sc-write-mode itself is rendered, which is why its companion key reads as a defaulted one") +} diff --git a/sei-db/config/reserve_defaults_mock_chain_validation.go b/sei-db/config/reserve_defaults_mock_chain_validation.go new file mode 100644 index 0000000000..3ac0d4ce6d --- /dev/null +++ b/sei-db/config/reserve_defaults_mock_chain_validation.go @@ -0,0 +1,31 @@ +//go:build mock_chain_validation + +package config + +// applyReserveDefaults returns the state-commit defaults for this build's node +// role. This build starts only as a reserve, so it honors the explicit write +// mode rather than resolving to auto. +func applyReserveDefaults(cfg StateCommitConfig) StateCommitConfig { + // sc-write-mode-enable-auto is never rendered into app.toml, so this in-code + // default is what a node resolves against unless an operator adds the key by + // hand. At true the explicit sc-write-mode is discarded and the node joins a + // governance-driven migration on the first block after the batch size rises, + // which spends the reserve with nothing to signal that it happened. Flipping + // it here makes a reserve correct with no operator configuration at all; an + // explicit key in app.toml still wins, and assertReserveNodeAllowed refuses + // the node when it wins in the wrong direction. + cfg.WriteModeEnableAuto = false + return cfg +} + +// reserveStateCommitConfigTemplate is the app.toml fragment this build adds to +// the [state-commit] section. It renders sc-write-mode-enable-auto, which the +// stock template omits. +const reserveStateCommitConfigTemplate = ` +# sc-write-mode-enable-auto is rendered by this build and omitted by the stock +# one, because the two default it differently: false here, true there. At false +# the explicit sc-write-mode above is honored, which is what makes this node a +# reserve. Set it to true and this build refuses to start, since an unpinned +# node joins the migration and stops being a recovery source. +sc-write-mode-enable-auto = {{ .StateCommit.WriteModeEnableAuto }} +` diff --git a/sei-db/config/reserve_defaults_mock_chain_validation_test.go b/sei-db/config/reserve_defaults_mock_chain_validation_test.go new file mode 100644 index 0000000000..e2d4675706 --- /dev/null +++ b/sei-db/config/reserve_defaults_mock_chain_validation_test.go @@ -0,0 +1,30 @@ +//go:build mock_chain_validation + +package config + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/sei-protocol/sei-chain/sei-db/state_db/sc/types" +) + +func TestDefaultStateCommitConfigWriteModeOnAReserveBuild(t *testing.T) { + cfg := DefaultStateCommitConfig() + // The raw default is the same fixed fallback as the stock build. Only + // WriteModeEnableAuto moves, and at false ApplyWriteModeAuto leaves the + // fallback standing instead of replacing it with auto. + require.Equal(t, types.MemiavlOnly, cfg.WriteMode) + require.False(t, cfg.WriteModeEnableAuto) +} + +// TestStateCommitConfigTemplateRendersWriteModeEnableAuto records that this build +// writes the key the stock template omits. Rendering it is what keeps a generated +// app.toml truthful: the two builds default the key differently, so an absent key +// would describe the wrong node on one of them. +func TestStateCommitConfigTemplateRendersWriteModeEnableAuto(t *testing.T) { + require.NotEmpty(t, reserveStateCommitConfigTemplate) + require.Contains(t, renderedAssignments(t), "sc-write-mode-enable-auto") + require.Contains(t, renderedAssignments(t), "sc-write-mode") +} diff --git a/sei-db/config/reserve_defaults_test.go b/sei-db/config/reserve_defaults_test.go new file mode 100644 index 0000000000..4b5856581a --- /dev/null +++ b/sei-db/config/reserve_defaults_test.go @@ -0,0 +1,37 @@ +package config + +import ( + "bytes" + "strings" + "testing" + "text/template" + + "github.com/stretchr/testify/require" +) + +// renderedAssignments returns the keys StateCommitConfigTemplate assigns when it +// is rendered with the in-code defaults, one name per entry. Comments are dropped, +// so a key the template only discusses in prose is not mistaken for one it writes. +func renderedAssignments(t *testing.T) []string { + t.Helper() + + tmpl, err := template.New("sc").Parse(StateCommitConfigTemplate) + require.NoError(t, err) + + var rendered bytes.Buffer + require.NoError(t, tmpl.Execute(&rendered, struct{ StateCommit StateCommitConfig }{ + StateCommit: DefaultStateCommitConfig(), + })) + + var keys []string + for _, line := range strings.Split(rendered.String(), "\n") { + line = strings.TrimSpace(line) + if line == "" || strings.HasPrefix(line, "#") || strings.HasPrefix(line, "[") { + continue + } + key, _, ok := strings.Cut(line, "=") + require.True(t, ok, "unexpected non-assignment line in the rendered template: %q", line) + keys = append(keys, strings.TrimSpace(key)) + } + return keys +} diff --git a/sei-db/config/sc_config.go b/sei-db/config/sc_config.go index 6b3bda0a54..9682a21205 100644 --- a/sei-db/config/sc_config.go +++ b/sei-db/config/sc_config.go @@ -109,7 +109,7 @@ type StateCommitConfig struct { // DefaultStateCommitConfig returns the default StateCommitConfig func DefaultStateCommitConfig() StateCommitConfig { - return StateCommitConfig{ + return applyReserveDefaults(StateCommitConfig{ Enable: true, WriteMode: types.MemiavlOnly, WriteModeEnableAuto: true, @@ -119,7 +119,7 @@ func DefaultStateCommitConfig() StateCommitConfig { HistoricalProofRateLimit: DefaultSCHistoricalProofRateLimit, HistoricalProofBurst: DefaultSCHistoricalProofBurst, HashLogger: DefaultHashLoggerConfig(), - } + }) } // ApplyWriteModeAuto resolves the effective write mode from the diff --git a/sei-db/config/sc_config_test.go b/sei-db/config/sc_config_test.go index 3e3a13fc1f..75d79f3c37 100644 --- a/sei-db/config/sc_config_test.go +++ b/sei-db/config/sc_config_test.go @@ -32,13 +32,9 @@ func TestApplyWriteModeAuto(t *testing.T) { } } -func TestDefaultStateCommitConfigWriteMode(t *testing.T) { - cfg := DefaultStateCommitConfig() - // The raw default is the fixed fallback; auto comes from WriteModeEnableAuto - // via ApplyWriteModeAuto at the config-parse boundary. - require.Equal(t, types.MemiavlOnly, cfg.WriteMode) - require.True(t, cfg.WriteModeEnableAuto) -} +// The WriteMode default records live in reserve_defaults_default_test.go and its +// mock_chain_validation counterpart, which default WriteModeEnableAuto the two +// opposite ways. func TestParseSCWriteMode(t *testing.T) { parsed, err := ParseSCWriteMode("cosmos_only") diff --git a/sei-db/config/toml.go b/sei-db/config/toml.go index 34e0d8b42b..158ed5bc47 100644 --- a/sei-db/config/toml.go +++ b/sei-db/config/toml.go @@ -1,7 +1,14 @@ package config -// StateCommitConfigTemplate defines the configuration template for state-commit -const StateCommitConfigTemplate = ` +// StateCommitConfigTemplate defines the configuration template for state-commit. +// The reserve fragment sits between the two halves so that the keys it adds land +// inside [state-commit] rather than in the [state-commit.flatkv] subsection the +// section ends with. +const StateCommitConfigTemplate = stateCommitConfigTemplateHead + + reserveStateCommitConfigTemplate + + stateCommitConfigTemplateTail + +const stateCommitConfigTemplateHead = ` ############################################################################### ### State Commit Configuration ### ############################################################################### @@ -68,7 +75,9 @@ sc-snapshot-write-rate-mbps = {{ .StateCommit.MemIAVLConfig.SnapshotWriteRateMBp # Valid values: memiavl_only, migrate_evm, evm_migrated, migrate_all_but_bank, # all_migrated_but_bank, migrate_bank, flatkv_only, test_only_dual_write, auto. sc-write-mode = "{{ .StateCommit.WriteMode }}" +` +const stateCommitConfigTemplateTail = ` # HashLogger records a per-block CSV of named hashes (memIAVL module/root hashes, flatKV DB/root # hashes, the app hash, the block hash, and the changeset hash) so block-hash computation can be # studied and compared across nodes. It is a debugging/forensics tool; enabled by default.