diff options
| author | Adam Soltys <[email protected]> | 2026-07-05 21:27:06 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-07-05 21:27:06 -0700 |
| commit | 76e1a88b9c2eae1de0145ff077f038b57e68c3a8 (patch) | |
| tree | f889e8fa67e47d1d3174e6cedfec14916d493f04 /src/features | |
| parent | 71fa56f0bad19d7ea7758ab6aef2ffccd46e3c5b (diff) | |
| download | coinosv3-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/features')
| -rw-r--r-- | src/features/sp-wallet.js | 36 |
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); |
