diff --git a/lightning/src/routing/gossip.rs b/lightning/src/routing/gossip.rs index 9b4e41ae17..0df62e0f61 100644 --- a/lightning/src/routing/gossip.rs +++ b/lightning/src/routing/gossip.rs @@ -1026,7 +1026,7 @@ impl<'a> DirectedChannelInfo<'a> { /// Returns information for the direction. #[inline] - pub(super) fn direction(&self) -> &'a ChannelUpdateInfo { self.direction } + pub(crate) fn direction(&self) -> &'a ChannelUpdateInfo { self.direction } /// Returns the `node_id` of the source hop. /// diff --git a/lightning/src/routing/router.rs b/lightning/src/routing/router.rs index a6da561c5b..16bc5f0ed9 100644 --- a/lightning/src/routing/router.rs +++ b/lightning/src/routing/router.rs @@ -90,6 +90,8 @@ impl> + Clone, L: Deref, S: Deref, SP: Sized, &self, recipient: PublicKey, first_hops: Vec, tlvs: ReceiveTlvs, amount_msats: u64, entropy_source: &ES, secp_ctx: &Secp256k1 ) -> Result, ()> { + let recipient_node_id = NodeId::from_pubkey(&recipient); + // Limit the number of blinded paths that are computed. const MAX_PAYMENT_PATHS: usize = 3; @@ -98,37 +100,82 @@ impl> + Clone, L: Deref, S: Deref, SP: Sized, const MIN_PEER_CHANNELS: usize = 3; let network_graph = self.network_graph.deref().read_only(); - first_hops.into_iter() - .filter(|details| details.is_public) + first_hops.iter() .filter(|details| details.counterparty.features.supports_route_blinding()) .filter(|details| amount_msats <= details.inbound_capacity_msat) .filter(|details| amount_msats >= details.inbound_htlc_minimum_msat.unwrap_or(0)) .filter(|details| amount_msats <= details.inbound_htlc_maximum_msat.unwrap_or(0)) - .filter(|details| network_graph - .node(&NodeId::from_pubkey(&details.counterparty.node_id)) - .map(|node_info| node_info.channels.len() >= MIN_PEER_CHANNELS) - .unwrap_or(false) + .map(|details| (details, NodeId::from_pubkey(&details.counterparty.node_id))) + // Limit to counterparties with announced channels + .filter_map(|(details, counterparty_node_id)| + network_graph + .node(&counterparty_node_id) + .map(|info| &info.channels[..]) + .and_then(|channels| (channels.len() >= MIN_PEER_CHANNELS).then(|| channels)) + .map(|channels| (details, counterparty_node_id, channels)) + ) + // Pair counterparties with their other channels + .flat_map(|(details, counterparty_node_id, counterparty_channels)| + counterparty_channels + .iter() + .filter_map(|scid| network_graph.channels().get_key_value(scid)) + .filter_map(move |(scid, info)| info + .as_directed_to(&counterparty_node_id) + .map(|(info, source)| (source, *scid, info)) + ) + .filter(|(source, _, _)| **source != recipient_node_id) + .filter(|(_, _, info)| amount_msats >= info.direction().htlc_minimum_msat) + .filter(|(_, _, info)| amount_msats <= info.direction().htlc_maximum_msat) + .map(move |(source, scid, info)| (source, scid, info, details)) ) - .map(|details| { - let short_channel_id = details.get_inbound_payment_scid().unwrap(); - let payment_relay: PaymentRelay = details.counterparty.forwarding_info.unwrap().into(); - let payment_constraints = PaymentConstraints { - max_cltv_expiry: tlvs.payment_constraints.max_cltv_expiry - + payment_relay.cltv_expiry_delta as u32, - htlc_minimum_msat: details.inbound_htlc_minimum_msat.unwrap_or(0), + // Construct blinded paths where the counterparty's counterparty is the introduction + // node: + // + // source --- info ---> counterparty --- details ---> recipient + .map(|(introduction_node_id, scid, info, details)| { + let counterparty_forward_node = { + let short_channel_id = details.get_inbound_payment_scid().unwrap(); + let payment_relay: PaymentRelay = + details.counterparty.forwarding_info.clone().unwrap().into(); + let payment_constraints = PaymentConstraints { + max_cltv_expiry: payment_relay.cltv_expiry_delta as u32 + + tlvs.payment_constraints.max_cltv_expiry, + htlc_minimum_msat: details.inbound_htlc_minimum_msat.unwrap_or(0), + }; + ForwardNode { + tlvs: ForwardTlvs { + short_channel_id, + payment_relay, + payment_constraints, + features: BlindedHopFeatures::empty(), + }, + node_id: details.counterparty.node_id, + htlc_maximum_msat: details.inbound_htlc_maximum_msat.unwrap_or(0), + } }; - let forward_node = ForwardNode { - tlvs: ForwardTlvs { - short_channel_id, - payment_relay, - payment_constraints, - features: BlindedHopFeatures::empty(), - }, - node_id: details.counterparty.node_id, - htlc_maximum_msat: details.inbound_htlc_maximum_msat.unwrap_or(0), + let introduction_forward_node = { + let htlc_minimum_msat = info.direction().htlc_minimum_msat; + let htlc_maximum_msat = info.direction().htlc_maximum_msat; + let payment_relay: PaymentRelay = info.into(); + let payment_constraints = PaymentConstraints { + max_cltv_expiry: payment_relay.cltv_expiry_delta as u32 + + counterparty_forward_node.tlvs.payment_constraints.max_cltv_expiry, + htlc_minimum_msat, + }; + ForwardNode { + tlvs: ForwardTlvs { + short_channel_id: scid, + payment_relay, + payment_constraints, + features: BlindedHopFeatures::empty(), + }, + node_id: introduction_node_id.as_pubkey().unwrap(), + htlc_maximum_msat, + } }; BlindedPath::new_for_payment( - &[forward_node], recipient, tlvs.clone(), u64::MAX, entropy_source, secp_ctx + &[introduction_forward_node, counterparty_forward_node], recipient, + tlvs.clone(), u64::MAX, entropy_source, secp_ctx ) }) .take(MAX_PAYMENT_PATHS)