Lightning payment idempotency gate + listener resilience #56
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "fix/payment-idempotency"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Second PR of the refactor series (stacked on #55 — merge that first; this PR's base is
fix/idempotent-migrationsso only its own commits show).Problem (CODE-REVIEW-2026-06 #4, #9, #10): Lightning payment recording relied entirely on Fava-side duplicate checks, which are read-then-write races. On restart with a persisted invoice queue, or webhook + poller firing together, the same
payment_hashcould be recorded twice. One unhandled exception inon_invoice_paidalso killed the invoice listener for the rest of the process lifetime.Changes:
processed_paymentstable (migration m005, idempotent):payment_hashPRIMARY KEY makes exactly one claimant win viaINSERT ... ON CONFLICT DO NOTHING. Claims are'processing'while in flight,'done'after; failures release the claim so redelivery retries; stale claims from a crashed process are cleared at listener startup.POST /record-paymentshares the same claim gate ('done' → replays "already recorded"; in-flight → 409) and now fails closed on Fava transport errors during the duplicate check (503) instead of proceeding to write.{base_url}/api/journal—base_urlalready ends in/api, so the check has been silently 404ing and never ran. Fixed.Note: libra-#22 (account-sync racing Fava init) turned out to be already fixed —
wait_for_account_syncgates onwait_for_fava_client()since09a5d6e; that issue can be closed.Tests: new
tests/test_payment_idempotency.py— double delivery records once; failed recording releases the claim and retry succeeds; listener survives a poison payment and clears stale claims; endpoint 409s while in flight and replays safely. Full suite: 158 passed, 3 skipped, 8 xfailed.🤖 Generated with Claude Code
Two fixes to POST /api/v1/record-payment: - The Fava duplicate check caught every exception and proceeded to write, so a transient Fava blip produced double entries. It now fails closed: transport errors return 503 and the client retries. While here: the check queried {base_url}/api/journal, but base_url already ends in /api — the doubled path 404'd, meaning the duplicate check has silently never run. - The endpoint now goes through the same processed_payments claim gate as the background invoice listener, so the webhook+poller pair can't both record the same payment_hash: a 'done' claim replays as "already recorded", an in-flight claim returns 409. Addresses CODE-REVIEW-2026-06 finding #10. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.Merge
Merge the changes and update on Forgejo.Warning: The "Autodetect manual merge" setting is not enabled for this repository, you will have to mark this pull request as manually merged afterwards.