hopr_internal_types/
path.rs

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