Skip to content

Commit 883f6e8

Browse files
Hard-code the state sync trust period per chain (#4679)
State sync's trust period has been a per-node `[statesync] trust-period` setting, defaulting to 168h. It has to stay below the unbonding time so that the validators who signed the trusted header are still slashable, so cutting the unbonding time from 21 to 7 days ([CON-381](https://linear.app/seilabs/issue/CON-381/reduce-21-days-unbonding-time-to-7-days)) would depend on every operator editing their config.toml. A governance param ([#4677](#4677), closed) does not fit either: a node that state syncs cannot learn the value without first trusting an RPC server for it. So the value now lives in the binary. `config.StateSyncTrustPeriod(chainID)` returns the period from a per-chain table: 112h on pacific-1, atlantic-2 and arctic-1, which all run a 21-day unbonding time today and stay below the planned 7 days, and 168h, the old default, on any other chain, such as the docker localnet. `Reactor.initStateProvider` passes it to both the RPC and the P2P state providers. The `TrustPeriod` field, its default and its `ValidateBasic` check are removed from `StateSyncConfig`, and `trust-period` is removed from the config.toml template and the docker configs. Consensus and state are untouched, so no upgrade handler is needed; the change should be backported to release/v6.8 so the whole network ships the same value. A config.toml that still sets `trust-period` keeps loading and the value is ignored, so an existing override stops applying: a node that state synced from an old trust height under a longer configured period now needs a trust height less than 112h old, or it fails with `ErrOldHeaderExpired`. `seid start` logs a warning when `trust-period` is still set to a value other than the chain's built-in period, naming the chain ID, the configured value and the period in use, so the ignored override does not go unnoticed; nothing is logged for a matching value or an absent key. The old template wrote `trust-period = "168h0m0s"`, so most existing nodes on pacific-1, atlantic-2 and arctic-1 will log it on every start, whether or not state sync is enabled, until the line is removed. `seid tendermint light --trusting-period` keeps its own 168h flag default. Validated with the `sei-tendermint/config`, `internal/statesync` and `sei-cosmos/server` tests, the config characterization suites in `cmd/seid/cmd` and `testutil/configtest`, and a `seid start` on a pacific-1 home that still sets the old default. --------- Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 47b8cac commit 883f6e8

10 files changed

Lines changed: 139 additions & 29 deletions

File tree

‎docker/localnode/config/config.toml‎

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -314,14 +314,10 @@ enable = false
314314

315315
# RPC servers (comma-separated) for light client verification of the synced state machine and
316316
# retrieval of state data for node bootstrapping. Also needs a trusted height and corresponding
317-
# header hash obtained from a trusted source, and a period during which validators can be trusted.
318-
#
319-
# For Cosmos SDK-based chains, trust_period should usually be about 2/3 of the unbonding time (~2
320-
# weeks) during which they can be financially punished (slashed) for misbehavior.
317+
# header hash obtained from a trusted source.
321318
rpc-servers = ""
322319
trust-height = 0
323320
trust-hash = ""
324-
trust-period = "168h0m0s"
325321

326322
# Time to spend discovering snapshots before initiating a restore.
327323
discovery-time = "15s"

‎docker/rpcnode/config/config.toml‎

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -314,14 +314,10 @@ enable = false
314314

315315
# RPC servers (comma-separated) for light client verification of the synced state machine and
316316
# retrieval of state data for node bootstrapping. Also needs a trusted height and corresponding
317-
# header hash obtained from a trusted source, and a period during which validators can be trusted.
318-
#
319-
# For Cosmos SDK-based chains, trust_period should usually be about 2/3 of the unbonding time (~2
320-
# weeks) during which they can be financially punished (slashed) for misbehavior.
317+
# header hash obtained from a trusted source.
321318
rpc-servers = ""
322319
trust-height = 0
323320
trust-hash = ""
324-
trust-period = "168h0m0s"
325321

326322
# Time to spend discovering snapshots before initiating a restore.
327323
discovery-time = "15s"

‎sei-cosmos/server/start.go‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,12 @@ import (
2626
genesistypes "github.com/sei-protocol/sei-chain/sei-cosmos/types/genesis"
2727
"github.com/sei-protocol/sei-chain/sei-cosmos/utils/tracing"
2828
tcmd "github.com/sei-protocol/sei-chain/sei-tendermint/cmd/tendermint/commands"
29+
tmcfg "github.com/sei-protocol/sei-chain/sei-tendermint/config"
2930
"github.com/sei-protocol/sei-chain/sei-tendermint/node"
3031
"github.com/sei-protocol/sei-chain/sei-tendermint/rpc/client/local"
3132
tmtypes "github.com/sei-protocol/sei-chain/sei-tendermint/types"
3233
"github.com/spf13/cobra"
34+
"github.com/spf13/viper"
3335
"go.opentelemetry.io/otel/sdk/trace"
3436
)
3537

@@ -316,6 +318,7 @@ func startInProcess(
316318
if ctx.Viper.GetBool("rosetta.enable") {
317319
logger.Error("WARNING: rosetta.enable is set in app.toml, but seid no longer includes the Rosetta API server; the setting is ignored")
318320
}
321+
warnIgnoredStateSyncTrustPeriod(ctx.Viper, clientCtx.ChainID)
319322
app := appCreator(nil, traceWriter, ctx.Config, ctx.Viper)
320323
defer func() {
321324
logger.Info("close any other open resource...")
@@ -417,3 +420,15 @@ func startInProcess(
417420
// wait for signal capture and gracefully return
418421
return WaitForQuitSignals(goCtx, restartCh)
419422
}
423+
424+
// warnIgnoredStateSyncTrustPeriod logs a warning when v sets [statesync] trust-period to a
425+
// value other than the trust period state sync uses on chainID.
426+
func warnIgnoredStateSyncTrustPeriod(v *viper.Viper, chainID string) {
427+
const key = "statesync.trust-period"
428+
trustPeriod := tmcfg.StateSyncTrustPeriod(chainID)
429+
if !v.IsSet(key) || v.GetDuration(key) == trustPeriod {
430+
return
431+
}
432+
logger.Warn("statesync.trust-period is set in config.toml, but state sync uses the trust period built into seid for this chain; the setting is ignored and can be removed",
433+
"chainID", chainID, "configured", v.GetString(key), "trustPeriod", trustPeriod.String())
434+
}

‎sei-cosmos/server/start_test.go‎

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,19 @@
11
package server
22

33
import (
4+
"bytes"
5+
"encoding/json"
6+
"fmt"
7+
"log/slog"
8+
"strings"
49
"testing"
510

611
"github.com/spf13/cobra"
12+
"github.com/spf13/viper"
713
"github.com/stretchr/testify/require"
814

915
"github.com/sei-protocol/sei-chain/sei-cosmos/telemetry"
16+
tmcfg "github.com/sei-protocol/sei-chain/sei-tendermint/config"
1017
)
1118

1219
func TestInjectTelemetryChainID(t *testing.T) {
@@ -59,3 +66,46 @@ func TestRemovedGRPCStartFlags(t *testing.T) {
5966
// grpc-only meant "do not start Tendermint"; ignoring it would start a full node.
6067
require.Error(t, cmd.ParseFlags([]string{"--grpc-only"}))
6168
}
69+
70+
func TestWarnIgnoredStateSyncTrustPeriod(t *testing.T) {
71+
for name, tc := range map[string]struct {
72+
chainID string
73+
trustPeriod string // config.toml value; empty leaves the key out
74+
wantWarning bool
75+
}{
76+
"unset": {chainID: "pacific-1"},
77+
"matches the chain's period": {chainID: "pacific-1", trustPeriod: "112h"},
78+
"old default on pacific-1": {chainID: "pacific-1", trustPeriod: "168h0m0s", wantWarning: true},
79+
"old default on an unlisted chain": {chainID: "sei", trustPeriod: "168h0m0s"},
80+
"override on an unlisted chain": {chainID: "sei", trustPeriod: "72h", wantWarning: true},
81+
"not a duration": {chainID: "sei", trustPeriod: "one week", wantWarning: true},
82+
} {
83+
t.Run(name, func(t *testing.T) {
84+
configToml := "[statesync]\n"
85+
if tc.trustPeriod != "" {
86+
configToml += fmt.Sprintf("trust-period = %q\n", tc.trustPeriod)
87+
}
88+
v := viper.New()
89+
v.SetConfigType("toml")
90+
require.NoError(t, v.ReadConfig(strings.NewReader(configToml)))
91+
92+
logs := &bytes.Buffer{}
93+
prev := logger
94+
logger = slog.New(slog.NewJSONHandler(logs, nil))
95+
t.Cleanup(func() { logger = prev })
96+
97+
warnIgnoredStateSyncTrustPeriod(v, tc.chainID)
98+
99+
if !tc.wantWarning {
100+
require.Empty(t, logs.String())
101+
return
102+
}
103+
var record map[string]any
104+
require.NoError(t, json.Unmarshal(logs.Bytes(), &record))
105+
require.Equal(t, slog.LevelWarn.String(), record[slog.LevelKey])
106+
require.Equal(t, tc.chainID, record["chainID"])
107+
require.Equal(t, tc.trustPeriod, record["configured"])
108+
require.Equal(t, tmcfg.StateSyncTrustPeriod(tc.chainID).String(), record["trustPeriod"])
109+
})
110+
}
111+
}

‎sei-tendermint/config/config.go‎

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1071,6 +1071,28 @@ func (cfg *MempoolConfig) ValidateBasic() error {
10711071
//-----------------------------------------------------------------------------
10721072
// StateSyncConfig
10731073

1074+
// stateSyncTrustPeriods maps a well-known chain ID to its state sync trust period.
1075+
var stateSyncTrustPeriods = map[string]time.Duration{
1076+
// A trust period plus the time needed to check headers and to report and
1077+
// punish misbehavior must stay below the network's unbonding time.
1078+
"pacific-1": 112 * time.Hour,
1079+
"atlantic-2": 112 * time.Hour,
1080+
"arctic-1": 112 * time.Hour,
1081+
}
1082+
1083+
// defaultStateSyncTrustPeriod is the state sync trust period of a chain that
1084+
// stateSyncTrustPeriods does not list.
1085+
const defaultStateSyncTrustPeriod = 168 * time.Hour
1086+
1087+
// StateSyncTrustPeriod returns how long after its time a header of chainID can
1088+
// still serve as the trusted root state sync verifies light blocks from.
1089+
func StateSyncTrustPeriod(chainID string) time.Duration {
1090+
if trustPeriod, ok := stateSyncTrustPeriods[chainID]; ok {
1091+
return trustPeriod
1092+
}
1093+
return defaultStateSyncTrustPeriod
1094+
}
1095+
10741096
// StateSyncConfig defines the configuration for the Tendermint state sync service
10751097
type StateSyncConfig struct {
10761098
// State sync rapidly bootstraps a new node by discovering, fetching, and restoring a
@@ -1090,15 +1112,11 @@ type StateSyncConfig struct {
10901112
// with net.Dial, for example: "host.example.com:2125".
10911113
RPCServers []string `mapstructure:"rpc-servers"`
10921114

1093-
// The hash and height of a trusted block. Must be within the trust-period.
1115+
// The hash and height of a trusted block. Must be within the trust period
1116+
// StateSyncTrustPeriod returns for the chain.
10941117
TrustHeight int64 `mapstructure:"trust-height"`
10951118
TrustHash string `mapstructure:"trust-hash"`
10961119

1097-
// The trust period should be set so that Tendermint can detect and gossip
1098-
// misbehavior before it is considered expired. For chains based on the Cosmos SDK,
1099-
// one day less than the unbonding period should suffice.
1100-
TrustPeriod time.Duration `mapstructure:"trust-period"`
1101-
11021120
// Backfill sequentially fetches, verifies and stores light blocks in reverse order.
11031121
// It does not stop verifying blocks until reaching a height
11041122
// that is less or equal to the latestBlock - backfill-blocks and latest block time - backfill-duration.
@@ -1149,7 +1167,6 @@ func (cfg *StateSyncConfig) TrustHashBytes() []byte {
11491167
// DefaultStateSyncConfig returns a default configuration for the state sync service
11501168
func DefaultStateSyncConfig() *StateSyncConfig {
11511169
return &StateSyncConfig{
1152-
TrustPeriod: 168 * time.Hour,
11531170
DiscoveryTime: 15 * time.Second,
11541171
ChunkRequestTimeout: 15 * time.Second,
11551172
Fetchers: 2,
@@ -1190,10 +1207,6 @@ func (cfg *StateSyncConfig) ValidateBasic() error {
11901207
return errors.New("discovery time must be 0s or greater than five seconds")
11911208
}
11921209

1193-
if cfg.TrustPeriod <= 0 {
1194-
return errors.New("trusted-period is required")
1195-
}
1196-
11971210
if cfg.TrustHeight <= 0 {
11981211
return errors.New("trusted-height is required")
11991212
}

‎sei-tendermint/config/config_test.go‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,20 @@ func TestStateSyncConfigValidateBasic(t *testing.T) {
141141
require.NoError(t, cfg.ValidateBasic())
142142
}
143143

144+
func TestStateSyncTrustPeriod(t *testing.T) {
145+
testcases := map[string]time.Duration{
146+
"pacific-1": 112 * time.Hour,
147+
"atlantic-2": 112 * time.Hour,
148+
"arctic-1": 112 * time.Hour,
149+
"sei": 168 * time.Hour,
150+
}
151+
for chainID, want := range testcases {
152+
t.Run(chainID, func(t *testing.T) {
153+
require.Equal(t, want, StateSyncTrustPeriod(chainID))
154+
})
155+
}
156+
}
157+
144158
func TestConsensusConfig_ValidateBasic(t *testing.T) {
145159
testcases := map[string]struct {
146160
modify func(*ConsensusConfig)

‎sei-tendermint/config/statesync_compat_test.go‎

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,6 @@ enable = true
7575
rpc-servers = "localhost:26657,localhost:26659"
7676
trust-height = 10
7777
trust-hash = "AABB"
78-
trust-period = "168h0m0s"
7978
`), 0600)
8079
require.NoError(t, err)
8180

@@ -96,3 +95,28 @@ trust-period = "168h0m0s"
9695

9796
require.NoError(t, cfg.StateSync.ValidateBasic())
9897
}
98+
99+
// TestStateSyncConfigIgnoresTrustPeriod pins that a config.toml which still sets
100+
// trust-period loads and validates, whatever value the key holds.
101+
func TestStateSyncConfigIgnoresTrustPeriod(t *testing.T) {
102+
viper.Reset()
103+
t.Cleanup(viper.Reset)
104+
105+
configPath := filepath.Join(t.TempDir(), "config.toml")
106+
err := os.WriteFile(configPath, []byte(`
107+
[statesync]
108+
enable = true
109+
rpc-servers = "localhost:26657,localhost:26659"
110+
trust-height = 10
111+
trust-hash = "AABB"
112+
trust-period = "0s"
113+
`), 0600)
114+
require.NoError(t, err)
115+
116+
viper.SetConfigFile(configPath)
117+
require.NoError(t, viper.ReadInConfig())
118+
119+
cfg, err := commands.ParseConfig(tmconfig.DefaultConfig())
120+
require.NoError(t, err)
121+
require.NoError(t, cfg.StateSync.ValidateBasic())
122+
}

‎sei-tendermint/config/toml.go‎

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -527,15 +527,11 @@ use-p2p = {{ .StateSync.UseP2P }}
527527
# for example: "host.example.com:2125"
528528
rpc-servers = "{{ StringsJoin .StateSync.RPCServers "," }}"
529529
530-
# The hash and height of a trusted block. Must be within the trust-period.
530+
# The hash and height of a trusted block. Must be within the chain's trust period, which is
531+
# built into the binary (112h on pacific-1, atlantic-2 and arctic-1).
531532
trust-height = {{ .StateSync.TrustHeight }}
532533
trust-hash = "{{ .StateSync.TrustHash }}"
533534
534-
# The trust period should be set so that Tendermint can detect and gossip misbehavior before
535-
# it is considered expired. For chains based on the Cosmos SDK, one day less than the unbonding
536-
# period should suffice.
537-
trust-period = "{{ .StateSync.TrustPeriod }}"
538-
539535
# Whether to use local snapshot only for state sync or not.
540536
# If this is true, then state sync will look for existing snapshots
541537
# which are located in the snapshot-dir configured in app.toml (default to [home-dir]/data/snapshots)

‎sei-tendermint/config/toml_test.go‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,12 @@ func checkConfig(t *testing.T, configFile string) {
9999
}
100100
}
101101

102+
// The state sync trust period is built into the binary per chain, so the
103+
// template must not offer a key for it. See StateSyncTrustPeriod.
104+
if configContainsKey(configFile, "trust-period") {
105+
t.Errorf("config file was not expected to contain trust-period")
106+
}
107+
102108
// accept-interval is rendered deliberately: an accept rate too low to drain
103109
// the kernel backlog silently stops a node acquiring inbound peers, and the
104110
// template is where an operator looks. Keep it discoverable.

‎sei-tendermint/internal/statesync/reactor.go‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,7 @@ func NewReactor(
268268

269269
func (r *Reactor) initStateProvider(ctx context.Context, chainID string, initialHeight int64) error {
270270
to := light.TrustOptions{
271-
Period: r.cfg.TrustPeriod,
271+
Period: config.StateSyncTrustPeriod(chainID),
272272
Height: r.cfg.TrustHeight,
273273
Hash: r.cfg.TrustHashBytes(),
274274
}

0 commit comments

Comments
 (0)