@@ -17,12 +17,15 @@ use lightning::blinded_path::IntroductionNode;
1717use lightning:: impl_writeable_tlv_based;
1818use lightning:: ln:: channel_state:: ChannelDetails ;
1919use lightning:: ln:: channelmanager:: { PaymentId , MIN_FINAL_CLTV_EXPIRY_DELTA } ;
20+ use lightning:: routing:: gossip:: NodeId ;
2021use lightning:: routing:: router:: { InFlightHtlcs , Route , RouteParameters , Router } ;
2122use lightning:: sign:: { EntropySource , ReceiveAuthKey } ;
2223use lightning:: types:: features:: BlindedHopFeatures ;
2324use lightning:: types:: payment:: PaymentHash ;
25+ use std:: sync:: Arc ;
2426
2527use crate :: payment:: PaymentMetadata ;
28+ use crate :: types:: Graph ;
2629
2730/// Parameters needed to construct an LSPS2 blinded payment path.
2831#[ derive( Clone , Copy , Debug , PartialEq , Eq ) ]
@@ -50,13 +53,14 @@ impl_writeable_tlv_based!(LSPS2LeaseParameters, {
5053/// A router wrapper that uses ordinary payment paths when available and LSPS2 JIT paths otherwise.
5154pub struct LSPS2Router < R : Router , ES : EntropySource > {
5255 inner_router : R ,
56+ network_graph : Arc < Graph > ,
5357 entropy_source : ES ,
5458}
5559
5660impl < R : Router , ES : EntropySource > LSPS2Router < R , ES > {
5761 /// Constructs an LSPS2-aware wrapper around `inner_router`.
58- pub fn new ( inner_router : R , entropy_source : ES ) -> Self {
59- Self { inner_router, entropy_source }
62+ pub fn new ( inner_router : R , network_graph : Arc < Graph > , entropy_source : ES ) -> Self {
63+ Self { inner_router, network_graph , entropy_source }
6064 }
6165
6266 fn payment_parameters ( & self , payment_context : & PaymentContext ) -> Vec < LSPS2LeaseParameters > {
@@ -103,15 +107,26 @@ impl<R: Router, ES: EntropySource> Router for LSPS2Router<R, ES> {
103107 ) -> Result < Vec < BlindedPaymentPath > , ( ) > {
104108 let parameters = self . payment_parameters ( & tlvs. payment_context ) ;
105109 let allow_mpp = parameters. iter ( ) . all ( |params| params. payment_size_msat . is_some ( ) ) ;
110+ let is_eligible_first_hop = |channel : & & ChannelDetails | {
111+ self . network_graph
112+ . read_only ( )
113+ . node ( & NodeId :: from_pubkey ( & channel. counterparty . node_id ) )
114+ . is_some ( ) && channel
115+ . counterparty
116+ . forwarding_info
117+ . clone ( )
118+ . is_some_and ( |info| PaymentRelay :: try_from ( info) . is_ok ( ) )
119+ } ;
106120 let direct_path_has_sufficient_liquidity = amount_msats. map_or ( true , |amount_msats| {
107121 if allow_mpp {
108122 first_hops
109123 . iter ( )
124+ . filter ( is_eligible_first_hop)
110125 . map ( |channel| channel. inbound_capacity_msat )
111126 . fold ( 0u64 , u64:: saturating_add)
112127 >= amount_msats
113128 } else {
114- first_hops. iter ( ) . any ( |channel| {
129+ first_hops. iter ( ) . filter ( is_eligible_first_hop ) . any ( |channel| {
115130 channel. inbound_capacity_msat >= amount_msats
116131 && channel. inbound_htlc_minimum_msat . unwrap_or ( 0 ) <= amount_msats
117132 && channel. inbound_htlc_maximum_msat . unwrap_or ( u64:: MAX ) >= amount_msats
@@ -135,9 +150,9 @@ impl<R: Router, ES: EntropySource> Router for LSPS2Router<R, ES> {
135150 ) ;
136151 // The default router may fall back to a direct path for an announced recipient even when no
137152 // channel-backed paths have enough inbound liquidity. Only accept that fallback when the
138- // locally known channels can receive the resolved amount: across channels when MPP is allowed,
139- // or on one channel, including its HTLC bounds, when MPP is disabled. Otherwise it would hide
140- // the need for a JIT path and leave an invoice that cannot be paid.
153+ // locally known, blinded-path-eligible channels can receive the resolved amount: across channels
154+ // when MPP is allowed, or on one channel, including its HTLC bounds, when MPP is disabled.
155+ // Otherwise it would hide the need for a JIT path and leave an invoice that cannot be paid.
141156 //
142157 // Always prefer usable ordinary paths. Besides avoiding an unnecessary channel open, this
143158 // prevents an MPP payer from splitting one payment across regular and JIT paths. The LSP only
@@ -222,16 +237,21 @@ mod tests {
222237 use super :: * ;
223238
224239 use bitcoin:: secp256k1:: SecretKey ;
240+ use bitcoin:: Network ;
225241 use core:: sync:: atomic:: { AtomicUsize , Ordering } ;
226242 use lightning:: blinded_path:: payment:: { Bolt12OfferContext , PaymentConstraints } ;
227- use lightning:: ln:: channel_state:: { ChannelCounterparty , ChannelShutdownState } ;
243+ use lightning:: ln:: channel_state:: {
244+ ChannelCounterparty , ChannelShutdownState , CounterpartyForwardingInfo ,
245+ } ;
228246 use lightning:: ln:: types:: ChannelId ;
229247 use lightning:: offers:: invoice_request:: InvoiceRequestFields ;
230248 use lightning:: offers:: offer:: OfferId ;
231- use lightning:: types:: features:: InitFeatures ;
249+ use lightning:: types:: features:: { ChannelFeatures , InitFeatures } ;
232250 use lightning:: types:: payment:: PaymentSecret ;
233251 use std:: collections:: BTreeMap ;
234252
253+ use crate :: logger:: Logger ;
254+
235255 #[ derive( Clone ) ]
236256 struct TestEntropy ;
237257
@@ -327,7 +347,11 @@ mod tests {
327347 node_id : pubkey ( 13 ) ,
328348 features : InitFeatures :: empty ( ) ,
329349 unspendable_punishment_reserve : 0 ,
330- forwarding_info : None ,
350+ forwarding_info : Some ( CounterpartyForwardingInfo {
351+ fee_base_msat : 0 ,
352+ fee_proportional_millionths : 0 ,
353+ cltv_expiry_delta : 18 ,
354+ } ) ,
331355 outbound_htlc_minimum_msat : None ,
332356 outbound_htlc_maximum_msat : None ,
333357 } ,
@@ -364,6 +388,26 @@ mod tests {
364388 }
365389 }
366390
391+ fn empty_graph ( ) -> Arc < Graph > {
392+ Arc :: new ( Graph :: new ( Network :: Regtest , Arc :: new ( Logger :: new_log_facade ( ) ) ) )
393+ }
394+
395+ fn announced_graph ( recipient : PublicKey , include_peer : bool ) -> Arc < Graph > {
396+ let graph = empty_graph ( ) ;
397+ let counterparty = if include_peer { pubkey ( 13 ) } else { pubkey ( 14 ) } ;
398+ graph
399+ . add_channel_from_partial_announcement (
400+ 42 ,
401+ None ,
402+ 0 ,
403+ ChannelFeatures :: empty ( ) ,
404+ NodeId :: from_pubkey ( & recipient) ,
405+ NodeId :: from_pubkey ( & counterparty) ,
406+ )
407+ . unwrap ( ) ;
408+ graph
409+ }
410+
367411 fn payment_tlvs ( metadata : BTreeMap < u64 , Vec < u8 > > ) -> ReceiveTlvs {
368412 ReceiveTlvs {
369413 payment_secret : PaymentSecret ( [ 2 ; 32 ] ) ,
@@ -398,7 +442,7 @@ mod tests {
398442 } ;
399443 let metadata = payment_metadata ( parameters) ;
400444 let inner_router = MockRouter { calls : AtomicUsize :: new ( 0 ) , path_kind : MockPathKind :: None } ;
401- let router = LSPS2Router :: new ( inner_router, TestEntropy ) ;
445+ let router = LSPS2Router :: new ( inner_router, empty_graph ( ) , TestEntropy ) ;
402446
403447 let paths = router
404448 . create_blinded_payment_paths (
@@ -433,7 +477,7 @@ mod tests {
433477 } ;
434478 let metadata = payment_metadata ( parameters) ;
435479 let inner_router = MockRouter { calls : AtomicUsize :: new ( 0 ) , path_kind : MockPathKind :: None } ;
436- let router = LSPS2Router :: new ( inner_router, TestEntropy ) ;
480+ let router = LSPS2Router :: new ( inner_router, empty_graph ( ) , TestEntropy ) ;
437481
438482 assert ! ( router
439483 . create_blinded_payment_paths(
@@ -458,7 +502,7 @@ mod tests {
458502 } ;
459503 let metadata = payment_metadata ( parameters) ;
460504 let inner_router = MockRouter { calls : AtomicUsize :: new ( 0 ) , path_kind : MockPathKind :: None } ;
461- let router = LSPS2Router :: new ( inner_router, TestEntropy ) ;
505+ let router = LSPS2Router :: new ( inner_router, empty_graph ( ) , TestEntropy ) ;
462506
463507 let paths = router
464508 . create_blinded_payment_paths (
@@ -488,7 +532,7 @@ mod tests {
488532 let metadata = payment_metadata ( parameters) ;
489533 let inner_router =
490534 MockRouter { calls : AtomicUsize :: new ( 0 ) , path_kind : MockPathKind :: ChannelBacked } ;
491- let router = LSPS2Router :: new ( inner_router, TestEntropy ) ;
535+ let router = LSPS2Router :: new ( inner_router, empty_graph ( ) , TestEntropy ) ;
492536 let recipient = pubkey ( 10 ) ;
493537
494538 let paths = router
@@ -515,7 +559,7 @@ mod tests {
515559 let recipient = pubkey ( 10 ) ;
516560 let inner_router =
517561 MockRouter { calls : AtomicUsize :: new ( 0 ) , path_kind : MockPathKind :: DirectRecipient } ;
518- let router = LSPS2Router :: new ( inner_router, TestEntropy ) ;
562+ let router = LSPS2Router :: new ( inner_router, empty_graph ( ) , TestEntropy ) ;
519563
520564 assert ! (
521565 router
@@ -544,7 +588,7 @@ mod tests {
544588 } ;
545589 let inner_router =
546590 MockRouter { calls : AtomicUsize :: new ( 0 ) , path_kind : MockPathKind :: DirectRecipient } ;
547- let router = LSPS2Router :: new ( inner_router, TestEntropy ) ;
591+ let router = LSPS2Router :: new ( inner_router, announced_graph ( recipient , true ) , TestEntropy ) ;
548592
549593 let paths = router
550594 . create_blinded_payment_paths (
@@ -560,6 +604,73 @@ mod tests {
560604 assert_eq ! ( paths[ 0 ] . introduction_node( ) , & IntroductionNode :: NodeId ( recipient) ) ;
561605 }
562606
607+ #[ test]
608+ fn ignores_capacity_without_forwarding_info ( ) {
609+ let recipient = pubkey ( 10 ) ;
610+ let lsp_node_id = pubkey ( 11 ) ;
611+ let parameters = LSPS2LeaseParameters {
612+ lsp_node_id,
613+ intercept_scid : 42 ,
614+ cltv_expiry_delta : 48 ,
615+ payment_size_msat : Some ( 3_000 ) ,
616+ valid_until : u64:: MAX ,
617+ } ;
618+ let mut missing_forwarding_info = first_hop ( 3_000 , None , None ) ;
619+ missing_forwarding_info. counterparty . forwarding_info = None ;
620+ let inner_router =
621+ MockRouter { calls : AtomicUsize :: new ( 0 ) , path_kind : MockPathKind :: DirectRecipient } ;
622+ let router = LSPS2Router :: new ( inner_router, announced_graph ( recipient, true ) , TestEntropy ) ;
623+ let paths = router
624+ . create_blinded_payment_paths (
625+ recipient,
626+ ReceiveAuthKey ( [ 3 ; 32 ] ) ,
627+ vec ! [ missing_forwarding_info] ,
628+ payment_tlvs ( payment_metadata ( parameters) ) ,
629+ Some ( 3_000 ) ,
630+ & Secp256k1 :: new ( ) ,
631+ )
632+ . unwrap ( ) ;
633+
634+ assert_eq ! (
635+ paths[ 0 ] . introduction_node( ) ,
636+ & IntroductionNode :: NodeId ( lsp_node_id) ,
637+ "capacity without forwarding parameters hid the JIT path"
638+ ) ;
639+ }
640+
641+ #[ test]
642+ fn ignores_capacity_from_unannounced_peer ( ) {
643+ let recipient = pubkey ( 10 ) ;
644+ let lsp_node_id = pubkey ( 11 ) ;
645+ let parameters = LSPS2LeaseParameters {
646+ lsp_node_id,
647+ intercept_scid : 42 ,
648+ cltv_expiry_delta : 48 ,
649+ payment_size_msat : Some ( 3_000 ) ,
650+ valid_until : u64:: MAX ,
651+ } ;
652+
653+ let inner_router =
654+ MockRouter { calls : AtomicUsize :: new ( 0 ) , path_kind : MockPathKind :: DirectRecipient } ;
655+ let router = LSPS2Router :: new ( inner_router, announced_graph ( recipient, false ) , TestEntropy ) ;
656+ let paths = router
657+ . create_blinded_payment_paths (
658+ recipient,
659+ ReceiveAuthKey ( [ 3 ; 32 ] ) ,
660+ vec ! [ first_hop( 3_000 , None , None ) ] ,
661+ payment_tlvs ( payment_metadata ( parameters) ) ,
662+ Some ( 3_000 ) ,
663+ & Secp256k1 :: new ( ) ,
664+ )
665+ . unwrap ( ) ;
666+
667+ assert_eq ! (
668+ paths[ 0 ] . introduction_node( ) ,
669+ & IntroductionNode :: NodeId ( lsp_node_id) ,
670+ "capacity through an unannounced peer hid the JIT path"
671+ ) ;
672+ }
673+
563674 #[ test]
564675 fn requires_one_sufficient_non_mpp_channel ( ) {
565676 let recipient = pubkey ( 10 ) ;
@@ -573,7 +684,7 @@ mod tests {
573684 } ;
574685 let inner_router =
575686 MockRouter { calls : AtomicUsize :: new ( 0 ) , path_kind : MockPathKind :: DirectRecipient } ;
576- let router = LSPS2Router :: new ( inner_router, TestEntropy ) ;
687+ let router = LSPS2Router :: new ( inner_router, announced_graph ( recipient , true ) , TestEntropy ) ;
577688
578689 let paths = router
579690 . create_blinded_payment_paths (
@@ -602,7 +713,7 @@ mod tests {
602713 } ;
603714 let inner_router =
604715 MockRouter { calls : AtomicUsize :: new ( 0 ) , path_kind : MockPathKind :: DirectRecipient } ;
605- let router = LSPS2Router :: new ( inner_router, TestEntropy ) ;
716+ let router = LSPS2Router :: new ( inner_router, empty_graph ( ) , TestEntropy ) ;
606717
607718 let paths = router
608719 . create_blinded_payment_paths (
0 commit comments