@@ -229,6 +229,23 @@ pub enum PendingHTLCRouting {
229229 /// [`ReleaseHeldHtlc`] onion message.
230230 hold_htlc: Option<()>,
231231 },
232+ /// A dummy HTLC hop which does not represent a real routing decision.
233+ ///
234+ /// Dummy HTLCs are introduced to extend a blinded path without adding a real hop.
235+ /// When such an HTLC is received, the dummy layer is peeled, producing a new onion
236+ /// packet which must be processed again. This process repeats until a non-dummy
237+ /// routing decision is reached, which is guaranteed to be
238+ /// [`PendingHTLCRouting::Receive`].
239+ Dummy {
240+ /// The onion packet obtained after removing the dummy layer.
241+ ///
242+ /// This packet must be re-processed as if it were freshly received.
243+ onion_packet: msgs::OnionPacket,
244+ /// Forwarding context for this HTLC within a blinded path.
245+ blinded: BlindedForward,
246+ /// The absolute CLTV of the inbound HTLC.
247+ incoming_cltv_expiry: Option<u32>,
248+ },
232249 /// An HTLC which should be forwarded on to another Trampoline node.
233250 TrampolineForward {
234251 /// The onion shared secret we build with the sender (or the preceding Trampoline node) used
@@ -352,6 +369,7 @@ impl PendingHTLCRouting {
352369 fn blinded_failure(&self) -> Option<BlindedFailure> {
353370 match self {
354371 Self::Forward { blinded: Some(BlindedForward { failure, .. }), .. } => Some(*failure),
372+ Self::Dummy { blinded: BlindedForward { failure, .. }, .. } => Some(*failure),
355373 Self::TrampolineForward { blinded: Some(BlindedForward { failure, .. }), .. } => {
356374 Some(*failure)
357375 },
@@ -368,6 +386,7 @@ impl PendingHTLCRouting {
368386 fn incoming_cltv_expiry(&self) -> Option<u32> {
369387 match self {
370388 Self::Forward { incoming_cltv_expiry, .. } => *incoming_cltv_expiry,
389+ Self::Dummy { incoming_cltv_expiry, .. } => *incoming_cltv_expiry,
371390 Self::TrampolineForward { incoming_cltv_expiry, .. } => Some(*incoming_cltv_expiry),
372391 Self::Receive { incoming_cltv_expiry, .. } => Some(*incoming_cltv_expiry),
373392 Self::ReceiveKeysend { incoming_cltv_expiry, .. } => Some(*incoming_cltv_expiry),
@@ -4974,6 +4993,11 @@ where
49744993 ) -> Result<(), LocalHTLCFailureReason> {
49754994 let outgoing_scid = match next_packet_details.outgoing_connector {
49764995 HopConnector::ShortChannelId(scid) => scid,
4996+ HopConnector::Dummy => {
4997+ // Dummy hops are only used for path padding and must not reach HTLC processing.
4998+ debug_assert!(false, "Dummy hop reached HTLC handling.");
4999+ return Err(LocalHTLCFailureReason::InvalidOnionPayload);
5000+ }
49775001 HopConnector::Trampoline(_) => {
49785002 return Err(LocalHTLCFailureReason::InvalidTrampolineForward);
49795003 }
@@ -6878,6 +6902,7 @@ where
68786902 fn process_pending_update_add_htlcs(&self) -> bool {
68796903 let mut should_persist = false;
68806904 let mut decode_update_add_htlcs = new_hash_map();
6905+ let mut dummy_update_add_htlcs = new_hash_map();
68816906 mem::swap(&mut decode_update_add_htlcs, &mut self.decode_update_add_htlcs.lock().unwrap());
68826907
68836908 let get_htlc_failure_type = |outgoing_scid_opt: Option<u64>, payment_hash: PaymentHash| {
@@ -6941,7 +6966,36 @@ where
69416966 &*self.logger,
69426967 &self.secp_ctx,
69436968 ) {
6944- Ok(decoded_onion) => decoded_onion,
6969+ Ok(decoded_onion) => match decoded_onion {
6970+ (
6971+ onion_utils::Hop::Dummy {
6972+ dummy_hop_data,
6973+ next_hop_hmac,
6974+ new_packet_bytes,
6975+ ..
6976+ },
6977+ Some(next_packet_details),
6978+ ) => {
6979+ let new_update_add_htlc =
6980+ onion_utils::peel_dummy_hop_update_add_htlc(
6981+ update_add_htlc,
6982+ dummy_hop_data,
6983+ next_hop_hmac,
6984+ new_packet_bytes,
6985+ next_packet_details,
6986+ &*self.node_signer,
6987+ &self.secp_ctx,
6988+ );
6989+
6990+ dummy_update_add_htlcs
6991+ .entry(incoming_scid_alias)
6992+ .or_insert_with(Vec::new)
6993+ .push(new_update_add_htlc);
6994+
6995+ continue;
6996+ },
6997+ _ => decoded_onion,
6998+ },
69456999
69467000 Err((htlc_fail, reason)) => {
69477001 let failure_type = HTLCHandlingFailureType::InvalidOnion;
@@ -6954,6 +7008,13 @@ where
69547008 let outgoing_scid_opt =
69557009 next_packet_details_opt.as_ref().and_then(|d| match d.outgoing_connector {
69567010 HopConnector::ShortChannelId(scid) => Some(scid),
7011+ HopConnector::Dummy => {
7012+ debug_assert!(
7013+ false,
7014+ "Dummy hops must never be processed at this stage."
7015+ );
7016+ None
7017+ },
69577018 HopConnector::Trampoline(_) => None,
69587019 });
69597020 let shared_secret = next_hop.shared_secret().secret_bytes();
@@ -7097,6 +7158,19 @@ where
70977158 ));
70987159 }
70997160 }
7161+
7162+ // Merge peeled dummy HTLCs into the existing decode queue so they can be
7163+ // processed in the next iteration. We avoid replacing the whole queue
7164+ // (e.g. via mem::swap) because other threads may have enqueued new HTLCs
7165+ // meanwhile; merging preserves everything safely.
7166+ if !dummy_update_add_htlcs.is_empty() {
7167+ let mut decode_update_add_htlc_source = self.decode_update_add_htlcs.lock().unwrap();
7168+
7169+ for (incoming_scid_alias, htlcs) in dummy_update_add_htlcs.into_iter() {
7170+ decode_update_add_htlc_source.entry(incoming_scid_alias).or_default().extend(htlcs);
7171+ }
7172+ }
7173+
71007174 should_persist
71017175 }
71027176
@@ -11419,6 +11493,13 @@ This indicates a bug inside LDK. Please report this error at https://github.com/
1141911493 for (forward_info, prev_htlc_id) in pending_forwards.drain(..) {
1142011494 let scid = match forward_info.routing {
1142111495 PendingHTLCRouting::Forward { short_channel_id, .. } => short_channel_id,
11496+ PendingHTLCRouting::Dummy { .. } => {
11497+ debug_assert!(
11498+ false,
11499+ "Dummy hops must never be processed at this stage."
11500+ );
11501+ 0
11502+ },
1142211503 PendingHTLCRouting::TrampolineForward { .. } => 0,
1142311504 PendingHTLCRouting::Receive { .. } => 0,
1142411505 PendingHTLCRouting::ReceiveKeysend { .. } => 0,
@@ -15966,6 +16047,11 @@ impl_writeable_tlv_based_enum!(PendingHTLCRouting,
1596616047 (4, blinded, option),
1596716048 (6, node_id, required),
1596816049 (8, incoming_cltv_expiry, required),
16050+ },
16051+ (4, Dummy) => {
16052+ (0, onion_packet, required),
16053+ (1, blinded, required),
16054+ (2, incoming_cltv_expiry, option),
1596916055 }
1597016056);
1597116057
0 commit comments