Repository navigation
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 |
ldk-node 0.7.0 marks an in-flight outbound payment Failed when an inbound HTLC uses the same payment hash. cdk-ldk-node reports that status as a failed melt. A melt requester who holds the HTLC of their own invoice and keysends the same hash to the mint could thus release the melt proofs and then settle the HTLC. ldk-node 0.7.1 fixes these bugs: - an inbound HTLC that reuses an outbound payment hash no longer fails the outbound payment (lightningdevkit/ldk-node#1157); - a late PaymentFailed no longer overwrites a succeeded payment (lightningdevkit/ldk-node#1065); - a ChannelManager read error stops the build instead of starting a fresh node (lightningdevkit/ldk-node#1032). lightning 0.2.8 (with lightning-liquidity, lightning-transaction-sync and lightning-persister 0.2.8, and lightning-invoice 0.34.2) fixes: - a malicious BOLT11 route hint that removes channels from the graph (lightningdevkit/rust-lightning#5503); - an MPP startup panic after a channel monitor is archived (#5528); - a ChannelManager that fails to load after several payments to one hash (#5541); - anchor commitments held back when the fee-bump input cannot be funded (4825c089e); - 64-byte transactions skipped in Esplora sync (#5559). Only these six packages change in Cargo.lock and Cargo.lock.msrv. cargo update also re-resolved unrelated multi-version range dependencies, so those edges keep their earlier versions. The other LDK crates are already at their latest compatible releases. The workspace floors now require the fixed versions, so a regenerated lock cannot select the vulnerable ones. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WDmqYFNSovaSho9M89aXC9
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.