X-Git-Url: http://git.bitcoin.ninja/index.cgi?a=blobdiff_plain;ds=inline;f=lightning-invoice%2Fsrc%2Futils.rs;h=d8e7bf12726454cfec9ac0134e7f3ab9fd6a1406;hb=ec67ee7020487c9b5fe7c9fb67af3bafdbd3de20;hp=d9e1847f1a735442a751e35cda6b34cc6d8c1fca;hpb=31807bc2f656aa4996b701d39eca12f0cc912f57;p=rust-lightning diff --git a/lightning-invoice/src/utils.rs b/lightning-invoice/src/utils.rs index d9e1847f..d8e7bf12 100644 --- a/lightning-invoice/src/utils.rs +++ b/lightning-invoice/src/utils.rs @@ -133,6 +133,8 @@ where ) } +const MAX_CHANNEL_HINTS: usize = 3; + fn _create_phantom_invoice( amt_msat: Option, payment_hash: Option, description: InvoiceDescription, invoice_expiry_delta_secs: u32, phantom_route_hints: Vec, entropy_source: ES, @@ -203,7 +205,8 @@ where invoice = invoice.amount_milli_satoshis(amt); } - for route_hint in select_phantom_hints(amt_msat, phantom_route_hints, logger) { + + for route_hint in select_phantom_hints(amt_msat, phantom_route_hints, logger).take(MAX_CHANNEL_HINTS) { invoice = invoice.private_route(route_hint); } @@ -230,36 +233,48 @@ where /// /// [`PhantomKeysManager`]: lightning::sign::PhantomKeysManager fn select_phantom_hints(amt_msat: Option, phantom_route_hints: Vec, - logger: L) -> Vec + logger: L) -> impl Iterator where L::Target: Logger, { - let mut phantom_hints: Vec> = Vec::new(); + let mut phantom_hints: Vec<_> = Vec::new(); for PhantomRouteHints { channels, phantom_scid, real_node_pubkey } in phantom_route_hints { log_trace!(logger, "Generating phantom route hints for node {}", log_pubkey!(real_node_pubkey)); - let mut route_hints = sort_and_filter_channels(channels, amt_msat, &logger); + let route_hints = sort_and_filter_channels(channels, amt_msat, &logger); // If we have any public channel, the route hints from `sort_and_filter_channels` will be // empty. In that case we create a RouteHint on which we will push a single hop with the // phantom route into the invoice, and let the sender find the path to the `real_node_pubkey` // node by looking at our public channels. - if route_hints.is_empty() { - route_hints.push(RouteHint(vec![])) - } - for route_hint in &mut route_hints { - route_hint.0.push(RouteHintHop { - src_node_id: real_node_pubkey, - short_channel_id: phantom_scid, - fees: RoutingFees { - base_msat: 0, - proportional_millionths: 0, - }, - cltv_expiry_delta: MIN_CLTV_EXPIRY_DELTA, - htlc_minimum_msat: None, - htlc_maximum_msat: None,}); - } + let empty_route_hints = route_hints.len() == 0; + let mut have_pushed_empty = false; + let route_hints = route_hints + .chain(core::iter::from_fn(move || { + if empty_route_hints && !have_pushed_empty { + // set flag of having handled the empty route_hints and ensure empty vector + // returned only once + have_pushed_empty = true; + Some(RouteHint(Vec::new())) + } else { + None + } + })) + .map(move |mut hint| { + hint.0.push(RouteHintHop { + src_node_id: real_node_pubkey, + short_channel_id: phantom_scid, + fees: RoutingFees { + base_msat: 0, + proportional_millionths: 0, + }, + cltv_expiry_delta: MIN_CLTV_EXPIRY_DELTA, + htlc_minimum_msat: None, + htlc_maximum_msat: None, + }); + hint + }); phantom_hints.push(route_hints); } @@ -268,29 +283,7 @@ where // the hints across our real nodes we add one hint from each in turn until no node has any hints // left (if one node has more hints than any other, these will accumulate at the end of the // vector). - let mut invoice_hints: Vec = Vec::new(); - let mut hint_idx = 0; - - loop { - let mut remaining_hints = false; - - for hints in phantom_hints.iter() { - if invoice_hints.len() == 3 { - return invoice_hints - } - - if hint_idx < hints.len() { - invoice_hints.push(hints[hint_idx].clone()); - remaining_hints = true - } - } - - if !remaining_hints { - return invoice_hints - } - - hint_idx +=1; - } + rotate_through_iterators(phantom_hints) } /// Draw items iteratively from multiple iterators. The items are retrieved by index and @@ -603,8 +596,13 @@ fn _create_invoice_from_channelmanager_and_duration_since_epoch_with_payment_has /// * Sorted by lowest inbound capacity if an online channel with the minimum amount requested exists, /// otherwise sort by highest inbound capacity to give the payment the best chance of succeeding. fn sort_and_filter_channels( - channels: Vec, min_inbound_capacity_msat: Option, logger: &L -) -> Vec where L::Target: Logger { + channels: Vec, + min_inbound_capacity_msat: Option, + logger: &L, +) -> impl ExactSizeIterator +where + L::Target: Logger, +{ let mut filtered_channels: HashMap = HashMap::new(); let min_inbound_capacity = min_inbound_capacity_msat.unwrap_or(0); let mut min_capacity_channel_exists = false; @@ -612,6 +610,20 @@ fn sort_and_filter_channels( let mut online_min_capacity_channel_exists = false; let mut has_pub_unconf_chan = false; + let route_hint_from_channel = |channel: ChannelDetails| { + let forwarding_info = channel.counterparty.forwarding_info.as_ref().unwrap(); + RouteHint(vec![RouteHintHop { + src_node_id: channel.counterparty.node_id, + short_channel_id: channel.get_inbound_payment_scid().unwrap(), + fees: RoutingFees { + base_msat: forwarding_info.fee_base_msat, + proportional_millionths: forwarding_info.fee_proportional_millionths, + }, + cltv_expiry_delta: forwarding_info.cltv_expiry_delta, + htlc_minimum_msat: channel.inbound_htlc_minimum_msat, + htlc_maximum_msat: channel.inbound_htlc_maximum_msat,}]) + }; + log_trace!(logger, "Considering {} channels for invoice route hints", channels.len()); for channel in channels.into_iter().filter(|chan| chan.is_channel_ready) { if channel.get_inbound_payment_scid().is_none() || channel.counterparty.forwarding_info.is_none() { @@ -630,7 +642,7 @@ fn sort_and_filter_channels( // look at the public channels instead. log_trace!(logger, "Not including channels in invoice route hints on account of public channel {}", log_bytes!(channel.channel_id)); - return vec![] + return vec![].into_iter().take(MAX_CHANNEL_HINTS).map(route_hint_from_channel); } } @@ -690,19 +702,6 @@ fn sort_and_filter_channels( } } - let route_hint_from_channel = |channel: ChannelDetails| { - let forwarding_info = channel.counterparty.forwarding_info.as_ref().unwrap(); - RouteHint(vec![RouteHintHop { - src_node_id: channel.counterparty.node_id, - short_channel_id: channel.get_inbound_payment_scid().unwrap(), - fees: RoutingFees { - base_msat: forwarding_info.fee_base_msat, - proportional_millionths: forwarding_info.fee_proportional_millionths, - }, - cltv_expiry_delta: forwarding_info.cltv_expiry_delta, - htlc_minimum_msat: channel.inbound_htlc_minimum_msat, - htlc_maximum_msat: channel.inbound_htlc_maximum_msat,}]) - }; // If all channels are private, prefer to return route hints which have a higher capacity than // the payment value and where we're currently connected to the channel counterparty. // Even if we cannot satisfy both goals, always ensure we include *some* hints, preferring @@ -752,7 +751,8 @@ fn sort_and_filter_channels( } else { b.inbound_capacity_msat.cmp(&a.inbound_capacity_msat) }}); - eligible_channels.into_iter().take(3).map(route_hint_from_channel).collect::>() + + eligible_channels.into_iter().take(MAX_CHANNEL_HINTS).map(route_hint_from_channel) } /// prefer_current_channel chooses a channel to use for route hints between a currently selected and candidate