zebra_network/
meta_addr.rs

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