@@ -2037,8 +2037,14 @@ where L::Target: Logger {
2037
2037
our_node_pubkey) ;
2038
2038
for details in first_channels {
2039
2039
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 ) ) ;
2042
2048
}
2043
2049
}
2044
2050
}
@@ -6711,6 +6717,159 @@ mod tests {
6711
6717
}
6712
6718
assert_eq ! ( total_amount_paid_msat, 100_000 ) ;
6713
6719
}
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
+ }
6714
6873
}
6715
6874
6716
6875
#[ cfg( all( any( test, ldk_bench) , not( feature = "no-std" ) ) ) ]
0 commit comments