aboutsummaryrefslogtreecommitdiffstats
path: root/tools
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-06-27 17:17:58 -0700
committerAdam Soltys <[email protected]>2026-06-27 17:17:58 -0700
commit56a2736be51d2f35d68ca88b477a948efe3398ce (patch)
tree9506c94c067f34ac5561f8d00a43f454c5956d6a /tools
parentef183514e078169bf171e29b88c85aa16c8ecc75 (diff)
downloadcoinosv3-56a2736be51d2f35d68ca88b477a948efe3398ce.tar.gz
coinosv3-56a2736be51d2f35d68ca88b477a948efe3398ce.zip
Catch-up scaling: dust filter + per-block Bloom filters
silentpay.js: silentPaymentCandidate() (our P_0 for a tweak) + buildBloom/bloomHas (double-hashed sha256, ~0.8% FP). Validated: candidate == P_0 on all no-label vectors. Indexer /scan now returns, per block, the tweaks + a Bloom over that block's taproot output keys (no outputs), with an optional dustLimit. The wallet derives a candidate per tweak, tests the filter, and only fetches /block on a hit — so a big historical scan costs candidate math + a handful of fetches instead of downloading and scanning every block. Live WS pushes still carry full items. SP_DUST_LIMIT defaults to 0 (receive everything; the indexer honors any value). Verified end-to-end on regtest: the filter flow finds the output. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Diffstat (limited to 'tools')
-rw-r--r--tools/sp-indexer.js23
1 files changed, 16 insertions, 7 deletions
diff --git a/tools/sp-indexer.js b/tools/sp-indexer.js
index 09f9a19..934c55c 100644
--- a/tools/sp-indexer.js
+++ b/tools/sp-indexer.js
@@ -16,7 +16,7 @@
import { Database } from 'bun:sqlite';
import net from 'node:net';
import http from 'node:http';
-import { inputPubKey, silentPaymentTweak } from '../src/silentpay.js';
+import { inputPubKey, silentPaymentTweak, buildBloom } from '../src/silentpay.js';
const RPC = process.env.CORE_RPC || 'http://127.0.0.1:18443';
const RPC_USER = process.env.RPC_USER || 'admin1';
@@ -216,13 +216,22 @@ const server = Bun.serve({
const b = qGetBlock.get(Number(m[1]));
return json(b ? JSON.parse(b.items).map((i) => i.tweak) : []);
}
- // All SP-eligible tx items from height `from` to the tip, in one response —
- // each tagged with its height. Lets a client scan a range without one fetch
- // per block. (A future BIP-158/Bloom filter will cut this for large ranges.)
+ // Catch-up over a height range: per block, the tweaks (dust-filtered) + a
+ // Bloom filter over that block's taproot output keys — NOT the outputs. The
+ // client derives its candidate key per tweak, tests the filter, and only
+ // fetches /block for the rare hits. dustLimit (sats) drops txs whose taproot
+ // outputs are all below it (fewer tweaks to check).
if ((m = p.match(/^\/scan\/(\d+)$/))) {
- const items = [];
- for (const row of qScanFrom.all(Number(m[1]))) for (const it of JSON.parse(row.items)) items.push({ height: row.height, ...it });
- return json({ tip: indexed, items });
+ const dust = Number(url.searchParams.get('dustLimit') || 0);
+ const blocks = [];
+ for (const row of qScanFrom.all(Number(m[1]))) {
+ let items = JSON.parse(row.items);
+ if (dust > 0) items = items.filter((it) => it.outputs.some((o) => o.value >= dust));
+ if (!items.length) continue;
+ const outKeys = items.flatMap((it) => it.outputs.map((o) => o.xonly));
+ blocks.push({ height: row.height, tweaks: items.map((it) => it.tweak), filter: buildBloom(outKeys) });
+ }
+ return json({ tip: indexed, blocks });
}
// Current unconfirmed (mempool) SP-eligible txs — for the pending catch-up on
// connect; live updates arrive as {type:'mempool'} WS pushes.