diff --git a/sei-tendermint/config/statesync_compat_test.go b/sei-tendermint/config/statesync_compat_test.go new file mode 100644 index 0000000000..3af54cff44 --- /dev/null +++ b/sei-tendermint/config/statesync_compat_test.go @@ -0,0 +1,98 @@ +package config_test + +import ( + "os" + "path/filepath" + "testing" + "time" + + "github.com/spf13/viper" + "github.com/stretchr/testify/require" + + "github.com/sei-protocol/sei-chain/sei-tendermint/cmd/tendermint/commands" + tmconfig "github.com/sei-protocol/sei-chain/sei-tendermint/config" +) + +// This test (and TestFreshStateSyncConfigValidatesWithoutHiddenKnobs) mutate the +// global viper singleton via commands.ParseConfig, so they must not run in +// parallel with other tests in this package. + +func TestHiddenStateSyncKnobsStillParseFromExistingConfig(t *testing.T) { + viper.Reset() + t.Cleanup(viper.Reset) + + configPath := filepath.Join(t.TempDir(), "config.toml") + err := os.WriteFile(configPath, []byte(` +[statesync] +enable = true +rpc-servers = "localhost:26657,localhost:26659" +trust-height = 10 +trust-hash = "AABB" +trust-period = "168h0m0s" +backfill-blocks = "100" +backfill-duration = "5m0s" +discovery-time = "20s" +temp-dir = "/tmp/statesync" +chunk-request-timeout = "30s" +fetchers = "5" +verify-light-block-timeout = "2m0s" +blacklist-ttl = "10m0s" +`), 0600) + require.NoError(t, err) + + viper.SetConfigFile(configPath) + require.NoError(t, viper.ReadInConfig()) + + cfg, err := commands.ParseConfig(tmconfig.DefaultConfig()) + require.NoError(t, err) + require.True(t, cfg.StateSync.Enable) + require.Equal(t, []string{"localhost:26657", "localhost:26659"}, cfg.StateSync.RPCServers) + require.Equal(t, int64(10), cfg.StateSync.TrustHeight) + require.Equal(t, "AABB", cfg.StateSync.TrustHash) + require.Equal(t, int64(100), cfg.StateSync.BackfillBlocks) + require.Equal(t, 5*time.Minute, cfg.StateSync.BackfillDuration) + require.Equal(t, 20*time.Second, cfg.StateSync.DiscoveryTime) + require.Equal(t, "/tmp/statesync", cfg.StateSync.TempDir) + require.Equal(t, 30*time.Second, cfg.StateSync.ChunkRequestTimeout) + require.Equal(t, int32(5), cfg.StateSync.Fetchers) + require.Equal(t, 2*time.Minute, cfg.StateSync.VerifyLightBlockTimeout) + require.Equal(t, 10*time.Minute, cfg.StateSync.BlacklistTTL) +} + +// TestFreshStateSyncConfigValidatesWithoutHiddenKnobs mirrors the freshly-rendered +// template (no hidden tuning knobs in the file) and verifies that ParseConfig +// still produces a valid StateSyncConfig that passes ValidateBasic. This guards +// against future regressions where someone removes or zeros a default in +// DefaultStateSyncConfig without realizing the template no longer carries it. +func TestFreshStateSyncConfigValidatesWithoutHiddenKnobs(t *testing.T) { + viper.Reset() + t.Cleanup(viper.Reset) + + configPath := filepath.Join(t.TempDir(), "config.toml") + err := os.WriteFile(configPath, []byte(` +[statesync] +enable = true +rpc-servers = "localhost:26657,localhost:26659" +trust-height = 10 +trust-hash = "AABB" +trust-period = "168h0m0s" +`), 0600) + require.NoError(t, err) + + viper.SetConfigFile(configPath) + require.NoError(t, viper.ReadInConfig()) + + cfg, err := commands.ParseConfig(tmconfig.DefaultConfig()) + require.NoError(t, err) + + defaults := tmconfig.DefaultStateSyncConfig() + require.Equal(t, defaults.BackfillBlocks, cfg.StateSync.BackfillBlocks) + require.Equal(t, defaults.BackfillDuration, cfg.StateSync.BackfillDuration) + require.Equal(t, defaults.DiscoveryTime, cfg.StateSync.DiscoveryTime) + require.Equal(t, defaults.ChunkRequestTimeout, cfg.StateSync.ChunkRequestTimeout) + require.Equal(t, defaults.Fetchers, cfg.StateSync.Fetchers) + require.Equal(t, defaults.VerifyLightBlockTimeout, cfg.StateSync.VerifyLightBlockTimeout) + require.Equal(t, defaults.BlacklistTTL, cfg.StateSync.BlacklistTTL) + + require.NoError(t, cfg.StateSync.ValidateBasic()) +} diff --git a/sei-tendermint/config/toml.go b/sei-tendermint/config/toml.go index 63998e32bb..d6c00997d4 100644 --- a/sei-tendermint/config/toml.go +++ b/sei-tendermint/config/toml.go @@ -458,38 +458,18 @@ trust-hash = "{{ .StateSync.TrustHash }}" # period should suffice. trust-period = "{{ .StateSync.TrustPeriod }}" -# Backfill sequentially fetches after state sync completes, verifies and stores light blocks in reverse order. -# backfill-blocks means it will keep reverse fetching up to backfill-blocks number of blocks behind state sync position -# backfill-duration means it will keep fetching up to backfill-duration old time -# The actual backfill process will take at backfill-blocks as priority: -# - If backfill-blocks is set, use backfill-blocks to backfill -# - If backfill-blocks is not set to be greater than 0, use backfill-duration to backfill -backfill-blocks = "{{ .StateSync.BackfillBlocks }}" -backfill-duration = "{{ .StateSync.BackfillDuration }}" - -# Time to spend discovering snapshots before initiating a restore. -discovery-time = "{{ .StateSync.DiscoveryTime }}" - -# Temporary directory for state sync snapshot chunks, defaults to os.TempDir(). -# The synchronizer will create a new, randomly named directory within this directory -# and remove it when the sync is complete. -temp-dir = "{{ .StateSync.TempDir }}" - # Whether to use local snapshot only for state sync or not. # If this is true, then state sync will look for existing snapshots # which are located in the snapshot-dir configured in app.toml (default to [home-dir]/data/snapshots) use-local-snapshot = {{ .StateSync.UseLocalSnapshot }} -# The timeout duration before re-requesting a chunk, possibly from a different -# peer (default: 15 seconds). -chunk-request-timeout = "{{ .StateSync.ChunkRequestTimeout }}" - -# The number of concurrent chunk and block fetchers to run (default: 4). -fetchers = "{{ .StateSync.Fetchers }}" - -verify-light-block-timeout = "{{ .StateSync.VerifyLightBlockTimeout }}" - -blacklist-ttl = "{{ .StateSync.BlacklistTTL }}" +# Advanced state-sync tuning knobs are intentionally omitted from this template: +# - backfill-blocks, backfill-duration: post-sync historical light-block backfill +# - discovery-time, temp-dir: snapshot discovery and chunk staging +# - chunk-request-timeout, fetchers: chunk transfer behavior +# - verify-light-block-timeout, blacklist-ttl: light client verification +# They are still parsed if set explicitly here; see the Sei docs for the full list +# and recommended values. ####################################################################### ### Consensus Configuration Options ### diff --git a/sei-tendermint/config/toml_test.go b/sei-tendermint/config/toml_test.go index cf27c4484a..762a31af79 100644 --- a/sei-tendermint/config/toml_test.go +++ b/sei-tendermint/config/toml_test.go @@ -80,4 +80,40 @@ func checkConfig(t *testing.T, configFile string) { t.Errorf("config file was expected to contain %s but did not", e) } } + + // Expert-only state sync tuning knobs are intentionally left out of the + // generated template, while still being parsed from existing config files. + var hiddenStateSyncElems = []string{ + "backfill-blocks", + "backfill-duration", + "discovery-time", + "temp-dir", + "chunk-request-timeout", + "fetchers", + "verify-light-block-timeout", + "blacklist-ttl", + } + for _, e := range hiddenStateSyncElems { + if configContainsKey(configFile, e) { + t.Errorf("config file was not expected to contain %s", e) + } + } +} + +func configContainsKey(configFile string, key string) bool { + for _, line := range strings.Split(configFile, "\n") { + line = strings.TrimSpace(line) + rest, ok := strings.CutPrefix(line, key) + if !ok { + continue + } + // Require the next character to be the assignment '=' or any whitespace + // followed by '=', so we don't match keys whose name happens to be a + // prefix of another key (e.g. "fetchers" vs "fetchers-extra"). + rest = strings.TrimLeft(rest, " \t") + if strings.HasPrefix(rest, "=") { + return true + } + } + return false }