1use std::{ops::Mul, sync::atomic::AtomicU64, time::Duration};
2
3use bytes::{BufMut, Bytes, BytesMut};
4use hopr_api::{
5 chain::*,
6 types::{crypto::prelude::*, internal::prelude::*, primitive::prelude::*},
7};
8use hopr_crypto_packet::prelude::*;
9use hopr_utils::trace_timed;
10
11use crate::{
12 AuxiliaryPacketInfo, HoprCodecConfig, IncomingAcknowledgementPacket, IncomingFinalPacket, IncomingForwardedPacket,
13 IncomingPacket, IncomingPacketError, PacketDecoder, SurbStore, errors::HoprProtocolError, tbf::TagBloomFilter,
14};
15
16const SURB_EVICTION_WARN_INTERVAL: u64 = 256;
23
24static SURB_EVICTING_INSERTS: AtomicU64 = AtomicU64::new(0);
30
31static SURB_EVICTED_TOTAL: AtomicU64 = AtomicU64::new(0);
33
34pub struct HoprDecoder<Chain, S, T> {
36 chain_api: Chain,
37 surb_store: std::sync::Arc<S>,
38 ticket_factory: T,
39 packet_key: OffchainKeypair,
40 chain_key: ChainKeypair,
41 channels_dst: Hash,
42 cfg: HoprCodecConfig,
43 tbf: parking_lot::Mutex<TagBloomFilter>,
44 peer_id_cache: moka::sync::Cache<PeerId, OffchainPublicKey>,
45}
46
47impl<Chain, S, T> HoprDecoder<Chain, S, T>
48where
49 Chain: ChainReadChannelOperations + ChainKeyOperations + ChainReadTicketOperations + ChainValues + Send + Sync,
50 S: SurbStore + Send + Sync,
51 T: hopr_api::tickets::TicketFactory + Send + Sync,
52{
53 pub fn new(
55 (packet_key, chain_key): (OffchainKeypair, ChainKeypair),
56 chain_api: Chain,
57 surb_store: S,
58 ticket_factory: T,
59 channels_dst: Hash,
60 cfg: HoprCodecConfig,
61 ) -> Self {
62 Self {
63 chain_api,
64 surb_store: std::sync::Arc::new(surb_store),
65 packet_key,
66 chain_key,
67 channels_dst,
68 cfg,
69 ticket_factory,
70 tbf: parking_lot::Mutex::new(Default::default()),
71 peer_id_cache: moka::sync::Cache::builder()
72 .time_to_idle(Duration::from_secs(600))
73 .max_capacity(100_000)
74 .build(),
75 }
76 }
77
78 #[tracing::instrument(skip(self, fwd), level = "debug", fields(path_pos = fwd.path_pos))]
79 fn validate_and_replace_ticket(
80 &self,
81 mut fwd: HoprForwardedPacket,
82 ) -> Result<(HoprForwardedPacket, UnacknowledgedTicket), HoprProtocolError> {
83 let previous_hop_addr = trace_timed!("previous_hop_addr lookup", {
84 self.chain_api
85 .packet_key_to_chain_key(&fwd.previous_hop)
86 .map_err(HoprProtocolError::resolver)?
87 .ok_or(HoprProtocolError::KeyNotFound)?
88 });
89
90 let next_hop_addr = trace_timed!("next_hop_addr lookup", {
91 self.chain_api
92 .packet_key_to_chain_key(&fwd.outgoing.next_hop)
93 .map_err(HoprProtocolError::resolver)?
94 .ok_or(HoprProtocolError::KeyNotFound)?
95 });
96
97 let incoming_channel = trace_timed!("incoming_channel lookup", {
98 self.chain_api
99 .channel_by_parties(&previous_hop_addr, self.chain_key.as_ref())
100 .map_err(HoprProtocolError::resolver)?
101 .ok_or_else(|| HoprProtocolError::ChannelNotFound(previous_hop_addr, *self.chain_key.as_ref()))?
102 });
103
104 let (win_prob, minimum_ticket_price) = self
107 .chain_api
108 .incoming_ticket_values()
109 .map_err(HoprProtocolError::resolver)?;
110
111 let minimum_ticket_price = minimum_ticket_price
112 .mul(U256::from(fwd.path_pos))
113 .max(self.cfg.min_incoming_ticket_price.unwrap_or_default());
114
115 let remaining_balance = trace_timed!("unrealized_balance lookup", {
116 self.ticket_factory
117 .remaining_incoming_channel_stake(&incoming_channel)
118 .map_err(HoprProtocolError::ticket_factory)?
119 });
120
121 let verified_incoming_ticket = trace_timed!("ticket_signature_verification", {
127 validate_unacknowledged_ticket(
128 fwd.outgoing.ticket,
129 &incoming_channel,
130 minimum_ticket_price,
131 win_prob,
132 remaining_balance,
133 &self.channels_dst,
134 )
135 })?;
136
137 tracing::trace!(%verified_incoming_ticket, "successfully verified incoming ticket");
139
140 let ticket_builder = if fwd.path_pos > 1 {
146 let outgoing_channel = self
150 .chain_api
151 .channel_by_parties(self.chain_key.as_ref(), &next_hop_addr)
152 .map_err(HoprProtocolError::resolver)?
153 .ok_or_else(|| HoprProtocolError::ChannelNotFound(*self.chain_key.as_ref(), next_hop_addr))?;
154
155 let (outgoing_ticket_win_prob, outgoing_ticket_price) = self
156 .chain_api
157 .outgoing_ticket_values(self.cfg.outgoing_win_prob, self.cfg.outgoing_ticket_price)
158 .map_err(HoprProtocolError::resolver)?;
159
160 let outgoing_ticket_win_prob = outgoing_ticket_win_prob.max(&verified_incoming_ticket.win_prob());
164
165 self.ticket_factory
169 .new_multihop_ticket(
170 &outgoing_channel,
171 fwd.path_pos.try_into().expect("path position is always > 1"),
172 outgoing_ticket_win_prob,
173 outgoing_ticket_price,
174 )
175 .map_err(HoprProtocolError::ticket_factory)?
176 } else {
177 TicketBuilder::zero_hop().counterparty(next_hop_addr)
178 };
179
180 let ticket_builder = ticket_builder.eth_challenge(fwd.next_challenge);
182 fwd.outgoing.ticket = trace_timed!("ticket_signing", {
183 ticket_builder.build_signed(&self.chain_key, &self.channels_dst)?.leak()
184 });
185
186 let unack_ticket = verified_incoming_ticket.into_unacknowledged(fwd.own_key);
187 Ok((fwd, unack_ticket))
188 }
189}
190
191impl<Chain, S, T> PacketDecoder for HoprDecoder<Chain, S, T>
192where
193 Chain: ChainReadChannelOperations + ChainKeyOperations + ChainReadTicketOperations + ChainValues + Send + Sync,
194 S: SurbStore + Send + Sync + 'static,
195 T: hopr_api::tickets::TicketFactory + Send + Sync,
196{
197 type Error = HoprProtocolError;
198
199 #[tracing::instrument(skip(self, sender, data), level = "trace", fields(%sender))]
200 fn decode(&self, sender: PeerId, data: Bytes) -> Result<IncomingPacket, IncomingPacketError<Self::Error>> {
201 #[cfg(feature = "trace-timing")]
202 let decode_start = std::time::Instant::now();
203 tracing::trace!(data_len = data.len(), "decoding packet");
204
205 let previous_hop = trace_timed!("peer_id_conversion complete", {
209 match self
210 .peer_id_cache
211 .try_get_with_by_ref(&sender, || OffchainPublicKey::from_peerid(&sender))
212 {
213 Ok(peer) => Ok(peer),
214 Err(error) => {
215 tracing::error!(%sender, %error, "dropping packet - cannot convert peer id");
216 Err(IncomingPacketError::Undecodable(HoprProtocolError::InvalidSender))
217 }
218 }
219 })?;
220
221 let replied_on_surb = std::cell::Cell::new(None);
229 let packet = trace_timed!("sphinx_decode complete", {
230 HoprPacket::from_incoming(
231 &data,
232 &self.packet_key,
233 previous_hop,
234 self.chain_api.key_id_mapper_ref(),
235 |p| {
236 let opener = self.surb_store.find_reply_opener(p);
237 if opener.is_some() {
238 replied_on_surb.set(Some(p.surb_id()));
239 }
240 opener
241 },
242 )
243 })
244 .map_err(IncomingPacketError::undecodable)?;
245
246 if let Some(tag) = packet.packet_tag() {
249 if self.tbf.lock().check_and_set(tag) {
252 return Err(IncomingPacketError::ProcessingError(
253 previous_hop.into(),
254 HoprProtocolError::Replay,
255 ));
256 }
257 }
258
259 match packet {
260 HoprPacket::Final(incoming) => {
261 let mut info = AuxiliaryPacketInfo {
263 packet_signals: incoming.signals,
264 num_surbs: incoming.surbs.len(),
265 num_evicted_surbs: 0,
266 };
267
268 if !incoming.surbs.is_empty() {
270 let outcome = self.surb_store.insert_surbs(incoming.sender, incoming.surbs);
271 info.num_evicted_surbs = outcome.evicted;
272 tracing::trace!(pseudonym = %incoming.sender, num_surbs = info.num_surbs, retained = outcome.retained, packet_type = "final", "stored incoming surbs for pseudonym");
273
274 if outcome.evicted > 0 {
280 let total_evicted = SURB_EVICTED_TOTAL
281 .fetch_add(outcome.evicted as u64, std::sync::atomic::Ordering::Relaxed)
282 + outcome.evicted as u64;
283 let prev_inserts = SURB_EVICTING_INSERTS.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
284
285 if prev_inserts.is_multiple_of(SURB_EVICTION_WARN_INTERVAL) {
288 tracing::warn!(
289 pseudonym = %incoming.sender,
290 evicted = outcome.evicted,
291 retained = outcome.retained,
292 total_evicted,
293 overflowing_inserts = prev_inserts + 1,
294 "SURB buffer full; dropping the oldest SURBs"
295 );
296 }
297 }
298 }
299
300 let result = match incoming.ack_key {
301 None => {
302 if incoming.plain_text.len() < size_of::<u16>() {
303 return Err(IncomingPacketError::Undecodable(
304 GeneralError::ParseError("invalid acknowledgement packet size".into()).into(),
305 ));
306 }
307
308 let num_acks =
309 u16::from_be_bytes(incoming.plain_text[..size_of::<u16>()].try_into().map_err(|_| {
310 IncomingPacketError::Undecodable(
311 GeneralError::ParseError("invalid num acks".into()).into(),
312 )
313 })?);
314
315 if incoming.plain_text.len() < size_of::<u16>() + (num_acks as usize) * Acknowledgement::SIZE {
316 return Err(IncomingPacketError::Undecodable(
317 GeneralError::ParseError("invalid number of acknowledgements in packet".into()).into(),
318 ));
319 }
320 tracing::trace!(num_acks, packet_type = "final", "received acknowledgement packet");
321
322 IncomingPacket::Acknowledgement(
324 IncomingAcknowledgementPacket {
325 packet_tag: incoming.packet_tag,
326 previous_hop: incoming.previous_hop,
327 received_acks: incoming.plain_text
328 [size_of::<u16>()..size_of::<u16>() + num_acks as usize * Acknowledgement::SIZE]
329 .chunks_exact(Acknowledgement::SIZE)
330 .map(Acknowledgement::try_from)
331 .collect::<Result<Vec<_>, _>>()
332 .map_err(|e: GeneralError| IncomingPacketError::Undecodable(e.into()))?,
333 }
334 .into(),
335 )
336 }
337 Some(ack_key) => IncomingPacket::Final(
338 IncomingFinalPacket {
339 packet_tag: incoming.packet_tag,
340 previous_hop: incoming.previous_hop,
341 sender: incoming.sender,
342 replied_on_surb: replied_on_surb.get(),
343 plain_text: incoming.plain_text,
344 ack_key,
345 info,
346 }
347 .into(),
348 ),
349 };
350 #[cfg(feature = "trace-timing")]
351 tracing::trace!(
352 total_ms = decode_start.elapsed().as_millis() as u64,
353 packet_type = "final",
354 "decode complete"
355 );
356 Ok(result)
357 }
358 HoprPacket::Forwarded(fwd) => {
359 let (fwd, verified_unack_ticket) = trace_timed!("ticket_validation complete", {
362 self.validate_and_replace_ticket(*fwd).map_err(|error| match error {
363 HoprProtocolError::TicketValidationError(e) => {
365 IncomingPacketError::InvalidTicket(previous_hop.into(), e)
366 }
367 e => IncomingPacketError::ProcessingError(previous_hop.into(), e),
368 })?
369 });
370
371 let mut payload = BytesMut::with_capacity(HoprPacket::SIZE);
372 payload.put_slice(fwd.outgoing.packet.as_ref());
373 payload.put_slice(&fwd.outgoing.ticket.into_encoded());
374
375 #[cfg(feature = "trace-timing")]
376 tracing::trace!(
377 total_ms = decode_start.elapsed().as_millis() as u64,
378 packet_type = "forwarded",
379 "decode complete"
380 );
381 Ok(IncomingPacket::Forwarded(
382 IncomingForwardedPacket {
383 packet_tag: fwd.packet_tag,
384 previous_hop: fwd.previous_hop,
385 next_hop: fwd.outgoing.next_hop,
386 data: payload.freeze(),
387 ack_challenge: fwd.outgoing.ack_challenge,
388 received_ticket: verified_unack_ticket,
389 ack_key_prev_hop: fwd.ack_key,
390 }
391 .into(),
392 ))
393 }
394 HoprPacket::Outgoing(_) => {
395 #[cfg(feature = "trace-timing")]
396 tracing::trace!(
397 total_ms = decode_start.elapsed().as_millis() as u64,
398 packet_type = "outgoing",
399 "decode complete"
400 );
401 Err(IncomingPacketError::ProcessingError(
402 previous_hop.into(),
403 HoprProtocolError::InvalidState("cannot be outgoing packet"),
404 ))
405 }
406 }
407 }
408}