Skip to main content

zebra_network/peer_set/
stall_tracker.rs

1//! Tracks peers that consistently return empty or failed `FindBlocks` or
2//! `FindHeaders` responses, so the peer set can disconnect them.
3//!
4//! A peer returning a single empty response may just be syncing itself; a peer
5//! that does so repeatedly stalls the syncer by forcing retries to others. The
6//! counter is per-peer and resets on any useful (non-empty) response.
7//!
8//! Only applies to `FindBlocks` and `FindHeaders`. An empty response to
9//! `BlocksByHash`/`TransactionsById` is a legitimate "I don't have this
10//! inventory" answer, so those don't feed the tracker.
11
12use std::collections::HashMap;
13
14use crate::PeerSocketAddr;
15
16/// Consecutive empty or failed `FindBlocks`/`FindHeaders` responses tolerated
17/// before the peer set disconnects a peer.
18pub(super) const FIND_RESPONSE_STALL_THRESHOLD: usize = 3;
19
20#[derive(Default)]
21pub(super) struct FindResponseStallTracker {
22    counts: HashMap<PeerSocketAddr, usize>,
23}
24
25impl FindResponseStallTracker {
26    pub(super) fn new() -> Self {
27        Self::default()
28    }
29
30    /// Records a stall for `addr`. Returns `true` once the peer reaches
31    /// [`FIND_RESPONSE_STALL_THRESHOLD`] — the caller must then disconnect it.
32    /// On threshold the entry is removed, so a reconnected peer starts fresh.
33    pub(super) fn record_stall(&mut self, addr: PeerSocketAddr) -> bool {
34        let count = self.counts.entry(addr).or_default();
35        *count += 1;
36
37        if *count >= FIND_RESPONSE_STALL_THRESHOLD {
38            self.counts.remove(&addr);
39            true
40        } else {
41            false
42        }
43    }
44
45    /// Clears tracking for a peer that sent a useful response or disconnected.
46    pub(super) fn clear(&mut self, addr: PeerSocketAddr) {
47        self.counts.remove(&addr);
48    }
49}
50
51#[cfg(test)]
52mod tests;