@@ -2037,8 +2037,14 @@ where L::Target: Logger {
20372037 our_node_pubkey) ;
20382038 for details in first_channels {
20392039 let first_hop_candidate = CandidateRouteHop :: FirstHop { details } ;
2040- add_entry ! ( first_hop_candidate, our_node_id, intro_node_id, 0 , path_contribution_msat, 0 ,
2041- 0_u64 , 0 , 0 ) ;
2040+ let blinded_path_fee = match compute_fees ( path_contribution_msat, candidate. fees ( ) ) {
2041+ Some ( fee) => fee,
2042+ None => continue
2043+ } ;
2044+ add_entry ! ( first_hop_candidate, our_node_id, intro_node_id, blinded_path_fee,
2045+ path_contribution_msat, candidate. htlc_minimum_msat( ) , 0_u64 ,
2046+ candidate. cltv_expiry_delta( ) ,
2047+ candidate. blinded_path( ) . map_or( 1 , |bp| bp. blinded_hops. len( ) as u8 ) ) ;
20422048 }
20432049 }
20442050 }
@@ -6711,6 +6717,159 @@ mod tests {
67116717 }
67126718 assert_eq ! ( total_amount_paid_msat, 100_000 ) ;
67136719 }
6720+
6721+ #[ test]
6722+ fn direct_to_intro_node ( ) {
6723+ // This previously caused a debug panic in the router when asserting
6724+ // `used_liquidity_msat <= hop_max_msat`, because when adding first_hop<>blinded_route_hint
6725+ // direct channels we failed to account for the fee charged for use of the blinded path.
6726+
6727+ // Build a graph:
6728+ // node0 -1(1)2 - node1
6729+ // such that there isn't enough liquidity to reach node1, but the router thinks there is if it
6730+ // doesn't account for the blinded path fee.
6731+
6732+ let secp_ctx = Secp256k1 :: new ( ) ;
6733+ let logger = Arc :: new ( ln_test_utils:: TestLogger :: new ( ) ) ;
6734+ let network_graph = Arc :: new ( NetworkGraph :: new ( Network :: Testnet , Arc :: clone ( & logger) ) ) ;
6735+ let gossip_sync = P2PGossipSync :: new ( Arc :: clone ( & network_graph) , None , Arc :: clone ( & logger) ) ;
6736+ let scorer = ln_test_utils:: TestScorer :: new ( ) ;
6737+ let keys_manager = ln_test_utils:: TestKeysInterface :: new ( & [ 0u8 ; 32 ] , Network :: Testnet ) ;
6738+ let random_seed_bytes = keys_manager. get_secure_random_bytes ( ) ;
6739+
6740+ let amt_msat = 10_000_000 ;
6741+ let ( _, _, privkeys, nodes) = get_nodes ( & secp_ctx) ;
6742+ add_channel ( & gossip_sync, & secp_ctx, & privkeys[ 0 ] , & privkeys[ 1 ] ,
6743+ ChannelFeatures :: from_le_bytes ( id_to_feature_flags ( 1 ) ) , 1 ) ;
6744+ update_channel ( & gossip_sync, & secp_ctx, & privkeys[ 0 ] , UnsignedChannelUpdate {
6745+ chain_hash : genesis_block ( Network :: Testnet ) . header . block_hash ( ) ,
6746+ short_channel_id : 1 ,
6747+ timestamp : 1 ,
6748+ flags : 0 ,
6749+ cltv_expiry_delta : 42 ,
6750+ htlc_minimum_msat : 1_000 ,
6751+ htlc_maximum_msat : 10_000_000 ,
6752+ fee_base_msat : 800 ,
6753+ fee_proportional_millionths : 0 ,
6754+ excess_data : Vec :: new ( )
6755+ } ) ;
6756+ update_channel ( & gossip_sync, & secp_ctx, & privkeys[ 1 ] , UnsignedChannelUpdate {
6757+ chain_hash : genesis_block ( Network :: Testnet ) . header . block_hash ( ) ,
6758+ short_channel_id : 1 ,
6759+ timestamp : 1 ,
6760+ flags : 1 ,
6761+ cltv_expiry_delta : 42 ,
6762+ htlc_minimum_msat : 1_000 ,
6763+ htlc_maximum_msat : 10_000_000 ,
6764+ fee_base_msat : 800 ,
6765+ fee_proportional_millionths : 0 ,
6766+ excess_data : Vec :: new ( )
6767+ } ) ;
6768+ let first_hops = vec ! [
6769+ get_channel_details( Some ( 1 ) , nodes[ 1 ] , InitFeatures :: from_le_bytes( vec![ 0b11 ] ) , 10_000_000 ) ] ;
6770+
6771+ let blinded_path = BlindedPath {
6772+ introduction_node_id : nodes[ 1 ] ,
6773+ blinding_point : ln_test_utils:: pubkey ( 42 ) ,
6774+ blinded_hops : vec ! [
6775+ BlindedHop { blinded_node_id: ln_test_utils:: pubkey( 42 as u8 ) , encrypted_payload: Vec :: new( ) } ,
6776+ BlindedHop { blinded_node_id: ln_test_utils:: pubkey( 42 as u8 ) , encrypted_payload: Vec :: new( ) }
6777+ ] ,
6778+ } ;
6779+ let blinded_payinfo = BlindedPayInfo {
6780+ fee_base_msat : 1000 ,
6781+ fee_proportional_millionths : 0 ,
6782+ htlc_minimum_msat : 1000 ,
6783+ htlc_maximum_msat : MAX_VALUE_MSAT ,
6784+ cltv_expiry_delta : 0 ,
6785+ features : BlindedHopFeatures :: empty ( ) ,
6786+ } ;
6787+ let blinded_hints = vec ! [ ( blinded_payinfo. clone( ) , blinded_path) ] ;
6788+
6789+ let payment_params = PaymentParameters :: blinded ( blinded_hints. clone ( ) ) ;
6790+
6791+ let netgraph = network_graph. read_only ( ) ;
6792+ if let Err ( LightningError { err, .. } ) = get_route ( & nodes[ 0 ] , & payment_params, & netgraph,
6793+ Some ( & first_hops. iter ( ) . collect :: < Vec < _ > > ( ) ) , amt_msat, Arc :: clone ( & logger) , & scorer, & ( ) ,
6794+ & random_seed_bytes) {
6795+ assert_eq ! ( err, "Failed to find a path to the given destination" ) ;
6796+ } else { panic ! ( "Expected error" ) }
6797+
6798+ // Sending an exact amount accounting for the blinded path fee works.
6799+ let amt_minus_blinded_path_fee = amt_msat - blinded_payinfo. fee_base_msat as u64 ;
6800+ let route = get_route ( & nodes[ 0 ] , & payment_params, & netgraph,
6801+ Some ( & first_hops. iter ( ) . collect :: < Vec < _ > > ( ) ) , amt_minus_blinded_path_fee,
6802+ Arc :: clone ( & logger) , & scorer, & ( ) , & random_seed_bytes) . unwrap ( ) ;
6803+ assert_eq ! ( route. get_total_fees( ) , blinded_payinfo. fee_base_msat as u64 ) ;
6804+ assert_eq ! ( route. get_total_amount( ) , amt_minus_blinded_path_fee) ;
6805+ }
6806+
6807+ #[ test]
6808+ fn direct_to_matching_intro_nodes ( ) {
6809+ // This previously caused us to enter `unreachable` code in the following situation:
6810+ // 1. We add a route candidate for intro_node contributing a high amount
6811+ // 2. We add a first_hop<>intro_node route candidate for the same high amount
6812+ // 3. We see a cheaper blinded route hint for the same intro node but a much lower contribution
6813+ // amount, and update our route candidate for intro_node for the lower amount
6814+ // 4. We then attempt to update the aforementioned first_hop<>intro_node route candidate for the
6815+ // lower contribution amount, but fail (this was previously caused by failure to account for
6816+ // blinded path fees when adding first_hop<>intro_node candidates)
6817+ // 5. We go to construct the path from these route candidates and our first_hop<>intro_node
6818+ // candidate still thinks its path is contributing the original higher amount. This caused us
6819+ // to hit an `unreachable` overflow when calculating the cheaper intro_node fees over the
6820+ // larger amount
6821+ let secp_ctx = Secp256k1 :: new ( ) ;
6822+ let logger = Arc :: new ( ln_test_utils:: TestLogger :: new ( ) ) ;
6823+ let network_graph = Arc :: new ( NetworkGraph :: new ( Network :: Testnet , Arc :: clone ( & logger) ) ) ;
6824+ let scorer = ln_test_utils:: TestScorer :: new ( ) ;
6825+ let keys_manager = ln_test_utils:: TestKeysInterface :: new ( & [ 0u8 ; 32 ] , Network :: Testnet ) ;
6826+ let random_seed_bytes = keys_manager. get_secure_random_bytes ( ) ;
6827+ let config = UserConfig :: default ( ) ;
6828+
6829+ // Values are taken from the fuzz input that uncovered this panic.
6830+ let amt_msat = 21_7020_5185_1403_2640 ;
6831+ let ( _, _, _, nodes) = get_nodes ( & secp_ctx) ;
6832+ let first_hops = vec ! [
6833+ get_channel_details( Some ( 1 ) , nodes[ 1 ] , channelmanager:: provided_init_features( & config) ,
6834+ 18446744073709551615 ) ] ;
6835+
6836+ let blinded_path = BlindedPath {
6837+ introduction_node_id : nodes[ 1 ] ,
6838+ blinding_point : ln_test_utils:: pubkey ( 42 ) ,
6839+ blinded_hops : vec ! [
6840+ BlindedHop { blinded_node_id: ln_test_utils:: pubkey( 42 as u8 ) , encrypted_payload: Vec :: new( ) } ,
6841+ BlindedHop { blinded_node_id: ln_test_utils:: pubkey( 42 as u8 ) , encrypted_payload: Vec :: new( ) }
6842+ ] ,
6843+ } ;
6844+ let blinded_payinfo = BlindedPayInfo {
6845+ fee_base_msat : 5046_2720 ,
6846+ fee_proportional_millionths : 0 ,
6847+ htlc_minimum_msat : 4503_5996_2737_0496 ,
6848+ htlc_maximum_msat : 45_0359_9627_3704_9600 ,
6849+ cltv_expiry_delta : 0 ,
6850+ features : BlindedHopFeatures :: empty ( ) ,
6851+ } ;
6852+ let mut blinded_hints = vec ! [
6853+ ( blinded_payinfo. clone( ) , blinded_path. clone( ) ) ,
6854+ ( blinded_payinfo. clone( ) , blinded_path. clone( ) ) ,
6855+ ] ;
6856+ blinded_hints[ 1 ] . 0 . fee_base_msat = 419_4304 ;
6857+ blinded_hints[ 1 ] . 0 . fee_proportional_millionths = 257 ;
6858+ blinded_hints[ 1 ] . 0 . htlc_minimum_msat = 280_8908_6115_8400 ;
6859+ blinded_hints[ 1 ] . 0 . htlc_maximum_msat = 2_8089_0861_1584_0000 ;
6860+ blinded_hints[ 1 ] . 0 . cltv_expiry_delta = 0 ;
6861+
6862+ let bolt12_features: Bolt12InvoiceFeatures = channelmanager:: provided_invoice_features ( & config) . to_context ( ) ;
6863+ let payment_params = PaymentParameters :: blinded ( blinded_hints. clone ( ) )
6864+ . with_bolt12_features ( bolt12_features. clone ( ) ) . unwrap ( ) ;
6865+
6866+ let netgraph = network_graph. read_only ( ) ;
6867+ let route = get_route ( & nodes[ 0 ] , & payment_params, & netgraph,
6868+ Some ( & first_hops. iter ( ) . collect :: < Vec < _ > > ( ) ) , amt_msat,
6869+ Arc :: clone ( & logger) , & scorer, & ( ) , & random_seed_bytes) . unwrap ( ) ;
6870+ assert_eq ! ( route. get_total_fees( ) , blinded_payinfo. fee_base_msat as u64 ) ;
6871+ assert_eq ! ( route. get_total_amount( ) , amt_msat) ;
6872+ }
67146873}
67156874
67166875#[ cfg( all( any( test, ldk_bench) , not( feature = "no-std" ) ) ) ]
0 commit comments