Skip to content

Commit 5ec66dc

Browse files
Merge pull request #4405 from valentinewallace/2026-02-dedup-htlc-fwd-data
Dedup `InboundUpdateAdd::Forwarded` data; fix `PaymentForwarded` fields
2 parents 0b45bfd + ab0ba65 commit 5ec66dc

File tree

5 files changed

+371
-113
lines changed

5 files changed

+371
-113
lines changed

lightning/src/ln/chanmon_update_fail_tests.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3519,8 +3519,9 @@ fn do_test_blocked_chan_preimage_release(completion_mode: BlockedUpdateComplMode
35193519
.node
35203520
.handle_commitment_signed_batch_test(node_a_id, &as_htlc_fulfill.commitment_signed);
35213521
check_added_monitors(&nodes[1], 1);
3522-
let (a, raa) = do_main_commitment_signed_dance(&nodes[1], &nodes[0], false);
3522+
let (a, raa, holding_cell) = do_main_commitment_signed_dance(&nodes[1], &nodes[0], false);
35233523
assert!(a.is_none());
3524+
assert!(holding_cell.is_empty());
35243525

35253526
nodes[1].node.handle_revoke_and_ack(node_a_id, &raa);
35263527
check_added_monitors(&nodes[1], 1);
@@ -3938,7 +3939,12 @@ fn do_test_durable_preimages_on_closed_channel(
39383939
let evs = nodes[1].node.get_and_clear_pending_events();
39393940
assert_eq!(evs.len(), if close_chans_before_reload { 2 } else { 1 });
39403941
for ev in evs {
3941-
if let Event::PaymentForwarded { .. } = ev {
3942+
if let Event::PaymentForwarded { claim_from_onchain_tx, next_user_channel_id, .. } = ev {
3943+
if !claim_from_onchain_tx {
3944+
// If the outbound channel is still open, the `next_user_channel_id` should be available.
3945+
// This was previously broken.
3946+
assert!(next_user_channel_id.is_some())
3947+
}
39423948
} else {
39433949
panic!();
39443950
}

lightning/src/ln/channel.rs

Lines changed: 120 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,10 @@ use crate::ln::channel_state::{
5050
OutboundHTLCDetails, OutboundHTLCStateDetails,
5151
};
5252
use crate::ln::channelmanager::{
53-
self, ChannelReadyOrder, FundingConfirmedMessage, HTLCFailureMsg, HTLCPreviousHopData,
54-
HTLCSource, OpenChannelMessage, PaymentClaimDetails, PendingHTLCInfo, PendingHTLCStatus,
55-
RAACommitmentOrder, SentHTLCId, BREAKDOWN_TIMEOUT, MAX_LOCAL_BREAKDOWN_TIMEOUT,
56-
MIN_CLTV_EXPIRY_DELTA,
53+
self, BlindedFailure, ChannelReadyOrder, FundingConfirmedMessage, HTLCFailureMsg,
54+
HTLCPreviousHopData, HTLCSource, OpenChannelMessage, PaymentClaimDetails, PendingHTLCInfo,
55+
PendingHTLCStatus, RAACommitmentOrder, SentHTLCId, BREAKDOWN_TIMEOUT,
56+
MAX_LOCAL_BREAKDOWN_TIMEOUT, MIN_CLTV_EXPIRY_DELTA,
5757
};
5858
use crate::ln::funding::{FundingTxInput, SpliceContribution};
5959
use crate::ln::interactivetxs::{
@@ -308,27 +308,53 @@ impl InboundHTLCState {
308308
}
309309
}
310310

311+
/// Information about the outbound hop for a forwarded HTLC. Useful for generating an accurate
312+
/// [`Event::PaymentForwarded`] if we need to claim this HTLC post-restart.
313+
///
314+
/// [`Event::PaymentForwarded`]: crate::events::Event::PaymentForwarded
315+
#[derive(Debug, Copy, Clone)]
316+
pub(super) struct OutboundHop {
317+
/// The amount forwarded outbound.
318+
pub(super) amt_msat: u64,
319+
/// The outbound channel this HTLC was forwarded over.
320+
pub(super) channel_id: ChannelId,
321+
/// The next-hop recipient of this HTLC.
322+
pub(super) node_id: PublicKey,
323+
/// The outbound channel's funding outpoint.
324+
pub(super) funding_txo: OutPoint,
325+
/// The outbound channel's user channel ID.
326+
pub(super) user_channel_id: u128,
327+
}
328+
329+
impl_writeable_tlv_based!(OutboundHop, {
330+
(0, amt_msat, required),
331+
(2, channel_id, required),
332+
(4, node_id, required),
333+
(6, funding_txo, required),
334+
(8, user_channel_id, required),
335+
});
336+
311337
/// A field of `InboundHTLCState::Committed` containing the HTLC's `update_add_htlc` message. If
312338
/// the HTLC is a forward and gets irrevocably committed to the outbound edge, we convert to
313339
/// `InboundUpdateAdd::Forwarded`, thus pruning the onion and not persisting it on every
314340
/// `ChannelManager` persist.
315341
///
316342
/// Useful for reconstructing the pending HTLC set on startup.
317343
#[derive(Debug, Clone)]
318-
pub(super) enum InboundUpdateAdd {
344+
enum InboundUpdateAdd {
319345
/// The inbound committed HTLC's update_add_htlc message.
320346
WithOnion { update_add_htlc: msgs::UpdateAddHTLC },
321347
/// This inbound HTLC is a forward that was irrevocably committed to the outbound edge, allowing
322348
/// its onion to be pruned and no longer persisted.
349+
///
350+
/// Contains data that is useful if we need to fail or claim this HTLC backwards after a restart
351+
/// and it's missing in the outbound edge.
323352
Forwarded {
324-
/// Useful if we need to fail or claim this HTLC backwards after restart, if it's missing in the
325-
/// outbound edge.
326-
hop_data: HTLCPreviousHopData,
327-
/// Useful if we need to claim this HTLC backwards after a restart and it's missing in the
328-
/// outbound edge, to generate an accurate [`Event::PaymentForwarded`].
329-
///
330-
/// [`Event::PaymentForwarded`]: crate::events::Event::PaymentForwarded
331-
outbound_amt_msat: u64,
353+
incoming_packet_shared_secret: [u8; 32],
354+
phantom_shared_secret: Option<[u8; 32]>,
355+
trampoline_shared_secret: Option<[u8; 32]>,
356+
blinded_failure: Option<BlindedFailure>,
357+
outbound_hop: OutboundHop,
332358
},
333359
/// This HTLC was received pre-LDK 0.3, before we started persisting the onion for inbound
334360
/// committed HTLCs.
@@ -341,8 +367,11 @@ impl_writeable_tlv_based_enum_upgradable!(InboundUpdateAdd,
341367
},
342368
(2, Legacy) => {},
343369
(4, Forwarded) => {
344-
(0, hop_data, required),
345-
(2, outbound_amt_msat, required),
370+
(0, incoming_packet_shared_secret, required),
371+
(2, outbound_hop, required),
372+
(4, phantom_shared_secret, option),
373+
(6, trampoline_shared_secret, option),
374+
(8, blinded_failure, option),
346375
},
347376
);
348377

@@ -7878,10 +7907,35 @@ where
78787907
Ok(())
78797908
}
78807909

7881-
/// Useful for reconstructing the set of pending HTLCs when deserializing the `ChannelManager`.
7882-
pub(super) fn inbound_committed_unresolved_htlcs(
7910+
/// Returns true if any committed inbound HTLCs were received pre-LDK 0.3 and cannot be used
7911+
/// during `ChannelManager` deserialization to reconstruct the set of pending HTLCs.
7912+
pub(super) fn has_legacy_inbound_htlcs(&self) -> bool {
7913+
self.context.pending_inbound_htlcs.iter().any(|htlc| {
7914+
matches!(
7915+
&htlc.state,
7916+
InboundHTLCState::Committed { update_add_htlc: InboundUpdateAdd::Legacy }
7917+
)
7918+
})
7919+
}
7920+
7921+
/// Returns committed inbound HTLCs whose onion has not yet been decoded and processed. Useful
7922+
/// for reconstructing the set of pending HTLCs when deserializing the `ChannelManager`.
7923+
pub(super) fn inbound_htlcs_pending_decode(
7924+
&self,
7925+
) -> impl Iterator<Item = msgs::UpdateAddHTLC> + '_ {
7926+
self.context.pending_inbound_htlcs.iter().filter_map(|htlc| match &htlc.state {
7927+
InboundHTLCState::Committed {
7928+
update_add_htlc: InboundUpdateAdd::WithOnion { update_add_htlc },
7929+
} => Some(update_add_htlc.clone()),
7930+
_ => None,
7931+
})
7932+
}
7933+
7934+
/// Returns committed inbound HTLCs that have been forwarded but not yet fully resolved. Useful
7935+
/// when reconstructing the set of pending HTLCs when deserializing the `ChannelManager`.
7936+
pub(super) fn inbound_forwarded_htlcs(
78837937
&self,
7884-
) -> Vec<(PaymentHash, InboundUpdateAdd)> {
7938+
) -> impl Iterator<Item = (PaymentHash, HTLCPreviousHopData, OutboundHop)> + '_ {
78857939
// We don't want to return an HTLC as needing processing if it already has a resolution that's
78867940
// pending in the holding cell.
78877941
let htlc_resolution_in_holding_cell = |id: u64| -> bool {
@@ -7895,19 +7949,45 @@ where
78957949
})
78967950
};
78977951

7898-
self.context
7899-
.pending_inbound_htlcs
7900-
.iter()
7901-
.filter_map(|htlc| match &htlc.state {
7902-
InboundHTLCState::Committed { update_add_htlc } => {
7903-
if htlc_resolution_in_holding_cell(htlc.htlc_id) {
7904-
return None;
7905-
}
7906-
Some((htlc.payment_hash, update_add_htlc.clone()))
7907-
},
7908-
_ => None,
7909-
})
7910-
.collect()
7952+
let prev_outbound_scid_alias = self.context.outbound_scid_alias();
7953+
let user_channel_id = self.context.get_user_id();
7954+
let channel_id = self.context.channel_id();
7955+
let outpoint = self.funding_outpoint();
7956+
let counterparty_node_id = self.context.get_counterparty_node_id();
7957+
7958+
self.context.pending_inbound_htlcs.iter().filter_map(move |htlc| match &htlc.state {
7959+
InboundHTLCState::Committed {
7960+
update_add_htlc:
7961+
InboundUpdateAdd::Forwarded {
7962+
incoming_packet_shared_secret,
7963+
phantom_shared_secret,
7964+
trampoline_shared_secret,
7965+
blinded_failure,
7966+
outbound_hop,
7967+
},
7968+
} => {
7969+
if htlc_resolution_in_holding_cell(htlc.htlc_id) {
7970+
return None;
7971+
}
7972+
// The reconstructed `HTLCPreviousHopData` is used to fail or claim the HTLC backwards
7973+
// post-restart, if it is missing in the outbound edge.
7974+
let prev_hop_data = HTLCPreviousHopData {
7975+
prev_outbound_scid_alias,
7976+
user_channel_id: Some(user_channel_id),
7977+
htlc_id: htlc.htlc_id,
7978+
incoming_packet_shared_secret: *incoming_packet_shared_secret,
7979+
phantom_shared_secret: *phantom_shared_secret,
7980+
trampoline_shared_secret: *trampoline_shared_secret,
7981+
blinded_failure: *blinded_failure,
7982+
channel_id,
7983+
outpoint,
7984+
counterparty_node_id: Some(counterparty_node_id),
7985+
cltv_expiry: Some(htlc.cltv_expiry),
7986+
};
7987+
Some((htlc.payment_hash, prev_hop_data, *outbound_hop))
7988+
},
7989+
_ => None,
7990+
})
79117991
}
79127992

79137993
/// Useful when reconstructing the set of pending HTLC forwards when deserializing the
@@ -7954,12 +8034,19 @@ where
79548034
/// This inbound HTLC was irrevocably forwarded to the outbound edge, so we no longer need to
79558035
/// persist its onion.
79568036
pub(super) fn prune_inbound_htlc_onion(
7957-
&mut self, htlc_id: u64, hop_data: HTLCPreviousHopData, outbound_amt_msat: u64,
8037+
&mut self, htlc_id: u64, prev_hop_data: &HTLCPreviousHopData,
8038+
outbound_hop_data: OutboundHop,
79588039
) {
79598040
for htlc in self.context.pending_inbound_htlcs.iter_mut() {
79608041
if htlc.htlc_id == htlc_id {
79618042
if let InboundHTLCState::Committed { ref mut update_add_htlc } = htlc.state {
7962-
*update_add_htlc = InboundUpdateAdd::Forwarded { hop_data, outbound_amt_msat };
8043+
*update_add_htlc = InboundUpdateAdd::Forwarded {
8044+
incoming_packet_shared_secret: prev_hop_data.incoming_packet_shared_secret,
8045+
phantom_shared_secret: prev_hop_data.phantom_shared_secret,
8046+
trampoline_shared_secret: prev_hop_data.trampoline_shared_secret,
8047+
blinded_failure: prev_hop_data.blinded_failure,
8048+
outbound_hop: outbound_hop_data,
8049+
};
79638050
return;
79648051
}
79658052
}

0 commit comments

Comments
 (0)