@@ -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,22 @@ 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 pad a blinded path. When such an HTLC is received, the dummy
235+ /// layer is peeled, producing a new onion packet which must be processed again. This process
236+ /// repeats until a non-dummy routing decision is reached, which is guaranteed to be
237+ /// [`PendingHTLCRouting::Receive`].
238+ Dummy {
239+ /// The onion packet obtained after removing the dummy layer.
240+ ///
241+ /// This packet must be re-processed as if it were freshly received.
242+ onion_packet: msgs::OnionPacket,
243+ /// Set if this HTLC is being forwarded within a blinded path.
244+ blinded: Option<BlindedForward>,
245+ /// The absolute CLTV of the inbound HTLC.
246+ incoming_cltv_expiry: Option<u32>,
247+ },
232248 /// An HTLC which should be forwarded on to another Trampoline node.
233249 TrampolineForward {
234250 /// The onion shared secret we build with the sender (or the preceding Trampoline node) used
@@ -352,6 +368,7 @@ impl PendingHTLCRouting {
352368 fn blinded_failure(&self) -> Option<BlindedFailure> {
353369 match self {
354370 Self::Forward { blinded: Some(BlindedForward { failure, .. }), .. } => Some(*failure),
371+ Self::Dummy { blinded: Some(BlindedForward { failure, .. }), .. } => Some(*failure),
355372 Self::TrampolineForward { blinded: Some(BlindedForward { failure, .. }), .. } => {
356373 Some(*failure)
357374 },
@@ -368,6 +385,7 @@ impl PendingHTLCRouting {
368385 fn incoming_cltv_expiry(&self) -> Option<u32> {
369386 match self {
370387 Self::Forward { incoming_cltv_expiry, .. } => *incoming_cltv_expiry,
388+ Self::Dummy { incoming_cltv_expiry, .. } => *incoming_cltv_expiry,
371389 Self::TrampolineForward { incoming_cltv_expiry, .. } => Some(*incoming_cltv_expiry),
372390 Self::Receive { incoming_cltv_expiry, .. } => Some(*incoming_cltv_expiry),
373391 Self::ReceiveKeysend { incoming_cltv_expiry, .. } => Some(*incoming_cltv_expiry),
@@ -4974,6 +4992,11 @@ where
49744992 ) -> Result<(), LocalHTLCFailureReason> {
49754993 let outgoing_scid = match next_packet_details.outgoing_connector {
49764994 HopConnector::ShortChannelId(scid) => scid,
4995+ HopConnector::Dummy => {
4996+ // Dummy hops are only used for path padding and must not reach HTLC processing.
4997+ debug_assert!(false, "Dummy hop reached HTLC handling.");
4998+ return Err(LocalHTLCFailureReason::InvalidOnionPayload);
4999+ }
49775000 HopConnector::Trampoline(_) => {
49785001 return Err(LocalHTLCFailureReason::InvalidTrampolineForward);
49795002 }
@@ -6878,6 +6901,7 @@ where
68786901 fn process_pending_update_add_htlcs(&self) -> bool {
68796902 let mut should_persist = false;
68806903 let mut decode_update_add_htlcs = new_hash_map();
6904+ let mut dummy_update_add_htlcs = new_hash_map();
68816905 mem::swap(&mut decode_update_add_htlcs, &mut self.decode_update_add_htlcs.lock().unwrap());
68826906
68836907 let get_htlc_failure_type = |outgoing_scid_opt: Option<u64>, payment_hash: PaymentHash| {
@@ -6941,7 +6965,68 @@ where
69416965 &*self.logger,
69426966 &self.secp_ctx,
69436967 ) {
6944- Ok(decoded_onion) => decoded_onion,
6968+ Ok(decoded_onion) => match decoded_onion {
6969+ (
6970+ onion_utils::Hop::Dummy {
6971+ intro_node_blinding_point,
6972+ next_hop_hmac,
6973+ new_packet_bytes,
6974+ ..
6975+ },
6976+ Some(NextPacketDetails {
6977+ next_packet_pubkey,
6978+ outgoing_connector,
6979+ ..
6980+ }),
6981+ ) => {
6982+ debug_assert!(
6983+ matches!(outgoing_connector, HopConnector::Dummy),
6984+ "Dummy hop must always map to HopConnector::Dummy"
6985+ );
6986+
6987+ // Dummy hops are not forwarded. Instead, we reconstruct a new UpdateAddHTLC
6988+ // with the next onion packet (ephemeral pubkey, hop data, and HMAC) and push
6989+ // it back into our own processing queue. This lets us step through the dummy
6990+ // layers locally until we reach the next real hop.
6991+ let next_blinding_point = intro_node_blinding_point
6992+ .or(update_add_htlc.blinding_point)
6993+ .and_then(|blinding_point| {
6994+ let ss = self
6995+ .node_signer
6996+ .ecdh(Recipient::Node, &blinding_point, None)
6997+ .ok()?
6998+ .secret_bytes();
6999+
7000+ onion_utils::next_hop_pubkey(
7001+ &self.secp_ctx,
7002+ blinding_point,
7003+ &ss,
7004+ )
7005+ .ok()
7006+ });
7007+
7008+ let new_onion_packet = OnionPacket {
7009+ version: 0,
7010+ public_key: next_packet_pubkey,
7011+ hop_data: new_packet_bytes,
7012+ hmac: next_hop_hmac,
7013+ };
7014+
7015+ let new_update_add_htlc = UpdateAddHTLC {
7016+ onion_routing_packet: new_onion_packet,
7017+ blinding_point: next_blinding_point,
7018+ ..update_add_htlc.clone()
7019+ };
7020+
7021+ dummy_update_add_htlcs
7022+ .entry(incoming_scid_alias)
7023+ .or_insert_with(Vec::new)
7024+ .push(new_update_add_htlc);
7025+
7026+ continue;
7027+ },
7028+ _ => decoded_onion,
7029+ },
69457030
69467031 Err((htlc_fail, reason)) => {
69477032 let failure_type = HTLCHandlingFailureType::InvalidOnion;
@@ -6954,6 +7039,13 @@ where
69547039 let outgoing_scid_opt =
69557040 next_packet_details_opt.as_ref().and_then(|d| match d.outgoing_connector {
69567041 HopConnector::ShortChannelId(scid) => Some(scid),
7042+ HopConnector::Dummy => {
7043+ debug_assert!(
7044+ false,
7045+ "Dummy hops must never be processed at this stage."
7046+ );
7047+ None
7048+ },
69577049 HopConnector::Trampoline(_) => None,
69587050 });
69597051 let shared_secret = next_hop.shared_secret().secret_bytes();
@@ -7097,6 +7189,19 @@ where
70977189 ));
70987190 }
70997191 }
7192+
7193+ // Merge peeled dummy HTLCs into the existing decode queue so they can be
7194+ // processed in the next iteration. We avoid replacing the whole queue
7195+ // (e.g. via mem::swap) because other threads may have enqueued new HTLCs
7196+ // meanwhile; merging preserves everything safely.
7197+ if !dummy_update_add_htlcs.is_empty() {
7198+ let mut decode_update_add_htlc_source = self.decode_update_add_htlcs.lock().unwrap();
7199+
7200+ for (incoming_scid_alias, htlcs) in dummy_update_add_htlcs.into_iter() {
7201+ decode_update_add_htlc_source.entry(incoming_scid_alias).or_default().extend(htlcs);
7202+ }
7203+ }
7204+
71007205 should_persist
71017206 }
71027207
@@ -11419,6 +11524,13 @@ This indicates a bug inside LDK. Please report this error at https://github.com/
1141911524 for (forward_info, prev_htlc_id) in pending_forwards.drain(..) {
1142011525 let scid = match forward_info.routing {
1142111526 PendingHTLCRouting::Forward { short_channel_id, .. } => short_channel_id,
11527+ PendingHTLCRouting::Dummy { .. } => {
11528+ debug_assert!(
11529+ false,
11530+ "Dummy hops must never be processed at this stage."
11531+ );
11532+ 0
11533+ },
1142211534 PendingHTLCRouting::TrampolineForward { .. } => 0,
1142311535 PendingHTLCRouting::Receive { .. } => 0,
1142411536 PendingHTLCRouting::ReceiveKeysend { .. } => 0,
@@ -15966,6 +16078,11 @@ impl_writeable_tlv_based_enum!(PendingHTLCRouting,
1596616078 (4, blinded, option),
1596716079 (6, node_id, required),
1596816080 (8, incoming_cltv_expiry, required),
16081+ },
16082+ (4, Dummy) => {
16083+ (0, onion_packet, required),
16084+ (1, blinded, option),
16085+ (2, incoming_cltv_expiry, option),
1596916086 }
1597016087);
1597116088
0 commit comments