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,14 +222,18 @@ where
220
222
221
223
/// Utility to select route hints for phantom invoices.
222
224
/// See [`PhantomKeysManager`] for more information on phantom node payments.
223
- ///
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.
229
+ ///
224
230
/// [`PhantomKeysManager`]: lightning::chain::keysinterface::PhantomKeysManager
225
231
fn select_phantom_hints < L : Deref > ( amt_msat : Option < u64 > , phantom_route_hints : Vec < PhantomRouteHints > ,
226
232
logger : L ) -> Vec < RouteHint >
227
233
where
228
234
L :: Target : Logger ,
229
235
{
230
- let mut phantom_hints: Vec < RouteHint > = Vec :: new ( ) ;
236
+ let mut phantom_hints: Vec < Vec < RouteHint > > = Vec :: new ( ) ;
231
237
232
238
for PhantomRouteHints { channels, phantom_scid, real_node_pubkey } in phantom_route_hints {
233
239
log_trace ! ( logger, "Generating phantom route hints for node {}" ,
@@ -241,7 +247,7 @@ where
241
247
if route_hints. is_empty ( ) {
242
248
route_hints. push ( RouteHint ( vec ! [ ] ) )
243
249
}
244
- for mut route_hint in route_hints {
250
+ for route_hint in & mut route_hints {
245
251
route_hint. 0 . push ( RouteHintHop {
246
252
src_node_id : real_node_pubkey,
247
253
short_channel_id : phantom_scid,
@@ -252,12 +258,38 @@ where
252
258
cltv_expiry_delta : MIN_CLTV_EXPIRY_DELTA ,
253
259
htlc_minimum_msat : None ,
254
260
htlc_maximum_msat : None , } ) ;
255
-
256
- phantom_hints. push ( route_hint. clone ( ) ) ;
257
261
}
262
+
263
+ phantom_hints. push ( route_hints) ;
258
264
}
259
265
260
- phantom_hints
266
+ // We have one vector per real node involved in creating the phantom invoice. To distribute
267
+ // the hints across our real nodes we add one hint from each in turn until no node has any hints
268
+ // left (if one node has more hints than any other, these will accumulate at the end of the
269
+ // vector).
270
+ let mut invoice_hints: Vec < RouteHint > = Vec :: new ( ) ;
271
+ let mut hint_idx = 0 ;
272
+
273
+ loop {
274
+ let mut remaining_hints = false ;
275
+
276
+ for hints in phantom_hints. iter ( ) {
277
+ if invoice_hints. len ( ) == 3 {
278
+ return invoice_hints
279
+ }
280
+
281
+ if hint_idx < hints. len ( ) {
282
+ invoice_hints. push ( hints[ hint_idx] . clone ( ) ) ;
283
+ remaining_hints = true
284
+ }
285
+ }
286
+
287
+ if !remaining_hints {
288
+ return invoice_hints
289
+ }
290
+
291
+ hint_idx +=1 ;
292
+ }
261
293
}
262
294
263
295
#[ cfg( feature = "std" ) ]
@@ -1721,7 +1753,98 @@ mod test {
1721
1753
) ;
1722
1754
}
1723
1755
1724
- #[ cfg( feature = "std" ) ]
1756
+ #[ test]
1757
+ fn test_multi_node_hints_limited_to_3 ( ) {
1758
+ let mut chanmon_cfgs = create_chanmon_cfgs ( 6 ) ;
1759
+ let seed_1 = [ 42 as u8 ; 32 ] ;
1760
+ let seed_2 = [ 43 as u8 ; 32 ] ;
1761
+ let seed_3 = [ 44 as u8 ; 32 ] ;
1762
+ let seed_4 = [ 45 as u8 ; 32 ] ;
1763
+ let cross_node_seed = [ 44 as u8 ; 32 ] ;
1764
+ chanmon_cfgs[ 2 ] . keys_manager . backing = PhantomKeysManager :: new ( & seed_1, 43 , 44 , & cross_node_seed) ;
1765
+ chanmon_cfgs[ 3 ] . keys_manager . backing = PhantomKeysManager :: new ( & seed_2, 43 , 44 , & cross_node_seed) ;
1766
+ chanmon_cfgs[ 4 ] . keys_manager . backing = PhantomKeysManager :: new ( & seed_3, 43 , 44 , & cross_node_seed) ;
1767
+ chanmon_cfgs[ 5 ] . keys_manager . backing = PhantomKeysManager :: new ( & seed_4, 43 , 44 , & cross_node_seed) ;
1768
+ let node_cfgs = create_node_cfgs ( 6 , & chanmon_cfgs) ;
1769
+ let node_chanmgrs = create_node_chanmgrs ( 6 , & node_cfgs, & [ None , None , None , None , None , None ] ) ;
1770
+ let nodes = create_network ( 6 , & node_cfgs, & node_chanmgrs) ;
1771
+
1772
+ // Setup each phantom node with two channels from distinct peers.
1773
+ let chan_0_2 = create_unannounced_chan_between_nodes_with_value ( & nodes, 0 , 2 , 10_000 , 0 ) ;
1774
+ let chan_1_2 = create_unannounced_chan_between_nodes_with_value ( & nodes, 1 , 2 , 20_000 , 0 ) ;
1775
+ let chan_0_3 = create_unannounced_chan_between_nodes_with_value ( & nodes, 0 , 3 , 20_000 , 0 ) ;
1776
+ let _chan_1_3 = create_unannounced_chan_between_nodes_with_value ( & nodes, 1 , 3 , 10_000 , 0 ) ;
1777
+ let chan_0_4 = create_unannounced_chan_between_nodes_with_value ( & nodes, 0 , 4 , 20_000 , 0 ) ;
1778
+ let _chan_1_4 = create_unannounced_chan_between_nodes_with_value ( & nodes, 1 , 4 , 10_000 , 0 ) ;
1779
+ let _chan_0_5 = create_unannounced_chan_between_nodes_with_value ( & nodes, 0 , 5 , 20_000 , 0 ) ;
1780
+ let _chan_1_5 = create_unannounced_chan_between_nodes_with_value ( & nodes, 1 , 5 , 10_000 , 0 ) ;
1781
+
1782
+ // Set invoice amount > all channels inbound so that every one is eligible for inclusion
1783
+ // and hints will be sorted by largest inbound capacity.
1784
+ let invoice_amt = Some ( 100_000_000 ) ;
1785
+
1786
+ // With 4 phantom nodes, assert that we include 1 hint per node, up to 3 nodes.
1787
+ let mut scid_aliases = HashSet :: new ( ) ;
1788
+ scid_aliases. insert ( chan_1_2. 0 . short_channel_id_alias . unwrap ( ) ) ;
1789
+ scid_aliases. insert ( chan_0_3. 0 . short_channel_id_alias . unwrap ( ) ) ;
1790
+ scid_aliases. insert ( chan_0_4. 0 . short_channel_id_alias . unwrap ( ) ) ;
1791
+
1792
+ match_multi_node_invoice_routes (
1793
+ invoice_amt,
1794
+ & nodes[ 3 ] ,
1795
+ vec ! [ & nodes[ 2 ] , & nodes[ 3 ] , & nodes[ 4 ] , & nodes[ 5 ] ] ,
1796
+ scid_aliases,
1797
+ false ,
1798
+ ) ;
1799
+
1800
+ // With 2 phantom nodes, assert that we include no more than 3 hints.
1801
+ let mut scid_aliases = HashSet :: new ( ) ;
1802
+ scid_aliases. insert ( chan_1_2. 0 . short_channel_id_alias . unwrap ( ) ) ;
1803
+ scid_aliases. insert ( chan_0_3. 0 . short_channel_id_alias . unwrap ( ) ) ;
1804
+ scid_aliases. insert ( chan_0_2. 0 . short_channel_id_alias . unwrap ( ) ) ;
1805
+
1806
+ match_multi_node_invoice_routes (
1807
+ invoice_amt,
1808
+ & nodes[ 3 ] ,
1809
+ vec ! [ & nodes[ 2 ] , & nodes[ 3 ] ] ,
1810
+ scid_aliases,
1811
+ false ,
1812
+ ) ;
1813
+ }
1814
+
1815
+ #[ test]
1816
+ fn test_multi_node_hints_at_least_3 ( ) {
1817
+ let mut chanmon_cfgs = create_chanmon_cfgs ( 5 ) ;
1818
+ let seed_1 = [ 42 as u8 ; 32 ] ;
1819
+ let seed_2 = [ 43 as u8 ; 32 ] ;
1820
+ let cross_node_seed = [ 44 as u8 ; 32 ] ;
1821
+ chanmon_cfgs[ 1 ] . keys_manager . backing = PhantomKeysManager :: new ( & seed_1, 43 , 44 , & cross_node_seed) ;
1822
+ chanmon_cfgs[ 2 ] . keys_manager . backing = PhantomKeysManager :: new ( & seed_2, 43 , 44 , & cross_node_seed) ;
1823
+ let node_cfgs = create_node_cfgs ( 5 , & chanmon_cfgs) ;
1824
+ let node_chanmgrs = create_node_chanmgrs ( 5 , & node_cfgs, & [ None , None , None , None , None ] ) ;
1825
+ let nodes = create_network ( 5 , & node_cfgs, & node_chanmgrs) ;
1826
+
1827
+ let _chan_0_3 = create_unannounced_chan_between_nodes_with_value ( & nodes, 0 , 3 , 10_000 , 0 ) ;
1828
+ let chan_1_3 = create_unannounced_chan_between_nodes_with_value ( & nodes, 1 , 3 , 20_000 , 0 ) ;
1829
+ let chan_2_3 = create_unannounced_chan_between_nodes_with_value ( & nodes, 2 , 3 , 30_000 , 0 ) ;
1830
+ let chan_0_4 = create_unannounced_chan_between_nodes_with_value ( & nodes, 0 , 4 , 10_000 , 0 ) ;
1831
+
1832
+ // Since the invoice amount is above all channels inbound, all four are eligible. Test that
1833
+ // we still include 3 hints from 2 distinct nodes sorted by inbound.
1834
+ let mut scid_aliases = HashSet :: new ( ) ;
1835
+ scid_aliases. insert ( chan_1_3. 0 . short_channel_id_alias . unwrap ( ) ) ;
1836
+ scid_aliases. insert ( chan_2_3. 0 . short_channel_id_alias . unwrap ( ) ) ;
1837
+ scid_aliases. insert ( chan_0_4. 0 . short_channel_id_alias . unwrap ( ) ) ;
1838
+
1839
+ match_multi_node_invoice_routes (
1840
+ Some ( 100_000_000 ) ,
1841
+ & nodes[ 3 ] ,
1842
+ vec ! [ & nodes[ 3 ] , & nodes[ 4 ] , ] ,
1843
+ scid_aliases,
1844
+ false ,
1845
+ ) ;
1846
+ }
1847
+
1725
1848
fn match_multi_node_invoice_routes < ' a , ' b : ' a , ' c : ' b > (
1726
1849
invoice_amt : Option < u64 > ,
1727
1850
invoice_node : & Node < ' a , ' b , ' c > ,
0 commit comments