Ignore late failures for successful payments - #1065
Conversation
|
I've assigned @tnull as a reviewer! |
| }; | ||
| }, | ||
| LdkEvent::PaymentFailed { payment_id, payment_hash, reason, .. } => { | ||
| 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()); | ||
| }, | ||
| }; | ||
|
|
||
| // LDK may emit `PaymentFailed` after `PaymentSent` in exceedingly rare cases. | ||
| // The payment-store update above preserves success in that case; re-read the | ||
| // resulting state so we also avoid surfacing a contradictory public event. | ||
| match self.payment_store.get(&payment_id).await { | ||
| Ok(Some(payment)) if payment.status == PaymentStatus::Succeeded => { | ||
| log_info!( | ||
| self.logger, | ||
| "Ignoring late payment failure for already-succeeded payment with ID {}.", | ||
| payment_id | ||
| ); | ||
| return Ok(()); | ||
| }, | ||
| Ok(_) => {}, | ||
| Err(e) => { | ||
| log_error!(self.logger, "Failed to access payment store: {}", e); | ||
| return Err(ReplayEvent()); | ||
| }, | ||
| } | ||
|
|
||
| log_info!( | ||
| self.logger, | ||
| "Failed to send payment with ID {} due to {:?}.", | ||
| payment_id, | ||
| reason | ||
| ); | ||
|
|
||
| let event = Event::PaymentFailed { payment_id, payment_hash, reason }; | ||
| match self.event_queue.add_event(event).await { | ||
| Ok(_) => return Ok(()), |
There was a problem hiding this comment.
This is again related to the split-brain situation with persistence, but can the initial payment store write fail after LDK accepts the payment? PaymentSent then accepts Ok(NotFound) and emits success, so a replayed late failure could escape this check after restart.
4df77f4 to
8cf15f2
Compare
tnull
left a comment
There was a problem hiding this comment.
Please squash, one minor comment though.
1accb18 to
73dc6c4
Compare
Ignore PaymentFailed when an outbound Lightning payment has already succeeded. Check and update the record atomically, preserving its metadata and suppressing the contradictory failure notification. Continue emitting failure events for unchanged or missing records so replay can recover from event-queue persistence failures. Developed with assistance from OpenAI Codex.
73dc6c4 to
1bb6aa3
Compare
|
Sorry, squashed into 1 commit now when you said prefactor commit I thought you wanted that one on its own. |
Ah, indeed. Just skimmed the history and saw the |
rust-lightning documents that PaymentFailed can arrive after PaymentSent in rare cases. In that ordering, the failure must be ignored and the payment must be treated as successful:
https://github.com/lightningdevkit/rust-lightning/blob/9174965af9437196c527a9aa0df36bbcf050c8bb/lightning/src/events/mod.rs#L1230-L1233
Keep succeeded outbound Lightning records monotonic and suppress the contradictory user-facing PaymentFailed event. Cover both BOLT11 and BOLT12 on the persistence-backed store path.
Developed with assistance from OpenAI Codex.