Skip to content

Commit 14c8a60

Browse files
Limit phantom invoice hints to 3
1 parent 48a0991 commit 14c8a60

File tree

1 file changed

+109
-10
lines changed

1 file changed

+109
-10
lines changed

lightning-invoice/src/utils.rs

+109-10
Original file line numberDiff line numberDiff line change
@@ -86,9 +86,11 @@ where
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,14 +222,18 @@ where
220222

221223
/// Utility to select route hints for phantom invoices.
222224
/// 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+
///
224230
/// [`PhantomKeysManager`]: lightning::chain::keysinterface::PhantomKeysManager
225231
fn select_phantom_hints<L: Deref>(amt_msat: Option<u64>, phantom_route_hints: Vec<PhantomRouteHints>,
226232
logger: L) -> Vec<RouteHint>
227233
where
228234
L::Target: Logger,
229235
{
230-
let mut phantom_hints : Vec<RouteHint> = Vec::new();
236+
let mut phantom_hints : Vec<Vec<RouteHint>> = Vec::new();
231237

232238
for PhantomRouteHints { channels, phantom_scid, real_node_pubkey } in phantom_route_hints {
233239
log_trace!(logger, "Generating phantom route hints for node {}",
@@ -241,7 +247,7 @@ where
241247
if route_hints.is_empty() {
242248
route_hints.push(RouteHint(vec![]))
243249
}
244-
for mut route_hint in route_hints {
250+
for route_hint in &mut route_hints {
245251
route_hint.0.push(RouteHintHop {
246252
src_node_id: real_node_pubkey,
247253
short_channel_id: phantom_scid,
@@ -252,12 +258,34 @@ where
252258
cltv_expiry_delta: MIN_CLTV_EXPIRY_DELTA,
253259
htlc_minimum_msat: None,
254260
htlc_maximum_msat: None,});
255-
256-
phantom_hints.push(route_hint.clone());
257261
}
262+
263+
phantom_hints.push(route_hints);
258264
}
259265

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 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+
}
261289
}
262290

263291
#[cfg(feature = "std")]
@@ -1721,7 +1749,78 @@ mod test {
17211749
);
17221750
}
17231751

1724-
#[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+
17251824
fn match_multi_node_invoice_routes<'a, 'b: 'a, 'c: 'b>(
17261825
invoice_amt: Option<u64>,
17271826
invoice_node: &Node<'a, 'b, 'c>,

0 commit comments

Comments
 (0)