8686/// participating node
8787/// * It is fine to cache `phantom_route_hints` and reuse it across invoices, as long as the data is
8888/// 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.
9294///
9395/// `description_hash` is a SHA-256 hash of the description text
9496///
@@ -220,12 +222,16 @@ where
220222
221223/// Utility to select route hints for phantom invoices.
222224/// 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.
223229fn select_phantom_hints < L : Deref > ( amt_msat : Option < u64 > , phantom_route_hints : Vec < PhantomRouteHints > ,
224230 logger : L ) -> Vec < RouteHint >
225231where
226232 L :: Target : Logger ,
227233{
228- let mut phantom_hints : Vec < RouteHint > = Vec :: new ( ) ;
234+ let mut phantom_hints : Vec < Vec < RouteHint > > = Vec :: new ( ) ;
229235
230236 for PhantomRouteHints { channels, phantom_scid, real_node_pubkey } in phantom_route_hints {
231237 log_trace ! ( logger, "Generating phantom route hints for node {}" ,
@@ -239,7 +245,7 @@ where
239245 if route_hints. is_empty ( ) {
240246 route_hints. push ( RouteHint ( vec ! [ ] ) )
241247 }
242- for mut route_hint in route_hints {
248+ for route_hint in & mut route_hints {
243249 route_hint. 0 . push ( RouteHintHop {
244250 src_node_id : real_node_pubkey,
245251 short_channel_id : phantom_scid,
@@ -250,12 +256,36 @@ where
250256 cltv_expiry_delta : MIN_CLTV_EXPIRY_DELTA ,
251257 htlc_minimum_msat : None ,
252258 htlc_maximum_msat : None , } ) ;
259+ }
260+
261+ phantom_hints. push ( route_hints) ;
262+ }
253263
254- phantom_hints. push ( route_hint. clone ( ) ) ;
264+ // We have one vector per real node involved in creating the phantom invoice. To distribute
265+ // the hints across our real nodes we add one hint from each 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] ) ;
277+ remaining_hints = true
278+ }
255279 }
280+
281+ if !remaining_hints {
282+ break
283+ }
284+
285+ hint_idx +=1 ;
256286 }
257287
258- phantom_hints
288+ invoice_hints . into_iter ( ) . take ( 3 ) . cloned ( ) . collect ( )
259289}
260290
261291#[ cfg( feature = "std" ) ]
@@ -1536,6 +1566,64 @@ mod test {
15361566 ) ;
15371567 }
15381568
1569+ #[ test]
1570+ fn test_multi_node_hints_limited_to_3 ( ) {
1571+ let mut chanmon_cfgs = create_chanmon_cfgs ( 5 ) ;
1572+ let seed_1 = [ 42 as u8 ; 32 ] ;
1573+ let seed_2 = [ 43 as u8 ; 32 ] ;
1574+ let seed_3 = [ 44 as u8 ; 32 ] ;
1575+ let cross_node_seed = [ 44 as u8 ; 32 ] ;
1576+ chanmon_cfgs[ 2 ] . keys_manager . backing = PhantomKeysManager :: new ( & seed_1, 43 , 44 , & cross_node_seed) ;
1577+ chanmon_cfgs[ 3 ] . keys_manager . backing = PhantomKeysManager :: new ( & seed_2, 43 , 44 , & cross_node_seed) ;
1578+ chanmon_cfgs[ 4 ] . keys_manager . backing = PhantomKeysManager :: new ( & seed_3, 43 , 44 , & cross_node_seed) ;
1579+ let node_cfgs = create_node_cfgs ( 5 , & chanmon_cfgs) ;
1580+ let node_chanmgrs = create_node_chanmgrs ( 5 , & node_cfgs, & [ None , None , None , None , None ] ) ;
1581+ let nodes = create_network ( 5 , & node_cfgs, & node_chanmgrs) ;
1582+
1583+ // Setup each phantom node with two channels from distinct peers.
1584+ let _chan_0_2 = create_unannounced_chan_between_nodes_with_value ( & nodes, 0 , 2 , 10_000 , 0 ) ;
1585+ let _chan_1_2 = create_unannounced_chan_between_nodes_with_value ( & nodes, 1 , 2 , 20_000 , 0 ) ;
1586+ let _chan_0_3 = create_unannounced_chan_between_nodes_with_value ( & nodes, 0 , 3 , 20_000 , 0 ) ;
1587+ let _chan_1_3 = create_unannounced_chan_between_nodes_with_value ( & nodes, 1 , 3 , 10_000 , 0 ) ;
1588+ let _chan_0_4 = create_unannounced_chan_between_nodes_with_value ( & nodes, 0 , 4 , 20_000 , 0 ) ;
1589+ let _chan_1_4 = create_unannounced_chan_between_nodes_with_value ( & nodes, 1 , 4 , 10_000 , 0 ) ;
1590+
1591+ // Since the invoice amount is above all channels inbound, every one is eligible. Test that
1592+ // we include one channel from each of our three nodes.
1593+ match_multi_node_invoice_distinct_nodes (
1594+ Some ( 100_000_000 ) ,
1595+ & nodes[ 3 ] ,
1596+ vec ! [ & nodes[ 2 ] , & nodes[ 3 ] , & nodes[ 4 ] , ] ,
1597+ 3 ,
1598+ ) ;
1599+ }
1600+
1601+ #[ test]
1602+ fn test_multi_node_hints_at_least_3 ( ) {
1603+ let mut chanmon_cfgs = create_chanmon_cfgs ( 5 ) ;
1604+ let seed_1 = [ 42 as u8 ; 32 ] ;
1605+ let seed_2 = [ 43 as u8 ; 32 ] ;
1606+ let cross_node_seed = [ 44 as u8 ; 32 ] ;
1607+ chanmon_cfgs[ 1 ] . keys_manager . backing = PhantomKeysManager :: new ( & seed_1, 43 , 44 , & cross_node_seed) ;
1608+ chanmon_cfgs[ 2 ] . keys_manager . backing = PhantomKeysManager :: new ( & seed_2, 43 , 44 , & cross_node_seed) ;
1609+ let node_cfgs = create_node_cfgs ( 5 , & chanmon_cfgs) ;
1610+ let node_chanmgrs = create_node_chanmgrs ( 5 , & node_cfgs, & [ None , None , None , None , None ] ) ;
1611+ let nodes = create_network ( 5 , & node_cfgs, & node_chanmgrs) ;
1612+
1613+ let _chan_0_3 = create_unannounced_chan_between_nodes_with_value ( & nodes, 0 , 3 , 10_000 , 0 ) ;
1614+ let _chan_1_3 = create_unannounced_chan_between_nodes_with_value ( & nodes, 1 , 3 , 20_000 , 0 ) ;
1615+ let _chan_2_3 = create_unannounced_chan_between_nodes_with_value ( & nodes, 2 , 3 , 30_000 , 0 ) ;
1616+ let _chan_0_4 = create_unannounced_chan_between_nodes_with_value ( & nodes, 0 , 4 , 10_000 , 0 ) ;
1617+
1618+ // Since the invoice amount is above all channels inbound, all four are eligible. Test that
1619+ // we still include 3 hints from 2 distinct nodes.
1620+ match_multi_node_invoice_distinct_nodes (
1621+ Some ( 100_000_000 ) ,
1622+ & nodes[ 3 ] , vec ! [ & nodes[ 3 ] , & nodes[ 4 ] , ] ,
1623+ 2 ,
1624+ ) ;
1625+ }
1626+
15391627 fn create_multi_node_invoice < ' a , ' b : ' a , ' c : ' b > (
15401628 invoice_amt : Option < u64 > ,
15411629 invoice_node : & Node < ' a , ' b , ' c > ,
@@ -1556,6 +1644,29 @@ mod test {
15561644 ( invoice, phantom_scids)
15571645 }
15581646
1647+ fn match_multi_node_invoice_distinct_nodes < ' a , ' b : ' a , ' c : ' b > (
1648+ invoice_amt : Option < u64 > ,
1649+ invoice_node : & Node < ' a , ' b , ' c > ,
1650+ network_multi_nodes : Vec < & Node < ' a , ' b , ' c > > ,
1651+ distinct_nodes : usize ,
1652+ ) {
1653+ let ( invoice, phantom_scids) = create_multi_node_invoice ( invoice_amt, invoice_node, network_multi_nodes) ;
1654+
1655+ let mut hint_nodes = crate :: HashMap :: new ( ) ;
1656+ for hint in invoice. private_routes ( ) {
1657+ let hints = & ( hint. 0 ) . 0 ;
1658+ match hints. len ( ) {
1659+ 2 => {
1660+ hint_nodes. insert ( hints[ 1 ] . src_node_id , true ) ;
1661+ let phantom_scid = hints[ 1 ] . short_channel_id ;
1662+ assert ! ( phantom_scids. contains( & phantom_scid) ) ;
1663+ } ,
1664+ _ => panic ! ( "Incorrect hint length generated" )
1665+ }
1666+ }
1667+ assert_eq ! ( hint_nodes. len( ) , distinct_nodes, "Unmatched number of distinct phantom nodes for hints." ) ;
1668+ }
1669+
15591670 fn match_multi_node_invoice_routes < ' a , ' b : ' a , ' c : ' b > (
15601671 invoice_amt : Option < u64 > ,
15611672 invoice_node : & Node < ' a , ' b , ' c > ,
0 commit comments