Skip to main content

zebra_network/peer/
handshake.rs

1//! Initial [`Handshake`]s with Zebra peers over a `PeerTransport`.
2
3use std::{
4    cmp::min,
5    fmt,
6    future::Future,
7    net::{Ipv4Addr, SocketAddr},
8    panic,
9    pin::Pin,
10    sync::Arc,
11    task::{Context, Poll},
12    time::Duration,
13};
14
15use chrono::{TimeZone, Utc};
16use futures::{channel::oneshot, future, pin_mut, FutureExt, SinkExt, StreamExt};
17use indexmap::IndexSet;
18use tokio::{
19    io::{AsyncRead, AsyncWrite},
20    sync::broadcast,
21    task::JoinError,
22    time::{error, timeout, Instant},
23};
24use tokio_stream::wrappers::IntervalStream;
25use tokio_util::codec::Framed;
26use tower::Service;
27use tracing::{span, Level, Span};
28use tracing_futures::Instrument;
29
30use zebra_chain::{
31    block,
32    chain_tip::{ChainTip, NoChainTip},
33    parameters::Network,
34    serialization::{DateTime32, SerializationError},
35};
36
37use crate::{
38    constants,
39    meta_addr::MetaAddrChange,
40    peer::{
41        CancelHeartbeatTask, Client, ClientRequest, Connection, ErrorSlot, HandshakeError,
42        MinimumPeerVersion, PeerError,
43    },
44    peer_set::{ConnectionTracker, InventoryChange},
45    protocol::{
46        external::{types::*, AddrInVersion, Codec, InventoryHash, Message},
47        internal::{Request, Response},
48    },
49    types::MetaAddr,
50    BoxError, Config, PeerSocketAddr, VersionMessage,
51};
52
53#[cfg(test)]
54mod tests;
55
56/// A [`Service`] that handshakes with a remote peer and constructs a
57/// client/server pair.
58///
59/// CORRECTNESS
60///
61/// To avoid hangs, each handshake (or its connector) should be:
62/// - launched in a separate task, and
63/// - wrapped in a timeout.
64pub struct Handshake<S, C = NoChainTip>
65where
66    S: Service<Request, Response = Response, Error = BoxError> + Clone + Send + 'static,
67    S::Future: Send,
68    C: ChainTip + Clone + Send + 'static,
69{
70    config: Config,
71    user_agent: String,
72    our_services: PeerServices,
73    relay: bool,
74
75    inbound_service: S,
76    address_book_updater: tokio::sync::mpsc::Sender<MetaAddrChange>,
77    inv_collector: broadcast::Sender<InventoryChange>,
78    minimum_peer_version: MinimumPeerVersion<C>,
79    nonces: Arc<futures::lock::Mutex<IndexSet<Nonce>>>,
80
81    parent_span: Span,
82}
83
84impl<S, C> fmt::Debug for Handshake<S, C>
85where
86    S: Service<Request, Response = Response, Error = BoxError> + Clone + Send + 'static,
87    S::Future: Send,
88    C: ChainTip + Clone + Send + 'static,
89{
90    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
91        // skip the channels, they don't tell us anything useful
92        f.debug_struct(std::any::type_name::<Handshake<S, C>>())
93            .field("config", &self.config)
94            .field("user_agent", &self.user_agent)
95            .field("our_services", &self.our_services)
96            .field("relay", &self.relay)
97            .field("minimum_peer_version", &self.minimum_peer_version)
98            .field("parent_span", &self.parent_span)
99            .finish()
100    }
101}
102
103impl<S, C> Clone for Handshake<S, C>
104where
105    S: Service<Request, Response = Response, Error = BoxError> + Clone + Send + 'static,
106    S::Future: Send,
107    C: ChainTip + Clone + Send + 'static,
108{
109    fn clone(&self) -> Self {
110        Self {
111            config: self.config.clone(),
112            user_agent: self.user_agent.clone(),
113            our_services: self.our_services,
114            relay: self.relay,
115            inbound_service: self.inbound_service.clone(),
116            address_book_updater: self.address_book_updater.clone(),
117            inv_collector: self.inv_collector.clone(),
118            minimum_peer_version: self.minimum_peer_version.clone(),
119            nonces: self.nonces.clone(),
120            parent_span: self.parent_span.clone(),
121        }
122    }
123}
124
125/// The metadata for a peer connection.
126#[derive(Clone, Debug, PartialEq, Eq)]
127pub struct ConnectionInfo {
128    /// The connected peer address, if known.
129    /// This address might not be valid for outbound connections.
130    ///
131    /// Peers can be connected via a transient inbound or proxy address,
132    /// which will appear as the connected address to the OS and Zebra.
133    pub connected_addr: ConnectedAddr,
134
135    /// The network protocol [`VersionMessage`] sent by the remote peer.
136    pub remote: VersionMessage,
137
138    /// The network protocol version negotiated with the remote peer.
139    ///
140    /// Derived from `remote.version` and the
141    /// [current `zebra_network` protocol version](constants::CURRENT_NETWORK_PROTOCOL_VERSION).
142    pub negotiated_version: Version,
143}
144
145/// The peer address that we are handshaking with.
146///
147/// Typically, we can rely on outbound addresses, but inbound addresses don't
148/// give us enough information to reconnect to that peer.
149#[derive(Copy, Clone, PartialEq, Eq)]
150pub enum ConnectedAddr {
151    /// The address we used to make a direct outbound connection.
152    ///
153    /// In an honest network, a Zcash peer is listening on this exact address
154    /// and port.
155    OutboundDirect {
156        /// The connected outbound remote address and port.
157        addr: PeerSocketAddr,
158    },
159
160    /// The address we received from the OS, when a remote peer directly
161    /// connected to our Zcash listener port.
162    ///
163    /// In an honest network, a Zcash peer might be listening on this address,
164    /// if its outbound address is the same as its listener address. But the port
165    /// is an ephemeral outbound TCP port, not a listener port.
166    InboundDirect {
167        /// The connected inbound remote address and ephemeral port.
168        ///
169        /// The IP address might be the address of a Zcash peer, but the port is an ephemeral port.
170        addr: PeerSocketAddr,
171    },
172
173    /// The proxy address we used to make an outbound connection.
174    ///
175    /// The proxy address can be used by many connections, but our own ephemeral
176    /// outbound address and port can be used as an identifier for the duration
177    /// of this connection.
178    OutboundProxy {
179        /// The remote address and port of the proxy.
180        proxy_addr: SocketAddr,
181
182        /// The local address and transient port we used to connect to the proxy.
183        transient_local_addr: SocketAddr,
184    },
185
186    /// The address we received from the OS, when a remote peer connected via an
187    /// inbound proxy.
188    ///
189    /// The proxy's ephemeral outbound address can be used as an identifier for
190    /// the duration of this connection.
191    InboundProxy {
192        /// The local address and transient port we used to connect to the proxy.
193        transient_addr: SocketAddr,
194    },
195
196    /// An isolated connection, where we deliberately don't have any connection metadata.
197    Isolated,
198    //
199    // TODO: handle Tor onion addresses
200}
201
202/// Get an unspecified IPv4 address for `network`
203pub fn get_unspecified_ipv4_addr(network: Network) -> SocketAddr {
204    (Ipv4Addr::UNSPECIFIED, network.default_port()).into()
205}
206
207use ConnectedAddr::*;
208
209impl ConnectedAddr {
210    /// Returns a new outbound directly connected addr.
211    pub fn new_outbound_direct(addr: PeerSocketAddr) -> ConnectedAddr {
212        OutboundDirect { addr }
213    }
214
215    /// Returns a new inbound directly connected addr.
216    pub fn new_inbound_direct(addr: PeerSocketAddr) -> ConnectedAddr {
217        InboundDirect { addr }
218    }
219
220    /// Returns a new outbound connected addr via `proxy`.
221    ///
222    /// `local_addr` is the ephemeral local address of the connection.
223    #[allow(unused)]
224    pub fn new_outbound_proxy(proxy: SocketAddr, local_addr: SocketAddr) -> ConnectedAddr {
225        OutboundProxy {
226            proxy_addr: proxy,
227            transient_local_addr: local_addr,
228        }
229    }
230
231    /// Returns a new inbound connected addr from `proxy`.
232    //
233    // TODO: distinguish between direct listeners and proxy listeners in the
234    //       rest of zebra-network
235    #[allow(unused)]
236    pub fn new_inbound_proxy(proxy: SocketAddr) -> ConnectedAddr {
237        InboundProxy {
238            transient_addr: proxy,
239        }
240    }
241
242    /// Returns a new isolated connected addr, with no metadata.
243    pub fn new_isolated() -> ConnectedAddr {
244        Isolated
245    }
246
247    /// Returns a `PeerSocketAddr` that can be used to track this connection in the
248    /// `AddressBook`.
249    ///
250    /// `None` for inbound connections, proxy connections, and isolated
251    /// connections.
252    ///
253    /// # Correctness
254    ///
255    /// This address can be used for reconnection attempts, or as a permanent
256    /// identifier.
257    ///
258    /// # Security
259    ///
260    /// This address must not depend on the canonical address from the `Version`
261    /// message. Otherwise, malicious peers could interfere with other peers
262    /// `AddressBook` state.
263    ///
264    /// TODO: remove the `get_` from these methods (Rust style avoids `get` prefixes)
265    pub fn get_address_book_addr(&self) -> Option<PeerSocketAddr> {
266        match self {
267            OutboundDirect { addr } | InboundDirect { addr } => Some(*addr),
268            // TODO: consider using the canonical address of the peer to track
269            //       outbound proxy connections
270            OutboundProxy { .. } | InboundProxy { .. } | Isolated => None,
271        }
272    }
273
274    /// Returns a `PeerSocketAddr` that can be used to temporarily identify a
275    /// connection.
276    ///
277    /// Isolated connections must not change Zebra's peer set or address book
278    /// state, so they do not have an identifier.
279    ///
280    /// # Correctness
281    ///
282    /// The returned address is only valid while the original connection is
283    /// open. It must not be used in the `AddressBook`, for outbound connection
284    /// attempts, or as a permanent identifier.
285    ///
286    /// # Security
287    ///
288    /// This address must not depend on the canonical address from the `Version`
289    /// message. Otherwise, malicious peers could interfere with other peers'
290    /// `PeerSet` state.
291    pub fn get_transient_addr(&self) -> Option<PeerSocketAddr> {
292        match self {
293            OutboundDirect { addr } => Some(*addr),
294            InboundDirect { addr } => Some(*addr),
295            OutboundProxy {
296                transient_local_addr,
297                ..
298            } => Some(PeerSocketAddr::from(*transient_local_addr)),
299            InboundProxy { transient_addr } => Some(PeerSocketAddr::from(*transient_addr)),
300            Isolated => None,
301        }
302    }
303
304    /// Returns the metrics label for this connection's address.
305    pub fn get_transient_addr_label(&self) -> String {
306        self.get_transient_addr()
307            .map_or_else(|| "isolated".to_string(), |addr| addr.to_string())
308    }
309
310    /// Returns a short label for the kind of connection.
311    pub fn get_short_kind_label(&self) -> &'static str {
312        match self {
313            OutboundDirect { .. } => "Out",
314            InboundDirect { .. } => "In",
315            OutboundProxy { .. } => "ProxOut",
316            InboundProxy { .. } => "ProxIn",
317            Isolated => "Isol",
318        }
319    }
320
321    /// Returns a list of alternate remote peer addresses, which can be used for
322    /// reconnection attempts.
323    ///
324    /// Uses the connected address, and the remote canonical address.
325    ///
326    /// Skips duplicates. If this is an outbound connection, also skips the
327    /// remote address that we're currently connected to.
328    pub fn get_alternate_addrs(
329        &self,
330        mut canonical_remote: PeerSocketAddr,
331    ) -> impl Iterator<Item = PeerSocketAddr> {
332        let addrs = match self {
333            OutboundDirect { addr } => {
334                // Fixup unspecified addresses and ports using known good data
335                if canonical_remote.ip().is_unspecified() {
336                    canonical_remote.set_ip(addr.ip());
337                }
338                if canonical_remote.port() == 0 {
339                    canonical_remote.set_port(addr.port());
340                }
341
342                // Try the canonical remote address, if it is different from the
343                // outbound address (which we already have in our address book)
344                if &canonical_remote != addr {
345                    vec![canonical_remote]
346                } else {
347                    // we didn't learn a new address from the handshake:
348                    // it's the same as the outbound address, which is already in our address book
349                    Vec::new()
350                }
351            }
352
353            InboundDirect { addr } => {
354                // Use the IP from the TCP connection, and the port the peer told us
355                let maybe_addr = SocketAddr::new(addr.ip(), canonical_remote.port()).into();
356
357                // Try both addresses, but remove one duplicate if they match
358                if canonical_remote != maybe_addr {
359                    vec![canonical_remote, maybe_addr]
360                } else {
361                    vec![canonical_remote]
362                }
363            }
364
365            // Proxy addresses can't be used for reconnection attempts, but we
366            // can try the canonical remote address
367            OutboundProxy { .. } | InboundProxy { .. } => vec![canonical_remote],
368
369            // Hide all metadata for isolated connections
370            Isolated => Vec::new(),
371        };
372
373        addrs.into_iter()
374    }
375
376    /// Returns true if the [`ConnectedAddr`] was created for an inbound connection.
377    pub fn is_inbound(&self) -> bool {
378        matches!(self, InboundDirect { .. } | InboundProxy { .. })
379    }
380}
381
382impl fmt::Debug for ConnectedAddr {
383    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
384        let kind = self.get_short_kind_label();
385        let addr = self.get_transient_addr_label();
386
387        if matches!(self, Isolated) {
388            f.write_str(kind)
389        } else {
390            f.debug_tuple(kind).field(&addr).finish()
391        }
392    }
393}
394
395/// A builder for `Handshake`.
396pub struct Builder<S, C = NoChainTip>
397where
398    S: Service<Request, Response = Response, Error = BoxError> + Clone + Send + 'static,
399    S::Future: Send,
400    C: ChainTip + Clone + Send + 'static,
401{
402    config: Option<Config>,
403    our_services: Option<PeerServices>,
404    user_agent: Option<String>,
405    relay: Option<bool>,
406
407    inbound_service: Option<S>,
408    address_book_updater: Option<tokio::sync::mpsc::Sender<MetaAddrChange>>,
409    inv_collector: Option<broadcast::Sender<InventoryChange>>,
410    latest_chain_tip: C,
411}
412
413impl<S, C> Builder<S, C>
414where
415    S: Service<Request, Response = Response, Error = BoxError> + Clone + Send + 'static,
416    S::Future: Send,
417    C: ChainTip + Clone + Send + 'static,
418{
419    /// Provide a config.  Mandatory.
420    pub fn with_config(mut self, config: Config) -> Self {
421        self.config = Some(config);
422        self
423    }
424
425    /// Provide a service to handle inbound requests. Mandatory.
426    pub fn with_inbound_service(mut self, inbound_service: S) -> Self {
427        self.inbound_service = Some(inbound_service);
428        self
429    }
430
431    /// Provide a channel for registering inventory advertisements. Optional.
432    ///
433    /// This channel takes transient remote addresses, which the `PeerSet` uses
434    /// to look up peers that have specific inventory.
435    pub fn with_inventory_collector(
436        mut self,
437        inv_collector: broadcast::Sender<InventoryChange>,
438    ) -> Self {
439        self.inv_collector = Some(inv_collector);
440        self
441    }
442
443    /// Provide a hook for timestamp collection. Optional.
444    ///
445    /// This channel takes `MetaAddr`s, permanent addresses which can be used to
446    /// make outbound connections to peers.
447    pub fn with_address_book_updater(
448        mut self,
449        address_book_updater: tokio::sync::mpsc::Sender<MetaAddrChange>,
450    ) -> Self {
451        self.address_book_updater = Some(address_book_updater);
452        self
453    }
454
455    /// Provide the services this node advertises to other peers.  Optional.
456    ///
457    /// If this is unset, the node will advertise itself as a client.
458    pub fn with_advertised_services(mut self, services: PeerServices) -> Self {
459        self.our_services = Some(services);
460        self
461    }
462
463    /// Provide this node's user agent.  Optional.
464    ///
465    /// This must be a valid BIP14 string.  If it is unset, the user-agent will be empty.
466    pub fn with_user_agent(mut self, user_agent: String) -> Self {
467        self.user_agent = Some(user_agent);
468        self
469    }
470
471    /// Provide a realtime endpoint to obtain the current best chain tip block height. Optional.
472    ///
473    /// If this is unset, the minimum accepted protocol version for peer connections is kept
474    /// constant over network upgrade activations.
475    ///
476    /// Use [`NoChainTip`] to explicitly provide no chain tip.
477    pub fn with_latest_chain_tip<NewC>(self, latest_chain_tip: NewC) -> Builder<S, NewC>
478    where
479        NewC: ChainTip + Clone + Send + 'static,
480    {
481        Builder {
482            latest_chain_tip,
483
484            // TODO: Until Rust RFC 2528 reaches stable, we can't do `..self`
485            config: self.config,
486            inbound_service: self.inbound_service,
487            address_book_updater: self.address_book_updater,
488            our_services: self.our_services,
489            user_agent: self.user_agent,
490            relay: self.relay,
491            inv_collector: self.inv_collector,
492        }
493    }
494
495    /// Whether to request that peers relay transactions to our node.  Optional.
496    ///
497    /// If this is unset, the node will not request transactions.
498    pub fn want_transactions(mut self, relay: bool) -> Self {
499        self.relay = Some(relay);
500        self
501    }
502
503    /// Consume this builder and produce a [`Handshake`].
504    ///
505    /// Returns an error only if any mandatory field was unset.
506    pub fn finish(self) -> Result<Handshake<S, C>, &'static str> {
507        let config = self.config.ok_or("did not specify config")?;
508        let inbound_service = self
509            .inbound_service
510            .ok_or("did not specify inbound service")?;
511        let inv_collector = self.inv_collector.unwrap_or_else(|| {
512            let (tx, _) = broadcast::channel(100);
513            tx
514        });
515        let address_book_updater = self.address_book_updater.unwrap_or_else(|| {
516            // No `AddressBookUpdater` for timestamp collection was passed, so create a stub
517            // channel. Dropping the receiver means sends will fail, but we don't care.
518            let (tx, _rx) = tokio::sync::mpsc::channel(1);
519            tx
520        });
521        let nonces = Arc::new(futures::lock::Mutex::new(IndexSet::new()));
522        let user_agent = self.user_agent.unwrap_or_default();
523        let our_services = self.our_services.unwrap_or_else(PeerServices::empty);
524        let relay = self.relay.unwrap_or(false);
525        let network = config.network.clone();
526        let minimum_peer_version = MinimumPeerVersion::new(self.latest_chain_tip, &network);
527
528        Ok(Handshake {
529            config,
530            user_agent,
531            our_services,
532            relay,
533            inbound_service,
534            address_book_updater,
535            inv_collector,
536            minimum_peer_version,
537            nonces,
538            parent_span: Span::current(),
539        })
540    }
541}
542
543impl<S> Handshake<S, NoChainTip>
544where
545    S: Service<Request, Response = Response, Error = BoxError> + Clone + Send + 'static,
546    S::Future: Send,
547{
548    /// Create a builder that configures a [`Handshake`] service.
549    pub fn builder() -> Builder<S, NoChainTip> {
550        // We don't derive `Default` because the derive inserts a `where S:
551        // Default` bound even though `Option<S>` implements `Default` even if
552        // `S` does not.
553        Builder {
554            config: None,
555            our_services: None,
556            user_agent: None,
557            relay: None,
558            inbound_service: None,
559            address_book_updater: None,
560            inv_collector: None,
561            latest_chain_tip: NoChainTip,
562        }
563    }
564}
565
566/// Negotiate the Zcash network protocol version with the remote peer at `connected_addr`, using
567/// the connection `peer_conn`.
568///
569/// We split `Handshake` into its components before calling this function, to avoid infectious
570/// `Sync` bounds on the returned future.
571///
572/// Returns the [`VersionMessage`] sent by the remote peer, and the [`Version`] negotiated with the
573/// remote peer, inside a [`ConnectionInfo`] struct.
574#[allow(clippy::too_many_arguments)]
575pub async fn negotiate_version<PeerTransport>(
576    peer_conn: &mut Framed<PeerTransport, Codec>,
577    connected_addr: &ConnectedAddr,
578    config: Config,
579    nonces: Arc<futures::lock::Mutex<IndexSet<Nonce>>>,
580    user_agent: String,
581    our_services: PeerServices,
582    relay: bool,
583    mut minimum_peer_version: MinimumPeerVersion<impl ChainTip>,
584) -> Result<Arc<ConnectionInfo>, HandshakeError>
585where
586    PeerTransport: AsyncRead + AsyncWrite + Unpin + Send + 'static,
587{
588    // Create a random nonce for this connection
589    let local_nonce = Nonce::default();
590
591    // Insert the nonce for this handshake into the shared nonce set.
592    // Each connection has its own connection state, and handshakes execute concurrently.
593    //
594    // # Correctness
595    //
596    // It is ok to wait for the lock here, because handshakes have a short
597    // timeout, and the async mutex will be released when the task times
598    // out.
599    {
600        let mut locked_nonces = nonces.lock().await;
601
602        // Duplicate nonces are very rare, because they require a 64-bit random number collision,
603        // and the nonce set is limited to a few hundred entries.
604        let is_unique_nonce = locked_nonces.insert(local_nonce);
605        if !is_unique_nonce {
606            return Err(HandshakeError::LocalDuplicateNonce);
607        }
608
609        // # Security
610        //
611        // Limit the amount of memory used for nonces.
612        // Nonces can be left in the set if the connection fails or times out between
613        // the nonce being inserted, and it being removed.
614        //
615        // Zebra has strict connection limits, so we limit the number of nonces to
616        // the configured connection limit.
617        // This is a tradeoff between:
618        // - avoiding memory denial of service attacks which make large numbers of connections,
619        //   for example, 100 failed inbound connections takes 1 second.
620        // - memory usage: 16 bytes per `Nonce`, 3.2 kB for 200 nonces
621        // - collision probability: two hundred 64-bit nonces have a very low collision probability
622        //   <https://en.wikipedia.org/wiki/Birthday_problem#Probability_of_a_shared_birthday_(collision)>
623        while locked_nonces.len() > config.peerset_total_connection_limit() {
624            locked_nonces.shift_remove_index(0);
625        }
626
627        std::mem::drop(locked_nonces);
628    }
629
630    // Don't leak our exact clock skew to our peers. On the other hand,
631    // we can't deviate too much, or zcashd will get confused.
632    // Inspection of the zcashd source code reveals that the timestamp
633    // is only ever used at the end of parsing the version message, in
634    //
635    // pfrom->nTimeOffset = timeWarning.AddTimeData(pfrom->addr, nTime, GetTime());
636    //
637    // AddTimeData is defined in src/timedata.cpp and is a no-op as long
638    // as the difference between the specified timestamp and the
639    // zcashd's local time is less than TIMEDATA_WARNING_THRESHOLD, set
640    // to 10 * 60 seconds (10 minutes).
641    //
642    // nTimeOffset is peer metadata that is never used, except for
643    // statistics.
644    //
645    // To try to stay within the range where zcashd will ignore our clock skew,
646    // truncate the timestamp to the nearest 5 minutes.
647    let now = Utc::now().timestamp();
648    let timestamp = Utc
649        .timestamp_opt(now - now.rem_euclid(5 * 60), 0)
650        .single()
651        .expect("in-range number of seconds and valid nanosecond");
652
653    // Whether this node is still syncing, used below to decide whether outbound peers must
654    // advertise `NODE_NETWORK`. Read before the match below, which can move `config.network`.
655    let is_syncing = !minimum_peer_version
656        .chain_tip()
657        .is_at_or_near_network_tip(&config.network);
658
659    let (their_addr, our_services, our_listen_addr) = match connected_addr {
660        // Version messages require an address, so we use
661        // an unspecified address for Isolated connections
662        Isolated => {
663            let unspec_ipv4 = get_unspecified_ipv4_addr(config.network);
664            (unspec_ipv4.into(), PeerServices::empty(), unspec_ipv4)
665        }
666        _ => {
667            let their_addr = connected_addr
668                .get_transient_addr()
669                .expect("non-Isolated connections have a remote addr");
670
671            // Include the configured external address in our version message, if any, otherwise, include our listen address.
672            let advertise_addr = match config.external_addr {
673                Some(external_addr) => {
674                    info!(?their_addr, ?config.listen_addr, "using external address for Version messages");
675                    external_addr
676                }
677                None => config.listen_addr,
678            };
679
680            (their_addr, our_services, advertise_addr)
681        }
682    };
683
684    let start_height = minimum_peer_version
685        .chain_tip()
686        .best_tip_height()
687        .unwrap_or(block::Height(0));
688
689    let our_version = VersionMessage {
690        version: constants::CURRENT_NETWORK_PROTOCOL_VERSION,
691        services: our_services,
692        timestamp,
693        address_recv: AddrInVersion::new(their_addr, PeerServices::NODE_NETWORK),
694        // TODO: detect external address (#1893)
695        address_from: AddrInVersion::new(our_listen_addr, our_services),
696        nonce: local_nonce,
697        user_agent: user_agent.clone(),
698        start_height,
699        relay,
700    }
701    .into();
702
703    debug!(?our_version, "sending initial version message");
704    peer_conn.send(our_version).await?;
705
706    let mut remote_msg = peer_conn
707        .next()
708        .await
709        .ok_or(HandshakeError::ConnectionClosed)??;
710
711    // Wait for next message if the one we got is not Version
712    let remote: VersionMessage = loop {
713        match remote_msg {
714            Message::Version(version_message) => {
715                debug!(?version_message, "got version message from remote peer");
716                break version_message;
717            }
718            _ => {
719                remote_msg = peer_conn
720                    .next()
721                    .await
722                    .ok_or(HandshakeError::ConnectionClosed)??;
723                debug!(?remote_msg, "ignoring non-version message from remote peer");
724            }
725        }
726    };
727
728    let remote_address_services = remote.address_from.untrusted_services();
729    if remote_address_services != remote.services {
730        info!(
731            ?remote.services,
732            ?remote_address_services,
733            ?remote.user_agent,
734            "peer with inconsistent version services and version address services",
735        );
736    }
737
738    // Check for nonce reuse, indicating self-connection
739    //
740    // # Correctness
741    //
742    // We must wait for the lock before we continue with the connection, to avoid
743    // self-connection. If the connection times out, the async lock will be
744    // released.
745    //
746    // # Security
747    //
748    // We don't remove the nonce here, because peers that observe our network traffic could
749    // maliciously remove nonces, and force us to make self-connections.
750    let nonce_reuse = nonces.lock().await.contains(&remote.nonce);
751    if nonce_reuse {
752        info!(?connected_addr, "rejecting self-connection attempt");
753        Err(HandshakeError::RemoteNonceReuse)?;
754    }
755
756    // # Security
757    //
758    // Reject connections to peers on old versions, because they might not know about all
759    // network upgrades and could lead to chain forks or slower block propagation.
760    let min_version = minimum_peer_version.current();
761
762    if remote.version < min_version {
763        debug!(
764            remote_ip = ?their_addr,
765            ?remote.version,
766            ?min_version,
767            ?remote.user_agent,
768            "disconnecting from peer with obsolete network protocol version",
769        );
770
771        // the value is the number of rejected handshakes, by peer IP and protocol version
772        metrics::counter!(
773            "zcash.net.peers.obsolete",
774            "remote_ip" => their_addr.to_string(),
775            "remote_version" => remote.version.to_string(),
776            "min_version" => min_version.to_string(),
777            "user_agent" => remote.user_agent.clone(),
778        )
779        .increment(1);
780
781        // the value is the remote version of the most recent rejected handshake from each peer
782        metrics::gauge!(
783            "zcash.net.peers.version.obsolete",
784            "remote_ip" => their_addr.to_string(),
785        )
786        .set(remote.version.0 as f64);
787
788        // Disconnect if peer is using an obsolete version.
789        return Err(HandshakeError::ObsoleteVersion(remote.version));
790    }
791
792    // # Security
793    //
794    // While syncing, require `NODE_NETWORK` from outbound peers: peers without it can't serve
795    // us historic blocks, but still occupy outbound slots and receive syncer block requests.
796    // When many reachable listeners are non-serving, those slots can fill up and stall a fresh
797    // sync (#11061). This mirrors Bitcoin Core, which requires block-serving peers during
798    // initial block download.
799    //
800    // At or near the network tip the requirement is dropped, because non-serving peers (like
801    // pruned nodes) can still serve recent blocks and transactions. Inbound and isolated
802    // connections are always exempt, so light clients can still connect to us.
803    if is_syncing
804        && matches!(connected_addr, OutboundDirect { .. } | OutboundProxy { .. })
805        && !remote.services.contains(PeerServices::NODE_NETWORK)
806    {
807        debug!(
808            remote_ip = ?their_addr,
809            ?remote.services,
810            ?remote.user_agent,
811            "disconnecting from non-serving peer",
812        );
813
814        // the value is the number of rejected handshakes, by peer IP and advertised services
815        metrics::counter!(
816            "zcash.net.peers.missing_services",
817            "remote_ip" => their_addr.to_string(),
818            "remote_services" => format!("{:?}", remote.services),
819            "user_agent" => remote.user_agent.clone(),
820        )
821        .increment(1);
822
823        // Disconnect if the outbound peer doesn't advertise the required services.
824        return Err(HandshakeError::MissingRequiredServices {
825            services: remote.services,
826        });
827    }
828
829    let negotiated_version = min(constants::CURRENT_NETWORK_PROTOCOL_VERSION, remote.version);
830
831    // Limit containing struct size, and avoid multiple duplicates of 300+ bytes of data.
832    let connection_info = Arc::new(ConnectionInfo {
833        connected_addr: *connected_addr,
834        remote,
835        negotiated_version,
836    });
837
838    debug!(
839        remote_ip = ?their_addr,
840        ?connection_info.remote.version,
841        ?negotiated_version,
842        ?min_version,
843        ?connection_info.remote.user_agent,
844        "negotiated network protocol version with peer",
845    );
846
847    // the value is the number of connected handshakes, by peer IP and protocol version
848    metrics::counter!(
849        "zcash.net.peers.connected",
850        "remote_ip" => their_addr.to_string(),
851        "remote_version" => connection_info.remote.version.to_string(),
852        "negotiated_version" => negotiated_version.to_string(),
853        "min_version" => min_version.to_string(),
854        "user_agent" => connection_info.remote.user_agent.clone(),
855    )
856    .increment(1);
857
858    // the value is the remote version of the most recent connected handshake from each peer
859    metrics::gauge!(
860        "zcash.net.peers.version.connected",
861        "remote_ip" => their_addr.to_string(),
862    )
863    .set(connection_info.remote.version.0 as f64);
864
865    peer_conn.send(Message::Verack).await?;
866
867    let mut remote_msg = peer_conn
868        .next()
869        .await
870        .ok_or(HandshakeError::ConnectionClosed)??;
871
872    // Wait for next message if the one we got is not Verack
873    loop {
874        match remote_msg {
875            Message::Verack => {
876                debug!(?remote_msg, "got verack message from remote peer");
877                break;
878            }
879            _ => {
880                remote_msg = peer_conn
881                    .next()
882                    .await
883                    .ok_or(HandshakeError::ConnectionClosed)??;
884                debug!(?remote_msg, "ignoring non-verack message from remote peer");
885            }
886        }
887    }
888
889    Ok(connection_info)
890}
891
892/// A handshake request.
893/// Contains the information needed to handshake with the peer.
894pub struct HandshakeRequest<PeerTransport>
895where
896    PeerTransport: AsyncRead + AsyncWrite + Unpin + Send + 'static,
897{
898    /// The tokio [`TcpStream`](tokio::net::TcpStream) or Tor
899    /// `arti_client::DataStream` to the peer.
900    // Use [`arti_client::DataStream`] when #5492 is done.
901    pub data_stream: PeerTransport,
902
903    /// The address of the peer, and other related information.
904    pub connected_addr: ConnectedAddr,
905
906    /// A connection tracker that reduces the open connection count when dropped.
907    ///
908    /// Used to limit the number of open connections in Zebra.
909    pub connection_tracker: ConnectionTracker,
910}
911
912impl<S, PeerTransport, C> Service<HandshakeRequest<PeerTransport>> for Handshake<S, C>
913where
914    S: Service<Request, Response = Response, Error = BoxError> + Clone + Send + 'static,
915    S::Future: Send,
916    C: ChainTip + Clone + Send + 'static,
917    PeerTransport: AsyncRead + AsyncWrite + Unpin + Send + 'static,
918{
919    type Response = Client;
920    type Error = BoxError;
921    type Future =
922        Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send + 'static>>;
923
924    fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
925        Poll::Ready(Ok(()))
926    }
927
928    fn call(&mut self, req: HandshakeRequest<PeerTransport>) -> Self::Future {
929        let HandshakeRequest {
930            data_stream,
931            connected_addr,
932            mut connection_tracker,
933        } = req;
934
935        let negotiator_span = debug_span!("negotiator", peer = ?connected_addr);
936        // set the peer connection span's parent to the global span, as it
937        // should exist independently of its creation source (inbound
938        // connection, crawler, initial peer, ...)
939        let connection_span =
940            span!(parent: &self.parent_span, Level::INFO, "", peer = ?connected_addr);
941
942        // Clone these upfront, so they can be moved into the future.
943        let nonces = self.nonces.clone();
944        let inbound_service = self.inbound_service.clone();
945        let address_book_updater = self.address_book_updater.clone();
946        let inv_collector = self.inv_collector.clone();
947        let config = self.config.clone();
948        let user_agent = self.user_agent.clone();
949        let our_services = self.our_services;
950        let relay = self.relay;
951        let minimum_peer_version = self.minimum_peer_version.clone();
952
953        // # Security
954        //
955        // `zebra_network::init()` implements a connection timeout on this future.
956        // Any code outside this future does not have a timeout.
957        let fut = async move {
958            debug!(
959                addr = ?connected_addr,
960                "negotiating protocol version with remote peer"
961            );
962
963            // Start timing the handshake for metrics
964            let handshake_start = Instant::now();
965
966            let mut peer_conn = Framed::new(
967                data_stream,
968                Codec::builder()
969                    .for_network(&config.network)
970                    .with_metrics_addr_label(connected_addr.get_transient_addr_label())
971                    .finish(),
972            );
973
974            let connection_info = match negotiate_version(
975                &mut peer_conn,
976                &connected_addr,
977                config,
978                nonces,
979                user_agent,
980                our_services,
981                relay,
982                minimum_peer_version,
983            )
984            .await
985            {
986                Ok(info) => {
987                    // Record successful handshake duration
988                    let duration = handshake_start.elapsed().as_secs_f64();
989                    metrics::histogram!(
990                        "zcash.net.peer.handshake.duration_seconds",
991                        "result" => "success"
992                    )
993                    .record(duration);
994                    info
995                }
996                Err(err) => {
997                    // Record failed handshake duration and failure reason
998                    let duration = handshake_start.elapsed().as_secs_f64();
999                    let reason = match &err {
1000                        HandshakeError::UnexpectedMessage(_) => "unexpected_message",
1001                        HandshakeError::RemoteNonceReuse => "nonce_reuse",
1002                        HandshakeError::LocalDuplicateNonce => "duplicate_nonce",
1003                        HandshakeError::ConnectionClosed => "connection_closed",
1004                        HandshakeError::Io(_) => "io_error",
1005                        HandshakeError::Serialization(_) => "serialization",
1006                        HandshakeError::ObsoleteVersion(_) => "obsolete_version",
1007                        HandshakeError::MissingRequiredServices { .. } => {
1008                            "missing_required_services"
1009                        }
1010                        HandshakeError::Timeout => "timeout",
1011                    };
1012                    metrics::histogram!(
1013                        "zcash.net.peer.handshake.duration_seconds",
1014                        "result" => "failure"
1015                    )
1016                    .record(duration);
1017                    metrics::counter!(
1018                        "zcash.net.peer.handshake.failures.total",
1019                        "reason" => reason
1020                    )
1021                    .increment(1);
1022
1023                    // Rejected non-serving peers are reported by the crawler's `report_failed`
1024                    // without their services, so they get the standard failure backoff and can
1025                    // be dialed again once the node is near the network tip (#11061).
1026                    return Err(err);
1027                }
1028            };
1029
1030            let remote_services = connection_info.remote.services;
1031
1032            // The handshake succeeded: update the peer status from AttemptPending to Responded,
1033            // send initial connection info, and update the active connection counter.
1034            connection_tracker.mark_open();
1035            if let Some(book_addr) = connected_addr.get_address_book_addr() {
1036                // the collector doesn't depend on network activity,
1037                // so this await should not hang
1038                let _ = address_book_updater
1039                    .send(MetaAddr::new_connected(
1040                        book_addr,
1041                        &remote_services,
1042                        connected_addr.is_inbound(),
1043                        connection_info.remote.user_agent.clone(),
1044                        connection_info.negotiated_version,
1045                    ))
1046                    .await;
1047            }
1048
1049            // Reconfigure the codec to use the negotiated version.
1050            //
1051            // TODO: The tokio documentation says not to do this while any frames are still being processed.
1052            // Since we don't know that here, another way might be to release the tcp
1053            // stream from the unversioned Framed wrapper and construct a new one with a versioned codec.
1054            let bare_codec = peer_conn.codec_mut();
1055            bare_codec.reconfigure_version(connection_info.negotiated_version);
1056            bare_codec.reconfigure_full_body_len();
1057
1058            debug!("constructing client, spawning server");
1059
1060            // These channels communicate between the inbound and outbound halves of the connection,
1061            // and between the different connection tasks. We create separate tasks and channels
1062            // for each new connection.
1063            let (server_tx, server_rx) = futures::channel::mpsc::channel(0);
1064            let (shutdown_tx, shutdown_rx) = oneshot::channel();
1065            let error_slot = ErrorSlot::default();
1066
1067            let (peer_tx, peer_rx) = peer_conn.split();
1068
1069            // Instrument the peer's rx and tx streams.
1070
1071            let inner_conn_span = connection_span.clone();
1072            let peer_tx = peer_tx.with(move |msg: Message| {
1073                let span = debug_span!(parent: inner_conn_span.clone(), "outbound_metric");
1074                // Add a metric for outbound messages.
1075                metrics::counter!(
1076                    "zcash.net.out.messages",
1077                    "command" => msg.command(),
1078                    "addr" => connected_addr.get_transient_addr_label(),
1079                )
1080                .increment(1);
1081                // We need to use future::ready rather than an async block here,
1082                // because we need the sink to be Unpin, and the With<Fut, ...>
1083                // returned by .with is Unpin only if Fut is Unpin, and the
1084                // futures generated by async blocks are not Unpin.
1085                future::ready(Ok(msg)).instrument(span)
1086            });
1087
1088            // CORRECTNESS
1089            //
1090            // Ping/Pong messages and every error must update the peer address state via
1091            // the inbound_ts_collector.
1092            //
1093            // The heartbeat task sends regular Ping/Pong messages,
1094            // and it ends the connection if the heartbeat times out.
1095            // So we can just track peer activity based on Ping and Pong.
1096            // (This significantly improves performance, by reducing time system calls.)
1097            let inbound_ts_collector = address_book_updater.clone();
1098            let inbound_inv_collector = inv_collector.clone();
1099            let ts_inner_conn_span = connection_span.clone();
1100            let inv_inner_conn_span = connection_span.clone();
1101            let peer_rx = peer_rx
1102                .then(move |msg| {
1103                    // Add a metric for inbound messages and errors.
1104                    // Fire a timestamp or failure event.
1105                    let inbound_ts_collector = inbound_ts_collector.clone();
1106                    let span =
1107                        debug_span!(parent: ts_inner_conn_span.clone(), "inbound_ts_collector");
1108
1109                    async move {
1110                        match &msg {
1111                            Ok(msg) => {
1112                                metrics::counter!(
1113                                    "zcash.net.in.messages",
1114                                    "command" => msg.command(),
1115                                    "addr" => connected_addr.get_transient_addr_label(),
1116                                )
1117                                .increment(1);
1118
1119                                // # Security
1120                                //
1121                                // Peer messages are not rate-limited, so we can't send anything
1122                                // to a shared channel or do anything expensive here.
1123                            }
1124                            Err(err) => {
1125                                metrics::counter!(
1126                                    "zebra.net.in.errors",
1127                                    "error" => err.to_string(),
1128                                    "addr" => connected_addr.get_transient_addr_label(),
1129                                )
1130                                .increment(1);
1131
1132                                // # Security
1133                                //
1134                                // Peer errors are rate-limited because:
1135                                // - opening connections is rate-limited
1136                                // - the number of connections is limited
1137                                // - after the first error, the peer is disconnected
1138                                if let Some(book_addr) = connected_addr.get_address_book_addr() {
1139                                    let _ = inbound_ts_collector
1140                                        .send(MetaAddr::new_errored(book_addr, remote_services))
1141                                        .await;
1142                                }
1143                            }
1144                        }
1145                        msg
1146                    }
1147                    .instrument(span)
1148                })
1149                .then(move |msg| {
1150                    let inbound_inv_collector = inbound_inv_collector.clone();
1151                    let span = debug_span!(parent: inv_inner_conn_span.clone(), "inventory_filter");
1152                    register_inventory_status(msg, connected_addr, inbound_inv_collector)
1153                        .instrument(span)
1154                })
1155                .boxed();
1156
1157            // If we've learned potential peer addresses from the inbound connection remote address
1158            // or the handshake version message, add those addresses to the peer cache for this
1159            // peer.
1160            //
1161            // # Security
1162            //
1163            // We can't add these alternate addresses directly to the address book. If we did,
1164            // malicious peers could interfere with the address book state of other peers by
1165            // providing their addresses in `Version` messages. Or they could fill the address book
1166            // with fake addresses.
1167            //
1168            // These peer addresses are rate-limited because:
1169            // - opening connections is rate-limited
1170            // - these addresses are put in the peer address cache
1171            // - the peer address cache is only used when Zebra requests addresses from that peer
1172            let remote_canonical_addr = connection_info.remote.address_from.addr();
1173            let alternate_addrs = connected_addr
1174                .get_alternate_addrs(remote_canonical_addr)
1175                .map(|addr| {
1176                    // Assume the connecting node is a server node, and it's available now.
1177                    MetaAddr::new_gossiped_meta_addr(
1178                        addr,
1179                        PeerServices::NODE_NETWORK,
1180                        DateTime32::now(),
1181                    )
1182                });
1183
1184            let server = Connection::new(
1185                inbound_service,
1186                server_rx,
1187                error_slot.clone(),
1188                peer_tx,
1189                connection_tracker,
1190                connection_info.clone(),
1191                alternate_addrs.collect(),
1192            );
1193
1194            let connection_task = tokio::spawn(
1195                server
1196                    .run(peer_rx)
1197                    .instrument(connection_span.clone())
1198                    .boxed(),
1199            );
1200
1201            let heartbeat_task = tokio::spawn(
1202                send_periodic_heartbeats_with_shutdown_handle(
1203                    connected_addr,
1204                    shutdown_rx,
1205                    server_tx.clone(),
1206                    address_book_updater.clone(),
1207                )
1208                .instrument(tracing::debug_span!(parent: connection_span, "heartbeat"))
1209                .boxed(),
1210            );
1211
1212            let client = Client {
1213                connection_info,
1214                shutdown_tx: Some(shutdown_tx),
1215                server_tx,
1216                inv_collector,
1217                error_slot,
1218                connection_task,
1219                heartbeat_task,
1220            };
1221
1222            Ok(client)
1223        };
1224
1225        // Correctness: As a defence-in-depth against hangs, wrap the entire handshake in a timeout.
1226        let fut = timeout(constants::HANDSHAKE_TIMEOUT, fut);
1227
1228        // Spawn a new task to drive this handshake, forwarding panics to the calling task.
1229        tokio::spawn(fut.instrument(negotiator_span))
1230            .map(
1231                |join_result: Result<
1232                    Result<Result<Client, HandshakeError>, error::Elapsed>,
1233                    JoinError,
1234                >| {
1235                    match join_result {
1236                        Ok(Ok(Ok(connection_client))) => Ok(connection_client),
1237                        Ok(Ok(Err(handshake_error))) => Err(handshake_error.into()),
1238                        Ok(Err(timeout_error)) => Err(timeout_error.into()),
1239                        Err(join_error) => match join_error.try_into_panic() {
1240                            // Forward panics to the calling task
1241                            Ok(panic_reason) => panic::resume_unwind(panic_reason),
1242                            Err(join_error) => Err(join_error.into()),
1243                        },
1244                    }
1245                },
1246            )
1247            .boxed()
1248    }
1249}
1250
1251/// Register any advertised or missing inventory in `msg` for `connected_addr`.
1252pub(crate) async fn register_inventory_status(
1253    msg: Result<Message, SerializationError>,
1254    connected_addr: ConnectedAddr,
1255    inv_collector: broadcast::Sender<InventoryChange>,
1256) -> Result<Message, SerializationError> {
1257    match (&msg, connected_addr.get_transient_addr()) {
1258        (Ok(Message::Inv(advertised)), Some(transient_addr)) => {
1259            // We ignore inventory messages with more than one
1260            // block, because they are most likely replies to a
1261            // query, rather than a newly gossiped block.
1262            //
1263            // (We process inventory messages with any number of
1264            // transactions.)
1265            //
1266            // https://zebra.zfnd.org/dev/rfcs/0003-inventory-tracking.html#inventory-monitoring
1267            //
1268            // Note: zcashd has a bug where it merges queued inv messages of
1269            // the same or different types. Zebra compensates by sending `notfound`
1270            // responses to the inv collector. (#2156, #1768)
1271            //
1272            // (We can't split `inv`s, because that fills the inventory registry
1273            // with useless entries that the whole network has, making it large and slow.)
1274            match advertised.as_slice() {
1275                [advertised @ InventoryHash::Block(_)] => {
1276                    debug!(
1277                        ?advertised,
1278                        "registering gossiped advertised block inventory for peer"
1279                    );
1280
1281                    // The peer set and inv collector use the peer's remote
1282                    // address as an identifier
1283                    // If all receivers have been dropped, `send` returns an error.
1284                    // When that happens, Zebra is shutting down, so we want to ignore this error.
1285                    let _ = inv_collector
1286                        .send(InventoryChange::new_available(*advertised, transient_addr));
1287                }
1288                advertised => {
1289                    let advertised = advertised
1290                        .iter()
1291                        .filter(|advertised| advertised.unmined_tx_id().is_some());
1292
1293                    debug!(
1294                        ?advertised,
1295                        "registering advertised unmined transaction inventory for peer",
1296                    );
1297
1298                    if let Some(change) =
1299                        InventoryChange::new_available_multi(advertised, transient_addr)
1300                    {
1301                        // Ignore channel errors that should only happen during shutdown.
1302                        let _ = inv_collector.send(change);
1303                    }
1304                }
1305            }
1306        }
1307
1308        (Ok(Message::NotFound(missing)), Some(transient_addr)) => {
1309            // Ignore Errors and the unsupported FilteredBlock type
1310            let missing = missing.iter().filter(|missing| {
1311                missing.unmined_tx_id().is_some() || missing.block_hash().is_some()
1312            });
1313
1314            debug!(?missing, "registering missing inventory for peer");
1315
1316            if let Some(change) = InventoryChange::new_missing_multi(missing, transient_addr) {
1317                let _ = inv_collector.send(change);
1318            }
1319        }
1320        _ => {}
1321    }
1322
1323    msg
1324}
1325
1326/// Send periodical heartbeats to `server_tx`, and update the peer status through
1327/// `heartbeat_ts_collector`.
1328///
1329/// # Correctness
1330///
1331/// To prevent hangs:
1332/// - every await that depends on the network must have a timeout (or interval)
1333/// - every error/shutdown must update the address book state and return
1334///
1335/// The address book state can be updated via `ClientRequest.tx`, or the
1336/// heartbeat_ts_collector.
1337///
1338/// Returning from this function terminates the connection's heartbeat task.
1339async fn send_periodic_heartbeats_with_shutdown_handle(
1340    connected_addr: ConnectedAddr,
1341    shutdown_rx: oneshot::Receiver<CancelHeartbeatTask>,
1342    server_tx: futures::channel::mpsc::Sender<ClientRequest>,
1343    heartbeat_ts_collector: tokio::sync::mpsc::Sender<MetaAddrChange>,
1344) -> Result<(), BoxError> {
1345    use futures::future::Either;
1346
1347    let heartbeat_run_loop = send_periodic_heartbeats_run_loop(
1348        connected_addr,
1349        server_tx,
1350        heartbeat_ts_collector.clone(),
1351    );
1352
1353    pin_mut!(shutdown_rx);
1354    pin_mut!(heartbeat_run_loop);
1355
1356    // CORRECTNESS
1357    //
1358    // Currently, select prefers the first future if multiple
1359    // futures are ready.
1360    //
1361    // Starvation is impossible here, because interval has a
1362    // slow rate, and shutdown is a oneshot. If both futures
1363    // are ready, we want the shutdown to take priority over
1364    // sending a useless heartbeat.
1365    match future::select(shutdown_rx, heartbeat_run_loop).await {
1366        Either::Left((Ok(CancelHeartbeatTask), _unused_run_loop)) => {
1367            tracing::trace!("shutting down because Client requested shut down");
1368            handle_heartbeat_shutdown(
1369                PeerError::ClientCancelledHeartbeatTask,
1370                &heartbeat_ts_collector,
1371                &connected_addr,
1372            )
1373            .await
1374        }
1375        Either::Left((Err(oneshot::Canceled), _unused_run_loop)) => {
1376            tracing::trace!("shutting down because Client was dropped");
1377            handle_heartbeat_shutdown(
1378                PeerError::ClientDropped,
1379                &heartbeat_ts_collector,
1380                &connected_addr,
1381            )
1382            .await
1383        }
1384        Either::Right((result, _unused_shutdown)) => {
1385            tracing::trace!("shutting down due to heartbeat failure");
1386            // heartbeat_timeout() already send an error on the timestamp collector channel
1387
1388            result
1389        }
1390    }
1391}
1392
1393/// Send periodical heartbeats to `server_tx`, and update the peer status through
1394/// `heartbeat_ts_collector`.
1395///
1396/// See `send_periodic_heartbeats_with_shutdown_handle` for details.
1397async fn send_periodic_heartbeats_run_loop(
1398    connected_addr: ConnectedAddr,
1399    mut server_tx: futures::channel::mpsc::Sender<ClientRequest>,
1400    heartbeat_ts_collector: tokio::sync::mpsc::Sender<MetaAddrChange>,
1401) -> Result<(), BoxError> {
1402    // Don't send the first heartbeat immediately - we've just completed the handshake!
1403    let mut interval = tokio::time::interval_at(
1404        Instant::now() + constants::HEARTBEAT_INTERVAL,
1405        constants::HEARTBEAT_INTERVAL,
1406    );
1407    // If the heartbeat is delayed, also delay all future heartbeats.
1408    // (Shorter heartbeat intervals just add load, without any benefit.)
1409    interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay);
1410
1411    let mut interval_stream = IntervalStream::new(interval);
1412
1413    while let Some(_instant) = interval_stream.next().await {
1414        // We've reached another heartbeat interval without
1415        // shutting down, so do a heartbeat request.
1416        let ping_sent_at = Instant::now();
1417        if let Some(book_addr) = connected_addr.get_address_book_addr() {
1418            let _ = heartbeat_ts_collector
1419                .send(MetaAddr::new_ping_sent(book_addr, ping_sent_at.into()))
1420                .await;
1421        }
1422
1423        let heartbeat = send_one_heartbeat(&mut server_tx);
1424        let rtt = heartbeat_timeout(heartbeat, &heartbeat_ts_collector, &connected_addr).await?;
1425
1426        // # Security
1427        //
1428        // Peer heartbeats are rate-limited because:
1429        // - opening connections is rate-limited
1430        // - the number of connections is limited
1431        // - Zebra initiates each heartbeat using a timer
1432        if let Some(book_addr) = connected_addr.get_address_book_addr() {
1433            if let Some(rtt) = rtt {
1434                // the collector doesn't depend on network activity,
1435                // so this await should not hang
1436                let _ = heartbeat_ts_collector
1437                    .send(MetaAddr::new_responded(book_addr, Some(rtt)))
1438                    .await;
1439            }
1440        }
1441    }
1442
1443    unreachable!("unexpected IntervalStream termination")
1444}
1445
1446/// Send one heartbeat using `server_tx`.
1447async fn send_one_heartbeat(
1448    server_tx: &mut futures::channel::mpsc::Sender<ClientRequest>,
1449) -> Result<Response, BoxError> {
1450    // We just reached a heartbeat interval, so start sending
1451    // a heartbeat.
1452    let (tx, rx) = oneshot::channel();
1453
1454    // Try to send the heartbeat request
1455    let request = Request::Ping(Nonce::default());
1456    tracing::trace!(?request, "queueing heartbeat request");
1457    match server_tx.try_send(ClientRequest {
1458        request,
1459        tx,
1460        // we're not requesting inventory, so we don't need to update the registry
1461        inv_collector: None,
1462        transient_addr: None,
1463        span: tracing::Span::current(),
1464    }) {
1465        Ok(()) => {}
1466        Err(e) => {
1467            if e.is_disconnected() {
1468                Err(PeerError::ConnectionClosed)?;
1469            } else if e.is_full() {
1470                // Send the message when the Client becomes ready.
1471                // If sending takes too long, the heartbeat timeout will elapse
1472                // and close the connection, reducing our load to busy peers.
1473                server_tx.send(e.into_inner()).await?;
1474            } else {
1475                // we need to map unexpected error types to PeerErrors
1476                warn!(?e, "unexpected try_send error");
1477                Err(e)?;
1478            };
1479        }
1480    }
1481
1482    // Flush the heartbeat request from the queue
1483    server_tx.flush().await?;
1484    tracing::trace!("sent heartbeat request");
1485
1486    // Heartbeats are checked internally to the
1487    // connection logic, but we need to wait on the
1488    // response to avoid canceling the request.
1489    let response = rx.await??;
1490    tracing::trace!(?response, "got heartbeat response");
1491
1492    Ok(response)
1493}
1494
1495/// Wrap `fut` in a timeout, handing any inner or outer errors using
1496/// `handle_heartbeat_error`.
1497async fn heartbeat_timeout(
1498    fut: impl Future<Output = Result<Response, BoxError>>,
1499    address_book_updater: &tokio::sync::mpsc::Sender<MetaAddrChange>,
1500    connected_addr: &ConnectedAddr,
1501) -> Result<Option<Duration>, BoxError> {
1502    let response = match timeout(constants::HEARTBEAT_INTERVAL, fut).await {
1503        Ok(inner_result) => {
1504            handle_heartbeat_error(inner_result, address_book_updater, connected_addr).await?
1505        }
1506        Err(elapsed) => {
1507            handle_heartbeat_error(Err(elapsed), address_book_updater, connected_addr).await?
1508        }
1509    };
1510
1511    let rtt = match response {
1512        Response::Pong(rtt) => Some(rtt),
1513        _ => None,
1514    };
1515
1516    Ok(rtt)
1517}
1518
1519/// If `result.is_err()`, mark `connected_addr` as failed using `address_book_updater`.
1520async fn handle_heartbeat_error<T, E>(
1521    result: Result<T, E>,
1522    address_book_updater: &tokio::sync::mpsc::Sender<MetaAddrChange>,
1523    connected_addr: &ConnectedAddr,
1524) -> Result<T, E>
1525where
1526    E: std::fmt::Debug,
1527{
1528    match result {
1529        Ok(t) => Ok(t),
1530        Err(err) => {
1531            tracing::debug!(?err, "heartbeat error, shutting down");
1532
1533            // # Security
1534            //
1535            // Peer errors and shutdowns are rate-limited because:
1536            // - opening connections is rate-limited
1537            // - the number of connections is limited
1538            // - after the first error or shutdown, the peer is disconnected
1539            if let Some(book_addr) = connected_addr.get_address_book_addr() {
1540                let _ = address_book_updater
1541                    .send(MetaAddr::new_errored(book_addr, None))
1542                    .await;
1543            }
1544            Err(err)
1545        }
1546    }
1547}
1548
1549/// Mark `connected_addr` as shut down using `address_book_updater`.
1550async fn handle_heartbeat_shutdown(
1551    peer_error: PeerError,
1552    address_book_updater: &tokio::sync::mpsc::Sender<MetaAddrChange>,
1553    connected_addr: &ConnectedAddr,
1554) -> Result<(), BoxError> {
1555    tracing::debug!(?peer_error, "client shutdown, shutting down heartbeat");
1556
1557    if let Some(book_addr) = connected_addr.get_address_book_addr() {
1558        let _ = address_book_updater
1559            .send(MetaAddr::new_shutdown(book_addr))
1560            .await;
1561    }
1562
1563    Err(peer_error.into())
1564}