@@ -484,11 +484,57 @@ pub trait SimNetwork: Send + Sync {
484
484
fn list_nodes ( & self ) -> Result < Vec < NodeInfo > , LightningError > ;
485
485
}
486
486
487
+ /// A trait for custom pathfinding implementations.
488
+ pub trait PathFinder < ' a > : Send + Sync {
489
+ fn find_route (
490
+ & self ,
491
+ source : & PublicKey ,
492
+ dest : PublicKey ,
493
+ amount_msat : u64 ,
494
+ pathfinding_graph : & NetworkGraph < & ' a WrappedLog > ,
495
+ scorer : & ProbabilisticScorer < Arc < NetworkGraph < & ' a WrappedLog > > , & ' a WrappedLog > ,
496
+ ) -> Result < Route , SimulationError > ;
497
+ }
498
+
499
+ /// Default pathfinder that uses LDK's pathfinding algorithm.
500
+ #[ derive( Clone ) ]
501
+ pub struct DefaultPathFinder ;
502
+
503
+ impl < ' a > PathFinder < ' a > for DefaultPathFinder {
504
+ fn find_route (
505
+ & self ,
506
+ source : & PublicKey ,
507
+ dest : PublicKey ,
508
+ amount_msat : u64 ,
509
+ pathfinding_graph : & NetworkGraph < & ' a WrappedLog > ,
510
+ scorer : & ProbabilisticScorer < Arc < NetworkGraph < & ' a WrappedLog > > , & ' a WrappedLog > ,
511
+ ) -> Result < Route , SimulationError > {
512
+ find_route (
513
+ source,
514
+ & RouteParameters {
515
+ payment_params : PaymentParameters :: from_node_id ( dest, 0 )
516
+ . with_max_total_cltv_expiry_delta ( u32:: MAX )
517
+ . with_max_path_count ( 1 )
518
+ . with_max_channel_saturation_power_of_half ( 1 ) ,
519
+ final_value_msat : amount_msat,
520
+ max_total_routing_fee_msat : None ,
521
+ } ,
522
+ pathfinding_graph,
523
+ None ,
524
+ & WrappedLog { } ,
525
+ scorer,
526
+ & Default :: default ( ) ,
527
+ & [ 0 ; 32 ] ,
528
+ )
529
+ . map_err ( |e| SimulationError :: SimulatedNetworkError ( e. err ) )
530
+ }
531
+ }
532
+
487
533
/// A wrapper struct used to implement the LightningNode trait (can be thought of as "the" lightning node). Passes
488
534
/// all functionality through to a coordinating simulation network. This implementation contains both the [`SimNetwork`]
489
535
/// implementation that will allow us to dispatch payments and a read-only NetworkGraph that is used for pathfinding.
490
536
/// While these two could be combined, we re-use the LDK-native struct to allow re-use of their pathfinding logic.
491
- pub struct SimNode < ' a , T : SimNetwork > {
537
+ pub struct SimNode < ' a , T : SimNetwork , P : PathFinder < ' a > = DefaultPathFinder > {
492
538
info : NodeInfo ,
493
539
/// The underlying execution network that will be responsible for dispatching payments.
494
540
network : Arc < Mutex < T > > ,
@@ -499,15 +545,18 @@ pub struct SimNode<'a, T: SimNetwork> {
499
545
/// Probabilistic scorer used to rank paths through the network for routing. This is reused across
500
546
/// multiple payments to maintain scoring state.
501
547
scorer : ProbabilisticScorer < Arc < NetworkGraph < & ' a WrappedLog > > , & ' a WrappedLog > ,
548
+ /// The pathfinder implementation to use for finding routes
549
+ pathfinder : P ,
502
550
}
503
551
504
- impl < ' a , T : SimNetwork > SimNode < ' a , T > {
552
+ impl < ' a , T : SimNetwork , P : PathFinder < ' a > > SimNode < ' a , T , P > {
505
553
/// Creates a new simulation node that refers to the high level network coordinator provided to process payments
506
554
/// on its behalf. The pathfinding graph is provided separately so that each node can handle its own pathfinding.
507
555
pub fn new (
508
556
pubkey : PublicKey ,
509
557
payment_network : Arc < Mutex < T > > ,
510
558
pathfinding_graph : Arc < NetworkGraph < & ' a WrappedLog > > ,
559
+ pathfinder : P ,
511
560
) -> Self {
512
561
// Initialize the probabilistic scorer with default parameters for learning from payment
513
562
// history. These parameters control how much successful/failed payments affect routing
@@ -524,6 +573,7 @@ impl<'a, T: SimNetwork> SimNode<'a, T> {
524
573
in_flight : HashMap :: new ( ) ,
525
574
pathfinding_graph,
526
575
scorer,
576
+ pathfinder,
527
577
}
528
578
}
529
579
@@ -557,6 +607,37 @@ impl<'a, T: SimNetwork> SimNode<'a, T> {
557
607
558
608
Ok ( ( ) )
559
609
}
610
+
611
+ /// Dispatches a payment to a specified route.
612
+ /// The [`lightning::routing::router::build_route_from_hops`] function can be used to build the route to be passed here.
613
+ ///
614
+ /// **Note:** The payment hash passed in here should be used in track_payment to track the payment outcome.
615
+ pub async fn send_to_route (
616
+ & mut self ,
617
+ route : Route ,
618
+ payment_hash : PaymentHash ,
619
+ ) -> Result < ( ) , LightningError > {
620
+ let ( sender, receiver) = channel ( ) ;
621
+
622
+ // Check for payment hash collision, failing the payment if we happen to repeat one.
623
+ match self . in_flight . entry ( payment_hash) {
624
+ Entry :: Occupied ( _) => {
625
+ return Err ( LightningError :: SendPaymentError (
626
+ "payment hash exists" . to_string ( ) ,
627
+ ) ) ;
628
+ } ,
629
+ Entry :: Vacant ( vacant) => {
630
+ vacant. insert ( receiver) ;
631
+ } ,
632
+ }
633
+
634
+ self . network
635
+ . lock ( )
636
+ . await
637
+ . dispatch_payment ( self . info . pubkey , route, payment_hash, sender) ;
638
+
639
+ Ok ( ( ) )
640
+ }
560
641
}
561
642
562
643
/// Produces the node info for a mocked node, filling in the features that the simulator requires.
@@ -572,39 +653,8 @@ fn node_info(pubkey: PublicKey) -> NodeInfo {
572
653
}
573
654
}
574
655
575
- /// Uses LDK's pathfinding algorithm with default parameters to find a path from source to destination, with no
576
- /// restrictions on fee budget.
577
- fn find_payment_route < ' a > (
578
- source : & PublicKey ,
579
- dest : PublicKey ,
580
- amount_msat : u64 ,
581
- pathfinding_graph : & NetworkGraph < & ' a WrappedLog > ,
582
- scorer : & ProbabilisticScorer < Arc < NetworkGraph < & ' a WrappedLog > > , & ' a WrappedLog > ,
583
- ) -> Result < Route , SimulationError > {
584
- find_route (
585
- source,
586
- & RouteParameters {
587
- payment_params : PaymentParameters :: from_node_id ( dest, 0 )
588
- . with_max_total_cltv_expiry_delta ( u32:: MAX )
589
- // TODO: set non-zero value to support MPP.
590
- . with_max_path_count ( 1 )
591
- // Allow sending htlcs up to 50% of the channel's capacity.
592
- . with_max_channel_saturation_power_of_half ( 1 ) ,
593
- final_value_msat : amount_msat,
594
- max_total_routing_fee_msat : None ,
595
- } ,
596
- pathfinding_graph,
597
- None ,
598
- & WrappedLog { } ,
599
- scorer,
600
- & Default :: default ( ) ,
601
- & [ 0 ; 32 ] ,
602
- )
603
- . map_err ( |e| SimulationError :: SimulatedNetworkError ( e. err ) )
604
- }
605
-
606
656
#[ async_trait]
607
- impl < T : SimNetwork > LightningNode for SimNode < ' _ , T > {
657
+ impl < ' a , T : SimNetwork , P : PathFinder < ' a > > LightningNode for SimNode < ' a , T , P > {
608
658
fn get_info ( & self ) -> & NodeInfo {
609
659
& self . info
610
660
}
@@ -620,8 +670,24 @@ impl<T: SimNetwork> LightningNode for SimNode<'_, T> {
620
670
dest : PublicKey ,
621
671
amount_msat : u64 ,
622
672
) -> Result < PaymentHash , LightningError > {
623
- // Create a sender and receiver pair that will be used to report the results of the payment and add them to
624
- // our internal tracking state along with the chosen payment hash.
673
+ // Use the stored scorer when finding a route
674
+ let route = match self . pathfinder . find_route (
675
+ & self . info . pubkey ,
676
+ dest,
677
+ amount_msat,
678
+ & self . pathfinding_graph ,
679
+ & self . scorer ,
680
+ ) {
681
+ Ok ( route) => route,
682
+ Err ( e) => {
683
+ log:: warn!( "No route found: {e}" ) ;
684
+ return Err ( LightningError :: SendPaymentError ( format ! (
685
+ "No route found: {e}"
686
+ ) ) ) ;
687
+ } ,
688
+ } ;
689
+
690
+ // Create a channel to receive the payment result.
625
691
let ( sender, receiver) = channel ( ) ;
626
692
let preimage = PaymentPreimage ( rand:: random ( ) ) ;
627
693
let payment_hash = preimage. into ( ) ;
@@ -638,36 +704,13 @@ impl<T: SimNetwork> LightningNode for SimNode<'_, T> {
638
704
} ,
639
705
}
640
706
641
- // Use the stored scorer when finding a route
642
- let route = match find_payment_route (
643
- & self . info . pubkey ,
644
- dest,
645
- amount_msat,
646
- & self . pathfinding_graph ,
647
- & self . scorer ,
648
- ) {
649
- Ok ( path) => path,
650
- // In the case that we can't find a route for the payment, we still report a successful payment *api call*
651
- // and report RouteNotFound to the tracking channel. This mimics the behavior of real nodes.
652
- Err ( e) => {
653
- log:: trace!( "Could not find path for payment: {:?}." , e) ;
654
-
655
- if let Err ( e) = sender. send ( Ok ( PaymentResult {
656
- htlc_count : 0 ,
657
- payment_outcome : PaymentOutcome :: RouteNotFound ,
658
- } ) ) {
659
- log:: error!( "Could not send payment result: {:?}." , e) ;
660
- }
661
-
662
- return Ok ( payment_hash) ;
663
- } ,
664
- } ;
665
-
666
- // If we did successfully obtain a route, dispatch the payment through the network and then report success.
667
- self . network
668
- . lock ( )
669
- . await
670
- . dispatch_payment ( self . info . pubkey , route, payment_hash, sender) ;
707
+ // Dispatch the payment through the network
708
+ self . network . lock ( ) . await . dispatch_payment (
709
+ self . info . pubkey ,
710
+ route,
711
+ payment_hash,
712
+ sender,
713
+ ) ;
671
714
672
715
Ok ( payment_hash)
673
716
}
@@ -1007,19 +1050,24 @@ impl SimGraph {
1007
1050
}
1008
1051
1009
1052
/// Produces a map of node public key to lightning node implementation to be used for simulations.
1010
- pub async fn ln_node_from_graph (
1053
+ pub async fn ln_node_from_graph < P > (
1011
1054
graph : Arc < Mutex < SimGraph > > ,
1012
- routing_graph : Arc < NetworkGraph < & ' _ WrappedLog > > ,
1013
- ) -> HashMap < PublicKey , Arc < Mutex < dyn LightningNode + ' _ > > > {
1055
+ routing_graph : Arc < NetworkGraph < & ' static WrappedLog > > ,
1056
+ pathfinder : P ,
1057
+ ) -> HashMap < PublicKey , Arc < Mutex < dyn LightningNode > > >
1058
+ where
1059
+ P : for < ' a > PathFinder < ' a > + Clone + ' static ,
1060
+ {
1014
1061
let mut nodes: HashMap < PublicKey , Arc < Mutex < dyn LightningNode > > > = HashMap :: new ( ) ;
1015
-
1062
+
1016
1063
for pk in graph. lock ( ) . await . nodes . keys ( ) {
1017
1064
nodes. insert (
1018
1065
* pk,
1019
1066
Arc :: new ( Mutex :: new ( SimNode :: new (
1020
1067
* pk,
1021
1068
graph. clone ( ) ,
1022
1069
routing_graph. clone ( ) ,
1070
+ pathfinder. clone ( ) ,
1023
1071
) ) ) ,
1024
1072
) ;
1025
1073
}
@@ -1897,7 +1945,7 @@ mod tests {
1897
1945
1898
1946
// Create a simulated node for the first channel in our network.
1899
1947
let pk = channels[ 0 ] . node_1 . policy . pubkey ;
1900
- let mut node = SimNode :: new ( pk, sim_network. clone ( ) , Arc :: new ( graph) ) ;
1948
+ let mut node = SimNode :: new ( pk, sim_network. clone ( ) , Arc :: new ( graph) , DefaultPathFinder ) ;
1901
1949
1902
1950
// Prime mock to return node info from lookup and assert that we get the pubkey we're expecting.
1903
1951
let lookup_pk = channels[ 3 ] . node_1 . policy . pubkey ;
@@ -1988,6 +2036,7 @@ mod tests {
1988
2036
routing_graph : Arc < NetworkGraph < & ' a WrappedLog > > ,
1989
2037
scorer : ProbabilisticScorer < Arc < NetworkGraph < & ' a WrappedLog > > , & ' a WrappedLog > ,
1990
2038
shutdown : ( Trigger , Listener ) ,
2039
+ pathfinder : DefaultPathFinder ,
1991
2040
}
1992
2041
1993
2042
impl DispatchPaymentTestKit < ' _ > {
@@ -2036,6 +2085,7 @@ mod tests {
2036
2085
routing_graph,
2037
2086
scorer,
2038
2087
shutdown : shutdown_clone,
2088
+ pathfinder : DefaultPathFinder ,
2039
2089
} ;
2040
2090
2041
2091
// Assert that our channel balance is all on the side of the channel opener when we start up.
@@ -2078,8 +2128,13 @@ mod tests {
2078
2128
dest : PublicKey ,
2079
2129
amt : u64 ,
2080
2130
) -> ( Route , Result < PaymentResult , LightningError > ) {
2081
- let route =
2082
- find_payment_route ( & source, dest, amt, & self . routing_graph , & self . scorer ) . unwrap ( ) ;
2131
+ let route = self . pathfinder . find_route (
2132
+ & source,
2133
+ dest,
2134
+ amt,
2135
+ & self . routing_graph ,
2136
+ & self . scorer ,
2137
+ ) . unwrap ( ) ;
2083
2138
2084
2139
let ( sender, receiver) = oneshot:: channel ( ) ;
2085
2140
self . graph
0 commit comments