From a5bd2dadd7cc4da4addcf31f2348505788cca832 Mon Sep 17 00:00:00 2001 From: DanS Date: Mon, 22 Jun 2026 13:09:29 -0500 Subject: [PATCH] fix(fullnode): make witness sub-phase upgrade-only to stop progress thrash MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The initial-witness pass ("Setting Initial Sapling Witness") and the cache walk ("Building Witnesses for block … remaining") interleave during a rescan — the daemon does both per block. The phase selector picked phase 1 whenever a batch had only initial-pass lines, so once the cache walk started an interleaved initial line would flip the phase back to 1 and reset the bar to 0 every batch. Make the phase upgrade-only (once the cache walk is seen it never drops back), so the reset happens at most twice (→1, →2) and the cache-walk percentage advances monotonically. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/app.cpp | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/app.cpp b/src/app.cpp index dc1aefa..884208e 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -898,15 +898,19 @@ void App::update() rescan_confirmed_active_ = true; state_.sync.building_witnesses = true; - // Which sub-phase is this batch? A " remaining" token means the - // cache walk (phase 2); otherwise per-tx "Setting Initial" lines are the - // initial pass (phase 1). When both appear in one batch the walk wins — - // it always runs after the initial pass within a BuildWitnessCache call. - const int phase = (witnessRemaining >= 0) ? 2 - : (!witnessTxids.empty() ? 1 : state_.sync.witness_phase); + // Which sub-phase is this batch? A " remaining" token means the cache + // walk (phase 2); otherwise per-tx "Setting Initial" lines are the initial + // pass (phase 1). The two INTERLEAVE during a rescan (the daemon does both + // per block), so the phase is UPGRADE-ONLY: once the cache walk is seen it + // never drops back to the initial pass. Otherwise an interleaved initial + // line would flip the phase back and reset the bar every batch (thrash). + int phase = state_.sync.witness_phase; + if (witnessRemaining >= 0) phase = 2; + else if (!witnessTxids.empty() && phase < 2) phase = 1; - // Entering a new sub-phase → reset its progress + accumulators so the bar - // restarts from 0 instead of inheriting the previous phase's value. + // Entering a higher sub-phase → reset its progress + accumulators so the + // bar restarts from 0 (the two phases have different scales). Upgrade-only, + // so this happens at most twice (→1, →2), never repeatedly. if (phase != state_.sync.witness_phase) { state_.sync.witness_phase = phase; state_.sync.witness_progress = 0.0f;