From ebb8e79f80187071fd36475c48bd6b7c0502db37 Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Wed, 17 Sep 2025 18:17:23 -0400 Subject: [PATCH] Don't auto-fail offers payments pre-HTLC lock in Previously, we had a bug that particularly affected async payments where if an outbound payment was in the state {Static}InvoiceReceived and there was a call to process_pending_htlc_forwards, the payment would be automatically abandoned. We would behave correctly and avoid abandoning if the payment was awaiting an invoice, but not if the payment had an invoice but the HTLCs weren't yet locked in. --- lightning/src/ln/async_payments_tests.rs | 3 +++ lightning/src/ln/outbound_payment.rs | 12 ++++++++---- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/lightning/src/ln/async_payments_tests.rs b/lightning/src/ln/async_payments_tests.rs index 1fa1b77f706..8d3f67804fd 100644 --- a/lightning/src/ln/async_payments_tests.rs +++ b/lightning/src/ln/async_payments_tests.rs @@ -240,6 +240,9 @@ fn pass_async_payments_oms( .next_onion_message_for_peer(sender_node_id) .unwrap(); sender.onion_messenger.handle_onion_message(always_online_node_id, &static_invoice_om); + // Check that the node will not lock in HTLCs yet. + sender.node.process_pending_htlc_forwards(); + assert!(sender.node.get_and_clear_pending_msg_events().is_empty()); let held_htlc_available_om_0_1 = sender.onion_messenger.next_onion_message_for_peer(always_online_node_id).unwrap(); diff --git a/lightning/src/ln/outbound_payment.rs b/lightning/src/ln/outbound_payment.rs index a66c21ac4ef..6b1719fca50 100644 --- a/lightning/src/ln/outbound_payment.rs +++ b/lightning/src/ln/outbound_payment.rs @@ -217,9 +217,13 @@ impl PendingOutboundPayment { params.insert_previously_failed_blinded_path(blinded_tail); } } - fn is_awaiting_invoice(&self) -> bool { + // Used for payments to BOLT 12 offers where we are either waiting for an invoice or have an + // invoice but have not locked in HTLCs for the payment yet. + fn is_pre_htlc_lock_in(&self) -> bool { match self { - PendingOutboundPayment::AwaitingInvoice { .. } => true, + PendingOutboundPayment::AwaitingInvoice { .. } + | PendingOutboundPayment::InvoiceReceived { .. } + | PendingOutboundPayment::StaticInvoiceReceived { .. } => true, _ => false, } } @@ -1368,7 +1372,7 @@ impl OutboundPayments { let mut retain = true; if !pmt.is_auto_retryable_now() && pmt.remaining_parts() == 0 - && !pmt.is_awaiting_invoice() + && !pmt.is_pre_htlc_lock_in() { pmt.mark_abandoned(PaymentFailureReason::RetriesExhausted); if let PendingOutboundPayment::Abandoned { payment_hash, reason, .. } = pmt { @@ -1396,7 +1400,7 @@ impl OutboundPayments { || !pmt.is_auto_retryable_now() && pmt.remaining_parts() == 0 && !pmt.is_fulfilled() - && !pmt.is_awaiting_invoice() + && !pmt.is_pre_htlc_lock_in() }) }