On a wallet with a very large shielded note set, the daemon re-decrypts all
notes under cs_main on every z_gettotalbalance / z_listunspent (GetFilteredNotes),
~15-33s per pass. The GUI issued these roughly every 15s and timed them out at
the 30s RPC limit, so the node looked permanently "busy" and per-address
balances read 0 (nothing to carry on a fresh session) while the total was fine.
Two wallet-side mitigations:
- A cost-proportional floor on VISIBLE balance polls (wallet_scan_governor): the
adaptive backoff previously applied only when the balance was off-screen. It
now also holds a visible poll until ~3x the measured scan cost has elapsed, so
a wallet whose sweep costs tens of seconds can't re-poll before the node
recovers. Negligible for small wallets (cost ~ms); forced / first-read bypass it.
- The heavy balance RPCs (z_gettotalbalance, z_listunspent) now use a 120s reply
timeout via the existing callLong path, so a slow pass completes and populates
per-address balances instead of aborting at 30s and leaving them at 0.
The root fix is daemon-side (cache decrypted notes so GetFilteredNotes isn't
O(notes x decrypt) per call); specced separately. Adds governor test coverage.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A z_sendmany submit set off a burst of wallet scans before anything had
changed. Ops now re-read the wallet when they finish, by who started them:
the user's sends and dialog ops get a re-read plus one forced balance once
none of their ops is pending; client auto-shield re-reads unforced; chat
messages and splits refresh only the chat note view. A pending send no
longer forces scans, and chat txids no longer enter the tracked-send set.
Chat ingest skips stored messages before decrypting and caches
undecryptable ones per identity. The inbox scan runs when getwalletinfo's
txcount changed (a cheap probe, at most 2% duty) or a cost-scaled safety
net is due; the note-buffer scan runs on need. Both are admitted by the
scan budget, on a steady clock. The chat payer comes from a per-address
view of verified notes, with debits re-applied across in-flight scans;
splits are sized to one payer and only when they can refill the buffer.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Connect gate: after the node starts answering, wallet scans wait until the
lock state is known and the node knows its tip (or the wallet has fewer
than 500 transactions), at most 20 s. A node already behind latches the
sync profile from the connect's getinfo. Meanwhile a list-only pass keeps
the address list and the cached history up, and the session's first
balance and per-address balances are read once even while catching up,
so the wallet shows its last-synced values instead of 0.
- Background: minimized, hidden, or covered without focus (3 s debounce,
any input cancels) polls only the chain state every 15 s. Wallet scans,
the mining poll, price, nudges and auto-lock interaction tracking pause;
auto-lock now fires while minimized. Leaving a held profile marks every
timer due again.
- Tab entry marks the timers due instead of forcing scans; a stale list or
a never-loaded history still loads on entry.
- The scan entry points own the hard gates, failed enumerations keep the
previous lists behind a retry hold, and failed balance calls never
overwrite known balances with 0.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The balance, address scan, history and probe were throttled one by one at
10% each, which added up to 30-40% of dragonxd's cs_main with all of them
due. services::WalletScanGovernor runs one wallet scan at a time across
both RPC lanes and keeps one shared quiet clock at 15% of wall-clock (cut
when the node is behind). Waiters are served first in, first out, with a
head start for what the page shows and a starvation floor of max(30 s
visible / 900 s hidden, 15x the stream's cost). Explicit work (a dirty
set, the user's send, a first load) is never refused but is charged.
Routine scans run only when the tip, the mempool size (getmempoolinfo:
no cs_main) or a wallet-mutation epoch changed since their last good scan,
or a safety sweep is due. Pages that don't show the balance poll it at
most every max(10 min, 50x its cost) (2 min with client-side auto-shield);
coming back into view with an old value fetches it at once. The staleness
badge counts from the last poll that confirmed nothing changed, and hides
while the budget holds the balance within its guarantee.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>