Skip to content

Commit 8e8363b

Browse files
committed
Make InvoiceReceived event generation idempotent
Ensures `InvoiceReceived` events are not generated multiple times when `manually_handle_bolt12_invoice` is enabled. Duplicate events for the same invoice could cause confusion—this change introduces an idempotency check to prevent that.
1 parent 5bc9ffa commit 8e8363b

File tree

2 files changed

+56
-23
lines changed

2 files changed

+56
-23
lines changed

lightning/src/ln/channelmanager.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12477,6 +12477,11 @@ where
1247712477
);
1247812478

1247912479
if self.default_configuration.manually_handle_bolt12_invoices {
12480+
// Update the corresponding entry in `PendingOutboundPayment` for this invoice.
12481+
// This ensures that event generation remains idempotent in case we receive
12482+
// the same invoice multiple times.
12483+
self.pending_outbound_payments.mark_invoice_received(&invoice, payment_id).ok()?;
12484+
1248012485
let event = Event::InvoiceReceived {
1248112486
payment_id, invoice, context, responder,
1248212487
};

lightning/src/ln/outbound_payment.rs

Lines changed: 51 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -73,9 +73,9 @@ pub(crate) enum PendingOutboundPayment {
7373
route_params_config: RouteParametersConfig,
7474
retryable_invoice_request: Option<RetryableInvoiceRequest>
7575
},
76-
// This state will never be persisted to disk because we transition from `AwaitingInvoice` to
77-
// `Retryable` atomically within the `ChannelManager::total_consistency_lock`. Useful to avoid
78-
// holding the `OutboundPayments::pending_outbound_payments` lock during pathfinding.
76+
// Represents the state after the invoice has been received, transitioning from the corresponding
77+
// `AwaitingInvoice` state.
78+
// Helps avoid holding the `OutboundPayments::pending_outbound_payments` lock during pathfinding.
7979
InvoiceReceived {
8080
payment_hash: PaymentHash,
8181
retry_strategy: Retry,
@@ -862,26 +862,9 @@ impl OutboundPayments {
862862
IH: Fn() -> InFlightHtlcs,
863863
SP: Fn(SendAlongPathArgs) -> Result<(), APIError>,
864864
{
865-
let payment_hash = invoice.payment_hash();
866-
let params_config;
867-
let retry_strategy;
868-
match self.pending_outbound_payments.lock().unwrap().entry(payment_id) {
869-
hash_map::Entry::Occupied(entry) => match entry.get() {
870-
PendingOutboundPayment::AwaitingInvoice {
871-
retry_strategy: retry, route_params_config, ..
872-
} => {
873-
retry_strategy = *retry;
874-
params_config = *route_params_config;
875-
*entry.into_mut() = PendingOutboundPayment::InvoiceReceived {
876-
payment_hash,
877-
retry_strategy: *retry,
878-
route_params_config: *route_params_config,
879-
};
880-
},
881-
_ => return Err(Bolt12PaymentError::DuplicateInvoice),
882-
},
883-
hash_map::Entry::Vacant(_) => return Err(Bolt12PaymentError::UnexpectedInvoice),
884-
}
865+
866+
let (payment_hash, retry_strategy, params_config, _) = self
867+
.mark_invoice_received_and_get_details(invoice, payment_id)?;
885868

886869
if invoice.invoice_features().requires_unknown_bits_from(&features) {
887870
self.abandon_payment(
@@ -1789,6 +1772,51 @@ impl OutboundPayments {
17891772
}
17901773
}
17911774

1775+
pub(super) fn mark_invoice_received(
1776+
&self, invoice: &Bolt12Invoice, payment_id: PaymentId
1777+
) -> Result<(), Bolt12PaymentError> {
1778+
self.mark_invoice_received_and_get_details(invoice, payment_id)
1779+
.and_then(|(_, _, _, is_newly_marked)| {
1780+
is_newly_marked
1781+
.then_some(())
1782+
.ok_or(Bolt12PaymentError::DuplicateInvoice)
1783+
})
1784+
}
1785+
1786+
fn mark_invoice_received_and_get_details(
1787+
&self, invoice: &Bolt12Invoice, payment_id: PaymentId
1788+
) -> Result<(PaymentHash, Retry, RouteParametersConfig, bool), Bolt12PaymentError> {
1789+
match self.pending_outbound_payments.lock().unwrap().entry(payment_id) {
1790+
hash_map::Entry::Occupied(entry) => match entry.get() {
1791+
PendingOutboundPayment::AwaitingInvoice {
1792+
retry_strategy: retry, route_params_config, ..
1793+
} => {
1794+
let payment_hash = invoice.payment_hash();
1795+
let retry = *retry;
1796+
let config = *route_params_config;
1797+
*entry.into_mut() = PendingOutboundPayment::InvoiceReceived {
1798+
payment_hash,
1799+
retry_strategy: retry,
1800+
route_params_config: config,
1801+
};
1802+
1803+
Ok((payment_hash, retry, config, true))
1804+
},
1805+
// When manual invoice handling is enabled, the corresponding `PendingOutboundPayment` entry
1806+
// is already updated at the time the invoice is received. This ensures that `InvoiceReceived`
1807+
// event generation remains idempotent, even if the same invoice is received again before the
1808+
// event is handled by the user.
1809+
PendingOutboundPayment::InvoiceReceived {
1810+
retry_strategy, route_params_config, ..
1811+
} => {
1812+
Ok((invoice.payment_hash(), *retry_strategy, *route_params_config, false))
1813+
},
1814+
_ => Err(Bolt12PaymentError::DuplicateInvoice),
1815+
},
1816+
hash_map::Entry::Vacant(_) => Err(Bolt12PaymentError::UnexpectedInvoice),
1817+
}
1818+
}
1819+
17921820
fn pay_route_internal<NS: Deref, F>(
17931821
&self, route: &Route, payment_hash: PaymentHash, recipient_onion: &RecipientOnionFields,
17941822
keysend_preimage: Option<PaymentPreimage>, invoice_request: Option<&InvoiceRequest>,

0 commit comments

Comments
 (0)