wallet: fix consolidation-scheduler wedge (ran every block; dead mutual-exclusion)

The Sapling auto-consolidation scheduler never advanced nextConsolidation after
init and never set fConsolidationRunning, so once the tip passed the init
threshold `-consolidation` dispatched a consolidation op every block instead of
once per -consolidationinterval, and every guard that reads fConsolidationRunning
(in RunSaplingSweep, and in the new autoshield driver) was dead.

Mirror the intended scheduler model:
- RunSaplingConsolidation sets fConsolidationRunning=true before dispatch and
  self-guards with `if (fConsolidationRunning) return;`.
- The consolidation op advances nextConsolidation = consolidationInterval +
  tipHeight and clears fConsolidationRunning on every terminal state
  (success/failure/exception/cancel), guarded by op id so only the current op
  mutates scheduler state.
- saplingConsolidationOperationId moved to public so the op can read it.

Restores the documented once-per-interval cadence and makes the
sweep/consolidation/autoshield mutual-exclusion guards effective.

Note: the sweep op has the same latent wedge (fSweepRunning/nextSweep are
cleared only on the sweepComplete success path, so a cancelled or throwing
sweep leaves fSweepRunning stuck true) - left for a follow-up; this commit is
the template to port.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-21 18:28:17 -05:00
co-authored by Claude Opus 4.8
parent 2ebbbc777c
commit ca730a5d98
3 changed files with 35 additions and 5 deletions
+7 -3
View File
@@ -644,6 +644,12 @@ void CWallet::RunSaplingConsolidation(int blockHeight) {
return;
}
// Self-guard: an op is already in flight (nextConsolidation only advances
// when it completes). Don't cancel + re-enqueue a fresh op every block.
if (fConsolidationRunning) {
return;
}
LogPrintf("%s: consolidation enabled at blockHeight=%d fSweepRunning=%d\n", __func__, blockHeight, fSweepRunning );
if (fSweepRunning) {
@@ -657,6 +663,7 @@ void CWallet::RunSaplingConsolidation(int blockHeight) {
}
LogPrintf("%s: creating consolidation operation at blockHeight=%d\n", __func__, blockHeight);
fConsolidationRunning = true;
std::shared_ptr<AsyncRPCQueue> q = getAsyncRPCQueue();
std::shared_ptr<AsyncRPCOperation> lastOperation = q->getOperationForId(saplingConsolidationOperationId);
if (lastOperation != nullptr) {
@@ -697,9 +704,6 @@ void CWallet::RunAutoShieldCoinbase(int blockHeight) {
// Mutual exclusion: sweep/consolidation share the single async worker and
// cs_wallet; don't queue an autoshield in the same connected block.
// NOTE: fConsolidationRunning is currently never set true (a pre-existing
// consolidation-scheduler bug); this half of the guard only becomes
// effective once that is fixed on the consolidation side.
if (fSweepRunning || fConsolidationRunning) {
LogPrintf("%s: not autoshielding since sweep/consolidation is running at height=%d\n", __func__, blockHeight);
return;