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