From 01c8524dbe7555c3783635689eed3cefd2870926 Mon Sep 17 00:00:00 2001 From: greg Date: Tue, 9 Jun 2026 21:44:39 +0000 Subject: [PATCH 1/3] gossip: preserve in-flight ping challenges --- gossip/src/ping_pong.rs | 170 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 155 insertions(+), 15 deletions(-) diff --git a/gossip/src/ping_pong.rs b/gossip/src/ping_pong.rs index c119a4c56f6..a2d6e19d813 100644 --- a/gossip/src/ping_pong.rs +++ b/gossip/src/ping_pong.rs @@ -1,5 +1,6 @@ use { crate::cluster_info_metrics::should_report_message_signature, + indexmap::IndexMap, lazy_lru::LruCache, rand::{CryptoRng, Rng}, serde::{Deserialize, Serialize}, @@ -50,17 +51,24 @@ pub struct Pong { pub struct PingCache { // Time-to-live of received pong messages. ttl: Duration, - // Rate limit delay to generate pings for a given address + // Rate limit delay to generate pings for a given address. + // Also used as the timeout for outstanding pings before they can be + // refreshed or evicted. rate_limit_delay: Duration, + // Capacity for the pings store. + max_pings: usize, // Timestamp and expected pong hash for each pinged remote node. - // Used for rate-limiting and pong validation. - pings: LruCache<(Pubkey, SocketAddr), (Instant, Hash)>, + pings: IndexMap<(Pubkey, SocketAddr), (Instant, Hash)>, // Verified pong responses from remote nodes. pongs: LruCache<(Pubkey, SocketAddr), Instant>, // Timestamp of last ping message sent to a remote IP. ping_times: LruCache, } +/// max number of slots in [`PingCache::pings`] to check when looking for +/// a free entry for a new ping probe +const MAX_PING_PROBES: usize = 4; + impl Ping { pub fn new(token: [u8; N], keypair: &Keypair) -> Self { let signature = keypair.sign_message(&token); @@ -148,15 +156,17 @@ impl Signable for Pong { } impl PingCache { - pub fn new(ttl: Duration, rate_limit_delay: Duration, cap: usize) -> Self { + pub fn new(ttl: Duration, rate_limit_delay: Duration, max_pings: usize) -> Self { // Sanity check ttl/rate_limit_delay assert!(rate_limit_delay <= ttl / 2); + assert!(max_pings > 0, "Must cache nonzero amount of hosts"); Self { ttl, rate_limit_delay, - pings: LruCache::new(cap), - pongs: LruCache::new(cap), - ping_times: LruCache::new(cap), + max_pings, + pings: IndexMap::with_capacity(max_pings), + pongs: LruCache::new(max_pings), + ping_times: LruCache::new(max_pings), } } @@ -165,10 +175,10 @@ impl PingCache { /// Note: Does not verify the signature. pub fn add(&mut self, pong: &Pong, socket: SocketAddr, now: Instant) -> bool { let remote_node = (pong.pubkey(), socket); - if !matches!(self.pings.peek(&remote_node), Some((_, h)) if *h == pong.hash) { + if !matches!(self.pings.get(&remote_node), Some((_, h)) if *h == pong.hash) { return false; }; - self.pings.pop(&remote_node); + self.pings.swap_remove(&remote_node); self.pongs.put(remote_node, now); if let Some(sent_time) = self.ping_times.pop(&socket.ip()) && should_report_message_signature( @@ -196,12 +206,40 @@ impl PingCache { now: Instant, remote_node: (Pubkey, SocketAddr), ) -> Option> { - // Rate limit consecutive pings sent to a remote node. - if matches!(self.pings.peek(&remote_node), - Some((t, _)) if now.saturating_duration_since(*t) < self.rate_limit_delay) - { - return None; + // If the existing ping is still in-flight (age < rate_limit_delay), + // don't send another one. + let is_new_key = if let Some((t, _)) = self.pings.get(&remote_node) { + if now.saturating_duration_since(*t) < self.rate_limit_delay { + return None; + } + false // existing entry will be updated in-place + } else { + true // no entry for this node yet + }; + + // If this is a new entry and the pings store is at capacity, + // probe random existing entries and evict the first timed-out one + // (age >= rate_limit_delay, peer never responded). + // Decline if all probes are in-flight — avoids evicting challenges + // still awaiting a Pong. + if is_new_key && self.pings.len() >= self.max_pings { + let n = self.pings.len(); + let mut evicted = false; + for _ in 0..MAX_PING_PROBES { + let idx = rng.random_range(0..n); + if let Some((_, (t, _))) = self.pings.get_index(idx) { + if now.saturating_duration_since(*t) >= self.rate_limit_delay { + self.pings.swap_remove_index(idx); + evicted = true; + break; + } + } + } + if !evicted { + return None; + } } + let token = { let mut token = [0u8; N]; const FILL: usize = std::mem::size_of::(); @@ -214,7 +252,7 @@ impl PingCache { }; // The hash we expect to see in the Pong message let ping_hash = hash_ping_token(&token); - self.pings.put(remote_node, (now, ping_hash)); + self.pings.insert(remote_node, (now, ping_hash)); self.ping_times.put(remote_node.1.ip(), Instant::now()); Some(Ping::new(token, keypair)) } @@ -457,6 +495,108 @@ mod tests { } } + #[test] + fn test_ping_cache_full_no_stale() { + // Verify that when the pings cache is at capacity and all entries are + // fresh, new pings are declined rather than evicting in-flight ones. + let mut rng = rand::rng(); + let this_node = Keypair::new(); + let cap = 3usize; + let ttl = Duration::from_secs(20 * 60); + let delay = ttl / 64; + let mut cache = PingCache::<32>::new(ttl, delay, cap); + let sockets: Vec = (1u8..=4) + .map(|i| SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(i, i, i, i), 8000))) + .collect(); + let keypairs: Vec = (0..4).map(|_| Keypair::new()).collect(); + + // Fill cache to capacity (3 entries) + let mut pings = Vec::new(); + for i in 0..cap { + let node = (keypairs[i].pubkey(), sockets[i]); + let (check, ping) = cache.check(&mut rng, &this_node, Instant::now(), node); + assert!(!check, "No pong yet, check should be false"); + assert!(ping.is_some(), "Should issue ping for entry {i}"); + pings.push((i, ping.unwrap())); + } + assert_eq!(cache.pings.len(), cap, "Cache should be at capacity"); + + // 4th new node must be declined — cache full, no stale entries + let node4 = (keypairs[3].pubkey(), sockets[3]); + let (check, ping) = cache.check(&mut rng, &this_node, Instant::now(), node4); + assert!(!check, "No pong, check should be false"); + assert!( + ping.is_none(), + "Must decline new ping when cache is full and no stale entries" + ); + + // Complete handshake for node 0 — frees one slot + let (idx0, ping0) = &pings[0]; + let pong0 = Pong::new(ping0, &keypairs[*idx0]); + assert!( + cache.add(&pong0, sockets[0], Instant::now()), + "Valid pong should be accepted" + ); + assert_eq!( + cache.pings.len(), + cap - 1, + "One slot should have been freed" + ); + + // Now 4th node should get a ping + let (check, ping) = cache.check(&mut rng, &this_node, Instant::now(), node4); + assert!(!check, "No pong for node4 yet"); + assert!( + ping.is_some(), + "Should issue ping for node4 after slot freed" + ); + } + + #[test] + fn test_ping_cache_full_with_stale() { + // Verify that when the pings cache is at capacity and entries are timed + // out (age >= rate_limit_delay, peer never responded), a new + // ping reclaims a stale slot. + let mut rng = rand::rng(); + let this_node = Keypair::new(); + let cap = 3usize; + let ttl = Duration::from_secs(20 * 60); + let delay = ttl / 64; + let mut cache = PingCache::<32>::new(ttl, delay, cap); + let sockets: Vec = (1u8..=4) + .map(|i| SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(i, i, i, i), 8000))) + .collect(); + let keypairs: Vec = (0..4).map(|_| Keypair::new()).collect(); + let now = Instant::now(); + + // Fill cache to capacity with nodes that won't answer pongs + for i in 0..cap { + let node = (keypairs[i].pubkey(), sockets[i]); + let (_, ping) = cache.check(&mut rng, &this_node, now, node); + assert!(ping.is_some(), "Should issue ping for entry {i}"); + } + assert_eq!(cache.pings.len(), cap, "Cache should be at capacity"); + + // Advance time so all in-flight pings are now stale + let expired = now + delay + Duration::from_millis(1); + + // 4th node should get a ping by reclaiming a stale slot + let node4 = (keypairs[3].pubkey(), sockets[3]); + // The 4-probe eviction is normally probabilistic; but with all entries + // stale, we expect it to always succeed. + let (check, ping) = cache.check(&mut rng, &this_node, expired, node4); + assert!(!check, "No pong for node4"); + assert!( + ping.is_some(), + "Should issue ping for node4 by reclaiming a stale entry" + ); + assert_eq!( + cache.pings.len(), + cap, + "Net size unchanged: one evicted, one inserted" + ); + } + #[test] fn test_expired_pong_returns_check_false() { let mut rng = rand::rng(); From 7dd9b4e7a0d6e0f747a88992f1c2c15bf26da5a6 Mon Sep 17 00:00:00 2001 From: greg Date: Tue, 9 Jun 2026 21:45:02 +0000 Subject: [PATCH 2/3] gossip: shorten ping retry delay --- gossip/src/cluster_info.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gossip/src/cluster_info.rs b/gossip/src/cluster_info.rs index d33483482d5..e1b8fbade87 100644 --- a/gossip/src/cluster_info.rs +++ b/gossip/src/cluster_info.rs @@ -120,7 +120,7 @@ const CHANNEL_CONSUME_CAPACITY: usize = 1024; pub(crate) const GOSSIP_CHANNEL_CAPACITY: usize = 4096; // 2^12 const GOSSIP_PING_CACHE_CAPACITY: usize = 126976; const GOSSIP_PING_CACHE_TTL: Duration = Duration::from_secs(1280); -const GOSSIP_PING_CACHE_RATE_LIMIT_DELAY: Duration = Duration::from_secs(1280 / 64); +const GOSSIP_PING_CACHE_RATE_LIMIT_DELAY: Duration = Duration::from_secs(2); pub const DEFAULT_CONTACT_DEBUG_INTERVAL_MILLIS: u64 = 10_000; pub const DEFAULT_CONTACT_SAVE_INTERVAL_MILLIS: u64 = 60_000; // Limit number of unique pubkeys in the crds table. From 7905bfac0c6738f405b3f22c0efa8d33feeaa293 Mon Sep 17 00:00:00 2001 From: greg Date: Tue, 9 Jun 2026 21:45:41 +0000 Subject: [PATCH 3/3] ping-cache: rename outstanding timeout --- core/src/repair/serve_repair.rs | 23 +++++++------- gossip/src/cluster_info.rs | 7 +++-- gossip/src/crds_gossip.rs | 13 +++++--- gossip/src/crds_gossip_pull.rs | 12 ++++--- gossip/src/crds_gossip_push.rs | 14 ++++++--- gossip/src/ping_pong.rs | 55 +++++++++++++++++++-------------- 6 files changed, 72 insertions(+), 52 deletions(-) diff --git a/core/src/repair/serve_repair.rs b/core/src/repair/serve_repair.rs index 3d6e00c789d..5e417b2dc3d 100644 --- a/core/src/repair/serve_repair.rs +++ b/core/src/repair/serve_repair.rs @@ -94,7 +94,7 @@ pub const MAX_ANCESTOR_RESPONSES: usize = const REPAIR_PING_TOKEN_SIZE: usize = HASH_BYTES; pub const REPAIR_PING_CACHE_CAPACITY: usize = 65536; pub const REPAIR_PING_CACHE_TTL: Duration = Duration::from_secs(1280); -const REPAIR_PING_CACHE_RATE_LIMIT_DELAY: Duration = Duration::from_secs(2); +const REPAIR_PING_CACHE_OUTSTANDING_PING_TIMEOUT: Duration = Duration::from_secs(2); pub(crate) const REPAIR_RESPONSE_SERIALIZED_PING_BYTES: usize = 4 /*enum discriminator*/ + PUBKEY_BYTES + REPAIR_PING_TOKEN_SIZE + SIGNATURE_BYTES; const SIGNED_REPAIR_TIME_WINDOW: Duration = Duration::from_secs(60 * 10); // 10 min @@ -1338,12 +1338,12 @@ impl ServeRepair { ) -> JoinHandle<()> { const MAX_BYTES_PER_SECOND: u64 = 12_000_000; - // rate limit delay should be greater than the repair request iteration delay - assert!(REPAIR_PING_CACHE_RATE_LIMIT_DELAY > Duration::from_millis(REPAIR_MS)); + // ping timeout should be greater than the repair request iteration delay + assert!(REPAIR_PING_CACHE_OUTSTANDING_PING_TIMEOUT > Duration::from_millis(REPAIR_MS)); let mut ping_cache = PingCache::new( REPAIR_PING_CACHE_TTL, - REPAIR_PING_CACHE_RATE_LIMIT_DELAY, + REPAIR_PING_CACHE_OUTSTANDING_PING_TIMEOUT, REPAIR_PING_CACHE_CAPACITY, ); @@ -2003,7 +2003,7 @@ mod tests { let from_addr = socketaddr!(Ipv4Addr::LOCALHOST, 1234); let mut ping_cache = PingCache::new( REPAIR_PING_CACHE_TTL, - REPAIR_PING_CACHE_RATE_LIMIT_DELAY, + REPAIR_PING_CACHE_OUTSTANDING_PING_TIMEOUT, REPAIR_PING_CACHE_CAPACITY, ); let slot = 42; @@ -3176,11 +3176,11 @@ mod tests { assert!(!repair.verify_response(&AncestorHashesResponse::Hashes(response))); } - // A second check() within REPAIR_PING_CACHE_RATE_LIMIT_DELAY must not generate + // A second check() within REPAIR_PING_CACHE_OUTSTANDING_PING_TIMEOUT must not generate // a new ping. If it did, it would overwrite the stored token and invalidate the Pong, // making Ping fail for no reason. #[test] - fn test_repair_no_ping_overwrite_within_rate_limit_delay() { + fn test_repair_no_ping_overwrite_while_already_probing() { let mut rng = rand::rng(); let this_node = Keypair::new(); let remote_socket = SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::LOCALHOST, 8001)); @@ -3188,7 +3188,7 @@ mod tests { let remote_node = (remote_keypair.pubkey(), remote_socket); let mut cache = PingCache::new( REPAIR_PING_CACHE_TTL, - REPAIR_PING_CACHE_RATE_LIMIT_DELAY, + REPAIR_PING_CACHE_OUTSTANDING_PING_TIMEOUT, REPAIR_PING_CACHE_CAPACITY, ); let now = Instant::now(); @@ -3196,13 +3196,12 @@ mod tests { let (_, ping1) = cache.check(&mut rng, &this_node, now, remote_node); let ping1 = ping1.expect("should generate ping for unknown node"); - // Second check within REPAIR_PING_CACHE_RATE_LIMIT_DELAY must not generate - // a new ping — that would overwrite the stored hash and invalidate the in-flight pong. - let within_delay = now + REPAIR_PING_CACHE_RATE_LIMIT_DELAY - Duration::from_millis(1); + let within_delay = + now + REPAIR_PING_CACHE_OUTSTANDING_PING_TIMEOUT - Duration::from_millis(1); let (_, ping2) = cache.check(&mut rng, &this_node, within_delay, remote_node); assert!( ping2.is_none(), - "must not generate a second ping within REPAIR_PING_CACHE_RATE_LIMIT_DELAY" + "must not generate a second ping within REPAIR_PING_CACHE_OUTSTANDING_PING_TIMEOUT" ); // Pong for ping1 must still be valid — token was not overwritten. diff --git a/gossip/src/cluster_info.rs b/gossip/src/cluster_info.rs index e1b8fbade87..c2679f1aaa6 100644 --- a/gossip/src/cluster_info.rs +++ b/gossip/src/cluster_info.rs @@ -119,8 +119,9 @@ const CHANNEL_CONSUME_CAPACITY: usize = 1024; /// of `MAX_GOSSIP_TRAFFIC` (103,896). pub(crate) const GOSSIP_CHANNEL_CAPACITY: usize = 4096; // 2^12 const GOSSIP_PING_CACHE_CAPACITY: usize = 126976; -const GOSSIP_PING_CACHE_TTL: Duration = Duration::from_secs(1280); -const GOSSIP_PING_CACHE_RATE_LIMIT_DELAY: Duration = Duration::from_secs(2); +pub(crate) const GOSSIP_PING_CACHE_TTL: Duration = Duration::from_secs(1280); +// max amount of time we wait for a Pong before pinging again +pub(crate) const GOSSIP_PING_CACHE_OUTSTANDING_PING_TIMEOUT: Duration = Duration::from_secs(2); pub const DEFAULT_CONTACT_DEBUG_INTERVAL_MILLIS: u64 = 10_000; pub const DEFAULT_CONTACT_SAVE_INTERVAL_MILLIS: u64 = 60_000; // Limit number of unique pubkeys in the crds table. @@ -188,7 +189,7 @@ impl ClusterInfo { my_contact_info: RwLock::new(contact_info), ping_cache: Mutex::new(PingCache::new( GOSSIP_PING_CACHE_TTL, - GOSSIP_PING_CACHE_RATE_LIMIT_DELAY, + GOSSIP_PING_CACHE_OUTSTANDING_PING_TIMEOUT, GOSSIP_PING_CACHE_CAPACITY, )), stats: GossipStats::default(), diff --git a/gossip/src/crds_gossip.rs b/gossip/src/crds_gossip.rs index 73b1e5fb9d7..676ef2ae5f1 100644 --- a/gossip/src/crds_gossip.rs +++ b/gossip/src/crds_gossip.rs @@ -392,7 +392,12 @@ pub(crate) fn maybe_ping_gossip_addresses( #[cfg(test)] mod test { use { - super::*, crate::contact_info::ContactInfo, solana_sha256_hasher::hash, + super::*, + crate::{ + cluster_info::{GOSSIP_PING_CACHE_OUTSTANDING_PING_TIMEOUT, GOSSIP_PING_CACHE_TTL}, + contact_info::ContactInfo, + }, + solana_sha256_hasher::hash, solana_time_utils::timestamp, }; @@ -414,9 +419,9 @@ mod test { ) .unwrap(); let ping_cache = PingCache::new( - Duration::from_secs(20 * 60), // ttl - Duration::from_secs(20 * 60) / 64, // rate_limit_delay - 128, // capacity + GOSSIP_PING_CACHE_TTL, + GOSSIP_PING_CACHE_OUTSTANDING_PING_TIMEOUT, + 128, // capacity (small for tests) ); let ping_cache = Mutex::new(ping_cache); crds_gossip.refresh_push_active_set( diff --git a/gossip/src/crds_gossip_pull.rs b/gossip/src/crds_gossip_pull.rs index 282e606e65e..df08d782385 100644 --- a/gossip/src/crds_gossip_pull.rs +++ b/gossip/src/crds_gossip_pull.rs @@ -659,7 +659,11 @@ pub(crate) fn get_max_bloom_filter_bytes(caller: &CrdsValue) -> usize { pub(crate) mod tests { use { super::*, - crate::{crds_data::CrdsData, protocol::Protocol}, + crate::{ + cluster_info::{GOSSIP_PING_CACHE_OUTSTANDING_PING_TIMEOUT, GOSSIP_PING_CACHE_TTL}, + crds_data::CrdsData, + protocol::Protocol, + }, itertools::Itertools, rand::{SeedableRng, prelude::IndexedRandom as _}, rand_chacha::ChaChaRng, @@ -730,9 +734,9 @@ pub(crate) mod tests { fn new_ping_cache() -> PingCache { PingCache::new( - Duration::from_secs(20 * 60), // ttl - Duration::from_secs(20 * 60) / 64, // rate_limit_delay - 128, // capacity + GOSSIP_PING_CACHE_TTL, + GOSSIP_PING_CACHE_OUTSTANDING_PING_TIMEOUT, + 128, // capacity (small for tests) ) } diff --git a/gossip/src/crds_gossip_push.rs b/gossip/src/crds_gossip_push.rs index 5f9353dd247..b90e8ae3c46 100644 --- a/gossip/src/crds_gossip_push.rs +++ b/gossip/src/crds_gossip_push.rs @@ -288,15 +288,19 @@ impl CrdsGossipPush { mod tests { use { super::*, - crate::{contact_info::ContactInfo, crds_data::CrdsData}, - std::time::{Duration, Instant}, + crate::{ + cluster_info::{GOSSIP_PING_CACHE_OUTSTANDING_PING_TIMEOUT, GOSSIP_PING_CACHE_TTL}, + contact_info::ContactInfo, + crds_data::CrdsData, + }, + std::time::Instant, }; fn new_ping_cache() -> PingCache { PingCache::new( - Duration::from_secs(20 * 60), // ttl - Duration::from_secs(20 * 60) / 64, // rate_limit_delay - 128, // capacity + GOSSIP_PING_CACHE_TTL, + GOSSIP_PING_CACHE_OUTSTANDING_PING_TIMEOUT, + 128, // capacity (small for tests) ) } diff --git a/gossip/src/ping_pong.rs b/gossip/src/ping_pong.rs index a2d6e19d813..da339caaf9c 100644 --- a/gossip/src/ping_pong.rs +++ b/gossip/src/ping_pong.rs @@ -51,10 +51,8 @@ pub struct Pong { pub struct PingCache { // Time-to-live of received pong messages. ttl: Duration, - // Rate limit delay to generate pings for a given address. - // Also used as the timeout for outstanding pings before they can be - // refreshed or evicted. - rate_limit_delay: Duration, + // Max time to wait for a Pong before considering the ping timed out. + outstanding_ping_timeout: Duration, // Capacity for the pings store. max_pings: usize, // Timestamp and expected pong hash for each pinged remote node. @@ -156,13 +154,15 @@ impl Signable for Pong { } impl PingCache { - pub fn new(ttl: Duration, rate_limit_delay: Duration, max_pings: usize) -> Self { - // Sanity check ttl/rate_limit_delay - assert!(rate_limit_delay <= ttl / 2); + pub fn new(ttl: Duration, outstanding_ping_timeout: Duration, max_pings: usize) -> Self { + assert!( + outstanding_ping_timeout <= ttl / 2, + "outstanding_ping_timeout should be < ttl" + ); assert!(max_pings > 0, "Must cache nonzero amount of hosts"); Self { ttl, - rate_limit_delay, + outstanding_ping_timeout, max_pings, pings: IndexMap::with_capacity(max_pings), pongs: LruCache::new(max_pings), @@ -206,10 +206,10 @@ impl PingCache { now: Instant, remote_node: (Pubkey, SocketAddr), ) -> Option> { - // If the existing ping is still in-flight (age < rate_limit_delay), + // If the existing ping is still in-flight (age < outstanding_ping_timeout), // don't send another one. let is_new_key = if let Some((t, _)) = self.pings.get(&remote_node) { - if now.saturating_duration_since(*t) < self.rate_limit_delay { + if now.saturating_duration_since(*t) < self.outstanding_ping_timeout { return None; } false // existing entry will be updated in-place @@ -219,7 +219,7 @@ impl PingCache { // If this is a new entry and the pings store is at capacity, // probe random existing entries and evict the first timed-out one - // (age >= rate_limit_delay, peer never responded). + // (age >= outstanding_ping_timeout, peer never responded). // Decline if all probes are in-flight — avoids evicting challenges // still awaiting a Pong. if is_new_key && self.pings.len() >= self.max_pings { @@ -228,7 +228,7 @@ impl PingCache { for _ in 0..MAX_PING_PROBES { let idx = rng.random_range(0..n); if let Some((_, (t, _))) = self.pings.get_index(idx) { - if now.saturating_duration_since(*t) >= self.rate_limit_delay { + if now.saturating_duration_since(*t) >= self.outstanding_ping_timeout { self.pings.swap_remove_index(idx); evicted = true; break; @@ -307,6 +307,7 @@ fn hash_ping_token(token: &[u8; N]) -> Hash { mod tests { use { super::*, + crate::cluster_info::{GOSSIP_PING_CACHE_OUTSTANDING_PING_TIMEOUT, GOSSIP_PING_CACHE_TTL}, std::{ collections::HashSet, iter::repeat_with, @@ -502,9 +503,11 @@ mod tests { let mut rng = rand::rng(); let this_node = Keypair::new(); let cap = 3usize; - let ttl = Duration::from_secs(20 * 60); - let delay = ttl / 64; - let mut cache = PingCache::<32>::new(ttl, delay, cap); + let mut cache = PingCache::<32>::new( + GOSSIP_PING_CACHE_TTL, + GOSSIP_PING_CACHE_OUTSTANDING_PING_TIMEOUT, + cap, + ); let sockets: Vec = (1u8..=4) .map(|i| SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(i, i, i, i), 8000))) .collect(); @@ -555,14 +558,16 @@ mod tests { #[test] fn test_ping_cache_full_with_stale() { // Verify that when the pings cache is at capacity and entries are timed - // out (age >= rate_limit_delay, peer never responded), a new + // out (age >= outstanding_ping_timeout, peer never responded), a new // ping reclaims a stale slot. let mut rng = rand::rng(); let this_node = Keypair::new(); let cap = 3usize; - let ttl = Duration::from_secs(20 * 60); - let delay = ttl / 64; - let mut cache = PingCache::<32>::new(ttl, delay, cap); + let mut cache = PingCache::<32>::new( + GOSSIP_PING_CACHE_TTL, + GOSSIP_PING_CACHE_OUTSTANDING_PING_TIMEOUT, + cap, + ); let sockets: Vec = (1u8..=4) .map(|i| SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(i, i, i, i), 8000))) .collect(); @@ -578,7 +583,7 @@ mod tests { assert_eq!(cache.pings.len(), cap, "Cache should be at capacity"); // Advance time so all in-flight pings are now stale - let expired = now + delay + Duration::from_millis(1); + let expired = now + GOSSIP_PING_CACHE_OUTSTANDING_PING_TIMEOUT + Duration::from_millis(1); // 4th node should get a ping by reclaiming a stale slot let node4 = (keypairs[3].pubkey(), sockets[3]); @@ -604,10 +609,12 @@ mod tests { let remote_socket = SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(10, 10, 10, 10), 8000)); let remote_node_keypair = Keypair::new(); let remote_node = (remote_node_keypair.pubkey(), remote_socket); - let ttl = Duration::from_secs(20 * 60); // 20 minutes - let delay = ttl / 64; let mut now = Instant::now(); - let mut cache = PingCache::<32>::new(ttl, delay, /*cap=*/ 1000); + let mut cache = PingCache::<32>::new( + GOSSIP_PING_CACHE_TTL, + GOSSIP_PING_CACHE_OUTSTANDING_PING_TIMEOUT, + /*cap=*/ 1000, + ); // Add a pong for the remote node cache.mock_pong(remote_node.0, remote_node.1, now); @@ -618,7 +625,7 @@ mod tests { assert!(ping.is_none(), "Should not generate ping for recent pong"); // Advance time past TTL to expire the pong - now = now + ttl + Duration::from_secs(1); + now = now + GOSSIP_PING_CACHE_TTL + Duration::from_secs(1); // After expiration, check should return false but should_ping should be true (to re-verify) let (check, ping) = cache.check(&mut rng, &this_node, now, remote_node);