1use std::{collections::HashSet, hash::RandomState, sync::Arc};
2
3use hopr_api::{
4 OffchainPublicKey,
5 graph::{
6 function::EdgeValueFn,
7 traits::{EdgeNetworkObservableRead, EdgeObservableRead, ValueFn},
8 },
9 types::internal::routing::PathId,
10};
11use petgraph::graph::NodeIndex;
12
13use crate::{ChannelGraph, algorithm::all_simple_paths_multi, graph::InnerGraph};
14
15pub(crate) type SharedValueFn<C> = Arc<dyn Fn(C, &crate::Observations, usize) -> C + Send + Sync>;
17
18#[allow(clippy::too_many_arguments)]
21pub(crate) fn find_paths<C>(
22 inner: &InnerGraph,
23 source: NodeIndex,
24 destinations: &HashSet<NodeIndex>,
25 length: usize,
26 take_count: Option<usize>,
27 initial_value: C,
28 min_value: Option<C>,
29 value_fn: SharedValueFn<C>,
30) -> Vec<(Vec<OffchainPublicKey>, PathId, C)>
31where
32 C: Clone + PartialOrd,
33{
34 if length == 0 {
35 return Default::default();
36 }
37
38 let intermediates = length - 1;
39
40 let paths = all_simple_paths_multi::<Vec<_>, _, RandomState, _, _>(
41 &inner.graph,
42 source,
43 destinations,
44 None,
45 intermediates,
46 Some(intermediates),
47 initial_value,
48 min_value,
49 move |c, w, i| value_fn(c, w, i),
50 )
51 .filter_map(|(node_indices, final_cost)| {
52 let mut path_id: PathId = [0u64; 5];
54 for (i, &node_idx) in node_indices.iter().enumerate() {
55 if i >= path_id.len() {
56 return None;
57 }
58 path_id[i] = node_idx.index() as u64;
59 }
60
61 let nodes = node_indices
64 .into_iter()
65 .skip(1)
66 .filter_map(|v| inner.indices.get_by_right(&v).copied())
67 .collect::<Vec<_>>();
68 (nodes.len() == length).then_some((nodes, path_id, final_cost))
71 });
72
73 if let Some(take_count) = take_count {
74 paths.take(take_count).collect::<Vec<_>>()
75 } else {
76 paths.collect::<Vec<_>>()
77 }
78}
79
80impl hopr_api::graph::NetworkGraphTraverse for ChannelGraph {
81 type NodeId = OffchainPublicKey;
82 type Observed = crate::Observations;
83
84 fn simple_paths<C: ValueFn<Weight = Self::Observed>>(
85 &self,
86 source: &Self::NodeId,
87 destination: &Self::NodeId,
88 length: usize,
89 take_count: Option<usize>,
90 value_fn: C,
91 ) -> Vec<(Vec<Self::NodeId>, PathId, C::Value)> {
92 if length == 0 {
93 return Default::default();
94 }
95
96 let inner = self.inner.read();
97 let Some(start) = inner.indices.get_by_left(source) else {
98 return Default::default();
99 };
100 let Some(end) = inner.indices.get_by_left(destination) else {
101 return Default::default();
102 };
103 let end = HashSet::from_iter([*end]);
104
105 find_paths(
108 &inner,
109 *start,
110 &end,
111 length,
112 take_count,
113 value_fn.initial_value(),
114 value_fn.min_value(),
115 value_fn.into_value_fn(),
116 )
117 .into_iter()
118 .map(|(mut nodes, path_id, cost)| {
119 nodes.pop(); (nodes, path_id, cost)
121 })
122 .collect()
123 }
124
125 fn simple_paths_from<C: ValueFn<Weight = Self::Observed>>(
126 &self,
127 source: &Self::NodeId,
128 length: usize,
129 take_count: Option<usize>,
130 value_fn: C,
131 ) -> Vec<(Vec<Self::NodeId>, PathId, C::Value)> {
132 if length == 0 {
133 return Default::default();
134 }
135
136 let inner = self.inner.read();
137 let Some(start) = inner.indices.get_by_left(source) else {
138 return Default::default();
139 };
140
141 let destinations: HashSet<NodeIndex> = inner.graph.node_indices().filter(|idx| idx != start).collect();
142
143 find_paths(
144 &inner,
145 *start,
146 &destinations,
147 length,
148 take_count,
149 value_fn.initial_value(),
150 value_fn.min_value(),
151 value_fn.into_value_fn(),
152 )
153 }
154
155 fn simple_loopback_to_self(&self, length: usize, take_count: Option<usize>) -> Vec<(Vec<Self::NodeId>, PathId)> {
156 if length > 1 {
157 let inner = self.inner.read();
158
159 if let Some(me_idx) = inner.indices.get_by_left(&self.me) {
160 let connected_neighbors = inner
161 .graph
162 .neighbors(*me_idx)
163 .filter(|neighbor| {
164 inner
165 .graph
166 .edges_connecting(*me_idx, *neighbor)
167 .next()
168 .and_then(|e| e.weight().immediate_qos().map(|e| e.is_connected()))
169 .unwrap_or(false)
170 })
171 .collect::<HashSet<_>>();
172
173 let value_fn = EdgeValueFn::forward_without_self_loopback(self.edge_penalty, self.min_ack_rate);
174
175 return find_paths(
176 &inner,
177 *me_idx,
178 &connected_neighbors,
179 length,
180 take_count,
181 value_fn.initial_value(),
182 value_fn.min_value(),
183 value_fn.into_value_fn(),
184 )
185 .into_iter()
186 .map(|(mut a, mut b, _c)| {
187 let path_node_count = a.len();
194 if path_node_count + 1 < b.len() {
195 b[path_node_count + 1] = me_idx.index() as u64;
196 }
197 a.push(self.me);
198 (a, b)
199 })
200 .collect();
201 };
202 }
203
204 vec![]
205 }
206}
207
208#[cfg(test)]
209mod tests {
210 use anyhow::Context;
211 use hex_literal::hex;
212 use hopr_api::{
213 graph::{
214 NetworkGraphTraverse, NetworkGraphWrite,
215 function::EdgeValueFn,
216 traits::{EdgeObservableWrite, EdgeWeightType},
217 },
218 types::{
219 crypto::prelude::{Keypair, OffchainKeypair},
220 internal::routing::PathId,
221 },
222 };
223
224 use super::*;
225
226 const TEST_EDGE_PENALTY: f64 = 0.73;
229 const TEST_MIN_ACK_RATE: f64 = 0.0;
231
232 const SECRET_0: [u8; 32] = hex!("60741b83b99e36aa0c1331578156e16b8e21166d01834abb6c64b103f885734d");
234 const SECRET_1: [u8; 32] = hex!("71bf1f42ebbfcd89c3e197a3fd7cda79b92499e509b6fefa0fe44d02821d146a");
235 const SECRET_2: [u8; 32] = hex!("c24bd833704dd2abdae3933fcc9962c2ac404f84132224c474147382d4db2299");
236 const SECRET_3: [u8; 32] = hex!("e0bf93e9c916104da00b1850adc4608bd7e9087bbd3f805451f4556aa6b3fd6e");
237 const SECRET_4: [u8; 32] = hex!("cfc66f718ec66fb822391775d749d7a0d66b690927673634816b63339bc12a3c");
238 const SECRET_5: [u8; 32] = hex!("203ca4d3c5f98dd2066bb204b5930c10b15c095585c224c826b4e11f08bfa85d");
239 const SECRET_7: [u8; 32] = hex!("4ab03f6f75f845ca1bf8b7104804ea5bda18bda29d1ec5fc5d4267feca5fb8e1");
240
241 fn pubkey_from(secret: &[u8; 32]) -> OffchainPublicKey {
243 *OffchainKeypair::from_secret(secret).expect("valid secret key").public()
244 }
245
246 fn mark_edge_connected(graph: &ChannelGraph, src: &OffchainPublicKey, dest: &OffchainPublicKey) {
249 graph.upsert_edge(src, dest, |obs| {
250 obs.record(EdgeWeightType::Connected(true));
251 obs.record(EdgeWeightType::Immediate(Ok(std::time::Duration::from_millis(50))));
252 });
253 }
254
255 #[test]
256 fn one_edge_path_should_return_direct_route() -> anyhow::Result<()> {
257 let me = pubkey_from(&SECRET_0);
258 let dest = pubkey_from(&SECRET_1);
259
260 let graph = ChannelGraph::new(me);
261 graph.add_node(dest);
262 graph.add_edge(&me, &dest)?;
263 mark_edge_loopback_ready(&graph, &me, &dest);
264
265 let routes = graph.simple_paths(
266 &me,
267 &dest,
268 1,
269 None,
270 EdgeValueFn::forward(
271 std::num::NonZeroUsize::new(1).context("should be non-zero")?,
272 TEST_EDGE_PENALTY,
273 TEST_MIN_ACK_RATE,
274 ),
275 );
276
277 assert_eq!(routes.len(), 1, "should find exactly one 1-edge route");
278
279 Ok(())
280 }
281
282 #[test]
283 fn two_edge_path_should_route_through_intermediate() -> anyhow::Result<()> {
284 let me = pubkey_from(&SECRET_0);
285 let hop = pubkey_from(&SECRET_1);
286 let dest = pubkey_from(&SECRET_2);
287
288 let graph = ChannelGraph::new(me);
289 graph.add_node(hop);
290 graph.add_node(dest);
291 graph.add_edge(&me, &hop)?;
292 graph.add_edge(&hop, &dest)?;
293 mark_edge_loopback_ready(&graph, &me, &hop);
294 mark_edge_connected(&graph, &hop, &dest);
295
296 let length = std::num::NonZeroUsize::new(2).context("should be non-zero")?;
297 let routes = graph.simple_paths(
298 &me,
299 &dest,
300 2,
301 None,
302 EdgeValueFn::forward(length, TEST_EDGE_PENALTY, TEST_MIN_ACK_RATE),
303 );
304
305 assert!(!routes.is_empty(), "should find at least one 2-edge route");
306
307 Ok(())
308 }
309
310 #[test]
311 fn penalty_should_affect_cost_of_unprobed_edges() -> anyhow::Result<()> {
312 let me = pubkey_from(&SECRET_0);
313 let hop = pubkey_from(&SECRET_1);
314 let dest = pubkey_from(&SECRET_2);
315
316 let graph = ChannelGraph::new(me);
317 graph.add_node(hop);
318 graph.add_node(dest);
319 graph.add_edge(&me, &hop)?;
320 graph.add_edge(&hop, &dest)?;
321
322 mark_edge_loopback_ready(&graph, &me, &hop);
324 let length = std::num::NonZeroUsize::new(2).context("should be non-zero")?;
327
328 let routes_test = graph.simple_paths(
329 &me,
330 &dest,
331 2,
332 None,
333 EdgeValueFn::forward(length, TEST_EDGE_PENALTY, TEST_MIN_ACK_RATE),
334 );
335 let routes_other = graph.simple_paths(
336 &me,
337 &dest,
338 2,
339 None,
340 EdgeValueFn::forward(length, 0.99, TEST_MIN_ACK_RATE),
341 );
342
343 assert_eq!(routes_test.len(), 1);
344 assert_eq!(routes_other.len(), 1);
345
346 let (_, _, cost_test) = &routes_test[0];
347 let (_, _, cost_other) = &routes_other[0];
348 assert!(
349 (cost_test - cost_other).abs() > f64::EPSILON,
350 "different penalties ({TEST_EDGE_PENALTY} vs 0.99) should produce different costs for unprobed edges, got \
351 {cost_test} vs {cost_other}"
352 );
353
354 Ok(())
355 }
356
357 #[test]
358 fn unreachable_destination_should_return_empty() -> anyhow::Result<()> {
359 let me = pubkey_from(&SECRET_0);
360 let dest = pubkey_from(&SECRET_1);
361
362 let graph = ChannelGraph::new(me);
363 graph.add_node(dest);
364 let routes = graph.simple_paths(
367 &me,
368 &dest,
369 1,
370 None,
371 EdgeValueFn::forward(
372 std::num::NonZeroUsize::new(1).context("should be non-zero")?,
373 TEST_EDGE_PENALTY,
374 TEST_MIN_ACK_RATE,
375 ),
376 );
377
378 assert!(routes.is_empty(), "should return no routes when unreachable");
379
380 Ok(())
381 }
382
383 #[test]
384 fn unknown_destination_should_return_empty() -> anyhow::Result<()> {
385 let me = pubkey_from(&SECRET_0);
386 let unknown = pubkey_from(&SECRET_1);
387
388 let graph = ChannelGraph::new(me);
389 let routes = graph.simple_paths(
390 &me,
391 &unknown,
392 1,
393 None,
394 EdgeValueFn::forward(
395 std::num::NonZeroUsize::new(1).context("should be non-zero")?,
396 TEST_EDGE_PENALTY,
397 TEST_MIN_ACK_RATE,
398 ),
399 );
400
401 assert!(routes.is_empty());
402
403 Ok(())
404 }
405
406 #[test]
407 fn diamond_topology_should_yield_multiple_paths() -> anyhow::Result<()> {
408 let me = pubkey_from(&SECRET_0);
411 let a = pubkey_from(&SECRET_1);
412 let b = pubkey_from(&SECRET_2);
413 let dest = pubkey_from(&SECRET_3);
414
415 let graph = ChannelGraph::new(me);
416 graph.add_node(a);
417 graph.add_node(b);
418 graph.add_node(dest);
419 graph.add_edge(&me, &a)?;
420 graph.add_edge(&me, &b)?;
421 graph.add_edge(&a, &dest)?;
422 graph.add_edge(&b, &dest)?;
423 mark_edge_loopback_ready(&graph, &me, &a);
424 mark_edge_loopback_ready(&graph, &me, &b);
425 mark_edge_connected(&graph, &a, &dest);
426 mark_edge_connected(&graph, &b, &dest);
427
428 let routes = graph.simple_paths(
429 &me,
430 &dest,
431 2,
432 None,
433 EdgeValueFn::forward(
434 std::num::NonZeroUsize::new(2).context("should be non-zero")?,
435 TEST_EDGE_PENALTY,
436 TEST_MIN_ACK_RATE,
437 ),
438 );
439 assert_eq!(routes.len(), 2, "diamond topology should yield two 2-edge routes");
440 Ok(())
441 }
442
443 #[test]
444 fn three_edge_chain_should_find_single_path() -> anyhow::Result<()> {
445 let me = pubkey_from(&SECRET_0);
447 let a = pubkey_from(&SECRET_1);
448 let b = pubkey_from(&SECRET_2);
449 let dest = pubkey_from(&SECRET_3);
450
451 let graph = ChannelGraph::new(me);
452 graph.add_node(a);
453 graph.add_node(b);
454 graph.add_node(dest);
455 graph.add_edge(&me, &a)?;
456 graph.add_edge(&a, &b)?;
457 graph.add_edge(&b, &dest)?;
458 mark_edge_loopback_ready(&graph, &me, &a);
459 mark_edge_with_capacity(&graph, &a, &b);
460
461 let routes = graph.simple_paths(
462 &me,
463 &dest,
464 3,
465 None,
466 EdgeValueFn::forward(
467 std::num::NonZeroUsize::new(3).context("should be non-zero")?,
468 TEST_EDGE_PENALTY,
469 TEST_MIN_ACK_RATE,
470 ),
471 );
472 assert_eq!(routes.len(), 1, "should find exactly one 3-edge route");
473 Ok(())
474 }
475
476 #[test]
477 fn back_edge_should_not_produce_cyclic_paths() -> anyhow::Result<()> {
478 let me = pubkey_from(&SECRET_0);
480 let a = pubkey_from(&SECRET_1);
481 let b = pubkey_from(&SECRET_2);
482 let dest = pubkey_from(&SECRET_3);
483
484 let graph = ChannelGraph::new(me);
485 graph.add_node(a);
486 graph.add_node(b);
487 graph.add_node(dest);
488 graph.add_edge(&me, &a)?;
489 graph.add_edge(&a, &b)?;
490 graph.add_edge(&b, &dest)?;
491 graph.add_edge(&a, &me)?; mark_edge_loopback_ready(&graph, &me, &a);
493 mark_edge_with_capacity(&graph, &a, &b);
494
495 let routes = graph.simple_paths(
496 &me,
497 &dest,
498 3,
499 None,
500 EdgeValueFn::forward(
501 std::num::NonZeroUsize::new(3).context("should be non-zero")?,
502 TEST_EDGE_PENALTY,
503 TEST_MIN_ACK_RATE,
504 ),
505 );
506 assert_eq!(routes.len(), 1, "cycle should not produce extra paths");
507 Ok(())
508 }
509
510 #[test]
511 fn mismatched_edge_count_should_return_empty() -> anyhow::Result<()> {
512 let me = pubkey_from(&SECRET_0);
514 let dest = pubkey_from(&SECRET_1);
515 let graph = ChannelGraph::new(me);
516 graph.add_node(dest);
517 graph.add_edge(&me, &dest)?;
518
519 let routes = graph.simple_paths(
520 &me,
521 &dest,
522 2,
523 None,
524 EdgeValueFn::forward(
525 std::num::NonZeroUsize::new(2).context("should be non-zero")?,
526 TEST_EDGE_PENALTY,
527 TEST_MIN_ACK_RATE,
528 ),
529 );
530 assert!(routes.is_empty(), "no 2-edge route should exist for a single edge");
531 Ok(())
532 }
533
534 #[test]
535 fn zero_edge_should_always_return_empty() -> anyhow::Result<()> {
536 let me = pubkey_from(&SECRET_0);
537 let other = pubkey_from(&SECRET_1);
538 let graph = ChannelGraph::new(me);
539
540 let routes = graph.simple_paths(
542 &me,
543 &other,
544 0,
545 None,
546 EdgeValueFn::forward(
547 std::num::NonZeroUsize::new(1).context("should be non-zero")?,
548 TEST_EDGE_PENALTY,
549 TEST_MIN_ACK_RATE,
550 ),
551 );
552 assert!(routes.is_empty(), "zero-edge path should find no routes");
553 Ok(())
554 }
555
556 #[test]
557 fn reverse_edge_should_not_be_traversable() -> anyhow::Result<()> {
558 let me = pubkey_from(&SECRET_0);
560 let a = pubkey_from(&SECRET_1);
561 let dest = pubkey_from(&SECRET_2);
562
563 let graph = ChannelGraph::new(me);
564 graph.add_node(a);
565 graph.add_node(dest);
566 graph.add_edge(&me, &a)?;
567 graph.add_edge(&dest, &a)?; let routes = graph.simple_paths(
570 &me,
571 &dest,
572 2,
573 None,
574 EdgeValueFn::forward(
575 std::num::NonZeroUsize::new(2).context("should be non-zero")?,
576 TEST_EDGE_PENALTY,
577 TEST_MIN_ACK_RATE,
578 ),
579 );
580 assert!(routes.is_empty(), "should not traverse edge in wrong direction");
581 Ok(())
582 }
583
584 #[test]
585 fn non_trivial_graph_should_find_all_simple_paths() -> anyhow::Result<()> {
586 let me = pubkey_from(&SECRET_0);
611 let a = pubkey_from(&SECRET_1);
612 let b = pubkey_from(&SECRET_2);
613 let c = pubkey_from(&SECRET_3);
614 let d = pubkey_from(&SECRET_4);
615 let e = pubkey_from(&SECRET_5);
616 let f = pubkey_from(&SECRET_7);
617
618 let graph = ChannelGraph::new(me);
619 for node in [a, b, c, d, e, f] {
620 graph.add_node(node);
621 }
622
623 graph.add_edge(&me, &a)?;
625 graph.add_edge(&me, &b)?;
626
627 graph.add_edge(&a, &c)?;
629 graph.add_edge(&a, &d)?;
630
631 graph.add_edge(&b, &c)?;
633 graph.add_edge(&b, &d)?;
634 graph.add_edge(&b, &e)?;
635
636 graph.add_edge(&c, &f)?;
638 graph.add_edge(&d, &f)?;
639 graph.add_edge(&e, &f)?;
640
641 mark_edge_loopback_ready(&graph, &me, &a);
643 mark_edge_loopback_ready(&graph, &me, &b);
644
645 mark_edge_with_capacity(&graph, &a, &c);
647 mark_edge_with_capacity(&graph, &a, &d);
648 mark_edge_with_capacity(&graph, &b, &c);
649 mark_edge_with_capacity(&graph, &b, &d);
650 mark_edge_with_capacity(&graph, &b, &e);
651
652 let routes_3 = graph.simple_paths(
656 &me,
657 &f,
658 3,
659 None,
660 EdgeValueFn::forward(
661 std::num::NonZeroUsize::new(3).context("should be non-zero")?,
662 TEST_EDGE_PENALTY,
663 TEST_MIN_ACK_RATE,
664 ),
665 );
666 assert_eq!(routes_3.len(), 5, "should find exactly 5 three-edge paths");
667
668 for (path, _path_id, cost) in &routes_3 {
671 assert!(*cost > 0.0, "path {path:?} should have positive cost, got {cost}");
672 assert_eq!(
673 path.len(),
674 2,
675 "3-edge path should contain 2 intermediates (src and dest stripped)"
676 );
677 assert!(!path.contains(&me), "path must not contain src");
678 assert!(!path.contains(&f), "path must not contain dest");
679 }
680
681 let routes_1 = graph.simple_paths(
683 &me,
684 &f,
685 1,
686 None,
687 EdgeValueFn::forward(
688 std::num::NonZeroUsize::new(1).context("should be non-zero")?,
689 TEST_EDGE_PENALTY,
690 TEST_MIN_ACK_RATE,
691 ),
692 );
693 assert!(routes_1.is_empty(), "no direct edge from me to f");
694
695 Ok(())
696 }
697
698 #[test]
699 fn three_edge_loop_should_return_empty_because_source_is_visited() -> anyhow::Result<()> {
700 let me = pubkey_from(&SECRET_0);
709 let a = pubkey_from(&SECRET_1);
710 let b = pubkey_from(&SECRET_2);
711
712 let graph = ChannelGraph::new(me);
713 graph.add_node(a);
714 graph.add_node(b);
715 graph.add_edge(&me, &a)?;
716 graph.add_edge(&a, &b)?;
717 graph.add_edge(&b, &me)?;
718 mark_edge_connected(&graph, &b, &me);
719
720 let routes = graph.simple_paths(
721 &me,
722 &me,
723 3,
724 None,
725 EdgeValueFn::forward(
726 std::num::NonZeroUsize::new(3).context("should be non-zero")?,
727 TEST_EDGE_PENALTY,
728 TEST_MIN_ACK_RATE,
729 ),
730 );
731 assert!(
732 routes.is_empty(),
733 "simple_paths cannot discover cycles (source == destination) due to visited-set semantics"
734 );
735
736 Ok(())
737 }
738
739 #[test]
740 fn path_id_should_contain_node_indices_for_one_edge() -> anyhow::Result<()> {
741 let me = pubkey_from(&SECRET_0);
743 let dest = pubkey_from(&SECRET_1);
744
745 let graph = ChannelGraph::new(me);
746 graph.add_node(dest);
747 graph.add_edge(&me, &dest)?;
748 mark_edge_loopback_ready(&graph, &me, &dest);
749
750 let routes = graph.simple_paths(
751 &me,
752 &dest,
753 1,
754 None,
755 EdgeValueFn::forward(
756 std::num::NonZeroUsize::new(1).context("should be non-zero")?,
757 TEST_EDGE_PENALTY,
758 TEST_MIN_ACK_RATE,
759 ),
760 );
761 assert_eq!(routes.len(), 1);
762
763 let (_path, path_id, _cost) = &routes[0];
764 assert_eq!(path_id[0], 0, "first node should be me (node index 0)");
765 assert_eq!(path_id[1], 1, "second node should be dest (node index 1)");
766 assert_eq!(path_id[2..], [0, 0, 0], "unused positions should be 0");
767
768 Ok(())
769 }
770
771 #[test]
772 fn path_id_should_contain_node_indices_for_three_edges() -> anyhow::Result<()> {
773 let me = pubkey_from(&SECRET_0);
775 let a = pubkey_from(&SECRET_1);
776 let b = pubkey_from(&SECRET_2);
777 let dest = pubkey_from(&SECRET_3);
778
779 let graph = ChannelGraph::new(me);
780 graph.add_node(a);
781 graph.add_node(b);
782 graph.add_node(dest);
783 graph.add_edge(&me, &a)?;
784 graph.add_edge(&a, &b)?;
785 graph.add_edge(&b, &dest)?;
786 mark_edge_loopback_ready(&graph, &me, &a);
787 mark_edge_with_capacity(&graph, &a, &b);
788
789 let routes = graph.simple_paths(
790 &me,
791 &dest,
792 3,
793 None,
794 EdgeValueFn::forward(
795 std::num::NonZeroUsize::new(3).context("should be non-zero")?,
796 TEST_EDGE_PENALTY,
797 TEST_MIN_ACK_RATE,
798 ),
799 );
800 assert_eq!(routes.len(), 1);
801
802 let (_path, path_id, _cost) = &routes[0];
803 assert_eq!(path_id[0], 0, "me should be node index 0");
804 assert_eq!(path_id[1], 1, "a should be node index 1");
805 assert_eq!(path_id[2], 2, "b should be node index 2");
806 assert_eq!(path_id[3], 3, "dest should be node index 3");
807 assert_eq!(path_id[4], 0, "unused position should be 0");
808
809 Ok(())
810 }
811
812 #[test]
813 fn path_id_should_differ_for_distinct_paths_in_diamond() -> anyhow::Result<()> {
814 let me = pubkey_from(&SECRET_0);
818 let a = pubkey_from(&SECRET_1);
819 let b = pubkey_from(&SECRET_2);
820 let dest = pubkey_from(&SECRET_3);
821
822 let graph = ChannelGraph::new(me);
823 graph.add_node(a);
824 graph.add_node(b);
825 graph.add_node(dest);
826 graph.add_edge(&me, &a)?;
827 graph.add_edge(&me, &b)?;
828 graph.add_edge(&a, &dest)?;
829 graph.add_edge(&b, &dest)?;
830 mark_edge_loopback_ready(&graph, &me, &a);
831 mark_edge_loopback_ready(&graph, &me, &b);
832 mark_edge_connected(&graph, &a, &dest);
833 mark_edge_connected(&graph, &b, &dest);
834
835 let routes = graph.simple_paths(
836 &me,
837 &dest,
838 2,
839 None,
840 EdgeValueFn::forward(
841 std::num::NonZeroUsize::new(2).context("should be non-zero")?,
842 TEST_EDGE_PENALTY,
843 TEST_MIN_ACK_RATE,
844 ),
845 );
846 assert_eq!(routes.len(), 2, "diamond should yield two 2-edge routes");
847
848 let path_ids: Vec<PathId> = routes.iter().map(|(_, pid, _)| *pid).collect();
849 assert_ne!(path_ids[0], path_ids[1], "distinct paths should have different PathIds");
850
851 for pid in &path_ids {
853 assert_eq!(pid[0], 0, "first node should be me (node index 0)");
854 assert!(pid[1] == 1 || pid[1] == 2, "second node should be a (1) or b (2)");
855 assert_eq!(pid[2], 3, "third node should be dest (node index 3)");
856 assert_eq!(pid[3..], [0, 0], "unused positions should be 0");
857 }
858
859 Ok(())
860 }
861
862 #[test]
865 fn return_path_one_edge_should_find_route() -> anyhow::Result<()> {
866 let me = pubkey_from(&SECRET_0);
869 let dest = pubkey_from(&SECRET_1);
870
871 let graph = ChannelGraph::new(me);
872 graph.add_node(dest);
873 graph.add_edge(&dest, &me)?;
874 mark_edge_connected(&graph, &dest, &me);
876
877 let routes = graph.simple_paths(
878 &dest,
879 &me,
880 1,
881 None,
882 EdgeValueFn::returning(
883 std::num::NonZeroUsize::new(1).context("should be non-zero")?,
884 TEST_EDGE_PENALTY,
885 TEST_MIN_ACK_RATE,
886 ),
887 );
888
889 assert_eq!(routes.len(), 1, "should find exactly one 1-edge return route");
890 Ok(())
891 }
892
893 #[test]
894 fn return_path_two_edge_should_route_through_intermediate() -> anyhow::Result<()> {
895 let me = pubkey_from(&SECRET_0);
899 let relay = pubkey_from(&SECRET_1);
900 let dest = pubkey_from(&SECRET_2);
901
902 let graph = ChannelGraph::new(me);
903 graph.add_node(relay);
904 graph.add_node(dest);
905 graph.add_edge(&dest, &relay)?;
906 graph.add_edge(&relay, &me)?;
907 mark_edge_with_capacity(&graph, &dest, &relay);
909 mark_edge_connected(&graph, &relay, &me);
911
912 let routes = graph.simple_paths(
913 &dest,
914 &me,
915 2,
916 None,
917 EdgeValueFn::returning(
918 std::num::NonZeroUsize::new(2).context("should be non-zero")?,
919 TEST_EDGE_PENALTY,
920 TEST_MIN_ACK_RATE,
921 ),
922 );
923
924 assert!(!routes.is_empty(), "should find at least one 2-edge return route");
925 Ok(())
926 }
927
928 #[test]
929 fn return_path_last_edge_without_connectivity_should_be_pruned() -> anyhow::Result<()> {
930 let me = pubkey_from(&SECRET_0);
933 let relay = pubkey_from(&SECRET_1);
934 let dest = pubkey_from(&SECRET_2);
935
936 let graph = ChannelGraph::new(me);
937 graph.add_node(relay);
938 graph.add_node(dest);
939 graph.add_edge(&dest, &relay)?;
940 graph.add_edge(&relay, &me)?;
941 mark_edge_with_capacity(&graph, &dest, &relay);
943 let routes = graph.simple_paths(
946 &dest,
947 &me,
948 2,
949 None,
950 EdgeValueFn::returning(
951 std::num::NonZeroUsize::new(2).context("should be non-zero")?,
952 TEST_EDGE_PENALTY,
953 TEST_MIN_ACK_RATE,
954 ),
955 );
956
957 assert!(
958 routes.is_empty(),
959 "return path should be pruned when last edge lacks connectivity"
960 );
961 Ok(())
962 }
963
964 #[test]
965 fn return_path_first_edge_without_capacity_should_be_pruned() -> anyhow::Result<()> {
966 let me = pubkey_from(&SECRET_0);
969 let relay = pubkey_from(&SECRET_1);
970 let dest = pubkey_from(&SECRET_2);
971
972 let graph = ChannelGraph::new(me);
973 graph.add_node(relay);
974 graph.add_node(dest);
975 graph.add_edge(&dest, &relay)?;
976 graph.add_edge(&relay, &me)?;
977 mark_edge_connected(&graph, &relay, &me);
980
981 let routes = graph.simple_paths(
982 &dest,
983 &me,
984 2,
985 None,
986 EdgeValueFn::returning(
987 std::num::NonZeroUsize::new(2).context("should be non-zero")?,
988 TEST_EDGE_PENALTY,
989 TEST_MIN_ACK_RATE,
990 ),
991 );
992
993 assert!(
994 routes.is_empty(),
995 "return path should be pruned when first edge lacks capacity"
996 );
997 Ok(())
998 }
999
1000 #[test]
1001 fn return_path_diamond_should_yield_multiple_paths() -> anyhow::Result<()> {
1002 let me = pubkey_from(&SECRET_0);
1004 let a = pubkey_from(&SECRET_1);
1005 let b = pubkey_from(&SECRET_2);
1006 let dest = pubkey_from(&SECRET_3);
1007
1008 let graph = ChannelGraph::new(me);
1009 graph.add_node(a);
1010 graph.add_node(b);
1011 graph.add_node(dest);
1012 graph.add_edge(&dest, &a)?;
1013 graph.add_edge(&dest, &b)?;
1014 graph.add_edge(&a, &me)?;
1015 graph.add_edge(&b, &me)?;
1016 mark_edge_with_capacity(&graph, &dest, &a);
1018 mark_edge_with_capacity(&graph, &dest, &b);
1019 mark_edge_connected(&graph, &a, &me);
1021 mark_edge_connected(&graph, &b, &me);
1022
1023 let routes = graph.simple_paths(
1024 &dest,
1025 &me,
1026 2,
1027 None,
1028 EdgeValueFn::returning(
1029 std::num::NonZeroUsize::new(2).context("should be non-zero")?,
1030 TEST_EDGE_PENALTY,
1031 TEST_MIN_ACK_RATE,
1032 ),
1033 );
1034 assert_eq!(
1035 routes.len(),
1036 2,
1037 "diamond topology should yield two 2-edge return routes"
1038 );
1039 Ok(())
1040 }
1041
1042 fn mark_edge_loopback_ready(graph: &ChannelGraph, src: &OffchainPublicKey, dest: &OffchainPublicKey) {
1048 graph.upsert_edge(src, dest, |obs| {
1049 obs.record(EdgeWeightType::Connected(true));
1050 obs.record(EdgeWeightType::Immediate(Ok(std::time::Duration::from_millis(50))));
1051 obs.record(EdgeWeightType::Intermediate(Ok(std::time::Duration::from_millis(50))));
1052 obs.record(EdgeWeightType::Capacity(Some(1000)));
1053 });
1054 }
1055
1056 fn mark_edge_with_capacity(graph: &ChannelGraph, src: &OffchainPublicKey, dest: &OffchainPublicKey) {
1059 graph.upsert_edge(src, dest, |obs| {
1060 obs.record(EdgeWeightType::Intermediate(Ok(std::time::Duration::from_millis(50))));
1061 obs.record(EdgeWeightType::Capacity(Some(1000)));
1062 });
1063 }
1064
1065 #[test]
1066 fn loopback_returns_empty_for_length_zero() {
1067 let me = pubkey_from(&SECRET_0);
1068 let graph = ChannelGraph::new(me);
1069 assert!(graph.simple_loopback_to_self(0, None).is_empty());
1070 }
1071
1072 #[test]
1073 fn loopback_returns_empty_for_length_one() {
1074 let me = pubkey_from(&SECRET_0);
1075 let a = pubkey_from(&SECRET_1);
1076 let graph = ChannelGraph::new(me);
1077 graph.add_node(a);
1078 graph.add_edge(&me, &a).unwrap();
1079 mark_edge_loopback_ready(&graph, &me, &a);
1080
1081 assert!(
1082 graph.simple_loopback_to_self(1, None).is_empty(),
1083 "length=1 is below the minimum threshold"
1084 );
1085 }
1086
1087 #[test]
1088 fn loopback_returns_empty_without_any_peers() {
1089 let me = pubkey_from(&SECRET_0);
1090 let graph = ChannelGraph::new(me);
1091 assert!(
1092 graph.simple_loopback_to_self(2, None).is_empty(),
1093 "no peers means no connected neighbors"
1094 );
1095 }
1096
1097 #[test]
1098 fn loopback_returns_empty_without_connected_neighbors() -> anyhow::Result<()> {
1099 let me = pubkey_from(&SECRET_0);
1101 let a = pubkey_from(&SECRET_1);
1102 let b = pubkey_from(&SECRET_2);
1103
1104 let graph = ChannelGraph::new(me);
1105 graph.add_node(a);
1106 graph.add_node(b);
1107 graph.add_edge(&me, &a)?;
1108 graph.add_edge(&a, &b)?;
1109 graph.add_edge(&me, &b)?;
1110 mark_edge_loopback_ready(&graph, &me, &a);
1112 mark_edge_with_capacity(&graph, &a, &b);
1113
1114 assert!(
1115 graph.simple_loopback_to_self(2, None).is_empty(),
1116 "b is not a connected neighbor, so no loopback destinations exist"
1117 );
1118
1119 Ok(())
1120 }
1121
1122 #[test]
1123 fn loopback_returns_empty_when_first_hop_lacks_capacity() -> anyhow::Result<()> {
1124 let me = pubkey_from(&SECRET_0);
1127 let a = pubkey_from(&SECRET_1);
1128 let b = pubkey_from(&SECRET_2);
1129
1130 let graph = ChannelGraph::new(me);
1131 graph.add_node(a);
1132 graph.add_node(b);
1133 graph.add_edge(&me, &a)?;
1134 graph.add_edge(&a, &b)?;
1135 graph.add_edge(&me, &b)?;
1136 mark_edge_connected(&graph, &me, &a);
1138 mark_edge_with_capacity(&graph, &a, &b);
1140 mark_edge_connected(&graph, &me, &b);
1142
1143 assert!(
1144 graph.simple_loopback_to_self(2, None).is_empty(),
1145 "edge me→a lacks intermediate capacity, so EdgeValueFn::forward_without_self_loopback prunes it"
1146 );
1147
1148 Ok(())
1149 }
1150
1151 #[test]
1152 fn loopback_returns_empty_when_intermediate_edge_lacks_capacity() -> anyhow::Result<()> {
1153 let me = pubkey_from(&SECRET_0);
1156 let a = pubkey_from(&SECRET_1);
1157 let b = pubkey_from(&SECRET_2);
1158
1159 let graph = ChannelGraph::new(me);
1160 graph.add_node(a);
1161 graph.add_node(b);
1162 graph.add_edge(&me, &a)?;
1163 graph.add_edge(&a, &b)?;
1164 graph.add_edge(&me, &b)?;
1165 mark_edge_loopback_ready(&graph, &me, &a);
1167 mark_edge_connected(&graph, &me, &b);
1170
1171 assert!(
1172 graph.simple_loopback_to_self(2, None).is_empty(),
1173 "edge a→b lacks capacity, so EdgeValueFn::forward_without_self_loopback prunes the path"
1174 );
1175
1176 Ok(())
1177 }
1178
1179 #[test]
1180 fn loopback_two_edge_triangle() -> anyhow::Result<()> {
1181 let me = pubkey_from(&SECRET_0);
1184 let a = pubkey_from(&SECRET_1);
1185 let b = pubkey_from(&SECRET_2);
1186
1187 let graph = ChannelGraph::new(me);
1188 graph.add_node(a);
1189 graph.add_node(b);
1190 graph.add_edge(&me, &a)?;
1191 graph.add_edge(&a, &b)?;
1192 graph.add_edge(&me, &b)?;
1193 mark_edge_loopback_ready(&graph, &me, &a);
1195 mark_edge_with_capacity(&graph, &a, &b);
1197 mark_edge_connected(&graph, &me, &b);
1199
1200 let routes = graph.simple_loopback_to_self(2, None);
1201 assert_eq!(routes.len(), 1, "should find exactly one 2-edge loopback");
1202
1203 let (path, _path_id) = &routes[0];
1204 assert_eq!(path.len(), 3, "loopback path: 2 internal nodes + closing me");
1207 assert_eq!(path.last(), Some(&me), "path should end with me (closing loopback)");
1208 assert_eq!(path[0], a, "first intermediate should be a");
1209 assert_eq!(path[1], b, "destination (connected neighbor) should be b");
1210
1211 Ok(())
1212 }
1213
1214 #[test]
1215 fn loopback_three_edge_chain() -> anyhow::Result<()> {
1216 let me = pubkey_from(&SECRET_0);
1219 let a = pubkey_from(&SECRET_1);
1220 let b = pubkey_from(&SECRET_2);
1221 let c = pubkey_from(&SECRET_3);
1222
1223 let graph = ChannelGraph::new(me);
1224 graph.add_node(a);
1225 graph.add_node(b);
1226 graph.add_node(c);
1227 graph.add_edge(&me, &a)?;
1228 graph.add_edge(&a, &b)?;
1229 graph.add_edge(&b, &c)?;
1230 graph.add_edge(&me, &c)?;
1231 mark_edge_loopback_ready(&graph, &me, &a);
1232 mark_edge_with_capacity(&graph, &a, &b);
1233 mark_edge_with_capacity(&graph, &b, &c);
1234 mark_edge_connected(&graph, &me, &c);
1235
1236 let routes = graph.simple_loopback_to_self(3, None);
1237 assert_eq!(routes.len(), 1, "should find exactly one 3-edge loopback");
1238
1239 let (path, _path_id) = &routes[0];
1240 assert_eq!(path.len(), 4, "3-edge internal path + closing me = 4 nodes");
1243 assert_eq!(path.last(), Some(&me), "ends with me");
1244 assert_eq!(&path[0..3], &[a, b, c], "interior nodes");
1245
1246 Ok(())
1247 }
1248
1249 #[test]
1250 fn loopback_multiple_paths_through_diamond() -> anyhow::Result<()> {
1251 let me = pubkey_from(&SECRET_0);
1255 let a = pubkey_from(&SECRET_1);
1256 let b = pubkey_from(&SECRET_2);
1257 let c = pubkey_from(&SECRET_3);
1258
1259 let graph = ChannelGraph::new(me);
1260 graph.add_node(a);
1261 graph.add_node(b);
1262 graph.add_node(c);
1263 graph.add_edge(&me, &a)?;
1264 graph.add_edge(&me, &b)?;
1265 graph.add_edge(&a, &c)?;
1266 graph.add_edge(&b, &c)?;
1267 graph.add_edge(&me, &c)?;
1268 mark_edge_loopback_ready(&graph, &me, &a);
1269 mark_edge_loopback_ready(&graph, &me, &b);
1270 mark_edge_with_capacity(&graph, &a, &c);
1271 mark_edge_with_capacity(&graph, &b, &c);
1272 mark_edge_connected(&graph, &me, &c);
1273
1274 let routes = graph.simple_loopback_to_self(2, None);
1275 assert_eq!(routes.len(), 2, "diamond should yield two 2-edge loopback paths");
1276
1277 for (path, _path_id) in &routes {
1278 assert_eq!(path.last(), Some(&me), "every path ends with me");
1280 assert_eq!(path[path.len() - 2], c, "penultimate node is c (connected neighbor)");
1281 }
1282
1283 let intermediates: HashSet<_> = routes.iter().map(|(p, _)| p[0]).collect();
1285 assert!(intermediates.contains(&a), "should include path through a");
1286 assert!(intermediates.contains(&b), "should include path through b");
1287
1288 Ok(())
1289 }
1290
1291 #[test]
1292 fn loopback_to_multiple_connected_neighbors() -> anyhow::Result<()> {
1293 let me = pubkey_from(&SECRET_0);
1297 let a = pubkey_from(&SECRET_1);
1298 let b = pubkey_from(&SECRET_2);
1299
1300 let graph = ChannelGraph::new(me);
1301 graph.add_node(a);
1302 graph.add_node(b);
1303 graph.add_edge(&me, &a)?;
1304 graph.add_edge(&me, &b)?;
1305 graph.add_edge(&a, &b)?;
1306 graph.add_edge(&b, &a)?;
1307 mark_edge_loopback_ready(&graph, &me, &a);
1308 mark_edge_loopback_ready(&graph, &me, &b);
1309 mark_edge_with_capacity(&graph, &a, &b);
1310 mark_edge_with_capacity(&graph, &b, &a);
1311
1312 let routes = graph.simple_loopback_to_self(2, None);
1313 assert_eq!(
1314 routes.len(),
1315 2,
1316 "should find loopback paths to both connected neighbors"
1317 );
1318
1319 for (path, _) in &routes {
1321 assert_eq!(path.last(), Some(&me));
1322 }
1323
1324 let destinations: HashSet<_> = routes.iter().map(|(p, _)| p[p.len() - 2]).collect();
1326 assert_eq!(destinations.len(), 2, "should reach both connected neighbors");
1327 assert!(destinations.contains(&a));
1328 assert!(destinations.contains(&b));
1329
1330 Ok(())
1331 }
1332
1333 #[test]
1334 fn loopback_disconnected_neighbor_is_excluded() -> anyhow::Result<()> {
1335 let me = pubkey_from(&SECRET_0);
1339 let a = pubkey_from(&SECRET_1);
1340 let b = pubkey_from(&SECRET_2);
1341 let c = pubkey_from(&SECRET_3);
1342
1343 let graph = ChannelGraph::new(me);
1344 graph.add_node(a);
1345 graph.add_node(b);
1346 graph.add_node(c);
1347 graph.add_edge(&me, &a)?;
1348 graph.add_edge(&a, &b)?;
1349 graph.add_edge(&a, &c)?;
1350 graph.add_edge(&me, &b)?;
1351 graph.add_edge(&me, &c)?;
1352 mark_edge_loopback_ready(&graph, &me, &a);
1353 mark_edge_with_capacity(&graph, &a, &b);
1354 mark_edge_with_capacity(&graph, &a, &c);
1355 mark_edge_connected(&graph, &me, &b);
1357 let routes = graph.simple_loopback_to_self(2, None);
1360 assert_eq!(routes.len(), 1, "only the path to connected neighbor b should be found");
1361
1362 let (path, _) = &routes[0];
1363 assert_eq!(path[path.len() - 2], b, "destination should be b, not c");
1364
1365 Ok(())
1366 }
1367
1368 #[test]
1369 fn loopback_take_count_limits_results() -> anyhow::Result<()> {
1370 let me = pubkey_from(&SECRET_0);
1373 let a = pubkey_from(&SECRET_1);
1374 let b = pubkey_from(&SECRET_2);
1375 let c = pubkey_from(&SECRET_3);
1376 let d = pubkey_from(&SECRET_4);
1377
1378 let graph = ChannelGraph::new(me);
1379 for node in [a, b, c, d] {
1380 graph.add_node(node);
1381 }
1382 graph.add_edge(&me, &a)?;
1383 graph.add_edge(&me, &b)?;
1384 graph.add_edge(&me, &c)?;
1385 graph.add_edge(&me, &d)?;
1386 graph.add_edge(&a, &d)?;
1387 graph.add_edge(&b, &d)?;
1388 graph.add_edge(&c, &d)?;
1389 mark_edge_loopback_ready(&graph, &me, &a);
1390 mark_edge_loopback_ready(&graph, &me, &b);
1391 mark_edge_loopback_ready(&graph, &me, &c);
1392 mark_edge_with_capacity(&graph, &a, &d);
1393 mark_edge_with_capacity(&graph, &b, &d);
1394 mark_edge_with_capacity(&graph, &c, &d);
1395 mark_edge_connected(&graph, &me, &d);
1396
1397 let all_routes = graph.simple_loopback_to_self(2, None);
1399 assert_eq!(all_routes.len(), 3, "should find 3 loopback paths without limit");
1400
1401 let limited = graph.simple_loopback_to_self(2, Some(1));
1403 assert_eq!(limited.len(), 1, "take_count=1 should limit to 1 result");
1404
1405 Ok(())
1406 }
1407
1408 #[test]
1409 fn loopback_path_ids_differ_for_distinct_routes() -> anyhow::Result<()> {
1410 let me = pubkey_from(&SECRET_0);
1412 let a = pubkey_from(&SECRET_1);
1413 let b = pubkey_from(&SECRET_2);
1414 let c = pubkey_from(&SECRET_3);
1415
1416 let graph = ChannelGraph::new(me);
1417 graph.add_node(a);
1418 graph.add_node(b);
1419 graph.add_node(c);
1420 graph.add_edge(&me, &a)?;
1421 graph.add_edge(&me, &b)?;
1422 graph.add_edge(&a, &c)?;
1423 graph.add_edge(&b, &c)?;
1424 graph.add_edge(&me, &c)?;
1425 mark_edge_loopback_ready(&graph, &me, &a);
1426 mark_edge_loopback_ready(&graph, &me, &b);
1427 mark_edge_with_capacity(&graph, &a, &c);
1428 mark_edge_with_capacity(&graph, &b, &c);
1429 mark_edge_connected(&graph, &me, &c);
1430
1431 let routes = graph.simple_loopback_to_self(2, None);
1432 assert_eq!(routes.len(), 2);
1433
1434 let path_ids: Vec<PathId> = routes.iter().map(|(_, pid)| *pid).collect();
1435 assert_ne!(
1436 path_ids[0], path_ids[1],
1437 "distinct loopback paths should have different PathIds"
1438 );
1439
1440 Ok(())
1441 }
1442
1443 #[test]
1444 fn loopback_mismatched_length_returns_empty() -> anyhow::Result<()> {
1445 let me = pubkey_from(&SECRET_0);
1448 let a = pubkey_from(&SECRET_1);
1449 let b = pubkey_from(&SECRET_2);
1450
1451 let graph = ChannelGraph::new(me);
1452 graph.add_node(a);
1453 graph.add_node(b);
1454 graph.add_edge(&me, &a)?;
1455 graph.add_edge(&a, &b)?;
1456 graph.add_edge(&me, &b)?;
1457 mark_edge_loopback_ready(&graph, &me, &a);
1458 mark_edge_with_capacity(&graph, &a, &b);
1459 mark_edge_connected(&graph, &me, &b);
1460
1461 assert_eq!(graph.simple_loopback_to_self(2, None).len(), 1);
1463 assert!(
1465 graph.simple_loopback_to_self(3, None).is_empty(),
1466 "no 3-edge internal path exists"
1467 );
1468
1469 Ok(())
1470 }
1471}