86
86
/// participating node
87
87
/// * It is fine to cache `phantom_route_hints` and reuse it across invoices, as long as the data is
88
88
/// updated when a channel becomes disabled or closes
89
- /// * Note that if too many channels are included in [`PhantomRouteHints::channels`], the invoice
90
- /// may be too long for QR code scanning. To fix this, `PhantomRouteHints::channels` may be pared
91
- /// down
89
+ /// * Note that the route hints generated from `phantom_route_hints` will be limited to a maximum
90
+ /// of 3 hints to ensure that the invoice can be scanned in a QR code. These hints are selected
91
+ /// in the order that the nodes in `PhantomRouteHints` are specified, selecting one hint per node
92
+ /// until the maximum is hit. Callers may provide as many `PhantomRouteHints::channels` as
93
+ /// desired, but note that some nodes will be trimmed if more than 3 nodes are provided.
92
94
///
93
95
/// `description_hash` is a SHA-256 hash of the description text
94
96
///
@@ -220,12 +222,16 @@ where
220
222
221
223
/// Utility to select route hints for phantom invoices.
222
224
/// See [`PhantomKeysManager`] for more information on phantom node payments.
225
+ ///
226
+ /// To ensure that the phantom invoice is still readable by QR code, we limit to 3 hints per invoice:
227
+ /// * Select up to three channels per node.
228
+ /// * Select one hint from each node, up to three hints or until we run out of hints.
223
229
fn select_phantom_hints < L : Deref > ( amt_msat : Option < u64 > , phantom_route_hints : Vec < PhantomRouteHints > ,
224
230
logger : L ) -> Vec < RouteHint >
225
231
where
226
232
L :: Target : Logger ,
227
233
{
228
- let mut phantom_hints : Vec < RouteHint > = Vec :: new ( ) ;
234
+ let mut phantom_hints : Vec < Vec < RouteHint > > = Vec :: new ( ) ;
229
235
230
236
for PhantomRouteHints { channels, phantom_scid, real_node_pubkey } in phantom_route_hints {
231
237
log_trace ! ( logger, "Generating phantom route hints for node {}" ,
@@ -239,7 +245,7 @@ where
239
245
if route_hints. is_empty ( ) {
240
246
route_hints. push ( RouteHint ( vec ! [ ] ) )
241
247
}
242
- for mut route_hint in route_hints {
248
+ for route_hint in & mut route_hints {
243
249
route_hint. 0 . push ( RouteHintHop {
244
250
src_node_id : real_node_pubkey,
245
251
short_channel_id : phantom_scid,
@@ -250,12 +256,36 @@ where
250
256
cltv_expiry_delta : MIN_CLTV_EXPIRY_DELTA ,
251
257
htlc_minimum_msat : None ,
252
258
htlc_maximum_msat : None , } ) ;
259
+ }
260
+
261
+ phantom_hints. push ( route_hints) ;
262
+ }
253
263
254
- phantom_hints. push ( route_hint. clone ( ) ) ;
264
+ // We have one vector per phantom node involved in creating the invoice. To distribute the
265
+ // hints across our nodes we add one hint from each node in turn until no node has any hints
266
+ // left (if one node has more hints than any other, these will accumulate at the end of the
267
+ // vector).
268
+ let mut invoice_hints : Vec < RouteHint > = Vec :: new ( ) ;
269
+ let mut hint_idx = 0 ;
270
+
271
+ loop {
272
+ let mut remaining_hints = false ;
273
+
274
+ for hints in phantom_hints. iter ( ) {
275
+ if hint_idx < hints. len ( ) {
276
+ invoice_hints. push ( hints[ hint_idx] . clone ( ) ) ;
277
+ remaining_hints = true ;
255
278
}
279
+ }
280
+
281
+ if !remaining_hints{
282
+ break
283
+ }
284
+
285
+ hint_idx +=1 ;
256
286
}
257
287
258
- phantom_hints
288
+ invoice_hints . iter ( ) . take ( 3 ) . map ( |x| x . clone ( ) ) . collect ( )
259
289
}
260
290
261
291
#[ cfg( feature = "std" ) ]
@@ -1538,7 +1568,67 @@ mod test {
1538
1568
) ;
1539
1569
}
1540
1570
1571
+ #[ test]
1541
1572
#[ cfg( feature = "std" ) ]
1573
+ fn test_multi_node_hints_limited_to_3 ( ) {
1574
+ let mut chanmon_cfgs = create_chanmon_cfgs ( 5 ) ;
1575
+ let seed_1 = [ 42 as u8 ; 32 ] ;
1576
+ let seed_2 = [ 43 as u8 ; 32 ] ;
1577
+ let seed_3 = [ 44 as u8 ; 32 ] ;
1578
+ let cross_node_seed = [ 44 as u8 ; 32 ] ;
1579
+ chanmon_cfgs[ 2 ] . keys_manager . backing = PhantomKeysManager :: new ( & seed_1, 43 , 44 , & cross_node_seed) ;
1580
+ chanmon_cfgs[ 3 ] . keys_manager . backing = PhantomKeysManager :: new ( & seed_2, 43 , 44 , & cross_node_seed) ;
1581
+ chanmon_cfgs[ 4 ] . keys_manager . backing = PhantomKeysManager :: new ( & seed_3, 43 , 44 , & cross_node_seed) ;
1582
+ let node_cfgs = create_node_cfgs ( 5 , & chanmon_cfgs) ;
1583
+ let node_chanmgrs = create_node_chanmgrs ( 5 , & node_cfgs, & [ None , None , None , None , None ] ) ;
1584
+ let nodes = create_network ( 5 , & node_cfgs, & node_chanmgrs) ;
1585
+
1586
+ // Setup each phantom node with two channels from distinct peers.
1587
+ let _chan_0_2 = create_unannounced_chan_between_nodes_with_value ( & nodes, 0 , 2 , 10_000 , 0 ) ;
1588
+ let _chan_1_2 = create_unannounced_chan_between_nodes_with_value ( & nodes, 1 , 2 , 20_000 , 0 ) ;
1589
+ let _chan_0_3 = create_unannounced_chan_between_nodes_with_value ( & nodes, 0 , 3 , 20_000 , 0 ) ;
1590
+ let _chan_1_3 = create_unannounced_chan_between_nodes_with_value ( & nodes, 1 , 3 , 10_000 , 0 ) ;
1591
+ let _chan_0_4 = create_unannounced_chan_between_nodes_with_value ( & nodes, 0 , 4 , 20_000 , 0 ) ;
1592
+ let _chan_1_4 = create_unannounced_chan_between_nodes_with_value ( & nodes, 1 , 4 , 10_000 , 0 ) ;
1593
+
1594
+ // Since the invoice amount is above all channels inbound, every one is eligible. Test that
1595
+ // we include one channel from each of our three nodes.
1596
+ match_multi_node_invoice_distinct_nodes (
1597
+ Some ( 100_000_000 ) ,
1598
+ & nodes[ 3 ] ,
1599
+ vec ! [ & nodes[ 2 ] , & nodes[ 3 ] , & nodes[ 4 ] , ] ,
1600
+ 3 ,
1601
+ ) ;
1602
+ }
1603
+
1604
+ #[ test]
1605
+ #[ cfg( feature = "std" ) ]
1606
+ fn test_multi_node_hints_at_least_3 ( ) {
1607
+ let mut chanmon_cfgs = create_chanmon_cfgs ( 5 ) ;
1608
+ let seed_1 = [ 42 as u8 ; 32 ] ;
1609
+ let seed_2 = [ 43 as u8 ; 32 ] ;
1610
+ let cross_node_seed = [ 44 as u8 ; 32 ] ;
1611
+ chanmon_cfgs[ 1 ] . keys_manager . backing = PhantomKeysManager :: new ( & seed_1, 43 , 44 , & cross_node_seed) ;
1612
+ chanmon_cfgs[ 2 ] . keys_manager . backing = PhantomKeysManager :: new ( & seed_2, 43 , 44 , & cross_node_seed) ;
1613
+ let node_cfgs = create_node_cfgs ( 5 , & chanmon_cfgs) ;
1614
+ let node_chanmgrs = create_node_chanmgrs ( 5 , & node_cfgs, & [ None , None , None , None , None ] ) ;
1615
+ let nodes = create_network ( 5 , & node_cfgs, & node_chanmgrs) ;
1616
+
1617
+ let _chan_0_3 = create_unannounced_chan_between_nodes_with_value ( & nodes, 0 , 3 , 10_000 , 0 ) ;
1618
+ let _chan_1_3 = create_unannounced_chan_between_nodes_with_value ( & nodes, 1 , 3 , 20_000 , 0 ) ;
1619
+ let _chan_2_3 = create_unannounced_chan_between_nodes_with_value ( & nodes, 2 , 3 , 30_000 , 0 ) ;
1620
+ let _chan_0_4 = create_unannounced_chan_between_nodes_with_value ( & nodes, 0 , 4 , 10_000 , 0 ) ;
1621
+
1622
+ // Since the invoice amount is above all channels inbound, all four are eligible. Test that
1623
+ // we still include 3 hints from 2 distinct peers of our two phantom nodes.
1624
+ match_multi_node_invoice_distinct_nodes (
1625
+ Some ( 100_000_000 ) ,
1626
+ & nodes[ 3 ] , vec ! [ & nodes[ 3 ] , & nodes[ 4 ] , ] ,
1627
+ 2 ,
1628
+ ) ;
1629
+ }
1630
+
1631
+ #[ cfg( feature = "std" ) ]
1542
1632
fn create_multi_node_invoice < ' a , ' b : ' a , ' c : ' b > (
1543
1633
invoice_amt : Option < u64 > ,
1544
1634
invoice_node : & Node < ' a , ' b , ' c > ,
@@ -1559,6 +1649,30 @@ mod test {
1559
1649
( invoice, phantom_scids)
1560
1650
}
1561
1651
1652
+ #[ cfg( feature = "std" ) ]
1653
+ fn match_multi_node_invoice_distinct_nodes < ' a , ' b : ' a , ' c : ' b > (
1654
+ invoice_amt : Option < u64 > ,
1655
+ invoice_node : & Node < ' a , ' b , ' c > ,
1656
+ network_multi_nodes : Vec < & Node < ' a , ' b , ' c > > ,
1657
+ distinct_nodes : usize ,
1658
+ ) {
1659
+ let ( invoice, phantom_scids) = create_multi_node_invoice ( invoice_amt, invoice_node, network_multi_nodes) ;
1660
+
1661
+ let mut hint_nodes = crate :: HashMap :: new ( ) ;
1662
+ for hint in invoice. private_routes ( ) {
1663
+ let hints = & ( hint. 0 ) . 0 ;
1664
+ match hints. len ( ) {
1665
+ 2 => {
1666
+ hint_nodes. insert ( hints[ 1 ] . src_node_id , true ) ;
1667
+ let phantom_scid = hints[ 1 ] . short_channel_id ;
1668
+ assert ! ( phantom_scids. contains( & phantom_scid) ) ;
1669
+ } ,
1670
+ _ => panic ! ( "Incorrect hint length generated" )
1671
+ }
1672
+ }
1673
+ assert_eq ! ( hint_nodes. len( ) , distinct_nodes, "Unmatched number of distinct phantom nodes for hints." ) ;
1674
+ }
1675
+
1562
1676
#[ cfg( feature = "std" ) ]
1563
1677
fn match_multi_node_invoice_routes < ' a , ' b : ' a , ' c : ' b > (
1564
1678
invoice_amt : Option < u64 > ,
0 commit comments