@@ -66,7 +66,7 @@ use crate::ln::channel_state::ChannelDetails;
6666use crate::ln::funding::SpliceContribution;
6767use crate::ln::inbound_payment;
6868use crate::ln::interactivetxs::InteractiveTxMessageSend;
69- use crate::ln::msgs;
69+ use crate::ln::msgs::{self, OnionPacket, UpdateAddHTLC} ;
7070use crate::ln::msgs::{
7171 BaseMessageHandler, ChannelMessageHandler, CommitmentUpdate, DecodeError, LightningError,
7272 MessageSendEvent,
@@ -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,68 @@ 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+ intro_node_blinding_point,
6973+ next_hop_hmac,
6974+ new_packet_bytes,
6975+ ..
6976+ },
6977+ Some(NextPacketDetails {
6978+ next_packet_pubkey,
6979+ outgoing_connector,
6980+ ..
6981+ }),
6982+ ) => {
6983+ debug_assert!(
6984+ matches!(outgoing_connector, HopConnector::Dummy),
6985+ "Dummy hop must always map to HopConnector::Dummy"
6986+ );
6987+
6988+ // Dummy hops are not forwarded. Instead, we reconstruct a new UpdateAddHTLC
6989+ // with the next onion packet (ephemeral pubkey, hop data, and HMAC) and push
6990+ // it back into our own processing queue. This lets us step through the dummy
6991+ // layers locally until we reach the next real hop.
6992+ let next_blinding_point = intro_node_blinding_point
6993+ .or(update_add_htlc.blinding_point)
6994+ .and_then(|blinding_point| {
6995+ let ss = self
6996+ .node_signer
6997+ .ecdh(Recipient::Node, &blinding_point, None)
6998+ .ok()?
6999+ .secret_bytes();
7000+
7001+ onion_utils::next_hop_pubkey(
7002+ &self.secp_ctx,
7003+ blinding_point,
7004+ &ss,
7005+ )
7006+ .ok()
7007+ });
7008+
7009+ let new_onion_packet = OnionPacket {
7010+ version: 0,
7011+ public_key: next_packet_pubkey,
7012+ hop_data: new_packet_bytes,
7013+ hmac: next_hop_hmac,
7014+ };
7015+
7016+ let new_update_add_htlc = UpdateAddHTLC {
7017+ onion_routing_packet: new_onion_packet,
7018+ blinding_point: next_blinding_point,
7019+ ..update_add_htlc.clone()
7020+ };
7021+
7022+ dummy_update_add_htlcs
7023+ .entry(incoming_scid_alias)
7024+ .or_insert_with(Vec::new)
7025+ .push(new_update_add_htlc);
7026+
7027+ continue;
7028+ },
7029+ _ => decoded_onion,
7030+ },
69457031
69467032 Err((htlc_fail, reason)) => {
69477033 let failure_type = HTLCHandlingFailureType::InvalidOnion;
@@ -6954,6 +7040,13 @@ where
69547040 let outgoing_scid_opt =
69557041 next_packet_details_opt.as_ref().and_then(|d| match d.outgoing_connector {
69567042 HopConnector::ShortChannelId(scid) => Some(scid),
7043+ HopConnector::Dummy => {
7044+ debug_assert!(
7045+ false,
7046+ "Dummy hops must never be processed at this stage."
7047+ );
7048+ None
7049+ },
69577050 HopConnector::Trampoline(_) => None,
69587051 });
69597052 let shared_secret = next_hop.shared_secret().secret_bytes();
@@ -7097,6 +7190,19 @@ where
70977190 ));
70987191 }
70997192 }
7193+
7194+ // Merge peeled dummy HTLCs into the existing decode queue so they can be
7195+ // processed in the next iteration. We avoid replacing the whole queue
7196+ // (e.g. via mem::swap) because other threads may have enqueued new HTLCs
7197+ // meanwhile; merging preserves everything safely.
7198+ if !dummy_update_add_htlcs.is_empty() {
7199+ let mut decode_update_add_htlc_source = self.decode_update_add_htlcs.lock().unwrap();
7200+
7201+ for (incoming_scid_alias, htlcs) in dummy_update_add_htlcs.into_iter() {
7202+ decode_update_add_htlc_source.entry(incoming_scid_alias).or_default().extend(htlcs);
7203+ }
7204+ }
7205+
71007206 should_persist
71017207 }
71027208
@@ -11419,6 +11525,13 @@ This indicates a bug inside LDK. Please report this error at https://github.com/
1141911525 for (forward_info, prev_htlc_id) in pending_forwards.drain(..) {
1142011526 let scid = match forward_info.routing {
1142111527 PendingHTLCRouting::Forward { short_channel_id, .. } => short_channel_id,
11528+ PendingHTLCRouting::Dummy { .. } => {
11529+ debug_assert!(
11530+ false,
11531+ "Dummy hops must never be processed at this stage."
11532+ );
11533+ 0
11534+ },
1142211535 PendingHTLCRouting::TrampolineForward { .. } => 0,
1142311536 PendingHTLCRouting::Receive { .. } => 0,
1142411537 PendingHTLCRouting::ReceiveKeysend { .. } => 0,
@@ -15966,6 +16079,11 @@ impl_writeable_tlv_based_enum!(PendingHTLCRouting,
1596616079 (4, blinded, option),
1596716080 (6, node_id, required),
1596816081 (8, incoming_cltv_expiry, required),
16082+ },
16083+ (4, Dummy) => {
16084+ (0, onion_packet, required),
16085+ (1, blinded, required),
16086+ (2, incoming_cltv_expiry, option),
1596916087 }
1597016088);
1597116089
0 commit comments