1use crate::{
5 Error,
6 peer::{
7 Answer, Behaviour, CALL_TIMEOUT, Command, PeerNetwork, Pending, bind_response, request,
8 },
9 peer_address::{AddressKind, Dialed, is_circuit, parse, relay},
10 peer_limits::{Class, LimitDenied},
11 peer_links::Links,
12 peer_nat::{Nat, PeerLink, Reachability},
13 peer_protocol::{
14 GossipPeer, Kind, PEER_ADDRESSES_MAX, PEERS_LIMIT, PEERS_MAX, Reply, Response,
15 },
16 source::check_cancelled,
17};
18use bitcoin::BlockHash;
19use libp2p::{
20 Multiaddr, PeerId, Swarm,
21 multiaddr::Protocol,
22 swarm::{ConnectionId, DialError, dial_opts::DialOpts},
23};
24use rand::seq::SliceRandom;
25use std::{
26 collections::{BTreeMap, BTreeSet, HashMap, VecDeque},
27 net::{Ipv4Addr, Ipv6Addr},
28 sync::{
29 Mutex, MutexGuard,
30 atomic::{AtomicBool, Ordering},
31 mpsc::{self, RecvTimeoutError},
32 },
33 time::{Duration, Instant, SystemTime, UNIX_EPOCH},
34};
35
36pub(crate) struct Shared {
40 local: PeerId,
41 gossip: Mutex<Vec<GossipPeer>>,
42 observed: Mutex<ObservedAddrs>,
43 connected: Mutex<BTreeMap<PeerId, Links>>,
44 identified: Mutex<VecDeque<(PeerId, Vec<Multiaddr>)>>,
45 listening: Mutex<Vec<Multiaddr>>,
46 pub(crate) nat: Nat,
47}
48
49pub(crate) fn guard<T>(lock: &Mutex<T>) -> MutexGuard<'_, T> {
50 match lock.lock() {
51 Ok(guard) => guard,
52 Err(poisoned) => poisoned.into_inner(),
53 }
54}
55
56#[derive(Default)]
59pub struct ObservedAddrs {
60 candidates: Vec<(Multiaddr, BTreeSet<PeerId>)>,
61 confirmed: Vec<Multiaddr>,
62}
63
64impl ObservedAddrs {
65 pub const WITNESSES: usize = 2;
66 const CANDIDATES_MAX: usize = 32;
67 const CONFIRMED_MAX: usize = 8;
68
69 pub fn observe(&mut self, address: Multiaddr, reporter: PeerId) -> Option<Multiaddr> {
72 if !is_public(&address)
73 || validate_gossip_address(&address).is_err()
74 || self.confirmed.contains(&address)
75 {
76 return None;
77 }
78 let index = match self
79 .candidates
80 .iter()
81 .position(|(known, _)| *known == address)
82 {
83 Some(index) => index,
84 None => {
85 if self.candidates.len() == Self::CANDIDATES_MAX {
86 self.candidates.remove(0);
87 }
88 self.candidates.push((address, BTreeSet::new()));
89 self.candidates.len() - 1
90 }
91 };
92 self.candidates[index].1.insert(reporter);
93 if self.candidates[index].1.len() < Self::WITNESSES {
94 return None;
95 }
96 let (address, _) = self.candidates.remove(index);
97 if self.confirmed.len() == Self::CONFIRMED_MAX {
98 self.confirmed.remove(0);
99 }
100 self.confirmed.push(address.clone());
101 Some(address)
102 }
103
104 pub fn confirmed(&self) -> Vec<Multiaddr> {
105 self.confirmed.clone()
106 }
107}
108
109impl Shared {
110 const GOSSIP_MAX: usize = 256;
111 const IDENTIFIED_MAX: usize = 64;
112 const ENVELOPE: usize = 1024;
113
114 pub(crate) fn new(local: PeerId) -> Self {
115 Self {
116 local,
117 gossip: Mutex::new(Vec::new()),
118 observed: Mutex::new(ObservedAddrs::default()),
119 connected: Mutex::new(BTreeMap::new()),
120 identified: Mutex::new(VecDeque::new()),
121 listening: Mutex::new(Vec::new()),
122 nat: Nat::default(),
123 }
124 }
125
126 pub(crate) fn identified(&self, peer: PeerId, advertised: Vec<Multiaddr>) {
128 let addresses: Vec<Multiaddr> = advertised
129 .into_iter()
130 .filter(|address| validate_gossip_address(address).is_ok() && is_public(address))
131 .take(PEER_ADDRESSES_MAX)
132 .collect();
133 if addresses.is_empty() || peer == self.local {
134 return;
135 }
136 let mut queue = match self.identified.lock() {
137 Ok(queue) => queue,
138 Err(poisoned) => poisoned.into_inner(),
139 };
140 queue.retain(|(known, _)| *known != peer);
141 if queue.len() == Self::IDENTIFIED_MAX {
142 queue.pop_front();
143 }
144 queue.push_back((peer, addresses));
145 }
146
147 fn drain_identified(&self) -> Vec<(PeerId, Vec<Multiaddr>)> {
148 match self.identified.lock() {
149 Ok(mut queue) => queue.drain(..).collect(),
150 Err(poisoned) => poisoned.into_inner().drain(..).collect(),
151 }
152 }
153
154 fn set_gossip(&self, mut peers: Vec<GossipPeer>) {
155 peers.truncate(Self::GOSSIP_MAX);
156 if let Ok(mut gossip) = self.gossip.lock() {
157 *gossip = peers;
158 }
159 }
160
161 fn observed(&self) -> Vec<Multiaddr> {
162 match self.observed.lock() {
163 Ok(observed) => observed.confirmed(),
164 Err(poisoned) => poisoned.into_inner().confirmed(),
165 }
166 }
167
168 pub(crate) fn is_connected(&self, peer: PeerId) -> bool {
169 guard(&self.connected).contains_key(&peer)
170 }
171
172 pub(crate) fn is_outbound(&self, peer: PeerId) -> bool {
174 guard(&self.connected)
175 .get(&peer)
176 .is_some_and(|links| links.outbound_data > 0)
177 }
178
179 fn connected(&self) -> Vec<PeerId> {
180 guard(&self.connected).keys().copied().collect()
181 }
182
183 pub(crate) fn live(&self) -> Vec<PeerId> {
187 let connected = guard(&self.connected);
188 let (outbound, inbound): (Vec<_>, Vec<_>) = connected
189 .iter()
190 .filter(|(_, links)| links.on_chain && links.data > 0)
191 .partition(|(_, links)| links.outbound_data > 0);
192 outbound
193 .into_iter()
194 .chain(inbound)
195 .map(|(peer, _)| *peer)
196 .collect()
197 }
198
199 pub(crate) fn has_direct(&self, peer: PeerId) -> bool {
202 guard(&self.connected)
203 .get(&peer)
204 .is_some_and(|links| links.data > 0)
205 }
206
207 pub(crate) fn can_gossip(&self, peer: PeerId) -> bool {
212 guard(&self.connected)
213 .get(&peer)
214 .is_some_and(|links| links.data > 0 || links.outbound_demoted > 0)
215 }
216
217 pub(crate) fn any_direct(&self, peer: PeerId) -> bool {
220 guard(&self.connected)
221 .get(&peer)
222 .is_some_and(|links| links.data > 0 || links.demoted > 0)
223 }
224
225 pub(crate) fn links(&self) -> Vec<(PeerId, PeerLink)> {
227 guard(&self.connected)
228 .iter()
229 .filter_map(|(peer, links)| Some((*peer, links.link()?)))
230 .collect()
231 }
232
233 pub(crate) fn opened(&self, peer: PeerId, outbound: bool, class: Class) {
234 let mut connected = guard(&self.connected);
235 connected
236 .entry(peer)
237 .or_default()
238 .count(outbound, class, true);
239 }
240
241 pub(crate) fn closed(&self, peer: PeerId, outbound: bool, class: Class) {
243 let mut connected = guard(&self.connected);
244 let Some(links) = connected.get_mut(&peer) else {
245 return;
246 };
247 links.count(outbound, class, false);
248 if links.inbound == 0 && links.outbound == 0 {
249 connected.remove(&peer);
250 }
251 }
252
253 pub(crate) fn reclassed(&self, peer: PeerId, outbound: bool, (from, to): (Class, Class)) {
255 if let Some(links) = guard(&self.connected).get_mut(&peer) {
256 links.count(outbound, from, false);
257 links.count(outbound, to, true);
258 }
259 }
260
261 pub(crate) fn is_on_chain(&self, peer: PeerId) -> bool {
262 guard(&self.connected)
263 .get(&peer)
264 .is_some_and(|links| links.on_chain)
265 }
266
267 pub(crate) fn on_chain(&self, peer: PeerId) {
270 if let Some(links) = guard(&self.connected).get_mut(&peer) {
271 links.on_chain = true;
272 }
273 }
274
275 pub(crate) fn listening(&self, address: Multiaddr, bound: bool) {
276 let mut listening = guard(&self.listening);
277 listening.retain(|known| *known != address);
278 if bound {
279 listening.push(address);
280 }
281 }
282
283 pub(crate) fn listeners(&self) -> Vec<Multiaddr> {
284 guard(&self.listening).clone()
285 }
286
287 pub(crate) fn observe(&self, address: Multiaddr, reporter: PeerId) -> Option<Multiaddr> {
290 match self.observed.lock() {
291 Ok(mut observed) => observed.observe(address, reporter),
292 Err(poisoned) => poisoned.into_inner().observe(address, reporter),
293 }
294 }
295
296 pub(crate) fn serve(&self, requester: PeerId, loopback: bool, limit: u32) -> Reply {
301 let Ok(limit) = usize::try_from(limit) else {
302 return Reply::Refused;
303 };
304 if limit > usize::try_from(PEERS_MAX).unwrap_or(0) {
305 return Reply::Refused;
306 }
307 let now = unix_now();
308 let reserved = self.reserved(requester, now);
309 let held: Vec<PeerId> = reserved.iter().map(|peer| peer.peer_id).collect();
310 let Ok(snapshot) = self.gossip.lock() else {
311 return Reply::Busy;
312 };
313 let mut listed = snapshot
314 .iter()
315 .filter(|entry| entry.peer_id != requester && entry.peer_id != self.local)
316 .filter(|entry| !held.contains(&entry.peer_id))
317 .filter_map(|entry| servable(entry, loopback, now));
318 let mut reserved = reserved.into_iter();
319 let mut peers = Vec::new();
320 while peers.len() < limit {
321 let (held, known) = (reserved.next(), listed.next());
322 if held.is_none() && known.is_none() {
323 break;
324 }
325 peers.extend(held.into_iter().chain(known));
326 }
327 peers.truncate(limit);
328 loop {
329 match serde_json::to_vec(&peers) {
330 Ok(bytes) if bytes.len() + Self::ENVELOPE <= PEERS_LIMIT => break,
331 Ok(_) => {
332 peers.pop();
333 }
334 Err(_) => return Reply::Busy,
335 }
336 }
337 Reply::Peers(peers)
338 }
339
340 fn reserved(&self, requester: PeerId, now: u64) -> Vec<GossipPeer> {
344 let hops: Vec<Multiaddr> = self
345 .nat
346 .external()
347 .into_iter()
348 .map(|external| {
349 external
350 .with(Protocol::P2p(self.local))
351 .with(Protocol::P2pCircuit)
352 })
353 .filter(|hop| parse(hop).is_ok())
354 .take(PEER_ADDRESSES_MAX)
355 .collect();
356 if hops.is_empty() {
357 return Vec::new();
358 }
359 let mut holders = self.nat.reserved_by_others();
360 holders.retain(|holder| *holder != requester && *holder != self.local);
361 holders.shuffle(&mut rand::thread_rng());
362 holders
363 .into_iter()
364 .map(|peer_id| GossipPeer {
365 peer_id,
366 addrs: hops.clone(),
367 last_seen: now,
368 })
369 .collect()
370 }
371}
372
373fn servable(entry: &GossipPeer, loopback: bool, now: u64) -> Option<GossipPeer> {
375 let addrs: Vec<Multiaddr> = entry
376 .addrs
377 .iter()
378 .filter(|address| validate_gossip_address(address).is_ok())
379 .filter(|address| loopback || is_public(address))
380 .take(PEER_ADDRESSES_MAX)
381 .cloned()
382 .collect();
383 (!addrs.is_empty()).then(|| GossipPeer {
384 peer_id: entry.peer_id,
385 addrs,
386 last_seen: entry.last_seen.min(now),
387 })
388}
389
390pub fn is_public(address: &Multiaddr) -> bool {
393 match address.iter().next() {
394 Some(Protocol::Ip4(ip)) => public_v4(ip),
395 Some(Protocol::Ip6(ip)) => match ip.to_ipv4_mapped() {
396 Some(mapped) => public_v4(mapped),
397 None => public_v6(ip),
398 },
399 Some(
400 Protocol::Dns(name)
401 | Protocol::Dns4(name)
402 | Protocol::Dns6(name)
403 | Protocol::Dnsaddr(name),
404 ) => name != "localhost" && !name.ends_with(".localhost"),
405 _ => false,
406 }
407}
408
409fn public_v4(ip: Ipv4Addr) -> bool {
410 let [first, second, third, _] = ip.octets();
411 let reserved = first == 0
412 || (first == 100 && (second & 0xc0) == 64)
413 || (first == 192 && second == 0 && third == 0)
414 || (first == 198 && (second & 0xfe) == 18)
415 || first >= 240;
416 !(reserved
417 || ip.is_loopback()
418 || ip.is_private()
419 || ip.is_link_local()
420 || ip.is_multicast()
421 || ip.is_documentation())
422}
423
424fn public_v6(ip: Ipv6Addr) -> bool {
425 let documentation = ip.segments()[..2] == [0x2001, 0x0db8];
426 !(documentation
427 || ip.is_loopback()
428 || ip.is_unspecified()
429 || ip.is_multicast()
430 || ip.is_unique_local()
431 || ip.is_unicast_link_local())
432}
433
434fn unix_now() -> u64 {
435 match SystemTime::now().duration_since(UNIX_EPOCH) {
436 Ok(elapsed) => elapsed.as_secs(),
437 Err(_clock_before_epoch) => 0,
438 }
439}
440
441pub(crate) fn is_loopback(address: &Multiaddr) -> bool {
442 match address.iter().next() {
443 Some(Protocol::Ip4(ip)) => ip.is_loopback(),
444 Some(Protocol::Ip6(ip)) => ip.is_loopback(),
445 _ => false,
446 }
447}
448
449pub fn validate_gossip_address(address: &Multiaddr) -> Result<Option<PeerId>, Error> {
452 let shape = parse(address)?;
453 match shape.bare.iter().next() {
454 Some(Protocol::Ip4(_) | Protocol::Ip6(_)) => Ok(shape.peer),
455 _ => Err(Error::Invalid(
456 "gossiped addresses must be IP literals".into(),
457 )),
458 }
459}
460
461pub(crate) fn probe_closes(error: &Error) -> bool {
466 matches!(error, Error::Invalid(_))
467}
468
469pub fn validate_dial(address: &Multiaddr) -> Result<Option<PeerId>, Error> {
472 let shape = parse(address)?;
473 if shape.peer.is_none() && matches!(shape.kind, AddressKind::Circuit(_)) {
474 return Err(Error::Invalid(
475 "circuit dial must name its target peer".into(),
476 ));
477 }
478 Ok(shape.peer)
479}
480
481pub(crate) fn validate_peers(
483 genesis: BlockHash,
484 peer: PeerId,
485 pending: &Pending,
486 response: Response,
487) -> Result<Vec<GossipPeer>, Error> {
488 bind_response(genesis, peer, pending, &response)?;
489 let Kind::Peers { limit } = pending.kind else {
490 return Err(Error::Invalid("peers reply to another request".into()));
491 };
492 match response.result {
493 Reply::Peers(peers) => {
494 let limit = usize::try_from(limit).map_err(|e| Error::Invalid(e.to_string()))?;
495 if peers.len() > limit {
496 return Err(Error::Invalid(
497 "peer gossip exceeds the requested limit".into(),
498 ));
499 }
500 for entry in &peers {
501 check_gossip(entry)?;
502 }
503 Ok(peers)
504 }
505 Reply::Busy => Err(Error::PeerBusy),
506 _ => Err(Error::Invalid(
507 "peer refused the peers request or answered another kind".into(),
508 )),
509 }
510}
511
512fn check_gossip(entry: &GossipPeer) -> Result<(), Error> {
513 let peer = entry.peer_id;
514 if entry.addrs.is_empty() || entry.addrs.len() > PEER_ADDRESSES_MAX {
515 return Err(Error::Invalid(
516 "peer gossip address count out of range".into(),
517 ));
518 }
519 for address in &entry.addrs {
520 if validate_gossip_address(address)?.is_some_and(|named| named != peer)
521 || relay(address) == Some(peer)
522 {
523 return Err(Error::Invalid(
524 "peer gossip address names another peer".into(),
525 ));
526 }
527 }
528 Ok(())
529}
530
531pub(crate) struct Dial {
532 address: Multiaddr,
533 deadline: Instant,
534 answer: mpsc::SyncSender<Result<PeerId, Error>>,
535}
536
537impl Dial {
538 fn answer(&self, result: Result<PeerId, Error>) {
539 let _ = self.answer.try_send(result);
540 }
541}
542
543#[derive(Default)]
545pub(crate) struct Dials(HashMap<ConnectionId, (Option<PeerId>, Dial)>);
546
547impl Dials {
548 const MAX: usize = 8;
549
550 pub(crate) fn start(&mut self, swarm: &mut Swarm<Behaviour>, dial: Dial) {
551 let expected = match validate_dial(&dial.address) {
552 Ok(expected) => expected,
553 Err(error) => return dial.answer(Err(error)),
554 };
555 if Instant::now() >= dial.deadline || self.0.len() >= Self::MAX {
556 return dial.answer(Err(Error::Busy("peer dial capacity or deadline".into())));
557 }
558 if let Some(peer) = expected.filter(|peer| swarm.is_connected(peer)) {
559 return dial.answer(Ok(peer));
560 }
561 let options = match expected {
562 Some(peer) => DialOpts::peer_id(peer)
563 .addresses(vec![dial.address.clone()])
564 .build(),
565 None => DialOpts::unknown_peer_id()
566 .address(dial.address.clone())
567 .build(),
568 };
569 let connection = options.connection_id();
570 match swarm.dial(options) {
571 Ok(()) => {
572 self.0.insert(connection, (expected, dial));
573 }
574 Err(error) => dial.answer(Err(dial_error(&error))),
575 }
576 }
577
578 pub(crate) fn established(&mut self, connection: ConnectionId, peer: PeerId) {
579 if let Some((expected, dial)) = self.0.remove(&connection) {
580 if expected.is_some_and(|expected| expected != peer) {
581 return dial.answer(Err(Error::Invalid(
582 "dialed peer authenticated as another identity".into(),
583 )));
584 }
585 dial.answer(Ok(peer));
586 }
587 }
588
589 pub(crate) fn failed(&mut self, connection: ConnectionId, error: &DialError) {
590 if let Some((_, dial)) = self.0.remove(&connection) {
591 dial.answer(Err(dial_error(error)));
592 }
593 }
594
595 pub(crate) fn expire(&mut self) {
597 let now = Instant::now();
598 self.0.retain(|_, (_, dial)| {
599 if now < dial.deadline {
600 return true;
601 }
602 dial.answer(Err(Error::Busy("peer dial deadline".into())));
603 false
604 });
605 }
606
607 pub(crate) fn close(&mut self) {
608 for (_, (_, dial)) in self.0.drain() {
609 dial.answer(Err(Error::Busy("peer network stopped".into())));
610 }
611 }
612}
613
614fn dial_error(error: &DialError) -> Error {
615 match error {
616 DialError::Denied { cause } => match cause.downcast_ref::<LimitDenied>() {
617 Some(LimitDenied(limit)) => Error::DialDenied(*limit),
618 None => Error::Invalid(format!("peer dial: {error}")),
619 },
620 DialError::LocalPeerId { .. } => Error::LocalPeer,
621 DialError::DialPeerConditionFalse(condition) => {
622 Error::Busy(format!("peer dial already pending ({condition:?})"))
623 }
624 other => Error::Invalid(format!("peer dial: {other}")),
625 }
626}
627
628impl PeerNetwork {
629 const DIAL_POLL: Duration = Duration::from_millis(50);
630
631 pub fn set_gossip(&self, peers: Vec<GossipPeer>) {
634 self.shared.set_gossip(peers);
635 }
636
637 pub fn observed_addrs(&self) -> Vec<Multiaddr> {
639 self.shared.observed()
640 }
641
642 pub fn connected_peer_ids(&self) -> Vec<PeerId> {
644 self.shared.connected()
645 }
646
647 pub fn peer_links(&self) -> Vec<(PeerId, PeerLink)> {
649 self.shared.links()
650 }
651
652 pub fn has_direct(&self, peer: PeerId) -> bool {
655 self.shared.has_direct(peer)
656 }
657
658 pub fn can_gossip(&self, peer: PeerId) -> bool {
660 self.shared.can_gossip(peer)
661 }
662
663 pub fn any_direct(&self, peer: PeerId) -> bool {
665 self.shared.any_direct(peer)
666 }
667
668 pub fn reachability(&self) -> Reachability {
669 self.shared.nat.reachability()
670 }
671
672 pub fn external_addresses(&self) -> Vec<Multiaddr> {
674 self.shared.nat.external()
675 }
676
677 pub fn relays(&self) -> Vec<PeerId> {
679 self.shared.nat.relays()
680 }
681
682 pub fn reserved_by_others(&self) -> Vec<PeerId> {
684 self.shared.nat.reserved_by_others()
685 }
686
687 pub fn circuit_addresses(&self) -> Vec<Multiaddr> {
689 self.shared.nat.circuits()
690 }
691
692 pub fn drain_identified(&self) -> Vec<(PeerId, Vec<Multiaddr>)> {
695 self.shared.drain_identified()
696 }
697
698 pub fn dial(
701 &self,
702 address: Multiaddr,
703 cancelled: &AtomicBool,
704 deadline: Instant,
705 ) -> Result<PeerId, Error> {
706 match self.dial_any(address, cancelled, deadline)? {
707 Dialed::Direct(peer) => Ok(peer),
708 Dialed::Relayed(peer) => Err(Error::Busy(format!("{peer} reachable only via relay"))),
709 }
710 }
711
712 pub fn dial_any(
716 &self,
717 address: Multiaddr,
718 cancelled: &AtomicBool,
719 deadline: Instant,
720 ) -> Result<Dialed, Error> {
721 validate_dial(&address)?;
722 check_cancelled(cancelled)?;
723 if self.stop.load(Ordering::Acquire) {
724 return Err(Error::Busy("peer network stopped".into()));
725 }
726 let deadline = deadline.min(Instant::now() + CALL_TIMEOUT);
727 let circuit = is_circuit(&address);
728 let (answer, receiver) = mpsc::sync_channel(1);
729 self.commands
730 .try_send(Command::Dial(Dial {
731 address,
732 deadline,
733 answer,
734 }))
735 .map_err(|_| Error::Busy("peer command queue unavailable".into()))?;
736 let peer = loop {
737 check_cancelled(cancelled)?;
738 let left = deadline.saturating_duration_since(Instant::now());
739 if left.is_zero() {
740 return Err(Error::Busy("peer dial deadline".into()));
741 }
742 match receiver.recv_timeout(left.min(Self::DIAL_POLL)) {
743 Ok(result) => break result?,
744 Err(RecvTimeoutError::Timeout) => (),
745 Err(RecvTimeoutError::Disconnected) => {
746 return Err(Error::Busy("peer network stopped".into()));
747 }
748 }
749 };
750 if circuit && !self.any_direct(peer) {
751 return Ok(Dialed::Relayed(peer));
752 }
753 self.probe(peer, cancelled, deadline).map(Dialed::Direct)
754 }
755
756 pub fn probe(
759 &self,
760 peer: PeerId,
761 cancelled: &AtomicBool,
762 deadline: Instant,
763 ) -> Result<PeerId, Error> {
764 let error = match self.peers_of(peer, 0, cancelled, deadline) {
765 Ok(_) => return Ok(peer),
766 Err(error) => error,
767 };
768 if probe_closes(&error) {
769 let _ = self.commands.try_send(Command::Disconnect(peer));
770 }
771 Err(error)
772 }
773
774 pub fn peers_of(
777 &self,
778 peer: PeerId,
779 limit: u32,
780 cancelled: &AtomicBool,
781 deadline: Instant,
782 ) -> Result<Vec<GossipPeer>, Error> {
783 if limit > PEERS_MAX {
784 return Err(Error::Invalid("peers request limit exceeds 32".into()));
785 }
786 check_cancelled(cancelled)?;
787 let kind = Kind::Peers { limit };
788 request(
789 &self.commands,
790 &self.stop,
791 (peer, Vec::new()),
792 kind,
793 deadline,
794 Answer::Peers,
795 )
796 }
797}