diff --git a/src/event.rs b/src/event.rs index 728f625ab..e700873cd 100644 --- a/src/event.rs +++ b/src/event.rs @@ -37,7 +37,7 @@ use lightning_types::payment::{PaymentHash, PaymentPreimage}; use crate::config::{may_announce_channel, Config, PEER_RECONNECTION_INTERVAL}; use crate::connection::ConnectionManager; -use crate::data_store::DataStoreUpdateResult; +use crate::data_store::{DataStoreUpdateResult, UpdatableObject}; use crate::fee_estimator::ConfirmationTarget; #[cfg(feature = "uniffi")] use crate::ffi::PaidBolt12Invoice; @@ -1472,26 +1472,50 @@ where }; }, LdkEvent::PaymentFailed { payment_id, payment_hash, reason, .. } => { + let mut ignore_late_failure = false; + self.payment_store + .mutate(&payment_id, |current| { + let current = current?; + // LDK may emit PaymentFailed after PaymentSent. Ignore the entire late + // failure so it cannot alter payment metadata or emit a contradictory event. + if current.direction == PaymentDirection::Outbound + && current.kind.is_lightning_payment() + && current.status == PaymentStatus::Succeeded + { + ignore_late_failure = true; + return None; + } + let mut updated = current.clone(); + let update = PaymentDetailsUpdate { + hash: Some(payment_hash), + status: Some(PaymentStatus::Failed), + ..PaymentDetailsUpdate::new(payment_id) + }; + updated.update(update).then_some(updated) + }) + .await + .map_err(|e| { + log_error!(self.logger, "Failed to access payment store: {}", e); + ReplayEvent() + })?; + + if ignore_late_failure { + log_info!( + self.logger, + "Ignoring late payment failure for already-succeeded payment with ID {}.", + payment_id + ); + return Ok(()); + } + + // An unchanged or absent record must still emit the event, including on replay + // after the payment update succeeded but event-queue persistence failed. log_info!( self.logger, "Failed to send payment with ID {} due to {:?}.", payment_id, reason ); - - let update = PaymentDetailsUpdate { - hash: Some(payment_hash), - status: Some(PaymentStatus::Failed), - ..PaymentDetailsUpdate::new(payment_id) - }; - match self.payment_store.update(update).await { - Ok(_) => {}, - Err(e) => { - log_error!(self.logger, "Failed to access payment store: {}", e); - return Err(ReplayEvent()); - }, - }; - let event = Event::PaymentFailed { payment_id, payment_hash, reason }; match self.event_queue.add_event(event).await { Ok(_) => return Ok(()), diff --git a/src/payment/store.rs b/src/payment/store.rs index 41c39045f..46cc57b87 100644 --- a/src/payment/store.rs +++ b/src/payment/store.rs @@ -652,6 +652,18 @@ pub enum PaymentKind { }, } +impl PaymentKind { + pub(crate) fn is_lightning_payment(&self) -> bool { + match self { + Self::Onchain { .. } => false, + Self::Bolt11 { .. } + | Self::Bolt12Offer { .. } + | Self::Bolt12Refund { .. } + | Self::Spontaneous { .. } => true, + } + } +} + impl_writeable_tlv_based_enum!(PaymentKind, (0, Onchain) => { (0, txid, required),