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>
Users who keep funds apart under different labelled addresses can now
shed notes (smaller wallet, faster scans/witness rebuilds) without the
funds being merged into a single address.
- Merge/Consolidate dialog: "Where should the funds go?" - Into one
address (unchanged) or Keep in each address. The latter lists shielded
addresses with 2+ spendable notes (label, note count, amount;
too-small ones disabled) and merges each address's notes back into
that address with z_mergetoaddress [addr] addr. Addresses with more
notes than the batch size get several batches (the daemon locks a
batch's notes before returning, so calls take disjoint notes); the
dialog suggests a second run after those confirm. Per-address
progress; ops keep running if the dialog is closed. The Consolidate
(wallet-size) preset opens in this mode; Merge keeps "Into one".
Shielded only: a transparent address can't take a merge's output.
- services/note_consolidation: groupSpendableNotes, worthConsolidating,
estimateBatches, consolidateInPlace (pure; driven by a merge-call
function), with unit tests against a stand-in following dragonxd's
rules.
- fake_dragonxd: optional note wallet (FAKE_WALLET_NOTES,
FAKE_MERGE_SECS) with z_mergetoaddress and its operations.
- i18n: 17 new keys in all 8 languages; CJK subset rebuilt.
- UI sweep: modal-consolidate target.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- getrescaninfo takes no lock in dragonxd (it only reads wallet fields),
so the stub no longer holds it behind cs_main. Holding it there made
the GUI's rescan-progress poll look like an abandoned request every
30 s during a simulated rebuild.
- a `methods` event lists the 10 most-requested JSON-RPC methods with
counts (alongside `stats`, and at exit): what the GUI is polling.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Holds cs_main like a witness rebuild but logs no rebuild markers: a node
that is busy while its output says nothing (an external node, or a
rebuild path that prints no progress), for exercising the GUI's
"busy but alive" handling.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A scriptable fake dragonxd that reproduces, in minutes, the incident where
the GUI hard-killed a node busy with an hour-long witness rebuild:
- dragonxd's HTTP-RPC queue (8 workers + 16 queued, -rpcthreads /
-rpcworkqueue honoured): a full queue answers an immediate plain-text
500 "Work queue depth exceeded", and a request whose client gave up
keeps its slot until a worker reaches it
- a global cs_main held by a "witness rebuild" that logs the real
markers; every method except stop/help takes it
- warmup -28 with the daemon's allow-list, an optional startup replay
- dragonxd's shutdown order: stop (or SIGTERM on POSIX) unbinds RPC,
503s open connections, waits for the rebuild, then logs
"Shutdown: In progress..." / "Shutdown: done" and exits 0
Everything is appended to <datadir>/fake_dragonxd.events; a missing
"clean-exit" line means the process was killed. One portable C++17 file
(POSIX + Winsock), OFF by default (-DDRAGONX_BUILD_FAKE_DAEMON=ON), built
into <build>/fake_dragonxd/ so it can't shadow the real bin/dragonxd.
run-gui-with-fake.sh runs the GUI against it in a throwaway HOME.
Against master's GUI on Linux it reproduces the bug: closing during a
rebuild queues stop behind cs_main-blocked workers, then the GUI sends
SIGTERM and SIGKILL mid-rebuild, with no clean exit.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>