diff --git a/src/app.cpp b/src/app.cpp index b222982..0843d9c 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -5808,26 +5808,47 @@ std::vector App::tailDaemonDebugLog(int maxLines) const return lines; } +// Parse a "YYYY-MM-DD HH:MM:SS ..." debug.log line prefix to time_t. Interpreted as local time, but it +// is only ever used for DELTAS between two lines of the SAME log, so the timezone cancels. Returns 0 if +// the line has no such timestamp prefix. +static std::time_t parseDaemonLogTimestamp(const std::string& line) +{ + int y = 0, mo = 0, d = 0, h = 0, mi = 0, s = 0; + if (std::sscanf(line.c_str(), "%d-%d-%d %d:%d:%d", &y, &mo, &d, &h, &mi, &s) != 6) return 0; + std::tm tm{}; + tm.tm_year = y - 1900; tm.tm_mon = mo - 1; tm.tm_mday = d; + tm.tm_hour = h; tm.tm_min = mi; tm.tm_sec = s; tm.tm_isdst = -1; + return std::mktime(&tm); +} + bool App::daemonWitnessRebuildActive() const { // Scan the debug.log tail for the daemon's witness-rebuild markers (wallet.cpp): "Cleared witness - // data from" (start), "Setting Initial Sapling Witness" / "Reading blocks for witness rebuild" - // (progress), vs. "rebuilt N note witness cache(s)" / "aborting…" (finished). Active iff the most - // recent relevant line is a start/progress line, not a completion. - const auto lines = tailDaemonDebugLog(80); - int state = 0; // 0 none, 1 active, 2 finished/aborted + // data from" (start), "Reading blocks for witness rebuild" / "Setting Initial Sapling Witness" + // (progress), vs. "rebuilt N note witness cache(s)" / "aborting…" (finished). + // + // A genuine ongoing rebuild logs progress CONTINUOUSLY. The routine per-tx witness set the daemon + // does as each new wallet tx lands during normal sync is SPARSE (minutes apart) and must NOT trip + // this — that was firing the "node is rebuilding" prompt on wallets that just receive frequently. + // So require the last progress marker to be (a) later than any completion AND (b) part of the CURRENT + // activity — within a few seconds of the newest log line (same-log timestamp delta → timezone-free). + const auto lines = tailDaemonDebugLog(120); + std::time_t newest = 0, lastProgress = 0, lastDone = 0; for (const auto& l : lines) { + const std::time_t ts = parseDaemonLogTimestamp(l); + if (ts > newest) newest = ts; if (l.find("note witness cache(s) to height") != std::string::npos || l.find("aborting witness rebuild") != std::string::npos || l.find("aborted during witness rebuild") != std::string::npos) { - state = 2; + if (ts > lastDone) lastDone = ts; } else if (l.find("Reading blocks for witness rebuild") != std::string::npos || l.find("Setting Initial Sapling Witness") != std::string::npos || l.find("Cleared witness data from") != std::string::npos) { - state = 1; + if (ts > lastProgress) lastProgress = ts; } } - return state == 1; + if (lastProgress == 0 || lastDone >= lastProgress || newest == 0) return false; + return (newest - lastProgress) <= 15; // progress is part of the current activity → ongoing rebuild } bool App::shouldConfirmDaemonStop() const @@ -5839,6 +5860,10 @@ bool App::shouldConfirmDaemonStop() const settings_ && settings_->getKeepDaemonRunning(), settings_ && settings_->getStopExternalDaemon()); if (decision.action != daemon::DaemonController::ShutdownAction::StopDaemon) return false; + // v1.3.0+ checkpoints witness-rescan progress, so stopping mid-rebuild resumes on the next start + // instead of redoing it from scratch — the warning's premise no longer holds, so don't prompt. + // (daemon_version encodes major*1e6 + minor*1e4 + rev*100 + build; v1.3.0 == 1030000.) + if (state_.daemon_version >= 1030000) return false; return daemonWitnessRebuildActive(); }