diff options
| author | Adam Soltys <[email protected]> | 2026-08-22 08:46:17 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-08-22 08:46:47 -0700 |
| commit | 9abbb87e290dc7e6749e116fbeb9a0a153c581bc (patch) | |
| tree | d440047fe6c38b14dd4c6db8fd379299572f5ed3 /scripts/audit-lightning-backlog.ts | |
| parent | d6adcee27747ad92b8ce4de1350c692f5032dfdd (diff) | |
| download | coinos-server-9abbb87e290dc7e6749e116fbeb9a0a153c581bc.tar.gz coinos-server-9abbb87e290dc7e6749e116fbeb9a0a153c581bc.zip | |
scripts: add lightning-backlog audit + kvrocks archive merge tooling
audit-lightning-backlog.ts walks settled invoices from the stored pay_index to
surface receives the listener may have missed crediting (companion to the
listener backlog probe). merge-kvrocks-archives.ts overlays one Kvrocks/Redis
archive onto a base without materializing keys in memory (raw SCAN to survive
cursors beyond JS safe-integer range), with kvrocks-merge-lowmem.conf as the
low-memory server config for the merge. All parameterized via CLI flags; no
embedded hosts or secrets.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_01XiwgmQKwKnRxUmVDd4xSf7
Diffstat (limited to 'scripts/audit-lightning-backlog.ts')
| -rw-r--r-- | scripts/audit-lightning-backlog.ts | 76 |
1 files changed, 76 insertions, 0 deletions
diff --git a/scripts/audit-lightning-backlog.ts b/scripts/audit-lightning-backlog.ts new file mode 100644 index 00000000..4c78e295 --- /dev/null +++ b/scripts/audit-lightning-backlog.ts @@ -0,0 +1,76 @@ +import { db } from "$lib/db"; +import ln from "$lib/ln"; +import { getInvoice, getPayment, getUser } from "$lib/utils"; + +const max = Number(process.argv[2] || 10_000); +const stored = Number((await db.get("pay_index")) || 0); +let cursor = stored; +const rows: any[] = []; + +for (let i = 0; i < max; i++) { + let settled: any; + try { + settled = await ln.waitanyinvoice(cursor, 1); + } catch (e: any) { + if (Number(e?.code ?? e?.errno) === 904) break; + throw e; + } + + if (!settled?.pay_index || Number(settled.pay_index) <= cursor) break; + cursor = Number(settled.pay_index); + + const mintOwned = + typeof settled.label === "string" && settled.label.startsWith("lbl"); + const paymentRequest = settled.bolt11 || settled.bolt12; + const invoiceKey = settled.bolt11 || settled.local_offer_id || settled.bolt12; + const invoice = mintOwned ? null : await getInvoice(invoiceKey); + const payment = paymentRequest ? await getPayment(paymentRequest) : null; + const user = invoice?.uid ? await getUser(invoice.uid) : null; + const claim = settled.payment_preimage + ? await db.get(`credited:${settled.payment_preimage}`) + : null; + + rows.push({ + payIndex: cursor, + amountSats: Math.round(Number(settled.amount_received_msat || 0) / 1000), + mintOwned, + invoiceFound: !!invoice, + paymentFound: !!payment, + claimFound: !!claim, + username: user?.username, + autowithdraw: !!user?.autowithdraw, + }); +} + +const coinos = rows.filter((row) => !row.mintOwned); +const actionable = coinos.filter( + (row) => row.invoiceFound && !row.paymentFound && !row.claimFound, +); + +console.log( + JSON.stringify( + { + storedPayIndex: stored, + latestPayIndex: cursor, + backlog: rows.length, + mintOwned: rows.filter((row) => row.mintOwned).length, + coinos: coinos.length, + alreadyProcessed: coinos.filter((row) => row.paymentFound).length, + missingInvoice: coinos.filter((row) => !row.invoiceFound).length, + claimedWithoutPayment: coinos.filter( + (row) => row.claimFound && !row.paymentFound, + ).length, + actionable: actionable.length, + actionableSats: actionable.reduce( + (total, row) => total + row.amountSats, + 0, + ), + autowithdraw: actionable.filter((row) => row.autowithdraw).length, + rows, + }, + null, + 2, + ), +); + +process.exit(0); |
