Skip to content

Commit ed6ed42

Browse files
committed
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 ed6ed42

File tree

1 file changed

+94
-26
lines changed

1 file changed

+94
-26
lines changed

lightning-invoice/src/utils.rs

Lines changed: 94 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use lightning::chain;
1010
use lightning::chain::chaininterface::{BroadcasterInterface, FeeEstimator};
1111
use lightning::chain::keysinterface::{Recipient, KeysInterface, Sign};
1212
use lightning::ln::{PaymentHash, PaymentPreimage, PaymentSecret};
13-
use lightning::ln::channelmanager::{ChannelDetails, ChannelManager, PaymentId, PaymentSendFailure, MIN_FINAL_CLTV_EXPIRY};
13+
use lightning::ln::channelmanager::{ChannelDetails, ChannelManager, PaymentId, PaymentSendFailure, CounterpartyForwardingInfo, MIN_FINAL_CLTV_EXPIRY};
1414
#[cfg(feature = "std")]
1515
use lightning::ln::channelmanager::{PhantomRouteHints, MIN_CLTV_EXPIRY_DELTA};
1616
use lightning::ln::msgs::LightningError;
@@ -161,30 +161,10 @@ 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(
165+
channelmanager.list_usable_channels(),
166+
amt_msat,
167+
);
188168

189169
// `create_inbound_payment` only returns an error if the amount is greater than the total bitcoin
190170
// supply.
@@ -221,6 +201,91 @@ where
221201
}
222202
}
223203

204+
/// Filters channels for an invoice, and returns the corresponding route hints to include
205+
/// in the invoice.
206+
///
207+
/// The filtering ensures that the `RouteHints` returned only include the highest inbound capacity
208+
/// channel per counterparty node. If any channel with a higher inbound capacity than the given
209+
/// `min_inbound_capacity_msat` exists, channels to other counterparty nodes with a lower inbound
210+
/// capacity than `min_inbound_capacity_msat` will be filtered out, even if they are the highest
211+
/// inbound capacity channel for that specific counterparty node.
212+
/// If any public channel exists, the function returns no RouteHints, and the sender will need to
213+
/// look at the public channels to find a path instead.
214+
///
215+
/// Input:
216+
/// `channels`: The channels to filter.
217+
/// `min_inbound_capacity_msat`: Defines the lowest inbound capacity channels must have to not
218+
/// be filtered out, if any other channel above that amount exists.
219+
///
220+
/// Result:
221+
/// `Vec<RouteHint>`: The filtered `RouteHints`, which will be empty if any public channel exists.
222+
fn filter_channels(channels: Vec<ChannelDetails>, min_inbound_capacity_msat: Option<u64>) -> Vec<RouteHint>{
223+
let mut filtered_channels: HashMap<PublicKey, (u64, RouteHint)> = HashMap::new();
224+
let min_inbound_capacity = min_inbound_capacity_msat.unwrap_or(0);
225+
let mut min_capacity_channel_exists = false;
226+
227+
let route_hint_from_channel = |channel: &ChannelDetails, short_channel_id: u64, forwarding_info: &CounterpartyForwardingInfo| {
228+
RouteHint(vec![RouteHintHop {
229+
src_node_id: channel.counterparty.node_id,
230+
short_channel_id,
231+
fees: RoutingFees {
232+
base_msat: forwarding_info.fee_base_msat,
233+
proportional_millionths: forwarding_info.fee_proportional_millionths,
234+
},
235+
cltv_expiry_delta: forwarding_info.cltv_expiry_delta,
236+
htlc_minimum_msat: None,
237+
htlc_maximum_msat: None,}])
238+
};
239+
for channel in channels {
240+
let short_channel_id = match channel.get_inbound_payment_scid() {
241+
Some(id) => id,
242+
None => continue,
243+
};
244+
let forwarding_info = match &channel.counterparty.forwarding_info {
245+
Some(info) => info,
246+
None => continue,
247+
};
248+
if channel.is_public {
249+
// If any public channel exists, return no hints and let the sender
250+
// look at the public channels instead.
251+
return vec![]
252+
}
253+
254+
if channel.inbound_capacity_msat >= min_inbound_capacity {
255+
min_capacity_channel_exists = true;
256+
};
257+
match filtered_channels.entry(channel.counterparty.node_id) {
258+
hash_map::Entry::Occupied(mut entry) => {
259+
let current_max_capacity = entry.get().0;
260+
if channel.inbound_capacity_msat < current_max_capacity {
261+
continue;
262+
}
263+
entry.insert((
264+
channel.inbound_capacity_msat,
265+
route_hint_from_channel(&channel, short_channel_id, forwarding_info),
266+
));
267+
}
268+
hash_map::Entry::Vacant(entry) => {
269+
entry.insert((
270+
channel.inbound_capacity_msat,
271+
route_hint_from_channel(&channel, short_channel_id, forwarding_info),
272+
));
273+
}
274+
}
275+
}
276+
277+
// If all channels are private, return the route hint for the highest inbound capacity channel
278+
// per counterparty node. If channels with an higher inbound capacity than the
279+
// min_inbound_capacity exists, filter out the channels with a lower capacity than that.
280+
filtered_channels.into_iter()
281+
.filter(|(_channel, (capacity, _route_hint))| {
282+
min_capacity_channel_exists && capacity >= &min_inbound_capacity ||
283+
!min_capacity_channel_exists
284+
})
285+
.map(|(_channel, (_max_capacity, route_hint))| route_hint)
286+
.collect::<Vec<RouteHint>>()
287+
}
288+
224289
/// A [`Router`] implemented using [`find_route`].
225290
pub struct DefaultRouter<G: Deref<Target = NetworkGraph>, L: Deref> where L::Target: Logger {
226291
network_graph: G,
@@ -308,6 +373,7 @@ mod test {
308373
use lightning::util::enforcing_trait_impls::EnforcingSigner;
309374
use lightning::util::events::{MessageSendEvent, MessageSendEventsProvider, Event};
310375
use lightning::util::test_utils;
376+
use lightning::util::config::UserConfig;
311377
use lightning::chain::keysinterface::KeysInterface;
312378
use utils::create_invoice_from_channelmanager_and_duration_since_epoch;
313379

@@ -317,7 +383,9 @@ mod test {
317383
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
318384
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
319385
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
320-
let _chan = create_announced_chan_between_nodes(&nodes, 0, 1, InitFeatures::known(), InitFeatures::known());
386+
let mut private_chan_cfg = UserConfig::default();
387+
private_chan_cfg.channel_options.announced_channel = false;
388+
create_unannounced_chan_between_nodes_with_value(&nodes, 0, 1, 100000, 10001, InitFeatures::known(), InitFeatures::known());
321389
let invoice = create_invoice_from_channelmanager_and_duration_since_epoch(
322390
&nodes[1].node, nodes[1].keys_manager, Currency::BitcoinTestnet, Some(10_000), "test".to_string(),
323391
Duration::from_secs(1234567)).unwrap();

0 commit comments

Comments
 (0)