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 {}