hopr_path/
lib.rs

1//! This Rust crate contains all the path construction and path selection algorithms in the HOPR mixnet.
2
3/// Defines the graph of HOPR payment channels.
4pub mod channel_graph;
5pub mod errors;
6/// Implements different path selectors in the [ChannelGraph](crate::channel_graph::ChannelGraph).
7pub mod selectors;
8
9use std::{
10    fmt::{Display, Formatter},
11    hash::Hash,
12    ops::Deref,
13};
14
15use hopr_crypto_types::prelude::*;
16use hopr_internal_types::prelude::*;
17use hopr_primitive_types::prelude::*;
18
19use crate::{
20    channel_graph::ChannelGraph,
21    errors::{
22        PathError,
23        PathError::{ChannelNotOpened, InvalidPeer, LoopsNotAllowed, MissingChannel, PathNotValid},
24    },
25};
26
27/// Represents a type that determines a hop on a [`Path`].
28#[allow(clippy::large_enum_variant)] // TODO: use CompactOffchainPublicKey
29#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, strum::EnumTryAs, strum::EnumIs)]
30#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
31pub enum PathAddress {
32    Chain(Address),
33    Transport(OffchainPublicKey),
34}
35
36impl From<Address> for PathAddress {
37    fn from(value: Address) -> Self {
38        PathAddress::Chain(value)
39    }
40}
41
42impl From<OffchainPublicKey> for PathAddress {
43    fn from(value: OffchainPublicKey) -> Self {
44        PathAddress::Transport(value)
45    }
46}
47
48impl Display for PathAddress {
49    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
50        match self {
51            PathAddress::Chain(addr) => write!(f, "{}", addr.to_hex()),
52            PathAddress::Transport(key) => write!(f, "{}", key.to_hex()),
53        }
54    }
55}
56
57/// Base implementation of an abstract path.
58pub trait Path<N>: Clone + Eq + PartialEq + Deref<Target = [N]> + IntoIterator<Item = N>
59where
60    N: Into<PathAddress> + Copy,
61{
62    /// Individual hops in the path.
63    /// There must be always at least one hop.
64    fn hops(&self) -> &[N] {
65        self.deref()
66    }
67
68    /// Shorthand for the number of hops.
69    fn num_hops(&self) -> usize {
70        self.hops().len()
71    }
72
73    /// Returns the path with the hops in reverse order if it is possible.
74    fn invert(self) -> Option<Self>;
75
76    /// Checks if the path contains some entry twice.
77    fn contains_cycle(&self) -> bool {
78        std::collections::HashSet::<_, std::hash::RandomState>::from_iter(self.iter().copied().map(|h| h.into())).len()
79            != self.num_hops()
80    }
81}
82
83/// A [`Path`] that is guaranteed to have at least one hop - the destination.
84pub trait NonEmptyPath<N: Into<PathAddress> + Copy>: Path<N> {
85    /// Gets the last hop (destination)
86    fn last_hop(&self) -> &N {
87        self.hops().last().expect("non-empty path must have at least one hop")
88    }
89}
90
91impl<T: Into<PathAddress> + Copy + PartialEq + Eq> Path<T> for Vec<T> {
92    fn invert(self) -> Option<Self> {
93        Some(self.into_iter().rev().collect())
94    }
95}
96
97pub type ChannelPath = Vec<Address>;
98
99/// A [`NonEmptyPath`] that can be used to route packets using [`OffchainPublicKeys`].
100#[derive(Clone, Debug, PartialEq, Eq)]
101pub struct TransportPath(Vec<OffchainPublicKey>);
102
103impl TransportPath {
104    /// Creates a new instance from the given iterator.
105    ///
106    /// Fails if the iterator is empty.
107    pub fn new<T, I>(path: I) -> errors::Result<Self>
108    where
109        T: Into<OffchainPublicKey>,
110        I: IntoIterator<Item = T>,
111    {
112        let hops = path.into_iter().map(|t| t.into()).collect::<Vec<_>>();
113        if !hops.is_empty() {
114            Ok(Self(hops))
115        } else {
116            Err(PathNotValid)
117        }
118    }
119
120    /// Creates a direct path just to the `destination`.
121    pub fn direct(destination: OffchainPublicKey) -> Self {
122        Self(vec![destination])
123    }
124}
125
126impl Deref for TransportPath {
127    type Target = [OffchainPublicKey];
128
129    fn deref(&self) -> &Self::Target {
130        &self.0
131    }
132}
133
134impl IntoIterator for TransportPath {
135    type IntoIter = std::vec::IntoIter<Self::Item>;
136    type Item = OffchainPublicKey;
137
138    fn into_iter(self) -> Self::IntoIter {
139        self.0.into_iter()
140    }
141}
142
143impl Path<OffchainPublicKey> for TransportPath {
144    fn invert(self) -> Option<Self> {
145        Some(Self(self.0.into_iter().rev().collect()))
146    }
147}
148
149impl NonEmptyPath<OffchainPublicKey> for TransportPath {}
150
151/// Represents a [`NonEmptyPath`] that completely specifies a route using [`Addresses`](Address).
152///
153/// Transport cannot directly use this to deliver packets.
154///
155/// Note that this is different from [`ChannelPath`], which can be empty and does not contain
156/// the address of the destination.
157#[derive(Clone, Debug, PartialEq, Eq)]
158pub struct ChainPath(Vec<Address>);
159
160impl ChainPath {
161    /// Creates a new instance from the given iterator.
162    ///
163    /// Fails if the iterator is empty.
164    pub fn new<T, I>(path: I) -> errors::Result<Self>
165    where
166        T: Into<Address>,
167        I: IntoIterator<Item = T>,
168    {
169        let hops = path.into_iter().map(|t| t.into()).collect::<Vec<_>>();
170        if !hops.is_empty() {
171            Ok(Self(hops))
172        } else {
173            Err(PathNotValid)
174        }
175    }
176
177    /// Creates a path using the given [`ChannelPath`] (possibly empty) and the given `destination` address.
178    pub fn from_channel_path(mut path: ChannelPath, destination: Address) -> Self {
179        path.push(destination);
180        Self(path)
181    }
182
183    /// Creates a direct path just to the `destination`.
184    pub fn direct(destination: Address) -> Self {
185        Self(vec![destination])
186    }
187
188    /// Converts this chain path into the [`ChainPath`] by removing the destination.
189    pub fn into_channel_path(mut self) -> ChannelPath {
190        self.0.pop();
191        self.0
192    }
193}
194
195impl Deref for ChainPath {
196    type Target = [Address];
197
198    fn deref(&self) -> &Self::Target {
199        &self.0
200    }
201}
202
203impl Display for ChainPath {
204    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
205        write!(
206            f,
207            "chain path [{}]",
208            self.0.iter().map(|p| p.to_hex()).collect::<Vec<String>>().join(", ")
209        )
210    }
211}
212
213impl From<ChainPath> for ChannelPath {
214    fn from(value: ChainPath) -> Self {
215        let len = value.0.len();
216        value.0.into_iter().take(len - 1).collect()
217    }
218}
219
220impl IntoIterator for ChainPath {
221    type IntoIter = std::vec::IntoIter<Self::Item>;
222    type Item = Address;
223
224    fn into_iter(self) -> Self::IntoIter {
225        self.0.into_iter()
226    }
227}
228
229impl Path<Address> for ChainPath {
230    fn invert(self) -> Option<Self> {
231        Some(Self(self.0.into_iter().rev().collect()))
232    }
233}
234
235impl NonEmptyPath<Address> for ChainPath {}
236
237/// Allows resolution of [`OffchainPublicKeys`] for a given [`Address`] or vice versa.
238#[cfg_attr(test, mockall::automock)]
239#[async_trait::async_trait]
240pub trait PathAddressResolver {
241    /// Resolve [`OffchainPublicKey`] from the given [`Address`]
242    async fn resolve_transport_address(&self, address: &Address) -> Result<Option<OffchainPublicKey>, PathError>;
243    /// Resolve [`Address`] from the given [`OffchainPublicKey`]
244    async fn resolve_chain_address(&self, key: &OffchainPublicKey) -> Result<Option<Address>, PathError>;
245}
246
247/// Represents [`NonEmptyPath`] that has been resolved and validated.
248///
249/// Such a path can be directly used to deliver packets.
250#[derive(Clone, Debug, PartialEq, Eq)]
251pub struct ValidatedPath(TransportPath, ChainPath);
252
253impl ValidatedPath {
254    /// Shortcut to create a direct path to a destination with the given addresses.
255    pub fn direct(dst_key: OffchainPublicKey, dst_address: Address) -> Self {
256        Self(TransportPath(vec![dst_key]), ChainPath(vec![dst_address]))
257    }
258
259    /// Turns the given [`NonEmptyPath`] into a [`ValidatedPath`].
260    ///
261    /// This makes sure that all addresses and channels on the path exist
262    /// and do resolve to the corresponding [`OffchainPublicKeys`](OffchainPublicKey) or
263    /// [`Addresses`](Address).
264    pub async fn new<N, P, O, R>(origin: O, path: P, cg: &ChannelGraph, resolver: &R) -> errors::Result<ValidatedPath>
265    where
266        N: Into<PathAddress> + Copy,
267        P: NonEmptyPath<N>,
268        O: Into<PathAddress>,
269        R: PathAddressResolver,
270    {
271        let mut ticket_issuer = match origin.into() {
272            PathAddress::Chain(addr) => addr,
273            PathAddress::Transport(key) => resolver
274                .resolve_chain_address(&key)
275                .await?
276                .ok_or(InvalidPeer(key.to_string()))?,
277        };
278
279        let mut keys = Vec::with_capacity(path.num_hops());
280        let mut addrs = Vec::with_capacity(path.num_hops());
281
282        let num_hops = path.num_hops();
283        for (i, hop) in path.into_iter().enumerate() {
284            // Resolve the counterpart address
285            // and get the chain Address to validate against the channel graph
286            let ticket_receiver = match &hop.into() {
287                PathAddress::Chain(addr) => {
288                    let key = resolver
289                        .resolve_transport_address(addr)
290                        .await?
291                        .ok_or(InvalidPeer(addr.to_string()))?;
292                    keys.push(key);
293                    addrs.push(*addr);
294                    *addr
295                }
296                PathAddress::Transport(key) => {
297                    let addr = resolver
298                        .resolve_chain_address(key)
299                        .await?
300                        .ok_or(InvalidPeer(key.to_string()))?;
301                    addrs.push(addr);
302                    keys.push(*key);
303                    addr
304                }
305            };
306
307            // Check for loops
308            if ticket_issuer == ticket_receiver {
309                return Err(LoopsNotAllowed(ticket_receiver.to_hex()));
310            }
311
312            // Check if the channel is opened, if not the last hop
313            if i < num_hops - 1 {
314                let channel = cg
315                    .get_channel(&ticket_issuer, &ticket_receiver)
316                    .ok_or(MissingChannel(ticket_issuer.to_hex(), ticket_receiver.to_hex()))?;
317
318                if channel.status != ChannelStatus::Open {
319                    return Err(ChannelNotOpened(ticket_issuer.to_hex(), ticket_receiver.to_hex()));
320                }
321            }
322
323            ticket_issuer = ticket_receiver;
324        }
325
326        debug_assert_eq!(keys.len(), addrs.len());
327
328        Ok(ValidatedPath(TransportPath(keys), ChainPath(addrs)))
329    }
330
331    /// Valid chain path.
332    pub fn chain_path(&self) -> &ChainPath {
333        &self.1
334    }
335
336    /// Valid transport path.
337    pub fn transport_path(&self) -> &TransportPath {
338        &self.0
339    }
340}
341
342impl Deref for ValidatedPath {
343    type Target = [OffchainPublicKey];
344
345    fn deref(&self) -> &Self::Target {
346        &self.0
347    }
348}
349
350impl IntoIterator for ValidatedPath {
351    type IntoIter = std::vec::IntoIter<Self::Item>;
352    type Item = OffchainPublicKey;
353
354    fn into_iter(self) -> Self::IntoIter {
355        self.0.into_iter()
356    }
357}
358
359impl Path<OffchainPublicKey> for ValidatedPath {
360    /// Returns always `None`.
361    ///
362    /// A validated path cannot be inverted, as the inverted path could be invalid.
363    fn invert(self) -> Option<Self> {
364        None
365    }
366}
367
368impl Display for ValidatedPath {
369    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
370        write!(
371            f,
372            "validated path [{}]",
373            self.1.0.iter().map(|p| p.to_hex()).collect::<Vec<String>>().join(", ")
374        )
375    }
376}
377
378impl NonEmptyPath<OffchainPublicKey> for ValidatedPath {}
379
380#[cfg(test)]
381pub(crate) mod tests {
382    use std::{
383        iter,
384        ops::Add,
385        str::FromStr,
386        time::{Duration, SystemTime},
387    };
388
389    use anyhow::{Context, ensure};
390    use async_trait::async_trait;
391    use hex_literal::hex;
392    use hopr_internal_types::channels::ChannelEntry;
393    use parameterized::parameterized;
394
395    use super::*;
396
397    lazy_static::lazy_static! {
398        pub static ref PATH_ADDRS: bimap::BiMap<OffchainPublicKey, Address> = bimap::BiMap::from_iter([
399            (OffchainPublicKey::from_privkey(&hex!("e0bf93e9c916104da00b1850adc4608bd7e9087bbd3f805451f4556aa6b3fd6e")).unwrap(), Address::from_str("0x0000c178cf70d966be0a798e666ce2782c7b2288").unwrap()),
400            (OffchainPublicKey::from_privkey(&hex!("cfc66f718ec66fb822391775d749d7a0d66b690927673634816b63339bc12a3c")).unwrap(), Address::from_str("0x1000d5786d9e6799b3297da1ad55605b91e2c882").unwrap()),
401            (OffchainPublicKey::from_privkey(&hex!("203ca4d3c5f98dd2066bb204b5930c10b15c095585c224c826b4e11f08bfa85d")).unwrap(), Address::from_str("0x200060ddced1e33c9647a71f4fc2cf4ed33e4a9d").unwrap()),
402            (OffchainPublicKey::from_privkey(&hex!("fc71590e473b3e64e498e8a7f03ed19d1d7ee5f438c5d41300e8bb228b6b5d3c")).unwrap(), Address::from_str("0x30004105095c8c10f804109b4d1199a9ac40ed46").unwrap()),
403            (OffchainPublicKey::from_privkey(&hex!("4ab03f6f75f845ca1bf8b7104804ea5bda18bda29d1ec5fc5d4267feca5fb8e1")).unwrap(), Address::from_str("0x4000a288c38fa8a0f4b79127747257af4a03a623").unwrap()),
404            (OffchainPublicKey::from_privkey(&hex!("a1859043a6ae231259ad0bccac9a62377cd2b0700ba2248fcfa52ae1461f7087")).unwrap(), Address::from_str("0x50002f462ec709cf181bbe44a7e952487bd4591d").unwrap()),
405        ]);
406        pub static ref ADDRESSES: Vec<Address> = sorted_peers().iter().map(|p| p.1).collect();
407    }
408
409    pub fn sorted_peers() -> Vec<(OffchainPublicKey, Address)> {
410        let mut peers = PATH_ADDRS.iter().map(|(pk, a)| (*pk, *a)).collect::<Vec<_>>();
411        peers.sort_by(|a, b| a.1.to_string().cmp(&b.1.to_string()));
412        peers
413    }
414
415    #[async_trait]
416    impl PathAddressResolver for bimap::BiMap<OffchainPublicKey, Address> {
417        async fn resolve_transport_address(&self, address: &Address) -> Result<Option<OffchainPublicKey>, PathError> {
418            Ok(self.get_by_right(address).copied())
419        }
420
421        async fn resolve_chain_address(&self, key: &OffchainPublicKey) -> Result<Option<Address>, PathError> {
422            Ok(self.get_by_left(key).copied())
423        }
424    }
425
426    pub fn dummy_channel(src: Address, dst: Address, status: ChannelStatus) -> ChannelEntry {
427        ChannelEntry::new(src, dst, 1.into(), 1u32.into(), status, 1u32.into())
428    }
429
430    fn create_graph_and_resolver_entries(me: Address) -> (ChannelGraph, Vec<(OffchainPublicKey, Address)>) {
431        let addrs = sorted_peers();
432
433        let ts = SystemTime::now().add(Duration::from_secs(10));
434
435        // Channels: 0 -> 1 -> 2 -> 3 -> 4, 4 /> 0, 3 -> 1
436        let mut cg = ChannelGraph::new(me, Default::default());
437        cg.update_channel(dummy_channel(addrs[0].1, addrs[1].1, ChannelStatus::Open));
438        cg.update_channel(dummy_channel(addrs[1].1, addrs[2].1, ChannelStatus::Open));
439        cg.update_channel(dummy_channel(addrs[2].1, addrs[3].1, ChannelStatus::Open));
440        cg.update_channel(dummy_channel(addrs[3].1, addrs[4].1, ChannelStatus::Open));
441        cg.update_channel(dummy_channel(addrs[3].1, addrs[1].1, ChannelStatus::Open));
442        cg.update_channel(dummy_channel(addrs[4].1, addrs[0].1, ChannelStatus::PendingToClose(ts)));
443
444        (cg, addrs)
445    }
446
447    #[test]
448    fn chain_path_zero_hop_should_fail() -> anyhow::Result<()> {
449        ensure!(ChainPath::new::<Address, _>([]).is_err(), "must fail for zero hop");
450        Ok(())
451    }
452
453    #[test]
454    fn transport_path_zero_hop_should_fail() -> anyhow::Result<()> {
455        ensure!(
456            TransportPath::new::<OffchainPublicKey, _>([]).is_err(),
457            "must fail for zero hop"
458        );
459        Ok(())
460    }
461
462    #[parameterized(hops = { 1, 2, 3 })]
463    #[parameterized_macro(tokio::test)]
464    async fn validated_path_multi_hop_validation(hops: usize) -> anyhow::Result<()> {
465        let (cg, peers) = create_graph_and_resolver_entries(ADDRESSES[0]);
466
467        // path: 0 -> 1 -> 2 -> 3 -> 4
468        let chain_path = ChainPath::new(peers.iter().skip(1).take(hops + 1).map(|(_, a)| *a))?;
469
470        assert_eq!(hops + 1, chain_path.num_hops(), "must be a {hops} hop path");
471        ensure!(!chain_path.contains_cycle(), "must not be cyclic");
472
473        let validated = ValidatedPath::new(ADDRESSES[0], chain_path.clone(), &cg, PATH_ADDRS.deref())
474            .await
475            .context(format!("must be valid {hops} hop path"))?;
476
477        assert_eq!(
478            chain_path.num_hops(),
479            validated.num_hops(),
480            "validated path must have the same length"
481        );
482        assert_eq!(
483            validated.chain_path(),
484            &chain_path,
485            "validated path must have the same chain path"
486        );
487
488        assert_eq!(
489            peers.into_iter().skip(1).take(hops + 1).collect::<Vec<_>>(),
490            validated
491                .transport_path()
492                .iter()
493                .copied()
494                .zip(validated.chain_path().iter().copied())
495                .collect::<Vec<_>>(),
496            "validated path must have the same transport path"
497        );
498
499        Ok(())
500    }
501
502    #[parameterized(hops = { 1, 2, 3 })]
503    #[parameterized_macro(tokio::test)]
504    async fn validated_path_revalidation_should_be_identity(hops: usize) -> anyhow::Result<()> {
505        let (cg, peers) = create_graph_and_resolver_entries(ADDRESSES[0]);
506
507        // path: 0 -> 1 -> 2 -> 3 -> 4
508        let chain_path = ChainPath::new(peers.iter().skip(1).take(hops + 1).map(|(_, a)| *a))?;
509
510        let validated_1 = ValidatedPath::new(ADDRESSES[0], chain_path.clone(), &cg, PATH_ADDRS.deref())
511            .await
512            .context(format!("must be valid {hops} hop path"))?;
513
514        let validated_2 = ValidatedPath::new(ADDRESSES[0], validated_1.clone(), &cg, PATH_ADDRS.deref())
515            .await
516            .context(format!("must be valid {hops} hop path"))?;
517
518        assert_eq!(validated_1, validated_2, "revalidation must be identity");
519
520        Ok(())
521    }
522
523    #[parameterized(hops = { 2, 3 })]
524    #[parameterized_macro(tokio::test)]
525    async fn validated_path_must_allow_cyclic(hops: usize) -> anyhow::Result<()> {
526        let (cg, peers) = create_graph_and_resolver_entries(ADDRESSES[0]);
527
528        // path: 0 -> 1 -> 2 -> 3 -> 1
529        let chain_path = ChainPath::new(
530            peers
531                .iter()
532                .skip(1)
533                .take(hops)
534                .map(|(_, a)| *a)
535                .chain(iter::once(peers[1].1)),
536        )?;
537
538        assert_eq!(hops + 1, chain_path.num_hops(), "must be a {hops} hop path");
539        assert!(chain_path.contains_cycle(), "must be cyclic");
540
541        let validated = ValidatedPath::new(ADDRESSES[0], chain_path.clone(), &cg, PATH_ADDRS.deref())
542            .await
543            .context(format!("must be valid {hops} hop path"))?;
544
545        assert_eq!(
546            chain_path.num_hops(),
547            validated.num_hops(),
548            "validated path must have the same length"
549        );
550        assert_eq!(
551            validated.chain_path(),
552            &chain_path,
553            "validated path must have the same chain path"
554        );
555
556        assert_eq!(
557            peers
558                .iter()
559                .copied()
560                .skip(1)
561                .take(hops)
562                .chain(iter::once(peers[1]))
563                .collect::<Vec<_>>(),
564            validated
565                .transport_path()
566                .iter()
567                .copied()
568                .zip(validated.chain_path().iter().copied())
569                .collect::<Vec<_>>(),
570            "validated path must have the same transport path"
571        );
572
573        Ok(())
574    }
575
576    #[tokio::test]
577    async fn validated_path_should_allow_zero_hop_with_non_existing_channel() -> anyhow::Result<()> {
578        let (cg, peers) = create_graph_and_resolver_entries(ADDRESSES[0]);
579
580        // path: 0 -> 3 (channel 0 -> 3 does not exist)
581        let chain_path = ChainPath::new([peers[3].1])?;
582
583        let validated = ValidatedPath::new(ADDRESSES[0], chain_path.clone(), &cg, PATH_ADDRS.deref())
584            .await
585            .context("must be valid path")?;
586
587        assert_eq!(&chain_path, validated.chain_path(), "path must be the same");
588
589        Ok(())
590    }
591
592    #[tokio::test]
593    async fn validated_path_should_allow_zero_hop_with_non_open_channel() -> anyhow::Result<()> {
594        let (cg, peers) = create_graph_and_resolver_entries(ADDRESSES[0]);
595
596        // path: 4 -> 0 (channel 4 -> 0 is PendingToClose)
597        let chain_path = ChainPath::new([peers[0].1])?;
598
599        let validated = ValidatedPath::new(ADDRESSES[4], chain_path.clone(), &cg, PATH_ADDRS.deref())
600            .await
601            .context("must be valid path")?;
602
603        assert_eq!(&chain_path, validated.chain_path(), "path must be the same");
604
605        Ok(())
606    }
607
608    #[tokio::test]
609    async fn validated_path_should_allow_non_existing_channel_for_last_hop() -> anyhow::Result<()> {
610        let (cg, peers) = create_graph_and_resolver_entries(ADDRESSES[0]);
611
612        // path: 0 -> 1 -> 3 (channel 1 -> 3 does not exist)
613        let chain_path = ChainPath::new([peers[1].1, peers[3].1])?;
614
615        let validated = ValidatedPath::new(ADDRESSES[0], chain_path.clone(), &cg, PATH_ADDRS.deref())
616            .await
617            .context("must be valid path")?;
618
619        assert_eq!(&chain_path, validated.chain_path(), "path must be the same");
620
621        Ok(())
622    }
623
624    #[tokio::test]
625    async fn validated_path_should_allow_non_open_channel_for_the_last_hop() -> anyhow::Result<()> {
626        let (cg, peers) = create_graph_and_resolver_entries(ADDRESSES[0]);
627
628        // path: 3 -> 4 -> 0 (channel 4 -> 0 is PendingToClose)
629        let chain_path = ChainPath::new([peers[4].1, peers[0].1])?;
630
631        let validated = ValidatedPath::new(ADDRESSES[3], chain_path.clone(), &cg, PATH_ADDRS.deref())
632            .await
633            .context("must be valid path")?;
634
635        assert_eq!(&chain_path, validated.chain_path(), "path must be the same");
636
637        Ok(())
638    }
639
640    #[tokio::test]
641    async fn validated_path_should_fail_for_non_open_channel_not_in_the_last_hop() -> anyhow::Result<()> {
642        let (cg, peers) = create_graph_and_resolver_entries(ADDRESSES[0]);
643
644        // path: 4 -> 0 -> 1 (channel 4 -> 0 is PendingToClose)
645        let chain_path = ChainPath::new([peers[0].1, peers[1].1])?;
646
647        ensure!(
648            ValidatedPath::new(ADDRESSES[4], chain_path, &cg, PATH_ADDRS.deref())
649                .await
650                .is_err(),
651            "path must not be constructible"
652        );
653
654        // path: 3 -> 4 -> 0 -> 1 (channel 4 -> 0 is PendingToClose)
655        let chain_path = ChainPath::new([peers[4].1, peers[0].1, peers[1].1])?;
656
657        ensure!(
658            ValidatedPath::new(ADDRESSES[3], chain_path, &cg, PATH_ADDRS.deref())
659                .await
660                .is_err(),
661            "path must not be constructible"
662        );
663
664        // path: 2 -> 3 -> 4 -> 0 -> 1 (channel 4 -> 0 is PendingToClose)
665        let chain_path = ChainPath::new([peers[3].1, peers[4].1, peers[0].1, peers[1].1])?;
666
667        ensure!(
668            ValidatedPath::new(ADDRESSES[2], chain_path, &cg, PATH_ADDRS.deref())
669                .await
670                .is_err(),
671            "path must not be constructible"
672        );
673
674        Ok(())
675    }
676
677    #[tokio::test]
678    async fn validated_path_should_fail_for_non_existing_channel_not_in_the_last_hop() -> anyhow::Result<()> {
679        let (cg, peers) = create_graph_and_resolver_entries(ADDRESSES[0]);
680
681        // path: 0 -> 3 -> 4 (channel 0 -> 3 does not exist)
682        let chain_path = ChainPath::new([peers[3].1, peers[4].1])?;
683
684        ensure!(
685            ValidatedPath::new(ADDRESSES[0], chain_path, &cg, PATH_ADDRS.deref())
686                .await
687                .is_err(),
688            "path must not be constructible"
689        );
690
691        // path: 0 -> 1 -> 3 -> 0 (channel 1 -> 3 does not exist)
692        let chain_path = ChainPath::new([peers[1].1, peers[3].1, peers[0].1])?;
693
694        ensure!(
695            ValidatedPath::new(ADDRESSES[0], chain_path, &cg, PATH_ADDRS.deref())
696                .await
697                .is_err(),
698            "path must not be constructible"
699        );
700
701        // path: 0 -> 1 -> 2 -> 4 -> 0 (channel 2 -> 4 does not exist)
702        let chain_path = ChainPath::new([peers[1].1, peers[2].1, peers[2].1, peers[0].1])?;
703
704        ensure!(
705            ValidatedPath::new(ADDRESSES[0], chain_path, &cg, PATH_ADDRS.deref())
706                .await
707                .is_err(),
708            "path must not be constructible"
709        );
710
711        Ok(())
712    }
713
714    #[tokio::test]
715    async fn validated_path_should_not_allow_simple_loops() -> anyhow::Result<()> {
716        let (cg, peers) = create_graph_and_resolver_entries(ADDRESSES[0]);
717
718        // path: 0 -> 1 -> 1 -> 2
719        let chain_path = ChainPath::new([peers[1].1, peers[1].1, peers[2].1])?;
720
721        assert!(chain_path.contains_cycle(), "path must contain a cycle");
722
723        ensure!(
724            ValidatedPath::new(ADDRESSES[0], chain_path, &cg, PATH_ADDRS.deref())
725                .await
726                .is_err(),
727            "path must not be constructible"
728        );
729
730        Ok(())
731    }
732
733    #[tokio::test]
734    async fn validated_path_should_allow_long_cycles() -> anyhow::Result<()> {
735        let (cg, peers) = create_graph_and_resolver_entries(ADDRESSES[0]);
736
737        // path 0 -> 1 -> 2 -> 3 -> 1 -> 2
738        let chain_path = ChainPath::new([peers[1].1, peers[2].1, peers[3].1, peers[1].1, peers[2].1])?;
739
740        assert!(chain_path.contains_cycle(), "path must contain a cycle");
741
742        let validated = ValidatedPath::new(ADDRESSES[0], chain_path.clone(), &cg, PATH_ADDRS.deref())
743            .await
744            .context("must be valid path")?;
745
746        assert_eq!(&chain_path, validated.chain_path(), "path must be the same");
747
748        Ok(())
749    }
750}