wallet: don't delete payment infos when deleting paid/inflight invoice/req
What changed, and why it matters
This commit fixes a bug in the Electrum Bitcoin wallet where deleting a paid or in-flight Lightning invoice/request could incorrectly erase important payment records. The change now preserves those records when a payment has already completed or is still pending, preventing loss of payment history and avoiding a minor privacy leak if an in-flight payment later fails.
Review whether any existing user databases already lost PaymentInfo for paid/inflight invoices and consider a data-integrity note in release notes. No immediate exploit mitigation is required, but users should update to the fixed version.
Security signals we found
Data-loss / history-integrity bug: paid payment metadata could be removed
Privacy leak: in-flight Lightning payment info deleted prematurely may leave a payment that cannot be cleaned up later
State-consistency fix: payment status now gates deletion of PaymentInfo
Evidence from the diff
In electrum/wallet.py, delete_request() and delete_invoice() previously called lnworker.delete_payment_info() unconditionally for Lightning invoices/requests. The patch adds status checks: delete_request() skips deletion only when status is PR_PAID, while delete_invoice() skips deletion when status is PR_PAID or PR_INFLIGHT. A new PR_INFLIGHT constant is imported. The inline comment notes that deleting PaymentInfo for paid invoices removes history, and deleting it for inflight invoices can leak the payment if it later fails (because the record needed to clean up is gone).
Changed components
electrum/wallet.pyLightning invoice/request deletion logicPaymentInfo storage in lnworkerInspect captured patch +7 / −3
### electrum/wallet.py
@@ -76,7 +76,7 @@
AddressSynchronizer, TX_HEIGHT_LOCAL, TX_HEIGHT_UNCONF_PARENT, TX_HEIGHT_UNCONFIRMED, TX_HEIGHT_FUTURE,
TX_TIMESTAMP_INF
)
-from .invoices import BaseInvoice, Invoice, Request, PR_PAID, PR_UNPAID, PR_EXPIRED, PR_UNCONFIRMED
+from .invoices import BaseInvoice, Invoice, Request, PR_PAID, PR_UNPAID, PR_EXPIRED, PR_UNCONFIRMED, PR_INFLIGHT
from .contacts import Contacts
from .mnemonic import Mnemonic
from .lnworker import LNWallet
@@ -3098,7 +3098,8 @@ def delete_request(self, request_id, *, write_to_disk: bool = True):
self._receive_requests.pop(request_id, None)
if addr := req.get_address():
self._requests_addr_to_key[addr].discard(request_id)
- if req.is_lightning() and self.lnworker:
+ if req.is_lightning() and self.lnworker \
+ and self.lnworker.get_invoice_status(req) != PR_PAID:
self.lnworker.delete_payment_info(req.rhash, direction=RECEIVED)
if write_to_disk:
self.save_db()
@@ -3109,7 +3110,10 @@ def delete_invoice(self, invoice_id, *, write_to_disk: bool = True):
if inv is None:
return
self._paid_invoice_keys_cache.discard(invoice_id)
- if inv.is_lightning() and self.lnworker:
+ if inv.is_lightning() and self.lnworker \
+ and self.lnworker.get_invoice_status(inv) not in (PR_PAID, PR_INFLIGHT):
+ # if an invoice was paid we need the PaymentInfo for the history and don't delete it.
+ # if it is still inflight and the payment fails later on we leak it and never delete it.
self.lnworker.delete_payment_info(inv.rhash, direction=SENT)
if write_to_disk:
self.save_db()Why this scored 33/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.