From b178ec9a4a923188e8644a314cb71f36ad611cdf Mon Sep 17 00:00:00 2001 From: Csaba Kiraly Date: Thu, 16 Apr 2026 00:23:13 +0200 Subject: [PATCH] eth/peerstats: bump MinLatencySamples from 10 to 100 Require substantially more samples before a peer's request-latency EMA becomes eligible for protection. A 10-sample floor was too low: a peer hitting 10 fast replies in a short burst could earn protection before the slow alpha=0.01 EMA had moved meaningfully away from the bootstrap value. At ~70-sample EMA half-life, a 100-sample floor means the EMA has been refined through several half-lives before it can affect dropping decisions. Updates the dropper tests that previously used RequestSamples=50 to use peerstats.MinLatencySamples so they stay robust to future value changes. Design notes and a test comment reference the new value. --- eth/dropper_test.go | 10 +++++----- eth/peerstats/peerstats.go | 2 +- eth/peerstats/peerstats_test.go | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/eth/dropper_test.go b/eth/dropper_test.go index e2ed638322..90333d97d5 100644 --- a/eth/dropper_test.go +++ b/eth/dropper_test.go @@ -243,15 +243,15 @@ func TestProtectedByPoolRequestLatencyBasic(t *testing.T) { // Three peers have enough samples; the two fastest should win. stats[dialed[0].ID().String()] = peerstats.PeerStats{ RequestLatencyEMA: 50 * time.Millisecond, - RequestSamples: 50, + RequestSamples: peerstats.MinLatencySamples, } stats[dialed[1].ID().String()] = peerstats.PeerStats{ RequestLatencyEMA: 100 * time.Millisecond, - RequestSamples: 50, + RequestSamples: peerstats.MinLatencySamples, } stats[dialed[2].ID().String()] = peerstats.PeerStats{ RequestLatencyEMA: 2 * time.Second, - RequestSamples: 50, + RequestSamples: peerstats.MinLatencySamples, } protected := protectedPeersByPool(nil, dialed, stats) @@ -313,7 +313,7 @@ func TestProtectedByPoolRequestLatencyPerPool(t *testing.T) { for _, p := range inbound { stats[p.ID().String()] = peerstats.PeerStats{ RequestLatencyEMA: 50 * time.Millisecond, - RequestSamples: 50, + RequestSamples: peerstats.MinLatencySamples, } } // Dialed peers are slower (1s) — globally they would all lose, but @@ -321,7 +321,7 @@ func TestProtectedByPoolRequestLatencyPerPool(t *testing.T) { for _, p := range dialed { stats[p.ID().String()] = peerstats.PeerStats{ RequestLatencyEMA: 1 * time.Second, - RequestSamples: 50, + RequestSamples: peerstats.MinLatencySamples, } } diff --git a/eth/peerstats/peerstats.go b/eth/peerstats/peerstats.go index 567a27309a..ab02fe9705 100644 --- a/eth/peerstats/peerstats.go +++ b/eth/peerstats/peerstats.go @@ -52,7 +52,7 @@ const ( // MinLatencySamples is the number of latency samples a peer must accumulate // before its RequestLatencyEMA is considered meaningful for protection. // Prevents a single lucky-fast reply from displacing established peers. - MinLatencySamples = 10 + MinLatencySamples = 100 ) // PeerStats is the exported per-peer snapshot returned by GetAllPeerStats. diff --git a/eth/peerstats/peerstats_test.go b/eth/peerstats/peerstats_test.go index 42d3bfa385..2a8f89a0eb 100644 --- a/eth/peerstats/peerstats_test.go +++ b/eth/peerstats/peerstats_test.go @@ -181,7 +181,7 @@ func TestNotifyPeerDropClearsStats(t *testing.T) { // TestStaleRequestLatencyAfterDrop documents the accepted behavior: a // late sample after NotifyPeerDrop recreates a 1-sample entry. The -// dropper's MinLatencySamples=10 guard ensures this is harmless. +// dropper's MinLatencySamples=100 guard ensures this is harmless. func TestStaleRequestLatencyAfterDrop(t *testing.T) { s := New() s.NotifyRequestLatency("peerA", 200*time.Millisecond)