diff options
| -rw-r--r-- | lib/lightning.ts | 10 | ||||
| -rw-r--r-- | routes/preimages.ts | 7 |
2 files changed, 15 insertions, 2 deletions
diff --git a/lib/lightning.ts b/lib/lightning.ts index 48410ffe..6c37ddf1 100644 --- a/lib/lightning.ts +++ b/lib/lightning.ts @@ -155,9 +155,15 @@ export async function listenForLightning() { }); // credit() may decline a duplicate idempotency claim. Only advance the - // listener once the durable payment pointer proves the ledger write won. - if (!(await getPayment(paymentRequest))) + // listener once the durable payment pointer proves the ledger write won — + // unless the preimage was already credited by an earlier settlement (a + // reused merchant preimage), which can never persist and would otherwise + // wedge the listener in a restart loop. + if (!(await getPayment(paymentRequest))) { + if (preimage && (await db.get(`credited:${preimage}`))) + return warn("preimage already credited, skipping", paymentRequest); throw new Error(`lightning credit did not persist for ${paymentRequest}`); + } })(); // db.set is awaited directly: s() is intentionally fire-and-forget and an diff --git a/routes/preimages.ts b/routes/preimages.ts index 49673d17..5e7c484f 100644 --- a/routes/preimages.ts +++ b/routes/preimages.ts @@ -51,6 +51,13 @@ export default { for (let p of preimages) { p = p.toLowerCase(); if (seen.has(p)) continue; + // A preimage that already settled (or backs a live invoice) can't be + // credited again — reject it rather than let a buyer pay for nothing. + if ( + (await db.exists(`credited:${p}`)) || + (await db.exists(`preimage:${sha256(p)}`)) + ) + fail("preimage already used"); seen.add(p); fresh.push(p); } |
