Skip to main content

zebra_network/peer_set/
stall_tracker.rs

1//! Tracks peers that consistently return failed `FindBlocks` or `FindHeaders`
2//! responses, so the peer set can disconnect them.
3//!
4//! Successful responses, including empty ones, reset the per-peer counter.
5//! Empty replies can be legitimate when a peer has no blocks beyond our locator;
6//! fair request routing prevents them from monopolizing discovery.
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 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 successful 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;