Skip to content

Commit 081ebb5

Browse files
Store held htlcs in pending_intercepted_htlcs
As part of supporting sending payments as an often-offline sender, the sender's always-online channel counterparty needs to hold onto the sender's HTLC until they receive a release_held_htlc onion message from the often-offline recipient. Here we implement storing these held HTLCs in the existing ChannelManager::pending_intercepted_htlcs map. We want to move in the direction of obviating the need to persistence the ChannelManager entirely, so it doesn't really make sense to add a whole new map for these HTLCs.
1 parent 84f83fa commit 081ebb5

File tree

2 files changed

+101
-8
lines changed

2 files changed

+101
-8
lines changed

lightning/src/ln/channelmanager.rs

Lines changed: 100 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,9 @@ pub enum PendingHTLCRouting {
229229
blinded: Option<BlindedForward>,
230230
/// The absolute CLTV of the inbound HTLC
231231
incoming_cltv_expiry: Option<u32>,
232+
/// Whether this HTLC should be held by our node until we receive a corresponding
233+
/// [`ReleaseHeldHtlc`] onion message.
234+
hold_htlc: Option<()>,
232235
},
233236
/// An HTLC which should be forwarded on to another Trampoline node.
234237
TrampolineForward {
@@ -371,6 +374,15 @@ impl PendingHTLCRouting {
371374
Self::ReceiveKeysend { incoming_cltv_expiry, .. } => Some(*incoming_cltv_expiry),
372375
}
373376
}
377+
378+
/// Whether this HTLC should be held by our node until we receive a corresponding
379+
/// [`ReleaseHeldHtlc`] onion message.
380+
fn should_hold_htlc(&self) -> bool {
381+
match self {
382+
Self::Forward { hold_htlc: Some(()), .. } => true,
383+
_ => false,
384+
}
385+
}
374386
}
375387

376388
/// Information about an incoming HTLC, including the [`PendingHTLCRouting`] describing where it
@@ -638,9 +650,47 @@ impl Readable for PaymentId {
638650
/// An identifier used to uniquely identify an intercepted HTLC to LDK.
639651
///
640652
/// This is not exported to bindings users as we just use [u8; 32] directly
641-
#[derive(Hash, Copy, Clone, PartialEq, Eq, Debug)]
653+
#[derive(Hash, Copy, Clone, PartialEq, Eq)]
642654
pub struct InterceptId(pub [u8; 32]);
643655

656+
impl InterceptId {
657+
/// This intercept id corresponds to an HTLC that will be forwarded on
658+
/// [`ChannelManager::forward_intercepted_htlc`].
659+
fn from_incoming_shared_secret(ss: &[u8; 32]) -> Self {
660+
Self(Sha256::hash(ss).to_byte_array())
661+
}
662+
663+
/// This intercept id corresponds to an HTLC that will be forwarded on receipt of a
664+
/// [`ReleaseHeldHtlc`] onion message.
665+
fn from_htlc_id_and_chan_id(
666+
htlc_id: u64, chan_id: &ChannelId, counterparty_node_id: &PublicKey,
667+
) -> Self {
668+
let htlc_id_bytes = htlc_id.to_be_bytes();
669+
let cp_id_bytes = counterparty_node_id.serialize();
670+
671+
const RES_SIZE: usize = 8 + 32 + 33;
672+
debug_assert_eq!(RES_SIZE, htlc_id_bytes.len() + chan_id.0.len() + cp_id_bytes.len());
673+
674+
let mut res = [0u8; RES_SIZE];
675+
res[..htlc_id_bytes.len()].copy_from_slice(&htlc_id_bytes);
676+
res[htlc_id_bytes.len()..htlc_id_bytes.len() + chan_id.0.len()].copy_from_slice(&chan_id.0);
677+
res[htlc_id_bytes.len() + chan_id.0.len()..].copy_from_slice(&cp_id_bytes);
678+
679+
Self(Sha256::hash(&res[..]).to_byte_array())
680+
}
681+
}
682+
683+
impl Borrow<[u8]> for InterceptId {
684+
fn borrow(&self) -> &[u8] {
685+
&self.0[..]
686+
}
687+
}
688+
impl_fmt_traits! {
689+
impl fmt_traits for InterceptId {
690+
const LENGTH: usize = 32;
691+
}
692+
}
693+
644694
impl Writeable for InterceptId {
645695
fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
646696
self.0.write(w)
@@ -2598,8 +2648,14 @@ pub struct ChannelManager<
25982648
pub(super) forward_htlcs: Mutex<HashMap<u64, Vec<HTLCForwardInfo>>>,
25992649
#[cfg(not(test))]
26002650
forward_htlcs: Mutex<HashMap<u64, Vec<HTLCForwardInfo>>>,
2601-
/// Storage for HTLCs that have been intercepted and bubbled up to the user. We hold them here
2602-
/// until the user tells us what we should do with them.
2651+
/// Storage for HTLCs that have been intercepted.
2652+
///
2653+
/// These HTLCs fall into two categories:
2654+
/// 1. HTLCs that are bubbled up to the user and held until the invocation of
2655+
/// [`ChannelManager::forward_intercepted_htlc`] or [`ChannelManager::fail_intercepted_htlc`]
2656+
/// (or timeout)
2657+
/// 2. HTLCs that are being held on behalf of an often-offline sender until receipt of a
2658+
/// [`ReleaseHeldHtlc`] onion message from an often-offline recipient
26032659
///
26042660
/// See `ChannelManager` struct-level documentation for lock order requirements.
26052661
pending_intercepted_htlcs: Mutex<HashMap<InterceptId, PendingAddHTLCInfo>>,
@@ -6282,11 +6338,19 @@ where
62826338
})?;
62836339

62846340
let routing = match payment.forward_info.routing {
6285-
PendingHTLCRouting::Forward { onion_packet, blinded, incoming_cltv_expiry, .. } => {
6341+
PendingHTLCRouting::Forward {
6342+
onion_packet,
6343+
blinded,
6344+
incoming_cltv_expiry,
6345+
hold_htlc,
6346+
..
6347+
} => {
6348+
debug_assert!(hold_htlc.is_none(), "Held intercept HTLCs should not be surfaced in an event until the recipient comes online");
62866349
PendingHTLCRouting::Forward {
62876350
onion_packet,
62886351
blinded,
62896352
incoming_cltv_expiry,
6353+
hold_htlc,
62906354
short_channel_id: next_hop_scid,
62916355
}
62926356
},
@@ -10719,16 +10783,43 @@ This indicates a bug inside LDK. Please report this error at https://github.com/
1071910783
));
1072010784
};
1072110785

10722-
if !is_our_scid
10786+
// In the case that we have an HTLC that we're supposed to hold onto until the
10787+
// recipient comes online *and* the outbound scid is encoded as
10788+
// `fake_scid::is_valid_intercept`, we should first wait for the recipient to come
10789+
// online before generating an `HTLCIntercepted` event, since the event cannot be
10790+
// acted on until the recipient is online to cooperatively open the JIT channel. Once
10791+
// we receive the `ReleaseHeldHtlc` message from the recipient, we will circle back
10792+
// here and resume generating the event below.
10793+
if pending_add.forward_info.routing.should_hold_htlc() {
10794+
let intercept_id = InterceptId::from_htlc_id_and_chan_id(
10795+
prev_htlc_id,
10796+
&prev_channel_id,
10797+
&prev_counterparty_node_id,
10798+
);
10799+
let mut held_htlcs = self.pending_intercepted_htlcs.lock().unwrap();
10800+
match held_htlcs.entry(intercept_id) {
10801+
hash_map::Entry::Vacant(entry) => {
10802+
log_trace!(
10803+
logger,
10804+
"Intercepted held HTLC with id {}, holding until the recipient is online",
10805+
intercept_id
10806+
);
10807+
entry.insert(pending_add);
10808+
},
10809+
hash_map::Entry::Occupied(_) => {
10810+
debug_assert!(false, "Should never have two HTLCs with the same channel id and htlc id");
10811+
fail_intercepted_htlc(pending_add);
10812+
},
10813+
}
10814+
} else if !is_our_scid
1072310815
&& pending_add.forward_info.incoming_amt_msat.is_some()
1072410816
&& fake_scid::is_valid_intercept(
1072510817
&self.fake_scid_rand_bytes,
1072610818
scid,
1072710819
&self.chain_hash,
1072810820
) {
10729-
let intercept_id = InterceptId(
10730-
Sha256::hash(&pending_add.forward_info.incoming_shared_secret)
10731-
.to_byte_array(),
10821+
let intercept_id = InterceptId::from_incoming_shared_secret(
10822+
&pending_add.forward_info.incoming_shared_secret,
1073210823
);
1073310824
let mut pending_intercepts = self.pending_intercepted_htlcs.lock().unwrap();
1073410825
match pending_intercepts.entry(intercept_id) {
@@ -14905,6 +14996,7 @@ impl_writeable_tlv_based_enum!(PendingHTLCRouting,
1490514996
(1, blinded, option),
1490614997
(2, short_channel_id, required),
1490714998
(3, incoming_cltv_expiry, option),
14999+
(4, hold_htlc, option),
1490815000
},
1490915001
(1, Receive) => {
1491015002
(0, payment_data, required),

lightning/src/ln/onion_payment.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,7 @@ pub(super) fn create_fwd_pending_htlc_info(
190190
onion_packet: outgoing_packet,
191191
short_channel_id,
192192
incoming_cltv_expiry: Some(msg.cltv_expiry),
193+
hold_htlc: msg.hold_htlc,
193194
blinded: intro_node_blinding_point.or(msg.blinding_point)
194195
.map(|bp| BlindedForward {
195196
inbound_blinding_point: bp,

0 commit comments

Comments
 (0)