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