@@ -467,7 +467,7 @@ impl SimulatedChannel {
467467
468468/// SimNetwork represents a high level network coordinator that is responsible for the task of actually propagating
469469/// payments through the simulated network.
470- trait SimNetwork : Send + Sync {
470+ pub trait SimNetwork : Send + Sync {
471471 /// Sends payments over the route provided through the network, reporting the final payment outcome to the sender
472472 /// channel provided.
473473 fn dispatch_payment (
@@ -488,7 +488,7 @@ trait SimNetwork: Send + Sync {
488488/// all functionality through to a coordinating simulation network. This implementation contains both the [`SimNetwork`]
489489/// implementation that will allow us to dispatch payments and a read-only NetworkGraph that is used for pathfinding.
490490/// While these two could be combined, we re-use the LDK-native struct to allow re-use of their pathfinding logic.
491- struct SimNode < ' a , T : SimNetwork > {
491+ pub struct SimNode < ' a , T : SimNetwork > {
492492 info : NodeInfo ,
493493 /// The underlying execution network that will be responsible for dispatching payments.
494494 network : Arc < Mutex < T > > ,
@@ -526,6 +526,37 @@ impl<'a, T: SimNetwork> SimNode<'a, T> {
526526 scorer,
527527 }
528528 }
529+
530+ /// Dispatches a payment to a specified route.
531+ /// The [`lightning::routing::router::build_route_from_hops`] function can be used to build the route to be passed here.
532+ ///
533+ /// **Note:** The payment hash passed in here should be used in track_payment to track the payment outcome.
534+ pub async fn send_to_route (
535+ & mut self ,
536+ route : Route ,
537+ payment_hash : PaymentHash ,
538+ ) -> Result < ( ) , LightningError > {
539+ let ( sender, receiver) = channel ( ) ;
540+
541+ // Check for payment hash collision, failing the payment if we happen to repeat one.
542+ match self . in_flight . entry ( payment_hash) {
543+ Entry :: Occupied ( _) => {
544+ return Err ( LightningError :: SendPaymentError (
545+ "payment hash exists" . to_string ( ) ,
546+ ) ) ;
547+ } ,
548+ Entry :: Vacant ( vacant) => {
549+ vacant. insert ( receiver) ;
550+ } ,
551+ }
552+
553+ self . network
554+ . lock ( )
555+ . await
556+ . dispatch_payment ( self . info . pubkey , route, payment_hash, sender) ;
557+
558+ Ok ( ( ) )
559+ }
529560}
530561
531562/// Produces the node info for a mocked node, filling in the features that the simulator requires.
@@ -642,7 +673,8 @@ impl<T: SimNetwork> LightningNode for SimNode<'_, T> {
642673 }
643674
644675 /// track_payment blocks until a payment outcome is returned for the payment hash provided, or the shutdown listener
645- /// provided is triggered. This call will fail if the hash provided was not obtained by calling send_payment first.
676+ /// provided is triggered. This call will fail if the hash provided was not obtained from send_payment or passed
677+ /// into send_to_route first.
646678 async fn track_payment (
647679 & mut self ,
648680 hash : & PaymentHash ,
@@ -1470,6 +1502,7 @@ mod tests {
14701502 use super :: * ;
14711503 use crate :: clock:: SystemClock ;
14721504 use crate :: test_utils:: get_random_keypair;
1505+ use lightning:: routing:: router:: build_route_from_hops;
14731506 use lightning:: routing:: router:: Route ;
14741507 use mockall:: mock;
14751508 use ntest:: assert_true;
@@ -2244,6 +2277,50 @@ mod tests {
22442277 test_kit. graph . tasks . wait ( ) . await ;
22452278 }
22462279
2280+ #[ tokio:: test]
2281+ async fn test_send_and_track_payment_to_route ( ) {
2282+ let chan_capacity = 500_000_000 ;
2283+ let test_kit =
2284+ DispatchPaymentTestKit :: new ( chan_capacity, vec ! [ ] , CustomRecords :: default ( ) ) . await ;
2285+
2286+ let mut node = SimNode :: new (
2287+ test_kit. nodes [ 0 ] ,
2288+ Arc :: new ( Mutex :: new ( test_kit. graph ) ) ,
2289+ test_kit. routing_graph . clone ( ) ,
2290+ ) ;
2291+
2292+ let route = build_route_from_hops (
2293+ & test_kit. nodes [ 0 ] ,
2294+ & [ test_kit. nodes [ 1 ] , test_kit. nodes [ 2 ] , test_kit. nodes [ 3 ] ] ,
2295+ & RouteParameters {
2296+ payment_params : PaymentParameters :: from_node_id ( * test_kit. nodes . last ( ) . unwrap ( ) , 0 )
2297+ . with_max_total_cltv_expiry_delta ( u32:: MAX )
2298+ // TODO: set non-zero value to support MPP.
2299+ . with_max_path_count ( 1 )
2300+ // Allow sending htlcs up to 50% of the channel's capacity.
2301+ . with_max_channel_saturation_power_of_half ( 1 ) ,
2302+ final_value_msat : 20_000 ,
2303+ max_total_routing_fee_msat : None ,
2304+ } ,
2305+ & test_kit. routing_graph ,
2306+ & WrappedLog { } ,
2307+ & [ 0 ; 32 ] ,
2308+ )
2309+ . unwrap ( ) ;
2310+
2311+ let preimage = PaymentPreimage ( rand:: random ( ) ) ;
2312+ let payment_hash = preimage. into ( ) ;
2313+ node. send_to_route ( route, payment_hash) . await . unwrap ( ) ;
2314+
2315+ let ( _, shutdown_listener) = triggered:: trigger ( ) ;
2316+ let result = node
2317+ . track_payment ( & payment_hash, shutdown_listener)
2318+ . await
2319+ . unwrap ( ) ;
2320+
2321+ assert ! ( matches!( result. payment_outcome, PaymentOutcome :: Success ) ) ;
2322+ }
2323+
22472324 fn create_intercept_request ( shutdown_listener : Listener ) -> InterceptRequest {
22482325 let ( _, pubkey) = get_random_keypair ( ) ;
22492326 InterceptRequest {
0 commit comments