Skip to content

Commit c0f13dd

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 amt * If any private channel exists, the invoice rout_hints should be empty
1 parent e43cfe1 commit c0f13dd

File tree

1 file changed

+88
-17
lines changed

1 file changed

+88
-17
lines changed

lightning-invoice/src/utils.rs

Lines changed: 88 additions & 17 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, PhantomRouteHints, MIN_FINAL_CLTV_EXPIRY, MIN_CLTV_EXPIRY_DELTA};
13+
use lightning::ln::channelmanager::{ChannelDetails, ChannelManager, PaymentId, PaymentSendFailure, PhantomRouteHints, CounterpartyForwardingInfo, MIN_FINAL_CLTV_EXPIRY, MIN_CLTV_EXPIRY_DELTA};
1414
use lightning::ln::msgs::LightningError;
1515
use lightning::routing::scoring::Score;
1616
use lightning::routing::network_graph::{NetworkGraph, RoutingFees};
@@ -158,19 +158,9 @@ where
158158
F::Target: FeeEstimator,
159159
L::Target: Logger,
160160
{
161-
// Marshall route hints.
162-
let our_channels = channelmanager.list_usable_channels();
163-
let mut route_hints = vec![];
164-
for channel in our_channels {
165-
let short_channel_id = match channel.short_channel_id {
166-
Some(id) => id,
167-
None => continue,
168-
};
169-
let forwarding_info = match channel.counterparty.forwarding_info {
170-
Some(info) => info,
171-
None => continue,
172-
};
173-
route_hints.push(RouteHint(vec![RouteHintHop {
161+
let channel_pubkey_selector = |channel: ChannelDetails| channel.counterparty.node_id;
162+
let route_hint_from_channel = |channel: ChannelDetails, short_channel_id: u64, forwarding_info: CounterpartyForwardingInfo| {
163+
RouteHint(vec![RouteHintHop {
174164
src_node_id: channel.counterparty.node_id,
175165
short_channel_id,
176166
fees: RoutingFees {
@@ -179,9 +169,15 @@ where
179169
},
180170
cltv_expiry_delta: forwarding_info.cltv_expiry_delta,
181171
htlc_minimum_msat: None,
182-
htlc_maximum_msat: None,
183-
}]));
184-
}
172+
htlc_maximum_msat: None,}])
173+
};
174+
let route_hints = filter_channels(
175+
channelmanager.list_usable_channels(),
176+
amt_msat,
177+
true, // If any of our channels are private, the sender should look at our public channels instead
178+
channel_pubkey_selector,
179+
route_hint_from_channel
180+
);
185181

186182
// `create_inbound_payment` only returns an error if the amount is greater than the total bitcoin
187183
// supply.
@@ -218,6 +214,81 @@ where
218214
}
219215
}
220216

217+
/// Utility to filter channels for an invoice, and return the corresponding route hints to include
218+
/// in the invoice.
219+
///
220+
/// The filtering ensures that the RouteHints returned only include the highest inbound capacity
221+
/// channel for the pubkey returned by the map_channels_to_pubkey. Channels with a lower inbound
222+
/// capacity than the invoice amount aren't included in the hints.
223+
///
224+
/// `channels`: The channels to filter.
225+
/// `min_inbound_capacity_required`: Defines the lowest inbound capacity a channel must have to not
226+
/// be filtered out.
227+
/// `empty_result_if_private_channels_exists`: If set to `true`, this entire function will return
228+
/// zero route hints if any of the inputed channels is a private channel.
229+
/// `channel_pubkey_selector`: Function that is called for every channel evaluated in the filtering
230+
/// that the required fields and has an high enough inbound_capacity. The route hint for the
231+
/// highest inbound capacity channel will be returned for each unique `PublicKey` returned by
232+
/// `channel_pubkey_selector`. The `ChannelDetails` parameter in `channel_pubkey_selector`, is the
233+
/// current channel evaluated for filtering.
234+
/// `route_hint_from_channel`: Mapping function that defines how a channel should be mapped into a
235+
/// `RouteHint`. The `ChannelDetails` parameter in `channel_pubkey_selector`, is the current channel evaluated
236+
/// for filtering. The `u64` parameter is the channel's `short_channel_id`.
237+
fn filter_channels<F1, F2>(
238+
channels: Vec<ChannelDetails>,
239+
min_inbound_capacity_required: Option<u64>,
240+
empty_result_if_private_channels_exists: bool,
241+
channel_pubkey_selector: F1,
242+
route_hint_from_channel: F2
243+
) -> Vec<RouteHint> where
244+
F1: Fn(ChannelDetails) -> PublicKey,
245+
F2: Fn(ChannelDetails, u64, CounterpartyForwardingInfo) -> RouteHint
246+
{
247+
let mut filtered_channels: HashMap<PublicKey, (u64, RouteHint)> = HashMap::new();
248+
let min_capacity_required = match min_inbound_capacity_required {
249+
Some(amt) => amt,
250+
None => 0,
251+
};
252+
for channel in channels {
253+
if empty_result_if_private_channels_exists && !channel.is_public {
254+
filtered_channels.clear();
255+
break;
256+
}
257+
let short_channel_id = match channel.clone().short_channel_id {
258+
Some(id) => id,
259+
None => continue,
260+
};
261+
let forwarding_info = match channel.clone().counterparty.forwarding_info {
262+
Some(info) => info,
263+
None => continue,
264+
};
265+
if channel.inbound_capacity_msat < min_capacity_required {
266+
continue;
267+
};
268+
match filtered_channels.entry(channel_pubkey_selector(channel.clone())) {
269+
hash_map::Entry::Occupied(mut entry) => {
270+
let current_max_capacity = entry.get().0;
271+
if channel.inbound_capacity_msat < current_max_capacity {
272+
continue;
273+
}
274+
entry.insert((
275+
channel.inbound_capacity_msat,
276+
route_hint_from_channel(channel, short_channel_id, forwarding_info),
277+
));
278+
}
279+
hash_map::Entry::Vacant(entry) => {
280+
entry.insert((
281+
channel.inbound_capacity_msat,
282+
route_hint_from_channel(channel, short_channel_id, forwarding_info),
283+
));
284+
}
285+
}
286+
}
287+
filtered_channels.iter()
288+
.map(|(_channel, (_max_capacity, route_hint))| route_hint.clone())
289+
.collect::<Vec<RouteHint>>()
290+
}
291+
221292
/// A [`Router`] implemented using [`find_route`].
222293
pub struct DefaultRouter<G: Deref<Target = NetworkGraph>, L: Deref> where L::Target: Logger {
223294
network_graph: G,

0 commit comments

Comments
 (0)