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