aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-07-05 21:27:06 -0700
committerAdam Soltys <[email protected]>2026-07-05 21:27:06 -0700
commit76e1a88b9c2eae1de0145ff077f038b57e68c3a8 (patch)
treef889e8fa67e47d1d3174e6cedfec14916d493f04 /src
parent71fa56f0bad19d7ea7758ab6aef2ffccd46e3c5b (diff)
downloadcoinosv3-76e1a88b9c2eae1de0145ff077f038b57e68c3a8.tar.gz
coinosv3-76e1a88b9c2eae1de0145ff077f038b57e68c3a8.zip
Chunk the silent-payment catch-up scan
An imported mainnet wallet's first scan pulled the indexer's whole window (~336k tweaks, 25MB) and posted it to the worker as ONE message: it blew the 30s worker timeout, fell back to the inline main-thread loop (minutes of jank) while the worker kept grinding the orphaned request — the runaway CPU/memory users saw. The catch-up now processes 100-block chunks: each worker call is seconds, the scan watermark checkpoints after every chunk (an interrupted catch-up resumes instead of restarting from zero), buffers free as it goes, and the UI yields between chunks. Verified against the live indexer: full catch-up in ~5min of background work, main heap bounded (~80MB peak), no timeouts. Co-Authored-By: Claude Fable 5 <[email protected]>
Diffstat (limited to 'src')
-rw-r--r--src/features/sp-wallet.js36
1 files changed, 26 insertions, 10 deletions
diff --git a/src/features/sp-wallet.js b/src/features/sp-wallet.js
index c2d1cdb..e72532c 100644
--- a/src/features/sp-wallet.js
+++ b/src/features/sp-wallet.js
@@ -212,18 +212,34 @@ export function installSpWallet(wallet) {
const tip = res.tip || 0;
const have = new Set(this.spUtxos.map((u) => `${u.txid}:${u.vout}`));
const found = [];
- const hits = await this._spHitBlocks(res.blocks || []);
- for (const block of res.blocks || []) {
- if (!hits.has(block.height)) continue;
- const blk = await (await fetch(`${indexer}/block/${block.height}`)).json();
- for (const f of await this._spScanItems(blk.items || [])) {
- const id = `${f.txid}:${f.vout}`;
- if (have.has(id)) continue;
- have.add(id);
- found.push(f);
+ const blocks = res.blocks || [];
+ // Process in chunks. An imported wallet's first catch-up can be the
+ // indexer's whole window (hundreds of thousands of tweaks, ~25MB) —
+ // sent as ONE worker message it blew the 30s worker timeout, fell
+ // back to the inline loop (janking the UI for minutes while the
+ // worker kept grinding the orphaned request), and held the giant
+ // buffers live the whole time. Chunks keep each call sub-second, let
+ // the scan watermark advance as blocks complete (interruptions
+ // resume instead of restarting), and free memory as they go.
+ const CHUNK = 100;
+ for (let c = 0; c < blocks.length; c += CHUNK) {
+ const slice = blocks.slice(c, c + CHUNK);
+ const hits = await this._spHitBlocks(slice);
+ for (const block of slice) {
+ if (!hits.has(block.height)) continue;
+ const blk = await (await fetch(`${indexer}/block/${block.height}`)).json();
+ for (const f of await this._spScanItems(blk.items || [])) {
+ const id = `${f.txid}:${f.vout}`;
+ if (have.has(id)) continue;
+ have.add(id);
+ found.push(f);
+ }
}
+ const maxH = slice[slice.length - 1].height;
+ if (maxH > (this.lastSpScan || 0)) { this.lastSpScan = maxH; this.saveCache(); } // checkpoint
+ await new Promise((r) => setTimeout(r, 0)); // let the UI breathe
}
- if (tip > this.lastSpScan) this.lastSpScan = tip;
+ if (tip > (this.lastSpScan || 0)) this.lastSpScan = tip;
// Verify newly-found outputs are unspent + get confirmation status.
for (const f of found) {
const address = this._spAddress(f.xonly);