Skip to main content

hopr_protocol_hopr/codec/
mod.rs

1mod decoder;
2mod encoder;
3
4pub use decoder::HoprDecoder;
5pub use encoder::{HoprEncoder, MAX_ACKNOWLEDGEMENTS_BATCH_SIZE};
6
7/// Configuration of [`HoprEncoder`] and [`HoprDecoder`].
8#[cfg_attr(feature = "serde", cfg_eval::cfg_eval, serde_with::serde_as)]
9#[derive(Clone, Copy, Debug, smart_default::SmartDefault, validator::Validate)]
10#[cfg_attr(
11    feature = "serde",
12    derive(serde::Serialize, serde::Deserialize),
13    serde(deny_unknown_fields)
14)]
15pub struct HoprCodecConfig {
16    /// Optional price of outgoing tickets.
17    ///
18    /// If not set (default), the network default will be used, which is the minimum allowed ticket price in the HOPR
19    /// network.
20    #[cfg_attr(
21        feature = "serde",
22        serde(default),
23        serde_as(as = "Option<serde_with::DisplayFromStr>")
24    )]
25    pub outgoing_ticket_price: Option<hopr_api::types::primitive::balance::HoprBalance>,
26    /// Optional minimum price of incoming tickets.
27    ///
28    /// The value cannot be lower than the default outgoing ticket price times the node's path position.
29    ///
30    /// If not set (default), the network default outgoing ticket price times the node's path position
31    /// will be used.
32    #[cfg_attr(
33        feature = "serde",
34        serde(default),
35        serde_as(as = "Option<serde_with::DisplayFromStr>")
36    )]
37    pub min_incoming_ticket_price: Option<hopr_api::types::primitive::balance::HoprBalance>,
38    /// Optional probability of winning an outgoing ticket.
39    ///
40    /// If not set (default), the network default will be used, which is the minimum allowed winning probability in the
41    /// HOPR network.
42    #[cfg_attr(
43        feature = "serde",
44        serde(default),
45        serde_as(as = "Option<serde_with::DisplayFromStr>")
46    )]
47    pub outgoing_win_prob: Option<hopr_api::types::internal::prelude::WinningProbability>,
48}
49
50impl PartialEq for HoprCodecConfig {
51    fn eq(&self, other: &Self) -> bool {
52        self.outgoing_ticket_price.eq(&other.outgoing_ticket_price)
53            && match (self.outgoing_win_prob, other.outgoing_win_prob) {
54                (Some(a), Some(b)) => a.approx_eq(&b),
55                (None, None) => true,
56                _ => false,
57            }
58    }
59}
60
61#[cfg(test)]
62mod tests {
63    use std::sync::Arc;
64
65    use hopr_api::types::{
66        crypto::prelude::*,
67        crypto_random::Randomizable,
68        internal::{prelude::*, routing::ResolvedTransportRouting},
69    };
70    use hopr_chain_connector::{
71        HoprBlockchainSafeConnector,
72        testing::{BlokliTestClient, StaticState},
73    };
74    use hopr_ticket_manager::{HoprTicketFactory, MemoryStore};
75
76    use crate::{
77        HoprCodecConfig, HoprDecoder, HoprEncoder, MemorySurbStore, PacketDecoder, PacketEncoder, SurbStoreConfig,
78        codec::encoder::MAX_ACKNOWLEDGEMENTS_BATCH_SIZE, utils::*,
79    };
80
81    type TestEncoder = HoprEncoder<
82        Arc<HoprBlockchainSafeConnector<BlokliTestClient<StaticState>>>,
83        MemorySurbStore,
84        HoprTicketFactory<MemoryStore>,
85    >;
86
87    type TestDecoder = HoprDecoder<
88        Arc<HoprBlockchainSafeConnector<BlokliTestClient<StaticState>>>,
89        MemorySurbStore,
90        HoprTicketFactory<MemoryStore>,
91    >;
92
93    pub fn create_encoder(sender: &Node) -> TestEncoder {
94        HoprEncoder::new(
95            sender.chain_key.clone(),
96            sender.chain_api.clone(),
97            MemorySurbStore::new(SurbStoreConfig::default()),
98            HoprTicketFactory::new(MemoryStore::default()),
99            Hash::default(),
100            HoprCodecConfig::default(),
101        )
102    }
103
104    pub fn create_decoder(receiver: &Node) -> TestDecoder {
105        HoprDecoder::new(
106            (receiver.offchain_key.clone(), receiver.chain_key.clone()),
107            receiver.chain_api.clone(),
108            MemorySurbStore::new(SurbStoreConfig::default()),
109            HoprTicketFactory::new(MemoryStore::default()),
110            Hash::default(),
111            HoprCodecConfig::default(),
112        )
113    }
114
115    #[tokio::test]
116    async fn encode_decode_packet() -> anyhow::Result<()> {
117        let blokli_client = create_blokli_client()?;
118        let sender = create_node(0, &blokli_client).await?;
119        let receiver = create_node(1, &blokli_client).await?;
120
121        let encoder = create_encoder(&sender);
122        let decoder = create_decoder(&receiver);
123
124        let data = b"some random message to encode and decode";
125
126        let out_packet = encoder.encode_packet(
127            data,
128            ResolvedTransportRouting::Forward {
129                pseudonym: HoprPseudonym::random(),
130                forward_path: ValidatedPath::direct(
131                    *receiver.offchain_key.public(),
132                    receiver.chain_key.public().to_address(),
133                ),
134                return_paths: vec![],
135            },
136            None,
137        )?;
138
139        let in_packet = decoder.decode(sender.offchain_key.public().into(), out_packet.data)?;
140        let in_packet = in_packet.try_as_final().ok_or(anyhow::anyhow!("packet is not final"))?;
141
142        assert_eq!(data, in_packet.plain_text.as_ref());
143        Ok(())
144    }
145
146    #[tokio::test]
147    async fn encode_decode_packet_should_fail_for_too_long_messages() -> anyhow::Result<()> {
148        let blokli_client = create_blokli_client()?;
149        let sender = create_node(0, &blokli_client).await?;
150        let receiver = create_node(1, &blokli_client).await?;
151
152        let encoder = create_encoder(&sender);
153
154        let data = hopr_api::types::crypto_random::random_bytes::<2048>();
155
156        assert!(
157            encoder
158                .encode_packet(
159                    data,
160                    ResolvedTransportRouting::Forward {
161                        pseudonym: HoprPseudonym::random(),
162                        forward_path: ValidatedPath::direct(
163                            *receiver.offchain_key.public(),
164                            receiver.chain_key.public().to_address(),
165                        ),
166                        return_paths: vec![],
167                    },
168                    None,
169                )
170                .is_err()
171        );
172
173        Ok(())
174    }
175
176    #[tokio::test]
177    async fn encode_decode_packet_on_relay() -> anyhow::Result<()> {
178        let blokli_client = create_blokli_client()?;
179        let sender = create_node(0, &blokli_client).await?;
180        let relay = create_node(1, &blokli_client).await?;
181        let receiver = create_node(2, &blokli_client).await?;
182
183        let sender_encoder = create_encoder(&sender);
184        let relay_decoder = create_decoder(&relay);
185        let receiver_decoder = create_decoder(&receiver);
186
187        let data = b"some random message to encode and decode";
188
189        let out_packet = sender_encoder.encode_packet(
190            data,
191            ResolvedTransportRouting::Forward {
192                pseudonym: HoprPseudonym::random(),
193                forward_path: ValidatedPath::new(
194                    sender.chain_key.public().to_address(),
195                    vec![
196                        relay.chain_key.public().to_address(),
197                        receiver.chain_key.public().to_address(),
198                    ],
199                    &sender.chain_api.as_path_resolver(),
200                )
201                .await?,
202                return_paths: vec![],
203            },
204            None,
205        )?;
206
207        let fwd_packet = relay_decoder.decode(sender.offchain_key.public().into(), out_packet.data)?;
208        let fwd_packet = fwd_packet
209            .try_as_forwarded()
210            .ok_or(anyhow::anyhow!("packet is not forwarded"))?;
211
212        let in_packet = receiver_decoder.decode(relay.offchain_key.public().into(), fwd_packet.data)?;
213        let in_packet = in_packet.try_as_final().ok_or(anyhow::anyhow!("packet is not final"))?;
214
215        assert_eq!(data, in_packet.plain_text.as_ref());
216        Ok(())
217    }
218
219    #[tokio::test]
220    async fn encode_decode_acknowledgements() -> anyhow::Result<()> {
221        let blokli_client = create_blokli_client()?;
222        let sender = create_node(0, &blokli_client).await?;
223        let receiver = create_node(1, &blokli_client).await?;
224
225        let encoder = create_encoder(&sender);
226        let decoder = create_decoder(&receiver);
227
228        let acks = (0..MAX_ACKNOWLEDGEMENTS_BATCH_SIZE)
229            .map(|_| VerifiedAcknowledgement::random(&PEERS[0].1))
230            .collect::<Vec<_>>();
231        let out_packet = encoder.encode_acknowledgements(&acks, PEERS[1].1.public())?;
232
233        let in_packet = decoder.decode(sender.offchain_key.public().into(), out_packet.data)?;
234        let in_packet = in_packet
235            .try_as_acknowledgement()
236            .ok_or(anyhow::anyhow!("packet is not acknowledgement"))?;
237
238        assert_eq!(acks.len(), in_packet.received_acks.len());
239
240        for (i, ack) in in_packet.received_acks.into_iter().enumerate() {
241            let verified = ack.verify(PEERS[0].1.public())?;
242            assert_eq!(acks[i], verified);
243        }
244
245        Ok(())
246    }
247
248    #[tokio::test]
249    async fn encode_should_fail_on_too_many_acknowledgements() -> anyhow::Result<()> {
250        let blokli_client = create_blokli_client()?;
251        let sender = create_node(0, &blokli_client).await?;
252
253        let encoder = create_encoder(&sender);
254        let acks = (0..MAX_ACKNOWLEDGEMENTS_BATCH_SIZE + 1)
255            .map(|_| VerifiedAcknowledgement::random(&PEERS[0].1))
256            .collect::<Vec<_>>();
257
258        assert!(encoder.encode_acknowledgements(&acks, PEERS[1].1.public()).is_err());
259
260        Ok(())
261    }
262
263    #[tokio::test]
264    async fn decode_should_fail_on_invalid_data() -> anyhow::Result<()> {
265        let blokli_client = create_blokli_client()?;
266        let receiver = create_node(0, &blokli_client).await?;
267        let decoder = create_decoder(&receiver);
268
269        let sender_peer_id = PEERS[1].1.public().into();
270        let invalid_data = bytes::Bytes::from(vec![0u8; 100]);
271
272        let result = decoder.decode(sender_peer_id, invalid_data);
273        assert!(result.is_err());
274        assert!(result.unwrap_err().is_undecodable());
275
276        Ok(())
277    }
278
279    #[tokio::test]
280    async fn decode_should_fail_on_replay() -> anyhow::Result<()> {
281        let blokli_client = create_blokli_client()?;
282        let sender = create_node(0, &blokli_client).await?;
283        let receiver = create_node(1, &blokli_client).await?;
284
285        let encoder = create_encoder(&sender);
286        let decoder = create_decoder(&receiver);
287
288        let data = b"some message";
289        let out_packet = encoder.encode_packet(
290            data,
291            ResolvedTransportRouting::Forward {
292                pseudonym: HoprPseudonym::random(),
293                forward_path: ValidatedPath::direct(
294                    *receiver.offchain_key.public(),
295                    receiver.chain_key.public().to_address(),
296                ),
297                return_paths: vec![],
298            },
299            None,
300        )?;
301
302        // First decode should succeed
303        decoder.decode(sender.offchain_key.public().into(), out_packet.data.clone())?;
304
305        // Second decode with same packet should fail (replay)
306        let result = decoder.decode(sender.offchain_key.public().into(), out_packet.data);
307        assert!(result.is_err());
308        assert!(result.unwrap_err().is_processing_error());
309
310        Ok(())
311    }
312
313    #[tokio::test]
314    async fn decode_should_fail_on_incorrect_key() -> anyhow::Result<()> {
315        let blokli_client = create_blokli_client()?;
316        let sender = create_node(0, &blokli_client).await?;
317        let receiver = create_node(1, &blokli_client).await?;
318        let incorrect_receiver = create_node(2, &blokli_client).await?;
319
320        let encoder = create_encoder(&sender);
321        let decoder = create_decoder(&incorrect_receiver);
322
323        let data = b"some message";
324        let out_packet = encoder.encode_packet(
325            data,
326            ResolvedTransportRouting::Forward {
327                pseudonym: HoprPseudonym::random(),
328                forward_path: ValidatedPath::direct(
329                    *receiver.offchain_key.public(),
330                    receiver.chain_key.public().to_address(),
331                ),
332                return_paths: vec![],
333            },
334            None,
335        )?;
336
337        let result = decoder.decode(sender.offchain_key.public().into(), out_packet.data);
338        assert!(result.is_err());
339        assert!(result.unwrap_err().is_undecodable());
340
341        Ok(())
342    }
343}