diff --git a/config_test.go b/config_test.go index 3a3bc1b..ad5c885 100644 --- a/config_test.go +++ b/config_test.go @@ -63,13 +63,29 @@ func TestDefaultForMode_SeedHighConnections(t *testing.T) { t.Errorf("seed max_connections: got %d, want 1000", cfg.Network.P2P.MaxConnections) } if !cfg.Network.P2P.AllowDuplicateIP { - t.Error("seed should allow duplicate IPs") + t.Error("seed should allow duplicate IPs — records the intent only; sei-tendermint never reads this field") } if cfg.Storage.PruningStrategy != PruningEverything { t.Errorf("seed pruning: got %s, want everything", cfg.Storage.PruningStrategy) } } +func TestDefaultForMode_SeedBoundsPerSourceIPConnections(t *testing.T) { + cfg := DefaultForMode(ModeSeed) + + got := cfg.Network.P2P.MaxIncomingConnectionAttempts + if got != 32 { + t.Errorf("seed max_incoming_connection_attempts: got %d, want 32", got) + } + // The cap is per source IP and counts concurrent connections, so it constrains + // nothing unless it sits below the total. Upstream leaves the two equal, where a + // single source can hold every slot. + if got >= uint(cfg.Network.P2P.MaxConnections) { + t.Errorf("seed per-IP cap %d must be below max_connections %d, or one source can fill the node", + got, cfg.Network.P2P.MaxConnections) + } +} + func TestDefaultForMode_SeedBindsP2POnAllInterfaces(t *testing.T) { cfg := DefaultForMode(ModeSeed) diff --git a/defaults.go b/defaults.go index b868939..cdc10b7 100644 --- a/defaults.go +++ b/defaults.go @@ -282,7 +282,18 @@ func applySeedOverrides(cfg *SeiConfig) { cfg.TxIndex.Indexer = []string{"null"} cfg.Network.P2P.ListenAddress = p2pListenAddrAllInterfaces cfg.Network.P2P.MaxConnections = 1000 + // Inert in sei-tendermint: declared and rendered into config.toml, never read. + // Kept because true is the right intent for a seed, but the limit that actually + // applies is MaxIncomingConnectionAttempts below. cfg.Network.P2P.AllowDuplicateIP = true + // Caps CONCURRENT connections per source IP, despite the name. Upstream leaves + // it equal to max_connections (100/100) where it can never bind; raising the + // total to 1000 makes it the first real constraint. 32 gives one source ~3% of + // inbound — ample for an operator running several nodes behind one NAT, while + // forcing a flood across ~31 addresses. This assumes the load balancer in front + // preserves client IPs; under SNAT it multiplies against the LB's address count + // rather than the peers'. + cfg.Network.P2P.MaxIncomingConnectionAttempts = 32 // Bounds the PEX recv path only, where ReadSizedMsg allocates a peer-declared // size before the channel's ~26 KB RecvMessageCapacity can reject it. The // multiplier is maxInbound (max_connections less the outbound reserve), so at