@@ -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
}
@@ -6710,6 +6716,159 @@ mod tests {
6710
6716
}
6711
6717
assert_eq ! ( total_amount_paid_msat, 100_000 ) ;
6712
6718
}
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
+ }
6713
6872
}
6714
6873
6715
6874
#[ cfg( all( any( test, ldk_bench) , not( feature = "no-std" ) ) ) ]
0 commit comments