Skip to main content

urma_node/
peer_live.rs

1//! The overlay peers that inventory sync, parent recovery and record retrieval
2//! use right now: configured peers always, then every connected peer that
3//! spoke this protocol version on our chain, inbound or outbound. A connected
4//! peer leaves the set with its last connection.
5use crate::{
6    Error,
7    model::{
8        OverlayStatus, PeerLink as LinkStatus, Reachability as ReachabilityStatus, RecordPackage,
9    },
10    peer::{Answer, CALL_TIMEOUT, Command, request},
11    peer_gossip::Shared,
12    peer_nat::{PeerLink, Reachability},
13    peer_protocol::Kind,
14    peerbook::PeerAddress,
15    recorded::RecordedError,
16    retrieval::RecordSource,
17};
18use bitcoin::Txid;
19use libp2p::{Multiaddr, PeerId};
20use std::{
21    sync::{
22        Arc,
23        atomic::{AtomicBool, AtomicUsize, Ordering},
24        mpsc,
25    },
26    time::Instant,
27};
28use tokio::sync::mpsc as async_mpsc;
29
30/// The network thread's command queue and shared state, without ownership:
31/// handles built from it never keep the network alive.
32#[derive(Clone)]
33pub(crate) struct Link {
34    pub(crate) commands: async_mpsc::Sender<Command>,
35    pub(crate) stop: Arc<AtomicBool>,
36    pub(crate) shared: Arc<Shared>,
37}
38
39impl Link {
40    /// Without addresses the request rides an existing connection.
41    pub(crate) fn source(&self, peer: PeerId, addresses: Vec<Multiaddr>) -> PeerSource {
42        PeerSource {
43            peer,
44            addresses,
45            link: self.clone(),
46        }
47    }
48}
49
50/// A configured or connected untrusted package source. Metadata is matched to
51/// the request; proof verification and independent chain selection belong to
52/// the caller. Cloning a source never dials; fetch performs the bounded request.
53#[derive(Clone)]
54pub struct PeerSource {
55    peer: PeerId,
56    addresses: Vec<Multiaddr>,
57    link: Link,
58}
59
60impl PeerSource {
61    pub(crate) fn request<T>(
62        &self,
63        kind: Kind,
64        limit: Instant,
65        answer: fn(mpsc::SyncSender<Result<T, Error>>) -> Answer,
66    ) -> Result<T, Error> {
67        let target = (self.peer, self.addresses.clone());
68        request(
69            &self.link.commands,
70            &self.link.stop,
71            target,
72            kind,
73            limit,
74            answer,
75        )
76    }
77
78    /// Whether this peer has an established connection right now.
79    pub(crate) fn connected(&self) -> bool {
80        self.link.shared.is_connected(self.peer)
81    }
82
83    /// Whether it holds a data connection (a slot in our data budget).
84    pub(crate) fn has_data(&self) -> bool {
85        self.link.shared.has_direct(self.peer)
86    }
87
88    /// Whether one of its connections is one we dialed.
89    pub(crate) fn outbound(&self) -> bool {
90        self.link.shared.is_outbound(self.peer)
91    }
92
93    /// Configured with an address to dial, not only reached over a connection.
94    pub(crate) fn configured(&self) -> bool {
95        !self.addresses.is_empty()
96    }
97
98    /// Fire and forget; a full queue only delays the idle-connection timeout.
99    pub(crate) fn disconnect(&self) {
100        let _ = self.link.commands.try_send(Command::Disconnect(self.peer));
101    }
102
103    /// Sync no longer needs this peer: its data slot is freed, but a link
104    /// carrying a relay reservation stays (`peer_release`).
105    pub(crate) fn release(&self) {
106        let _ = self.link.commands.try_send(Command::Release(self.peer));
107    }
108}
109
110impl RecordSource for PeerSource {
111    fn fetch(&self, txid: Txid) -> Result<Option<RecordPackage>, Error> {
112        self.fetch_until(txid, Instant::now() + CALL_TIMEOUT)
113    }
114}
115
116/// Configured peers, which are dialed when not connected, then the live ones:
117/// those we dialed before those that dialed us. Cheap to clone; every call
118/// reads the current overlay.
119#[derive(Clone)]
120pub struct SyncPeers {
121    link: Link,
122    manual: Vec<PeerAddress>,
123    next: Arc<AtomicUsize>,
124    next_inbound: Arc<AtomicUsize>,
125}
126
127impl SyncPeers {
128    /// Record sources per retrieval call: retrieval tries this many.
129    pub const RECORD_PEERS: usize = 4;
130
131    pub(crate) fn new(link: Link, manual: Vec<PeerAddress>) -> Self {
132        Self {
133            link,
134            manual,
135            next: Arc::new(AtomicUsize::new(0)),
136            next_inbound: Arc::new(AtomicUsize::new(0)),
137        }
138    }
139
140    /// One source per identity, labelled by it, skipping `omitting` peers.
141    pub fn sources(&self, omitting: &[String]) -> Vec<(String, PeerSource)> {
142        let configured = self
143            .manual
144            .iter()
145            .map(|address| (address.peer_id(), vec![address.address().clone()]));
146        let live = self
147            .link
148            .shared
149            .live()
150            .into_iter()
151            .map(|peer| (peer, Vec::new()));
152        let mut sources: Vec<(String, PeerSource)> = Vec::new();
153        for (peer, addresses) in configured.chain(live) {
154            let label = peer.to_string();
155            if omitting.contains(&label) || sources.iter().any(|(known, _)| *known == label) {
156                continue;
157            }
158            sources.push((label, self.link.source(peer, addresses)));
159        }
160        sources
161    }
162
163    /// Up to `size` current peers: every configured one first, then the live
164    /// peers we dialed, then those that dialed us, each live group rotating
165    /// across calls on its own. A crowd of live peers cannot push a configured
166    /// peer out of the window, and inbound peers never come before outbound.
167    pub(crate) fn window(&self, size: usize) -> Vec<PeerSource> {
168        let peers = self.sources(&[]);
169        let (configured, live): (Vec<PeerSource>, Vec<PeerSource>) = peers
170            .into_iter()
171            .map(|(_, source)| source)
172            .partition(PeerSource::configured);
173        let (outbound, inbound): (Vec<PeerSource>, Vec<PeerSource>) =
174            live.into_iter().partition(PeerSource::outbound);
175        let mut window: Vec<PeerSource> = configured.into_iter().take(size).collect();
176        for (group, next) in [(&outbound, &self.next), (&inbound, &self.next_inbound)] {
177            let room = size.saturating_sub(window.len()).min(group.len());
178            if room > 0 {
179                let start = next.fetch_add(room, Ordering::Relaxed) % group.len();
180                window
181                    .extend((0..room).map(|offset| group[(start + offset) % group.len()].clone()));
182            }
183        }
184        window
185    }
186
187    /// Separate sources for one retrieval call, each verified on its own.
188    pub fn record_sources(&self) -> Vec<PeerSource> {
189        self.window(Self::RECORD_PEERS)
190    }
191
192    /// How many record sources a retrieval call would get right now.
193    pub fn record_count(&self) -> usize {
194        self.sources(&[]).len().min(Self::RECORD_PEERS)
195    }
196
197    /// The peer's link right now; `None` while it has no connection.
198    pub(crate) fn link_of(&self, peer: PeerId) -> Option<LinkStatus> {
199        let links = self.link.shared.links();
200        let (_, link) = links.iter().find(|(known, _)| *known == peer)?;
201        Some(match link {
202            PeerLink::Direct => LinkStatus::Direct,
203            PeerLink::Relayed => LinkStatus::Relayed,
204        })
205    }
206
207    /// Reachability, this node's relays and hole punch counters.
208    pub(crate) fn nat_status(&self, status: &mut OverlayStatus) {
209        let nat = &self.link.shared.nat;
210        status.reachability = match nat.reachability() {
211            Reachability::Unknown => ReachabilityStatus::Unknown,
212            Reachability::Public => ReachabilityStatus::Public,
213            Reachability::Private => ReachabilityStatus::Private,
214        };
215        status.relays = nat.relays().iter().map(PeerId::to_string).collect();
216        let punch = nat.hole_punch();
217        let (last_error, last_error_at) = RecordedError::split(punch.last_error);
218        status.hole_punch.attempts = punch.attempts;
219        status.hole_punch.successes = punch.successes;
220        status.hole_punch.last_error = last_error;
221        status.hole_punch.last_error_at = last_error_at;
222    }
223}