Skip to content

Commit fe49fbd

Browse files
Filter route hints for create invoice
Filter the route hints in `create_invoice_from_channelmanager` based on the following criteria: * Only one channel per counterparty node * Always select the channel with the highest inbound capacity * Filter out channels with a lower inbound capacity than the invoice amount, if any channel exists with enough capacity to cover the invoice amount * If any public channel exists, the invoice route_hints should be empty, and the sender will need to find the path to the payment-receiving node by looking at the public channels instead
1 parent ca163c3 commit fe49fbd

File tree

1 file changed

+70
-25
lines changed

1 file changed

+70
-25
lines changed

lightning-invoice/src/utils.rs

Lines changed: 70 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -161,30 +161,7 @@ where
161161
F::Target: FeeEstimator,
162162
L::Target: Logger,
163163
{
164-
// Marshall route hints.
165-
let our_channels = channelmanager.list_usable_channels();
166-
let mut route_hints = vec![];
167-
for channel in our_channels {
168-
let short_channel_id = match channel.get_inbound_payment_scid() {
169-
Some(id) => id,
170-
None => continue,
171-
};
172-
let forwarding_info = match channel.counterparty.forwarding_info {
173-
Some(info) => info,
174-
None => continue,
175-
};
176-
route_hints.push(RouteHint(vec![RouteHintHop {
177-
src_node_id: channel.counterparty.node_id,
178-
short_channel_id,
179-
fees: RoutingFees {
180-
base_msat: forwarding_info.fee_base_msat,
181-
proportional_millionths: forwarding_info.fee_proportional_millionths,
182-
},
183-
cltv_expiry_delta: forwarding_info.cltv_expiry_delta,
184-
htlc_minimum_msat: None,
185-
htlc_maximum_msat: None,
186-
}]));
187-
}
164+
let route_hints = filter_channels(channelmanager.list_usable_channels(), amt_msat);
188165

189166
// `create_inbound_payment` only returns an error if the amount is greater than the total bitcoin
190167
// supply.
@@ -221,6 +198,74 @@ where
221198
}
222199
}
223200

201+
/// Filters the `channels` for an invoice, and returns the corresponding `RouteHint`s to include
202+
/// in the invoice.
203+
///
204+
/// The filtering is based on the following criteria:
205+
/// * Only one channel per counterparty node
206+
/// * Always select the channel with the highest inbound capacity per counterparty node
207+
/// * Filter out channels with a lower inbound capacity than `min_inbound_capacity_msat`, if any
208+
/// channel with a higher or equal inbound capacity than `min_inbound_capacity_msat` exists
209+
/// * If any public channel exists, the returned `RouteHint`s will be empty, and the sender will
210+
/// need to find the path by looking at the public channels instead
211+
fn filter_channels(channels: Vec<ChannelDetails>, min_inbound_capacity_msat: Option<u64>) -> Vec<RouteHint>{
212+
let mut filtered_channels: HashMap<PublicKey, &ChannelDetails> = HashMap::new();
213+
let min_inbound_capacity = min_inbound_capacity_msat.unwrap_or(0);
214+
let mut min_capacity_channel_exists = false;
215+
216+
for channel in channels.iter() {
217+
if channel.get_inbound_payment_scid().is_none() || channel.counterparty.forwarding_info.is_none() {
218+
continue;
219+
}
220+
221+
if channel.is_public {
222+
// If any public channel exists, return no hints and let the sender
223+
// look at the public channels instead.
224+
return vec![]
225+
}
226+
227+
if channel.inbound_capacity_msat >= min_inbound_capacity {
228+
min_capacity_channel_exists = true;
229+
};
230+
match filtered_channels.entry(channel.counterparty.node_id) {
231+
hash_map::Entry::Occupied(mut entry) => {
232+
let current_max_capacity = entry.get().inbound_capacity_msat;
233+
if channel.inbound_capacity_msat < current_max_capacity {
234+
continue;
235+
}
236+
entry.insert(channel);
237+
}
238+
hash_map::Entry::Vacant(entry) => {
239+
entry.insert(channel);
240+
}
241+
}
242+
}
243+
244+
let route_hint_from_channel = |channel: &ChannelDetails| {
245+
let forwarding_info = channel.counterparty.forwarding_info.as_ref().unwrap();
246+
RouteHint(vec![RouteHintHop {
247+
src_node_id: channel.counterparty.node_id,
248+
short_channel_id: channel.get_inbound_payment_scid().unwrap(),
249+
fees: RoutingFees {
250+
base_msat: forwarding_info.fee_base_msat,
251+
proportional_millionths: forwarding_info.fee_proportional_millionths,
252+
},
253+
cltv_expiry_delta: forwarding_info.cltv_expiry_delta,
254+
htlc_minimum_msat: None,
255+
htlc_maximum_msat: None,}])
256+
};
257+
// If all channels are private, return the route hint for the highest inbound capacity channel
258+
// per counterparty node. If channels with an higher inbound capacity than the
259+
// min_inbound_capacity exists, filter out the channels with a lower capacity than that.
260+
filtered_channels.into_iter()
261+
.filter(|(_counterparty_id, channel)| {
262+
min_capacity_channel_exists && channel.inbound_capacity_msat >= min_inbound_capacity ||
263+
!min_capacity_channel_exists
264+
})
265+
.map(|(_counterparty_id, channel)| route_hint_from_channel(&channel))
266+
.collect::<Vec<RouteHint>>()
267+
}
268+
224269
/// A [`Router`] implemented using [`find_route`].
225270
pub struct DefaultRouter<G: Deref<Target = NetworkGraph>, L: Deref> where L::Target: Logger {
226271
network_graph: G,
@@ -317,7 +362,7 @@ mod test {
317362
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
318363
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
319364
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
320-
let _chan = create_announced_chan_between_nodes(&nodes, 0, 1, InitFeatures::known(), InitFeatures::known());
365+
create_unannounced_chan_between_nodes_with_value(&nodes, 0, 1, 100000, 10001, InitFeatures::known(), InitFeatures::known());
321366
let invoice = create_invoice_from_channelmanager_and_duration_since_epoch(
322367
&nodes[1].node, nodes[1].keys_manager, Currency::BitcoinTestnet, Some(10_000), "test".to_string(),
323368
Duration::from_secs(1234567)).unwrap();

0 commit comments

Comments
 (0)