1use crate::{
7 Error,
8 engine::Engine,
9 model::{BookEntry, DiscoveryStatus},
10 peer::PeerNetwork,
11 peer_address::Dialed,
12 peer_protocol::GossipPeer,
13 peerbook::PeerBook,
14 recorded::{RecordedError, read, unix_seconds as now},
15};
16use libp2p::{Multiaddr, PeerId, multiaddr::Protocol};
17use rand::seq::SliceRandom;
18use std::{
19 mem,
20 path::PathBuf,
21 sync::{
22 Arc, Mutex, MutexGuard,
23 atomic::{AtomicBool, AtomicU64, Ordering},
24 },
25 time::{Duration, Instant},
26};
27
28pub struct Discovery {
29 network: Arc<PeerNetwork>,
30 book: Arc<Mutex<PeerBook>>,
31 path: PathBuf,
32 round: AtomicU64,
33 seed_error: Mutex<Option<RecordedError>>,
34 gossip_error: Mutex<Option<RecordedError>>,
35 sync_backoff: Mutex<Vec<String>>,
36 own_addresses: Mutex<Vec<Multiaddr>>,
37 relayed: Mutex<Vec<Relayed>>,
38}
39
40struct Relayed {
43 address: Multiaddr,
44 peer: PeerId,
45 since: Instant,
46}
47
48#[derive(Debug, PartialEq, Eq)]
50enum Settle {
51 Probe,
53 Wait,
55 Fail,
58}
59
60fn settle(direct: bool, connected: bool, waited: Duration) -> Settle {
61 match (direct, connected && waited < Discovery::CIRCUIT_WAIT) {
62 (true, _) => Settle::Probe,
63 (false, true) => Settle::Wait,
64 (false, false) => Settle::Fail,
65 }
66}
67
68struct Budget<'a> {
70 left: usize,
71 stop: &'a AtomicBool,
72}
73
74impl Budget<'_> {
75 fn take(&mut self) -> bool {
76 if self.left == 0 || self.stop.load(Ordering::Acquire) {
77 return false;
78 }
79 self.left -= 1;
80 true
81 }
82}
83
84fn names_self(address: &Multiaddr, local: &Protocol) -> bool {
87 let literal = matches!(
88 address.iter().next(),
89 Some(Protocol::Ip4(_) | Protocol::Ip6(_))
90 );
91 literal || address.iter().any(|part| part == *local)
92}
93
94fn names_connected(address: &Multiaddr, connected: &[PeerId]) -> bool {
97 matches!(address.iter().last(), Some(Protocol::P2p(peer)) if connected.contains(&peer))
98}
99
100fn poisoned() -> Error {
101 Error::Invalid("peer discovery state poisoned".into())
102}
103
104impl Discovery {
105 pub const TARGET: usize = 8;
107 pub const DIALS_PER_ROUND: usize = 2;
108 pub const OPERATIONS_PER_ROUND: usize = 3;
109 pub const GOSSIP_EVERY: u64 = 30;
111 pub const GOSSIP_LIMIT: u32 = 32;
112 pub const SEED_EVERY: u64 = 600;
114 pub const BOOTSTRAP_REACHED: u64 = 4;
116 pub const OPERATION_TIME: Duration = Duration::from_secs(10);
118 pub const CIRCUIT_WAIT: Duration = Duration::from_secs(120);
120 pub const RELAYED_MAX: usize = 16;
122
123 pub fn new(network: Arc<PeerNetwork>, book: Arc<Mutex<PeerBook>>, path: PathBuf) -> Self {
125 Self {
126 network,
127 book,
128 path,
129 round: AtomicU64::new(0),
130 seed_error: Mutex::new(None),
131 gossip_error: Mutex::new(None),
132 sync_backoff: Mutex::new(Vec::new()),
133 own_addresses: Mutex::new(Vec::new()),
134 relayed: Mutex::new(Vec::new()),
135 }
136 }
137
138 pub fn hold_off(&self, peers: Vec<String>) -> Result<(), Error> {
141 *self.sync_backoff.lock().map_err(|_| poisoned())? = peers;
142 Ok(())
143 }
144
145 fn lock(&self) -> Result<MutexGuard<'_, PeerBook>, Error> {
146 self.book.lock().map_err(|_| poisoned())
147 }
148
149 pub fn step(&self, engine: &Engine, stop: &AtomicBool) -> Result<(), Error> {
153 let round = self.round.fetch_add(1, Ordering::Relaxed);
154 let omitting = engine.omitting_peers()?;
155 let mut changed = self.merge_identified(&omitting)?;
156 self.publish(&omitting)?;
157 let mut budget = Budget {
158 left: Self::OPERATIONS_PER_ROUND,
159 stop,
160 };
161 let mut avoid = omitting.clone();
162 avoid.extend(
163 self.sync_backoff
164 .lock()
165 .map_err(|_| poisoned())?
166 .iter()
167 .cloned(),
168 );
169 if self.seeds_due(round)? {
170 changed |= self.dial_seeds(&avoid, &mut budget, round == 0)?;
171 }
172 changed |= self.settle_relayed(&mut budget)?;
173 changed |= self.dial_candidates(&avoid, &mut budget)?;
174 if round % Self::GOSSIP_EVERY == Self::GOSSIP_EVERY - 1 {
175 changed |= self.ask_for_peers(&omitting, &mut budget)?;
176 }
177 if changed {
178 self.lock()?.save(&self.path)?;
179 }
180 Ok(())
181 }
182
183 fn merge_identified(&self, omitting: &[String]) -> Result<bool, Error> {
186 let at = now()?;
187 let peers: Vec<GossipPeer> = self
188 .network
189 .drain_identified()
190 .into_iter()
191 .map(|(peer_id, addrs)| GossipPeer {
192 peer_id,
193 addrs,
194 last_seen: at,
195 })
196 .collect();
197 if peers.is_empty() {
198 return Ok(false);
199 }
200 let local = self.network.peer_id();
201 Ok(self.lock()?.merge_identified(&peers, local, omitting) > 0)
202 }
203
204 fn publish(&self, omitting: &[String]) -> Result<(), Error> {
206 let limit = usize::try_from(Self::GOSSIP_LIMIT)
207 .map_err(|error| Error::Invalid(format!("gossip limit: {error}")))?;
208 let at = now()?;
209 let mut peers = Vec::new();
210 for mut peer in self.lock()?.gossip(limit, omitting) {
211 peer.last_seen = peer.last_seen.min(at);
212 peers.push(peer);
213 }
214 self.network.set_gossip(peers);
215 Ok(())
216 }
217
218 fn seeds_due(&self, round: u64) -> Result<bool, Error> {
220 let connected = self.direct_peers().len();
221 if connected >= Self::TARGET {
222 return Ok(false);
223 }
224 if connected == 0 {
225 return Ok(true);
226 }
227 let reached_ever = self.lock()?.stats().reached_ever;
228 Ok((round == 0 && reached_ever < Self::BOOTSTRAP_REACHED)
229 || (round > 0 && round % Self::SEED_EVERY == 0))
230 }
231
232 fn dial(&self, address: &Multiaddr, stop: &AtomicBool) -> Result<Result<PeerId, Error>, Error> {
237 let deadline = Instant::now() + Self::OPERATION_TIME;
238 let local = Protocol::P2p(self.network.peer_id());
239 let outcome = match address.iter().any(|part| part == local) {
240 true => Err(Error::LocalPeer),
241 false => match self.network.dns().admits(address) {
242 Err(unresolvable) => return Ok(Err(unresolvable)),
244 Ok(()) => match self.network.dial_any(address.clone(), stop, deadline) {
245 Ok(Dialed::Direct(peer)) => Ok(peer),
246 Ok(Dialed::Relayed(peer)) => {
248 self.await_direct(address.clone(), peer)?;
249 Err(Error::Busy(format!(
250 "{peer} reachable only through a relay; awaiting hole punching"
251 )))
252 }
253 Err(failure) => Err(failure),
254 },
255 },
256 };
257 if stop.load(Ordering::Acquire) {
258 return Ok(outcome);
259 }
260 let at = now()?;
261 let mut book = self.lock()?;
262 match &outcome {
263 Ok(peer) => book.succeeded(address, *peer, self.network.peer_id(), at),
264 Err(Error::LocalPeer) if names_self(address, &local) => {
265 self.own()?.push(address.clone())
266 }
267 Err(Error::LocalPeer) => (),
269 Err(Error::DialDenied(_) | Error::Busy(_) | Error::PeerBusy) => (),
271 Err(_failure) => book.failed(address, at),
272 }
273 Ok(outcome)
274 }
275
276 fn own(&self) -> Result<MutexGuard<'_, Vec<Multiaddr>>, Error> {
277 self.own_addresses.lock().map_err(|_| poisoned())
278 }
279
280 fn dial_seeds(
282 &self,
283 omitting: &[String],
284 budget: &mut Budget,
285 startup: bool,
286 ) -> Result<bool, Error> {
287 let backoff_at = if startup { None } else { Some(now()?) };
288 let mut seeds = self.lock()?.seeds(omitting, backoff_at);
289 let own = self.own()?.clone();
290 let connected = self.network.connected_peer_ids();
291 seeds.retain(|seed| !own.contains(seed) && !names_connected(seed, &connected));
292 let mut dialed = false;
293 let mut error = None;
294 let mut reached = false;
295 for seed in seeds {
296 if !budget.take() {
297 break;
298 }
299 match self.dial(&seed, budget.stop)? {
300 Ok(_peer) => reached = true,
301 Err(Error::LocalPeer) => continue,
302 Err(failure) => error = Some(format!("{seed}: {failure}")),
303 }
304 dialed = true;
305 }
306 if dialed {
307 let recorded = if reached {
308 None
309 } else {
310 error.map(RecordedError::now)
311 };
312 *self.seed_error.lock().map_err(|_| poisoned())? = recorded;
313 }
314 Ok(dialed)
315 }
316
317 fn dial_candidates(&self, omitting: &[String], budget: &mut Budget) -> Result<bool, Error> {
318 if self.direct_peers().len() >= Self::TARGET {
319 return Ok(false);
320 }
321 let connected = self.network.connected_peer_ids();
323 let mut candidates =
324 self.lock()?
325 .candidates(now()?, &connected, omitting, Self::DIALS_PER_ROUND);
326 let own = self.own()?.clone();
327 candidates.retain(|candidate| !own.contains(candidate));
328 let mut dialed = false;
329 for candidate in candidates {
330 if !budget.take() {
331 break;
332 }
333 dialed = true;
334 match self.dial(&candidate, budget.stop)? {
336 Ok(_reached) => (),
337 Err(_backed_off) => (),
338 }
339 }
340 Ok(dialed)
341 }
342
343 fn ask_for_peers(&self, omitting: &[String], budget: &mut Budget) -> Result<bool, Error> {
347 let connected: Vec<PeerId> = self
348 .network
349 .connected_peer_ids()
350 .into_iter()
351 .filter(|peer| self.network.can_gossip(*peer))
352 .filter(|peer| !omitting.contains(&peer.to_string()))
353 .collect();
354 let Some(peer) = connected.choose(&mut rand::thread_rng()).copied() else {
355 return Ok(false);
356 };
357 if !budget.take() {
358 return Ok(false);
359 }
360 let deadline = Instant::now() + Self::OPERATION_TIME;
361 let outcome = self
362 .network
363 .peers_of(peer, Self::GOSSIP_LIMIT, budget.stop, deadline);
364 let (changed, recorded) = match outcome {
365 Ok(peers) => {
366 let local = self.network.peer_id();
367 (self.lock()?.merge(&peers, local, omitting) > 0, None)
368 }
369 Err(failure) => (
370 false,
371 Some(RecordedError::now(format!("{peer}: {failure}"))),
372 ),
373 };
374 *self.gossip_error.lock().map_err(|_| poisoned())? = recorded;
375 Ok(changed)
376 }
377
378 fn direct_peers(&self) -> Vec<PeerId> {
382 let mut peers = self.network.connected_peer_ids();
383 peers.retain(|peer| self.network.has_direct(*peer));
384 peers
385 }
386
387 fn relayed(&self) -> Result<MutexGuard<'_, Vec<Relayed>>, Error> {
388 self.relayed.lock().map_err(|_| poisoned())
389 }
390
391 fn await_direct(&self, address: Multiaddr, peer: PeerId) -> Result<(), Error> {
396 let evicted = {
397 let mut relayed = self.relayed()?;
398 relayed.retain(|waiting| waiting.peer != peer);
399 let evicted = match relayed.len() == Self::RELAYED_MAX {
400 true => Some(relayed.remove(0)),
401 false => None,
402 };
403 relayed.push(Relayed {
404 address,
405 peer,
406 since: Instant::now(),
407 });
408 evicted
409 };
410 let Some(entry) = evicted else {
411 return Ok(());
412 };
413 self.lock()?.failed(&entry.address, now()?);
414 Ok(())
415 }
416
417 fn settle_relayed(&self, budget: &mut Budget) -> Result<bool, Error> {
422 let waiting = mem::take(&mut *self.relayed()?);
423 let connected = self.network.connected_peer_ids();
424 let mut keep = Vec::new();
425 let mut changed = false;
426 for entry in waiting {
427 let direct = self.network.any_direct(entry.peer);
430 let verdict = settle(
431 direct,
432 connected.contains(&entry.peer),
433 entry.since.elapsed(),
434 );
435 let settled = match verdict {
436 Settle::Wait => false,
437 Settle::Probe => budget.take() && self.probe_relayed(&entry, budget.stop)?,
438 Settle::Fail => {
439 self.lock()?.failed(&entry.address, now()?);
440 true
441 }
442 };
443 match settled {
444 true => changed = true,
445 false => keep.push(entry),
446 }
447 }
448 self.relayed()?.extend(keep);
449 Ok(changed)
450 }
451
452 fn probe_relayed(&self, entry: &Relayed, stop: &AtomicBool) -> Result<bool, Error> {
456 let deadline = Instant::now() + Self::OPERATION_TIME;
457 let outcome = self.network.probe(entry.peer, stop, deadline);
458 let at = now()?;
459 let local = self.network.peer_id();
460 match outcome {
461 Ok(peer) => self.lock()?.succeeded(&entry.address, peer, local, at),
462 Err(Error::Busy(_) | Error::PeerBusy | Error::Transport(_)) => return Ok(false),
463 Err(_failure) => self.lock()?.failed(&entry.address, at),
464 }
465 Ok(true)
466 }
467
468 pub fn status(&self) -> Result<DiscoveryStatus, Error> {
469 let stats = self.lock()?.stats();
470 let (last_seed_round_error, last_seed_round_error_at) =
471 RecordedError::split(read(&self.seed_error, "seed round")?);
472 let (last_gossip_error, last_gossip_error_at) =
473 RecordedError::split(read(&self.gossip_error, "gossip")?);
474 let mut status = DiscoveryStatus::default();
475 status.seeds = stats.seeds;
476 status.book = stats.book;
477 status.reached = stats.reached;
478 status.reached_ever = stats.reached_ever;
479 status.gossip = stats.gossip;
480 status.last_seed_round_error = last_seed_round_error;
481 status.last_seed_round_error_at = last_seed_round_error_at;
482 status.last_gossip_error = last_gossip_error;
483 status.last_gossip_error_at = last_gossip_error_at;
484 Ok(status)
485 }
486
487 pub fn penalize(&self, peers: &[PeerId]) -> Result<(), Error> {
490 if peers.is_empty() {
491 return Ok(());
492 }
493 let at = now()?;
494 let mut book = self.lock()?;
495 let mut changed = false;
496 for peer in peers {
497 changed |= book.failed_peer(*peer, at);
498 }
499 if changed {
500 book.save(&self.path)?;
501 }
502 Ok(())
503 }
504
505 pub fn book(&self) -> Result<Vec<BookEntry>, Error> {
506 Ok(self.lock()?.listing())
507 }
508}
509
510#[cfg(test)]
511mod tests {
512 use super::{Discovery, Settle, names_connected, settle};
513 use crate::{
514 Error,
515 error::DialLimit,
516 peer_gossip::{Shared, probe_closes},
517 peer_limits::Class,
518 };
519 use libp2p::{Multiaddr, PeerId, identity::Keypair, multiaddr::Protocol};
520 use std::time::Duration;
521
522 fn peer() -> PeerId {
523 Keypair::generate_ed25519().public().to_peer_id()
524 }
525
526 #[test]
527 fn only_a_peer_on_another_chain_loses_its_connections_to_a_failed_probe() {
528 assert!(probe_closes(&Error::Invalid(
529 "peer response network mismatch".into()
530 )));
531 let kept = [
532 Error::Busy("peer dial deadline".into()),
533 Error::PeerBusy,
534 Error::Transport("Timeout".into()),
535 Error::Transport("Failed to dial the requested peer".into()),
536 Error::DialDenied(DialLimit::Duplicate),
537 ];
538 for error in kept {
539 assert!(!probe_closes(&error), "{error}");
540 }
541 }
542
543 #[test]
544 fn a_connected_seed_is_not_dialed_again() {
545 let (seed, other) = (peer(), peer());
546 let named: Multiaddr = format!("/ip4/11.0.0.1/tcp/28332/p2p/{seed}")
547 .parse()
548 .unwrap();
549 assert!(names_connected(&named, &[other, seed]));
550 assert!(!names_connected(&named, &[other]));
551 let unnamed: Multiaddr = "/dnsaddr/seed.example".parse().unwrap();
552 assert!(
553 !names_connected(&unnamed, &[seed]),
554 "an unidentified seed is dialed"
555 );
556 let circuit = named
557 .clone()
558 .with(Protocol::P2pCircuit)
559 .with(Protocol::P2p(other));
560 assert!(names_connected(&circuit, &[other]));
561 }
562
563 #[test]
564 fn a_punched_peer_is_probed_whatever_its_relay_does() {
565 let late = Discovery::CIRCUIT_WAIT + Duration::from_secs(1);
566 assert_eq!(settle(true, true, Duration::ZERO), Settle::Probe);
567 assert_eq!(settle(true, false, late), Settle::Probe);
568 }
569
570 #[test]
571 fn a_punched_link_demoted_before_its_probe_is_still_probed() {
572 let shared = Shared::new(peer());
573 let remote = peer();
574 shared.opened(remote, false, Class::Relayed);
575 shared.opened(remote, true, Class::Data);
576 shared.reclassed(remote, true, (Class::Data, Class::Demoted));
577 assert!(!shared.has_direct(remote));
578 let late = Discovery::CIRCUIT_WAIT + Duration::from_secs(1);
579 assert_eq!(
580 settle(shared.any_direct(remote), shared.is_connected(remote), late),
581 Settle::Probe
582 );
583 }
584
585 #[test]
586 fn a_relayed_contact_waits_one_circuit_then_fails() {
587 let early = Discovery::CIRCUIT_WAIT - Duration::from_secs(1);
588 assert_eq!(settle(false, true, early), Settle::Wait);
589 assert_eq!(settle(false, true, Discovery::CIRCUIT_WAIT), Settle::Fail);
590 assert_eq!(settle(false, false, Duration::ZERO), Settle::Fail);
591 }
592}