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::RecordPackage,
8    peer::{Answer, CALL_TIMEOUT, Command, request},
9    peer_gossip::Shared,
10    peer_protocol::Kind,
11    peerbook::PeerAddress,
12    retrieval::RecordSource,
13};
14use bitcoin::Txid;
15use libp2p::{Multiaddr, PeerId};
16use std::{
17    sync::{
18        Arc,
19        atomic::{AtomicBool, AtomicUsize, Ordering},
20        mpsc,
21    },
22    time::Instant,
23};
24use tokio::sync::mpsc as async_mpsc;
25
26/// The network thread's command queue and shared state, without ownership:
27/// handles built from it never keep the network alive.
28#[derive(Clone)]
29pub(crate) struct Link {
30    pub(crate) commands: async_mpsc::Sender<Command>,
31    pub(crate) stop: Arc<AtomicBool>,
32    pub(crate) shared: Arc<Shared>,
33}
34
35impl Link {
36    /// Without addresses the request rides an existing connection.
37    pub(crate) fn source(&self, peer: PeerId, addresses: Vec<Multiaddr>) -> PeerSource {
38        PeerSource {
39            peer,
40            addresses,
41            link: self.clone(),
42        }
43    }
44}
45
46/// A configured or connected untrusted package source. Metadata is matched to
47/// the request; proof verification and independent chain selection belong to
48/// the caller. Cloning a source never dials; fetch performs the bounded request.
49#[derive(Clone)]
50pub struct PeerSource {
51    peer: PeerId,
52    addresses: Vec<Multiaddr>,
53    link: Link,
54}
55
56impl PeerSource {
57    pub(crate) fn request<T>(
58        &self,
59        kind: Kind,
60        limit: Instant,
61        answer: fn(mpsc::SyncSender<Result<T, Error>>) -> Answer,
62    ) -> Result<T, Error> {
63        let target = (self.peer, self.addresses.clone());
64        request(
65            &self.link.commands,
66            &self.link.stop,
67            target,
68            kind,
69            limit,
70            answer,
71        )
72    }
73
74    /// Whether this peer has an established connection right now.
75    pub(crate) fn connected(&self) -> bool {
76        self.link.shared.is_connected(self.peer)
77    }
78
79    /// Whether one of its connections is one we dialed.
80    pub(crate) fn outbound(&self) -> bool {
81        self.link.shared.is_outbound(self.peer)
82    }
83
84    /// Configured with an address to dial, not only reached over a connection.
85    pub(crate) fn configured(&self) -> bool {
86        !self.addresses.is_empty()
87    }
88
89    /// Fire and forget; a full queue only delays the idle-connection timeout.
90    pub(crate) fn disconnect(&self) {
91        let _ = self.link.commands.try_send(Command::Disconnect(self.peer));
92    }
93}
94
95impl RecordSource for PeerSource {
96    fn fetch(&self, txid: Txid) -> Result<Option<RecordPackage>, Error> {
97        self.fetch_until(txid, Instant::now() + CALL_TIMEOUT)
98    }
99}
100
101/// Configured peers, which are dialed when not connected, then the live ones:
102/// those we dialed before those that dialed us. Cheap to clone; every call
103/// reads the current overlay.
104#[derive(Clone)]
105pub struct SyncPeers {
106    link: Link,
107    manual: Vec<PeerAddress>,
108    next: Arc<AtomicUsize>,
109    next_inbound: Arc<AtomicUsize>,
110}
111
112impl SyncPeers {
113    /// Record sources per retrieval call: retrieval tries this many.
114    pub const RECORD_PEERS: usize = 4;
115
116    pub(crate) fn new(link: Link, manual: Vec<PeerAddress>) -> Self {
117        Self {
118            link,
119            manual,
120            next: Arc::new(AtomicUsize::new(0)),
121            next_inbound: Arc::new(AtomicUsize::new(0)),
122        }
123    }
124
125    /// One source per identity, labelled by it, skipping `omitting` peers.
126    pub fn sources(&self, omitting: &[String]) -> Vec<(String, PeerSource)> {
127        let configured = self
128            .manual
129            .iter()
130            .map(|address| (address.peer_id(), vec![address.address().clone()]));
131        let live = self
132            .link
133            .shared
134            .live()
135            .into_iter()
136            .map(|peer| (peer, Vec::new()));
137        let mut sources: Vec<(String, PeerSource)> = Vec::new();
138        for (peer, addresses) in configured.chain(live) {
139            let label = peer.to_string();
140            if omitting.contains(&label) || sources.iter().any(|(known, _)| *known == label) {
141                continue;
142            }
143            sources.push((label, self.link.source(peer, addresses)));
144        }
145        sources
146    }
147
148    /// Up to `size` current peers: every configured one first, then the live
149    /// peers we dialed, then those that dialed us, each live group rotating
150    /// across calls on its own. A crowd of live peers cannot push a configured
151    /// peer out of the window, and inbound peers never come before outbound.
152    pub(crate) fn window(&self, size: usize) -> Vec<PeerSource> {
153        let peers = self.sources(&[]);
154        let (configured, live): (Vec<PeerSource>, Vec<PeerSource>) = peers
155            .into_iter()
156            .map(|(_, source)| source)
157            .partition(PeerSource::configured);
158        let (outbound, inbound): (Vec<PeerSource>, Vec<PeerSource>) =
159            live.into_iter().partition(PeerSource::outbound);
160        let mut window: Vec<PeerSource> = configured.into_iter().take(size).collect();
161        for (group, next) in [(&outbound, &self.next), (&inbound, &self.next_inbound)] {
162            let room = size.saturating_sub(window.len()).min(group.len());
163            if room > 0 {
164                let start = next.fetch_add(room, Ordering::Relaxed) % group.len();
165                window
166                    .extend((0..room).map(|offset| group[(start + offset) % group.len()].clone()));
167            }
168        }
169        window
170    }
171
172    /// Separate sources for one retrieval call, each verified on its own.
173    pub fn record_sources(&self) -> Vec<PeerSource> {
174        self.window(Self::RECORD_PEERS)
175    }
176
177    /// How many record sources a retrieval call would get right now.
178    pub fn record_count(&self) -> usize {
179        self.sources(&[]).len().min(Self::RECORD_PEERS)
180    }
181}