aboutsummaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-05-20 22:29:35 -0700
committerAdam Soltys <[email protected]>2026-05-20 22:29:35 -0700
commitfe767002dd59f000d54fd09c9d617d69b9a5c894 (patch)
tree0eec59481f66c122a2c1ef56966d829317650af6 /lib
parent51107ed4b4079238ed1f657ec7231943f3c08696 (diff)
downloadcoinos-server-fe767002dd59f000d54fd09c9d617d69b9a5c894.tar.gz
coinos-server-fe767002dd59f000d54fd09c9d617d69b9a5c894.zip
tighten LEAKED DEBIT guard: don't false-alarm on successful-via-fallback paths
The first deployment surfaced two LEAKED DEBIT lines that were actually clean finalizes via the new failed_parts listpays-fallback path — exactly the path that recovers payments that would have been stuck under the old code (joho33's case). The leak guard's `outcome !== "finalized"` check was too narrow: any path that finalizes the payment, regardless of how it got there, is a success. Bucket outcomes explicitly: - finalizedOutcomes — clean successes (finalize ran, ref set) - safeNonFinalized — reverse ran, OR debit left in pending for check() to reconcile (these aren't leaks) - anything else — actually stranded (finalize-threw-*, unknown) → err Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Diffstat (limited to 'lib')
-rw-r--r--lib/payments.ts30
1 files changed, 23 insertions, 7 deletions
diff --git a/lib/payments.ts b/lib/payments.ts
index af45ebc3..5e073715 100644
--- a/lib/payments.ts
+++ b/lib/payments.ts
@@ -779,14 +779,30 @@ export const sendLightning = async ({
throw e;
}
- // Leak guard: if we got here without a clean finalize, the payment is
- // confirmed=true (from debit's default) but has no ref/preimage — the
- // exact stuck state we want to avoid. Log loudly so the next occurrence
- // shows up in app logs immediately.
- if (outcome !== "finalized") {
- err("LEAKED DEBIT in sendLightning", p.id, "outcome=", outcome, "user=", user?.username, "amount=", amount, "fee=", fee);
- } else {
+ // Leak guard: outcomes that leave a confirmed=true debit with no ref AND
+ // no entry in the pending set are the joho33-style stuck state. Pending-*
+ // outcomes are fine — check() will reconcile. Finalize-via-* outcomes
+ // are clean successes via a fallback path. The truly stranded cases are
+ // the finalize-threw-* family.
+ const finalizedOutcomes = new Set([
+ "finalized",
+ "finalized-via-listpays-after-failed_parts",
+ "finalized-after-xpay-throw",
+ ]);
+ const safeNonFinalized = new Set([
+ "reversed-via-failed_parts",
+ "reversed-after-xpay-throw",
+ "pending-after-failed_parts",
+ "pending-after-xpay-throw",
+ "verify-failed-after-failed_parts",
+ "verify-failed-after-xpay-throw",
+ ]);
+ if (finalizedOutcomes.has(outcome)) {
l("sendLightning outcome", p.id, "=", outcome);
+ } else if (safeNonFinalized.has(outcome)) {
+ warn("sendLightning outcome", p.id, "=", outcome);
+ } else {
+ err("LEAKED DEBIT in sendLightning", p.id, "outcome=", outcome, "user=", user?.username, "amount=", amount, "fee=", fee);
}
return p;