@@ -435,6 +435,10 @@ const MEDIAN_HOP_CLTV_EXPIRY_DELTA: u32 = 40;
435
435
// down from (1300-93) / 61 = 19.78... to arrive at a conservative estimate of 19.
436
436
const MAX_PATH_LENGTH_ESTIMATE : u8 = 19 ;
437
437
438
+ /// We need to create RouteHintHops for blinded pathfinding, but we don't have an scid, so use a
439
+ /// dummy value.
440
+ const BLINDED_PATH_SCID : u64 = 0 ;
441
+
438
442
/// The recipient of a payment.
439
443
#[ derive( Clone , Debug , Hash , PartialEq , Eq ) ]
440
444
pub struct PaymentParameters {
@@ -589,6 +593,13 @@ impl PaymentParameters {
589
593
Self { route_hints : Hints :: Clear ( route_hints) , ..self }
590
594
}
591
595
596
+ /// Includes blinded hints for routing to the payee.
597
+ ///
598
+ /// (C-not exported) since bindings don't support move semantics
599
+ pub fn with_blinded_route_hints ( self , blinded_route_hints : Vec < ( BlindedPayInfo , BlindedPath ) > ) -> Self {
600
+ Self { route_hints : Hints :: Blinded ( blinded_route_hints) , ..self }
601
+ }
602
+
592
603
/// Includes a payment expiration in seconds relative to the UNIX epoch.
593
604
///
594
605
/// (C-not exported) since bindings don't support move semantics
@@ -628,6 +639,15 @@ pub enum Hints {
628
639
Clear ( Vec < RouteHint > ) ,
629
640
}
630
641
642
+ impl Hints {
643
+ fn blinded_len ( & self ) -> usize {
644
+ match self {
645
+ Self :: Blinded ( hints) => hints. len ( ) ,
646
+ Self :: Clear ( _) => 0 ,
647
+ }
648
+ }
649
+ }
650
+
631
651
/// A list of hops along a payment path terminating with a channel to the recipient.
632
652
#[ derive( Clone , Debug , Hash , Eq , PartialEq ) ]
633
653
pub struct RouteHint ( pub Vec < RouteHintHop > ) ;
@@ -1087,7 +1107,18 @@ where L::Target: Logger {
1087
1107
}
1088
1108
}
1089
1109
} ,
1090
- _ => todo ! ( )
1110
+ Hints :: Blinded ( hints) => {
1111
+ for ( _, blinded_path) in hints. iter ( ) {
1112
+ let intro_node_is_payee = blinded_path. introduction_node_id == payment_params. payee_pubkey ;
1113
+ if blinded_path. blinded_hops . len ( ) > 1 && intro_node_is_payee {
1114
+ return Err ( LightningError { err : "Blinded path cannot have the payee as the source." . to_owned ( ) , action : ErrorAction :: IgnoreError } ) ;
1115
+ } else if !intro_node_is_payee && blinded_path. blinded_hops . len ( ) == 1 {
1116
+ return Err ( LightningError { err : format ! ( "1-hop blinded path introduction node id {} did not match payee {}" , blinded_path. introduction_node_id, payment_params. payee_pubkey) , action : ErrorAction :: IgnoreError } ) ;
1117
+ } else if blinded_path. blinded_hops . len ( ) == 0 {
1118
+ return Err ( LightningError { err : "0-hop blinded path provided" . to_owned ( ) , action : ErrorAction :: IgnoreError } ) ;
1119
+ }
1120
+ }
1121
+ }
1091
1122
}
1092
1123
if payment_params. max_total_cltv_expiry_delta <= final_cltv_expiry_delta {
1093
1124
return Err ( LightningError { err : "Can't find a route where the maximum total CLTV expiry delta is below the final CLTV expiry." . to_owned ( ) , action : ErrorAction :: IgnoreError } ) ;
@@ -1575,6 +1606,26 @@ where L::Target: Logger {
1575
1606
1576
1607
let mut payment_paths = Vec :: < PaymentPath > :: new ( ) ;
1577
1608
1609
+ let mut route_hints = Vec :: with_capacity ( payment_params. route_hints . blinded_len ( ) ) ;
1610
+ let route_hints_ref = match & payment_params. route_hints {
1611
+ Hints :: Clear ( hints) => hints,
1612
+ Hints :: Blinded ( blinded_hints) => {
1613
+ for ( blinded_payinfo, blinded_path) in blinded_hints {
1614
+ route_hints. push ( RouteHint ( vec ! [ RouteHintHop {
1615
+ src_node_id: blinded_path. introduction_node_id,
1616
+ short_channel_id: BLINDED_PATH_SCID ,
1617
+ fees: RoutingFees {
1618
+ base_msat: blinded_payinfo. fee_base_msat,
1619
+ proportional_millionths: blinded_payinfo. fee_proportional_millionths,
1620
+ } ,
1621
+ cltv_expiry_delta: blinded_payinfo. cltv_expiry_delta,
1622
+ htlc_minimum_msat: Some ( blinded_payinfo. htlc_minimum_msat) ,
1623
+ htlc_maximum_msat: Some ( blinded_payinfo. htlc_maximum_msat) ,
1624
+ } ] ) ) ;
1625
+ }
1626
+ & route_hints
1627
+ }
1628
+ } ;
1578
1629
// TODO: diversify by nodes (so that all paths aren't doomed if one node is offline).
1579
1630
' paths_collection: loop {
1580
1631
// For every new path, start from scratch, except for used_channel_liquidities, which
@@ -1612,11 +1663,7 @@ where L::Target: Logger {
1612
1663
// If a caller provided us with last hops, add them to routing targets. Since this happens
1613
1664
// earlier than general path finding, they will be somewhat prioritized, although currently
1614
1665
// it matters only if the fees are exactly the same.
1615
- let route_hints = match & payment_params. route_hints {
1616
- Hints :: Clear ( hints) => hints,
1617
- _ => todo ! ( )
1618
- } ;
1619
- for route in route_hints. iter ( ) . filter ( |route| !route. 0 . is_empty ( ) ) {
1666
+ for route in route_hints_ref. iter ( ) . filter ( |route| !route. 0 . is_empty ( ) ) {
1620
1667
let first_hop_in_route = & ( route. 0 ) [ 0 ] ;
1621
1668
let have_hop_src_in_graph =
1622
1669
// Only add the hops in this route to our candidate set if either
@@ -2035,7 +2082,16 @@ where L::Target: Logger {
2035
2082
for results_vec in selected_paths {
2036
2083
let mut hops = Vec :: new ( ) ;
2037
2084
for res in results_vec { hops. push ( res?) ; }
2038
- paths. push ( Path { hops, blinded_tail : None } ) ;
2085
+ let mut blinded_tail = None ;
2086
+ if let Hints :: Blinded ( hints) = & payment_params. route_hints {
2087
+ blinded_tail = hints. iter ( )
2088
+ . find ( |( _, p) | {
2089
+ let intro_node_idx = if p. blinded_hops . len ( ) == 1 { hops. len ( ) - 1 } else { hops. len ( ) - 2 } ;
2090
+ p. introduction_node_id == hops[ intro_node_idx] . pubkey
2091
+ } )
2092
+ . map ( |( _, p) | p. clone ( ) ) ;
2093
+ }
2094
+ paths. push ( Path { hops, blinded_tail } ) ;
2039
2095
}
2040
2096
let route = Route {
2041
2097
paths,
@@ -2216,12 +2272,14 @@ mod tests {
2216
2272
use crate :: routing:: utxo:: UtxoResult ;
2217
2273
use crate :: routing:: router:: { get_route, build_route_from_hops_internal, add_random_cltv_offset, default_node_features,
2218
2274
Path , PaymentParameters , Route , RouteHint , RouteHintHop , RouteHop , RoutingFees ,
2219
- DEFAULT_MAX_TOTAL_CLTV_EXPIRY_DELTA , MAX_PATH_LENGTH_ESTIMATE } ;
2275
+ BLINDED_PATH_SCID , DEFAULT_MAX_TOTAL_CLTV_EXPIRY_DELTA , MAX_PATH_LENGTH_ESTIMATE } ;
2220
2276
use crate :: routing:: scoring:: { ChannelUsage , FixedPenaltyScorer , Score , ProbabilisticScorer , ProbabilisticScoringParameters } ;
2221
2277
use crate :: routing:: test_utils:: { add_channel, add_or_update_node, build_graph, build_line_graph, id_to_feature_flags, get_nodes, update_channel} ;
2278
+ use crate :: blinded_path:: { BlindedHop , BlindedPath } ;
2222
2279
use crate :: chain:: transaction:: OutPoint ;
2223
2280
use crate :: chain:: keysinterface:: EntropySource ;
2224
- use crate :: ln:: features:: { ChannelFeatures , InitFeatures , NodeFeatures } ;
2281
+ use crate :: offers:: invoice:: BlindedPayInfo ;
2282
+ use crate :: ln:: features:: { BlindedHopFeatures , ChannelFeatures , InitFeatures , NodeFeatures } ;
2225
2283
use crate :: ln:: msgs:: { ErrorAction , LightningError , UnsignedChannelUpdate , MAX_VALUE_MSAT } ;
2226
2284
use crate :: ln:: channelmanager;
2227
2285
use crate :: util:: config:: UserConfig ;
@@ -5712,6 +5770,48 @@ mod tests {
5712
5770
let route = get_route ( & our_id, & payment_params, & network_graph. read_only ( ) , None , 100 , 42 , Arc :: clone ( & logger) , & scorer, & random_seed_bytes) ;
5713
5771
assert ! ( route. is_ok( ) ) ;
5714
5772
}
5773
+
5774
+ #[ test]
5775
+ fn simple_blinded_path_routing ( ) {
5776
+ // Check that we can generate a route to a blinded path with the expected hops.
5777
+ let ( secp_ctx, network, _, _, logger) = build_graph ( ) ;
5778
+ let ( _, our_id, _, nodes) = get_nodes ( & secp_ctx) ;
5779
+ let network_graph = network. read_only ( ) ;
5780
+
5781
+ let scorer = ln_test_utils:: TestScorer :: new ( ) ;
5782
+ let keys_manager = ln_test_utils:: TestKeysInterface :: new ( & [ 0u8 ; 32 ] , Network :: Testnet ) ;
5783
+ let random_seed_bytes = keys_manager. get_secure_random_bytes ( ) ;
5784
+
5785
+ let blinded_path = BlindedPath {
5786
+ introduction_node_id : nodes[ 2 ] ,
5787
+ blinding_point : ln_test_utils:: pubkey ( 42 ) ,
5788
+ blinded_hops : vec ! [
5789
+ BlindedHop { blinded_node_id: ln_test_utils:: pubkey( 43 ) , encrypted_payload: vec![ 0 ; 43 ] } ,
5790
+ BlindedHop { blinded_node_id: ln_test_utils:: pubkey( 44 ) , encrypted_payload: vec![ 0 ; 44 ] } ,
5791
+ ] ,
5792
+ } ;
5793
+ let blinded_payinfo = BlindedPayInfo {
5794
+ fee_base_msat : 100 ,
5795
+ fee_proportional_millionths : 500 ,
5796
+ htlc_minimum_msat : 1000 ,
5797
+ htlc_maximum_msat : 100_000_000 ,
5798
+ cltv_expiry_delta : 15 ,
5799
+ features : BlindedHopFeatures :: empty ( ) ,
5800
+ } ;
5801
+
5802
+ let payee_pubkey = ln_test_utils:: pubkey ( 45 ) ;
5803
+ let payment_params = PaymentParameters :: from_node_id ( payee_pubkey, 0 )
5804
+ . with_blinded_route_hints ( vec ! [ ( blinded_payinfo, blinded_path. clone( ) ) ] ) ;
5805
+ let route = get_route ( & our_id, & payment_params, & network_graph, None , 1001 , 0 ,
5806
+ Arc :: clone ( & logger) , & scorer, & random_seed_bytes) . unwrap ( ) ;
5807
+ assert_eq ! ( route. paths. len( ) , 1 ) ;
5808
+ assert_eq ! ( route. paths[ 0 ] . hops. len( ) , 3 ) ;
5809
+ assert_eq ! ( route. paths[ 0 ] . len( ) , 5 ) ;
5810
+ assert_eq ! ( route. paths[ 0 ] . hops[ 2 ] . pubkey, payee_pubkey) ;
5811
+ assert_eq ! ( route. paths[ 0 ] . hops[ 2 ] . short_channel_id, BLINDED_PATH_SCID ) ;
5812
+ assert_eq ! ( route. paths[ 0 ] . hops[ 1 ] . pubkey, nodes[ 2 ] ) ;
5813
+ assert_eq ! ( route. paths[ 0 ] . blinded_tail, Some ( blinded_path) ) ;
5814
+ }
5715
5815
}
5716
5816
5717
5817
#[ cfg( all( test, not( feature = "no-std" ) ) ) ]
0 commit comments