aboutsummaryrefslogtreecommitdiffstats
path: root/scripts/audit-lightning-backlog.ts
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-08-22 08:46:17 -0700
committerAdam Soltys <[email protected]>2026-08-22 08:46:47 -0700
commit9abbb87e290dc7e6749e116fbeb9a0a153c581bc (patch)
treed440047fe6c38b14dd4c6db8fd379299572f5ed3 /scripts/audit-lightning-backlog.ts
parentd6adcee27747ad92b8ce4de1350c692f5032dfdd (diff)
downloadcoinos-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.ts76
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);