Follow-up from the review of #853.
hold_invoice_paid (src/flow.rs) now guards on the order's status and invoice_held_at, but it reads them and then acts without atomicity. A concurrent writer on the main loop — cancel_order_by_maker in src/app/cancel.rs is the concrete one — can interleave between the read and the write, so the guard can pass against state that is already stale by the time the flow commits.
Adding invoice_held_at == 0 to the guard (done in #853) closes re-entry from LND redelivering Accepted. It does not close this race — that is a separate problem and needs a conditional write.
The codebase already has the right pattern: on_bond_invoice_accepted in src/app/bond/flow.rs claims the transition with an atomic UPDATE ... WHERE and branches on rows_affected, bailing cleanly when the claim misses. hold_invoice_paid should do the same rather than read-then-act.
Low severity — it needs an exact interleaving — but the fix is a known-good pattern already in the tree.
Follow-up from the review of #853.
hold_invoice_paid(src/flow.rs) now guards on the order's status andinvoice_held_at, but it reads them and then acts without atomicity. A concurrent writer on the main loop —cancel_order_by_makerinsrc/app/cancel.rsis the concrete one — can interleave between the read and the write, so the guard can pass against state that is already stale by the time the flow commits.Adding
invoice_held_at == 0to the guard (done in #853) closes re-entry from LND redeliveringAccepted. It does not close this race — that is a separate problem and needs a conditional write.The codebase already has the right pattern:
on_bond_invoice_acceptedinsrc/app/bond/flow.rsclaims the transition with an atomicUPDATE ... WHEREand branches onrows_affected, bailing cleanly when the claim misses.hold_invoice_paidshould do the same rather than read-then-act.Low severity — it needs an exact interleaving — but the fix is a known-good pattern already in the tree.