feat(daemon): recognise our detached node, tail POSIX debug.log, more RPC threads

S12 - our node across sessions:
- The node the wallet launches is recorded in <datadir>/obsidiandragon-
  dragonxd.pid (pid + process start time + exe). A later session that
  finds it still running (after "Leave running" / keep_daemon_running)
  stops it gracefully on quit and waits for that exact process (no time
  limit, "Leave running" offered); never signals or kills it. A reused
  pid or a reboot never matches; stale records are deleted.
- stop_external_daemon is now honoured when this session only connected
  (no controller); it was ignored.

S13 - the node's own log on every platform:
- No -printtoconsole on Linux/macOS: the node writes its timestamped
  debug.log (which outlives the GUI) and we tail it next to the stderr
  pipe. Tail path from the datadir or the isolated override (Windows had
  %APPDATA% hard-coded, wrong for migrate-to-seed starts).
- After the node shrinks debug.log at startup, resume at this run's
  banner instead of re-reading the old run's tail.
- A node we didn't launch (left running, or found running) feeds the
  busy tracker from its debug.log, tied to the live process (our record,
  or POSIX hushd.pid) and only from lines it wrote. A GUI attaching to a
  node mid-rebuild now says "Node busy: rebuilding note witnesses" and
  asks before stopping it.
- A start that only finds a running node no longer resets the tracker;
  a found busy node shows the busy overlay at once.

S14: launch our node with -rpcthreads=16 unless DRAGONX.conf sets it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-02 12:06:52 -05:00
co-authored by Claude Opus 5.5
parent e20b70d73f
commit a3361020d1
7 changed files with 696 additions and 85 deletions
+32
View File
@@ -141,6 +141,38 @@ inline bool shouldConfirmStop(bool stopsDaemon, const DaemonBusyTracker& tracker
return stopsDaemon && (tracker.rebuilding() || rpcBusyWhileAlive);
}
/**
* Where to start feeding a DaemonBusyTracker from the last `window` of the debug.log of a node someone else
* started at `processStartUtc` (UTC seconds). Only lines that process wrote count: a node started with
* -printtoconsole leaves an old debug.log behind, whose last run may have died mid-rebuild. Returns the
* offset of that process's startup banner in the window; 0 when its banner is older than the window
* (`windowStartsMidFile`) and the window's first stamped line postdates the process start; npos when the
* log isn't that process's.
*/
inline size_t externalLogFeedOffset(const std::string& window, bool windowStartsMidFile, std::int64_t processStartUtc)
{
size_t lastBanner = std::string::npos;
std::int64_t lastBannerAt = 0, firstStamp = 0;
size_t pos = 0;
while (pos < window.size()) {
size_t end = window.find('\n', pos);
if (end == std::string::npos) end = window.size();
const std::string line = window.substr(pos, end - pos);
const std::int64_t at = parseDaemonLogUtc(line);
if (at > 0 && firstStamp == 0) firstStamp = at;
if (line.find("DragonX version ") != std::string::npos || line.find("Hush version ") != std::string::npos) {
lastBanner = pos;
lastBannerAt = at;
}
pos = end + 1;
}
if (processStartUtc <= 0) return std::string::npos;
const std::int64_t since = processStartUtc - 5; // the log's whole seconds vs the process's start time
if (lastBanner != std::string::npos) return (lastBannerAt > 0 && lastBannerAt >= since) ? lastBanner : std::string::npos;
if (windowStartsMidFile && firstStamp > 0 && firstStamp >= since) return 0;
return std::string::npos;
}
// What the "node is starting / not answering" overlay should say, most specific first.
enum class DaemonStatusKind {
Rebuilding, // its own log says it's rebuilding note witnesses