Skip to main content

urma_node/service/
overlay.rs

1//! Overlay steps of the Service round: peer discovery, then inventory sync.
2//! Each is optional, stop-guarded and keeps its own recorded error.
3use super::Service;
4use crate::{
5    Error,
6    model::{BookEntry, OverlayStatus},
7    peer_discovery::Discovery,
8    peer_sync::{PeerSync, Scheduler},
9    recorded::{record, unix_seconds},
10};
11use std::sync::atomic::Ordering;
12
13impl Service {
14    /// Fast inventory synchronization from configured overlay peers. Only
15    /// run(true) schedules it; configuring peers does not dial or fetch.
16    pub fn with_peer_sync(mut self, sync: PeerSync) -> Result<Self, Error> {
17        self.peer_sync = Some(Scheduler::new(sync)?);
18        Ok(self)
19    }
20
21    /// Automatic peer discovery from seeds and peer address exchange. Only
22    /// run(true) schedules it, as its own bounded step before inventory sync.
23    pub fn with_discovery(mut self, discovery: Discovery) -> Result<Self, Error> {
24        self.discovery = Some(discovery);
25        Ok(self)
26    }
27
28    pub(super) fn discover_peers(&self) {
29        let Some(discovery) = &self.discovery else {
30            return;
31        };
32        if self.stop.load(Ordering::Acquire) {
33            return;
34        }
35        let result = self
36            .share_sync_backoff(discovery)
37            .and_then(|()| discovery.step(&self.engine, &self.stop));
38        if self.stop.load(Ordering::Acquire) {
39            return;
40        }
41        record(&self.discovery_error, &result);
42    }
43
44    /// Discovery must not redial a peer sync is holding off, or it would
45    /// refill the slot an eviction or idle release just freed.
46    fn share_sync_backoff(&self, discovery: &Discovery) -> Result<(), Error> {
47        let Some(scheduler) = &self.peer_sync else {
48            return Ok(());
49        };
50        discovery.hold_off(scheduler.backed_off(unix_seconds()?)?)
51    }
52
53    pub(super) fn sync_next_inventory(&self) {
54        let Some(scheduler) = &self.peer_sync else {
55            return;
56        };
57        if self.stop.load(Ordering::Acquire) {
58            return;
59        }
60        let result = self
61            .refresh_sync_peers(scheduler)
62            .and_then(|()| scheduler.step(&self.engine, &self.stop))
63            .and_then(|()| self.penalize_in_book(scheduler));
64        if self.stop.load(Ordering::Acquire) {
65            return;
66        }
67        record(&self.inventory_error, &result);
68    }
69
70    /// Peers the scheduler backed off also back off in the book, so discovery
71    /// stops dialing them; without discovery there is no book to tell.
72    fn penalize_in_book(&self, scheduler: &Scheduler) -> Result<(), Error> {
73        let penalized = scheduler.drain_penalized()?;
74        match &self.discovery {
75            Some(discovery) => discovery.penalize(&penalized),
76            None => Ok(()),
77        }
78    }
79
80    /// With discovery, the inventory peers follow the book every step.
81    fn refresh_sync_peers(&self, scheduler: &Scheduler) -> Result<(), Error> {
82        let Some(discovery) = &self.discovery else {
83            return Ok(());
84        };
85        let omitting = self.engine.omitting_peers()?;
86        scheduler.refresh(discovery.sync_sources(&omitting)?)
87    }
88
89    /// The sync view with discovery counters attached when both run.
90    pub(super) fn overlay_status(&self) -> Result<Option<OverlayStatus>, Error> {
91        let Some(scheduler) = &self.peer_sync else {
92            return Ok(None);
93        };
94        let mut overlay = scheduler.status(&self.engine)?;
95        if let Some(discovery) = &self.discovery {
96            overlay.discovery = Some(discovery.status()?);
97        }
98        Ok(Some(overlay))
99    }
100
101    /// `None` without discovery: the route answers 404 "overlay disabled".
102    pub(super) fn overlay_book(&self) -> Result<Option<Vec<BookEntry>>, Error> {
103        self.discovery.as_ref().map(Discovery::book).transpose()
104    }
105}