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