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,18 @@ 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.
229+ ///
230+ /// [`PhantomKeysManager`]: lightning::chain::keysinterface::PhantomKeysManager
223231fn select_phantom_hints < L : Deref > ( amt_msat : Option < u64 > , phantom_route_hints : Vec < PhantomRouteHints > ,
224232 logger : L ) -> Vec < RouteHint >
225233where
226234 L :: Target : Logger ,
227235{
228- let mut phantom_hints : Vec < RouteHint > = Vec :: new ( ) ;
236+ let mut phantom_hints : Vec < Vec < RouteHint > > = Vec :: new ( ) ;
229237
230238 for PhantomRouteHints { channels, phantom_scid, real_node_pubkey } in phantom_route_hints {
231239 log_trace ! ( logger, "Generating phantom route hints for node {}" ,
@@ -239,7 +247,7 @@ where
239247 if route_hints. is_empty ( ) {
240248 route_hints. push ( RouteHint ( vec ! [ ] ) )
241249 }
242- for mut route_hint in route_hints {
250+ for route_hint in & mut route_hints {
243251 route_hint. 0 . push ( RouteHintHop {
244252 src_node_id : real_node_pubkey,
245253 short_channel_id : phantom_scid,
@@ -250,12 +258,34 @@ where
250258 cltv_expiry_delta : MIN_CLTV_EXPIRY_DELTA ,
251259 htlc_minimum_msat : None ,
252260 htlc_maximum_msat : None , } ) ;
253-
254- phantom_hints. push ( route_hint. clone ( ) ) ;
255261 }
262+
263+ phantom_hints. push ( route_hints) ;
256264 }
257265
258- 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 hint_idx < hints. len ( ) {
278+ invoice_hints. push ( hints[ hint_idx] . clone ( ) ) ;
279+ remaining_hints = true
280+ }
281+ }
282+
283+ if !remaining_hints || invoice_hints. len ( ) == 3 {
284+ return invoice_hints
285+ }
286+
287+ hint_idx +=1 ;
288+ }
259289}
260290
261291#[ cfg( feature = "std" ) ]
@@ -1719,7 +1749,78 @@ mod test {
17191749 ) ;
17201750 }
17211751
1722- #[ cfg( feature = "std" ) ]
1752+ #[ test]
1753+ fn test_multi_node_hints_limited_to_3 ( ) {
1754+ let mut chanmon_cfgs = create_chanmon_cfgs ( 5 ) ;
1755+ let seed_1 = [ 42 as u8 ; 32 ] ;
1756+ let seed_2 = [ 43 as u8 ; 32 ] ;
1757+ let seed_3 = [ 44 as u8 ; 32 ] ;
1758+ let cross_node_seed = [ 44 as u8 ; 32 ] ;
1759+ chanmon_cfgs[ 2 ] . keys_manager . backing = PhantomKeysManager :: new ( & seed_1, 43 , 44 , & cross_node_seed) ;
1760+ chanmon_cfgs[ 3 ] . keys_manager . backing = PhantomKeysManager :: new ( & seed_2, 43 , 44 , & cross_node_seed) ;
1761+ chanmon_cfgs[ 4 ] . keys_manager . backing = PhantomKeysManager :: new ( & seed_3, 43 , 44 , & cross_node_seed) ;
1762+ let node_cfgs = create_node_cfgs ( 5 , & chanmon_cfgs) ;
1763+ let node_chanmgrs = create_node_chanmgrs ( 5 , & node_cfgs, & [ None , None , None , None , None ] ) ;
1764+ let nodes = create_network ( 5 , & node_cfgs, & node_chanmgrs) ;
1765+
1766+ // Setup each phantom node with two channels from distinct peers.
1767+ let _chan_0_2 = create_unannounced_chan_between_nodes_with_value ( & nodes, 0 , 2 , 10_000 , 0 ) ;
1768+ let chan_1_2 = create_unannounced_chan_between_nodes_with_value ( & nodes, 1 , 2 , 20_000 , 0 ) ;
1769+ let chan_0_3 = create_unannounced_chan_between_nodes_with_value ( & nodes, 0 , 3 , 20_000 , 0 ) ;
1770+ let _chan_1_3 = create_unannounced_chan_between_nodes_with_value ( & nodes, 1 , 3 , 10_000 , 0 ) ;
1771+ let chan_0_4 = create_unannounced_chan_between_nodes_with_value ( & nodes, 0 , 4 , 20_000 , 0 ) ;
1772+ let _chan_1_4 = create_unannounced_chan_between_nodes_with_value ( & nodes, 1 , 4 , 10_000 , 0 ) ;
1773+
1774+ let mut scid_aliases = HashSet :: new ( ) ;
1775+ scid_aliases. insert ( chan_1_2. 0 . short_channel_id_alias . unwrap ( ) ) ;
1776+ scid_aliases. insert ( chan_0_3. 0 . short_channel_id_alias . unwrap ( ) ) ;
1777+ scid_aliases. insert ( chan_0_4. 0 . short_channel_id_alias . unwrap ( ) ) ;
1778+
1779+ // Since the invoice amount is above all channels inbound, every one is eligible and we'll
1780+ // sort by largest inbound capacity. Test that we include one channel from each of our
1781+ // three nodes.
1782+ match_multi_node_invoice_routes (
1783+ Some ( 100_000_000 ) ,
1784+ & nodes[ 3 ] ,
1785+ vec ! [ & nodes[ 2 ] , & nodes[ 3 ] , & nodes[ 4 ] , ] ,
1786+ scid_aliases,
1787+ false ,
1788+ ) ;
1789+ }
1790+
1791+ #[ test]
1792+ fn test_multi_node_hints_at_least_3 ( ) {
1793+ let mut chanmon_cfgs = create_chanmon_cfgs ( 5 ) ;
1794+ let seed_1 = [ 42 as u8 ; 32 ] ;
1795+ let seed_2 = [ 43 as u8 ; 32 ] ;
1796+ let cross_node_seed = [ 44 as u8 ; 32 ] ;
1797+ chanmon_cfgs[ 1 ] . keys_manager . backing = PhantomKeysManager :: new ( & seed_1, 43 , 44 , & cross_node_seed) ;
1798+ chanmon_cfgs[ 2 ] . keys_manager . backing = PhantomKeysManager :: new ( & seed_2, 43 , 44 , & cross_node_seed) ;
1799+ let node_cfgs = create_node_cfgs ( 5 , & chanmon_cfgs) ;
1800+ let node_chanmgrs = create_node_chanmgrs ( 5 , & node_cfgs, & [ None , None , None , None , None ] ) ;
1801+ let nodes = create_network ( 5 , & node_cfgs, & node_chanmgrs) ;
1802+
1803+ let _chan_0_3 = create_unannounced_chan_between_nodes_with_value ( & nodes, 0 , 3 , 10_000 , 0 ) ;
1804+ let chan_1_3 = create_unannounced_chan_between_nodes_with_value ( & nodes, 1 , 3 , 20_000 , 0 ) ;
1805+ let chan_2_3 = create_unannounced_chan_between_nodes_with_value ( & nodes, 2 , 3 , 30_000 , 0 ) ;
1806+ let chan_0_4 = create_unannounced_chan_between_nodes_with_value ( & nodes, 0 , 4 , 10_000 , 0 ) ;
1807+
1808+ // Since the invoice amount is above all channels inbound, all four are eligible. Test that
1809+ // we still include 3 hints from 2 distinct nodes sorted by inbound.
1810+ let mut scid_aliases = HashSet :: new ( ) ;
1811+ scid_aliases. insert ( chan_1_3. 0 . short_channel_id_alias . unwrap ( ) ) ;
1812+ scid_aliases. insert ( chan_2_3. 0 . short_channel_id_alias . unwrap ( ) ) ;
1813+ scid_aliases. insert ( chan_0_4. 0 . short_channel_id_alias . unwrap ( ) ) ;
1814+
1815+ match_multi_node_invoice_routes (
1816+ Some ( 100_000_000 ) ,
1817+ & nodes[ 3 ] ,
1818+ vec ! [ & nodes[ 3 ] , & nodes[ 4 ] , ] ,
1819+ scid_aliases,
1820+ false ,
1821+ ) ;
1822+ }
1823+
17231824 fn match_multi_node_invoice_routes < ' a , ' b : ' a , ' c : ' b > (
17241825 invoice_amt : Option < u64 > ,
17251826 invoice_node : & Node < ' a , ' b , ' c > ,
0 commit comments