1use 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
63pub 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 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#[derive(Clone, Debug, PartialEq, Eq)]
140pub struct ConnectionInfo {
141 pub connected_addr: ConnectedAddr,
147
148 pub remote: VersionMessage,
150
151 pub local: VersionMessage,
153
154 pub negotiated_version: Version,
159}
160
161#[derive(Copy, Clone, PartialEq, Eq)]
166pub enum ConnectedAddr {
167 OutboundDirect {
172 addr: PeerSocketAddr,
174 },
175
176 InboundDirect {
183 addr: PeerSocketAddr,
187 },
188
189 OutboundProxy {
195 proxy_addr: SocketAddr,
197
198 transient_local_addr: SocketAddr,
200 },
201
202 InboundProxy {
208 transient_addr: SocketAddr,
210 },
211
212 Isolated,
214 }
217
218pub fn get_unspecified_ipv4_addr(network: Network) -> SocketAddr {
220 (Ipv4Addr::UNSPECIFIED, network.default_port()).into()
221}
222
223use ConnectedAddr::*;
224
225impl ConnectedAddr {
226 pub fn new_outbound_direct(addr: PeerSocketAddr) -> ConnectedAddr {
228 OutboundDirect { addr }
229 }
230
231 pub fn new_inbound_direct(addr: PeerSocketAddr) -> ConnectedAddr {
233 InboundDirect { addr }
234 }
235
236 #[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 #[allow(unused)]
252 pub fn new_inbound_proxy(proxy: SocketAddr) -> ConnectedAddr {
253 InboundProxy {
254 transient_addr: proxy,
255 }
256 }
257
258 pub fn new_isolated() -> ConnectedAddr {
260 Isolated
261 }
262
263 pub fn get_address_book_addr(&self) -> Option<PeerSocketAddr> {
282 match self {
283 OutboundDirect { addr } | InboundDirect { addr } => Some(*addr),
284 OutboundProxy { .. } | InboundProxy { .. } | Isolated => None,
287 }
288 }
289
290 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 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 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 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 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 if &canonical_remote != addr {
361 vec![canonical_remote]
362 } else {
363 Vec::new()
366 }
367 }
368
369 InboundDirect { addr } => {
370 let maybe_addr = SocketAddr::new(addr.ip(), canonical_remote.port()).into();
372
373 if canonical_remote != maybe_addr {
375 vec![canonical_remote, maybe_addr]
376 } else {
377 vec![canonical_remote]
378 }
379 }
380
381 OutboundProxy { .. } | InboundProxy { .. } => vec![canonical_remote],
384
385 Isolated => Vec::new(),
387 };
388
389 addrs.into_iter()
390 }
391
392 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
411pub 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 pub fn with_config(mut self, config: Config) -> Self {
438 self.config = Some(config);
439 self
440 }
441
442 pub fn with_inbound_service(mut self, inbound_service: S) -> Self {
444 self.inbound_service = Some(inbound_service);
445 self
446 }
447
448 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 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 pub fn with_advertised_services(mut self, services: PeerServices) -> Self {
476 self.our_services = Some(services);
477 self
478 }
479
480 pub fn with_user_agent(mut self, user_agent: String) -> Self {
484 self.user_agent = Some(user_agent);
485 self
486 }
487
488 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 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 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 pub fn want_transactions(mut self, relay: bool) -> Self {
526 self.relay = Some(relay);
527 self
528 }
529
530 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 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 pub fn builder() -> Builder<S, NoChainTip> {
581 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
598fn 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
609fn 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 format!("/{trimmed_user_agent}/")
627 } else {
628 format!("/{zakura_token}/{trimmed_user_agent}/")
629 }
630}
631
632fn 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#[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 let local_nonce = Nonce::default();
665
666 {
675 let mut locked_nonces = nonces.lock().await;
676
677 let is_unique_nonce = locked_nonces.insert(local_nonce);
680 if !is_unique_nonce {
681 return Err(HandshakeError::LocalDuplicateNonce);
682 }
683
684 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 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 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 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 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 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 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 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 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 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 return Err(HandshakeError::ObsoleteVersion(remote.version));
864 }
865
866 let negotiated_version = min(constants::CURRENT_NETWORK_PROTOCOL_VERSION, remote.version);
867
868 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 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 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 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
930async 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 #[cfg(test)]
963 if let Some(outcome) = connector.consume_test_outcome() {
964 return Ok(outcome);
965 }
966
967 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 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 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 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
1059fn neutral_upgrade_fallback() -> ZakuraUpgradeOutcome {
1061 ZakuraUpgradeOutcome::Rejected {
1062 reason: ZakuraRejectReason::TemporaryUnavailable,
1063 }
1064}
1065
1066async 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 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 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
1140async 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 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 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
1218async 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
1238fn decode_upgrade_prelude(payload: &[u8]) -> Result<P2pV2Upgrade, HandshakeError> {
1244 decode_upgrade_prelude_with(|| P2pV2Upgrade::decode(payload))
1245}
1246
1247fn 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
1272async 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 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
1310pub struct HandshakeRequest<PeerTransport>
1313where
1314 PeerTransport: AsyncRead + AsyncWrite + Unpin + Send + 'static,
1315{
1316 pub data_stream: PeerTransport,
1320
1321 pub connected_addr: ConnectedAddr,
1323
1324 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 let connection_span =
1358 span!(parent: &self.parent_span, Level::INFO, "", peer = ?connected_addr);
1359
1360 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 let fut = async move {
1377 debug!(
1378 addr = ?connected_addr,
1379 "negotiating protocol version with remote peer"
1380 );
1381
1382 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 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 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 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 return Err(HandshakeError::ZakuraUpgradeSelected);
1482 }
1483 Err(error) => return Err(error),
1484 }
1485 }
1486
1487 connection_tracker.mark_open();
1490 if let Some(book_addr) = connected_addr.get_address_book_addr() {
1491 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 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 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 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 metrics::counter!(
1529 "zcash.net.out.messages",
1530 "command" => msg.command(),
1531 "addr" => connected_addr.get_transient_addr_label(),
1532 )
1533 .increment(1);
1534 future::ready(Ok(msg)).instrument(span)
1539 });
1540
1541 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 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 }
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 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 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 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 let fut = timeout(constants::HANDSHAKE_TIMEOUT, fut);
1680
1681 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 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
1704pub(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 match advertised.as_slice() {
1728 [advertised @ InventoryHash::Block(_)] => {
1729 debug!(
1730 ?advertised,
1731 "registering gossiped advertised block inventory for peer"
1732 );
1733
1734 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 let _ = inv_collector.send(change);
1756 }
1757 }
1758 }
1759 }
1760
1761 (Ok(Message::NotFound(missing)), Some(transient_addr)) => {
1762 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
1779async 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 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 result
1842 }
1843 }
1844}
1845
1846async 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 let mut interval = tokio::time::interval_at(
1857 Instant::now() + constants::HEARTBEAT_INTERVAL,
1858 constants::HEARTBEAT_INTERVAL,
1859 );
1860 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 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 if let Some(book_addr) = connected_addr.get_address_book_addr() {
1886 if let Some(rtt) = rtt {
1887 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
1899async fn send_one_heartbeat(
1901 server_tx: &mut futures::channel::mpsc::Sender<ClientRequest>,
1902) -> Result<Response, BoxError> {
1903 let (tx, rx) = oneshot::channel();
1906
1907 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 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 server_tx.send(e.into_inner()).await?;
1927 } else {
1928 warn!(?e, "unexpected try_send error");
1930 Err(e)?;
1931 };
1932 }
1933 }
1934
1935 server_tx.flush().await?;
1937 tracing::trace!("sent heartbeat request");
1938
1939 let response = rx.await??;
1943 tracing::trace!(?response, "got heartbeat response");
1944
1945 Ok(response)
1946}
1947
1948async 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
1972async 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 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
2002async 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}