feat(merge): run per-address consolidation as a paced background job

"Keep in each address" posted one task that called z_mergetoaddress for
every batch of every address back to back; each call is a whole-wallet
scan under cs_main, and the daemon runs the ops one at a time anyway.
It is now an App-owned services::ConsolidationRun:

- one RPC at a time; the next merge only after the previous batch left the
  queue and a gap as long as the last call took (2-120 s);
- pauses while the node syncs, the wallet is locked, a send of ours is in
  progress, or another wallet scan is running;
- failures classified: nothing that may have queued is ever resent; a
  breaker stops after 3 dead addresses or 3 failed ops in a row;
- each merge is charged to the scan budget; one re-read and one forced
  balance at the end;
- it keeps running when the dialog closes: a status-bar chip, a run view
  with progress, the wait reason and a Stop button.

The "Into one" merge can no longer be confirmed twice while its call is
in flight. fake_dragonxd gains FAKE_SCAN_SECS, FAKE_MERGE_SERIAL and
FAKE_MERGE_FAIL.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-09 11:06:34 -05:00
co-authored by Claude Opus 5.5
parent b29a09eaa0
commit cee8b9d7f6
19 changed files with 1875 additions and 202 deletions
+16
View File
@@ -2543,6 +2543,22 @@ void I18n::loadBuiltinEnglish()
strings_["merge_keep_each_status_running"] = "%s — merging %d notes (%d of %d batches done)…";
strings_["merge_keep_each_status_unconfirmed"] = "%s \xE2\x80\x94 status could not be confirmed";
strings_["merge_keep_each_run_again"] = "Addresses merged in several batches now hold one note per batch. Run this again after those confirm to merge them into one.";
strings_["merge_run_progress_fmt"] = "Consolidating\xE2\x80\xA6 batch %d of about %d, %d notes merged so far. You can close this window; it keeps running in the background.";
strings_["merge_run_stop"] = "Stop";
strings_["merge_run_stopping"] = "Stopping after the batches already sent\xE2\x80\xA6";
strings_["merge_run_status_waiting"] = "%s \xE2\x80\x94 waiting (%d notes)";
strings_["merge_run_status_not_run"] = "%s \xE2\x80\x94 not consolidated (%d notes)";
strings_["merge_run_wait_sync"] = "Paused: the node is still syncing.";
strings_["merge_run_wait_send"] = "Paused until your pending transaction goes through.";
strings_["merge_run_wait_batch"] = "Spacing the batches so the node stays responsive\xE2\x80\xA6";
strings_["merge_run_err_timeout"] = "The node did not answer in time; the batch may still be queued.";
strings_["merge_run_stopped_toast"] = "Consolidation stopped. Run it again to merge the rest.";
strings_["merge_run_aborted_toast"] = "The node connection was reset, so consolidation stopped. Run it again to continue.";
strings_["merge_run_start_failed"] = "Couldn't start the consolidation \xE2\x80\x94 the node isn't connected.";
strings_["merge_run_chip_fmt"] = "Consolidating %d/%d";
strings_["merge_run_halted_fmt"] = "Consolidation stopped after repeated failures: %s";
strings_["merge_run_busy"] = "A consolidation is running. Wait for it to finish, or stop it, first.";
strings_["merge_run_migration_pending"] = "Finish or cancel the seed migration before consolidating.";
strings_["done"] = "Done";
// --- Transaction Details Dialog ---