Skip to content

Commit 5cacbd6

Browse files
committed
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.
1 parent 5502204 commit 5cacbd6

File tree

4 files changed

+36
-3
lines changed

4 files changed

+36
-3
lines changed

fuzz/src/router.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,7 @@ pub fn do_test<Out: test_logger::Output>(data: &[u8], out: Out) {
222222
channel_type: None,
223223
short_channel_id: Some(scid),
224224
inbound_scid_alias: None,
225+
outbound_scid_alias: None,
225226
channel_value_satoshis: capacity,
226227
user_channel_id: 0, inbound_capacity_msat: 0,
227228
unspendable_punishment_reserve: None,

lightning/src/ln/channelmanager.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -966,9 +966,25 @@ pub struct ChannelDetails {
966966
/// Note that if [`inbound_scid_alias`] is set, it must be used for invoices and inbound
967967
/// payments instead of this. See [`get_inbound_payment_scid`].
968968
///
969+
/// For channels with [`confirmations_required`] set to `Some(0)`, [`outbound_scid_alias`] may
970+
/// be used in place of this in outbound routes. See [`get_outbound_payment_scid`].
971+
///
969972
/// [`inbound_scid_alias`]: Self::inbound_scid_alias
973+
/// [`outbound_scid_alias`]: Self::outbound_scid_alias
970974
/// [`get_inbound_payment_scid`]: Self::get_inbound_payment_scid
975+
/// [`get_outbound_payment_scid`]: Self::get_outbound_payment_scid
976+
/// [`confirmations_required`]: Self::confirmations_required
971977
pub short_channel_id: Option<u64>,
978+
/// An optional [`short_channel_id`] alias for this channel, randomly generated by us and
979+
/// usable in place of [`short_channel_id`] to reference the channel in outbound routes when
980+
/// the channel has not yet been confirmed (as long as [`confirmations_required`] is
981+
/// `Some(0)`).
982+
///
983+
/// This will be `None` as long as the channel is not available for routing outbound payments.
984+
///
985+
/// [`short_channel_id`]: Self::short_channel_id
986+
/// [`confirmations_required`]: Self::confirmations_required
987+
pub outbound_scid_alias: Option<u64>,
972988
/// An optional [`short_channel_id`] alias for this channel, randomly generated by our
973989
/// counterparty and usable in place of [`short_channel_id`] in invoice route hints. Our
974990
/// counterparty will recognize the alias provided here in place of the [`short_channel_id`]
@@ -1086,6 +1102,16 @@ impl ChannelDetails {
10861102
pub fn get_inbound_payment_scid(&self) -> Option<u64> {
10871103
self.inbound_scid_alias.or(self.short_channel_id)
10881104
}
1105+
1106+
/// Gets the current SCID which should be used to identify this channel for outbound payments.
1107+
/// This should be used in [`Route`]s to describe the first hop or in other contexts where
1108+
/// we're sending or forwarding a payment outbound over this channel.
1109+
///
1110+
/// This is either the [`ChannelDetails::short_channel_id`], if set, or the
1111+
/// [`ChannelDetails::outbound_scid_alias`]. See those for more information.
1112+
pub fn get_outbound_payment_scid(&self) -> Option<u64> {
1113+
self.short_channel_id.or(self.outbound_scid_alias)
1114+
}
10891115
}
10901116

10911117
/// If a payment fails to send, it can be in one of several states. This enum is returned as the
@@ -1712,6 +1738,7 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
17121738
// `have_received_message` indicates that type negotiation has completed.
17131739
channel_type: if channel.have_received_message() { Some(channel.get_channel_type().clone()) } else { None },
17141740
short_channel_id: channel.get_short_channel_id(),
1741+
outbound_scid_alias: if channel.is_usable() { Some(channel.outbound_scid_alias()) } else { None },
17151742
inbound_scid_alias: channel.latest_inbound_scid_alias(),
17161743
channel_value_satoshis: channel.get_value_satoshis(),
17171744
unspendable_punishment_reserve: to_self_reserve_satoshis,
@@ -5982,6 +6009,7 @@ impl_writeable_tlv_based!(ChannelDetails, {
59826009
(2, channel_id, required),
59836010
(3, channel_type, option),
59846011
(4, counterparty, required),
6012+
(5, outbound_scid_alias, option),
59856013
(6, funding_txo, option),
59866014
(8, short_channel_id, option),
59876015
(10, channel_value_satoshis, required),

lightning/src/ln/priv_short_conf_tests.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -639,4 +639,6 @@ fn test_simple_0conf_channel() {
639639

640640
assert_eq!(nodes[0].node.list_usable_channels().len(), 1);
641641
assert_eq!(nodes[1].node.list_usable_channels().len(), 1);
642+
643+
send_payment(&nodes[0], &[&nodes[1]], 100_000);
642644
}

lightning/src/routing/router.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -368,7 +368,7 @@ enum CandidateRouteHop<'a> {
368368
impl<'a> CandidateRouteHop<'a> {
369369
fn short_channel_id(&self) -> u64 {
370370
match self {
371-
CandidateRouteHop::FirstHop { details } => details.short_channel_id.unwrap(),
371+
CandidateRouteHop::FirstHop { details } => details.get_outbound_payment_scid().unwrap(),
372372
CandidateRouteHop::PublicHop { short_channel_id, .. } => *short_channel_id,
373373
CandidateRouteHop::PrivateHop { hint } => hint.short_channel_id,
374374
}
@@ -777,7 +777,7 @@ where L::Target: Logger {
777777
HashMap::with_capacity(if first_hops.is_some() { first_hops.as_ref().unwrap().len() } else { 0 });
778778
if let Some(hops) = first_hops {
779779
for chan in hops {
780-
if chan.short_channel_id.is_none() {
780+
if chan.get_outbound_payment_scid().is_none() {
781781
panic!("first_hops should be filled in with usable channels, not pending ones");
782782
}
783783
if chan.counterparty.node_id == *our_node_pubkey {
@@ -1331,7 +1331,7 @@ where L::Target: Logger {
13311331
let mut features_set = false;
13321332
if let Some(first_channels) = first_hop_targets.get(&ordered_hops.last().unwrap().0.node_id) {
13331333
for details in first_channels {
1334-
if details.short_channel_id.unwrap() == ordered_hops.last().unwrap().0.candidate.short_channel_id() {
1334+
if details.get_outbound_payment_scid().unwrap() == ordered_hops.last().unwrap().0.candidate.short_channel_id() {
13351335
ordered_hops.last_mut().unwrap().1 = details.counterparty.features.to_context();
13361336
features_set = true;
13371337
break;
@@ -1742,6 +1742,7 @@ mod tests {
17421742
funding_txo: Some(OutPoint { txid: bitcoin::Txid::from_slice(&[0; 32]).unwrap(), index: 0 }),
17431743
channel_type: None,
17441744
short_channel_id,
1745+
outbound_scid_alias: None,
17451746
inbound_scid_alias: None,
17461747
channel_value_satoshis: 0,
17471748
user_channel_id: 0,
@@ -5474,6 +5475,7 @@ mod benches {
54745475
channel_type: None,
54755476
short_channel_id: Some(1),
54765477
inbound_scid_alias: None,
5478+
outbound_scid_alias: None,
54775479
channel_value_satoshis: 10_000_000,
54785480
user_channel_id: 0,
54795481
balance_msat: 10_000_000,

0 commit comments

Comments
 (0)