Skip to main content

zebra_network/
meta_addr.rs

1//! An address-with-metadata type used in Bitcoin networking.
2
3use std::{
4    cmp::{max, Ordering},
5    time::{Duration, Instant},
6};
7
8use chrono::Utc;
9
10use zebra_chain::{parameters::Network, serialization::DateTime32};
11
12use crate::{
13    constants,
14    peer::{address_is_valid_for_outbound_connections, PeerPreference},
15    protocol::{
16        external::{canonical_peer_addr, types::Version},
17        types::PeerServices,
18    },
19};
20
21use MetaAddrChange::*;
22use PeerAddrState::*;
23
24pub mod peer_addr;
25
26pub use peer_addr::PeerSocketAddr;
27
28#[cfg(any(test, feature = "proptest-impl"))]
29use proptest_derive::Arbitrary;
30
31#[cfg(any(test, feature = "proptest-impl"))]
32use crate::protocol::external::arbitrary::canonical_peer_addr_strategy;
33
34#[cfg(any(test, feature = "proptest-impl"))]
35pub(crate) mod arbitrary;
36
37#[cfg(test)]
38pub(crate) mod tests;
39
40/// Peer connection state, based on our interactions with the peer.
41///
42/// Zebra also tracks how recently a peer has sent us messages, and derives peer
43/// liveness based on the current time. This derived state is tracked using
44/// [`maybe_connected_peers`][mcp] and
45/// [`reconnection_peers`][rp].
46///
47/// [mcp]: crate::AddressBook::maybe_connected_peers
48/// [rp]: crate::AddressBook::reconnection_peers
49#[derive(Copy, Clone, Debug, Eq, PartialEq)]
50#[cfg_attr(any(test, feature = "proptest-impl"), derive(Arbitrary))]
51pub enum PeerAddrState {
52    /// The peer has sent us a valid message.
53    ///
54    /// Peers remain in this state, even if they stop responding to requests.
55    /// (Peer liveness is derived from the `last_seen` timestamp, and the current
56    /// time.)
57    Responded,
58
59    /// The peer's address has just been fetched from a DNS seeder, or via peer
60    /// gossip, or as part of a `Version` message, or guessed from an inbound remote IP,
61    /// but we haven't attempted to connect to it yet.
62    NeverAttemptedGossiped,
63
64    /// The peer's TCP connection failed, or the peer sent us an unexpected
65    /// Zcash protocol message, so we failed the connection.
66    Failed,
67
68    /// We just started a connection attempt to this peer.
69    AttemptPending,
70}
71
72impl std::fmt::Display for PeerAddrState {
73    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
74        match self {
75            Responded => write!(f, "connected"),
76            NeverAttemptedGossiped => write!(f, "never_connected"),
77            Failed => write!(f, "failed"),
78            AttemptPending => write!(f, "connecting"),
79        }
80    }
81}
82
83impl PeerAddrState {
84    /// Return true if this state is a "never attempted" state.
85    pub fn is_never_attempted(&self) -> bool {
86        match self {
87            NeverAttemptedGossiped => true,
88            AttemptPending | Responded | Failed => false,
89        }
90    }
91
92    /// Returns the typical connection state machine order of `self` and `other`.
93    /// Partially ordered states are sorted in connection attempt order.
94    ///
95    /// See [`MetaAddrChange::apply_to_meta_addr()`] for more details.
96    fn connection_state_order(&self, other: &Self) -> Ordering {
97        use Ordering::*;
98        match (self, other) {
99            _ if self == other => Equal,
100            // Peers start in the "never attempted" state,
101            // then typically progress towards a "responded" or "failed" state.
102            (NeverAttemptedGossiped, _) => Less,
103            (_, NeverAttemptedGossiped) => Greater,
104            (AttemptPending, _) => Less,
105            (_, AttemptPending) => Greater,
106            (Responded, _) => Less,
107            (_, Responded) => Greater,
108            // These patterns are redundant, but Rust doesn't assume that `==` is reflexive,
109            // so the first is still required (but unreachable).
110            (Failed, _) => Less,
111            //(_, Failed) => Greater,
112        }
113    }
114}
115
116// non-test code should explicitly specify the peer address state
117#[cfg(test)]
118#[allow(clippy::derivable_impls)]
119impl Default for PeerAddrState {
120    fn default() -> Self {
121        NeverAttemptedGossiped
122    }
123}
124
125impl Ord for PeerAddrState {
126    /// `PeerAddrState`s are sorted in approximate reconnection attempt
127    /// order, ignoring liveness.
128    ///
129    /// See [`CandidateSet`] and [`MetaAddr::cmp`] for more details.
130    ///
131    /// [`CandidateSet`]: super::peer_set::CandidateSet
132    fn cmp(&self, other: &Self) -> Ordering {
133        use Ordering::*;
134        match (self, other) {
135            _ if self == other => Equal,
136            // We reconnect to `Responded` peers that have stopped sending messages,
137            // then `NeverAttempted` peers, then `Failed` peers
138            (Responded, _) => Less,
139            (_, Responded) => Greater,
140            (NeverAttemptedGossiped, _) => Less,
141            (_, NeverAttemptedGossiped) => Greater,
142            (Failed, _) => Less,
143            (_, Failed) => Greater,
144            // These patterns are redundant, but Rust doesn't assume that `==` is reflexive,
145            // so the first is still required (but unreachable).
146            (AttemptPending, _) => Less,
147            //(_, AttemptPending) => Greater,
148        }
149    }
150}
151
152impl PartialOrd for PeerAddrState {
153    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
154        Some(self.cmp(other))
155    }
156}
157
158/// An address with metadata on its advertised services and last-seen time.
159///
160/// This struct can be created from `addr` or `addrv2` messages.
161///
162/// [Bitcoin reference](https://en.bitcoin.it/wiki/Protocol_documentation#Network_address)
163#[derive(Clone, Debug)]
164#[cfg_attr(any(test, feature = "proptest-impl"), derive(Arbitrary))]
165pub struct MetaAddr {
166    /// The peer's canonical socket address.
167    #[cfg_attr(
168        any(test, feature = "proptest-impl"),
169        proptest(strategy = "canonical_peer_addr_strategy()")
170    )]
171    //
172    // TODO: make addr private, so the constructors can make sure it is a
173    // canonical SocketAddr (#2357)
174    pub(crate) addr: PeerSocketAddr,
175
176    /// The services advertised by the peer.
177    ///
178    /// The exact meaning depends on `last_connection_state`:
179    ///   - `Responded`: the services advertised by this peer, the last time we
180    ///     performed a handshake with it
181    ///   - `NeverAttempted`: the unverified services advertised by another peer,
182    ///     then gossiped by the peer that sent us this address
183    ///   - `Failed` or `AttemptPending`: unverified services via another peer,
184    ///     or services advertised in a previous handshake
185    ///
186    /// ## Security
187    ///
188    /// `services` from `NeverAttempted` peers may be invalid due to outdated
189    /// records, older peer versions, or buggy or malicious peers.
190    //
191    // TODO: make services private
192    //       split gossiped and handshake services? (#2324)
193    pub(crate) services: Option<PeerServices>,
194
195    /// The unverified "last seen time" gossiped by the remote peer that sent us
196    /// this address.
197    ///
198    /// See the [`MetaAddr::last_seen`] method for details.
199    untrusted_last_seen: Option<DateTime32>,
200
201    /// The last time we received a message from this peer.
202    ///
203    /// See the [`MetaAddr::last_seen`] method for details.
204    last_response: Option<DateTime32>,
205
206    /// The last measured round-trip time (RTT) for this peer, if available.
207    ///
208    /// This value is updated when the peer responds to a ping (Pong).
209    rtt: Option<Duration>,
210
211    /// The last time we sent a ping to this peer.
212    ///
213    /// This value is updated each time a heartbeat ping is sent,
214    /// even if we never receive a response.
215    ping_sent_at: Option<Instant>,
216
217    /// The last time we tried to open an outbound connection to this peer.
218    ///
219    /// See the [`MetaAddr::last_attempt`] method for details.
220    last_attempt: Option<Instant>,
221
222    /// The last time our outbound connection with this peer failed.
223    ///
224    /// See the [`MetaAddr::last_failure`] method for details.
225    last_failure: Option<Instant>,
226
227    /// The misbehavior score for this peer.
228    #[cfg_attr(any(test, feature = "proptest-impl"), proptest(value = 0))]
229    misbehavior_score: u32,
230
231    /// The outcome of our most recent communication attempt with this peer.
232    //
233    // TODO: move the time and services fields into PeerAddrState?
234    //       then some fields could be required in some states
235    pub(crate) last_connection_state: PeerAddrState,
236
237    /// Whether this peer address was added to the address book
238    /// when the peer made an inbound connection.
239    is_inbound: bool,
240
241    /// The user agent string reported by the peer during handshake, if available.
242    user_agent: Option<String>,
243
244    /// The protocol version negotiated with the peer during handshake, if available.
245    negotiated_version: Option<Version>,
246}
247
248/// A change to an existing `MetaAddr`.
249#[derive(Clone, Debug, Eq, PartialEq)]
250#[cfg_attr(any(test, feature = "proptest-impl"), derive(Arbitrary))]
251pub enum MetaAddrChange {
252    // TODO:
253    // - split the common `addr` field into an outer struct
254    //
255    /// Creates a `MetaAddr` for an initial peer.
256    NewInitial {
257        #[cfg_attr(
258            any(test, feature = "proptest-impl"),
259            proptest(strategy = "canonical_peer_addr_strategy()")
260        )]
261        addr: PeerSocketAddr,
262    },
263
264    /// Creates a new gossiped `MetaAddr`.
265    NewGossiped {
266        #[cfg_attr(
267            any(test, feature = "proptest-impl"),
268            proptest(strategy = "canonical_peer_addr_strategy()")
269        )]
270        addr: PeerSocketAddr,
271        untrusted_services: PeerServices,
272        untrusted_last_seen: DateTime32,
273    },
274
275    /// Creates new local listener `MetaAddr`.
276    NewLocal {
277        #[cfg_attr(
278            any(test, feature = "proptest-impl"),
279            proptest(strategy = "canonical_peer_addr_strategy()")
280        )]
281        addr: PeerSocketAddr,
282    },
283
284    /// Updates an existing `MetaAddr` when an outbound connection attempt
285    /// starts.
286    UpdateAttempt {
287        #[cfg_attr(
288            any(test, feature = "proptest-impl"),
289            proptest(strategy = "canonical_peer_addr_strategy()")
290        )]
291        addr: PeerSocketAddr,
292    },
293
294    /// Updates an existing `MetaAddr` when we've made a successful connection with a peer.
295    UpdateConnected {
296        #[cfg_attr(
297            any(test, feature = "proptest-impl"),
298            proptest(strategy = "canonical_peer_addr_strategy()")
299        )]
300        addr: PeerSocketAddr,
301        services: PeerServices,
302        is_inbound: bool,
303        user_agent: String,
304        negotiated_version: Version,
305    },
306
307    /// Updates an existing `MetaAddr` when we send a ping to a peer.
308    UpdatePingSent {
309        #[cfg_attr(
310            any(test, feature = "proptest-impl"),
311            proptest(strategy = "canonical_peer_addr_strategy()")
312        )]
313        addr: PeerSocketAddr,
314        ping_sent_at: Instant,
315    },
316
317    /// Updates an existing `MetaAddr` when a peer responds with a message.
318    UpdateResponded {
319        #[cfg_attr(
320            any(test, feature = "proptest-impl"),
321            proptest(strategy = "canonical_peer_addr_strategy()")
322        )]
323        addr: PeerSocketAddr,
324        rtt: Option<Duration>,
325    },
326
327    /// Updates an existing `MetaAddr` when a peer fails.
328    UpdateFailed {
329        #[cfg_attr(
330            any(test, feature = "proptest-impl"),
331            proptest(strategy = "canonical_peer_addr_strategy()")
332        )]
333        addr: PeerSocketAddr,
334        services: Option<PeerServices>,
335    },
336
337    /// Updates an existing `MetaAddr` when a peer misbehaves such as by advertising
338    /// semantically invalid blocks or transactions.
339    #[cfg_attr(any(test, feature = "proptest-impl"), proptest(skip))]
340    UpdateMisbehavior {
341        addr: PeerSocketAddr,
342        score_increment: u32,
343    },
344}
345
346impl MetaAddr {
347    /// Returns a [`MetaAddrChange::NewInitial`] for a peer that was excluded from
348    /// the list of the initial peers.
349    pub fn new_initial_peer(addr: PeerSocketAddr) -> MetaAddrChange {
350        NewInitial {
351            addr: canonical_peer_addr(addr),
352        }
353    }
354
355    /// Returns a new `MetaAddr`, based on the deserialized fields from a
356    /// gossiped peer [`Addr`][crate::protocol::external::Message::Addr] message.
357    pub fn new_gossiped_meta_addr(
358        addr: PeerSocketAddr,
359        untrusted_services: PeerServices,
360        untrusted_last_seen: DateTime32,
361    ) -> MetaAddr {
362        MetaAddr {
363            addr: canonical_peer_addr(addr),
364            services: Some(untrusted_services),
365            untrusted_last_seen: Some(untrusted_last_seen),
366            last_response: None,
367            rtt: None,
368            ping_sent_at: None,
369            last_attempt: None,
370            last_failure: None,
371            last_connection_state: NeverAttemptedGossiped,
372            misbehavior_score: 0,
373            is_inbound: false,
374            user_agent: None,
375            negotiated_version: None,
376        }
377    }
378
379    /// Returns a [`MetaAddrChange::NewGossiped`], based on a gossiped peer
380    /// [`MetaAddr`].
381    ///
382    /// Returns [`None`] if the gossiped peer is missing the untrusted services field.
383    #[allow(clippy::unwrap_in_result)]
384    pub fn new_gossiped_change(self) -> Option<MetaAddrChange> {
385        let untrusted_services = self.services?;
386
387        Some(NewGossiped {
388            addr: canonical_peer_addr(self.addr),
389            untrusted_services,
390            untrusted_last_seen: self
391                .untrusted_last_seen
392                .expect("unexpected missing last seen"),
393        })
394    }
395
396    /// Returns a [`MetaAddrChange::UpdateConnected`] for a peer that has just successfully
397    /// connected.
398    ///
399    /// # Security
400    ///
401    /// This address must be the remote address from an outbound connection,
402    /// and the services must be the services from that peer's handshake.
403    ///
404    /// Otherwise:
405    /// - malicious peers could interfere with other peers' [`AddressBook`](crate::AddressBook)
406    ///   state, or
407    /// - Zebra could advertise unreachable addresses to its own peers.
408    pub fn new_connected(
409        addr: PeerSocketAddr,
410        services: &PeerServices,
411        is_inbound: bool,
412        user_agent: String,
413        negotiated_version: Version,
414    ) -> MetaAddrChange {
415        UpdateConnected {
416            addr: canonical_peer_addr(*addr),
417            services: *services,
418            is_inbound,
419            user_agent,
420            negotiated_version,
421        }
422    }
423
424    /// Returns a [`MetaAddrChange::UpdatePingSent`] for a peer that we just sent a ping to.
425    pub fn new_ping_sent(addr: PeerSocketAddr, ping_sent_at: Instant) -> MetaAddrChange {
426        UpdatePingSent {
427            addr: canonical_peer_addr(*addr),
428            ping_sent_at,
429        }
430    }
431
432    /// Returns a [`MetaAddrChange::UpdateResponded`] for a peer that has just
433    /// sent us a message.
434    ///
435    /// # Security
436    ///
437    /// This address must be the remote address from an outbound connection.
438    ///
439    /// Otherwise:
440    /// - malicious peers could interfere with other peers' [`AddressBook`](crate::AddressBook)
441    ///   state, or
442    /// - Zebra could advertise unreachable addresses to its own peers.
443    pub fn new_responded(addr: PeerSocketAddr, rtt: Option<Duration>) -> MetaAddrChange {
444        UpdateResponded {
445            addr: canonical_peer_addr(*addr),
446            rtt,
447        }
448    }
449
450    /// Returns a [`MetaAddrChange::UpdateAttempt`] for a peer that we
451    /// want to make an outbound connection to.
452    pub fn new_reconnect(addr: PeerSocketAddr) -> MetaAddrChange {
453        UpdateAttempt {
454            addr: canonical_peer_addr(*addr),
455        }
456    }
457
458    /// Returns a [`MetaAddrChange::NewLocal`] for our own listener address.
459    pub fn new_local_listener_change(addr: impl Into<PeerSocketAddr>) -> MetaAddrChange {
460        NewLocal {
461            addr: canonical_peer_addr(addr),
462        }
463    }
464
465    /// Returns a [`MetaAddrChange::UpdateFailed`] for a peer that has just had an error.
466    pub fn new_errored(
467        addr: PeerSocketAddr,
468        services: impl Into<Option<PeerServices>>,
469    ) -> MetaAddrChange {
470        UpdateFailed {
471            addr: canonical_peer_addr(*addr),
472            services: services.into(),
473        }
474    }
475
476    /// Returns a [`MetaAddrChange::UpdateMisbehavior`] for a peer that has misbehaved.
477    ///
478    /// Canonicalizes the address to match the form stored by a successful handshake
479    /// (`new_connected`). On Linux dual-stack sockets, inbound IPv4 connections
480    /// arrive as IPv4-mapped IPv6 addresses (`::ffff:A.B.C.D`); without
481    /// canonicalization, `apply_to_meta_addr` panics on the addr invariant.
482    pub fn new_misbehavior(addr: PeerSocketAddr, score_increment: u32) -> MetaAddrChange {
483        UpdateMisbehavior {
484            addr: canonical_peer_addr(*addr),
485            score_increment,
486        }
487    }
488
489    /// Create a new `MetaAddr` for a peer that has just shut down.
490    pub fn new_shutdown(addr: PeerSocketAddr) -> MetaAddrChange {
491        // TODO: if the peer shut down in the Responded state, preserve that
492        // state. All other states should be treated as (timeout) errors.
493        MetaAddr::new_errored(addr, None)
494    }
495
496    /// Return the address for this `MetaAddr`.
497    pub fn addr(&self) -> PeerSocketAddr {
498        self.addr
499    }
500
501    /// Return the address preference level for this `MetaAddr`.
502    pub fn peer_preference(&self) -> Result<PeerPreference, &'static str> {
503        PeerPreference::new(self.addr, None)
504    }
505
506    /// Returns the time of the last successful interaction with this peer.
507    ///
508    /// Initially set to the unverified "last seen time" gossiped by the remote
509    /// peer that sent us this address.
510    ///
511    /// If the `last_connection_state` has ever been `Responded`, this field is
512    /// set to the last time we processed a message from this peer.
513    ///
514    /// ## Security
515    ///
516    /// `last_seen` times from peers that have never `Responded` may be
517    /// incorrect due to clock skew, or buggy or malicious peers.
518    pub fn last_seen(&self) -> Option<DateTime32> {
519        self.last_response.or(self.untrusted_last_seen)
520    }
521
522    /// Returns whether the address is from an inbound peer connection
523    pub fn is_inbound(&self) -> bool {
524        self.is_inbound
525    }
526
527    /// Returns the round-trip time (RTT) for this peer, if available.
528    pub fn rtt(&self) -> Option<Duration> {
529        self.rtt
530    }
531
532    /// Returns the time this peer was last pinged, if available.
533    pub fn ping_sent_at(&self) -> Option<Instant> {
534        self.ping_sent_at
535    }
536
537    /// Returns the unverified "last seen time" gossiped by the remote peer that
538    /// sent us this address.
539    ///
540    /// See the [`MetaAddr::last_seen`] method for details.
541    //
542    // TODO: pub(in crate::address_book) - move meta_addr into address_book
543    pub(crate) fn untrusted_last_seen(&self) -> Option<DateTime32> {
544        self.untrusted_last_seen
545    }
546
547    /// Returns the last time we received a message from this peer.
548    ///
549    /// See the [`MetaAddr::last_seen`] method for details.
550    //
551    // TODO: pub(in crate::address_book) - move meta_addr into address_book
552    #[allow(dead_code)]
553    pub(crate) fn last_response(&self) -> Option<DateTime32> {
554        self.last_response
555    }
556
557    /// Set the gossiped untrusted last seen time for this peer.
558    pub(crate) fn set_untrusted_last_seen(&mut self, untrusted_last_seen: DateTime32) {
559        self.untrusted_last_seen = Some(untrusted_last_seen);
560    }
561
562    /// Returns the time of our last outbound connection attempt with this peer.
563    ///
564    /// If the `last_connection_state` has ever been `AttemptPending`, this
565    /// field is set to the last time we started an outbound connection attempt
566    /// with this peer.
567    pub fn last_attempt(&self) -> Option<Instant> {
568        self.last_attempt
569    }
570
571    /// Returns the time of our last failed outbound connection with this peer.
572    ///
573    /// If the `last_connection_state` has ever been `Failed`, this field is set
574    /// to the last time:
575    /// - a connection attempt failed, or
576    /// - an open connection encountered a fatal protocol error.
577    pub fn last_failure(&self) -> Option<Instant> {
578        self.last_failure
579    }
580
581    /// Have we had any recently messages from this peer?
582    ///
583    /// Returns `true` if the peer is likely connected and responsive in the peer
584    /// set.
585    ///
586    /// [`constants::MIN_PEER_RECONNECTION_DELAY`] represents the time interval in which
587    /// we should receive at least one message from a peer, or close the
588    /// connection. Therefore, if the last-seen timestamp is older than
589    /// [`constants::MIN_PEER_RECONNECTION_DELAY`] ago, we know we should have
590    /// disconnected from it. Otherwise, we could potentially be connected to it.
591    pub fn has_connection_recently_responded(&self, now: chrono::DateTime<Utc>) -> bool {
592        if let Some(last_response) = self.last_response {
593            // Recent times and future times are considered live
594            last_response.saturating_elapsed(now)
595                <= constants::MIN_PEER_RECONNECTION_DELAY
596                    .try_into()
597                    .expect("unexpectedly large constant")
598        } else {
599            // If there has never been any response, it can't possibly be live
600            false
601        }
602    }
603
604    /// Have we recently attempted an outbound connection to this peer?
605    ///
606    /// Returns `true` if this peer was recently attempted, or has a connection
607    /// attempt in progress.
608    pub fn was_connection_recently_attempted(&self, now: Instant) -> bool {
609        if let Some(last_attempt) = self.last_attempt {
610            // Recent times and future times are considered live.
611            // Instants are monotonic, so `now` should always be later than `last_attempt`,
612            // except for synthetic data in tests.
613            now.saturating_duration_since(last_attempt) <= constants::MIN_PEER_RECONNECTION_DELAY
614        } else {
615            // If there has never been any attempt, it can't possibly be live
616            false
617        }
618    }
619
620    /// Have we recently had a failed connection to this peer?
621    ///
622    /// Returns `true` if this peer has recently failed.
623    pub fn has_connection_recently_failed(&self, now: Instant) -> bool {
624        if let Some(last_failure) = self.last_failure {
625            // Recent times and future times are considered live
626            now.saturating_duration_since(last_failure) <= constants::MIN_PEER_RECONNECTION_DELAY
627        } else {
628            // If there has never been any failure, it can't possibly be recent
629            false
630        }
631    }
632
633    /// Returns true if this peer has recently sent us a message.
634    pub fn was_recently_live(&self, now: chrono::DateTime<Utc>) -> bool {
635        // NeverAttempted, Failed, and AttemptPending peers should never be live
636        self.last_connection_state == PeerAddrState::Responded
637            && self.has_connection_recently_responded(now)
638    }
639
640    /// Has this peer been seen recently?
641    ///
642    /// Returns `true` if this peer has responded recently or if the peer was gossiped with a
643    /// recent reported last seen time.
644    ///
645    /// [`constants::MAX_PEER_ACTIVE_FOR_GOSSIP`] represents the maximum time since a peer was seen
646    /// to still be considered reachable.
647    pub fn is_active_for_gossip(&self, now: chrono::DateTime<Utc>) -> bool {
648        if let Some(last_seen) = self.last_seen() {
649            // Correctness: `last_seen` shouldn't ever be in the future, either because we set the
650            // time or because another peer's future time was sanitized when it was added to the
651            // address book
652            last_seen.saturating_elapsed(now) <= constants::MAX_PEER_ACTIVE_FOR_GOSSIP
653        } else {
654            // Peer has never responded and does not have a gossiped last seen time
655            false
656        }
657    }
658
659    /// Returns true if any messages were recently sent to or received from this address.
660    pub fn was_recently_updated(
661        &self,
662        instant_now: Instant,
663        chrono_now: chrono::DateTime<Utc>,
664    ) -> bool {
665        self.has_connection_recently_responded(chrono_now)
666            || self.was_connection_recently_attempted(instant_now)
667            || self.has_connection_recently_failed(instant_now)
668    }
669
670    /// Is this address ready for a new outbound connection attempt?
671    pub fn is_ready_for_connection_attempt(
672        &self,
673        instant_now: Instant,
674        chrono_now: chrono::DateTime<Utc>,
675        network: &Network,
676    ) -> bool {
677        self.last_known_info_is_valid_for_outbound(network)
678            && !self.was_recently_updated(instant_now, chrono_now)
679            && self.is_probably_reachable(chrono_now)
680    }
681
682    /// Is the [`PeerSocketAddr`] we have for this peer valid for outbound
683    /// connections?
684    ///
685    /// Since the addresses in the address book are unique, this check can be
686    /// used to permanently reject entire [`MetaAddr`]s.
687    pub fn address_is_valid_for_outbound(&self, network: &Network) -> bool {
688        address_is_valid_for_outbound_connections(self.addr, network.clone()).is_ok()
689    }
690
691    /// Is the last known information for this peer valid for outbound
692    /// connections?
693    ///
694    /// The last known info might be outdated or untrusted, so this check can
695    /// only be used to:
696    /// - reject `NeverAttempted...` [`MetaAddrChange`]s, and
697    /// - temporarily stop outbound connections to a [`MetaAddr`].
698    pub fn last_known_info_is_valid_for_outbound(&self, network: &Network) -> bool {
699        let is_node = match self.services {
700            Some(services) => services.contains(PeerServices::NODE_NETWORK),
701            None => true,
702        };
703
704        is_node && self.address_is_valid_for_outbound(network)
705    }
706
707    /// Should this peer considered reachable?
708    ///
709    /// A peer is probably reachable if:
710    /// - it has never been attempted, or
711    /// - the last connection attempt was successful, or
712    /// - the last successful connection was less than 3 days ago.
713    ///
714    /// # Security
715    ///
716    /// This is used by [`Self::is_ready_for_connection_attempt`] so that Zebra stops trying to
717    /// connect to peers that are likely unreachable.
718    ///
719    /// The `untrusted_last_seen` time is used as a fallback time if the local node has never
720    /// itself seen the peer. If the reported last seen time is a long time ago or `None`, then the local
721    /// node will attempt to connect the peer once, and if that attempt fails it won't
722    /// try to connect ever again. (The state can't be `Failed` until after the first connection attempt.)
723    pub fn is_probably_reachable(&self, now: chrono::DateTime<Utc>) -> bool {
724        self.last_connection_state != PeerAddrState::Failed || self.last_seen_is_recent(now)
725    }
726
727    /// Was this peer last seen recently?
728    ///
729    /// Returns `true` if this peer was last seen at most
730    /// [`MAX_RECENT_PEER_AGE`][constants::MAX_RECENT_PEER_AGE] ago.
731    /// Returns false if the peer is outdated, or it has no last seen time.
732    pub fn last_seen_is_recent(&self, now: chrono::DateTime<Utc>) -> bool {
733        match self.last_seen() {
734            Some(last_seen) => last_seen.saturating_elapsed(now) <= constants::MAX_RECENT_PEER_AGE,
735            None => false,
736        }
737    }
738
739    /// Returns the services advertised by the peer, if available.
740    pub fn services(&self) -> Option<PeerServices> {
741        self.services
742    }
743
744    /// Returns the last known connection state for this peer.
745    pub fn last_connection_state(&self) -> PeerAddrState {
746        self.last_connection_state
747    }
748
749    /// Returns the user agent string reported by this peer, if available.
750    pub fn user_agent(&self) -> Option<&str> {
751        self.user_agent.as_deref()
752    }
753
754    /// Returns the negotiated protocol version for this peer, if available.
755    pub fn negotiated_version(&self) -> Option<Version> {
756        self.negotiated_version
757    }
758
759    /// Returns a score of misbehavior encountered in a peer at this address.
760    pub fn misbehavior(&self) -> u32 {
761        self.misbehavior_score
762    }
763
764    /// Return a sanitized version of this `MetaAddr`, for sending to a remote peer.
765    ///
766    /// Returns `None` if this `MetaAddr` should not be sent to remote peers.
767    #[allow(clippy::unwrap_in_result)]
768    pub fn sanitize(&self, network: &Network) -> Option<MetaAddr> {
769        if !self.last_known_info_is_valid_for_outbound(network) {
770            return None;
771        }
772
773        // Avoid responding to GetAddr requests with addresses of misbehaving peers.
774        if self.misbehavior_score != 0 || self.is_inbound {
775            return None;
776        }
777
778        // Sanitize time
779        let last_seen = self.last_seen()?;
780        let remainder = last_seen
781            .timestamp()
782            .rem_euclid(crate::constants::TIMESTAMP_TRUNCATION_SECONDS);
783        let last_seen = last_seen
784            .checked_sub(remainder.into())
785            .expect("unexpected underflow: rem_euclid is strictly less than timestamp");
786
787        Some(MetaAddr {
788            addr: canonical_peer_addr(self.addr),
789            // initial peers are sanitized assuming they are `NODE_NETWORK`
790            // TODO: split untrusted and direct services
791            //       consider sanitizing untrusted services to NODE_NETWORK (#2324)
792            services: self.services.or(Some(PeerServices::NODE_NETWORK)),
793            // only put the last seen time in the untrusted field,
794            // this matches deserialization, and avoids leaking internal state
795            untrusted_last_seen: Some(last_seen),
796            last_response: None,
797            // these fields aren't sent to the remote peer, but sanitize them anyway
798            rtt: None,
799            ping_sent_at: None,
800            last_attempt: None,
801            last_failure: None,
802            last_connection_state: NeverAttemptedGossiped,
803            misbehavior_score: 0,
804            is_inbound: false,
805            user_agent: None,
806            negotiated_version: None,
807        })
808    }
809}
810
811#[cfg(test)]
812impl MetaAddr {
813    /// Forcefully change the time this peer last responded.
814    ///
815    /// This method is for testing purposes only.
816    pub(crate) fn set_last_response(&mut self, last_response: DateTime32) {
817        self.last_response = Some(last_response);
818    }
819}
820
821impl MetaAddrChange {
822    /// Return the address for this change.
823    pub fn addr(&self) -> PeerSocketAddr {
824        match self {
825            NewInitial { addr }
826            | NewGossiped { addr, .. }
827            | NewLocal { addr, .. }
828            | UpdateAttempt { addr }
829            | UpdateConnected { addr, .. }
830            | UpdatePingSent { addr, .. }
831            | UpdateResponded { addr, .. }
832            | UpdateFailed { addr, .. }
833            | UpdateMisbehavior { addr, .. } => *addr,
834        }
835    }
836
837    #[cfg(any(test, feature = "proptest-impl"))]
838    /// Set the address for this change to `new_addr`.
839    ///
840    /// This method should only be used in tests.
841    pub fn set_addr(&mut self, new_addr: PeerSocketAddr) {
842        match self {
843            NewInitial { addr }
844            | NewGossiped { addr, .. }
845            | NewLocal { addr, .. }
846            | UpdateAttempt { addr }
847            | UpdateConnected { addr, .. }
848            | UpdatePingSent { addr, .. }
849            | UpdateResponded { addr, .. }
850            | UpdateFailed { addr, .. }
851            | UpdateMisbehavior { addr, .. } => *addr = new_addr,
852        }
853    }
854
855    /// Return the untrusted services for this change, if available.
856    pub fn untrusted_services(&self) -> Option<PeerServices> {
857        match self {
858            NewInitial { .. } => None,
859            // TODO: split untrusted and direct services (#2324)
860            NewGossiped {
861                untrusted_services, ..
862            } => Some(*untrusted_services),
863            // TODO: create a "services implemented by Zebra" constant (#2324)
864            NewLocal { .. } => Some(PeerServices::NODE_NETWORK),
865            UpdateAttempt { .. } => None,
866            UpdateConnected { services, .. } => Some(*services),
867            UpdatePingSent { .. } => None,
868            UpdateResponded { .. } => None,
869            UpdateFailed { services, .. } => *services,
870            UpdateMisbehavior { .. } => None,
871        }
872    }
873
874    /// Return the untrusted last seen time for this change, if available.
875    pub fn untrusted_last_seen(&self, now: DateTime32) -> Option<DateTime32> {
876        match self {
877            NewInitial { .. } => None,
878            NewGossiped {
879                untrusted_last_seen,
880                ..
881            } => Some(*untrusted_last_seen),
882            // We know that our local listener is available
883            NewLocal { .. } => Some(now),
884            UpdateAttempt { .. }
885            | UpdateConnected { .. }
886            | UpdatePingSent { .. }
887            | UpdateResponded { .. }
888            | UpdateFailed { .. }
889            | UpdateMisbehavior { .. } => None,
890        }
891    }
892
893    // # Concurrency
894    //
895    // We assign a time to each change when it is applied to the address book by either the
896    // address book updater or candidate set tasks. This is the time that the change was received
897    // from the updater channel, rather than the time that the message was read from the peer
898    // connection.
899    //
900    // Since the connection tasks run concurrently in an unspecified order, and the address book
901    // updater runs in a separate thread, these times are almost always very similar. If Zebra's
902    // address book is under load, we should use lower rate-limits for new inbound or outbound
903    // connections, disconnections, peer gossip crawls, or peer `UpdateResponded` updates.
904    //
905    // TODO:
906    // - move the time API calls from `impl MetaAddrChange` `last_*()` methods:
907    //   - if they impact performance, call them once in the address book updater task,
908    //     then apply them to all the waiting changes
909    //   - otherwise, move them to the `impl MetaAddrChange` `new_*()` methods,
910    //     so they are called in the connection tasks
911    //
912    /// Return the last attempt for this change, if available.
913    pub fn last_attempt(&self, now: Instant) -> Option<Instant> {
914        match self {
915            NewInitial { .. } | NewGossiped { .. } | NewLocal { .. } => None,
916            // Attempt changes are applied before we start the handshake to the
917            // peer address. So the attempt time is a lower bound for the actual
918            // handshake time.
919            UpdateAttempt { .. } => Some(now),
920            UpdateConnected { .. }
921            | UpdatePingSent { .. }
922            | UpdateResponded { .. }
923            | UpdateFailed { .. }
924            | UpdateMisbehavior { .. } => None,
925        }
926    }
927
928    /// Return the last response for this change, if available.
929    pub fn last_response(&self, now: DateTime32) -> Option<DateTime32> {
930        match self {
931            NewInitial { .. } | NewGossiped { .. } | NewLocal { .. } | UpdateAttempt { .. } => None,
932            // If there is a large delay applying this change, then:
933            // - the peer might stay in the `AttemptPending` state for longer,
934            // - we might send outdated last seen times to our peers, and
935            // - the peer will appear to be live for longer, delaying future
936            //   reconnection attempts.
937            UpdateConnected { .. } | UpdateResponded { .. } => Some(now),
938            UpdateFailed { .. } | UpdateMisbehavior { .. } => None,
939            UpdatePingSent { .. } => None,
940        }
941    }
942
943    /// Return the timestamp when a ping was last sent, if available.
944    pub fn ping_sent(&self) -> Option<Instant> {
945        match self {
946            UpdatePingSent { ping_sent_at, .. } => Some(*ping_sent_at),
947            _ => None,
948        }
949    }
950
951    /// Return the RTT for this change, if available
952    pub fn rtt(&self) -> Option<Duration> {
953        match self {
954            UpdateResponded { rtt, .. } => *rtt,
955            _ => None,
956        }
957    }
958
959    /// Returns the timestamp when a ping was last sent, if available.
960    pub fn ping_sent_at(&self) -> Option<Instant> {
961        match self {
962            UpdatePingSent { ping_sent_at, .. } => Some(*ping_sent_at),
963            _ => None,
964        }
965    }
966
967    /// Return the last failure for this change, if available.
968    pub fn last_failure(&self, now: Instant) -> Option<Instant> {
969        match self {
970            NewInitial { .. }
971            | NewGossiped { .. }
972            | NewLocal { .. }
973            | UpdateAttempt { .. }
974            | UpdateConnected { .. }
975            | UpdatePingSent { .. }
976            | UpdateResponded { .. } => None,
977            // If there is a large delay applying this change, then:
978            // - the peer might stay in the `AttemptPending` or `Responded`
979            //   states for longer, and
980            // - the peer will appear to be used for longer, delaying future
981            //   reconnection attempts.
982            UpdateFailed { .. } | UpdateMisbehavior { .. } => Some(now),
983        }
984    }
985
986    /// Return the peer connection state for this change.
987    pub fn peer_addr_state(&self) -> PeerAddrState {
988        match self {
989            NewInitial { .. } => NeverAttemptedGossiped,
990            NewGossiped { .. } => NeverAttemptedGossiped,
991            // local listeners get sanitized, so the state doesn't matter here
992            NewLocal { .. } => NeverAttemptedGossiped,
993            UpdateAttempt { .. } => AttemptPending,
994            UpdateConnected { .. }
995            // Sending a ping is an interaction with a connected peer, but does not indicate new liveness.
996            // Peers stay in Responded once connected, so we keep them in that state for UpdatePingSent.
997            | UpdatePingSent { .. }
998            | UpdateResponded { .. }
999            | UpdateMisbehavior { .. } => Responded,
1000            UpdateFailed { .. } => Failed,
1001        }
1002    }
1003
1004    /// Returns the corresponding `MetaAddr` for this change.
1005    pub fn into_new_meta_addr(self, instant_now: Instant, local_now: DateTime32) -> MetaAddr {
1006        let user_agent = self.user_agent();
1007        let negotiated_version = self.negotiated_version();
1008        MetaAddr {
1009            addr: self.addr(),
1010            services: self.untrusted_services(),
1011            untrusted_last_seen: self.untrusted_last_seen(local_now),
1012            last_response: self.last_response(local_now),
1013            rtt: self.rtt(),
1014            ping_sent_at: self.ping_sent_at(),
1015            last_attempt: self.last_attempt(instant_now),
1016            last_failure: self.last_failure(instant_now),
1017            last_connection_state: self.peer_addr_state(),
1018            misbehavior_score: self.misbehavior_score(),
1019            is_inbound: self.is_inbound(),
1020            user_agent,
1021            negotiated_version,
1022        }
1023    }
1024
1025    /// Returns the misbehavior score increment for the current change.
1026    pub fn misbehavior_score(&self) -> u32 {
1027        match self {
1028            MetaAddrChange::UpdateMisbehavior {
1029                score_increment, ..
1030            } => *score_increment,
1031            _ => 0,
1032        }
1033    }
1034
1035    /// Returns whether this change was created for a new inbound connection.
1036    pub fn is_inbound(&self) -> bool {
1037        if let MetaAddrChange::UpdateConnected { is_inbound, .. } = self {
1038            *is_inbound
1039        } else {
1040            false
1041        }
1042    }
1043
1044    /// Returns the user agent from this change, if available.
1045    pub fn user_agent(&self) -> Option<String> {
1046        if let MetaAddrChange::UpdateConnected { user_agent, .. } = self {
1047            Some(user_agent.clone())
1048        } else {
1049            None
1050        }
1051    }
1052
1053    /// Returns the negotiated protocol version from this change, if available.
1054    pub fn negotiated_version(&self) -> Option<Version> {
1055        if let MetaAddrChange::UpdateConnected {
1056            negotiated_version, ..
1057        } = self
1058        {
1059            Some(*negotiated_version)
1060        } else {
1061            None
1062        }
1063    }
1064
1065    /// Returns the corresponding [`MetaAddr`] for a local listener change.
1066    ///
1067    /// This method exists so we don't have to provide an unused [`Instant`] to get a local
1068    /// listener `MetaAddr`.
1069    ///
1070    /// # Panics
1071    ///
1072    /// If this change is not a [`MetaAddrChange::NewLocal`].
1073    pub fn local_listener_into_new_meta_addr(self, local_now: DateTime32) -> MetaAddr {
1074        assert!(matches!(self, MetaAddrChange::NewLocal { .. }));
1075
1076        MetaAddr {
1077            addr: self.addr(),
1078            services: self.untrusted_services(),
1079            untrusted_last_seen: self.untrusted_last_seen(local_now),
1080            last_response: self.last_response(local_now),
1081            rtt: None,
1082            ping_sent_at: None,
1083            last_attempt: None,
1084            last_failure: None,
1085            last_connection_state: self.peer_addr_state(),
1086            misbehavior_score: self.misbehavior_score(),
1087            is_inbound: self.is_inbound(),
1088            user_agent: None,
1089            negotiated_version: None,
1090        }
1091    }
1092
1093    /// Apply this change to a previous `MetaAddr` from the address book,
1094    /// producing a new or updated `MetaAddr`.
1095    ///
1096    /// If the change isn't valid for the `previous` address, returns `None`.
1097    #[allow(clippy::unwrap_in_result)]
1098    pub fn apply_to_meta_addr(
1099        &self,
1100        previous: impl Into<Option<MetaAddr>>,
1101        instant_now: Instant,
1102        chrono_now: chrono::DateTime<Utc>,
1103    ) -> Option<MetaAddr> {
1104        let local_now: DateTime32 = chrono_now.try_into().expect("will succeed until 2038");
1105
1106        let Some(previous) = previous.into() else {
1107            // no previous: create a new entry
1108            return Some(self.clone().into_new_meta_addr(instant_now, local_now));
1109        };
1110
1111        assert_eq!(previous.addr, self.addr(), "unexpected addr mismatch");
1112
1113        let instant_previous = max(previous.last_attempt, previous.last_failure);
1114        let local_previous = previous.last_response;
1115
1116        // Is this change potentially concurrent with the previous change?
1117        //
1118        // Since we're using saturating arithmetic, one of each pair of less than comparisons
1119        // will always be true, because subtraction saturates to zero.
1120        let change_is_concurrent = instant_previous
1121            .map(|instant_previous| {
1122                instant_previous.saturating_duration_since(instant_now)
1123                    < constants::CONCURRENT_ADDRESS_CHANGE_PERIOD
1124                    && instant_now.saturating_duration_since(instant_previous)
1125                        < constants::CONCURRENT_ADDRESS_CHANGE_PERIOD
1126            })
1127            .unwrap_or_default()
1128            || local_previous
1129                .map(|local_previous| {
1130                    local_previous.saturating_duration_since(local_now).to_std()
1131                        < constants::CONCURRENT_ADDRESS_CHANGE_PERIOD
1132                        && local_now.saturating_duration_since(local_previous).to_std()
1133                            < constants::CONCURRENT_ADDRESS_CHANGE_PERIOD
1134                })
1135                .unwrap_or_default();
1136        let change_is_out_of_order = instant_previous
1137            .map(|instant_previous| instant_previous > instant_now)
1138            .unwrap_or_default()
1139            || local_previous
1140                .map(|local_previous| local_previous > local_now)
1141                .unwrap_or_default();
1142
1143        // Is this change typically from a connection state that has more progress?
1144        let connection_has_more_progress = self
1145            .peer_addr_state()
1146            .connection_state_order(&previous.last_connection_state)
1147            == Ordering::Greater;
1148
1149        let previous_has_been_attempted = !previous.last_connection_state.is_never_attempted();
1150        let change_to_never_attempted = self.peer_addr_state().is_never_attempted();
1151        let is_misbehavior_update = self.misbehavior_score() != 0;
1152
1153        // Invalid changes
1154
1155        if change_to_never_attempted && previous_has_been_attempted && !is_misbehavior_update {
1156            // Existing entry has been attempted, change is NeverAttempted
1157            // - ignore the change
1158            //
1159            // # Security
1160            //
1161            // Ignore NeverAttempted changes once we have made an attempt,
1162            // so malicious peers can't keep changing our peer connection order.
1163            return None;
1164        }
1165
1166        if change_is_out_of_order && !change_is_concurrent && !is_misbehavior_update {
1167            // Change is significantly out of order: ignore it.
1168            //
1169            // # Security
1170            //
1171            // Ignore changes that arrive out of order, if they are far enough apart.
1172            // This enforces the peer connection retry interval.
1173            return None;
1174        }
1175
1176        if change_is_concurrent && !connection_has_more_progress && !is_misbehavior_update {
1177            // Change is close together in time, and it would revert the connection to an earlier
1178            // state.
1179            //
1180            // # Security
1181            //
1182            // If the changes might have been concurrent, ignore connection states with less
1183            // progress.
1184            //
1185            // ## Sources of Concurrency
1186            //
1187            // If two changes happen close together, the async scheduler can run their change
1188            // send and apply code in any order. This includes the code that records the time of
1189            // the change. So even if a failure happens after a response message, the failure time
1190            // can be recorded before the response time code is run.
1191            //
1192            // Some machines and OSes have limited time resolution, so we can't guarantee that
1193            // two messages on the same connection will always have different times. There are
1194            // also known bugs impacting monotonic times which make them go backwards or stay
1195            // equal. For wall clock times, clock skew is an expected event, particularly with
1196            // network time server updates.
1197            //
1198            // Also, the application can fail a connection independently and simultaneously
1199            // (or slightly before) a positive update from that peer connection. We want the
1200            // application change to take priority in the address book, because the connection
1201            // state machine also prioritises failures over any other peer messages.
1202            //
1203            // ## Resolution
1204            //
1205            // In these cases, we want to apply the failure, then ignore any nearby changes that
1206            // reset the address book entry to a more appealing state. This prevents peers from
1207            // sending updates right before failing a connection, in order to make themselves more
1208            // likely to get a reconnection.
1209            //
1210            // The connection state machine order is used so that state transitions which are
1211            // typically close together are preserved. These transitions are:
1212            // - NeverAttempted*->AttemptPending->(Responded|Failed)
1213            // - Responded->Failed
1214            //
1215            // State transitions like (Responded|Failed)->AttemptPending only happen after the
1216            // reconnection timeout, so they will never be considered concurrent.
1217            return None;
1218        }
1219
1220        // Valid changes
1221
1222        if change_to_never_attempted && !previous_has_been_attempted {
1223            // Existing entry and change are both NeverAttempted
1224            // - preserve original values of all fields
1225            // - but replace None with Some
1226            //
1227            // # Security
1228            //
1229            // Preserve the original field values for NeverAttempted peers,
1230            // so malicious peers can't keep changing our peer connection order.
1231            Some(MetaAddr {
1232                addr: self.addr(),
1233                services: previous.services.or_else(|| self.untrusted_services()),
1234                untrusted_last_seen: previous
1235                    .untrusted_last_seen
1236                    .or_else(|| self.untrusted_last_seen(local_now)),
1237                // The peer has not been attempted, so these fields must be None
1238                last_response: None,
1239                rtt: None,
1240                ping_sent_at: None,
1241                last_attempt: None,
1242                last_failure: None,
1243                last_connection_state: self.peer_addr_state(),
1244                misbehavior_score: previous.misbehavior_score + self.misbehavior_score(),
1245                is_inbound: previous.is_inbound || self.is_inbound(),
1246                user_agent: None,
1247                negotiated_version: None,
1248            })
1249        } else {
1250            // Existing entry and change are both Attempt, Responded, Failed,
1251            // and the change is later, either in time or in connection progress
1252            // (this is checked above and returns None early):
1253            // - update the fields from the change
1254            Some(MetaAddr {
1255                addr: self.addr(),
1256                // Always update optional fields, unless the update is None.
1257                //
1258                // We want up-to-date services, even if they have fewer bits
1259                services: self.untrusted_services().or(previous.services),
1260                // Only NeverAttempted changes can modify the last seen field
1261                untrusted_last_seen: previous.untrusted_last_seen,
1262                // This is a wall clock time, but we already checked that responses are in order.
1263                // Even if the wall clock time has jumped, we want to use the latest time.
1264                last_response: self.last_response(local_now).or(previous.last_response),
1265                rtt: self.rtt(),
1266                ping_sent_at: self.ping_sent_at(),
1267                // These are monotonic times, we already checked the responses are in order.
1268                last_attempt: self.last_attempt(instant_now).or(previous.last_attempt),
1269                last_failure: self.last_failure(instant_now).or(previous.last_failure),
1270                // Replace the state with the updated state.
1271                last_connection_state: self.peer_addr_state(),
1272                misbehavior_score: previous.misbehavior_score + self.misbehavior_score(),
1273                is_inbound: previous.is_inbound || self.is_inbound(),
1274                user_agent: self.user_agent().or(previous.user_agent),
1275                negotiated_version: self.negotiated_version().or(previous.negotiated_version),
1276            })
1277        }
1278    }
1279}
1280
1281impl Ord for MetaAddr {
1282    /// `MetaAddr`s are sorted in approximate reconnection attempt order, but
1283    /// with `Responded` peers sorted first as a group.
1284    ///
1285    /// But this order should not be used for reconnection attempts: use
1286    /// [`reconnection_peers`] instead.
1287    ///
1288    /// See [`CandidateSet`] for more details.
1289    ///
1290    /// [`CandidateSet`]: super::peer_set::CandidateSet
1291    /// [`reconnection_peers`]: crate::AddressBook::reconnection_peers
1292    fn cmp(&self, other: &Self) -> Ordering {
1293        use std::net::IpAddr::{V4, V6};
1294        use Ordering::*;
1295
1296        // First, try states that are more likely to work
1297        let more_reliable_state = self.last_connection_state.cmp(&other.last_connection_state);
1298
1299        // Then, try addresses that are more likely to be valid.
1300        // Currently, this prefers addresses with canonical Zcash ports.
1301        let more_likely_valid = self.peer_preference().cmp(&other.peer_preference());
1302
1303        // # Security and Correctness
1304        //
1305        // Prioritise older attempt times, so we try all peers in each state,
1306        // before re-trying any of them. This avoids repeatedly reconnecting to
1307        // peers that aren't working.
1308        //
1309        // Using the internal attempt time for peer ordering also minimises the
1310        // amount of information `Addrs` responses leak about Zebra's retry order.
1311
1312        // If the states are the same, try peers that we haven't tried for a while.
1313        //
1314        // Each state change updates a specific time field, and
1315        // None is less than Some(T),
1316        // so the resulting ordering for each state is:
1317        // - Responded: oldest attempts first (attempt times are required and unique)
1318        // - NeverAttempted...: recent gossiped times first (all other times are None)
1319        // - Failed: oldest attempts first (attempt times are required and unique)
1320        // - AttemptPending: oldest attempts first (attempt times are required and unique)
1321        //
1322        // We also compare the other local times, because:
1323        // - seed peers may not have an attempt time, and
1324        // - updates can be applied to the address book in any order.
1325        let older_attempt = self.last_attempt.cmp(&other.last_attempt);
1326        let older_failure = self.last_failure.cmp(&other.last_failure);
1327        let older_response = self.last_response.cmp(&other.last_response);
1328
1329        // # Security
1330        //
1331        // Compare local times before untrusted gossiped times and services.
1332        // This gives malicious peers less influence over our peer connection
1333        // order.
1334
1335        // If all local times are None, try peers that other peers have seen more recently
1336        let newer_untrusted_last_seen = self
1337            .untrusted_last_seen
1338            .cmp(&other.untrusted_last_seen)
1339            .reverse();
1340
1341        // Finally, prefer numerically larger service bit patterns
1342        //
1343        // As of June 2021, Zebra only recognises the NODE_NETWORK bit.
1344        // When making outbound connections, Zebra skips non-nodes.
1345        // So this comparison will have no impact until Zebra implements
1346        // more service features.
1347        //
1348        // None is less than Some(T), so peers with missing services are chosen last.
1349        //
1350        // TODO: order services by usefulness, not bit pattern values (#2324)
1351        //       Security: split gossiped and direct services
1352        let larger_services = self.services.cmp(&other.services);
1353
1354        // The remaining comparisons are meaningless for peer connection priority.
1355        // But they are required so that we have a total order on `MetaAddr` values:
1356        // self and other must compare as Equal iff they are equal.
1357
1358        // As a tie-breaker, compare ip and port numerically
1359        //
1360        // Since SocketAddrs are unique in the address book, these comparisons
1361        // guarantee a total, unique order.
1362        let ip_tie_breaker = match (self.addr.ip(), other.addr.ip()) {
1363            (V4(a), V4(b)) => a.octets().cmp(&b.octets()),
1364            (V6(a), V6(b)) => a.octets().cmp(&b.octets()),
1365            (V4(_), V6(_)) => Less,
1366            (V6(_), V4(_)) => Greater,
1367        };
1368        let port_tie_breaker = self.addr.port().cmp(&other.addr.port());
1369
1370        more_reliable_state
1371            .then(more_likely_valid)
1372            .then(older_attempt)
1373            .then(older_failure)
1374            .then(older_response)
1375            .then(newer_untrusted_last_seen)
1376            .then(larger_services)
1377            .then(ip_tie_breaker)
1378            .then(port_tie_breaker)
1379    }
1380}
1381
1382impl PartialOrd for MetaAddr {
1383    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
1384        Some(self.cmp(other))
1385    }
1386}
1387
1388impl PartialEq for MetaAddr {
1389    fn eq(&self, other: &Self) -> bool {
1390        self.cmp(other) == Ordering::Equal
1391    }
1392}
1393
1394impl Eq for MetaAddr {}