fix(fullnode): smooth witness-cache progress from the new daemon's done/total

Use the new daemon's "Reading blocks for witness rebuild: <done> / <total>" 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 "<n> remaining").
Also snap to 100% on the parallel rebuild's completion line ("rebuilt <n> note
witness cache(s) … using <t> 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) <noreply@anthropic.com>
This commit is contained in:
2026-06-22 15:48:33 -05:00
parent ed6120eef9
commit 69a6fb3e64

View File

@@ -749,6 +749,8 @@ void App::update()
bool foundWitness = false; bool foundWitness = false;
float witnessPct = -1.0f; // -1 = no fraction parsed this batch float witnessPct = -1.0f; // -1 = no fraction parsed this batch
int witnessRemaining = -1; int witnessRemaining = -1;
int witnessReadTotal = -1; // new daemon: the <total> in "<done> / <total>"
bool witnessRebuilt = false; // new daemon's completion line seen this batch
std::vector<std::string> witnessTxids; // distinct-tx keys seen this batch std::vector<std::string> witnessTxids; // distinct-tx keys seen this batch
int witnessTotalTxs = -1; // max N 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: <done> / <total>". // Newer multi-threaded daemon: "Reading blocks for witness rebuild: <done> / <total>".
// This replaced the per-block "Building Witnesses" line. It's a clean done/total // This replaced the per-block "Building Witnesses" line and is an exact done/total
// count, so map it onto the same phase-2 "remaining" signal (remaining = total - // count, so capture BOTH (total drives the denominator directly — no peak anchoring).
// done) and the existing cache-walk logic yields progress = done/total.
auto rdIdx = line.find("Reading blocks for witness rebuild:"); auto rdIdx = line.find("Reading blocks for witness rebuild:");
if (rdIdx != std::string::npos) { if (rdIdx != std::string::npos) {
foundWitness = true; foundWitness = true;
@@ -865,14 +866,26 @@ void App::update()
try { try {
long done = std::stol(line.substr(dStart, dEnd - dStart)); long done = std::stol(line.substr(dStart, dEnd - dStart));
long total = std::stol(line.substr(tStart, tEnd - tStart)); 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<int>(total - done); witnessRemaining = static_cast<int>(total - done);
witnessReadTotal = static_cast<int>(total);
}
} catch (...) {} } catch (...) {}
} }
} }
lastStatus = line; lastStatus = line;
} }
// New daemon's witness-rebuild completion line:
// "... rebuilt <n> note witness cache(s) to height <h> in <ms>ms using <t> 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 <hash>, <i> of <N>". // Primary signal: "Setting Initial Sapling Witness for tx <hash>, <i> of <N>".
// <i> is the tx's slot in an unordered map (bounces — useless as progress), so // <i> is the tx's slot in an unordered map (bounces — useless as progress), so
// we key on <hash> (one per tx) and count distinct txs against <N>. Extract the // we key on <hash> (one per tx) and count distinct txs against <N>. Extract the
@@ -934,7 +947,7 @@ void App::update()
// never drops back to the initial pass. Otherwise an interleaved initial // never drops back to the initial pass. Otherwise an interleaved initial
// line would flip the phase back and reset the bar every batch (thrash). // line would flip the phase back and reset the bar every batch (thrash).
int phase = state_.sync.witness_phase; 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; else if (!witnessTxids.empty() && phase < 2) phase = 1;
// Entering a higher sub-phase → reset its progress + accumulators so the // Entering a higher sub-phase → reset its progress + accumulators so the
@@ -950,17 +963,27 @@ void App::update()
} }
if (phase == 2) { 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; state_.sync.witness_remaining = witnessRemaining;
if (witnessRemaining > witness_rebuild_total_blocks_) float p = -1.0f;
witness_rebuild_total_blocks_ = witnessRemaining; if (witnessRebuilt) {
if (witness_rebuild_total_blocks_ > 0) { // Parallel rebuild finished (it logs no progress) — snap to 100%.
float p = 1.0f - static_cast<float>(witnessRemaining) / p = 1.0f;
static_cast<float>(witness_rebuild_total_blocks_); state_.sync.witness_remaining = 0;
if (p < 0.0f) p = 0.0f; } 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<float>(done) / static_cast<float>(witnessReadTotal);
} else if (witnessRemaining >= 0) {
// Older daemon: bare "<n> 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<float>(witnessRemaining) /
static_cast<float>(witness_rebuild_total_blocks_);
}
if (p >= 0.0f) {
if (p > 1.0f) p = 1.0f; if (p > 1.0f) p = 1.0f;
if (p > state_.sync.witness_progress) // monotonic within the phase if (p > state_.sync.witness_progress) // monotonic within the phase
state_.sync.witness_progress = p; state_.sync.witness_progress = p;