1pub mod config;
17pub mod constants;
19pub mod errors;
21pub mod path;
23pub mod protocol;
25
26mod multiaddrs;
27
28#[cfg(feature = "capture")]
29mod capture;
30mod pipeline;
31pub mod socket;
32
33use std::{
34 sync::{Arc, OnceLock},
35 time::Duration,
36};
37
38use constants::MAXIMUM_MSG_OUTGOING_BUFFER_SIZE;
39use futures::{FutureExt, SinkExt, StreamExt, channel::mpsc::Sender, stream::select_with_strategy};
40pub use hopr_api::{
41 Multiaddr, PeerId,
42 network::{Health, traits::NetworkView},
43 types::{
44 crypto::{
45 keypairs::{ChainKeypair, Keypair, OffchainKeypair},
46 types::{HalfKeyChallenge, Hash, OffchainPublicKey},
47 },
48 internal::{prelude::HoprPseudonym, routing::RoutingOptions},
49 },
50};
51use hopr_api::{
52 chain::{ChainKeyOperations, ChainReadAccountOperations, ChainReadChannelOperations, ChainValues},
53 ct::{CoverTrafficGeneration, ProbingTrafficGeneration},
54 graph::{NetworkGraphUpdate, NetworkGraphView, traits::EdgeObservableRead},
55 network::{BoxedProcessFn, NetworkStreamControl},
56 types::primitive::prelude::*,
57};
58use hopr_crypto_packet::prelude::PacketSignal;
59pub use hopr_protocol_app::prelude::{ApplicationData, ApplicationDataIn, ApplicationDataOut, Tag};
60use hopr_protocol_hopr::MemorySurbStore;
61pub use hopr_transport_probe::{NeighborTelemetry, PathTelemetry, errors::ProbeError, ping::PingQueryReplier};
62use hopr_transport_probe::{
63 Probe,
64 ping::{PingConfig, Pinger},
65};
66pub use hopr_transport_session as session;
67#[cfg(feature = "runtime-tokio")]
68pub use hopr_transport_session::transfer_session;
69pub use hopr_transport_session::{
70 Capabilities as SessionCapabilities, Capability as SessionCapability, HoprSession, IncomingSession, SESSION_MTU,
71 SURB_SIZE, ServiceId, SessionClientConfig, SessionId, SessionTarget, SurbBalancerConfig,
72 errors::{SessionManagerError, TransportSessionError},
73};
74use hopr_transport_session::{DispatchResult, SessionManager, SessionManagerConfig};
75#[cfg(feature = "telemetry")]
76pub use hopr_transport_session::{SessionAckMode, SessionLifecycleState};
77pub use hopr_transport_tag_allocator::TagAllocatorConfig;
78use hopr_utils::{
79 network_types::{
80 crossfire_sink::{CrossfireSink, bounded_sink_channel},
81 prelude::*,
82 },
83 runtime::AbortableList,
84};
85pub use multiaddr::Protocol;
86use rust_stream_ext_concurrent::then_concurrent::StreamThenConcurrentExt;
87use tracing::{Instrument, debug, error, trace, warn};
88
89#[cfg(feature = "runtime-tokio")]
90use crate::path::BackgroundPathCacheRefreshable;
91pub use crate::{config::HoprProtocolConfig, protocol::PeerProtocolCounterRegistry};
92use crate::{
93 constants::SESSION_INITIATION_TIMEOUT_BASE,
94 errors::HoprTransportError,
95 multiaddrs::strip_p2p_protocol,
96 path::{HoprGraphPathSelector, PathPlanner},
97 pipeline::HoprPacketPipelineBuilder,
98 socket::HoprSocket,
99};
100
101pub const APPLICATION_TAG_RANGE: std::ops::Range<Tag> = Tag::APPLICATION_TAG_RANGE;
102
103pub use hopr_api as api;
104use hopr_api::{
105 chain::{ChainReadTicketOperations, ChainWriteTicketOperations},
106 tickets::TicketFactory,
107 types::internal::routing::DestinationRouting,
108};
109
110lazy_static::lazy_static! {
112 static ref SESSION_INITIATION_TIMEOUT_MAX: Duration = 2 * SESSION_INITIATION_TIMEOUT_BASE * RoutingOptions::MAX_INTERMEDIATE_HOPS as u32;
113
114 static ref PEER_ID_CACHE: moka::sync::Cache<PeerId, OffchainPublicKey> = moka::sync::Cache::builder()
115 .time_to_idle(Duration::from_mins(15))
116 .max_capacity(10_000)
117 .build();
118
119 static ref RANDOM_DATA: [u8; 400] = hopr_api::types::crypto_random::random_bytes();
120}
121
122pub fn peer_id_to_public_key(peer_id: &PeerId) -> crate::errors::Result<OffchainPublicKey> {
127 PEER_ID_CACHE
128 .try_get_with_by_ref(peer_id, move || {
129 OffchainPublicKey::from_peerid(peer_id).map_err(|e| e.into())
130 })
131 .map_err(|e: Arc<HoprTransportError>| {
132 crate::errors::HoprTransportError::Other(anyhow::anyhow!(
133 "failed to convert peer_id ({:?}) to an offchain public key: {e}",
134 peer_id
135 ))
136 })
137}
138
139#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, strum::Display)]
140pub enum HoprTransportProcess {
141 #[strum(to_string = "component responsible for the transport medium (libp2p swarm)")]
142 Medium,
143 #[strum(to_string = "HOPR packet pipeline ({0})")]
144 Pipeline(protocol::PacketPipelineProcesses),
145 #[strum(to_string = "session manager sub-process #{0}")]
146 SessionsManagement(usize),
147 #[strum(to_string = "network probing sub-process: {0}")]
148 Probing(hopr_transport_probe::HoprProbeProcess),
149 #[cfg(feature = "runtime-tokio")]
150 #[strum(to_string = "path cache refresh")]
151 PathRefresh,
152 #[strum(to_string = "sync of outgoing ticket indices")]
153 OutgoingIndexSync,
154 #[strum(to_string = "periodic protocol counter flush")]
155 CounterFlush,
156 #[strum(to_string = "mixer→wire forwarder")]
157 MixerForwarder,
158 #[cfg(feature = "capture")]
159 #[strum(to_string = "packet capture")]
160 Capture,
161}
162
163type HoprSessionManager = SessionManager<CrossfireSink<(DestinationRouting, ApplicationDataOut)>>;
165
166#[derive(Debug, Clone)]
171pub struct HoprSessionConfigurator {
172 id: SessionId,
173 smgr: std::sync::Weak<HoprSessionManager>,
175}
176
177impl HoprSessionConfigurator {
178 pub fn id(&self) -> &SessionId {
180 &self.id
181 }
182
183 pub async fn ping(&self) -> errors::Result<()> {
191 Ok(self
192 .smgr
193 .upgrade()
194 .ok_or(HoprTransportError::Other(anyhow::anyhow!("session manager is dropped")))?
195 .ping_session(&self.id)
196 .await?)
197 }
198
199 pub fn get_surb_balancer_config(&self) -> errors::Result<Option<SurbBalancerConfig>> {
205 Ok(self
206 .smgr
207 .upgrade()
208 .ok_or(HoprTransportError::Other(anyhow::anyhow!("session manager is dropped")))?
209 .get_surb_balancer_config(&self.id)?)
210 }
211
212 pub fn update_surb_balancer_config(&self, config: SurbBalancerConfig) -> errors::Result<()> {
217 Ok(self
218 .smgr
219 .upgrade()
220 .ok_or(HoprTransportError::Other(anyhow::anyhow!("session manager is dropped")))?
221 .update_surb_balancer_config(&self.id, config)?)
222 }
223
224 pub fn close(&self) -> bool {
231 match self.smgr.upgrade() {
232 Some(smgr) => smgr.close_session(&self.id),
233 None => false,
234 }
235 }
236}
237
238pub struct HoprTransport<Chain, Graph, Net> {
241 packet_key: OffchainKeypair,
242 chain_key: ChainKeypair,
243 chain_api: Chain,
244 ping: Arc<OnceLock<Pinger>>,
245 network: Arc<OnceLock<Net>>,
246 graph: Graph,
247 path_planner: PathPlanner<MemorySurbStore, Chain, HoprGraphPathSelector<Graph>>,
248 my_multiaddresses: Vec<Multiaddr>,
249 smgr: Arc<HoprSessionManager>,
250 session_telemetry_tag_allocator: Arc<dyn hopr_transport_tag_allocator::TagAllocator + Send + Sync>,
251 probing_tag_allocator: Arc<dyn hopr_transport_tag_allocator::TagAllocator + Send + Sync>,
252 counters: PeerProtocolCounterRegistry,
253 cfg: HoprProtocolConfig,
254}
255
256impl<Chain, Graph, Net> HoprTransport<Chain, Graph, Net>
257where
258 Chain: ChainReadChannelOperations
259 + ChainReadAccountOperations
260 + ChainWriteTicketOperations
261 + ChainKeyOperations
262 + ChainReadTicketOperations
263 + ChainValues
264 + Clone
265 + Send
266 + Sync
267 + 'static,
268 Graph: NetworkGraphView<NodeId = OffchainPublicKey>
269 + NetworkGraphUpdate
270 + hopr_api::graph::NetworkGraphWrite<NodeId = OffchainPublicKey>
271 + hopr_api::graph::NetworkGraphTraverse<NodeId = OffchainPublicKey>
272 + Clone
273 + Send
274 + Sync
275 + 'static,
276 <Graph as NetworkGraphView>::Observed: hopr_api::graph::traits::EdgeObservableRead + Send,
277 <Graph as hopr_api::graph::NetworkGraphTraverse>::Observed:
278 hopr_api::graph::traits::EdgeObservableRead + Send + 'static,
279 <Graph as hopr_api::graph::NetworkGraphWrite>::Observed: hopr_api::graph::traits::EdgeObservableWrite + Send,
280 Net: NetworkView + NetworkStreamControl + Clone + Send + Sync + 'static,
281{
282 pub fn new(
283 identity: (&ChainKeypair, &OffchainKeypair),
284 resolver: Chain,
285 graph: Graph,
286 my_multiaddresses: Vec<Multiaddr>,
287 cfg: HoprProtocolConfig,
288 ) -> errors::Result<Self> {
289 let me_offchain = *identity.1.public();
290 let planner_config = cfg.path_planner;
291 let selector = HoprGraphPathSelector::new(
292 me_offchain,
293 graph.clone(),
294 planner_config.max_cached_paths,
295 planner_config.edge_penalty,
296 planner_config.min_ack_rate,
297 planner_config.min_paths_anonymity_floor,
298 );
299
300 let tag_allocators = hopr_transport_tag_allocator::create_allocators_from_config(&cfg.session.tag_allocator)?;
301
302 let mut session_telemetry_tag_allocator = None;
303 let mut probing_tag_allocator = None;
304 for (usage, alloc) in tag_allocators {
305 match usage {
306 hopr_transport_tag_allocator::Usage::Session => {}
308 hopr_transport_tag_allocator::Usage::SessionTerminalTelemetry => {
309 session_telemetry_tag_allocator = Some(alloc)
310 }
311 hopr_transport_tag_allocator::Usage::ProvingTelemetry => probing_tag_allocator = Some(alloc),
312 }
313 }
314 let session_telemetry_tag_allocator = session_telemetry_tag_allocator
315 .ok_or_else(|| HoprTransportError::Api("session telemetry tag allocator missing".into()))?;
316 let probing_tag_allocator =
317 probing_tag_allocator.ok_or_else(|| HoprTransportError::Api("probing tag allocator missing".into()))?;
318
319 Ok(Self {
320 packet_key: identity.1.clone(),
321 chain_key: identity.0.clone(),
322 ping: Arc::new(OnceLock::new()),
323 network: Arc::new(OnceLock::new()),
324 graph,
325 path_planner: PathPlanner::new(
326 me_offchain,
327 MemorySurbStore::new(cfg.packet.surb_store),
328 resolver.clone(),
329 selector,
330 planner_config,
331 ),
332 my_multiaddresses,
333 smgr: Arc::new(SessionManager::new(SessionManagerConfig {
334 frame_mtu: std::env::var("HOPR_SESSION_FRAME_SIZE")
335 .ok()
336 .and_then(|s| s.parse::<usize>().ok())
337 .unwrap_or_else(|| SessionManagerConfig::default().frame_mtu)
338 .max(ApplicationData::PAYLOAD_SIZE),
339 max_frame_timeout: std::env::var("HOPR_SESSION_FRAME_TIMEOUT_MS")
340 .ok()
341 .and_then(|s| s.parse::<u64>().ok().map(Duration::from_millis))
342 .unwrap_or_else(|| SessionManagerConfig::default().max_frame_timeout)
343 .max(Duration::from_millis(100)),
344 max_buffered_segments: std::env::var("HOPR_SESSION_MAX_BUFFERED_SEGMENTS")
345 .ok()
346 .and_then(|s| s.parse::<usize>().ok())
347 .unwrap_or_else(|| SessionManagerConfig::default().max_buffered_segments),
348 initiation_timeout_base: SESSION_INITIATION_TIMEOUT_BASE,
349 idle_timeout: cfg.session.idle_timeout,
350 balancer_sampling_interval: cfg.session.balancer_sampling_interval,
351 initial_return_session_egress_rate: 10,
352 minimum_surb_buffer_duration: cfg.session.balancer_minimum_surb_buffer_duration,
353 maximum_surb_buffer_size: cfg.packet.surb_store.rb_capacity,
354 surb_balance_notify_period: cfg.session.surb_balance_notify_period,
355 surb_target_notify: true,
356 maximum_sessions: cfg.session.maximum_managed_sessions,
357 ..Default::default()
358 })),
359 chain_api: resolver,
360 session_telemetry_tag_allocator,
361 probing_tag_allocator,
362 counters: PeerProtocolCounterRegistry::default(),
363 cfg,
364 })
365 }
366
367 pub async fn run_relay<T, TFact, Ct>(
373 &self,
374 cover_traffic: Ct,
375 network: Net,
376 network_process: BoxedProcessFn,
377 ticket_events: T,
378 ticket_factory: TFact,
379 on_incoming_session: Sender<IncomingSession>,
380 ) -> errors::Result<(
381 HoprSocket<
382 futures::stream::BoxStream<'static, ApplicationDataIn>,
383 CrossfireSink<(DestinationRouting, ApplicationDataOut)>,
384 >,
385 AbortableList<HoprTransportProcess>,
386 )>
387 where
388 T: futures::Sink<hopr_api::node::TicketEvent> + Clone + Send + Unpin + 'static,
389 T::Error: std::error::Error + Clone + Send,
390 Ct: ProbingTrafficGeneration + CoverTrafficGeneration + Send + Sync + 'static,
391 TFact: TicketFactory + Clone + Send + Sync + 'static,
392 {
393 self.run_inner(
394 protocol::NodeType::Relay,
395 cover_traffic,
396 network,
397 network_process,
398 ticket_events,
399 ticket_factory,
400 Some(on_incoming_session),
401 )
402 .await
403 }
404
405 pub async fn run_exit<TFact, Ct>(
410 &self,
411 cover_traffic: Ct,
412 network: Net,
413 network_process: BoxedProcessFn,
414 ticket_factory: TFact,
415 on_incoming_session: Sender<IncomingSession>,
416 ) -> errors::Result<(
417 HoprSocket<
418 futures::stream::BoxStream<'static, ApplicationDataIn>,
419 CrossfireSink<(DestinationRouting, ApplicationDataOut)>,
420 >,
421 AbortableList<HoprTransportProcess>,
422 )>
423 where
424 Ct: ProbingTrafficGeneration + CoverTrafficGeneration + Send + Sync + 'static,
425 TFact: TicketFactory + Clone + Send + Sync + 'static,
426 {
427 self.run_inner(
428 protocol::NodeType::Exit,
429 cover_traffic,
430 network,
431 network_process,
432 futures::sink::drain(),
433 ticket_factory,
434 Some(on_incoming_session),
435 )
436 .await
437 }
438
439 pub async fn run_entry<TFact, Ct>(
445 &self,
446 cover_traffic: Ct,
447 network: Net,
448 network_process: BoxedProcessFn,
449 ticket_factory: TFact,
450 ) -> errors::Result<(
451 HoprSocket<
452 futures::stream::BoxStream<'static, ApplicationDataIn>,
453 CrossfireSink<(DestinationRouting, ApplicationDataOut)>,
454 >,
455 AbortableList<HoprTransportProcess>,
456 )>
457 where
458 Ct: ProbingTrafficGeneration + CoverTrafficGeneration + Send + Sync + 'static,
459 TFact: TicketFactory + Clone + Send + Sync + 'static,
460 {
461 self.run_inner(
462 protocol::NodeType::Entry,
463 cover_traffic,
464 network,
465 network_process,
466 futures::sink::drain(),
467 ticket_factory,
468 None,
469 )
470 .await
471 }
472
473 #[allow(clippy::too_many_arguments)]
480 async fn run_inner<T, TFact, Ct>(
481 &self,
482 role: protocol::NodeType,
483 cover_traffic: Ct,
484 network: Net,
485 network_process: BoxedProcessFn,
486 ticket_events: T,
487 ticket_factory: TFact,
488 on_incoming_session: Option<Sender<IncomingSession>>,
489 ) -> errors::Result<(
490 HoprSocket<
491 futures::stream::BoxStream<'static, ApplicationDataIn>,
492 CrossfireSink<(DestinationRouting, ApplicationDataOut)>,
493 >,
494 AbortableList<HoprTransportProcess>,
495 )>
496 where
497 T: futures::Sink<hopr_api::node::TicketEvent> + Clone + Send + Unpin + 'static,
498 T::Error: std::error::Error + Clone + Send,
499 Ct: ProbingTrafficGeneration + CoverTrafficGeneration + Send + Sync + 'static,
500 TFact: TicketFactory + Clone + Send + Sync + 'static,
501 {
502 let mut processes = AbortableList::<HoprTransportProcess>::default();
503
504 let (unresolved_routing_msg_tx, unresolved_routing_msg_rx) =
505 bounded_sink_channel::<(DestinationRouting, ApplicationDataOut)>(MAXIMUM_MSG_OUTGOING_BUFFER_SIZE);
506
507 let transport_network = network;
510 let transport_layer_process = network_process;
511
512 let msg_codec = crate::protocol::HoprBinaryCodec {};
513 let (wire_msg_tx, wire_msg_rx) =
514 protocol::stream::process_stream_protocol(msg_codec, transport_network.clone(), self.cfg.stream).await?;
515
516 let mut mixer_cfg = self.cfg.mixer;
521 mixer_cfg.metric_delay_window = u64::try_from(5 * mixer_cfg.delay_range.as_millis())
522 .unwrap_or(u64::MAX)
523 .max(1);
524 let (mixing_channel_tx, mix_rx) = hopr_transport_mixer::channel(mixer_cfg);
525 processes.insert(
526 HoprTransportProcess::MixerForwarder,
527 hopr_utils::spawn_as_abortable!(async move {
528 mix_rx
529 .fold(wire_msg_tx, |mut sink, item| async move {
530 if sink.send(item).await.is_err() {
531 tracing::error!(
532 task = %HoprTransportProcess::MixerForwarder,
533 "wire sink dropped — discarding mixed packet"
534 );
535 }
536 sink
537 })
538 .await;
539 tracing::warn!(
540 task = %HoprTransportProcess::MixerForwarder,
541 "long-running background task finished"
542 );
543 }),
544 );
545
546 #[cfg(feature = "runtime-tokio")]
548 processes.insert(
549 HoprTransportProcess::PathRefresh,
550 hopr_utils::spawn_as_abortable!(self.path_planner.run_background_refresh()),
551 );
552
553 processes.insert(
554 HoprTransportProcess::Medium,
555 hopr_utils::spawn_as_abortable!(transport_layer_process().inspect(|_| tracing::warn!(
556 task = %HoprTransportProcess::Medium,
557 "long-running background task finished"
558 ))),
559 );
560
561 let msg_protocol_bidirectional_channel_capacity =
562 std::env::var("HOPR_INTERNAL_PROTOCOL_BIDIRECTIONAL_CHANNEL_CAPACITY")
563 .ok()
564 .and_then(|s| s.trim().parse::<usize>().ok())
565 .filter(|&c| c > 0)
566 .unwrap_or(16_384);
567
568 debug!(
569 capacity = msg_protocol_bidirectional_channel_capacity,
570 "creating protocol bidirectional channel"
571 );
572 let (tx_from_protocol, rx_from_protocol) =
573 bounded_sink_channel::<(HoprPseudonym, ApplicationDataIn)>(msg_protocol_bidirectional_channel_capacity);
574
575 let cover_traffic_allocated_tag = self
579 .session_telemetry_tag_allocator
580 .allocate()
581 .ok_or_else(|| HoprTransportError::Api("failed to allocate cover traffic tag".into()))?;
582 let cover_traffic_tag: Tag = cover_traffic_allocated_tag.value().into();
583
584 let rx_from_protocol = rx_from_protocol.filter_map(move |(pseudonym, data)| {
587 let _keep_alive = &cover_traffic_allocated_tag;
588 async move { (data.data.application_tag != cover_traffic_tag).then_some((pseudonym, data)) }
589 });
590
591 let cover_traffic_stream = CoverTrafficGeneration::build(&cover_traffic).filter_map(move |routing| {
593 let start =
594 hopr_api::types::crypto_random::random_integer(0, Some((RANDOM_DATA.len() - 100) as u64)) as usize;
595 let data = &RANDOM_DATA[start..start + 100];
596
597 futures::future::ready(if let Ok(data) = ApplicationData::new(cover_traffic_tag, data) {
598 Some((routing, ApplicationDataOut::with_no_packet_info(data)))
599 } else {
600 tracing::error!("failed to construct cover traffic packet");
601 None
602 })
603 });
604
605 let merged_unresolved_output_data =
607 select_with_strategy(unresolved_routing_msg_rx, cover_traffic_stream, |_: &mut ()| {
608 futures::stream::PollNext::Left
609 });
610
611 let path_planner = self.path_planner.clone();
618 let distress_threshold = self.cfg.packet.surb_store.distress_threshold;
619 let routing_concurrency = {
620 let avail = std::thread::available_parallelism()
621 .ok()
622 .map(|n| n.get())
623 .unwrap_or(1)
624 .max(1)
625 * 8;
626 self.cfg
627 .packet
628 .pipeline
629 .output_concurrency
630 .filter(|&n| n > 0)
631 .unwrap_or(avail)
632 };
633 let all_resolved_external_msg_rx = merged_unresolved_output_data
634 .then_concurrent(
635 move |(unresolved, mut data)| {
636 let path_planner = path_planner.clone();
637 async move {
638 trace!(?unresolved, "resolving routing for packet");
639 match path_planner
640 .resolve_routing(data.data.total_len(), data.estimate_surbs_with_msg(), unresolved)
641 .await
642 {
643 Ok((resolved, rem_surbs)) => {
644 let mut signals_to_dst = data
648 .packet_info
649 .as_ref()
650 .map(|info| info.signals_to_destination)
651 .unwrap_or_default();
652
653 if resolved.is_return() {
654 signals_to_dst = match rem_surbs {
655 Some(rem) if (1..distress_threshold.max(2)).contains(&rem) => {
656 signals_to_dst | PacketSignal::SurbDistress
657 }
658 Some(0) => signals_to_dst | PacketSignal::OutOfSurbs,
659 _ => signals_to_dst - (PacketSignal::OutOfSurbs | PacketSignal::SurbDistress),
660 };
661 } else {
662 signals_to_dst -= PacketSignal::SurbDistress | PacketSignal::OutOfSurbs;
664 }
665
666 data.packet_info.get_or_insert_default().signals_to_destination = signals_to_dst;
667 trace!(?resolved, "resolved routing for packet");
668 Some((resolved, data))
669 }
670 Err(error) => {
671 error!(%error, "failed to resolve routing");
672 None
673 }
674 }
675 }
676 .in_current_span()
677 },
678 routing_concurrency,
679 )
680 .filter_map(futures::future::ready);
681
682 let channels_dst = self
683 .chain_api
684 .domain_separators()
685 .await
686 .map_err(HoprTransportError::chain)?
687 .channel;
688
689 let pipeline_builder = HoprPacketPipelineBuilder::new()
690 .identity((&self.chain_key, &self.packet_key))
691 .transport((mixing_channel_tx, wire_msg_rx))
692 .api((tx_from_protocol, all_resolved_external_msg_rx))
693 .surb_store(self.path_planner.surb_store.clone())
694 .chain_api(self.chain_api.clone())
695 .ticket_factory(ticket_factory)
696 .channels_dst(channels_dst)
697 .with_counters(self.counters.clone())
698 .with_config(self.cfg.packet);
699
700 let pipeline_processes = match role {
701 protocol::NodeType::Relay => pipeline_builder.with_ticket_events(ticket_events).build_for_relay(),
702 protocol::NodeType::Exit => pipeline_builder.build_for_exit(),
703 protocol::NodeType::Entry => pipeline_builder.build_for_entry(),
704 };
705 processes.extend_from(pipeline_processes);
706
707 let flush_counters = self.counters.clone();
709 let flush_graph = self.graph.clone();
710 let flush_me = *self.packet_key.public();
711 let flush_interval = self.cfg.counter_flush_interval;
712 processes.insert(
713 HoprTransportProcess::CounterFlush,
714 hopr_utils::spawn_as_abortable!(async move {
715 use hopr_api::graph::traits::{EdgeObservableWrite, EdgeWeightType};
716
717 futures_time::stream::interval(futures_time::time::Duration::from(flush_interval))
718 .for_each(|_| {
719 for (peer, num_packets, num_acks) in flush_counters.drain() {
720 tracing::trace!(
721 %peer,
722 num_packets,
723 num_acks,
724 "flushing protocol conformance counters"
725 );
726 flush_graph.upsert_edge(&flush_me, &peer, |obs| {
727 obs.record(EdgeWeightType::ImmediateProtocolConformance { num_packets, num_acks });
728 });
729 }
730 futures::future::ready(())
731 })
732 .await;
733 }),
734 );
735
736 let manual_ping_channel_capacity = std::env::var("HOPR_INTERNAL_MANUAL_PING_CHANNEL_CAPACITY")
738 .ok()
739 .and_then(|s| s.trim().parse::<usize>().ok())
740 .filter(|&c| c > 0)
741 .unwrap_or(128);
742 debug!(capacity = manual_ping_channel_capacity, "Creating manual ping channel");
743 let (manual_ping_tx, manual_ping_rx_raw) =
744 crossfire::mpsc::bounded_async::<(OffchainPublicKey, PingQueryReplier)>(manual_ping_channel_capacity);
745 let manual_ping_rx = manual_ping_rx_raw.into_stream();
746
747 let probe = Probe::new(self.cfg.probe, self.probing_tag_allocator.clone());
748
749 let (probing_processes, probe_classifier) = probe
750 .continuously_scan(
751 unresolved_routing_msg_tx.clone(),
752 manual_ping_rx,
753 cover_traffic,
754 self.graph.clone(),
755 )
756 .await;
757
758 processes.flat_map_extend_from(probing_processes, HoprTransportProcess::Probing);
759
760 self.ping
762 .clone()
763 .set(Pinger::new(
764 PingConfig {
765 timeout: self.cfg.probe.timeout,
766 },
767 manual_ping_tx,
768 ))
769 .map_err(|_| HoprTransportError::Api("must set the ticket aggregation writer only once".into()))?;
770
771 let smgr_start_res = if role != protocol::NodeType::Entry {
773 self.smgr.start(
775 unresolved_routing_msg_tx.clone(),
776 on_incoming_session.ok_or_else(|| {
777 HoprTransportError::Api("on_incoming_session channel is required for relay/exit nodes".into())
778 })?,
779 )
780 } else {
781 self.smgr
783 .start(unresolved_routing_msg_tx.clone(), futures::sink::drain())
784 };
785
786 smgr_start_res
787 .map_err(|_| HoprTransportError::Api("failed to start session manager".into()))?
788 .into_iter()
789 .enumerate()
790 .map(|(i, jh)| (HoprTransportProcess::SessionsManagement(i + 1), jh))
791 .for_each(|(k, v)| {
792 processes.insert(k, v);
793 });
794
795 let (on_incoming_data_tx, on_incoming_data_rx) =
804 bounded_sink_channel::<ApplicationDataIn>(msg_protocol_bidirectional_channel_capacity);
805 let smgr = self.smgr.clone();
806 let unresolved_routing_msg_tx_for_task = unresolved_routing_msg_tx.clone();
807 processes.insert(
808 HoprTransportProcess::SessionsManagement(0),
809 hopr_utils::spawn_as_abortable!(async move {
810 probe_classifier
811 .filter_stream(unresolved_routing_msg_tx_for_task, rx_from_protocol)
812 .filter_map(move |(pseudonym, data)| {
813 futures::future::ready(match smgr.dispatch_message(pseudonym, data) {
814 Ok(DispatchResult::Processed) => {
815 tracing::trace!("message dispatch completed");
816 None
817 }
818 Ok(DispatchResult::Unrelated(data)) => {
819 tracing::trace!("unrelated message dispatch completed");
820 Some(data)
821 }
822 Err(error) => {
823 tracing::error!(%error, "error while dispatching packet in the session manager");
824 None
825 }
826 })
827 })
828 .fold(on_incoming_data_tx, |mut tx, data| async move {
829 if tx.send(data).await.is_err() {
830 tracing::error!(
831 task = %HoprTransportProcess::SessionsManagement(0),
832 "incoming-data channel disconnected — dropping unrelated packet; \
833 HoprSocket must be consumed or drained by the caller"
834 );
835 }
836 tx
837 })
838 .await;
839 tracing::warn!(
840 task = %HoprTransportProcess::SessionsManagement(0),
841 "long-running background task finished"
842 );
843 }),
844 );
845
846 self.network
848 .clone()
849 .set(transport_network)
850 .map_err(|_| HoprTransportError::Api("transport network viewer already set".into()))?;
851
852 Ok((
853 (on_incoming_data_rx.boxed(), unresolved_routing_msg_tx).into(),
854 processes,
855 ))
856 }
857
858 #[tracing::instrument(level = "debug", skip(self))]
859 pub async fn ping(
860 &self,
861 peer: &OffchainPublicKey,
862 ) -> errors::Result<(std::time::Duration, <Graph as NetworkGraphView>::Observed)> {
863 let me: &OffchainPublicKey = self.packet_key.public();
864 if peer == me {
865 return Err(HoprTransportError::Api("ping to self does not make sense".into()));
866 }
867
868 let pinger = self
869 .ping
870 .get()
871 .ok_or_else(|| HoprTransportError::Api("ping processing is not yet initialized".into()))?;
872
873 let latency = (*pinger).ping(peer).await?;
874
875 if let Some(observations) = self.graph.edge(me, peer) {
876 Ok((latency, observations))
877 } else {
878 Err(HoprTransportError::Api(format!(
879 "no observations available for peer {peer}",
880 )))
881 }
882 }
883
884 #[tracing::instrument(level = "debug", skip(self))]
885 pub async fn new_session(
886 &self,
887 destination: Address,
888 target: SessionTarget,
889 cfg: SessionClientConfig,
890 ) -> errors::Result<(HoprSession, HoprSessionConfigurator)> {
891 let session = self.smgr.new_session(destination, target, cfg).await?;
892 let id = *session.id();
893 Ok((
894 session,
895 HoprSessionConfigurator {
896 id,
897 smgr: Arc::downgrade(&self.smgr),
898 },
899 ))
900 }
901
902 #[tracing::instrument(level = "debug", skip(self))]
903 pub async fn listening_multiaddresses(&self) -> Vec<Multiaddr> {
904 self.network
905 .get()
906 .ok_or_else(|| HoprTransportError::Api("transport network is not yet initialized".into()))
907 .map(|network| network.listening_as().into_iter().collect())
908 .unwrap_or_default()
909 }
910
911 #[tracing::instrument(level = "debug", skip(self))]
912 pub fn announceable_multiaddresses(&self) -> Vec<Multiaddr> {
913 let mut mas = self
914 .local_multiaddresses()
915 .into_iter()
916 .filter(|ma| {
917 crate::multiaddrs::is_supported(ma)
918 && (self.cfg.transport.announce_local_addresses || is_public_address(ma))
919 })
920 .map(|ma| strip_p2p_protocol(&ma))
921 .filter(|v| !v.is_empty())
922 .collect::<Vec<_>>();
923
924 mas.sort_by(|l, r| {
925 let is_left_dns = crate::multiaddrs::is_dns(l);
926 let is_right_dns = crate::multiaddrs::is_dns(r);
927
928 if !(is_left_dns ^ is_right_dns) {
929 std::cmp::Ordering::Equal
930 } else if is_left_dns {
931 std::cmp::Ordering::Less
932 } else {
933 std::cmp::Ordering::Greater
934 }
935 });
936
937 mas
938 }
939
940 pub fn graph(&self) -> &Graph {
942 &self.graph
943 }
944
945 #[tracing::instrument(level = "debug", skip(self))]
946 pub fn local_multiaddresses(&self) -> Vec<Multiaddr> {
947 self.network
948 .get()
949 .map(|network| network.listening_as().into_iter().collect())
950 .unwrap_or_else(|| {
951 tracing::error!("transport network is not yet initialized, cannot fetch announced multiaddresses");
952 self.my_multiaddresses.clone()
953 })
954 }
955
956 #[tracing::instrument(level = "debug", skip(self))]
957 pub async fn network_observed_multiaddresses(&self, peer: &OffchainPublicKey) -> Vec<Multiaddr> {
958 match self
959 .network
960 .get()
961 .ok_or_else(|| HoprTransportError::Api("transport network is not yet initialized".into()))
962 {
963 Ok(network) => network
964 .multiaddress_of(&peer.into())
965 .unwrap_or_default()
966 .into_iter()
967 .collect(),
968 Err(error) => {
969 tracing::error!(%error, "failed to get observed multiaddresses");
970 return vec![];
971 }
972 }
973 }
974
975 #[tracing::instrument(level = "debug", skip(self))]
976 pub async fn network_health(&self) -> Health {
977 self.network
978 .get()
979 .ok_or_else(|| HoprTransportError::Api("transport network is not yet initialized".into()))
980 .map(|network| network.health())
981 .unwrap_or(Health::Red)
982 }
983
984 pub async fn network_connected_peers(&self) -> errors::Result<Vec<OffchainPublicKey>> {
985 Ok(futures::stream::iter(
986 self.network
987 .get()
988 .ok_or_else(|| {
989 tracing::error!("transport network is not yet initialized");
990 HoprTransportError::Api("transport network is not yet initialized".into())
991 })?
992 .connected_peers(),
993 )
994 .filter_map(|peer_id| async move {
995 match peer_id_to_public_key(&peer_id) {
996 Ok(key) => Some(key),
997 Err(error) => {
998 tracing::warn!(%peer_id, %error, "failed to convert PeerId to OffchainPublicKey");
999 None
1000 }
1001 }
1002 })
1003 .collect()
1004 .await)
1005 }
1006
1007 #[tracing::instrument(level = "debug", skip(self))]
1008 pub fn network_peer_observations(&self, peer: &OffchainPublicKey) -> Option<<Graph as NetworkGraphView>::Observed> {
1009 self.graph.edge(self.packet_key.public(), peer)
1010 }
1011
1012 #[tracing::instrument(level = "debug", skip(self))]
1014 pub async fn all_network_peers(
1015 &self,
1016 minimum_score: f64,
1017 ) -> errors::Result<Vec<(OffchainPublicKey, <Graph as NetworkGraphView>::Observed)>> {
1018 let me = self.packet_key.public();
1019 Ok(self
1020 .network_connected_peers()
1021 .await?
1022 .into_iter()
1023 .filter_map(|peer| {
1024 let observation = self.graph.edge(me, &peer);
1025 if let Some(info) = observation {
1026 if info.score() >= minimum_score {
1027 Some((peer, info))
1028 } else {
1029 None
1030 }
1031 } else {
1032 None
1033 }
1034 })
1035 .collect::<Vec<_>>())
1036 }
1037}
1038
1039impl<Chain, Graph, Net> NetworkView for HoprTransport<Chain, Graph, Net>
1044where
1045 Net: NetworkView + Send + Sync + 'static,
1046{
1047 fn listening_as(&self) -> std::collections::HashSet<Multiaddr> {
1048 self.network.get().map(|n| n.listening_as()).unwrap_or_default()
1049 }
1050
1051 fn multiaddress_of(&self, peer: &PeerId) -> Option<std::collections::HashSet<Multiaddr>> {
1052 self.network.get()?.multiaddress_of(peer)
1053 }
1054
1055 fn discovered_peers(&self) -> std::collections::HashSet<PeerId> {
1056 self.network.get().map(|n| n.discovered_peers()).unwrap_or_default()
1057 }
1058
1059 fn connected_peers(&self) -> std::collections::HashSet<PeerId> {
1060 self.network.get().map(|n| n.connected_peers()).unwrap_or_default()
1061 }
1062
1063 fn is_connected(&self, peer: &PeerId) -> bool {
1064 self.network.get().map(|n| n.is_connected(peer)).unwrap_or(false)
1065 }
1066
1067 fn health(&self) -> Health {
1068 self.network.get().map(|n| n.health()).unwrap_or(Health::Red)
1069 }
1070
1071 fn subscribe_network_events(
1072 &self,
1073 ) -> impl futures::Stream<Item = hopr_api::network::NetworkEvent> + Send + 'static {
1074 match self.network.get() {
1075 Some(n) => futures::future::Either::Left(n.subscribe_network_events()),
1076 None => futures::future::Either::Right(futures::stream::empty()),
1077 }
1078 }
1079}
1080
1081#[async_trait::async_trait]
1086impl<Chain, Graph, Net> hopr_api::node::TransportOperations for HoprTransport<Chain, Graph, Net>
1087where
1088 Chain: ChainReadChannelOperations
1089 + ChainReadAccountOperations
1090 + hopr_api::chain::ChainWriteTicketOperations
1091 + ChainKeyOperations
1092 + hopr_api::chain::ChainReadTicketOperations
1093 + ChainValues
1094 + Clone
1095 + Send
1096 + Sync
1097 + 'static,
1098 Graph: NetworkGraphView<NodeId = OffchainPublicKey>
1099 + NetworkGraphUpdate
1100 + hopr_api::graph::NetworkGraphWrite<NodeId = OffchainPublicKey>
1101 + hopr_api::graph::NetworkGraphTraverse<NodeId = OffchainPublicKey>
1102 + Clone
1103 + Send
1104 + Sync
1105 + 'static,
1106 <Graph as NetworkGraphView>::Observed: EdgeObservableRead + Send,
1107 <Graph as hopr_api::graph::NetworkGraphTraverse>::Observed: EdgeObservableRead + Send + 'static,
1108 <Graph as hopr_api::graph::NetworkGraphWrite>::Observed: hopr_api::graph::traits::EdgeObservableWrite + Send,
1109 Net: NetworkView + NetworkStreamControl + Clone + Send + Sync + 'static,
1110{
1111 type Error = errors::HoprTransportError;
1112 type Observable = <Graph as NetworkGraphView>::Observed;
1113
1114 async fn ping(&self, key: &OffchainPublicKey) -> Result<(Duration, Self::Observable), Self::Error> {
1115 self.ping(key).await
1116 }
1117
1118 async fn observed_multiaddresses(&self, key: &OffchainPublicKey) -> Vec<Multiaddr> {
1119 self.network_observed_multiaddresses(key).await
1120 }
1121}