feat(wallet): optional background auto-consolidation (Settings toggle)
A mining/pool wallet can accumulate tens of thousands of shielded notes, and the
manual "Consolidate" run — capped at 20 batches per address per click — needs
many re-runs to flatten a 10k+ note address. This adds a hands-off alternative:
a Settings toggle ("Auto-consolidate notes", full-node, off by default) that
trickle-merges notes in the background while the wallet is idle, so it sheds
notes over hours/days without the user ever opening the dialog.
Deliberately gentle, per the request: a small batch (15 inputs/tx vs 50 manual)
and only 3 batches per address per cycle, with cycles ~10 min apart — each cycle
is a handful of tiny merges that barely touch the node. It only acts when the
wallet is genuinely idle (walletScanReady: connected, synced, unlocked, node not
busy, not rescanning) and no send / shield / manual run is in progress, and it
goes dormant (re-scanning hourly) once every address is tidy.
Implementation reuses the existing paced services::ConsolidationRun engine in
"keep in each address" mode, so there is NO fund-path change and the run's
pacing / failure breakers / note-locking all apply:
- App::pumpAutoConsolidation (pumped from update() after pumpConsolidation):
finalizes a finished cycle (schedules the cooldown + dismisses the run so the
manual dialog never shows a stale auto result), else starts a new cycle when
idle and the cooldown has elapsed.
- postAutoConsolidationScan: a quiet z_listunspent scan (the dialog's loadScope
minus the UI state), session-guarded; on result picks candidates and starts.
- startConsolidationQuiet: the same guards as startConsolidation but silent, with
a gentle RunConfig; finishConsolidation stays silent for auto runs (logs only).
- services::autoConsolidateCandidates (pure, unit-tested): addresses above the
note floor that are worth merging; plus the gentle kAutoConsolidate* constants.
- The one-time large-wallet nudge is suppressed while auto-consolidation is on.
Settings plumbing (auto_consolidate, default false) + a daemon-gated toggle in
the Wallet tab's options, with a tooltip. Build + tests + hygiene green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -7332,6 +7332,19 @@ void testNoteConsolidation()
|
||||
EXPECT_FALSE(worthConsolidating(groups[1], fee)); // a single note: nothing to merge
|
||||
EXPECT_FALSE(worthConsolidating(NoteGroup{"zsD", 4, 0.00015}, fee)); // the fee would eat it
|
||||
|
||||
// Auto-consolidation candidates: above the per-address note floor AND worth it. A near-tidy address
|
||||
// (at/below the floor) is left alone so the background driver can go dormant.
|
||||
std::vector<NoteGroup> autoGroups{
|
||||
{"zBig", 120, 50.0}, // above floor, worth it -> candidate
|
||||
{"zAtFloor", 25, 10.0}, // exactly the floor -> excluded (> is strict)
|
||||
{"zSmall", 8, 5.0}, // below floor -> excluded
|
||||
{"zDust", 200, 0.00005} // above floor but fee would eat it -> excluded
|
||||
};
|
||||
const auto cand = autoConsolidateCandidates(autoGroups, kAutoConsolidateMinNotes, fee);
|
||||
EXPECT_EQ(cand.size(), size_t(1));
|
||||
EXPECT_EQ(cand[0].address, std::string("zBig"));
|
||||
EXPECT_TRUE(autoConsolidateCandidates({}, kAutoConsolidateMinNotes, fee).empty());
|
||||
|
||||
EXPECT_EQ(estimateBatches(0, 90), 0);
|
||||
EXPECT_EQ(estimateBatches(1, 90), 0);
|
||||
EXPECT_EQ(estimateBatches(2, 90), 1);
|
||||
|
||||
Reference in New Issue
Block a user