1mod builder;
4mod config;
5
6pub use builder::{PacketPipelineBuilder, Unset};
7use bytes::Bytes;
8pub use config::{AcknowledgementPipelineConfig, PacketPipelineConfig};
9use futures::{SinkExt, StreamExt, future::Either};
10use futures_time::{future::FutureExt as TimeExt, stream::StreamExt as TimeStreamExt};
11use hopr_api::{
12 PeerId,
13 node::TicketEvent,
14 types::{crypto::prelude::*, internal::prelude::*},
15};
16use hopr_crypto_packet::HoprSurb;
17use hopr_protocol_app::prelude::*;
18use hopr_protocol_hopr::prelude::*;
19use hopr_utils::{
20 network_types::timeout::{SinkTimeoutError, TimeoutSinkExt, TimeoutStreamExt},
21 runtime::AbortableList,
22};
23use rust_stream_ext_concurrent::then_concurrent::StreamThenConcurrentExt;
24use tracing::Instrument;
25
26use crate::PeerProtocolCounterRegistry;
27
28const DEFAULT_ACK_INPUT_CONCURRENCY: usize = 10;
31const DEFAULT_ACK_OUTPUT_CONCURRENCY: usize = 10;
34const QUEUE_SEND_TIMEOUT: std::time::Duration = std::time::Duration::from_millis(50);
35const PACKET_DECODING_TIMEOUT: std::time::Duration = std::time::Duration::from_millis(150);
36const PACKET_ENCODING_TIMEOUT: std::time::Duration = std::time::Duration::from_millis(150);
37
38#[cfg(all(feature = "telemetry", not(test)))]
39lazy_static::lazy_static! {
40 static ref METRIC_PACKET_COUNT: hopr_api::types::telemetry::MultiCounter = hopr_api::types::telemetry::MultiCounter::new(
41 "hopr_packets_count",
42 "Number of processed packets of different types (sent, received, forwarded)",
43 &["type"]
44 ).unwrap();
45 static ref METRIC_PACKET_REJECTED_COUNT: hopr_api::types::telemetry::MultiCounter = hopr_api::types::telemetry::MultiCounter::new(
46 "hopr_packet_rejected_count",
47 "Number of incoming packets rejected due various reasons",
48 &["reason"]
49 ).unwrap();
50 static ref METRIC_PACKET_DECODE_TIMEOUTS: hopr_api::types::telemetry::SimpleCounter = hopr_api::types::telemetry::SimpleCounter::new(
55 "hopr_packet_decode_timeouts_total",
56 "Number of incoming packets dropped due to decode timeout (sustained rate indicates Rayon pool saturation)"
57 ).unwrap();
58 static ref METRIC_VALIDATION_ERRORS: hopr_api::types::telemetry::MultiCounter = hopr_api::types::telemetry::MultiCounter::new(
59 "hopr_packet_ticket_validation_errors",
60 "Number of different ticket validation errors encountered during packet processing",
61 &["type"]
62 ).unwrap();
63 static ref METRIC_RECEIVED_ACKS: hopr_api::types::telemetry::MultiCounter = hopr_api::types::telemetry::MultiCounter::new(
64 "hopr_protocol_ack_received_count",
65 "Number of received acknowledgements",
66 &["valid"]
67 ).unwrap();
68 static ref METRIC_SENT_ACKS: hopr_api::types::telemetry::SimpleCounter = hopr_api::types::telemetry::SimpleCounter::new(
69 "hopr_protocol_ack_sent_count",
70 "Number of sent message acknowledgements"
71 ).unwrap();
72 static ref METRIC_TICKETS_COUNT: hopr_api::types::telemetry::MultiCounter = hopr_api::types::telemetry::MultiCounter::new(
73 "hopr_tickets_count",
74 "Number of tickets by type (winning, losing, rejected)",
75 &["type"]
76 ).unwrap();
77}
78
79#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, strum::Display)]
80pub enum PacketPipelineProcesses {
81 #[strum(to_string = "HOPR [msg] - ingress")]
82 MsgIn,
83 #[strum(to_string = "HOPR [msg] - egress")]
84 MsgOut,
85 #[strum(to_string = "HOPR [ack] - egress")]
86 AckOut,
87 #[strum(to_string = "HOPR [ack] - ingress")]
88 AckIn,
89 #[strum(to_string = "HOPR [msg] - mixer")]
90 Mixer,
91}
92
93async fn start_outgoing_packet_pipeline<AppOut, E, WOut, WOutErr>(
95 app_outgoing: AppOut,
96 encoder: std::sync::Arc<E>,
97 wire_outgoing: WOut,
98 counters: super::counters::PeerProtocolCounterRegistry,
99 concurrency: usize,
100) where
101 AppOut: futures::Stream<Item = (ResolvedTransportRouting<HoprSurb>, ApplicationDataOut)> + Send + 'static,
102 E: PacketEncoder + Send + Sync + 'static,
103 WOut: futures::Sink<(PeerId, Bytes), Error = SinkTimeoutError<WOutErr>> + Clone + Unpin + Send + 'static,
104 WOutErr: std::error::Error,
105{
106 let res = app_outgoing
107 .then_concurrent(
108 |(routing, data)| {
109 let encoder = encoder.clone();
110 let counters = counters.clone();
111 async move {
112 match hopr_utils::parallelize::cpu::spawn_fifo_blocking(
113 move || {
114 encoder.encode_packet(
115 data.data.to_bytes(),
116 routing,
117 data.packet_info
118 .map(|data| data.signals_to_destination)
119 .unwrap_or_default(),
120 )
121 },
122 "packet_encode",
123 )
124 .timeout(futures_time::time::Duration::from(PACKET_ENCODING_TIMEOUT))
125 .await
126 {
127 Ok(Ok(Ok(packet))) => {
128 #[cfg(all(feature = "telemetry", not(test)))]
129 METRIC_PACKET_COUNT.increment(&["sent"]);
130
131 counters.get_or_create(&packet.next_hop).record_message_sent();
132 tracing::trace!(peer = packet.next_hop.to_peerid_str(), "protocol message out");
133 Some((packet.next_hop.into(), packet.data))
134 }
135 Ok(Ok(Err(error))) => {
136 tracing::error!(%error, "outgoing packet could not be encoded");
137 None
138 }
139 Ok(Err(error)) => {
140 tracing::error!(%error, "parallel processing of the outgoing packet failed");
141 None
142 }
143 Err(error) => {
144 tracing::error!(%error, "timeout while processing the outgoing packet");
145 None
146 }
147 }
148 }
149 },
150 concurrency,
151 )
152 .filter_map(futures::future::ready)
153 .map(Ok)
154 .forward_to_timeout(wire_outgoing)
155 .in_current_span()
156 .await;
157
158 if let Err(error) = res {
159 tracing::error!(
160 task = "transport (protocol - msg egress)",
161 %error,
162 "long-running background task finished with error"
163 );
164 } else {
165 tracing::warn!(
166 task = "transport (protocol - msg egress)",
167 "long-running background task finished"
168 )
169 }
170}
171
172#[allow(clippy::too_many_arguments)]
179async fn start_incoming_packet_pipeline<WIn, WOut, D, T, TEvt, AckIn, AckOut, AppIn, AppInErr>(
180 (wire_outgoing, wire_incoming): (WOut, WIn),
181 decoder: std::sync::Arc<D>,
182 ticket_proc: std::sync::Arc<T>,
183 ticket_events: TEvt,
184 (ack_outgoing, ack_incoming): (AckOut, AckIn),
185 app_incoming: AppIn,
186 counters: super::counters::PeerProtocolCounterRegistry,
187 concurrency: usize,
188) where
189 WIn: futures::Stream<Item = (PeerId, Bytes)> + Send + 'static,
190 WOut: futures::Sink<(PeerId, Bytes)> + Clone + Unpin + Send + 'static,
191 WOut::Error: std::error::Error,
192 D: PacketDecoder + Sync + Send + 'static,
193 T: UnacknowledgedTicketProcessor + Send + 'static,
194 TEvt: futures::Sink<TicketEvent> + Clone + Unpin + Send + 'static,
195 TEvt::Error: std::error::Error,
196 AckIn: futures::Sink<(OffchainPublicKey, Vec<Acknowledgement>)> + Send + Unpin + Clone + 'static,
197 AckIn::Error: std::error::Error,
198 AckOut: futures::Sink<(OffchainPublicKey, Option<HalfKey>)> + Send + Unpin + Clone + 'static,
199 AckOut::Error: std::error::Error,
200 AppIn: futures::Sink<(HoprPseudonym, ApplicationDataIn), Error = SinkTimeoutError<AppInErr>> + Send + 'static,
201 AppInErr: std::error::Error,
202{
203 let ack_outgoing_success = ack_outgoing.clone();
204 let ack_outgoing_failure = ack_outgoing;
205 let ticket_proc_success = ticket_proc;
206
207 let res = wire_incoming
208 .then_concurrent(move |(peer, data)| {
209 let decoder = decoder.clone();
210 let mut ack_outgoing_failure = ack_outgoing_failure.clone();
211 let mut ticket_events_reject = ticket_events.clone();
212
213 tracing::trace!(%peer, "protocol message in");
214
215 async move {
216 match hopr_utils::parallelize::cpu::spawn_fifo_blocking(move || decoder.decode(peer, data), "packet_decode")
217 .timeout(futures_time::time::Duration::from(PACKET_DECODING_TIMEOUT))
218 .await {
219 Ok(Ok(Ok(packet))) => {
220 tracing::trace!(%peer, ?packet, "successfully decoded incoming packet");
221 Some(packet)
222 },
223 Ok(Ok(Err(IncomingPacketError::Undecodable(error)))) => {
224 tracing::trace!(%peer, %error, "not sending ack back on undecodable packet - possible adversarial behavior");
228
229 #[cfg(all(feature = "telemetry", not(test)))]
230 METRIC_PACKET_REJECTED_COUNT.increment(&["undecodable"]);
231
232 None
233 },
234 Ok(Ok(Err(IncomingPacketError::ProcessingError(sender, error)))) => {
235 tracing::error!(%peer, %error, "failed to process the decoded packet");
236 ack_outgoing_failure
238 .send((*sender, None))
239 .await
240 .unwrap_or_else(|error| {
241 tracing::error!(%error, "failed to send ack to the egress queue");
242 });
243
244 #[cfg(all(feature = "telemetry", not(test)))]
245 METRIC_PACKET_REJECTED_COUNT.increment(&["processing_error"]);
246
247 None
248 },
249 Ok(Ok(Err(IncomingPacketError::InvalidTicket(sender, error)))) => {
250 tracing::error!(%peer, %error, "failed to validate ticket on the received packet");
251 if let Err(error) = ticket_events_reject
252 .send(TicketEvent::RejectedTicket(error.ticket, error.issuer))
253 .await {
254 tracing::error!(%error, "failed to notify invalid ticket rejection");
255 }
256 ack_outgoing_failure
258 .send((*sender, None))
259 .await
260 .unwrap_or_else(|error| {
261 tracing::error!(%error, "failed to send ack to the egress queue");
262 });
263
264 #[cfg(all(feature = "telemetry", not(test)))]
265 {
266 METRIC_VALIDATION_ERRORS.increment(&[error.kind.as_ref()]);
267 METRIC_PACKET_REJECTED_COUNT.increment(&["invalid_ticket"]);
268 METRIC_TICKETS_COUNT.increment(&["rejected"]);
269 }
270
271 None
272 }
273 Ok(Err(error)) => {
274 tracing::error!(%error, "parallel processing of the incoming packet failed");
275 None
276 },
277 Err(_) => {
278 tracing::error!(
280 %peer,
281 timeout_ms = PACKET_DECODING_TIMEOUT.as_millis() as u64,
282 "dropped incoming packet: decode timeout - check the 'hopr_rayon_queue_wait_seconds' metric for pool saturation"
283 );
284 #[cfg(all(feature = "telemetry", not(test)))]
285 {
286 METRIC_PACKET_DECODE_TIMEOUTS.increment();
287 METRIC_PACKET_REJECTED_COUNT.increment(&["timeout"]);
288 }
289
290 None
291 }
292 }
293 }.instrument(tracing::debug_span!("incoming_packet_decode", %peer))
294 }, concurrency)
295 .filter_map(futures::future::ready)
296 .then_concurrent(move |packet| {
300 match packet {
301 IncomingPacket::Acknowledgement(ack) => {
302 let mut ack_incoming = ack_incoming.clone();
303 let counters = counters.clone();
304 Either::Left(async move {
305 let IncomingAcknowledgementPacket { previous_hop, received_acks, .. } = *ack;
306 tracing::trace!(previous_hop = previous_hop.to_peerid_str(), num_acks = received_acks.len(), "incoming acknowledgements");
307 counters.get_or_create(&previous_hop).record_acks_received(received_acks.len() as u64);
308
309 ack_incoming
310 .send((previous_hop, received_acks))
311 .await
312 .unwrap_or_else(|error| {
313 tracing::error!(%error, "failed dispatching received acknowledgement to the ticket ack queue");
314 });
315
316 None
318 })
319 }
320 IncomingPacket::Final(final_packet) => {
321 let mut ack_outgoing_success = ack_outgoing_success.clone();
322 Either::Right(Either::Left(async move {
323 let IncomingFinalPacket {
324 previous_hop,
325 sender,
326 plain_text,
327 ack_key,
328 info,
329 ..
330 } = *final_packet;
331 tracing::trace!(previous_hop = previous_hop.to_peerid_str(), "incoming final packet");
332
333 ack_outgoing_success
335 .send((previous_hop, Some(ack_key)))
336 .await
337 .unwrap_or_else(|error| {
338 tracing::error!(%error, "failed to send ack to the egress queue");
339 });
340
341 #[cfg(all(feature = "telemetry", not(test)))]
342 METRIC_PACKET_COUNT.increment(&["received"]);
343
344 Some((sender, plain_text, info))
345 }))
346 }
347 IncomingPacket::Forwarded(fwd_packet) => {
348 let ticket_proc = ticket_proc_success.clone();
349 let mut wire_outgoing = wire_outgoing.clone();
350 let mut ack_outgoing_success = ack_outgoing_success.clone();
351 let counters = counters.clone();
352 Either::Right(Either::Right(async move {
353 let IncomingForwardedPacket {
354 previous_hop,
355 next_hop,
356 data,
357 ack_key_prev_hop,
358 ack_challenge,
359 received_ticket,
360 ..
361 } = *fwd_packet;
362 if let Err(error) = ticket_proc.insert_unacknowledged_ticket(&next_hop, ack_challenge, received_ticket) {
364 tracing::error!(
365 previous_hop = previous_hop.to_peerid_str(),
366 next_hop = next_hop.to_peerid_str(),
367 %error,
368 "failed to insert unacknowledged ticket into the ticket processor"
369 );
370
371 #[cfg(all(feature = "telemetry", not(test)))]
372 METRIC_PACKET_REJECTED_COUNT.increment(&["unack_processing_error"]);
373
374 return None;
375 }
376
377 tracing::trace!(
379 previous_hop = previous_hop.to_peerid_str(),
380 next_hop = next_hop.to_peerid_str(),
381 "forwarding packet to the next hop"
382 );
383
384 match wire_outgoing.send((next_hop.into(), data)).await {
385 Ok(()) => {
386 counters.get_or_create(&next_hop).record_message_sent();
387
388 #[cfg(all(feature = "telemetry", not(test)))]
389 METRIC_PACKET_COUNT.increment(&["forwarded"]);
390 }
391 Err(error) => {
392 tracing::error!(%error, "failed to forward a packet to the transport layer");
393 return None;
394 }
395 }
396
397 tracing::trace!(previous_hop = previous_hop.to_peerid_str(), "acknowledging forwarded packet back");
399 ack_outgoing_success
400 .send((previous_hop, Some(ack_key_prev_hop)))
401 .await
402 .unwrap_or_else(|error| {
403 tracing::error!(%error, "failed to send ack to the egress queue");
404 });
405
406 None
407 }))
408 }
409 }
410 }, concurrency)
411 .filter_map(|maybe_data| futures::future::ready(
412 maybe_data
414 .and_then(|(sender, data, aux_info)| ApplicationData::try_from(data.as_ref())
415 .inspect_err(|error| tracing::error!(%sender, %error, "failed to decode application data"))
416 .ok()
417 .map(|data| (sender, ApplicationDataIn {
418 data,
419 packet_info: IncomingPacketInfo {
420 signals_from_sender: aux_info.packet_signals,
421 num_saved_surbs: aux_info.num_surbs,
422 }
423 })))
424 ))
425 .map(Ok)
426 .forward_to_timeout(app_incoming)
427 .in_current_span()
428 .await;
429
430 if let Err(error) = res {
431 tracing::error!(
432 task = "transport (protocol - msg ingress)",
433 %error,
434 "long-running background task finished with error"
435 );
436 } else {
437 tracing::warn!(
438 task = "transport (protocol - msg ingress)",
439 "long-running background task finished"
440 )
441 }
442}
443
444async fn start_outgoing_ack_pipeline<AckOut, E, WOut>(
445 ack_outgoing: AckOut,
446 encoder: std::sync::Arc<E>,
447 cfg: AcknowledgementPipelineConfig,
448 packet_key: OffchainKeypair,
449 wire_outgoing: WOut,
450) where
451 AckOut: futures::Stream<Item = (OffchainPublicKey, Option<HalfKey>)> + Send + 'static,
452 E: PacketEncoder + Sync + Send + 'static,
453 WOut: futures::Sink<(PeerId, Bytes)> + Clone + Unpin + Send + 'static,
454 WOut::Error: std::error::Error,
455{
456 ack_outgoing
457 .map(move |(destination, maybe_ack_key)| {
458 let packet_key = packet_key.clone();
459 let ack = maybe_ack_key
461 .map(|ack_key| VerifiedAcknowledgement::new(ack_key, &packet_key))
462 .unwrap_or_else(|| VerifiedAcknowledgement::random(&packet_key));
463 (destination, ack)
464 })
465 .buffer(futures_time::time::Duration::from(cfg.ack_buffer_interval))
466 .filter(|acks| futures::future::ready(!acks.is_empty()))
467 .scan(
475 halfbrown::HashMap::<OffchainPublicKey, Vec<VerifiedAcknowledgement>, ahash::RandomState>::with_capacity_and_hasher(
476 cfg.ack_grouping_capacity,
477 ahash::RandomState::default(),
478 ),
479 |groups, buffered_acks| {
480 for (dst, ack) in buffered_acks {
481 groups
482 .entry(dst)
483 .and_modify(|v| v.push(ack))
484 .or_insert_with(|| vec![ack]);
485 }
486 tracing::trace!(
487 num_groups = groups.len(),
488 num_acks = groups.values().map(|v| v.len()).sum::<usize>(),
489 "acknowledgements grouped"
490 );
491 let drained: Vec<_> = groups.drain().collect();
492 futures::future::ready(Some(futures::stream::iter(drained)))
493 },
494 )
495 .flatten()
496 .for_each_concurrent(
497 cfg.ack_output_concurrency.filter(|&n| n > 0).unwrap_or(DEFAULT_ACK_OUTPUT_CONCURRENCY),
498 move |(destination, acks)| {
499 let encoder = encoder.clone();
500 let mut wire_outgoing = wire_outgoing.clone();
501 async move {
502 let c = acks.chunks(MAX_ACKNOWLEDGEMENTS_BATCH_SIZE).map(|c| c.to_vec()).collect::<Vec<_>>();
505 for ack_chunk in c {
506 let encoder = encoder.clone();
507 #[cfg(all(feature = "telemetry", not(test)))]
508 let ack_chunk_len = ack_chunk.len() as u64;
509 match hopr_utils::parallelize::cpu::spawn_fifo_blocking(move || encoder.encode_acknowledgements(&ack_chunk, &destination), "ack_encode").await {
510 Ok(Ok(ack_packet)) => {
511 wire_outgoing
512 .feed((ack_packet.next_hop.into(), ack_packet.data))
513 .await
514 .unwrap_or_else(|error| {
515 tracing::error!(%error, "failed to forward an acknowledgement to the transport layer");
516 });
517
518 #[cfg(all(feature = "telemetry", not(test)))]
519 METRIC_SENT_ACKS.increment_by(ack_chunk_len);
520 }
521 Ok(Err(error)) => tracing::error!(%error, "failed to encode acknowledgements"),
522 Err(error) => tracing::error!(%error, "parallel processing of the outgoing acknowledgements failed"),
523 }
524 }
525 if let Err(error) = wire_outgoing.flush().await {
526 tracing::error!(%error, "failed to flush acknowledgements batch to the transport layer");
527 }
528 tracing::trace!("acknowledgements out");
529 }.instrument(tracing::debug_span!("outgoing_ack_batch", peer = destination.to_peerid_str()))
530 }
531 )
532 .in_current_span()
533 .await;
534
535 tracing::warn!(
536 task = "transport (protocol - ack outgoing)",
537 "long-running background task finished"
538 );
539}
540
541async fn start_drain_incoming_ack_pipeline<AckIn>(ack_incoming: AckIn)
549where
550 AckIn: futures::Stream<Item = (OffchainPublicKey, Vec<Acknowledgement>)> + Send + 'static,
551{
552 ack_incoming
553 .for_each(move |(peer, acks)| {
554 tracing::trace!(%peer, num = acks.len(), "received acknowledgements (drained, not processed)");
555 futures::future::ready(())
556 })
557 .in_current_span()
558 .await;
559
560 tracing::warn!(
561 task = "transport (protocol - ticket acknowledgement drain)",
562 "long-running background task finished"
563 );
564}
565
566async fn start_relay_incoming_ack_pipeline<AckIn, T, TEvt>(
567 ack_incoming: AckIn,
568 ticket_events: TEvt,
569 ticket_proc: std::sync::Arc<T>,
570 concurrency: usize,
571) where
572 AckIn: futures::Stream<Item = (OffchainPublicKey, Vec<Acknowledgement>)> + Send + 'static,
573 T: UnacknowledgedTicketProcessor + Sync + Send + 'static,
574 TEvt: futures::Sink<TicketEvent> + Clone + Unpin + Send + 'static,
575 TEvt::Error: std::error::Error,
576{
577 ack_incoming
578 .for_each_concurrent(concurrency, move |(peer, acks)| {
579 let ticket_proc = ticket_proc.clone();
580 let mut ticket_evt = ticket_events.clone();
581 async move {
582 tracing::trace!(num = acks.len(), "received acknowledgements");
583 match hopr_utils::parallelize::cpu::spawn_fifo_blocking(
584 move || ticket_proc.acknowledge_tickets(peer, acks),
585 "ack_decode",
586 )
587 .await
588 {
589 Ok(Ok(resolutions)) if !resolutions.is_empty() => {
590 let resolutions_iter = resolutions.into_iter().filter_map(|resolution| match resolution {
591 ResolvedAcknowledgement::RelayingWin(redeemable_ticket) => {
592 tracing::trace!("received ack for a winning ticket");
593 #[cfg(all(feature = "telemetry", not(test)))]
594 {
595 METRIC_RECEIVED_ACKS.increment(&["true"]);
596 METRIC_TICKETS_COUNT.increment(&["winning"]);
597 }
598 Some(Ok(TicketEvent::WinningTicket(redeemable_ticket)))
599 }
600 ResolvedAcknowledgement::RelayingLoss(_) => {
601 tracing::trace!("received ack for a losing ticket");
603 #[cfg(all(feature = "telemetry", not(test)))]
604 {
605 METRIC_RECEIVED_ACKS.increment(&["true"]);
606 METRIC_TICKETS_COUNT.increment(&["losing"]);
607 }
608 None
609 }
610 });
611
612 if let Err(error) = ticket_evt.send_all(&mut futures::stream::iter(resolutions_iter)).await {
614 tracing::error!(%error, "failed to notify ticket resolutions");
615 }
616 }
617 Ok(Ok(_)) => {
618 tracing::debug!("acknowledgement batch could not acknowledge any ticket");
619 }
620 Ok(Err(TicketAcknowledgementError::UnexpectedAcknowledgement)) => {
621 tracing::trace!("received unexpected acknowledgement");
624 }
625 Ok(Err(error)) => {
626 tracing::error!(%error, "failed to acknowledge ticket");
627 }
628 Err(error) => {
629 tracing::error!(%error, "parallel processing of the incoming acknowledgements failed")
630 }
631 }
632 }
633 .instrument(tracing::debug_span!("incoming_ack_batch", peer = peer.to_peerid_str()))
634 })
635 .in_current_span()
636 .await;
637
638 tracing::warn!(
639 task = "transport (protocol - ticket acknowledgement)",
640 "long-running background task finished"
641 );
642}
643#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
652pub enum NodeType {
653 Relay,
654 Entry,
655 Exit,
656}
657
658#[derive(Debug, Default, Copy, Clone)]
663#[doc(hidden)]
664pub struct NoopTicketProcessor;
665
666impl UnacknowledgedTicketProcessor for NoopTicketProcessor {
667 type Error = std::convert::Infallible;
668
669 #[inline]
670 fn insert_unacknowledged_ticket(
671 &self,
672 _: &OffchainPublicKey,
673 _: HalfKeyChallenge,
674 _: UnacknowledgedTicket,
675 ) -> Result<(), Self::Error> {
676 Ok(())
677 }
678
679 #[inline]
680 fn acknowledge_tickets(
681 &self,
682 _: OffchainPublicKey,
683 _: Vec<Acknowledgement>,
684 ) -> Result<Vec<ResolvedAcknowledgement>, TicketAcknowledgementError<Self::Error>> {
685 Ok(Vec::with_capacity(0))
686 }
687}
688#[allow(clippy::too_many_arguments)]
691#[tracing::instrument(skip_all, level = "trace", fields(me = packet_key.public().to_peerid_str()))]
692pub(super) fn run_packet_pipeline_inner<WIn, WOut, C, D, T, TEvt, AppOut, AppIn>(
693 node_type: NodeType,
694 packet_key: OffchainKeypair,
695 wire_msg: (WOut, WIn),
696 codec: (C, D),
697 ticket_proc: T,
698 ticket_events: TEvt,
699 cfg: PacketPipelineConfig,
700 api: (AppOut, AppIn),
701 counters: PeerProtocolCounterRegistry,
702) -> AbortableList<PacketPipelineProcesses>
703where
704 WOut: futures::Sink<(PeerId, Bytes)> + Clone + Unpin + Send + 'static,
705 WOut::Error: std::error::Error,
706 WIn: futures::Stream<Item = (PeerId, Bytes)> + Send + 'static,
707 C: PacketEncoder + Sync + Send + 'static,
708 D: PacketDecoder + Sync + Send + 'static,
709 T: UnacknowledgedTicketProcessor + Sync + Send + 'static,
710 TEvt: futures::Sink<TicketEvent> + Clone + Unpin + Send + 'static,
711 TEvt::Error: std::error::Error,
712 AppOut: futures::Sink<(HoprPseudonym, ApplicationDataIn)> + Send + 'static,
713 AppOut::Error: std::error::Error,
714 AppIn: futures::Stream<Item = (ResolvedTransportRouting<HoprSurb>, ApplicationDataOut)> + Send + 'static,
715{
716 let mut processes = AbortableList::default();
717
718 #[cfg(all(feature = "telemetry", not(test)))]
719 {
720 lazy_static::initialize(&METRIC_PACKET_COUNT);
722 lazy_static::initialize(&METRIC_PACKET_DECODE_TIMEOUTS);
723 lazy_static::initialize(&METRIC_PACKET_REJECTED_COUNT);
724 lazy_static::initialize(&METRIC_VALIDATION_ERRORS);
725 }
726
727 let (outgoing_ack_tx, outgoing_ack_rx) = hopr_utils::network_types::crossfire_sink::bounded_sink_channel::<(
728 OffchainPublicKey,
729 Option<HalfKey>,
730 )>(cfg.ack_config.ack_out_buffer_size);
731
732 let (incoming_ack_tx, incoming_ack_rx) = hopr_utils::network_types::crossfire_sink::bounded_sink_channel::<(
733 OffchainPublicKey,
734 Vec<Acknowledgement>,
735 )>(cfg.ack_config.ticket_ack_buffer_size);
736
737 let (wire_out, wire_in) = (wire_msg.0.with_timeout(QUEUE_SEND_TIMEOUT), wire_msg.1);
740 let (app_out, app_in) = (api.0.with_timeout(QUEUE_SEND_TIMEOUT), api.1);
741 let incoming_ack_tx = incoming_ack_tx.with_timeout(QUEUE_SEND_TIMEOUT);
742 let outgoing_ack_tx = outgoing_ack_tx.with_timeout(QUEUE_SEND_TIMEOUT);
743 let ticket_events = ticket_events.with_timeout(QUEUE_SEND_TIMEOUT);
744
745 let encoder = std::sync::Arc::new(codec.0);
746 let decoder = std::sync::Arc::new(codec.1);
747 let ticket_proc = std::sync::Arc::new(ticket_proc);
748
749 let avail_concurrency = std::thread::available_parallelism()
752 .ok()
753 .map(|n| n.get())
754 .unwrap_or(1)
755 .max(1)
756 * 8;
757
758 let output_concurrency = cfg.output_concurrency.filter(|&n| n > 0).unwrap_or(avail_concurrency);
759 let input_concurrency = cfg.input_concurrency.filter(|&n| n > 0).unwrap_or(avail_concurrency);
760
761 processes.insert(
762 PacketPipelineProcesses::MsgOut,
763 hopr_utils::spawn_as_abortable!(
764 start_outgoing_packet_pipeline(
765 app_in,
766 encoder.clone(),
767 wire_out.clone(),
768 counters.clone(),
769 output_concurrency
770 )
771 .in_current_span()
772 ),
773 );
774
775 processes.insert(
776 PacketPipelineProcesses::MsgIn,
777 hopr_utils::spawn_as_abortable!(
778 start_incoming_packet_pipeline(
779 (wire_out.clone(), wire_in),
780 decoder,
781 ticket_proc.clone(),
782 ticket_events.clone(),
783 (outgoing_ack_tx, incoming_ack_tx),
784 app_out,
785 counters.clone(),
786 input_concurrency,
787 )
788 .in_current_span()
789 ),
790 );
791
792 processes.insert(
793 PacketPipelineProcesses::AckOut,
794 hopr_utils::spawn_as_abortable!(
795 start_outgoing_ack_pipeline(outgoing_ack_rx, encoder, cfg.ack_config, packet_key.clone(), wire_out,)
796 .in_current_span()
797 ),
798 );
799
800 let ack_input_concurrency = cfg
801 .ack_config
802 .ack_input_concurrency
803 .filter(|&n| n > 0)
804 .unwrap_or(DEFAULT_ACK_INPUT_CONCURRENCY);
805
806 match node_type {
807 NodeType::Relay => {
808 processes.insert(
809 PacketPipelineProcesses::AckIn,
810 hopr_utils::spawn_as_abortable!(
811 start_relay_incoming_ack_pipeline(
812 incoming_ack_rx,
813 ticket_events,
814 ticket_proc,
815 ack_input_concurrency
816 )
817 .in_current_span()
818 ),
819 );
820 }
821 NodeType::Exit => {
822 let _ = (ticket_events, ticket_proc, ack_input_concurrency);
826 processes.insert(
827 PacketPipelineProcesses::AckIn,
828 hopr_utils::spawn_as_abortable!(start_drain_incoming_ack_pipeline(incoming_ack_rx).in_current_span()),
829 );
830 }
831 NodeType::Entry => {
832 let _ = (ticket_events, ticket_proc, ack_input_concurrency);
836 processes.insert(
837 PacketPipelineProcesses::AckIn,
838 hopr_utils::spawn_as_abortable!(start_drain_incoming_ack_pipeline(incoming_ack_rx).in_current_span()),
839 );
840 }
841 }
842
843 processes
844}
845
846#[cfg(test)]
847mod tests {
848 use futures::channel::mpsc;
849
850 use super::*;
851
852 #[tokio::test]
862 async fn drain_pipeline_keeps_receiver_alive() {
863 let (tx, rx) = mpsc::channel::<(OffchainPublicKey, Vec<Acknowledgement>)>(16);
864 let drain = tokio::spawn(start_drain_incoming_ack_pipeline(rx));
865
866 tokio::task::yield_now().await;
868
869 assert!(!tx.is_closed(), "drain task must hold the receiver alive");
871
872 drop(tx);
874 drain
875 .await
876 .expect("drain task must finish cleanly after sender is dropped");
877 }
878
879 #[tokio::test]
881 async fn drain_pipeline_completes_on_empty_stream() {
882 let (tx, rx) = mpsc::channel::<(OffchainPublicKey, Vec<Acknowledgement>)>(32);
883 let drain = tokio::spawn(start_drain_incoming_ack_pipeline(rx));
884 drop(tx);
885 drain
886 .await
887 .expect("drain task must finish cleanly after sender is dropped");
888 }
889
890 #[tokio::test]
892 async fn disconnected_sender_returns_err_not_panic() {
893 let (tx, rx) = mpsc::channel::<u8>(4);
894 drop(rx);
895 let mut tx2 = tx.clone();
896 let result = tx2.send(42u8).await;
897 assert!(result.is_err(), "send to disconnected receiver must return Err");
898 }
899
900 #[tokio::test]
903 async fn session_management_dispatcher_survives_disconnected_sink() -> anyhow::Result<()> {
904 use anyhow::Context;
905 use futures::SinkExt;
906
907 let (data_tx, data_rx) = mpsc::channel::<u8>(4);
908
909 drop(data_rx);
911
912 let upstream = futures::stream::iter(0u8..10);
914
915 let dispatcher = tokio::spawn(async move {
917 upstream
918 .for_each(move |item| {
919 let mut tx = data_tx.clone();
920 async move {
921 let _ = tx.send(item).await;
923 }
924 })
925 .await;
926 });
927
928 dispatcher
930 .await
931 .context("dispatcher must complete cleanly even with a disconnected sink")?;
932 Ok(())
933 }
934
935 #[tokio::test]
938 async fn session_management_dispatcher_does_not_drop_upstream_on_disconnected_sink() -> anyhow::Result<()> {
939 use anyhow::Context;
940 use futures::SinkExt;
941
942 let (data_tx, data_rx) = mpsc::channel::<u8>(1);
943 drop(data_rx); let (upstream_tx, upstream_rx) = mpsc::channel::<u8>(16);
946 let upstream = upstream_rx;
947
948 let dispatcher = tokio::spawn(async move {
949 upstream
950 .for_each(move |item| {
951 let mut tx = data_tx.clone();
952 async move {
953 let _ = tx.send(item).await;
954 }
955 })
956 .await;
957 });
958
959 let mut sender = upstream_tx;
961 for i in 0u8..20 {
962 sender.send(i).await.context("upstream send must succeed")?;
963 }
964 drop(sender); dispatcher
967 .await
968 .context("dispatcher must complete when upstream closes, even with disconnected sink")?;
969 Ok(())
970 }
971}