Skip to main content

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