Problem
In 0.7.0-rc.66 (commit 190a17d966cf5a685444cfa17c3a419c290bf26c), an outbound BOLT11 payment can reach LDK before its LDK Node payment-history record is saved. A crash or persistence error at that boundary can leave a real payment absent from payment(id) and list_payments().
This is confirmed by source inspection. A fault-injection reproduction has not yet been run.
Evidence
Bolt11Payment::send calls channel_manager.pay_for_bolt11_invoice(...) before payment_store.insert(payment) at line 160. send_using_amount has the same ordering.
DataStore::insert persists before inserting into the in-memory map. A write failure therefore leaves no newly inserted history record and returns PersistenceFailed, even though dispatch has already been initiated.
- When a
PaymentSent event has no matching record, the handler logs Sent payment with ID ... couldn't be found in store and returns Ok(()) without emitting PaymentSuccessful. The PaymentFailed handler also discards events for missing records.
Node::payment and list_payments only inspect the payment store. Core LDK has ChannelManager::list_recent_payments() for crash recovery, but the current mobile interface does not expose it or an equivalent reconciled outcome.
Downstream impact
Bitkit must correlate an incoming Paykit payment request with its actual Lightning payment. A missing history record is ambiguous: the app may have crashed before calling LDK, or after LDK accepted the payment. Treating absence as failure after a timeout could permit a duplicate payment. Conservatively preserving the attempt can leave a subscription period hidden from the payable queue and prevent subscription cancellation.
Tracked in synonymdev/bitkit-ios#685 and synonymdev/bitkit-android#1186. The apps are tightening submission-error handling separately. This issue covers full recovery, not Paykit protocol changes.
Required outcome
- Persist payment intent before dispatch and reconcile interrupted submissions against underlying Lightning state after restart. Moving the write alone is insufficient because a crash before dispatch would otherwise leave an orphaned Pending record.
- Preserve successful and failed outcomes durably, including events whose history record is missing, without losing the preimage or permitting duplicate event processing to overwrite terminal state.
- Give consumers a reliable way to distinguish a payment that was never submitted or definitively failed from one still pending or already completed. This may be implemented within the existing payment APIs or via a focused recovery API.
- Do not infer failure solely from an elapsed timeout, invoice expiry, or absence from the payment-history store.
Verification needed
Add fault-injection coverage for crashes before dispatch, after dispatch but before payment-history persistence, and while persisting/handling terminal events. Include a payment-history write failure after dispatch. Verify restart recovery, durable preimages, terminal event delivery, and prevention of duplicate sends.
Problem
In
0.7.0-rc.66(commit190a17d966cf5a685444cfa17c3a419c290bf26c), an outbound BOLT11 payment can reach LDK before its LDK Node payment-history record is saved. A crash or persistence error at that boundary can leave a real payment absent frompayment(id)andlist_payments().This is confirmed by source inspection. A fault-injection reproduction has not yet been run.
Evidence
Bolt11Payment::sendcallschannel_manager.pay_for_bolt11_invoice(...)beforepayment_store.insert(payment)at line 160.send_using_amounthas the same ordering.DataStore::insertpersists before inserting into the in-memory map. A write failure therefore leaves no newly inserted history record and returnsPersistenceFailed, even though dispatch has already been initiated.PaymentSentevent has no matching record, the handler logsSent payment with ID ... couldn't be found in storeand returnsOk(())without emittingPaymentSuccessful. ThePaymentFailedhandler also discards events for missing records.Node::paymentandlist_paymentsonly inspect the payment store. Core LDK hasChannelManager::list_recent_payments()for crash recovery, but the current mobile interface does not expose it or an equivalent reconciled outcome.Downstream impact
Bitkit must correlate an incoming Paykit payment request with its actual Lightning payment. A missing history record is ambiguous: the app may have crashed before calling LDK, or after LDK accepted the payment. Treating absence as failure after a timeout could permit a duplicate payment. Conservatively preserving the attempt can leave a subscription period hidden from the payable queue and prevent subscription cancellation.
Tracked in synonymdev/bitkit-ios#685 and synonymdev/bitkit-android#1186. The apps are tightening submission-error handling separately. This issue covers full recovery, not Paykit protocol changes.
Required outcome
Verification needed
Add fault-injection coverage for crashes before dispatch, after dispatch but before payment-history persistence, and while persisting/handling terminal events. Include a payment-history write failure after dispatch. Verify restart recovery, durable preimages, terminal event delivery, and prevention of duplicate sends.