From c49b86dd50214e3b4cb759fe48794d0144b6f92f Mon Sep 17 00:00:00 2001 From: Matt Corallo Date: Sun, 13 Sep 2026 16:55:16 +0000 Subject: [PATCH 1/3] Drop spurious outbound payment status update on inb payment If we have sent an outbound payment with a given `payment_hash` and we receive an inbound payment with a matching `payment_hash`, its possible this is an indication of a payment loop, but its also possible its simply a random HTLC with the same hash. We should, thus, not update the outbound payment's status, as it might still be pending and complete. We can detect the payment's failure when the HTLC failure comes back to us as the sender. --- src/event.rs | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/src/event.rs b/src/event.rs index e700873cdc..3aefbcdda1 100644 --- a/src/event.rs +++ b/src/event.rs @@ -859,18 +859,7 @@ where payment_id ); self.channel_manager.fail_htlc_backwards(&payment_hash); - - let update = PaymentDetailsUpdate { - status: Some(PaymentStatus::Failed), - ..PaymentDetailsUpdate::new(payment_id) - }; - match self.payment_store.update(update).await { - Ok(_) => return Ok(()), - Err(e) => { - log_error!(self.logger, "Failed to access payment store: {}", e); - return Err(ReplayEvent()); - }, - }; + return Ok(()); } if info.status == PaymentStatus::Succeeded From a176d513c44060b4fdd3ed074ad9a92f28271142 Mon Sep 17 00:00:00 2001 From: Matt Corallo Date: Sun, 13 Sep 2026 16:57:26 +0000 Subject: [PATCH 2/3] Do not mark a successful inbound payment as failed on a dup HTLC If we have already received a payment and marked it as `PaymentStatus::Succeeded` before receiving a second HTLC with the same `payment_hash`, we should leave the payment at `PaymentStatus::Succeeded` rather than reverting it to failed. --- src/event.rs | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/src/event.rs b/src/event.rs index 3aefbcdda1..d091fa8732 100644 --- a/src/event.rs +++ b/src/event.rs @@ -891,18 +891,7 @@ where amount_msat, ); self.channel_manager.fail_htlc_backwards(&payment_hash); - - let update = PaymentDetailsUpdate { - status: Some(PaymentStatus::Failed), - ..PaymentDetailsUpdate::new(payment_id) - }; - match self.payment_store.update(update).await { - Ok(_) => return Ok(()), - Err(e) => { - log_error!(self.logger, "Failed to access payment store: {}", e); - return Err(ReplayEvent()); - }, - }; + return Ok(()); } } From 8dd1c9148ab73097f5c961216418303803f222fc Mon Sep 17 00:00:00 2001 From: Matt Corallo Date: Sun, 13 Sep 2026 17:04:06 +0000 Subject: [PATCH 3/3] Reject payments with unknown required TLVs `ChannelManager::claim_funds` fails if there are any custom even TLVs. While this is documented, its somewhat deadly for swap services - they might release the preimage in response to a `PaymentClaimable` event and assume they can definitely claim the payment, but LDK will refuse to do so. The `lightning` crate really needs to fix this API for the next release, but for now we should avoid having such an obvious API hole in `ldk-node`. --- src/event.rs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/event.rs b/src/event.rs index d091fa8732..4a1ecd2eae 100644 --- a/src/event.rs +++ b/src/event.rs @@ -23,7 +23,7 @@ use lightning::events::{ OutboundHTLCLocator as LdkOutboundHtlcLocator, PaymentFailureReason, PaymentPurpose, ReplayEvent, }; -use lightning::ln::channelmanager::{PaymentId, TrustedChannelFeatures}; +use lightning::ln::channelmanager::{FailureCode, PaymentId, TrustedChannelFeatures}; use lightning::ln::types::ChannelId; use lightning::routing::gossip::NodeId; use lightning::sign::EntropySource; @@ -851,6 +851,22 @@ where } => { let (payment_id, mut payment_info) = self.resolve_inbound_payment_id(payment_id, &payment_hash).await?; + + if let Some((tlv_type, _)) = onion_fields + .as_ref() + .and_then(|fields| fields.custom_tlvs().iter().find(|(id, _)| id % 2 == 0)) + { + log_info!( + self.logger, + "Refusing inbound payment with hash {} as the sender included unknown required TLVs (incl {})", + hex_utils::to_string(&payment_hash.0), + tlv_type, + ); + let error_code = FailureCode::InvalidOnionPayload(Some((*tlv_type, 0))); + self.channel_manager.fail_htlc_backwards_with_reason(&payment_hash, error_code); + return Ok(()); + } + if let Some(info) = payment_info.as_ref() { if info.direction == PaymentDirection::Outbound { log_info!(