Skip to main content

urma_node/
peer_sync.rs

1//! Fast inventory synchronization from configured URMA peers. A peer page is an
2//! untrusted claim: every fetched package is proof-verified by `admit_record`
3//! against the independently selected header chain, and coverage is recorded
4//! only after a complete range has every advertised record present locally.
5//! Full-block audit (`indexed_through`) remains the separate, stronger claim.
6//! A peer that answers with an invalid page, package or reply is disconnected
7//! and backed off; one that stays Busy for many turns is disconnected so the
8//! connection slot it holds frees up for others. So is one that answers turn
9//! after turn without anything we can use (stale listings, unscanned pages),
10//! and a peer refused by the local connection limit gets the slot of the
11//! connected peer that progressed longest ago.
12use crate::{
13    DialLimit, Error,
14    engine::Engine,
15    model::{OverlayPeerStatus, OverlayStatus},
16    peer::{PeerSource, SyncPeers},
17    peer_inventory::{placed, selected},
18    peer_protocol::Placement,
19    recorded::{RecordedError, unix_seconds},
20};
21use libp2p::{Multiaddr, PeerId};
22use range::{Accepted, Opened, Range, accept, open};
23use std::{
24    sync::{
25        Arc, Mutex,
26        atomic::{AtomicBool, AtomicU32, AtomicU64, AtomicUsize, Ordering},
27    },
28    time::{Duration, Instant},
29};
30
31mod range;
32
33pub type ConnectedPeers = Arc<dyn Fn() -> u64 + Send + Sync>;
34pub type Listeners = Arc<dyn Fn() -> Vec<Multiaddr> + Send + Sync>;
35
36/// Public overlay identity facts reported in status; never secrets.
37pub struct OverlayInfo {
38    pub peer_id: String,
39    pub listen: Listeners,
40    pub announce: Vec<String>,
41    pub peer_book: u64,
42    pub connected: ConnectedPeers,
43}
44
45/// Inventory peers, re-read every step: configured ones, then live ones.
46pub struct PeerSync {
47    pub peers: SyncPeers,
48    pub info: OverlayInfo,
49}
50
51struct Peer {
52    label: String,
53    id: String,
54    peer_id: PeerId,
55    source: PeerSource,
56    range: Mutex<Option<Range>>,
57    /// Lowest height the peer last reported scanning; ranges start at or
58    /// above it. 0 until a page says otherwise.
59    floor: AtomicU64,
60    last_request: Mutex<Option<Instant>>,
61    last_error: Mutex<Option<RecordedError>>,
62    backoff: Mutex<Backoff>,
63    busy_turns: AtomicU32,
64    idle_turns: AtomicU32,
65    last_progress: Mutex<Option<u64>>,
66    /// Scheduler step at which this peer was first seen connected, if it is.
67    connected_since: Mutex<Option<u64>>,
68}
69
70/// Sync backoff after peer errors: consecutive blamed turns and the Unix
71/// second before which the peer is skipped.
72struct Backoff {
73    failures: u32,
74    until: Option<u64>,
75}
76
77impl Peer {
78    fn backoff_until(&self, now: u64) -> Result<Option<u64>, Error> {
79        let backoff = self.backoff.lock().map_err(|_| poisoned())?;
80        Ok(backoff.until.filter(|until| *until > now))
81    }
82
83    fn last_progress(&self) -> Result<Option<u64>, Error> {
84        Ok(*self.last_progress.lock().map_err(|_| poisoned())?)
85    }
86
87    /// Skip this peer until at least `now + seconds`, unblamed: failures and
88    /// the book are untouched, and a longer backoff is never shortened.
89    fn hold_off(&self, now: u64, seconds: u64) -> Result<(), Error> {
90        let until = now.checked_add(seconds).unwrap_or(u64::MAX);
91        let mut backoff = self.backoff.lock().map_err(|_| poisoned())?;
92        backoff.until = Some(backoff.until.map_or(until, |current| current.max(until)));
93        Ok(())
94    }
95
96    /// Wait for an evicted connection to close so its slot is really free,
97    /// bounded by `EVICT_WAIT`, the step deadline and cancellation.
98    fn wait_closed(&self, stop: &AtomicBool, deadline: Instant) -> bool {
99        let until = deadline.min(Instant::now() + Scheduler::EVICT_WAIT);
100        while self.source.connected() {
101            if stop.load(Ordering::Acquire) || Instant::now() >= until {
102                return false;
103            }
104            std::thread::sleep(Duration::from_millis(25));
105        }
106        true
107    }
108}
109
110/// Why a step stopped early. `Blame` is a protocol violation by the peer;
111/// `Error` is ours, transport or retryable.
112enum Pause {
113    Done,
114    Abandon,
115    Error(Error),
116    Blame(Error),
117}
118
119/// How one step ended for the peer it ran with: remote Busy builds a streak,
120/// local Busy and transport failures retry quietly.
121enum Turn {
122    Quiet,
123    Busy(Error),
124    Retry(Error),
125    Blamed(Error),
126}
127
128/// A failed request blames the peer only for a protocol violation: never for
129/// capacity (ours or theirs), transport failure or our reorg.
130fn blame(error: Error) -> Pause {
131    match error {
132        Error::Busy(_)
133        | Error::PeerBusy
134        | Error::Transport(_)
135        | Error::DialDenied(_)
136        | Error::Stale => Pause::Error(error),
137        other => Pause::Blame(other),
138    }
139}
140
141impl From<Error> for Pause {
142    fn from(error: Error) -> Self {
143        Pause::Error(error)
144    }
145}
146
147/// Per-round budgets for one peer: request spacing, fetch count, wall clock;
148/// whether the peer was asked anything and whether an admission or coverage
149/// came of it.
150struct Round<'a> {
151    stop: &'a AtomicBool,
152    deadline: Instant,
153    fetched: usize,
154    requested_page: bool,
155    contacted: bool,
156    progressed: bool,
157}
158
159impl Round<'_> {
160    fn open(&self) -> Result<(), Pause> {
161        if self.stop.load(Ordering::Acquire) || Instant::now() >= self.deadline {
162            return Err(Pause::Done);
163        }
164        Ok(())
165    }
166}
167
168pub(crate) struct Scheduler {
169    offered: SyncPeers,
170    peers: Mutex<Vec<Arc<Peer>>>,
171    turn: AtomicUsize,
172    configured_turn: AtomicBool,
173    configured_next: AtomicUsize,
174    info: OverlayInfo,
175    admitted: AtomicU64,
176    rejected: AtomicU64,
177    penalized: Mutex<Vec<PeerId>>,
178    steps: AtomicU64,
179}
180
181fn poisoned() -> Error {
182    Error::Invalid("peer synchronization state poisoned".into())
183}
184
185/// The next non-omitting, non-backed-off peer after `turn`.
186fn rotate(
187    peers: &[Arc<Peer>],
188    turn: &AtomicUsize,
189    omitting: &[String],
190    now: u64,
191) -> Result<Option<Arc<Peer>>, Error> {
192    for _ in 0..peers.len() {
193        let peer = &peers[turn.fetch_add(1, Ordering::Relaxed) % peers.len()];
194        if !omitting.contains(&peer.id) && peer.backoff_until(now)?.is_none() {
195            return Ok(Some(Arc::clone(peer)));
196        }
197    }
198    Ok(None)
199}
200
201fn configured(peer: &Arc<Peer>) -> bool {
202    peer.source.configured()
203}
204
205/// A peer we dialed that has held its outbound slot for more than one step
206/// without progress for `IDLE_BACKOFF` seconds, or ever. Evicting an inbound
207/// peer would free nothing for our outbound dial.
208fn evictable(peer: &Peer, step: u64, now: u64) -> Result<bool, Error> {
209    let settled = peer
210        .connected_since
211        .lock()
212        .map_err(|_| poisoned())?
213        .is_some_and(|since| step >= since.saturating_add(2));
214    let idle = peer
215        .last_progress()?
216        .is_none_or(|at| now >= at.saturating_add(Scheduler::IDLE_BACKOFF));
217    Ok(settled && idle && peer.source.outbound())
218}
219
220fn inventory_peer(label: String, source: PeerSource) -> Result<Peer, Error> {
221    let peer_id = match label.parse::<PeerId>() {
222        Ok(id) => id,
223        Err(error) => {
224            return Err(Error::Invalid(format!("inventory peer label: {error}")));
225        }
226    };
227    Ok(Peer {
228        label,
229        id: peer_id.to_string(),
230        peer_id,
231        source,
232        range: Mutex::new(None),
233        floor: AtomicU64::new(0),
234        last_request: Mutex::new(None),
235        last_error: Mutex::new(None),
236        backoff: Mutex::new(Backoff {
237            failures: 0,
238            until: None,
239        }),
240        busy_turns: AtomicU32::new(0),
241        idle_turns: AtomicU32::new(0),
242        last_progress: Mutex::new(None),
243        connected_since: Mutex::new(None),
244    })
245}
246
247impl Scheduler {
248    /// Heights per claimed range, inclusive of both ends.
249    pub const RANGE_HEIGHTS: u64 = 4096;
250    /// Entries requested per inventory page.
251    pub const PAGE_ENTRIES: u32 = 512;
252    /// Package fetches per worker round.
253    pub const ROUND_FETCHES: usize = 16;
254    /// Wall-clock budget of one step, checked between requests.
255    pub const ROUND_TIME: Duration = Duration::from_secs(20);
256    /// Minimum spacing between requests to one peer (its 4/s token bucket).
257    pub const REQUEST_SPACING: Duration = Duration::from_millis(250);
258    /// Sync backoff after a peer error: 30 s, doubling per repeat, capped at a day.
259    pub const BACKOFF_SECONDS: u64 = 30;
260    pub const BACKOFF_CAP: u64 = 86_400;
261    /// Consecutive Busy turns after which a peer is disconnected.
262    pub const BUSY_TURNS: u32 = 10;
263    /// Consecutive Missing answers for advertised records that blame a peer.
264    pub const MISSING_ANSWERS: u32 = 2;
265    /// Consecutive answered turns without progress after which a peer's slot
266    /// is released for `IDLE_BACKOFF` seconds, unblamed. The same span guards
267    /// eviction: only a peer without progress for that long can be evicted,
268    /// and an evicted peer is held off for it too.
269    pub const IDLE_TURNS: u32 = 3;
270    pub const IDLE_BACKOFF: u64 = 60;
271    /// Longest wait for an evicted connection to close before the retry.
272    pub const EVICT_WAIT: Duration = Duration::from_secs(2);
273    const MAX_PEERS: usize = 1024;
274
275    /// Status lists the configured peers before the first step.
276    pub(crate) fn new(sync: PeerSync) -> Result<Self, Error> {
277        let scheduler = Self {
278            offered: sync.peers,
279            peers: Mutex::new(Vec::new()),
280            turn: AtomicUsize::new(0),
281            configured_turn: AtomicBool::new(false),
282            configured_next: AtomicUsize::new(0),
283            info: sync.info,
284            admitted: AtomicU64::new(0),
285            rejected: AtomicU64::new(0),
286            penalized: Mutex::new(Vec::new()),
287            steps: AtomicU64::new(0),
288        };
289        scheduler.refresh(scheduler.offered.sources(&[]), unix_seconds()?)?;
290        Ok(scheduler)
291    }
292
293    /// One bounded step with the next non-omitting, non-backed-off peer in
294    /// rotation. Busy, deadline and cancellation keep the range; a step
295    /// interrupted by `stop` records nothing about the peer.
296    pub(crate) fn step(&self, engine: &Engine, stop: &AtomicBool) -> Result<(), Error> {
297        let omitting = engine.omitting_peers()?;
298        let now = unix_seconds()?;
299        self.refresh(self.offered.sources(&omitting), now)?;
300        let step = self.steps.fetch_add(1, Ordering::Relaxed);
301        self.observe_connections(step)?;
302        let Some(peer) = self.next_peer(&omitting, now)? else {
303            return Ok(());
304        };
305        let deadline = Instant::now() + Self::ROUND_TIME;
306        let denied = self.turn(&peer, engine, stop, deadline, now)?;
307        if denied && self.free_slot_for(&peer, step, stop, deadline, now)? {
308            self.turn(&peer, engine, stop, deadline, now)?;
309        }
310        Ok(())
311    }
312
313    /// One turn with `peer`. Returns whether our established-connection
314    /// limit refused its dial, the only refusal an eviction can cure.
315    fn turn(
316        &self,
317        peer: &Peer,
318        engine: &Engine,
319        stop: &AtomicBool,
320        deadline: Instant,
321        now: u64,
322    ) -> Result<bool, Error> {
323        let mut round = Round {
324            stop,
325            deadline,
326            fetched: 0,
327            requested_page: false,
328            contacted: false,
329            progressed: false,
330        };
331        let outcome = self.advance(peer, engine, &mut round);
332        if stop.load(Ordering::Acquire) {
333            return Ok(false);
334        }
335        self.track_progress(peer, &outcome, &round, now)?;
336        let denied = matches!(
337            &outcome,
338            Ok(Turn::Retry(Error::DialDenied(DialLimit::Established)))
339        );
340        let result = self.judge(peer, outcome, now)?;
341        *peer.last_error.lock().map_err(|_| poisoned())? = RecordedError::from_result(&result);
342        Ok(denied)
343    }
344
345    /// Note at which step each inventory peer was first seen connected.
346    fn observe_connections(&self, step: u64) -> Result<(), Error> {
347        for peer in self.snapshot()? {
348            let mut since = peer.connected_since.lock().map_err(|_| poisoned())?;
349            *since = match (peer.source.connected(), *since) {
350                (true, Some(first)) => Some(first),
351                (true, None) => Some(step),
352                (false, _gone) => None,
353            };
354        }
355        Ok(())
356    }
357
358    /// Peers sync is currently skipping, for discovery not to redial them.
359    pub(crate) fn backed_off(&self, now: u64) -> Result<Vec<String>, Error> {
360        let mut skipped = Vec::new();
361        for peer in self.snapshot()? {
362            if peer.backoff_until(now)?.is_some() {
363                skipped.push(peer.id.clone());
364            }
365        }
366        Ok(skipped)
367    }
368
369    /// An admission, coverage or an already-held listed record is progress
370    /// and resets the idle streak. `IDLE_TURNS` quiet turns in a row that
371    /// asked the peer and got nothing usable release its slot for
372    /// `IDLE_BACKOFF`: no blame, no book penalty, failures untouched.
373    fn track_progress(
374        &self,
375        peer: &Peer,
376        outcome: &Result<Turn, Error>,
377        round: &Round,
378        now: u64,
379    ) -> Result<(), Error> {
380        if round.progressed {
381            peer.idle_turns.store(0, Ordering::Relaxed);
382            *peer.last_progress.lock().map_err(|_| poisoned())? = Some(now);
383            return Ok(());
384        }
385        if !round.contacted || !matches!(outcome, Ok(Turn::Quiet)) {
386            return Ok(());
387        }
388        let idle = peer.idle_turns.fetch_add(1, Ordering::Relaxed) + 1;
389        if idle < Self::IDLE_TURNS {
390            return Ok(());
391        }
392        peer.idle_turns.store(0, Ordering::Relaxed);
393        peer.source.disconnect();
394        peer.hold_off(now, Self::IDLE_BACKOFF)
395    }
396
397    /// Our established-connection limit refused `starved`: evict the
398    /// connected inventory peer that progressed longest ago, but only one
399    /// without progress for `IDLE_BACKOFF` (or ever) that has stayed connected
400    /// for more than one step. The victim is held off for `IDLE_BACKOFF`, in
401    /// sync and in discovery, so nothing redials it into the freed slot.
402    /// Returns whether its connection closed in time for `starved` to retry
403    /// in this step. One eviction per step, and no second one for `starved`
404    /// before that retry; the scheduler runs one request at a time, so no
405    /// other inventory peer is mid-request.
406    fn free_slot_for(
407        &self,
408        starved: &Peer,
409        step: u64,
410        stop: &AtomicBool,
411        deadline: Instant,
412        now: u64,
413    ) -> Result<bool, Error> {
414        let mut oldest: Option<(Arc<Peer>, Option<u64>)> = None;
415        for peer in self.snapshot()? {
416            if peer.id == starved.id || !evictable(&peer, step, now)? {
417                continue;
418            }
419            let progress = peer.last_progress()?;
420            if oldest.as_ref().is_none_or(|(_, best)| progress < *best) {
421                oldest = Some((peer, progress));
422            }
423        }
424        let Some((victim, _progress)) = oldest else {
425            return Ok(false);
426        };
427        victim.source.disconnect();
428        victim.hold_off(now, Self::IDLE_BACKOFF)?;
429        Ok(victim.wait_closed(stop, deadline))
430    }
431
432    /// A blamed turn disconnects the peer and backs it off; remote Busy turns
433    /// build a streak that disconnects the peer at `BUSY_TURNS`; a quiet turn
434    /// resets both. Retries and our own failures say nothing about the peer.
435    fn judge(
436        &self,
437        peer: &Peer,
438        outcome: Result<Turn, Error>,
439        now: u64,
440    ) -> Result<Result<(), Error>, Error> {
441        match outcome {
442            Ok(Turn::Quiet) => {
443                peer.busy_turns.store(0, Ordering::Relaxed);
444                peer.backoff.lock().map_err(|_| poisoned())?.failures = 0;
445                Ok(Ok(()))
446            }
447            Ok(Turn::Busy(error)) => {
448                let streak = peer.busy_turns.fetch_add(1, Ordering::Relaxed) + 1;
449                if streak < Self::BUSY_TURNS {
450                    return Ok(Err(error));
451                }
452                peer.busy_turns.store(0, Ordering::Relaxed);
453                peer.source.disconnect();
454                Ok(Err(Error::Busy(format!(
455                    "{error}; busy for {streak} turns, disconnected"
456                ))))
457            }
458            Ok(Turn::Blamed(error)) => {
459                peer.busy_turns.store(0, Ordering::Relaxed);
460                self.penalize(peer, now)?;
461                Ok(Err(error))
462            }
463            Ok(Turn::Retry(error)) | Err(error) => Ok(Err(error)),
464        }
465    }
466
467    fn penalize(&self, peer: &Peer, now: u64) -> Result<(), Error> {
468        peer.source.disconnect();
469        let mut backoff = peer.backoff.lock().map_err(|_| poisoned())?;
470        backoff.failures = backoff.failures.checked_add(1).unwrap_or(u32::MAX);
471        let delay = 2u64
472            .checked_pow(backoff.failures - 1)
473            .and_then(|factor| factor.checked_mul(Self::BACKOFF_SECONDS))
474            .map_or(Self::BACKOFF_CAP, |delay| delay.min(Self::BACKOFF_CAP));
475        backoff.until = Some(now.checked_add(delay).unwrap_or(u64::MAX));
476        drop(backoff);
477        let mut penalized = self.penalized.lock().map_err(|_| poisoned())?;
478        if !penalized.contains(&peer.peer_id) {
479            penalized.push(peer.peer_id);
480        }
481        Ok(())
482    }
483
484    /// Peers backed off since the last call, for the peer book to record.
485    pub(crate) fn drain_penalized(&self) -> Result<Vec<PeerId>, Error> {
486        let mut penalized = self.penalized.lock().map_err(|_| poisoned())?;
487        Ok(std::mem::take(&mut *penalized))
488    }
489
490    /// Reconcile with the offered peers: known identities keep their peer
491    /// state and in-flight range, new ones join. One no longer offered stays
492    /// while backed off, so a hold-off survives the disconnect it caused and
493    /// discovery keeps avoiding it; otherwise it is dropped.
494    fn refresh(&self, offered: Vec<(String, PeerSource)>, now: u64) -> Result<(), Error> {
495        if offered.len() > Self::MAX_PEERS {
496            return Err(Error::Invalid("too many inventory peers".into()));
497        }
498        let mut peers = self.peers.lock().map_err(|_| poisoned())?;
499        let mut next: Vec<Arc<Peer>> = Vec::with_capacity(offered.len());
500        for (label, source) in offered {
501            let candidate = inventory_peer(label, source)?;
502            match peers.iter().find(|known| known.id == candidate.id) {
503                Some(known) => next.push(Arc::clone(known)),
504                None => next.push(Arc::new(candidate)),
505            }
506        }
507        let mut held = Vec::new();
508        for known in peers.iter() {
509            if !next.iter().any(|kept| kept.id == known.id) && known.backoff_until(now)?.is_some() {
510                held.push(Arc::clone(known));
511            }
512        }
513        // Rotating inbound identities must not pile up: the oldest go first.
514        let room = Self::MAX_PEERS.saturating_sub(next.len());
515        next.extend(held.split_off(held.len().saturating_sub(room)));
516        *peers = next;
517        Ok(())
518    }
519
520    fn snapshot(&self) -> Result<Vec<Arc<Peer>>, Error> {
521        Ok(self.peers.lock().map_err(|_| poisoned())?.clone())
522    }
523
524    /// Configured and live-only peers alternate turns, each group rotating on
525    /// its own; a group with nobody ready passes its turn to the other.
526    fn next_peer(&self, omitting: &[String], now: u64) -> Result<Option<Arc<Peer>>, Error> {
527        let peers = self.snapshot()?;
528        let (configured, live): (Vec<_>, Vec<_>) = peers.into_iter().partition(configured);
529        let mut groups = [(&configured, &self.configured_next), (&live, &self.turn)];
530        if self.configured_turn.fetch_xor(true, Ordering::Relaxed) {
531            groups.reverse();
532        }
533        for (peers, turn) in groups {
534            if let Some(peer) = rotate(peers, turn, omitting, now)? {
535                return Ok(Some(peer));
536            }
537        }
538        Ok(None)
539    }
540
541    fn advance(&self, peer: &Peer, engine: &Engine, round: &mut Round) -> Result<Turn, Error> {
542        let mut slot = peer.range.lock().map_err(|_| poisoned())?;
543        if slot.is_none() {
544            match open(&peer.id, engine, peer.floor.load(Ordering::Relaxed))? {
545                Opened::Range(range) => *slot = Some(range),
546                // Nothing left to ask a fully synced peer: as fresh as progress,
547                // so surplus synced peers are not evicted every IDLE_BACKOFF.
548                Opened::Synced => round.progressed = true,
549                // Starting above our tip it serves nothing: idle, evictable.
550                Opened::AboveTip => (),
551            }
552        }
553        // A peer error or our own failure abandons the range; it is requested
554        // again from the persisted cursor on this peer's next turn.
555        match self.walk(peer, engine, &mut slot, round) {
556            Ok(()) | Err(Pause::Done) => Ok(Turn::Quiet),
557            Err(Pause::Abandon) | Err(Pause::Error(Error::Stale)) => {
558                *slot = None;
559                Ok(Turn::Quiet)
560            }
561            Err(Pause::Error(Error::PeerBusy)) => Ok(Turn::Busy(Error::PeerBusy)),
562            Err(Pause::Error(
563                retry @ (Error::Busy(_) | Error::Transport(_) | Error::DialDenied(_)),
564            )) => Ok(Turn::Retry(retry)),
565            Err(Pause::Blame(error)) => {
566                *slot = None;
567                Ok(Turn::Blamed(error))
568            }
569            Err(Pause::Error(error)) => {
570                *slot = None;
571                Err(error)
572            }
573        }
574    }
575
576    fn walk(
577        &self,
578        peer: &Peer,
579        engine: &Engine,
580        slot: &mut Option<Range>,
581        round: &mut Round,
582    ) -> Result<(), Pause> {
583        while let Some(range) = slot.as_mut() {
584            round.open()?;
585            if let Some(&entry) = range.entries.get(range.next) {
586                if !selected(engine, &entry)? {
587                    return Err(Pause::Abandon);
588                }
589                if engine.record(entry.txid)?.is_none() {
590                    if round.fetched == Self::ROUND_FETCHES {
591                        return Err(Pause::Done);
592                    }
593                    round.fetched += 1;
594                    pace(peer, round)?;
595                    let missing = &mut range.missing;
596                    round.progressed |=
597                        self.acquire(peer, engine, &entry, missing, round.deadline)?;
598                } else {
599                    round.progressed = true;
600                }
601                range.next += 1;
602            } else if range.more {
603                if round.requested_page {
604                    return Err(Pause::Done);
605                }
606                round.requested_page = true;
607                pace(peer, round)?;
608                let Some(page) = peer
609                    .source
610                    .inventory_until(
611                        (range.from, range.to),
612                        range.after,
613                        Self::PAGE_ENTRIES,
614                        round.stop,
615                        round.deadline,
616                    )
617                    .map_err(blame)?
618                else {
619                    return Err(Pause::Done);
620                };
621                match accept(range, page).map_err(Pause::Blame)? {
622                    Accepted::Walk => (),
623                    Accepted::Unscanned => *slot = None,
624                    Accepted::Floor(floor) => {
625                        peer.floor.store(floor, Ordering::Relaxed);
626                        *slot = open(&peer.id, engine, floor)?.range();
627                    }
628                }
629            } else {
630                let through = range.to.min(range.scanned_through);
631                engine.record_peer_coverage(&peer.id, range.from, through, range.generation)?;
632                round.progressed = true;
633                *slot = None;
634            }
635        }
636        Ok(())
637    }
638
639    /// Fetch one advertised record from the advertising peer and admit it. A
640    /// peer that answers Missing leaves the entry listed-only: it neither blocks
641    /// coverage nor is retried within this range. `MISSING_ANSWERS` Missing
642    /// answers in a row within one range blame the peer; an invalid package
643    /// always does. Returns whether a record was admitted.
644    fn acquire(
645        &self,
646        peer: &Peer,
647        engine: &Engine,
648        entry: &Placement,
649        missing: &mut u32,
650        deadline: Instant,
651    ) -> Result<bool, Pause> {
652        let fetched = peer
653            .source
654            .fetch_until(entry.txid, deadline)
655            .map_err(blame)?;
656        let Some(package) = fetched else {
657            *missing += 1;
658            if *missing < Self::MISSING_ANSWERS {
659                return Ok(false);
660            }
661            return Err(Pause::Blame(Error::Invalid(
662                "peer reported advertised records missing".into(),
663            )));
664        };
665        *missing = 0;
666        let receipt = match engine.admit_record(package) {
667            Ok(receipt) => receipt,
668            Err(invalid @ Error::Invalid(_)) => {
669                self.rejected.fetch_add(1, Ordering::Relaxed);
670                return Err(Pause::Blame(invalid));
671            }
672            Err(local) => return Err(Pause::Error(local)),
673        };
674        self.admitted.fetch_add(1, Ordering::Relaxed);
675        if !placed(&receipt, entry) {
676            return Err(Pause::Blame(Error::Invalid(
677                "peer inventory placement disagrees with its proven record".into(),
678            )));
679        }
680        Ok(true)
681    }
682
683    pub(crate) fn status(&self, engine: &Engine) -> Result<OverlayStatus, Error> {
684        let start = engine.status()?.application_start.height;
685        let now = unix_seconds()?;
686        let known = self.snapshot()?;
687        let mut peers = Vec::with_capacity(known.len());
688        for peer in &known {
689            let cursor = engine.peer_cursor(&peer.id)?;
690            let synced_through = cursor.checked_sub(1).filter(|height| *height >= start);
691            let recorded = peer.last_error.lock().map_err(|_| poisoned())?.clone();
692            let (last_error, last_error_at) = RecordedError::split(recorded);
693            let mut status = OverlayPeerStatus::default();
694            status.label = peer.label.clone();
695            status.cursor = cursor;
696            status.synced_through = synced_through;
697            status.last_error = last_error;
698            status.last_error_at = last_error_at;
699            status.backoff_until = peer.backoff_until(now)?;
700            status.last_progress_at = peer.last_progress()?;
701            peers.push(status);
702        }
703        let mut status = OverlayStatus::default();
704        status.peer_id = self.info.peer_id.clone();
705        status.listen = (self.info.listen)()
706            .iter()
707            .map(Multiaddr::to_string)
708            .collect();
709        status.announce = self.info.announce.clone();
710        status.peer_book = self.info.peer_book;
711        status.connected = (self.info.connected)();
712        status.admitted = self.admitted.load(Ordering::Relaxed);
713        status.rejected = self.rejected.load(Ordering::Relaxed);
714        status.peers = peers;
715        Ok(status)
716    }
717}
718
719/// Keep requests to one peer at least `REQUEST_SPACING` apart, waiting in
720/// short slices so cancellation and the round deadline stay responsive. The
721/// round counts as having asked the peer once pacing lets a request through.
722fn pace(peer: &Peer, round: &mut Round) -> Result<(), Pause> {
723    let mut last = peer.last_request.lock().map_err(|_| poisoned())?;
724    if let Some(previous) = *last {
725        let ready = previous + Scheduler::REQUEST_SPACING;
726        while Instant::now() < ready {
727            round.open()?;
728            let wait = ready.saturating_duration_since(Instant::now());
729            std::thread::sleep(wait.min(Duration::from_millis(50)));
730        }
731    }
732    round.open()?;
733    *last = Some(Instant::now());
734    round.contacted = true;
735    Ok(())
736}