From 69a6fb3e6414a2cc5c0e5aceaddf8a723ca2f45e Mon Sep 17 00:00:00 2001 From: DanS Date: Mon, 22 Jun 2026 15:48:33 -0500 Subject: [PATCH] fix(fullnode): smooth witness-cache progress from the new daemon's done/total MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use the new daemon's "Reading blocks for witness rebuild: / " as an exact fraction: the reported total is the denominator directly, so the bar sweeps 0..1 smoothly instead of being held near the top by the peak-anchored remaining heuristic (kept only as a fallback for older daemons that log bare " remaining"). Also snap to 100% on the parallel rebuild's completion line ("rebuilt note witness cache(s) … using thread(s)"), which otherwise logs no progress, so the bar visibly finishes before the rescan-complete signal clears it. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/app.cpp | 53 ++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 38 insertions(+), 15 deletions(-) diff --git a/src/app.cpp b/src/app.cpp index 3bd24d3..c0c83bf 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -749,6 +749,8 @@ void App::update() bool foundWitness = false; float witnessPct = -1.0f; // -1 = no fraction parsed this batch int witnessRemaining = -1; + int witnessReadTotal = -1; // new daemon: the in " / " + bool witnessRebuilt = false; // new daemon's completion line seen this batch std::vector witnessTxids; // distinct-tx keys seen this batch int witnessTotalTxs = -1; // max N seen this batch @@ -845,9 +847,8 @@ void App::update() } // Newer multi-threaded daemon: "Reading blocks for witness rebuild: / ". - // This replaced the per-block "Building Witnesses" line. It's a clean done/total - // count, so map it onto the same phase-2 "remaining" signal (remaining = total - - // done) and the existing cache-walk logic yields progress = done/total. + // This replaced the per-block "Building Witnesses" line and is an exact done/total + // count, so capture BOTH (total drives the denominator directly — no peak anchoring). auto rdIdx = line.find("Reading blocks for witness rebuild:"); if (rdIdx != std::string::npos) { foundWitness = true; @@ -865,14 +866,26 @@ void App::update() try { long done = std::stol(line.substr(dStart, dEnd - dStart)); long total = std::stol(line.substr(tStart, tEnd - tStart)); - if (total > 0 && done >= 0 && done <= total) + if (total > 0 && done >= 0 && done <= total) { witnessRemaining = static_cast(total - done); + witnessReadTotal = static_cast(total); + } } catch (...) {} } } lastStatus = line; } + // New daemon's witness-rebuild completion line: + // "... rebuilt note witness cache(s) to height in ms using thread(s)". + // The parallel rebuild logs no progress, so on completion snap the bar to 100%. + if (line.find("note witness cache") != std::string::npos && + line.find("thread(s)") != std::string::npos) { + foundWitness = true; + witnessRebuilt = true; + lastStatus = line; + } + // Primary signal: "Setting Initial Sapling Witness for tx , of ". // is the tx's slot in an unordered map (bounces — useless as progress), so // we key on (one per tx) and count distinct txs against . Extract the @@ -934,7 +947,7 @@ void App::update() // 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; + if (witnessRemaining >= 0 || witnessRebuilt) phase = 2; else if (!witnessTxids.empty() && phase < 2) phase = 1; // Entering a higher sub-phase → reset its progress + accumulators so the @@ -950,17 +963,27 @@ void App::update() } if (phase == 2) { - // Cache walk: derive a stable % from how far "remaining" has fallen - // from its peak (the first/largest value = the full walk span). The - // daemon re-invokes the walk per block, so "remaining" sawtooths; the - // peak anchor + monotonic clamp keep the bar advancing, never resetting. state_.sync.witness_remaining = witnessRemaining; - if (witnessRemaining > witness_rebuild_total_blocks_) - witness_rebuild_total_blocks_ = witnessRemaining; - if (witness_rebuild_total_blocks_ > 0) { - float p = 1.0f - static_cast(witnessRemaining) / - static_cast(witness_rebuild_total_blocks_); - if (p < 0.0f) p = 0.0f; + float p = -1.0f; + if (witnessRebuilt) { + // Parallel rebuild finished (it logs no progress) — snap to 100%. + p = 1.0f; + state_.sync.witness_remaining = 0; + } else if (witnessReadTotal > 0) { + // New daemon: exact done/total. Use the reported total as the + // denominator directly (no peak anchoring) for a smooth 0..1 sweep. + int done = witnessReadTotal - (witnessRemaining >= 0 ? witnessRemaining : 0); + p = static_cast(done) / static_cast(witnessReadTotal); + } else if (witnessRemaining >= 0) { + // Older daemon: bare " remaining" with no total — anchor the % to + // the largest remaining seen (its first/largest value ≈ the full span). + if (witnessRemaining > witness_rebuild_total_blocks_) + witness_rebuild_total_blocks_ = witnessRemaining; + if (witness_rebuild_total_blocks_ > 0) + p = 1.0f - static_cast(witnessRemaining) / + static_cast(witness_rebuild_total_blocks_); + } + if (p >= 0.0f) { if (p > 1.0f) p = 1.0f; if (p > state_.sync.witness_progress) // monotonic within the phase state_.sync.witness_progress = p;