From 26288e301440565b433958bf9bac89f019ed35f2 Mon Sep 17 00:00:00 2001 From: Matt Corallo Date: Fri, 1 Apr 2022 01:36:38 +0000 Subject: [PATCH] Expose outbound SCID alias in `ChannelDetails` and use in routing This supports routing outbound over 0-conf channels by utilizing the outbound SCID alias that we assign to all channels to refer to the selected channel when routing. --- fuzz/src/router.rs | 1 + lightning/src/ln/channelmanager.rs | 28 +++++++++++++++++++++++ lightning/src/ln/priv_short_conf_tests.rs | 2 ++ lightning/src/routing/router.rs | 8 ++++--- 4 files changed, 36 insertions(+), 3 deletions(-) diff --git a/fuzz/src/router.rs b/fuzz/src/router.rs index 786bfa3e5..80ea1f1bc 100644 --- a/fuzz/src/router.rs +++ b/fuzz/src/router.rs @@ -222,6 +222,7 @@ pub fn do_test(data: &[u8], out: Out) { channel_type: None, short_channel_id: Some(scid), inbound_scid_alias: None, + outbound_scid_alias: None, channel_value_satoshis: capacity, user_channel_id: 0, inbound_capacity_msat: 0, unspendable_punishment_reserve: None, diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 8193ba443..8053f9c32 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -968,9 +968,25 @@ pub struct ChannelDetails { /// Note that if [`inbound_scid_alias`] is set, it must be used for invoices and inbound /// payments instead of this. See [`get_inbound_payment_scid`]. /// + /// For channels with [`confirmations_required`] set to `Some(0)`, [`outbound_scid_alias`] may + /// be used in place of this in outbound routes. See [`get_outbound_payment_scid`]. + /// /// [`inbound_scid_alias`]: Self::inbound_scid_alias + /// [`outbound_scid_alias`]: Self::outbound_scid_alias /// [`get_inbound_payment_scid`]: Self::get_inbound_payment_scid + /// [`get_outbound_payment_scid`]: Self::get_outbound_payment_scid + /// [`confirmations_required`]: Self::confirmations_required pub short_channel_id: Option, + /// An optional [`short_channel_id`] alias for this channel, randomly generated by us and + /// usable in place of [`short_channel_id`] to reference the channel in outbound routes when + /// the channel has not yet been confirmed (as long as [`confirmations_required`] is + /// `Some(0)`). + /// + /// This will be `None` as long as the channel is not available for routing outbound payments. + /// + /// [`short_channel_id`]: Self::short_channel_id + /// [`confirmations_required`]: Self::confirmations_required + pub outbound_scid_alias: Option, /// An optional [`short_channel_id`] alias for this channel, randomly generated by our /// counterparty and usable in place of [`short_channel_id`] in invoice route hints. Our /// counterparty will recognize the alias provided here in place of the [`short_channel_id`] @@ -1088,6 +1104,16 @@ impl ChannelDetails { pub fn get_inbound_payment_scid(&self) -> Option { self.inbound_scid_alias.or(self.short_channel_id) } + + /// Gets the current SCID which should be used to identify this channel for outbound payments. + /// This should be used in [`Route`]s to describe the first hop or in other contexts where + /// we're sending or forwarding a payment outbound over this channel. + /// + /// This is either the [`ChannelDetails::short_channel_id`], if set, or the + /// [`ChannelDetails::outbound_scid_alias`]. See those for more information. + pub fn get_outbound_payment_scid(&self) -> Option { + self.short_channel_id.or(self.outbound_scid_alias) + } } /// If a payment fails to send, it can be in one of several states. This enum is returned as the @@ -1714,6 +1740,7 @@ impl ChannelMana // `have_received_message` indicates that type negotiation has completed. channel_type: if channel.have_received_message() { Some(channel.get_channel_type().clone()) } else { None }, short_channel_id: channel.get_short_channel_id(), + outbound_scid_alias: if channel.is_usable() { Some(channel.outbound_scid_alias()) } else { None }, inbound_scid_alias: channel.latest_inbound_scid_alias(), channel_value_satoshis: channel.get_value_satoshis(), unspendable_punishment_reserve: to_self_reserve_satoshis, @@ -5984,6 +6011,7 @@ impl_writeable_tlv_based!(ChannelDetails, { (2, channel_id, required), (3, channel_type, option), (4, counterparty, required), + (5, outbound_scid_alias, option), (6, funding_txo, option), (8, short_channel_id, option), (10, channel_value_satoshis, required), diff --git a/lightning/src/ln/priv_short_conf_tests.rs b/lightning/src/ln/priv_short_conf_tests.rs index ed11b993f..24a0e4787 100644 --- a/lightning/src/ln/priv_short_conf_tests.rs +++ b/lightning/src/ln/priv_short_conf_tests.rs @@ -639,4 +639,6 @@ fn test_simple_0conf_channel() { assert_eq!(nodes[0].node.list_usable_channels().len(), 1); assert_eq!(nodes[1].node.list_usable_channels().len(), 1); + + send_payment(&nodes[0], &[&nodes[1]], 100_000); } diff --git a/lightning/src/routing/router.rs b/lightning/src/routing/router.rs index dc094b9b6..c02da4d70 100644 --- a/lightning/src/routing/router.rs +++ b/lightning/src/routing/router.rs @@ -368,7 +368,7 @@ enum CandidateRouteHop<'a> { impl<'a> CandidateRouteHop<'a> { fn short_channel_id(&self) -> u64 { match self { - CandidateRouteHop::FirstHop { details } => details.short_channel_id.unwrap(), + CandidateRouteHop::FirstHop { details } => details.get_outbound_payment_scid().unwrap(), CandidateRouteHop::PublicHop { short_channel_id, .. } => *short_channel_id, CandidateRouteHop::PrivateHop { hint } => hint.short_channel_id, } @@ -777,7 +777,7 @@ where L::Target: Logger { HashMap::with_capacity(if first_hops.is_some() { first_hops.as_ref().unwrap().len() } else { 0 }); if let Some(hops) = first_hops { for chan in hops { - if chan.short_channel_id.is_none() { + if chan.get_outbound_payment_scid().is_none() { panic!("first_hops should be filled in with usable channels, not pending ones"); } if chan.counterparty.node_id == *our_node_pubkey { @@ -1331,7 +1331,7 @@ where L::Target: Logger { let mut features_set = false; if let Some(first_channels) = first_hop_targets.get(&ordered_hops.last().unwrap().0.node_id) { for details in first_channels { - if details.short_channel_id.unwrap() == ordered_hops.last().unwrap().0.candidate.short_channel_id() { + if details.get_outbound_payment_scid().unwrap() == ordered_hops.last().unwrap().0.candidate.short_channel_id() { ordered_hops.last_mut().unwrap().1 = details.counterparty.features.to_context(); features_set = true; break; @@ -1742,6 +1742,7 @@ mod tests { funding_txo: Some(OutPoint { txid: bitcoin::Txid::from_slice(&[0; 32]).unwrap(), index: 0 }), channel_type: None, short_channel_id, + outbound_scid_alias: None, inbound_scid_alias: None, channel_value_satoshis: 0, user_channel_id: 0, @@ -5474,6 +5475,7 @@ mod benches { channel_type: None, short_channel_id: Some(1), inbound_scid_alias: None, + outbound_scid_alias: None, channel_value_satoshis: 10_000_000, user_channel_id: 0, balance_msat: 10_000_000, -- 2.39.5