Every history refresh, address refresh, connect and lock-state check
rewrote the encrypted tx-history snapshot on the UI thread: a JSON
document of every row, its encryption, then INSERT OR REPLACE and a
prune, an estimated 40-120 ms on a 5k-transaction wallet, even when
nothing had changed.
Now a signature of what would be stored (about 1 ms on 5k rows)
decides whether to write. Confirmations count only by the thresholds
the UI reads (conflicted, pending, 1-9, 10-99, 100+); per-address scan
heights, exact confirmations and the tip drift every block and form a
separate part.
- Routine refreshes write a content change at most every 20 s (a
deferred one is released from the update loop) and the per-block
drift at most every 10 min. Nothing changed means no write.
- A mining-address relabel, a passphrase change, a rescan, a
disconnect, a wallet lock and exit write whatever is not on disk yet,
at once.
- The UI thread only copies the rows and the key into a job; a single
background writer (newest job wins) builds, encrypts and stores it. A
failed write is retried.
- Exit queues the final write so it overlaps the daemon stop, then
waits up to 3 s for the writer; a disk that hangs past that keeps the
previous snapshot.
- A loaded snapshot counts as stored, so the first identical refresh
doesn't rewrite it. An address refresh re-keys the snapshot only when
the address set changed.
After a hard kill the snapshot can now be up to ~20 s behind for rows
and 10 min for scan heights, exact confirmations and the tip (it was
one refresh); the first refresh replaces it either way. Lite builds,
which never read this cache, no longer write it on Refresh or a mining
relabel, and the screenshot capture mode's demo rows never reach it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>