diff options
| author | Adam Soltys <[email protected]> | 2026-06-27 17:17:58 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-06-27 17:17:58 -0700 |
| commit | 56a2736be51d2f35d68ca88b477a948efe3398ce (patch) | |
| tree | 9506c94c067f34ac5561f8d00a43f454c5956d6a /tools | |
| parent | ef183514e078169bf171e29b88c85aa16c8ecc75 (diff) | |
| download | coinosv3-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.js | 23 |
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. |
