Skip to content

Commit 3fb6378

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 266871a commit 3fb6378

File tree

2 files changed

+94
-8
lines changed

2 files changed

+94
-8
lines changed

lightning/src/ln/channelmanager.rs

Lines changed: 93 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,40 @@ 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, channel_id: &ChannelId, counterparty_node_id: &PublicKey,
667+
) -> Self {
668+
let mut sha = Sha256::engine();
669+
sha.input(&htlc_id.to_be_bytes());
670+
sha.input(&channel_id.0);
671+
sha.input(&counterparty_node_id.serialize());
672+
Self(Sha256::from_engine(sha).to_byte_array())
673+
}
674+
}
675+
676+
impl Borrow<[u8]> for InterceptId {
677+
fn borrow(&self) -> &[u8] {
678+
&self.0[..]
679+
}
680+
}
681+
impl_fmt_traits! {
682+
impl fmt_traits for InterceptId {
683+
const LENGTH: usize = 32;
684+
}
685+
}
686+
644687
impl Writeable for InterceptId {
645688
fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
646689
self.0.write(w)
@@ -2598,8 +2641,14 @@ pub struct ChannelManager<
25982641
pub(super) forward_htlcs: Mutex<HashMap<u64, Vec<HTLCForwardInfo>>>,
25992642
#[cfg(not(test))]
26002643
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.
2644+
/// Storage for HTLCs that have been intercepted.
2645+
///
2646+
/// These HTLCs fall into two categories:
2647+
/// 1. HTLCs that are bubbled up to the user and held until the invocation of
2648+
/// [`ChannelManager::forward_intercepted_htlc`] or [`ChannelManager::fail_intercepted_htlc`]
2649+
/// (or timeout)
2650+
/// 2. HTLCs that are being held on behalf of an often-offline sender until receipt of a
2651+
/// [`ReleaseHeldHtlc`] onion message from an often-offline recipient
26032652
///
26042653
/// See `ChannelManager` struct-level documentation for lock order requirements.
26052654
pending_intercepted_htlcs: Mutex<HashMap<InterceptId, PendingAddHTLCInfo>>,
@@ -6282,11 +6331,19 @@ where
62826331
})?;
62836332

62846333
let routing = match payment.forward_info.routing {
6285-
PendingHTLCRouting::Forward { onion_packet, blinded, incoming_cltv_expiry, .. } => {
6334+
PendingHTLCRouting::Forward {
6335+
onion_packet,
6336+
blinded,
6337+
incoming_cltv_expiry,
6338+
hold_htlc,
6339+
..
6340+
} => {
6341+
debug_assert!(hold_htlc.is_none(), "Held intercept HTLCs should not be surfaced in an event until the recipient comes online");
62866342
PendingHTLCRouting::Forward {
62876343
onion_packet,
62886344
blinded,
62896345
incoming_cltv_expiry,
6346+
hold_htlc,
62906347
short_channel_id: next_hop_scid,
62916348
}
62926349
},
@@ -10719,16 +10776,43 @@ This indicates a bug inside LDK. Please report this error at https://github.com/
1071910776
));
1072010777
};
1072110778

10722-
if !is_our_scid
10779+
// In the case that we have an HTLC that we're supposed to hold onto until the
10780+
// recipient comes online *and* the outbound scid is encoded as
10781+
// `fake_scid::is_valid_intercept`, we should first wait for the recipient to come
10782+
// online before generating an `HTLCIntercepted` event, since the event cannot be
10783+
// acted on until the recipient is online to cooperatively open the JIT channel. Once
10784+
// we receive the `ReleaseHeldHtlc` message from the recipient, we will circle back
10785+
// here and resume generating the event below.
10786+
if pending_add.forward_info.routing.should_hold_htlc() {
10787+
let intercept_id = InterceptId::from_htlc_id_and_chan_id(
10788+
prev_htlc_id,
10789+
&prev_channel_id,
10790+
&prev_counterparty_node_id,
10791+
);
10792+
let mut held_htlcs = self.pending_intercepted_htlcs.lock().unwrap();
10793+
match held_htlcs.entry(intercept_id) {
10794+
hash_map::Entry::Vacant(entry) => {
10795+
log_trace!(
10796+
logger,
10797+
"Intercepted held HTLC with id {}, holding until the recipient is online",
10798+
intercept_id
10799+
);
10800+
entry.insert(pending_add);
10801+
},
10802+
hash_map::Entry::Occupied(_) => {
10803+
debug_assert!(false, "Should never have two HTLCs with the same channel id and htlc id");
10804+
fail_intercepted_htlc(pending_add);
10805+
},
10806+
}
10807+
} else if !is_our_scid
1072310808
&& pending_add.forward_info.incoming_amt_msat.is_some()
1072410809
&& fake_scid::is_valid_intercept(
1072510810
&self.fake_scid_rand_bytes,
1072610811
scid,
1072710812
&self.chain_hash,
1072810813
) {
10729-
let intercept_id = InterceptId(
10730-
Sha256::hash(&pending_add.forward_info.incoming_shared_secret)
10731-
.to_byte_array(),
10814+
let intercept_id = InterceptId::from_incoming_shared_secret(
10815+
&pending_add.forward_info.incoming_shared_secret,
1073210816
);
1073310817
let mut pending_intercepts = self.pending_intercepted_htlcs.lock().unwrap();
1073410818
match pending_intercepts.entry(intercept_id) {
@@ -14905,6 +14989,7 @@ impl_writeable_tlv_based_enum!(PendingHTLCRouting,
1490514989
(1, blinded, option),
1490614990
(2, short_channel_id, required),
1490714991
(3, incoming_cltv_expiry, option),
14992+
(4, hold_htlc, option),
1490814993
},
1490914994
(1, Receive) => {
1491014995
(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)