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