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