Skip to main content

zakura_network/
address_book.rs

1//! The `AddressBook` manages information about what peers exist, when they were
2//! seen, and what services they provide.
3
4use std::{
5    cmp::Reverse,
6    collections::HashMap,
7    net::{IpAddr, SocketAddr},
8    sync::{Arc, Mutex},
9    time::Instant,
10};
11
12use chrono::Utc;
13use indexmap::IndexMap;
14use ordered_map::OrderedMap;
15use tokio::sync::watch;
16use tracing::Span;
17
18use zakura_chain::{parameters::Network, serialization::DateTime32};
19
20use crate::{
21    constants::{self, ADDR_RESPONSE_LIMIT_DENOMINATOR, MAX_ADDRS_IN_MESSAGE},
22    meta_addr::MetaAddrChange,
23    protocol::external::{canonical_peer_addr, canonical_socket_addr},
24    types::{MetaAddr, PeerServices},
25    AddressBookPeers, PeerAddrState, PeerSocketAddr,
26};
27
28#[cfg(test)]
29mod tests;
30
31/// A database of peer listener addresses, their advertised services, and
32/// information on when they were last seen.
33///
34/// # Security
35///
36/// Address book state must be based on outbound connections to peers.
37///
38/// If the address book is updated incorrectly:
39/// - malicious peers can interfere with other peers' `AddressBook` state,
40///   or
41/// - Zebra can advertise unreachable addresses to its own peers.
42///
43/// ## Adding Addresses
44///
45/// The address book should only contain Zcash listener port addresses from peers
46/// on the configured network. These addresses can come from:
47/// - DNS seeders
48/// - addresses gossiped by other peers
49/// - the canonical address (`Version.address_from`) provided by each peer,
50///   particularly peers on inbound connections.
51///
52/// The remote addresses of inbound connections must not be added to the address
53/// book, because they contain ephemeral outbound ports, not listener ports.
54///
55/// Isolated connections must not add addresses or update the address book.
56///
57/// ## Updating Address State
58///
59/// Updates to address state must be based on outbound connections to peers.
60///
61/// Updates must not be based on:
62/// - the remote addresses of inbound connections, or
63/// - the canonical address of any connection.
64#[derive(Debug)]
65pub struct AddressBook {
66    /// Peer listener addresses, suitable for outbound connections,
67    /// in connection attempt order.
68    ///
69    /// Some peers in this list might have open outbound or inbound connections.
70    ///
71    /// We reverse the comparison order, because the standard library
72    /// ([`BTreeMap`](std::collections::BTreeMap)) sorts in ascending order, but
73    /// [`OrderedMap`] sorts in descending order.
74    by_addr: OrderedMap<PeerSocketAddr, MetaAddr, Reverse<MetaAddr>>,
75
76    /// The address with a last_connection_state of [`PeerAddrState::Responded`] and
77    /// the most recent `last_response` time by IP.
78    ///
79    /// This is used to avoid initiating outbound connections past [`Config::max_connections_per_ip`](crate::config::Config), and
80    /// currently only supports a `max_connections_per_ip` of 1, and must be `None` when used with a greater `max_connections_per_ip`.
81    // TODO: Replace with `by_ip: HashMap<IpAddr, BTreeMap<DateTime32, MetaAddr>>` to support configured `max_connections_per_ip` greater than 1
82    most_recent_by_ip: Option<HashMap<IpAddr, MetaAddr>>,
83
84    /// A list of banned addresses, with the time they were banned.
85    bans_by_ip: Arc<IndexMap<IpAddr, Instant>>,
86
87    /// The local listener address.
88    local_listener: SocketAddr,
89
90    /// The services advertised for our own [`local_listener`](Self::local_listener)
91    /// address when it is gossiped to peers.
92    ///
93    /// This must match the services advertised during the handshake, so a node
94    /// that does not advertise [`PeerServices::NODE_NETWORK`] (for example, a
95    /// pruned node) does not gossip itself as a full node.
96    local_listener_services: PeerServices,
97
98    /// The configured Zcash network.
99    network: Network,
100
101    /// The maximum number of addresses in the address book.
102    ///
103    /// Always set to [`MAX_ADDRS_IN_ADDRESS_BOOK`](constants::MAX_ADDRS_IN_ADDRESS_BOOK),
104    /// in release builds. Lower values are used during testing.
105    addr_limit: usize,
106
107    /// The span for operations on this address book.
108    span: Span,
109
110    /// Whether operational log fields expose legacy peer addresses.
111    expose_peer_addresses: bool,
112
113    /// A channel used to send the latest address book metrics.
114    address_metrics_tx: watch::Sender<AddressMetrics>,
115
116    /// The last time we logged a message about the address metrics.
117    last_address_log: Option<Instant>,
118}
119
120/// Metrics about the states of the addresses in an [`AddressBook`].
121#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)]
122pub struct AddressMetrics {
123    /// The number of addresses in the `Responded` state.
124    pub responded: usize,
125
126    /// The number of addresses in the `NeverAttemptedGossiped` state.
127    pub never_attempted_gossiped: usize,
128
129    /// The number of addresses in the `Failed` state.
130    pub failed: usize,
131
132    /// The number of addresses in the `AttemptPending` state.
133    pub attempt_pending: usize,
134
135    /// The number of `Responded` addresses within the liveness limit.
136    pub recently_live: usize,
137
138    /// The number of `Responded` addresses outside the liveness limit.
139    pub recently_stopped_responding: usize,
140
141    /// The number of addresses in the address book, regardless of their states.
142    pub num_addresses: usize,
143
144    /// The maximum number of addresses in the address book.
145    pub address_limit: usize,
146}
147
148#[allow(clippy::len_without_is_empty)]
149impl AddressBook {
150    /// Construct an [`AddressBook`] with the given `local_listener` on `network`.
151    ///
152    /// Uses the supplied [`tracing::Span`] for address book operations.
153    pub fn new(
154        local_listener: SocketAddr,
155        network: &Network,
156        max_connections_per_ip: usize,
157        span: Span,
158    ) -> AddressBook {
159        let constructor_span = span.clone();
160        let _guard = constructor_span.enter();
161
162        let instant_now = Instant::now();
163        let chrono_now = Utc::now();
164
165        // The default value is correct for an empty address book,
166        // and it gets replaced by `update_metrics` anyway.
167        let (address_metrics_tx, _address_metrics_rx) = watch::channel(AddressMetrics::default());
168
169        // Avoid initiating outbound handshakes when max_connections_per_ip is 1.
170        let should_limit_outbound_conns_per_ip = max_connections_per_ip == 1;
171        let mut new_book = AddressBook {
172            by_addr: OrderedMap::new(|meta_addr| Reverse(*meta_addr)),
173            local_listener: canonical_socket_addr(local_listener),
174            // Default to full-node services; callers that advertise different
175            // services set them with `with_local_listener_services`.
176            local_listener_services: PeerServices::NODE_NETWORK,
177            network: network.clone(),
178            addr_limit: constants::MAX_ADDRS_IN_ADDRESS_BOOK,
179            span,
180            expose_peer_addresses: false,
181            address_metrics_tx,
182            last_address_log: None,
183            most_recent_by_ip: should_limit_outbound_conns_per_ip.then(HashMap::new),
184            bans_by_ip: Default::default(),
185        };
186
187        new_book.update_metrics(instant_now, chrono_now);
188        new_book
189    }
190
191    /// Sets the services advertised for our own gossiped listener address.
192    ///
193    /// These must match the services advertised during the handshake.
194    #[must_use]
195    pub fn with_local_listener_services(mut self, services: PeerServices) -> Self {
196        self.local_listener_services = services;
197        self
198    }
199
200    /// Sets whether operational logs expose legacy peer addresses.
201    #[must_use]
202    pub(crate) fn with_expose_peer_addresses(mut self, expose_peer_addresses: bool) -> Self {
203        self.expose_peer_addresses = expose_peer_addresses;
204        self
205    }
206
207    /// Construct an [`AddressBook`] with the given `local_listener`, `network`,
208    /// `addr_limit`, [`tracing::Span`], and addresses.
209    ///
210    /// `addr_limit` is enforced by this method, and by [`AddressBook::update`].
211    ///
212    /// If there are multiple [`MetaAddr`]s with the same address,
213    /// an arbitrary address is inserted into the address book,
214    /// and the rest are dropped.
215    ///
216    /// This constructor can be used to break address book invariants,
217    /// so it should only be used in tests.
218    #[cfg(any(test, feature = "proptest-impl"))]
219    pub fn new_with_addrs(
220        local_listener: SocketAddr,
221        network: &Network,
222        max_connections_per_ip: usize,
223        addr_limit: usize,
224        span: Span,
225        addrs: impl IntoIterator<Item = MetaAddr>,
226    ) -> AddressBook {
227        let constructor_span = span.clone();
228        let _guard = constructor_span.enter();
229
230        let instant_now = Instant::now();
231        let chrono_now = Utc::now();
232
233        // The maximum number of addresses should be always greater than 0
234        assert!(addr_limit > 0);
235
236        let mut new_book = AddressBook::new(local_listener, network, max_connections_per_ip, span);
237        new_book.addr_limit = addr_limit;
238
239        let addrs = addrs
240            .into_iter()
241            .map(|mut meta_addr| {
242                meta_addr.addr = canonical_peer_addr(meta_addr.addr);
243                meta_addr
244            })
245            .filter(|meta_addr| meta_addr.address_is_valid_for_outbound(network))
246            .map(|meta_addr| (meta_addr.addr, meta_addr));
247
248        for (socket_addr, meta_addr) in addrs {
249            // overwrite any duplicate addresses
250            new_book.by_addr.insert(socket_addr, meta_addr);
251            // Add the address to `most_recent_by_ip` if it has responded
252            if new_book.should_update_most_recent_by_ip(meta_addr) {
253                new_book
254                    .most_recent_by_ip
255                    .as_mut()
256                    .expect("should be some when should_update_most_recent_by_ip is true")
257                    .insert(socket_addr.ip(), meta_addr);
258            }
259            // exit as soon as we get enough addresses
260            if new_book.by_addr.len() >= addr_limit {
261                break;
262            }
263        }
264
265        new_book.update_metrics(instant_now, chrono_now);
266        new_book
267    }
268
269    /// Return a watch channel for the address book metrics.
270    ///
271    /// The metrics in the watch channel are only updated when the address book updates,
272    /// so they can be significantly outdated if Zebra is disconnected or hung.
273    ///
274    /// The current metrics value is marked as seen.
275    /// So `Receiver::changed` will only return after the next address book update.
276    pub fn address_metrics_watcher(&self) -> watch::Receiver<AddressMetrics> {
277        self.address_metrics_tx.subscribe()
278    }
279
280    /// Set the local listener address. Only for use in tests.
281    #[cfg(any(test, feature = "proptest-impl"))]
282    pub fn set_local_listener(&mut self, addr: SocketAddr) {
283        self.local_listener = addr;
284    }
285
286    /// Get the local listener address.
287    ///
288    /// This address contains minimal state, but it is not sanitized.
289    pub fn local_listener_meta_addr(&self, now: chrono::DateTime<Utc>) -> MetaAddr {
290        let now: DateTime32 = now.try_into().expect("will succeed until 2038");
291
292        MetaAddr::new_local_listener_change(self.local_listener, self.local_listener_services)
293            .local_listener_into_new_meta_addr(now)
294    }
295
296    /// Get the local listener [`SocketAddr`].
297    pub fn local_listener_socket_addr(&self) -> SocketAddr {
298        self.local_listener
299    }
300
301    /// Get the active addresses in `self` in random order with sanitized timestamps,
302    /// including our local listener address.
303    ///
304    /// Limited to the number of peer addresses Zebra should give out per `GetAddr` request.
305    pub fn fresh_get_addr_response(&self) -> Vec<MetaAddr> {
306        let now = Utc::now();
307        let mut peers = self.sanitized(now);
308        let address_limit = peers.len().div_ceil(ADDR_RESPONSE_LIMIT_DENOMINATOR);
309        peers.truncate(MAX_ADDRS_IN_MESSAGE.min(address_limit));
310
311        peers
312    }
313
314    /// Get the active addresses in `self` in random order with sanitized timestamps,
315    /// including our local listener address.
316    pub(crate) fn sanitized(&self, now: chrono::DateTime<Utc>) -> Vec<MetaAddr> {
317        use rand::seq::SliceRandom;
318        let _guard = self.span.enter();
319
320        let mut peers = self.by_addr.clone();
321
322        // Unconditionally add our local listener address to the advertised peers,
323        // to replace any self-connection failures. The address book and change
324        // constructors make sure that the SocketAddr is canonical.
325        let local_listener = self.local_listener_meta_addr(now);
326        peers.insert(local_listener.addr, local_listener);
327
328        // Then sanitize and shuffle
329        let mut peers: Vec<MetaAddr> = peers
330            .descending_values()
331            .filter_map(|meta_addr| meta_addr.sanitize(&self.network))
332            // # Security
333            //
334            // Remove peers that:
335            //   - last responded more than three hours ago, or
336            //   - haven't responded yet but were reported last seen more than three hours ago
337            //
338            // This prevents Zebra from gossiping nodes that are likely unreachable. Gossiping such
339            // nodes impacts the network health, because connection attempts end up being wasted on
340            // peers that are less likely to respond.
341            .filter(|addr| addr.is_active_for_gossip(now))
342            .collect();
343
344        peers.shuffle(&mut rand::thread_rng());
345
346        peers
347    }
348
349    /// Get the active addresses in `self`, in preferred caching order,
350    /// excluding our local listener address.
351    pub fn cacheable(&self, now: chrono::DateTime<Utc>) -> Vec<MetaAddr> {
352        let _guard = self.span.enter();
353
354        let peers = self.by_addr.clone();
355
356        // Get peers in preferred order, then keep the recently active ones
357        peers
358            .descending_values()
359            // # Security
360            //
361            // Remove peers that:
362            //   - last responded more than three hours ago, or
363            //   - haven't responded yet but were reported last seen more than three hours ago
364            //
365            // This prevents Zebra from caching nodes that are likely unreachable,
366            // which improves startup time and reliability.
367            .filter(|addr| addr.is_active_for_gossip(now))
368            .cloned()
369            .collect()
370    }
371
372    /// Look up `addr` in the address book, and return its [`MetaAddr`].
373    ///
374    /// Converts `addr` to a canonical address before looking it up.
375    pub fn get(&mut self, addr: PeerSocketAddr) -> Option<MetaAddr> {
376        let addr = canonical_peer_addr(*addr);
377
378        // Unfortunately, `OrderedMap` doesn't implement `get`.
379        let meta_addr = self.by_addr.remove(&addr);
380
381        if let Some(meta_addr) = meta_addr {
382            self.by_addr.insert(addr, meta_addr);
383        }
384
385        meta_addr
386    }
387
388    /// Returns true if `updated` needs to be applied to the recent outbound peer connection IP cache.
389    ///
390    /// Checks if there are no existing entries in the address book with this IP,
391    /// or if `updated` has a more recent `last_response` requiring the outbound connector to wait
392    /// longer before initiating handshakes with peers at this IP.
393    ///
394    /// This code only needs to check a single cache entry, rather than the entire address book,
395    /// because other code maintains these invariants:
396    /// - `last_response` times for an entry can only increase.
397    /// - this is the only field checked by `has_connection_recently_responded()`
398    ///
399    /// See [`AddressBook::is_ready_for_connection_attempt_with_ip`] for more details.
400    fn should_update_most_recent_by_ip(&self, updated: MetaAddr) -> bool {
401        let Some(most_recent_by_ip) = self.most_recent_by_ip.as_ref() else {
402            return false;
403        };
404
405        if let Some(previous) = most_recent_by_ip.get(&updated.addr.ip()) {
406            updated.last_connection_state == PeerAddrState::Responded
407                && updated.last_response() > previous.last_response()
408        } else {
409            updated.last_connection_state == PeerAddrState::Responded
410        }
411    }
412
413    /// Returns true if `addr` is the latest entry for its IP, which is stored in `most_recent_by_ip`.
414    /// The entry is checked for an exact match to the IP and port of `addr`.
415    fn should_remove_most_recent_by_ip(&self, addr: PeerSocketAddr) -> bool {
416        let Some(most_recent_by_ip) = self.most_recent_by_ip.as_ref() else {
417            return false;
418        };
419
420        if let Some(previous) = most_recent_by_ip.get(&addr.ip()) {
421            previous.addr == addr
422        } else {
423            false
424        }
425    }
426
427    /// Apply `change` to the address book, returning the updated `MetaAddr`,
428    /// if the change was valid.
429    ///
430    /// # Correctness
431    ///
432    /// All changes should go through `update`, so that the address book
433    /// only contains valid outbound addresses.
434    ///
435    /// Change addresses must be canonical `PeerSocketAddr`s. This makes sure that
436    /// each address book entry has a unique IP address.
437    ///
438    /// # Security
439    ///
440    /// This function must apply every attempted, responded, and failed change
441    /// to the address book. This prevents rapid reconnections to the same peer.
442    ///
443    /// As an exception, this function can ignore all changes for specific
444    /// [`PeerSocketAddr`]s. Ignored addresses will never be used to connect to
445    /// peers.
446    #[allow(clippy::unwrap_in_result)]
447    pub fn update(&mut self, change: MetaAddrChange) -> Option<MetaAddr> {
448        let addr_label = change.addr().addr_label(self.expose_peer_addresses);
449
450        if self.bans_by_ip.contains_key(&change.addr().ip()) {
451            tracing::warn!(
452                peer = %addr_label,
453                ?change,
454                "attempted to add a banned peer addr to address book"
455            );
456            return None;
457        }
458
459        let previous = self.get(change.addr());
460
461        let _guard = self.span.enter();
462
463        let instant_now = Instant::now();
464        let chrono_now = Utc::now();
465
466        let updated = change.apply_to_meta_addr(previous, instant_now, chrono_now);
467
468        trace!(
469            peer = %addr_label,
470            ?change,
471            ?updated,
472            ?previous,
473            total_peers = self.by_addr.len(),
474            recent_peers = self.recently_live_peers(chrono_now).len(),
475            "calculated updated address book entry",
476        );
477
478        if let Some(updated) = updated {
479            if updated.misbehavior() >= constants::MAX_PEER_MISBEHAVIOR_SCORE {
480                // Ban and skip outbound connections with excessively misbehaving peers.
481                let banned_ip = updated.addr.ip();
482                let bans_by_ip = Arc::make_mut(&mut self.bans_by_ip);
483
484                bans_by_ip.insert(banned_ip, Instant::now());
485                if bans_by_ip.len() > constants::MAX_BANNED_IPS {
486                    // Remove the oldest banned IP from the address book.
487                    bans_by_ip.shift_remove_index(0);
488                }
489
490                // `most_recent_by_ip` is only populated when
491                // `max_connections_per_ip == 1`. The ban path runs for any
492                // configured value, so we must guard the optional cache rather
493                // than unwrap it.
494                if let Some(most_recent_by_ip) = self.most_recent_by_ip.as_mut() {
495                    most_recent_by_ip.remove(&banned_ip);
496                }
497
498                let banned_addrs: Vec<_> = self
499                    .by_addr
500                    .descending_keys()
501                    .skip_while(|addr| addr.ip() != banned_ip)
502                    .take_while(|addr| addr.ip() == banned_ip)
503                    .cloned()
504                    .collect();
505
506                for addr in banned_addrs {
507                    self.by_addr.remove(&addr);
508                }
509
510                warn!(
511                    peer = %addr_label,
512                    ?updated,
513                    total_peers = self.by_addr.len(),
514                    recent_peers = self.recently_live_peers(chrono_now).len(),
515                    "banned ip and removed banned peer addresses from address book",
516                );
517
518                return None;
519            }
520
521            // Ignore invalid outbound addresses.
522            // (Inbound connections can be monitored via Zebra's metrics.)
523            if !updated.address_is_valid_for_outbound(&self.network) {
524                return None;
525            }
526
527            // Ignore invalid outbound services and other info,
528            // but only if the peer has never been attempted.
529            //
530            // Otherwise, if we got the info directly from the peer,
531            // store it in the address book, so we know not to reconnect.
532            if !updated.last_known_info_is_valid_for_outbound(&self.network)
533                && updated.last_connection_state.is_never_attempted()
534            {
535                return None;
536            }
537
538            self.by_addr.insert(updated.addr, updated);
539
540            // Add the address to `most_recent_by_ip` if it sent the most recent
541            // response Zebra has received from this IP.
542            if self.should_update_most_recent_by_ip(updated) {
543                self.most_recent_by_ip
544                    .as_mut()
545                    .expect("should be some when should_update_most_recent_by_ip is true")
546                    .insert(updated.addr.ip(), updated);
547            }
548
549            debug!(
550                peer = %addr_label,
551                ?change,
552                ?updated,
553                ?previous,
554                total_peers = self.by_addr.len(),
555                recent_peers = self.recently_live_peers(chrono_now).len(),
556                "updated address book entry",
557            );
558
559            // Security: Limit the number of peers in the address book.
560            //
561            // We only delete outdated peers when we have too many peers.
562            // If we deleted them as soon as they became too old,
563            // then other peers could re-insert them into the address book.
564            // And we would start connecting to those outdated peers again,
565            // ignoring the age limit in [`MetaAddr::is_probably_reachable`].
566            while self.by_addr.len() > self.addr_limit {
567                let surplus_peer = self
568                    .peers()
569                    .next_back()
570                    .expect("just checked there is at least one peer");
571
572                self.by_addr.remove(&surplus_peer.addr);
573
574                // Check if this surplus peer's addr matches that in `most_recent_by_ip`
575                // for this the surplus peer's ip to remove it there as well.
576                if self.should_remove_most_recent_by_ip(surplus_peer.addr) {
577                    self.most_recent_by_ip
578                        .as_mut()
579                        .expect("should be some when should_remove_most_recent_by_ip is true")
580                        .remove(&surplus_peer.addr.ip());
581                }
582
583                debug!(
584                    surplus = ?surplus_peer,
585                    ?updated,
586                    total_peers = self.by_addr.len(),
587                    recent_peers = self.recently_live_peers(chrono_now).len(),
588                    "removed surplus address book entry",
589                );
590            }
591
592            assert!(self.len() <= self.addr_limit);
593
594            std::mem::drop(_guard);
595            self.update_metrics(instant_now, chrono_now);
596        }
597
598        updated
599    }
600
601    /// Removes the entry with `addr`, returning it if it exists
602    ///
603    /// # Note
604    ///
605    /// All address removals should go through `take`, so that the address
606    /// book metrics are accurate.
607    #[allow(dead_code)]
608    fn take(&mut self, removed_addr: PeerSocketAddr) -> Option<MetaAddr> {
609        let _guard = self.span.enter();
610
611        let instant_now = Instant::now();
612        let chrono_now = Utc::now();
613
614        trace!(
615            ?removed_addr,
616            total_peers = self.by_addr.len(),
617            recent_peers = self.recently_live_peers(chrono_now).len(),
618        );
619
620        if let Some(entry) = self.by_addr.remove(&removed_addr) {
621            // Check if this surplus peer's addr matches that in `most_recent_by_ip`
622            // for this the surplus peer's ip to remove it there as well.
623            if self.should_remove_most_recent_by_ip(entry.addr) {
624                if let Some(most_recent_by_ip) = self.most_recent_by_ip.as_mut() {
625                    most_recent_by_ip.remove(&entry.addr.ip());
626                }
627            }
628
629            std::mem::drop(_guard);
630            self.update_metrics(instant_now, chrono_now);
631            Some(entry)
632        } else {
633            None
634        }
635    }
636
637    /// Returns true if the given [`PeerSocketAddr`] is pending a reconnection
638    /// attempt.
639    pub fn pending_reconnection_addr(&mut self, addr: PeerSocketAddr) -> bool {
640        let meta_addr = self.get(addr);
641
642        let _guard = self.span.enter();
643        match meta_addr {
644            None => false,
645            Some(peer) => peer.last_connection_state == PeerAddrState::AttemptPending,
646        }
647    }
648
649    /// Return an iterator over all peers.
650    ///
651    /// Returns peers in reconnection attempt order, including recently connected peers.
652    pub fn peers(&'_ self) -> impl DoubleEndedIterator<Item = MetaAddr> + '_ {
653        let _guard = self.span.enter();
654        self.by_addr.descending_values().cloned()
655    }
656
657    /// Is this IP ready for a new outbound connection attempt?
658    /// Checks if the outbound connection with the most recent response at this IP has recently responded.
659    ///
660    /// Note: last_response times may remain live for a long time if the local clock is changed to an earlier time.
661    fn is_ready_for_connection_attempt_with_ip(
662        &self,
663        ip: &IpAddr,
664        chrono_now: chrono::DateTime<Utc>,
665    ) -> bool {
666        let Some(most_recent_by_ip) = self.most_recent_by_ip.as_ref() else {
667            // if we're not checking IPs, any connection is allowed
668            return true;
669        };
670        let Some(same_ip_peer) = most_recent_by_ip.get(ip) else {
671            // If there's no entry for this IP, any connection is allowed
672            return true;
673        };
674        !same_ip_peer.has_connection_recently_responded(chrono_now)
675    }
676
677    /// Return an iterator over peers that are due for a reconnection attempt,
678    /// in reconnection attempt order.
679    pub fn reconnection_peers(
680        &'_ self,
681        instant_now: Instant,
682        chrono_now: chrono::DateTime<Utc>,
683    ) -> impl DoubleEndedIterator<Item = MetaAddr> + '_ {
684        let _guard = self.span.enter();
685
686        // Skip live peers, and peers pending a reconnect attempt.
687        // The peers are already stored in sorted order.
688        self.by_addr
689            .descending_values()
690            .filter(move |peer| {
691                peer.is_ready_for_connection_attempt(instant_now, chrono_now, &self.network)
692                    && self.is_ready_for_connection_attempt_with_ip(&peer.addr.ip(), chrono_now)
693            })
694            .cloned()
695    }
696
697    /// Return an iterator over all the peers in `state`,
698    /// in reconnection attempt order, including recently connected peers.
699    pub fn state_peers(
700        &'_ self,
701        state: PeerAddrState,
702    ) -> impl DoubleEndedIterator<Item = MetaAddr> + '_ {
703        let _guard = self.span.enter();
704
705        self.by_addr
706            .descending_values()
707            .filter(move |peer| peer.last_connection_state == state)
708            .cloned()
709    }
710
711    /// Return an iterator over peers that might be connected,
712    /// in reconnection attempt order.
713    pub fn maybe_connected_peers(
714        &'_ self,
715        instant_now: Instant,
716        chrono_now: chrono::DateTime<Utc>,
717    ) -> impl DoubleEndedIterator<Item = MetaAddr> + '_ {
718        let _guard = self.span.enter();
719
720        self.by_addr
721            .descending_values()
722            .filter(move |peer| {
723                !peer.is_ready_for_connection_attempt(instant_now, chrono_now, &self.network)
724            })
725            .cloned()
726    }
727
728    /// Returns banned IP addresses.
729    pub fn bans(&self) -> Arc<IndexMap<IpAddr, Instant>> {
730        self.bans_by_ip.clone()
731    }
732
733    /// Returns the number of entries in this address book.
734    pub fn len(&self) -> usize {
735        self.by_addr.len()
736    }
737
738    /// Returns metrics for the addresses in this address book.
739    /// Only for use in tests.
740    ///
741    /// # Correctness
742    ///
743    /// Use [`AddressBook::address_metrics_watcher`] in production code,
744    /// to avoid deadlocks.
745    #[cfg(test)]
746    pub fn address_metrics(&self, now: chrono::DateTime<Utc>) -> AddressMetrics {
747        self.address_metrics_internal(now)
748    }
749
750    /// Returns metrics for the addresses in this address book.
751    ///
752    /// # Correctness
753    ///
754    /// External callers should use [`AddressBook::address_metrics_watcher`]
755    /// in production code, to avoid deadlocks.
756    /// (Using the watch channel receiver does not lock the address book mutex.)
757    fn address_metrics_internal(&self, now: chrono::DateTime<Utc>) -> AddressMetrics {
758        let responded = self.state_peers(PeerAddrState::Responded).count();
759        let never_attempted_gossiped = self
760            .state_peers(PeerAddrState::NeverAttemptedGossiped)
761            .count();
762        let failed = self.state_peers(PeerAddrState::Failed).count();
763        let attempt_pending = self.state_peers(PeerAddrState::AttemptPending).count();
764
765        let recently_live = self.recently_live_peers(now).len();
766        let recently_stopped_responding = responded
767            .checked_sub(recently_live)
768            .expect("all recently live peers must have responded");
769
770        let num_addresses = self.len();
771
772        AddressMetrics {
773            responded,
774            never_attempted_gossiped,
775            failed,
776            attempt_pending,
777            recently_live,
778            recently_stopped_responding,
779            num_addresses,
780            address_limit: self.addr_limit,
781        }
782    }
783
784    /// Update the metrics for this address book.
785    fn update_metrics(&mut self, instant_now: Instant, chrono_now: chrono::DateTime<Utc>) {
786        let _guard = self.span.enter();
787
788        let m = self.address_metrics_internal(chrono_now);
789
790        // Ignore errors: we don't care if any receivers are listening.
791        let _ = self.address_metrics_tx.send(m);
792
793        // TODO: rename to address_book.[state_name]
794        metrics::gauge!("candidate_set.responded").set(m.responded as f64);
795        metrics::gauge!("candidate_set.gossiped").set(m.never_attempted_gossiped as f64);
796        metrics::gauge!("candidate_set.failed").set(m.failed as f64);
797        metrics::gauge!("candidate_set.pending").set(m.attempt_pending as f64);
798
799        // TODO: rename to address_book.responded.recently_live
800        metrics::gauge!("candidate_set.recently_live").set(m.recently_live as f64);
801        // TODO: rename to address_book.responded.stopped_responding
802        metrics::gauge!("candidate_set.disconnected").set(m.recently_stopped_responding as f64);
803
804        std::mem::drop(_guard);
805        self.log_metrics(&m, instant_now);
806    }
807
808    /// Log metrics for this address book
809    fn log_metrics(&mut self, m: &AddressMetrics, now: Instant) {
810        let _guard = self.span.enter();
811
812        trace!(
813            address_metrics = ?m,
814        );
815
816        if m.responded > 0 {
817            return;
818        }
819
820        // These logs are designed to be human-readable in a terminal, at the
821        // default Zebra log level. If you need to know address states for
822        // every request, use the trace-level logs, or the metrics exporter.
823        if let Some(last_address_log) = self.last_address_log {
824            // Avoid duplicate address logs
825            if now.saturating_duration_since(last_address_log).as_secs() < 60 {
826                return;
827            }
828        } else {
829            // Suppress initial logs until the peer set has started up.
830            // There can be multiple address changes before the first peer has
831            // responded.
832            self.last_address_log = Some(now);
833            return;
834        }
835
836        self.last_address_log = Some(now);
837        // if all peers have failed
838        if m.responded + m.attempt_pending + m.never_attempted_gossiped == 0 {
839            warn!(
840                address_metrics = ?m,
841                "all peer addresses have failed. Hint: check your network connection"
842            );
843        } else {
844            info!(
845                address_metrics = ?m,
846                "no active peer connections: trying gossiped addresses"
847            );
848        }
849    }
850}
851
852impl AddressBookPeers for AddressBook {
853    fn recently_live_peers(&self, now: chrono::DateTime<Utc>) -> Vec<MetaAddr> {
854        let _guard = self.span.enter();
855
856        self.by_addr
857            .descending_values()
858            .filter(|peer| peer.was_recently_live(now))
859            .cloned()
860            .collect()
861    }
862
863    fn add_peer(&mut self, peer: PeerSocketAddr) -> bool {
864        if self.get(peer).is_some() {
865            // Peer already exists in the address book, so we don't need to add it again.
866            return false;
867        }
868        self.update(MetaAddr::new_initial_peer(peer)).is_some()
869    }
870}
871
872impl AddressBookPeers for Arc<Mutex<AddressBook>> {
873    fn recently_live_peers(&self, now: chrono::DateTime<Utc>) -> Vec<MetaAddr> {
874        self.lock()
875            .expect("panic in a previous thread that was holding the mutex")
876            .recently_live_peers(now)
877    }
878
879    fn add_peer(&mut self, peer: PeerSocketAddr) -> bool {
880        self.lock()
881            .expect("panic in a previous thread that was holding the mutex")
882            .add_peer(peer)
883    }
884}
885
886impl Extend<MetaAddrChange> for AddressBook {
887    fn extend<T>(&mut self, iter: T)
888    where
889        T: IntoIterator<Item = MetaAddrChange>,
890    {
891        for change in iter.into_iter() {
892            self.update(change);
893        }
894    }
895}
896
897impl Clone for AddressBook {
898    /// Clone the addresses, address limit, local listener address, and span.
899    ///
900    /// Cloned address books have a separate metrics struct watch channel, and an empty last address log.
901    ///
902    /// All address books update the same prometheus metrics.
903    fn clone(&self) -> AddressBook {
904        // The existing metrics might be outdated, but we avoid calling `update_metrics`,
905        // so we don't overwrite the prometheus metrics from the main address book.
906        let (address_metrics_tx, _address_metrics_rx) =
907            watch::channel(*self.address_metrics_tx.borrow());
908
909        AddressBook {
910            by_addr: self.by_addr.clone(),
911            local_listener: self.local_listener,
912            local_listener_services: self.local_listener_services,
913            network: self.network.clone(),
914            addr_limit: self.addr_limit,
915            span: self.span.clone(),
916            expose_peer_addresses: self.expose_peer_addresses,
917            address_metrics_tx,
918            last_address_log: None,
919            most_recent_by_ip: self.most_recent_by_ip.clone(),
920            bans_by_ip: self.bans_by_ip.clone(),
921        }
922    }
923}