Skip to main content

zakura_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 rand::{rngs::OsRng, RngCore};
19use tokio::{
20    io::{AsyncRead, AsyncWrite},
21    sync::broadcast,
22    task::JoinError,
23    time::{error, timeout, Instant},
24};
25use tokio_stream::wrappers::IntervalStream;
26use tokio_util::codec::Framed;
27use tower::Service;
28use tracing::{span, Level, Span};
29use tracing_futures::Instrument;
30
31use zakura_chain::{
32    block,
33    chain_tip::{ChainTip, NoChainTip},
34    parameters::Network,
35    serialization::{DateTime32, SerializationError},
36};
37
38use crate::{
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    zakura::{
52        P2pV2Upgrade, P2pV2UpgradeAccept, P2pV2UpgradeInit, P2pV2UpgradeReject,
53        ZakuraHandshakeConfig, ZakuraLegacyNonces, ZakuraPeerId, ZakuraProtocolError,
54        P2P_V2_UPGRADE_COMMAND, PRELUDE_MAGIC,
55    },
56    zakura::{ZakuraHandshakeConnector, ZakuraRejectReason, ZakuraUpgradeOutcome},
57    BoxError, Config, PeerSocketAddr, VersionMessage,
58};
59
60#[cfg(test)]
61mod tests;
62
63/// A [`Service`] that handshakes with a remote peer and constructs a
64/// client/server pair.
65///
66/// CORRECTNESS
67///
68/// To avoid hangs, each handshake (or its connector) should be:
69/// - launched in a separate task, and
70/// - wrapped in a timeout.
71pub struct Handshake<S, C = NoChainTip>
72where
73    S: Service<Request, Response = Response, Error = BoxError> + Clone + Send + 'static,
74    S::Future: Send,
75    C: ChainTip + Clone + Send + 'static,
76{
77    config: Config,
78    user_agent: String,
79    our_services: PeerServices,
80    relay: bool,
81
82    inbound_service: S,
83    address_book_updater: tokio::sync::mpsc::Sender<MetaAddrChange>,
84    inv_collector: broadcast::Sender<InventoryChange>,
85    minimum_peer_version: MinimumPeerVersion<C>,
86    nonces: Arc<futures::lock::Mutex<IndexSet<Nonce>>>,
87    zakura_handshake_connector: Option<ZakuraHandshakeConnector>,
88
89    parent_span: Span,
90}
91
92impl<S, C> fmt::Debug for Handshake<S, C>
93where
94    S: Service<Request, Response = Response, Error = BoxError> + Clone + Send + 'static,
95    S::Future: Send,
96    C: ChainTip + Clone + Send + 'static,
97{
98    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
99        // skip the channels, they don't tell us anything useful
100        f.debug_struct(std::any::type_name::<Handshake<S, C>>())
101            .field("config", &self.config)
102            .field("user_agent", &self.user_agent)
103            .field("our_services", &self.our_services)
104            .field("relay", &self.relay)
105            .field("minimum_peer_version", &self.minimum_peer_version)
106            .field(
107                "zakura_handshake_connector",
108                &self.zakura_handshake_connector,
109            )
110            .field("parent_span", &self.parent_span)
111            .finish()
112    }
113}
114
115impl<S, C> Clone for Handshake<S, C>
116where
117    S: Service<Request, Response = Response, Error = BoxError> + Clone + Send + 'static,
118    S::Future: Send,
119    C: ChainTip + Clone + Send + 'static,
120{
121    fn clone(&self) -> Self {
122        Self {
123            config: self.config.clone(),
124            user_agent: self.user_agent.clone(),
125            our_services: self.our_services,
126            relay: self.relay,
127            inbound_service: self.inbound_service.clone(),
128            address_book_updater: self.address_book_updater.clone(),
129            inv_collector: self.inv_collector.clone(),
130            minimum_peer_version: self.minimum_peer_version.clone(),
131            nonces: self.nonces.clone(),
132            zakura_handshake_connector: self.zakura_handshake_connector.clone(),
133            parent_span: self.parent_span.clone(),
134        }
135    }
136}
137
138/// The metadata for a peer connection.
139#[derive(Clone, Debug, PartialEq, Eq)]
140pub struct ConnectionInfo {
141    /// The connected peer address, if known.
142    /// This address might not be valid for outbound connections.
143    ///
144    /// Peers can be connected via a transient inbound or proxy address,
145    /// which will appear as the connected address to the OS and Zebra.
146    pub connected_addr: ConnectedAddr,
147
148    /// The network protocol [`VersionMessage`] sent by the remote peer.
149    pub remote: VersionMessage,
150
151    /// The network protocol [`VersionMessage`] sent by this node.
152    pub local: VersionMessage,
153
154    /// The network protocol version negotiated with the remote peer.
155    ///
156    /// Derived from `remote.version` and the
157    /// [current `zakura_network` protocol version](constants::CURRENT_NETWORK_PROTOCOL_VERSION).
158    pub negotiated_version: Version,
159}
160
161/// The peer address that we are handshaking with.
162///
163/// Typically, we can rely on outbound addresses, but inbound addresses don't
164/// give us enough information to reconnect to that peer.
165#[derive(Copy, Clone, PartialEq, Eq)]
166pub enum ConnectedAddr {
167    /// The address we used to make a direct outbound connection.
168    ///
169    /// In an honest network, a Zcash peer is listening on this exact address
170    /// and port.
171    OutboundDirect {
172        /// The connected outbound remote address and port.
173        addr: PeerSocketAddr,
174    },
175
176    /// The address we received from the OS, when a remote peer directly
177    /// connected to our Zcash listener port.
178    ///
179    /// In an honest network, a Zcash peer might be listening on this address,
180    /// if its outbound address is the same as its listener address. But the port
181    /// is an ephemeral outbound TCP port, not a listener port.
182    InboundDirect {
183        /// The connected inbound remote address and ephemeral port.
184        ///
185        /// The IP address might be the address of a Zcash peer, but the port is an ephemeral port.
186        addr: PeerSocketAddr,
187    },
188
189    /// The proxy address we used to make an outbound connection.
190    ///
191    /// The proxy address can be used by many connections, but our own ephemeral
192    /// outbound address and port can be used as an identifier for the duration
193    /// of this connection.
194    OutboundProxy {
195        /// The remote address and port of the proxy.
196        proxy_addr: SocketAddr,
197
198        /// The local address and transient port we used to connect to the proxy.
199        transient_local_addr: SocketAddr,
200    },
201
202    /// The address we received from the OS, when a remote peer connected via an
203    /// inbound proxy.
204    ///
205    /// The proxy's ephemeral outbound address can be used as an identifier for
206    /// the duration of this connection.
207    InboundProxy {
208        /// The local address and transient port we used to connect to the proxy.
209        transient_addr: SocketAddr,
210    },
211
212    /// An isolated connection, where we deliberately don't have any connection metadata.
213    Isolated,
214    //
215    // TODO: handle Tor onion addresses
216}
217
218/// Get an unspecified IPv4 address for `network`
219pub fn get_unspecified_ipv4_addr(network: Network) -> SocketAddr {
220    (Ipv4Addr::UNSPECIFIED, network.default_port()).into()
221}
222
223use ConnectedAddr::*;
224
225impl ConnectedAddr {
226    /// Returns a new outbound directly connected addr.
227    pub fn new_outbound_direct(addr: PeerSocketAddr) -> ConnectedAddr {
228        OutboundDirect { addr }
229    }
230
231    /// Returns a new inbound directly connected addr.
232    pub fn new_inbound_direct(addr: PeerSocketAddr) -> ConnectedAddr {
233        InboundDirect { addr }
234    }
235
236    /// Returns a new outbound connected addr via `proxy`.
237    ///
238    /// `local_addr` is the ephemeral local address of the connection.
239    #[allow(unused)]
240    pub fn new_outbound_proxy(proxy: SocketAddr, local_addr: SocketAddr) -> ConnectedAddr {
241        OutboundProxy {
242            proxy_addr: proxy,
243            transient_local_addr: local_addr,
244        }
245    }
246
247    /// Returns a new inbound connected addr from `proxy`.
248    //
249    // TODO: distinguish between direct listeners and proxy listeners in the
250    //       rest of zakura-network
251    #[allow(unused)]
252    pub fn new_inbound_proxy(proxy: SocketAddr) -> ConnectedAddr {
253        InboundProxy {
254            transient_addr: proxy,
255        }
256    }
257
258    /// Returns a new isolated connected addr, with no metadata.
259    pub fn new_isolated() -> ConnectedAddr {
260        Isolated
261    }
262
263    /// Returns a `PeerSocketAddr` that can be used to track this connection in the
264    /// `AddressBook`.
265    ///
266    /// `None` for inbound connections, proxy connections, and isolated
267    /// connections.
268    ///
269    /// # Correctness
270    ///
271    /// This address can be used for reconnection attempts, or as a permanent
272    /// identifier.
273    ///
274    /// # Security
275    ///
276    /// This address must not depend on the canonical address from the `Version`
277    /// message. Otherwise, malicious peers could interfere with other peers
278    /// `AddressBook` state.
279    ///
280    /// TODO: remove the `get_` from these methods (Rust style avoids `get` prefixes)
281    pub fn get_address_book_addr(&self) -> Option<PeerSocketAddr> {
282        match self {
283            OutboundDirect { addr } | InboundDirect { addr } => Some(*addr),
284            // TODO: consider using the canonical address of the peer to track
285            //       outbound proxy connections
286            OutboundProxy { .. } | InboundProxy { .. } | Isolated => None,
287        }
288    }
289
290    /// Returns a `PeerSocketAddr` that can be used to temporarily identify a
291    /// connection.
292    ///
293    /// Isolated connections must not change Zebra's peer set or address book
294    /// state, so they do not have an identifier.
295    ///
296    /// # Correctness
297    ///
298    /// The returned address is only valid while the original connection is
299    /// open. It must not be used in the `AddressBook`, for outbound connection
300    /// attempts, or as a permanent identifier.
301    ///
302    /// # Security
303    ///
304    /// This address must not depend on the canonical address from the `Version`
305    /// message. Otherwise, malicious peers could interfere with other peers'
306    /// `PeerSet` state.
307    pub fn get_transient_addr(&self) -> Option<PeerSocketAddr> {
308        match self {
309            OutboundDirect { addr } => Some(*addr),
310            InboundDirect { addr } => Some(*addr),
311            OutboundProxy {
312                transient_local_addr,
313                ..
314            } => Some(PeerSocketAddr::from(*transient_local_addr)),
315            InboundProxy { transient_addr } => Some(PeerSocketAddr::from(*transient_addr)),
316            Isolated => None,
317        }
318    }
319
320    /// Returns the metrics label for this connection's address.
321    pub fn get_transient_addr_label(&self) -> String {
322        self.get_transient_addr()
323            .map_or_else(|| "isolated".to_string(), |addr| addr.to_string())
324    }
325
326    /// Returns a short label for the kind of connection.
327    pub fn get_short_kind_label(&self) -> &'static str {
328        match self {
329            OutboundDirect { .. } => "Out",
330            InboundDirect { .. } => "In",
331            OutboundProxy { .. } => "ProxOut",
332            InboundProxy { .. } => "ProxIn",
333            Isolated => "Isol",
334        }
335    }
336
337    /// Returns a list of alternate remote peer addresses, which can be used for
338    /// reconnection attempts.
339    ///
340    /// Uses the connected address, and the remote canonical address.
341    ///
342    /// Skips duplicates. If this is an outbound connection, also skips the
343    /// remote address that we're currently connected to.
344    pub fn get_alternate_addrs(
345        &self,
346        mut canonical_remote: PeerSocketAddr,
347    ) -> impl Iterator<Item = PeerSocketAddr> {
348        let addrs = match self {
349            OutboundDirect { addr } => {
350                // Fixup unspecified addresses and ports using known good data
351                if canonical_remote.ip().is_unspecified() {
352                    canonical_remote.set_ip(addr.ip());
353                }
354                if canonical_remote.port() == 0 {
355                    canonical_remote.set_port(addr.port());
356                }
357
358                // Try the canonical remote address, if it is different from the
359                // outbound address (which we already have in our address book)
360                if &canonical_remote != addr {
361                    vec![canonical_remote]
362                } else {
363                    // we didn't learn a new address from the handshake:
364                    // it's the same as the outbound address, which is already in our address book
365                    Vec::new()
366                }
367            }
368
369            InboundDirect { addr } => {
370                // Use the IP from the TCP connection, and the port the peer told us
371                let maybe_addr = SocketAddr::new(addr.ip(), canonical_remote.port()).into();
372
373                // Try both addresses, but remove one duplicate if they match
374                if canonical_remote != maybe_addr {
375                    vec![canonical_remote, maybe_addr]
376                } else {
377                    vec![canonical_remote]
378                }
379            }
380
381            // Proxy addresses can't be used for reconnection attempts, but we
382            // can try the canonical remote address
383            OutboundProxy { .. } | InboundProxy { .. } => vec![canonical_remote],
384
385            // Hide all metadata for isolated connections
386            Isolated => Vec::new(),
387        };
388
389        addrs.into_iter()
390    }
391
392    /// Returns true if the [`ConnectedAddr`] was created for an inbound connection.
393    pub fn is_inbound(&self) -> bool {
394        matches!(self, InboundDirect { .. } | InboundProxy { .. })
395    }
396}
397
398impl fmt::Debug for ConnectedAddr {
399    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
400        let kind = self.get_short_kind_label();
401        let addr = self.get_transient_addr_label();
402
403        if matches!(self, Isolated) {
404            f.write_str(kind)
405        } else {
406            f.debug_tuple(kind).field(&addr).finish()
407        }
408    }
409}
410
411/// A builder for `Handshake`.
412pub struct Builder<S, C = NoChainTip>
413where
414    S: Service<Request, Response = Response, Error = BoxError> + Clone + Send + 'static,
415    S::Future: Send,
416    C: ChainTip + Clone + Send + 'static,
417{
418    config: Option<Config>,
419    our_services: Option<PeerServices>,
420    user_agent: Option<String>,
421    relay: Option<bool>,
422
423    inbound_service: Option<S>,
424    address_book_updater: Option<tokio::sync::mpsc::Sender<MetaAddrChange>>,
425    inv_collector: Option<broadcast::Sender<InventoryChange>>,
426    zakura_handshake_connector: Option<ZakuraHandshakeConnector>,
427    latest_chain_tip: C,
428}
429
430impl<S, C> Builder<S, C>
431where
432    S: Service<Request, Response = Response, Error = BoxError> + Clone + Send + 'static,
433    S::Future: Send,
434    C: ChainTip + Clone + Send + 'static,
435{
436    /// Provide a config.  Mandatory.
437    pub fn with_config(mut self, config: Config) -> Self {
438        self.config = Some(config);
439        self
440    }
441
442    /// Provide a service to handle inbound requests. Mandatory.
443    pub fn with_inbound_service(mut self, inbound_service: S) -> Self {
444        self.inbound_service = Some(inbound_service);
445        self
446    }
447
448    /// Provide a channel for registering inventory advertisements. Optional.
449    ///
450    /// This channel takes transient remote addresses, which the `PeerSet` uses
451    /// to look up peers that have specific inventory.
452    pub fn with_inventory_collector(
453        mut self,
454        inv_collector: broadcast::Sender<InventoryChange>,
455    ) -> Self {
456        self.inv_collector = Some(inv_collector);
457        self
458    }
459
460    /// Provide a hook for timestamp collection. Optional.
461    ///
462    /// This channel takes `MetaAddr`s, permanent addresses which can be used to
463    /// make outbound connections to peers.
464    pub fn with_address_book_updater(
465        mut self,
466        address_book_updater: tokio::sync::mpsc::Sender<MetaAddrChange>,
467    ) -> Self {
468        self.address_book_updater = Some(address_book_updater);
469        self
470    }
471
472    /// Provide the services this node advertises to other peers.  Optional.
473    ///
474    /// If this is unset, the node will advertise itself as a client.
475    pub fn with_advertised_services(mut self, services: PeerServices) -> Self {
476        self.our_services = Some(services);
477        self
478    }
479
480    /// Provide this node's user agent.  Optional.
481    ///
482    /// This must be a valid BIP14 string.  If it is unset, the user-agent will be empty.
483    pub fn with_user_agent(mut self, user_agent: String) -> Self {
484        self.user_agent = Some(user_agent);
485        self
486    }
487
488    /// Provide a realtime endpoint to obtain the current best chain tip block height. Optional.
489    ///
490    /// If this is unset, the minimum accepted protocol version for peer connections is kept
491    /// constant over network upgrade activations.
492    ///
493    /// Use [`NoChainTip`] to explicitly provide no chain tip.
494    pub fn with_latest_chain_tip<NewC>(self, latest_chain_tip: NewC) -> Builder<S, NewC>
495    where
496        NewC: ChainTip + Clone + Send + 'static,
497    {
498        Builder {
499            latest_chain_tip,
500
501            // TODO: Until Rust RFC 2528 reaches stable, we can't do `..self`
502            config: self.config,
503            inbound_service: self.inbound_service,
504            address_book_updater: self.address_book_updater,
505            our_services: self.our_services,
506            user_agent: self.user_agent,
507            relay: self.relay,
508            inv_collector: self.inv_collector,
509            zakura_handshake_connector: self.zakura_handshake_connector,
510        }
511    }
512
513    /// Provide a handle for routing mutually capable peers to Zakura P2P v2.
514    pub fn with_zakura_handshake_connector(
515        mut self,
516        zakura_handshake_connector: ZakuraHandshakeConnector,
517    ) -> Self {
518        self.zakura_handshake_connector = Some(zakura_handshake_connector);
519        self
520    }
521
522    /// Whether to request that peers relay transactions to our node.  Optional.
523    ///
524    /// If this is unset, the node will not request transactions.
525    pub fn want_transactions(mut self, relay: bool) -> Self {
526        self.relay = Some(relay);
527        self
528    }
529
530    /// Consume this builder and produce a [`Handshake`].
531    ///
532    /// Returns an error only if any mandatory field was unset.
533    pub fn finish(self) -> Result<Handshake<S, C>, &'static str> {
534        let config = self.config.ok_or("did not specify config")?;
535        let inbound_service = self
536            .inbound_service
537            .ok_or("did not specify inbound service")?;
538        let inv_collector = self.inv_collector.unwrap_or_else(|| {
539            let (tx, _) = broadcast::channel(100);
540            tx
541        });
542        let address_book_updater = self.address_book_updater.unwrap_or_else(|| {
543            // No `AddressBookUpdater` for timestamp collection was passed, so create a stub
544            // channel. Dropping the receiver means sends will fail, but we don't care.
545            let (tx, _rx) = tokio::sync::mpsc::channel(1);
546            tx
547        });
548        let nonces = Arc::new(futures::lock::Mutex::new(IndexSet::new()));
549        let user_agent = configured_user_agent(&config, self.user_agent.unwrap_or_default());
550        let our_services = configured_advertised_services(
551            &config,
552            self.our_services.unwrap_or_else(PeerServices::empty),
553        );
554        let relay = self.relay.unwrap_or(false);
555        let network = config.network.clone();
556        let minimum_peer_version = MinimumPeerVersion::new(self.latest_chain_tip, &network);
557
558        Ok(Handshake {
559            config,
560            user_agent,
561            our_services,
562            relay,
563            inbound_service,
564            address_book_updater,
565            inv_collector,
566            minimum_peer_version,
567            nonces,
568            zakura_handshake_connector: self.zakura_handshake_connector,
569            parent_span: Span::current(),
570        })
571    }
572}
573
574impl<S> Handshake<S, NoChainTip>
575where
576    S: Service<Request, Response = Response, Error = BoxError> + Clone + Send + 'static,
577    S::Future: Send,
578{
579    /// Create a builder that configures a [`Handshake`] service.
580    pub fn builder() -> Builder<S, NoChainTip> {
581        // We don't derive `Default` because the derive inserts a `where S:
582        // Default` bound even though `Option<S>` implements `Default` even if
583        // `S` does not.
584        Builder {
585            config: None,
586            our_services: None,
587            user_agent: None,
588            relay: None,
589            inbound_service: None,
590            address_book_updater: None,
591            inv_collector: None,
592            zakura_handshake_connector: None,
593            latest_chain_tip: NoChainTip,
594        }
595    }
596}
597
598/// Return the services Zakura should advertise for this handshake.
599fn configured_advertised_services(config: &Config, mut services: PeerServices) -> PeerServices {
600    services.remove(PeerServices::NODE_P2P_V2);
601
602    if config.v2_p2p() {
603        services |= PeerServices::NODE_P2P_V2;
604    }
605
606    services
607}
608
609/// Return the user-agent Zakura should advertise for this handshake.
610fn configured_user_agent(config: &Config, user_agent: String) -> String {
611    if !config.v2_p2p() {
612        return user_agent;
613    }
614
615    let zakura_token = format!("Zakura:{}", env!("CARGO_PKG_VERSION"));
616    let trimmed_user_agent = user_agent.trim_matches('/');
617
618    if trimmed_user_agent.is_empty() {
619        format!("/{zakura_token}/")
620    } else if trimmed_user_agent
621        .split('/')
622        .any(|token| token == zakura_token)
623    {
624        // The default user agent already carries the Zakura token, so
625        // prepending it again would advertise `/Zakura:x/Zakura:x/`.
626        format!("/{trimmed_user_agent}/")
627    } else {
628        format!("/{zakura_token}/{trimmed_user_agent}/")
629    }
630}
631
632/// Returns true when the legacy handshake should try to route this peer to Zakura P2P v2.
633fn should_attempt_zakura_upgrade(config: &Config, connection_info: &ConnectionInfo) -> bool {
634    config.v2_p2p()
635        && connection_info
636            .remote
637            .services
638            .contains(PeerServices::NODE_P2P_V2)
639}
640
641/// Negotiate the Zcash network protocol version with the remote peer at `connected_addr`, using
642/// the connection `peer_conn`.
643///
644/// We split `Handshake` into its components before calling this function, to avoid infectious
645/// `Sync` bounds on the returned future.
646///
647/// Returns the [`VersionMessage`] sent by the remote peer, and the [`Version`] negotiated with the
648/// remote peer, inside a [`ConnectionInfo`] struct.
649#[allow(clippy::too_many_arguments)]
650pub async fn negotiate_version<PeerTransport>(
651    peer_conn: &mut Framed<PeerTransport, Codec>,
652    connected_addr: &ConnectedAddr,
653    config: Config,
654    nonces: Arc<futures::lock::Mutex<IndexSet<Nonce>>>,
655    user_agent: String,
656    our_services: PeerServices,
657    relay: bool,
658    mut minimum_peer_version: MinimumPeerVersion<impl ChainTip>,
659) -> Result<Arc<ConnectionInfo>, HandshakeError>
660where
661    PeerTransport: AsyncRead + AsyncWrite + Unpin + Send + 'static,
662{
663    // Create a random nonce for this connection
664    let local_nonce = Nonce::default();
665
666    // Insert the nonce for this handshake into the shared nonce set.
667    // Each connection has its own connection state, and handshakes execute concurrently.
668    //
669    // # Correctness
670    //
671    // It is ok to wait for the lock here, because handshakes have a short
672    // timeout, and the async mutex will be released when the task times
673    // out.
674    {
675        let mut locked_nonces = nonces.lock().await;
676
677        // Duplicate nonces are very rare, because they require a 64-bit random number collision,
678        // and the nonce set is limited to a few hundred entries.
679        let is_unique_nonce = locked_nonces.insert(local_nonce);
680        if !is_unique_nonce {
681            return Err(HandshakeError::LocalDuplicateNonce);
682        }
683
684        // # Security
685        //
686        // Limit the amount of memory used for nonces.
687        // Nonces can be left in the set if the connection fails or times out between
688        // the nonce being inserted, and it being removed.
689        //
690        // Zebra has strict connection limits, so we limit the number of nonces to
691        // the configured connection limit.
692        // This is a tradeoff between:
693        // - avoiding memory denial of service attacks which make large numbers of connections,
694        //   for example, 100 failed inbound connections takes 1 second.
695        // - memory usage: 16 bytes per `Nonce`, 3.2 kB for 200 nonces
696        // - collision probability: two hundred 64-bit nonces have a very low collision probability
697        //   <https://en.wikipedia.org/wiki/Birthday_problem#Probability_of_a_shared_birthday_(collision)>
698        while locked_nonces.len() > config.peerset_total_connection_limit() {
699            locked_nonces.shift_remove_index(0);
700        }
701
702        std::mem::drop(locked_nonces);
703    }
704
705    // Don't leak our exact clock skew to our peers. On the other hand,
706    // we can't deviate too much, or zcashd will get confused.
707    // Inspection of the zcashd source code reveals that the timestamp
708    // is only ever used at the end of parsing the version message, in
709    //
710    // pfrom->nTimeOffset = timeWarning.AddTimeData(pfrom->addr, nTime, GetTime());
711    //
712    // AddTimeData is defined in src/timedata.cpp and is a no-op as long
713    // as the difference between the specified timestamp and the
714    // zcashd's local time is less than TIMEDATA_WARNING_THRESHOLD, set
715    // to 10 * 60 seconds (10 minutes).
716    //
717    // nTimeOffset is peer metadata that is never used, except for
718    // statistics.
719    //
720    // To try to stay within the range where zcashd will ignore our clock skew,
721    // truncate the timestamp to the nearest 5 minutes.
722    let now = Utc::now().timestamp();
723    let timestamp = Utc
724        .timestamp_opt(now - now.rem_euclid(5 * 60), 0)
725        .single()
726        .expect("in-range number of seconds and valid nanosecond");
727
728    let (their_addr, our_services, our_listen_addr) = match connected_addr {
729        // Version messages require an address, so we use
730        // an unspecified address for Isolated connections
731        Isolated => {
732            let unspec_ipv4 = get_unspecified_ipv4_addr(config.network);
733            (unspec_ipv4.into(), PeerServices::empty(), unspec_ipv4)
734        }
735        _ => {
736            let their_addr = connected_addr
737                .get_transient_addr()
738                .expect("non-Isolated connections have a remote addr");
739
740            // Include the configured external address in our version message, if any, otherwise, include our listen address.
741            let advertise_addr = match config.external_addr {
742                Some(external_addr) => {
743                    info!(?their_addr, ?config.listen_addr, "using external address for Version messages");
744                    external_addr
745                }
746                None => config.listen_addr,
747            };
748
749            (their_addr, our_services, advertise_addr)
750        }
751    };
752
753    let start_height = minimum_peer_version
754        .chain_tip()
755        .best_tip_height()
756        .unwrap_or(block::Height(0));
757
758    let our_version = VersionMessage {
759        version: constants::CURRENT_NETWORK_PROTOCOL_VERSION,
760        services: our_services,
761        timestamp,
762        address_recv: AddrInVersion::new(their_addr, PeerServices::NODE_NETWORK),
763        // TODO: detect external address (#1893)
764        address_from: AddrInVersion::new(our_listen_addr, our_services),
765        nonce: local_nonce,
766        user_agent: user_agent.clone(),
767        start_height,
768        relay,
769    };
770
771    debug!(?our_version, "sending initial version message");
772    if our_services.contains(PeerServices::NODE_P2P_V2) {
773        metrics::counter!("zakura.p2p.handshake.service_bit.advertised").increment(1);
774    }
775    peer_conn.send(our_version.clone().into()).await?;
776
777    let mut remote_msg = peer_conn
778        .next()
779        .await
780        .ok_or(HandshakeError::ConnectionClosed)??;
781
782    // Wait for next message if the one we got is not Version
783    let remote: VersionMessage = loop {
784        match remote_msg {
785            Message::Version(version_message) => {
786                debug!(?version_message, "got version message from remote peer");
787                break version_message;
788            }
789            _ => {
790                remote_msg = peer_conn
791                    .next()
792                    .await
793                    .ok_or(HandshakeError::ConnectionClosed)??;
794                debug!(?remote_msg, "ignoring non-version message from remote peer");
795            }
796        }
797    };
798
799    let remote_address_services = remote.address_from.untrusted_services();
800    if remote_address_services != remote.services {
801        info!(
802            ?remote.services,
803            ?remote_address_services,
804            ?remote.user_agent,
805            "peer with inconsistent version services and version address services",
806        );
807    }
808    if remote.services.contains(PeerServices::NODE_P2P_V2) {
809        metrics::counter!("zakura.p2p.handshake.service_bit.remote").increment(1);
810    }
811
812    // Check for nonce reuse, indicating self-connection
813    //
814    // # Correctness
815    //
816    // We must wait for the lock before we continue with the connection, to avoid
817    // self-connection. If the connection times out, the async lock will be
818    // released.
819    //
820    // # Security
821    //
822    // We don't remove the nonce here, because peers that observe our network traffic could
823    // maliciously remove nonces, and force us to make self-connections.
824    let nonce_reuse = nonces.lock().await.contains(&remote.nonce);
825    if nonce_reuse {
826        info!(?connected_addr, "rejecting self-connection attempt");
827        Err(HandshakeError::RemoteNonceReuse)?;
828    }
829
830    // # Security
831    //
832    // Reject connections to peers on old versions, because they might not know about all
833    // network upgrades and could lead to chain forks or slower block propagation.
834    let min_version = minimum_peer_version.current();
835
836    if remote.version < min_version {
837        debug!(
838            remote_ip = ?their_addr,
839            ?remote.version,
840            ?min_version,
841            ?remote.user_agent,
842            "disconnecting from peer with obsolete network protocol version",
843        );
844
845        // the value is the number of rejected handshakes, by peer IP and protocol version
846        metrics::counter!(
847            "zcash.net.peers.obsolete",
848            "remote_ip" => their_addr.to_string(),
849            "remote_version" => remote.version.to_string(),
850            "min_version" => min_version.to_string(),
851            "user_agent" => remote.user_agent.clone(),
852        )
853        .increment(1);
854
855        // the value is the remote version of the most recent rejected handshake from each peer
856        metrics::gauge!(
857            "zcash.net.peers.version.obsolete",
858            "remote_ip" => their_addr.to_string(),
859        )
860        .set(remote.version.0 as f64);
861
862        // Disconnect if peer is using an obsolete version.
863        return Err(HandshakeError::ObsoleteVersion(remote.version));
864    }
865
866    let negotiated_version = min(constants::CURRENT_NETWORK_PROTOCOL_VERSION, remote.version);
867
868    // Limit containing struct size, and avoid multiple duplicates of 300+ bytes of data.
869    let connection_info = Arc::new(ConnectionInfo {
870        connected_addr: *connected_addr,
871        remote,
872        local: our_version,
873        negotiated_version,
874    });
875
876    debug!(
877        remote_ip = ?their_addr,
878        ?connection_info.remote.version,
879        ?negotiated_version,
880        ?min_version,
881        ?connection_info.remote.user_agent,
882        "negotiated network protocol version with peer",
883    );
884
885    // the value is the number of connected handshakes, by peer IP and protocol version
886    metrics::counter!(
887        "zcash.net.peers.connected",
888        "remote_ip" => their_addr.to_string(),
889        "remote_version" => connection_info.remote.version.to_string(),
890        "negotiated_version" => negotiated_version.to_string(),
891        "min_version" => min_version.to_string(),
892        "user_agent" => connection_info.remote.user_agent.clone(),
893    )
894    .increment(1);
895
896    // the value is the remote version of the most recent connected handshake from each peer
897    metrics::gauge!(
898        "zcash.net.peers.version.connected",
899        "remote_ip" => their_addr.to_string(),
900    )
901    .set(connection_info.remote.version.0 as f64);
902
903    peer_conn.send(Message::Verack).await?;
904
905    let mut remote_msg = peer_conn
906        .next()
907        .await
908        .ok_or(HandshakeError::ConnectionClosed)??;
909
910    // Wait for next message if the one we got is not Verack
911    loop {
912        match remote_msg {
913            Message::Verack => {
914                debug!(?remote_msg, "got verack message from remote peer");
915                break;
916            }
917            _ => {
918                remote_msg = peer_conn
919                    .next()
920                    .await
921                    .ok_or(HandshakeError::ConnectionClosed)??;
922                debug!(?remote_msg, "ignoring non-verack message from remote peer");
923            }
924        }
925    }
926
927    Ok(connection_info)
928}
929
930/// Route a mutually P2P-v2-capable peer into the Zakura handshake path.
931///
932/// After the legacy `version`/`verack` exchange, two mutually capable peers swap
933/// a bounded [`P2pV2Upgrade`] prelude over the legacy TCP stream to learn each
934/// other's Zakura (iroh) node address. The TCP initiator then dials the
935/// responder over QUIC; the responder's iroh router accepts that dial and
936/// registers the peer. On success the caller drops the legacy stream and the
937/// peers continue over Zakura. Any neutral problem (no local endpoint, malformed
938/// or rejected prelude) returns [`ZakuraUpgradeOutcome::Rejected`] with
939/// [`ZakuraRejectReason::TemporaryUnavailable`], so the caller keeps the legacy
940/// connection instead.
941async fn upgrade_to_zakura_handshake<PeerTransport>(
942    peer_conn: &mut Framed<PeerTransport, Codec>,
943    connection_info: &ConnectionInfo,
944    connected_addr: ConnectedAddr,
945    network: &Network,
946    dev_network: Option<&str>,
947    zakura_handshake_connector: Option<ZakuraHandshakeConnector>,
948    address_book_updater: &tokio::sync::mpsc::Sender<MetaAddrChange>,
949) -> Result<ZakuraUpgradeOutcome, HandshakeError>
950where
951    PeerTransport: AsyncRead + AsyncWrite + Unpin + Send + 'static,
952{
953    let Some(connector) = zakura_handshake_connector else {
954        metrics::counter!("zakura.p2p.handshake.upgrade.error").increment(1);
955        return Err(HandshakeError::ZakuraUpgrade(
956            crate::zakura::ZakuraUpgradeError::Unavailable,
957        ));
958    };
959
960    // Handshake routing tests inject a deterministic outcome instead of running
961    // the real prelude exchange over a live Zakura endpoint.
962    #[cfg(test)]
963    if let Some(outcome) = connector.consume_test_outcome() {
964        return Ok(outcome);
965    }
966
967    // Our own iroh dial hints. Without a live Zakura endpoint we cannot upgrade,
968    // so we stay on the legacy connection.
969    let Some((local_node_id, local_direct_addresses)) = connector.local_iroh_hints().await else {
970        return Ok(neutral_upgrade_fallback());
971    };
972
973    let config = ZakuraHandshakeConfig::for_network_with_dev_cohort(network, dev_network);
974    let nonces = ZakuraLegacyNonces {
975        local_zebra_nonce: connection_info.local.nonce,
976        remote_zebra_nonce: connection_info.remote.nonce,
977    };
978
979    // The side that opened the legacy TCP connection initiates the prelude and
980    // dials over QUIC; the accepting side responds and is dialed.
981    let outcome = if connected_addr.is_inbound() {
982        run_responder_upgrade(
983            peer_conn,
984            &connector,
985            &config,
986            nonces,
987            local_node_id,
988            local_direct_addresses,
989        )
990        .await?
991    } else {
992        run_initiator_upgrade(
993            peer_conn,
994            &connector,
995            &config,
996            nonces,
997            local_node_id,
998            local_direct_addresses,
999        )
1000        .await?
1001    };
1002
1003    match &outcome {
1004        ZakuraUpgradeOutcome::Upgraded { peer_id } => {
1005            // The success metric `zakura.p2p.handshake.upgraded` is incremented by
1006            // the supervisor when the dialed/accepted QUIC connection registers.
1007            info!(
1008                ?connected_addr,
1009                ?peer_id,
1010                remote_services = ?connection_info.remote.services,
1011                "upgraded mutually P2P-v2-capable peer to Zakura",
1012            );
1013        }
1014        ZakuraUpgradeOutcome::Duplicate { peer_id } => {
1015            info!(
1016                ?connected_addr,
1017                ?peer_id,
1018                "closing duplicate Zakura peer neutrally"
1019            );
1020            metrics::counter!("zakura.p2p.handshake.duplicate").increment(1);
1021        }
1022        ZakuraUpgradeOutcome::Rejected { reason } => {
1023            debug!(
1024                ?connected_addr,
1025                ?reason,
1026                "Zakura upgrade not completed; continuing on the legacy connection",
1027            );
1028            metrics::counter!(
1029                "zakura.p2p.upgrade.prelude.rejected",
1030                "reason" => format!("{reason:?}"),
1031                "network" => config.network_label(),
1032            )
1033            .increment(1);
1034        }
1035    }
1036
1037    // Keep the upgraded peer's legacy address-book entry live for as long as the
1038    // Zakura connection is registered, so the outbound crawler treats it as
1039    // connected and does not re-dial it (which would re-run this upgrade and
1040    // churn the QUIC connection). Only the outbound side reconnects, and only
1041    // when we have a dialable address book entry for the peer.
1042    if let ZakuraUpgradeOutcome::Upgraded { peer_id }
1043    | ZakuraUpgradeOutcome::Duplicate { peer_id } = &outcome
1044    {
1045        if !connected_addr.is_inbound() {
1046            if let Some(book_addr) = connected_addr.get_address_book_addr() {
1047                connector.spawn_legacy_liveness_keeper(
1048                    peer_id.clone(),
1049                    book_addr,
1050                    address_book_updater.clone(),
1051                );
1052            }
1053        }
1054    }
1055
1056    Ok(outcome)
1057}
1058
1059/// The neutral upgrade fallback outcome: keep the legacy connection.
1060fn neutral_upgrade_fallback() -> ZakuraUpgradeOutcome {
1061    ZakuraUpgradeOutcome::Rejected {
1062        reason: ZakuraRejectReason::TemporaryUnavailable,
1063    }
1064}
1065
1066/// The TCP initiator side of the legacy Zakura upgrade prelude exchange.
1067///
1068/// Sends our [`P2pV2UpgradeInit`], reads the responder's [`P2pV2UpgradeAccept`],
1069/// and dials the responder's advertised Zakura node address over QUIC.
1070async fn run_initiator_upgrade<PeerTransport>(
1071    peer_conn: &mut Framed<PeerTransport, Codec>,
1072    connector: &ZakuraHandshakeConnector,
1073    config: &ZakuraHandshakeConfig,
1074    nonces: ZakuraLegacyNonces,
1075    local_node_id: Vec<u8>,
1076    local_direct_addresses: Vec<Vec<u8>>,
1077) -> Result<ZakuraUpgradeOutcome, HandshakeError>
1078where
1079    PeerTransport: AsyncRead + AsyncWrite + Unpin + Send + 'static,
1080{
1081    let mut upgrade_nonce = [0u8; 32];
1082    OsRng.fill_bytes(&mut upgrade_nonce);
1083
1084    let init = P2pV2UpgradeInit {
1085        magic: PRELUDE_MAGIC,
1086        prelude_version: config.prelude_version,
1087        zakura_protocol_min: config.zakura_protocol_min,
1088        zakura_protocol_max: config.zakura_protocol_max,
1089        network_id: config.network_id,
1090        chain_id: config.chain_id,
1091        capabilities: config.supported_capabilities,
1092        local_zebra_nonce: nonces.local_zebra_nonce,
1093        remote_zebra_nonce: nonces.remote_zebra_nonce,
1094        upgrade_nonce,
1095        iroh_node_id: local_node_id,
1096        iroh_direct_addresses: local_direct_addresses,
1097        iroh_relay_hint: None,
1098        max_control_frame_bytes: config.max_control_frame_bytes,
1099        max_open_streams: config.max_open_streams,
1100    };
1101
1102    let Ok(init_bytes) = P2pV2Upgrade::Init(init.clone()).encode() else {
1103        return Ok(neutral_upgrade_fallback());
1104    };
1105    peer_conn.send(Message::P2pV2Upgrade(init_bytes)).await?;
1106
1107    let Some(P2pV2Upgrade::Accept(accept)) = read_upgrade_prelude(peer_conn).await? else {
1108        // A well-formed reject, an unexpected variant, or no prelude within the
1109        // skip window: keep the legacy connection. A malformed prelude is not
1110        // reached here; `read_upgrade_prelude` disconnects on the first
1111        // malformed upgrade message instead of falling back.
1112        return Ok(neutral_upgrade_fallback());
1113    };
1114
1115    if accept.validate(config, nonces, &init).is_err() {
1116        return Ok(neutral_upgrade_fallback());
1117    }
1118
1119    let Ok(peer_id) = ZakuraPeerId::new(accept.iroh_node_id.clone()) else {
1120        return Ok(neutral_upgrade_fallback());
1121    };
1122
1123    // Dial the responder's Zakura endpoint over QUIC and wait for the local
1124    // supervisor to register a usable outbound handle before dropping the
1125    // legacy connection.
1126    if !connector
1127        .spawn_zakura_dial_to_hints_and_wait(
1128            &peer_id,
1129            &accept.iroh_node_id,
1130            &accept.iroh_direct_addresses,
1131        )
1132        .await
1133    {
1134        return Ok(neutral_upgrade_fallback());
1135    }
1136
1137    Ok(ZakuraUpgradeOutcome::Upgraded { peer_id })
1138}
1139
1140/// The TCP responder side of the legacy Zakura upgrade prelude exchange.
1141///
1142/// Reads the initiator's [`P2pV2UpgradeInit`] and replies with our
1143/// [`P2pV2UpgradeAccept`], advertising our Zakura node address so the initiator
1144/// can dial us. Our iroh router accepts that inbound dial separately.
1145async fn run_responder_upgrade<PeerTransport>(
1146    peer_conn: &mut Framed<PeerTransport, Codec>,
1147    connector: &ZakuraHandshakeConnector,
1148    config: &ZakuraHandshakeConfig,
1149    nonces: ZakuraLegacyNonces,
1150    local_node_id: Vec<u8>,
1151    local_direct_addresses: Vec<Vec<u8>>,
1152) -> Result<ZakuraUpgradeOutcome, HandshakeError>
1153where
1154    PeerTransport: AsyncRead + AsyncWrite + Unpin + Send + 'static,
1155{
1156    // A well-formed unexpected variant or no prelude within the skip window: send
1157    // a neutral reject and keep legacy. A malformed prelude is not reached here;
1158    // `read_upgrade_prelude` disconnects on the first malformed upgrade message
1159    // instead of replying with a neutral reject and falling back.
1160    let Some(P2pV2Upgrade::Init(init)) = read_upgrade_prelude(peer_conn).await? else {
1161        send_upgrade_reject(peer_conn, config).await?;
1162        return Ok(neutral_upgrade_fallback());
1163    };
1164
1165    let selected_zakura_protocol = match init.validate(config, nonces) {
1166        Ok(selected) => selected,
1167        Err(_) => {
1168            send_upgrade_reject(peer_conn, config).await?;
1169            return Ok(neutral_upgrade_fallback());
1170        }
1171    };
1172
1173    let mut responder_upgrade_nonce = [0u8; 32];
1174    OsRng.fill_bytes(&mut responder_upgrade_nonce);
1175
1176    let accept = P2pV2UpgradeAccept {
1177        magic: PRELUDE_MAGIC,
1178        prelude_version: config.prelude_version,
1179        selected_zakura_protocol,
1180        network_id: config.network_id,
1181        chain_id: config.chain_id,
1182        capabilities: config.supported_capabilities,
1183        initiator_upgrade_nonce: init.upgrade_nonce,
1184        responder_upgrade_nonce,
1185        local_zebra_nonce: nonces.local_zebra_nonce,
1186        remote_zebra_nonce: nonces.remote_zebra_nonce,
1187        iroh_node_id: local_node_id,
1188        iroh_direct_addresses: local_direct_addresses,
1189        iroh_relay_hint: None,
1190        max_control_frame_bytes: config.max_control_frame_bytes,
1191        max_open_streams: config.max_open_streams,
1192    };
1193
1194    let Ok(accept_bytes) = P2pV2Upgrade::Accept(accept).encode() else {
1195        return Ok(neutral_upgrade_fallback());
1196    };
1197    peer_conn.send(Message::P2pV2Upgrade(accept_bytes)).await?;
1198
1199    let Ok(peer_id) = ZakuraPeerId::new(init.iroh_node_id.clone()) else {
1200        return Ok(neutral_upgrade_fallback());
1201    };
1202
1203    // The peer dials our advertised Zakura endpoint over QUIC after receiving
1204    // `Accept`, and our iroh router registers that inbound connection
1205    // separately. Wait for that native registration before reporting the
1206    // upgrade: the outer handshake drops the legacy TCP connection on
1207    // `Upgraded`, so without this wait an inbound peer that sends a valid `Init`
1208    // and then never completes the native dial would make us discard a working
1209    // legacy connection with no Zakura replacement. This mirrors the initiator's
1210    // `spawn_zakura_dial_to_hints_and_wait` hand-off wait.
1211    if !connector.wait_for_zakura_registration(&peer_id).await {
1212        return Ok(neutral_upgrade_fallback());
1213    }
1214
1215    Ok(ZakuraUpgradeOutcome::Upgraded { peer_id })
1216}
1217
1218/// Sends a neutral [`P2pV2UpgradeReject`] so the peer stops waiting for an accept
1219/// and falls back to the legacy connection.
1220async fn send_upgrade_reject<PeerTransport>(
1221    peer_conn: &mut Framed<PeerTransport, Codec>,
1222    config: &ZakuraHandshakeConfig,
1223) -> Result<(), HandshakeError>
1224where
1225    PeerTransport: AsyncRead + AsyncWrite + Unpin + Send + 'static,
1226{
1227    let reject = P2pV2UpgradeReject {
1228        magic: PRELUDE_MAGIC,
1229        prelude_version: config.prelude_version,
1230        reason: ZakuraRejectReason::TemporaryUnavailable,
1231    };
1232    if let Ok(reject_bytes) = P2pV2Upgrade::Reject(reject).encode() {
1233        peer_conn.send(Message::P2pV2Upgrade(reject_bytes)).await?;
1234    }
1235    Ok(())
1236}
1237
1238/// Decode a peer-controlled Zakura upgrade prelude.
1239///
1240/// A panic is terminal for this legacy connection: callers propagate the
1241/// returned serialization error and drop the framed transport without reusing
1242/// its parser state.
1243fn decode_upgrade_prelude(payload: &[u8]) -> Result<P2pV2Upgrade, HandshakeError> {
1244    decode_upgrade_prelude_with(|| P2pV2Upgrade::decode(payload))
1245}
1246
1247/// Run the structured upgrade prelude decoder inside its panic boundary.
1248fn decode_upgrade_prelude_with(
1249    decode: impl FnOnce() -> Result<P2pV2Upgrade, ZakuraProtocolError> + panic::UnwindSafe,
1250) -> Result<P2pV2Upgrade, HandshakeError> {
1251    match panic::catch_unwind(decode) {
1252        Ok(Ok(prelude)) => Ok(prelude),
1253        Ok(Err(error)) => {
1254            metrics::counter!("zakura.p2p.upgrade.prelude.malformed").increment(1);
1255            Err(HandshakeError::ZakuraUpgradePreludeMalformed(error))
1256        }
1257        Err(_panic_payload) => {
1258            metrics::counter!(
1259                "peer.message.parse.panics",
1260                "parser" => "upgrade_prelude",
1261            )
1262            .increment(1);
1263            tracing::error!(
1264                command = P2P_V2_UPGRADE_COMMAND,
1265                "peer-controlled message parser panicked; disconnecting legacy peer"
1266            );
1267            Err(SerializationError::Parse("Zakura P2P v2 upgrade prelude parser panicked").into())
1268        }
1269    }
1270}
1271
1272/// Reads the next legacy [`P2pV2Upgrade`] prelude from the peer.
1273///
1274/// Skips a small bounded number of unrelated messages (the overall handshake
1275/// timeout also applies), so a peer cannot stall the upgrade by streaming other
1276/// messages.
1277///
1278/// Returns `Ok(None)` only when no prelude arrives within the skip bound: a peer
1279/// that advertised `NODE_P2P_V2` but never frames a `p2pv2up` message is treated
1280/// as a neutral legacy fallback (compatibility).
1281///
1282/// In contrast, a peer that *does* frame a `p2pv2up` message whose payload fails
1283/// to decode has violated the upgrade protocol, so this returns
1284/// [`HandshakeError::ZakuraUpgradePreludeMalformed`] rather than erasing the
1285/// decode error to `None`. Erasing it would let a peer force a downgrade to
1286/// legacy by sending malformed upgrade bytes (SR-7 fail-closed); surfacing it
1287/// disconnects the peer on the first malformed upgrade message.
1288async fn read_upgrade_prelude<PeerTransport>(
1289    peer_conn: &mut Framed<PeerTransport, Codec>,
1290) -> Result<Option<P2pV2Upgrade>, HandshakeError>
1291where
1292    PeerTransport: AsyncRead + AsyncWrite + Unpin + Send + 'static,
1293{
1294    // Bound on unrelated messages tolerated before the prelude.
1295    const MAX_SKIPPED_MESSAGES: usize = 4;
1296
1297    for _ in 0..MAX_SKIPPED_MESSAGES {
1298        let message = peer_conn
1299            .next()
1300            .await
1301            .ok_or(HandshakeError::ConnectionClosed)??;
1302        if let Message::P2pV2Upgrade(payload) = message {
1303            return decode_upgrade_prelude(&payload).map(Some);
1304        }
1305    }
1306
1307    Ok(None)
1308}
1309
1310/// A handshake request.
1311/// Contains the information needed to handshake with the peer.
1312pub struct HandshakeRequest<PeerTransport>
1313where
1314    PeerTransport: AsyncRead + AsyncWrite + Unpin + Send + 'static,
1315{
1316    /// The tokio [`TcpStream`](tokio::net::TcpStream) or Tor
1317    /// `arti_client::DataStream` to the peer.
1318    // Use [`arti_client::DataStream`] when #5492 is done.
1319    pub data_stream: PeerTransport,
1320
1321    /// The address of the peer, and other related information.
1322    pub connected_addr: ConnectedAddr,
1323
1324    /// A connection tracker that reduces the open connection count when dropped.
1325    ///
1326    /// Used to limit the number of open connections in Zebra.
1327    pub connection_tracker: ConnectionTracker,
1328}
1329
1330impl<S, PeerTransport, C> Service<HandshakeRequest<PeerTransport>> for Handshake<S, C>
1331where
1332    S: Service<Request, Response = Response, Error = BoxError> + Clone + Send + 'static,
1333    S::Future: Send,
1334    C: ChainTip + Clone + Send + 'static,
1335    PeerTransport: AsyncRead + AsyncWrite + Unpin + Send + 'static,
1336{
1337    type Response = Client;
1338    type Error = BoxError;
1339    type Future =
1340        Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send + 'static>>;
1341
1342    fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
1343        Poll::Ready(Ok(()))
1344    }
1345
1346    fn call(&mut self, req: HandshakeRequest<PeerTransport>) -> Self::Future {
1347        let HandshakeRequest {
1348            data_stream,
1349            connected_addr,
1350            connection_tracker,
1351        } = req;
1352
1353        let negotiator_span = debug_span!("negotiator", peer = ?connected_addr);
1354        // set the peer connection span's parent to the global span, as it
1355        // should exist independently of its creation source (inbound
1356        // connection, crawler, initial peer, ...)
1357        let connection_span =
1358            span!(parent: &self.parent_span, Level::INFO, "", peer = ?connected_addr);
1359
1360        // Clone these upfront, so they can be moved into the future.
1361        let nonces = self.nonces.clone();
1362        let inbound_service = self.inbound_service.clone();
1363        let address_book_updater = self.address_book_updater.clone();
1364        let inv_collector = self.inv_collector.clone();
1365        let config = self.config.clone();
1366        let user_agent = self.user_agent.clone();
1367        let our_services = self.our_services;
1368        let relay = self.relay;
1369        let minimum_peer_version = self.minimum_peer_version.clone();
1370        let zakura_handshake_connector = self.zakura_handshake_connector.clone();
1371
1372        // # Security
1373        //
1374        // `zakura_network::init()` implements a connection timeout on this future.
1375        // Any code outside this future does not have a timeout.
1376        let fut = async move {
1377            debug!(
1378                addr = ?connected_addr,
1379                "negotiating protocol version with remote peer"
1380            );
1381
1382            // Start timing the handshake for metrics
1383            let handshake_start = Instant::now();
1384
1385            let mut peer_conn = Framed::new(
1386                data_stream,
1387                Codec::builder()
1388                    .for_network(&config.network)
1389                    .with_metrics_addr_label(connected_addr.get_transient_addr_label())
1390                    .finish(),
1391            );
1392            let mut connection_tracker = connection_tracker;
1393
1394            let connection_info = match negotiate_version(
1395                &mut peer_conn,
1396                &connected_addr,
1397                config.clone(),
1398                nonces,
1399                user_agent,
1400                our_services,
1401                relay,
1402                minimum_peer_version,
1403            )
1404            .await
1405            {
1406                Ok(info) => {
1407                    // Record successful handshake duration
1408                    let duration = handshake_start.elapsed().as_secs_f64();
1409                    metrics::histogram!(
1410                        "zcash.net.peer.handshake.duration_seconds",
1411                        "result" => "success"
1412                    )
1413                    .record(duration);
1414                    info
1415                }
1416                Err(err) => {
1417                    // Record failed handshake duration and failure reason
1418                    let duration = handshake_start.elapsed().as_secs_f64();
1419                    let reason = match &err {
1420                        HandshakeError::UnexpectedMessage(_) => "unexpected_message",
1421                        HandshakeError::RemoteNonceReuse => "nonce_reuse",
1422                        HandshakeError::LocalDuplicateNonce => "duplicate_nonce",
1423                        HandshakeError::ConnectionClosed => "connection_closed",
1424                        HandshakeError::Io(_) => "io_error",
1425                        HandshakeError::Serialization(_) => "serialization",
1426                        HandshakeError::ObsoleteVersion(_) => "obsolete_version",
1427                        HandshakeError::Timeout => "timeout",
1428                        HandshakeError::ZakuraUpgradeSelected
1429                        | HandshakeError::ZakuraUpgrade(_)
1430                        | HandshakeError::ZakuraUpgradePreludeMalformed(_) => {
1431                            unreachable!("negotiate_version returns before Zakura upgrade routing")
1432                        }
1433                    };
1434                    metrics::histogram!(
1435                        "zcash.net.peer.handshake.duration_seconds",
1436                        "result" => "failure"
1437                    )
1438                    .record(duration);
1439                    metrics::counter!(
1440                        "zcash.net.peer.handshake.failures.total",
1441                        "reason" => reason
1442                    )
1443                    .increment(1);
1444                    return Err(err);
1445                }
1446            };
1447
1448            let remote_services = connection_info.remote.services;
1449
1450            if should_attempt_zakura_upgrade(&config, &connection_info) {
1451                match upgrade_to_zakura_handshake(
1452                    &mut peer_conn,
1453                    &connection_info,
1454                    connected_addr,
1455                    &config.network,
1456                    config.zakura.dev_network.as_deref(),
1457                    zakura_handshake_connector,
1458                    &address_book_updater,
1459                )
1460                .await
1461                {
1462                    Ok(
1463                        ZakuraUpgradeOutcome::Upgraded { .. }
1464                        | ZakuraUpgradeOutcome::Duplicate { .. },
1465                    ) => {
1466                        // Returning here drops the legacy stream and connection tracker, cleanly
1467                        // closing the Zebra path and releasing the connection limit exactly once.
1468                        return Err(HandshakeError::ZakuraUpgradeSelected);
1469                    }
1470                    Ok(ZakuraUpgradeOutcome::Rejected {
1471                        reason: ZakuraRejectReason::TemporaryUnavailable,
1472                    }) => {
1473                        debug!(
1474                            ?connected_addr,
1475                            "Zakura upgrade is temporarily unavailable; continuing legacy handshake"
1476                        );
1477                    }
1478                    Ok(ZakuraUpgradeOutcome::Rejected { .. }) => {
1479                        // Returning here drops the legacy stream and connection tracker, cleanly
1480                        // closing the Zebra path and releasing the connection limit exactly once.
1481                        return Err(HandshakeError::ZakuraUpgradeSelected);
1482                    }
1483                    Err(error) => return Err(error),
1484                }
1485            }
1486
1487            // The handshake succeeded: update the peer status from AttemptPending to Responded,
1488            // send initial connection info, and update the active connection counter.
1489            connection_tracker.mark_open();
1490            if let Some(book_addr) = connected_addr.get_address_book_addr() {
1491                // the collector doesn't depend on network activity,
1492                // so this await should not hang
1493                let _ = address_book_updater
1494                    .send(MetaAddr::new_connected(
1495                        book_addr,
1496                        &remote_services,
1497                        connected_addr.is_inbound(),
1498                    ))
1499                    .await;
1500            }
1501
1502            // Reconfigure the codec to use the negotiated version.
1503            //
1504            // TODO: The tokio documentation says not to do this while any frames are still being processed.
1505            // Since we don't know that here, another way might be to release the tcp
1506            // stream from the unversioned Framed wrapper and construct a new one with a versioned codec.
1507            let bare_codec = peer_conn.codec_mut();
1508            bare_codec.reconfigure_version(connection_info.negotiated_version);
1509            bare_codec.reconfigure_full_body_len();
1510
1511            debug!("constructing client, spawning server");
1512
1513            // These channels communicate between the inbound and outbound halves of the connection,
1514            // and between the different connection tasks. We create separate tasks and channels
1515            // for each new connection.
1516            let (server_tx, server_rx) = futures::channel::mpsc::channel(0);
1517            let (shutdown_tx, shutdown_rx) = oneshot::channel();
1518            let error_slot = ErrorSlot::default();
1519
1520            let (peer_tx, peer_rx) = peer_conn.split();
1521
1522            // Instrument the peer's rx and tx streams.
1523
1524            let inner_conn_span = connection_span.clone();
1525            let peer_tx = peer_tx.with(move |msg: Message| {
1526                let span = debug_span!(parent: inner_conn_span.clone(), "outbound_metric");
1527                // Add a metric for outbound messages.
1528                metrics::counter!(
1529                    "zcash.net.out.messages",
1530                    "command" => msg.command(),
1531                    "addr" => connected_addr.get_transient_addr_label(),
1532                )
1533                .increment(1);
1534                // We need to use future::ready rather than an async block here,
1535                // because we need the sink to be Unpin, and the With<Fut, ...>
1536                // returned by .with is Unpin only if Fut is Unpin, and the
1537                // futures generated by async blocks are not Unpin.
1538                future::ready(Ok(msg)).instrument(span)
1539            });
1540
1541            // CORRECTNESS
1542            //
1543            // Ping/Pong messages and every error must update the peer address state via
1544            // the inbound_ts_collector.
1545            //
1546            // The heartbeat task sends regular Ping/Pong messages,
1547            // and it ends the connection if the heartbeat times out.
1548            // So we can just track peer activity based on Ping and Pong.
1549            // (This significantly improves performance, by reducing time system calls.)
1550            let inbound_ts_collector = address_book_updater.clone();
1551            let inbound_inv_collector = inv_collector.clone();
1552            let ts_inner_conn_span = connection_span.clone();
1553            let inv_inner_conn_span = connection_span.clone();
1554            let peer_rx = peer_rx
1555                .then(move |msg| {
1556                    // Add a metric for inbound messages and errors.
1557                    // Fire a timestamp or failure event.
1558                    let inbound_ts_collector = inbound_ts_collector.clone();
1559                    let span =
1560                        debug_span!(parent: ts_inner_conn_span.clone(), "inbound_ts_collector");
1561
1562                    async move {
1563                        match &msg {
1564                            Ok(msg) => {
1565                                metrics::counter!(
1566                                    "zcash.net.in.messages",
1567                                    "command" => msg.command(),
1568                                    "addr" => connected_addr.get_transient_addr_label(),
1569                                )
1570                                .increment(1);
1571
1572                                // # Security
1573                                //
1574                                // Peer messages are not rate-limited, so we can't send anything
1575                                // to a shared channel or do anything expensive here.
1576                            }
1577                            Err(err) => {
1578                                metrics::counter!(
1579                                    "zakura.net.in.errors",
1580                                    "error" => err.to_string(),
1581                                    "addr" => connected_addr.get_transient_addr_label(),
1582                                )
1583                                .increment(1);
1584
1585                                // # Security
1586                                //
1587                                // Peer errors are rate-limited because:
1588                                // - opening connections is rate-limited
1589                                // - the number of connections is limited
1590                                // - after the first error, the peer is disconnected
1591                                if let Some(book_addr) = connected_addr.get_address_book_addr() {
1592                                    let _ = inbound_ts_collector
1593                                        .send(MetaAddr::new_errored(book_addr, remote_services))
1594                                        .await;
1595                                }
1596                            }
1597                        }
1598                        msg
1599                    }
1600                    .instrument(span)
1601                })
1602                .then(move |msg| {
1603                    let inbound_inv_collector = inbound_inv_collector.clone();
1604                    let span = debug_span!(parent: inv_inner_conn_span.clone(), "inventory_filter");
1605                    register_inventory_status(msg, connected_addr, inbound_inv_collector)
1606                        .instrument(span)
1607                })
1608                .boxed();
1609
1610            // If we've learned potential peer addresses from the inbound connection remote address
1611            // or the handshake version message, add those addresses to the peer cache for this
1612            // peer.
1613            //
1614            // # Security
1615            //
1616            // We can't add these alternate addresses directly to the address book. If we did,
1617            // malicious peers could interfere with the address book state of other peers by
1618            // providing their addresses in `Version` messages. Or they could fill the address book
1619            // with fake addresses.
1620            //
1621            // These peer addresses are rate-limited because:
1622            // - opening connections is rate-limited
1623            // - these addresses are put in the peer address cache
1624            // - the peer address cache is only used when Zebra requests addresses from that peer
1625            let remote_canonical_addr = connection_info.remote.address_from.addr();
1626            let alternate_addrs = connected_addr
1627                .get_alternate_addrs(remote_canonical_addr)
1628                .map(|addr| {
1629                    // Assume the connecting node is a server node, and it's available now.
1630                    MetaAddr::new_gossiped_meta_addr(
1631                        addr,
1632                        PeerServices::NODE_NETWORK,
1633                        DateTime32::now(),
1634                    )
1635                });
1636
1637            let server = Connection::new(
1638                inbound_service,
1639                server_rx,
1640                error_slot.clone(),
1641                peer_tx,
1642                connection_tracker,
1643                connection_info.clone(),
1644                alternate_addrs.collect(),
1645            );
1646
1647            let connection_task = tokio::spawn(
1648                server
1649                    .run(peer_rx)
1650                    .instrument(connection_span.clone())
1651                    .boxed(),
1652            );
1653
1654            let heartbeat_task = tokio::spawn(
1655                send_periodic_heartbeats_with_shutdown_handle(
1656                    connected_addr,
1657                    shutdown_rx,
1658                    server_tx.clone(),
1659                    address_book_updater.clone(),
1660                )
1661                .instrument(tracing::debug_span!(parent: connection_span, "heartbeat"))
1662                .boxed(),
1663            );
1664
1665            let client = Client {
1666                connection_info,
1667                shutdown_tx: Some(shutdown_tx),
1668                server_tx,
1669                inv_collector,
1670                error_slot,
1671                connection_task,
1672                heartbeat_task,
1673            };
1674
1675            Ok(client)
1676        };
1677
1678        // Correctness: As a defence-in-depth against hangs, wrap the entire handshake in a timeout.
1679        let fut = timeout(constants::HANDSHAKE_TIMEOUT, fut);
1680
1681        // Spawn a new task to drive this handshake, forwarding panics to the calling task.
1682        tokio::spawn(fut.instrument(negotiator_span))
1683            .map(
1684                |join_result: Result<
1685                    Result<Result<Client, HandshakeError>, error::Elapsed>,
1686                    JoinError,
1687                >| {
1688                    match join_result {
1689                        Ok(Ok(Ok(connection_client))) => Ok(connection_client),
1690                        Ok(Ok(Err(handshake_error))) => Err(handshake_error.into()),
1691                        Ok(Err(timeout_error)) => Err(timeout_error.into()),
1692                        Err(join_error) => match join_error.try_into_panic() {
1693                            // Forward panics to the calling task
1694                            Ok(panic_reason) => panic::resume_unwind(panic_reason),
1695                            Err(join_error) => Err(join_error.into()),
1696                        },
1697                    }
1698                },
1699            )
1700            .boxed()
1701    }
1702}
1703
1704/// Register any advertised or missing inventory in `msg` for `connected_addr`.
1705pub(crate) async fn register_inventory_status(
1706    msg: Result<Message, SerializationError>,
1707    connected_addr: ConnectedAddr,
1708    inv_collector: broadcast::Sender<InventoryChange>,
1709) -> Result<Message, SerializationError> {
1710    match (&msg, connected_addr.get_transient_addr()) {
1711        (Ok(Message::Inv(advertised)), Some(transient_addr)) => {
1712            // We ignore inventory messages with more than one
1713            // block, because they are most likely replies to a
1714            // query, rather than a newly gossiped block.
1715            //
1716            // (We process inventory messages with any number of
1717            // transactions.)
1718            //
1719            // https://zebra.zfnd.org/dev/rfcs/0003-inventory-tracking.html#inventory-monitoring
1720            //
1721            // Note: zcashd has a bug where it merges queued inv messages of
1722            // the same or different types. Zebra compensates by sending `notfound`
1723            // responses to the inv collector. (#2156, #1768)
1724            //
1725            // (We can't split `inv`s, because that fills the inventory registry
1726            // with useless entries that the whole network has, making it large and slow.)
1727            match advertised.as_slice() {
1728                [advertised @ InventoryHash::Block(_)] => {
1729                    debug!(
1730                        ?advertised,
1731                        "registering gossiped advertised block inventory for peer"
1732                    );
1733
1734                    // The peer set and inv collector use the peer's remote
1735                    // address as an identifier
1736                    // If all receivers have been dropped, `send` returns an error.
1737                    // When that happens, Zebra is shutting down, so we want to ignore this error.
1738                    let _ = inv_collector
1739                        .send(InventoryChange::new_available(*advertised, transient_addr));
1740                }
1741                advertised => {
1742                    let advertised = advertised
1743                        .iter()
1744                        .filter(|advertised| advertised.unmined_tx_id().is_some());
1745
1746                    debug!(
1747                        ?advertised,
1748                        "registering advertised unmined transaction inventory for peer",
1749                    );
1750
1751                    if let Some(change) =
1752                        InventoryChange::new_available_multi(advertised, transient_addr)
1753                    {
1754                        // Ignore channel errors that should only happen during shutdown.
1755                        let _ = inv_collector.send(change);
1756                    }
1757                }
1758            }
1759        }
1760
1761        (Ok(Message::NotFound(missing)), Some(transient_addr)) => {
1762            // Ignore Errors and the unsupported FilteredBlock type
1763            let missing = missing.iter().filter(|missing| {
1764                missing.unmined_tx_id().is_some() || missing.block_hash().is_some()
1765            });
1766
1767            debug!(?missing, "registering missing inventory for peer");
1768
1769            if let Some(change) = InventoryChange::new_missing_multi(missing, transient_addr) {
1770                let _ = inv_collector.send(change);
1771            }
1772        }
1773        _ => {}
1774    }
1775
1776    msg
1777}
1778
1779/// Send periodical heartbeats to `server_tx`, and update the peer status through
1780/// `heartbeat_ts_collector`.
1781///
1782/// # Correctness
1783///
1784/// To prevent hangs:
1785/// - every await that depends on the network must have a timeout (or interval)
1786/// - every error/shutdown must update the address book state and return
1787///
1788/// The address book state can be updated via `ClientRequest.tx`, or the
1789/// heartbeat_ts_collector.
1790///
1791/// Returning from this function terminates the connection's heartbeat task.
1792async fn send_periodic_heartbeats_with_shutdown_handle(
1793    connected_addr: ConnectedAddr,
1794    shutdown_rx: oneshot::Receiver<CancelHeartbeatTask>,
1795    server_tx: futures::channel::mpsc::Sender<ClientRequest>,
1796    heartbeat_ts_collector: tokio::sync::mpsc::Sender<MetaAddrChange>,
1797) -> Result<(), BoxError> {
1798    use futures::future::Either;
1799
1800    let heartbeat_run_loop = send_periodic_heartbeats_run_loop(
1801        connected_addr,
1802        server_tx,
1803        heartbeat_ts_collector.clone(),
1804    );
1805
1806    pin_mut!(shutdown_rx);
1807    pin_mut!(heartbeat_run_loop);
1808
1809    // CORRECTNESS
1810    //
1811    // Currently, select prefers the first future if multiple
1812    // futures are ready.
1813    //
1814    // Starvation is impossible here, because interval has a
1815    // slow rate, and shutdown is a oneshot. If both futures
1816    // are ready, we want the shutdown to take priority over
1817    // sending a useless heartbeat.
1818    match future::select(shutdown_rx, heartbeat_run_loop).await {
1819        Either::Left((Ok(CancelHeartbeatTask), _unused_run_loop)) => {
1820            tracing::trace!("shutting down because Client requested shut down");
1821            handle_heartbeat_shutdown(
1822                PeerError::ClientCancelledHeartbeatTask,
1823                &heartbeat_ts_collector,
1824                &connected_addr,
1825            )
1826            .await
1827        }
1828        Either::Left((Err(oneshot::Canceled), _unused_run_loop)) => {
1829            tracing::trace!("shutting down because Client was dropped");
1830            handle_heartbeat_shutdown(
1831                PeerError::ClientDropped,
1832                &heartbeat_ts_collector,
1833                &connected_addr,
1834            )
1835            .await
1836        }
1837        Either::Right((result, _unused_shutdown)) => {
1838            tracing::trace!("shutting down due to heartbeat failure");
1839            // heartbeat_timeout() already send an error on the timestamp collector channel
1840
1841            result
1842        }
1843    }
1844}
1845
1846/// Send periodical heartbeats to `server_tx`, and update the peer status through
1847/// `heartbeat_ts_collector`.
1848///
1849/// See `send_periodic_heartbeats_with_shutdown_handle` for details.
1850async fn send_periodic_heartbeats_run_loop(
1851    connected_addr: ConnectedAddr,
1852    mut server_tx: futures::channel::mpsc::Sender<ClientRequest>,
1853    heartbeat_ts_collector: tokio::sync::mpsc::Sender<MetaAddrChange>,
1854) -> Result<(), BoxError> {
1855    // Don't send the first heartbeat immediately - we've just completed the handshake!
1856    let mut interval = tokio::time::interval_at(
1857        Instant::now() + constants::HEARTBEAT_INTERVAL,
1858        constants::HEARTBEAT_INTERVAL,
1859    );
1860    // If the heartbeat is delayed, also delay all future heartbeats.
1861    // (Shorter heartbeat intervals just add load, without any benefit.)
1862    interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay);
1863
1864    let mut interval_stream = IntervalStream::new(interval);
1865
1866    while let Some(_instant) = interval_stream.next().await {
1867        // We've reached another heartbeat interval without
1868        // shutting down, so do a heartbeat request.
1869        let ping_sent_at = Instant::now();
1870        if let Some(book_addr) = connected_addr.get_address_book_addr() {
1871            let _ = heartbeat_ts_collector
1872                .send(MetaAddr::new_ping_sent(book_addr, ping_sent_at.into()))
1873                .await;
1874        }
1875
1876        let heartbeat = send_one_heartbeat(&mut server_tx);
1877        let rtt = heartbeat_timeout(heartbeat, &heartbeat_ts_collector, &connected_addr).await?;
1878
1879        // # Security
1880        //
1881        // Peer heartbeats are rate-limited because:
1882        // - opening connections is rate-limited
1883        // - the number of connections is limited
1884        // - Zebra initiates each heartbeat using a timer
1885        if let Some(book_addr) = connected_addr.get_address_book_addr() {
1886            if let Some(rtt) = rtt {
1887                // the collector doesn't depend on network activity,
1888                // so this await should not hang
1889                let _ = heartbeat_ts_collector
1890                    .send(MetaAddr::new_responded(book_addr, Some(rtt)))
1891                    .await;
1892            }
1893        }
1894    }
1895
1896    unreachable!("unexpected IntervalStream termination")
1897}
1898
1899/// Send one heartbeat using `server_tx`.
1900async fn send_one_heartbeat(
1901    server_tx: &mut futures::channel::mpsc::Sender<ClientRequest>,
1902) -> Result<Response, BoxError> {
1903    // We just reached a heartbeat interval, so start sending
1904    // a heartbeat.
1905    let (tx, rx) = oneshot::channel();
1906
1907    // Try to send the heartbeat request
1908    let request = Request::Ping(Nonce::default());
1909    tracing::trace!(?request, "queueing heartbeat request");
1910    match server_tx.try_send(ClientRequest {
1911        request,
1912        tx,
1913        // we're not requesting inventory, so we don't need to update the registry
1914        inv_collector: None,
1915        transient_addr: None,
1916        span: tracing::Span::current(),
1917    }) {
1918        Ok(()) => {}
1919        Err(e) => {
1920            if e.is_disconnected() {
1921                Err(PeerError::ConnectionClosed)?;
1922            } else if e.is_full() {
1923                // Send the message when the Client becomes ready.
1924                // If sending takes too long, the heartbeat timeout will elapse
1925                // and close the connection, reducing our load to busy peers.
1926                server_tx.send(e.into_inner()).await?;
1927            } else {
1928                // we need to map unexpected error types to PeerErrors
1929                warn!(?e, "unexpected try_send error");
1930                Err(e)?;
1931            };
1932        }
1933    }
1934
1935    // Flush the heartbeat request from the queue
1936    server_tx.flush().await?;
1937    tracing::trace!("sent heartbeat request");
1938
1939    // Heartbeats are checked internally to the
1940    // connection logic, but we need to wait on the
1941    // response to avoid canceling the request.
1942    let response = rx.await??;
1943    tracing::trace!(?response, "got heartbeat response");
1944
1945    Ok(response)
1946}
1947
1948/// Wrap `fut` in a timeout, handing any inner or outer errors using
1949/// `handle_heartbeat_error`.
1950async fn heartbeat_timeout(
1951    fut: impl Future<Output = Result<Response, BoxError>>,
1952    address_book_updater: &tokio::sync::mpsc::Sender<MetaAddrChange>,
1953    connected_addr: &ConnectedAddr,
1954) -> Result<Option<Duration>, BoxError> {
1955    let response = match timeout(constants::HEARTBEAT_INTERVAL, fut).await {
1956        Ok(inner_result) => {
1957            handle_heartbeat_error(inner_result, address_book_updater, connected_addr).await?
1958        }
1959        Err(elapsed) => {
1960            handle_heartbeat_error(Err(elapsed), address_book_updater, connected_addr).await?
1961        }
1962    };
1963
1964    let rtt = match response {
1965        Response::Pong(rtt) => Some(rtt),
1966        _ => None,
1967    };
1968
1969    Ok(rtt)
1970}
1971
1972/// If `result.is_err()`, mark `connected_addr` as failed using `address_book_updater`.
1973async fn handle_heartbeat_error<T, E>(
1974    result: Result<T, E>,
1975    address_book_updater: &tokio::sync::mpsc::Sender<MetaAddrChange>,
1976    connected_addr: &ConnectedAddr,
1977) -> Result<T, E>
1978where
1979    E: std::fmt::Debug,
1980{
1981    match result {
1982        Ok(t) => Ok(t),
1983        Err(err) => {
1984            tracing::debug!(?err, "heartbeat error, shutting down");
1985
1986            // # Security
1987            //
1988            // Peer errors and shutdowns are rate-limited because:
1989            // - opening connections is rate-limited
1990            // - the number of connections is limited
1991            // - after the first error or shutdown, the peer is disconnected
1992            if let Some(book_addr) = connected_addr.get_address_book_addr() {
1993                let _ = address_book_updater
1994                    .send(MetaAddr::new_errored(book_addr, None))
1995                    .await;
1996            }
1997            Err(err)
1998        }
1999    }
2000}
2001
2002/// Mark `connected_addr` as shut down using `address_book_updater`.
2003async fn handle_heartbeat_shutdown(
2004    peer_error: PeerError,
2005    address_book_updater: &tokio::sync::mpsc::Sender<MetaAddrChange>,
2006    connected_addr: &ConnectedAddr,
2007) -> Result<(), BoxError> {
2008    tracing::debug!(?peer_error, "client shutdown, shutting down heartbeat");
2009
2010    if let Some(book_addr) = connected_addr.get_address_book_addr() {
2011        let _ = address_book_updater
2012            .send(MetaAddr::new_shutdown(book_addr))
2013            .await;
2014    }
2015
2016    Err(peer_error.into())
2017}