Skip to content

Commit 1058810

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 1058810

File tree

1 file changed

+88
-26
lines changed

1 file changed

+88
-26
lines changed

lightning-invoice/src/utils.rs

Lines changed: 88 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, CounterpartyForwardingInfo, PaymentId, PaymentSendFailure, 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,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,91 @@ where
221198
}
222199
}
223200

201+
/// Filters channels for an invoice, and returns the corresponding route hints to include
202+
/// in the invoice.
203+
///
204+
/// The filtering ensures that the `RouteHints` returned only include the highest inbound capacity
205+
/// channel per counterparty node. If any channel with a higher inbound capacity than the given
206+
/// `min_inbound_capacity_msat` exists, channels to other counterparty nodes with a lower inbound
207+
/// capacity than `min_inbound_capacity_msat` will be filtered out, even if they are the highest
208+
/// inbound capacity channel for that specific counterparty node.
209+
/// If any public channel exists, the function returns no RouteHints, and the sender will need to
210+
/// look at the public channels to find a path instead.
211+
///
212+
/// Input:
213+
/// `channels`: The channels to filter.
214+
/// `min_inbound_capacity_msat`: Defines the lowest inbound capacity channels must have to not
215+
/// be filtered out, if any other channel above that amount exists.
216+
///
217+
/// Result:
218+
/// `Vec<RouteHint>`: The filtered `RouteHints`, which will be empty if any public channel exists.
219+
fn filter_channels(channels: Vec<ChannelDetails>, min_inbound_capacity_msat: Option<u64>) -> Vec<RouteHint>{
220+
let mut filtered_channels: HashMap<PublicKey, (u64, RouteHint)> = HashMap::new();
221+
let min_inbound_capacity = min_inbound_capacity_msat.unwrap_or(0);
222+
let mut min_capacity_channel_exists = false;
223+
224+
let route_hint_from_channel = |channel: &ChannelDetails, short_channel_id: u64, forwarding_info: &CounterpartyForwardingInfo| {
225+
RouteHint(vec![RouteHintHop {
226+
src_node_id: channel.counterparty.node_id,
227+
short_channel_id,
228+
fees: RoutingFees {
229+
base_msat: forwarding_info.fee_base_msat,
230+
proportional_millionths: forwarding_info.fee_proportional_millionths,
231+
},
232+
cltv_expiry_delta: forwarding_info.cltv_expiry_delta,
233+
htlc_minimum_msat: None,
234+
htlc_maximum_msat: None,}])
235+
};
236+
for channel in channels {
237+
let short_channel_id = match channel.get_inbound_payment_scid() {
238+
Some(id) => id,
239+
None => continue,
240+
};
241+
let forwarding_info = match &channel.counterparty.forwarding_info {
242+
Some(info) => info,
243+
None => continue,
244+
};
245+
if channel.is_public {
246+
// If any public channel exists, return no hints and let the sender
247+
// look at the public channels instead.
248+
return vec![]
249+
}
250+
251+
if channel.inbound_capacity_msat >= min_inbound_capacity {
252+
min_capacity_channel_exists = true;
253+
};
254+
match filtered_channels.entry(channel.counterparty.node_id) {
255+
hash_map::Entry::Occupied(mut entry) => {
256+
let current_max_capacity = entry.get().0;
257+
if channel.inbound_capacity_msat < current_max_capacity {
258+
continue;
259+
}
260+
entry.insert((
261+
channel.inbound_capacity_msat,
262+
route_hint_from_channel(&channel, short_channel_id, forwarding_info),
263+
));
264+
}
265+
hash_map::Entry::Vacant(entry) => {
266+
entry.insert((
267+
channel.inbound_capacity_msat,
268+
route_hint_from_channel(&channel, short_channel_id, forwarding_info),
269+
));
270+
}
271+
}
272+
}
273+
274+
// If all channels are private, return the route hint for the highest inbound capacity channel
275+
// per counterparty node. If channels with an higher inbound capacity than the
276+
// min_inbound_capacity exists, filter out the channels with a lower capacity than that.
277+
filtered_channels.into_iter()
278+
.filter(|(_channel, (capacity, _route_hint))| {
279+
min_capacity_channel_exists && capacity >= &min_inbound_capacity ||
280+
!min_capacity_channel_exists
281+
})
282+
.map(|(_channel, (_max_capacity, route_hint))| route_hint)
283+
.collect::<Vec<RouteHint>>()
284+
}
285+
224286
/// A [`Router`] implemented using [`find_route`].
225287
pub struct DefaultRouter<G: Deref<Target = NetworkGraph>, L: Deref> where L::Target: Logger {
226288
network_graph: G,
@@ -317,7 +379,7 @@ mod test {
317379
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
318380
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
319381
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
320-
let _chan = create_announced_chan_between_nodes(&nodes, 0, 1, InitFeatures::known(), InitFeatures::known());
382+
create_unannounced_chan_between_nodes_with_value(&nodes, 0, 1, 100000, 10001, InitFeatures::known(), InitFeatures::known());
321383
let invoice = create_invoice_from_channelmanager_and_duration_since_epoch(
322384
&nodes[1].node, nodes[1].keys_manager, Currency::BitcoinTestnet, Some(10_000), "test".to_string(),
323385
Duration::from_secs(1234567)).unwrap();

0 commit comments

Comments
 (0)