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