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>
508 lines
23 KiB
C++
508 lines
23 KiB
C++
// DragonX Wallet - ImGui Edition
|
|
// Copyright 2024-2026 The Hush Developers
|
|
// Released under the GPLv3
|
|
|
|
#pragma once
|
|
|
|
#include <cstdint>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <string>
|
|
#include <functional>
|
|
#include <memory>
|
|
#include <atomic>
|
|
#include <thread>
|
|
#include <mutex>
|
|
#include <set>
|
|
#include <vector>
|
|
|
|
#ifdef _WIN32
|
|
#include <windows.h>
|
|
#else
|
|
#include <sys/types.h>
|
|
#endif
|
|
|
|
namespace dragonx {
|
|
namespace daemon {
|
|
|
|
/**
|
|
* @brief Manages the embedded dragonxd process
|
|
*
|
|
* Handles starting, stopping, and monitoring the dragonxd daemon process.
|
|
* Ports functionality from Qt version's connection.cpp.
|
|
*/
|
|
class EmbeddedDaemon {
|
|
public:
|
|
enum class State {
|
|
Stopped,
|
|
Starting,
|
|
Running,
|
|
Stopping,
|
|
Error
|
|
};
|
|
|
|
using StateCallback = std::function<void(State, const std::string&)>;
|
|
|
|
EmbeddedDaemon();
|
|
~EmbeddedDaemon();
|
|
|
|
// Non-copyable
|
|
EmbeddedDaemon(const EmbeddedDaemon&) = delete;
|
|
EmbeddedDaemon& operator=(const EmbeddedDaemon&) = delete;
|
|
|
|
/**
|
|
* @brief Start the embedded dragonxd daemon
|
|
* @param binary_path Path to dragonxd binary (auto-detect if empty)
|
|
* @return true if process started successfully
|
|
*/
|
|
bool start(const std::string& binary_path = "");
|
|
|
|
/**
|
|
* @brief Stop the daemon process this object launched (never any other dragonxd)
|
|
* @param wait_ms Maximum milliseconds to wait for graceful shutdown
|
|
* @return true if it exited on its own (or there was nothing to stop), false if it had to be
|
|
* force-killed — then its wallet/chain files may not have been closed cleanly
|
|
*/
|
|
bool stop(int wait_ms = 5000);
|
|
|
|
/**
|
|
* @brief Record that we are about to ask the daemon to stop (e.g. over RPC)
|
|
*
|
|
* Call it before sending the stop. If the daemon then exits before stop() runs, the monitor
|
|
* records a clean stop instead of a crash (crash_count_ drives auto-restart). stop() sets it
|
|
* too; start() clears it.
|
|
*/
|
|
void markStopRequested() { stop_requested_ = true; }
|
|
|
|
// --- A stop session, driven by DaemonController::executeDaemonStop through the app's StopRuntime ---
|
|
|
|
/** Marks the stop as requested and stops the monitor, so the session is the only reaper.
|
|
* @return true if a process this object launched is still alive; then finish with endStop(). */
|
|
bool beginStop();
|
|
/** Polls our own process (reaping it on POSIX) and drains its output; false once it has exited. */
|
|
bool pollOwnedProcess();
|
|
/** POSIX: SIGTERM our process group (dragonxd treats it like RPC stop). Windows has no such channel. */
|
|
bool signalGracefulStop();
|
|
/** Ends our own process by force (SIGKILL / TerminateProcess) and reaps it. Explicit user choice only. */
|
|
void forceKillOwnedProcess();
|
|
struct ResourceUsage {
|
|
double cpuSeconds = -1.0; // cumulative; < 0 if unknown
|
|
double ioBytes = -1.0; // cumulative storage I/O; < 0 if unknown
|
|
};
|
|
/** CPU and I/O used so far by our process (its whole process group on Linux). */
|
|
ResourceUsage ownedResourceUsage() const;
|
|
/** Finishes a stop session. The process is gone: release everything. Still running: keepWatching
|
|
* resumes the monitor (its exit is still seen, and booked as a clean stop); otherwise let it go —
|
|
* the user chose to leave it running. */
|
|
void endStop(bool keepWatching);
|
|
|
|
/**
|
|
* @brief Parse a /proc/<pid>/stat line: process group (field 5) and utime+stime ticks (fields 14+15)
|
|
*
|
|
* comm (field 2) is parenthesised and may itself contain spaces and ')', so fields are read from
|
|
* after the LAST ')'.
|
|
*/
|
|
static bool parseProcStatLine(const std::string& line, long& pgrp, unsigned long long& cpuTicks)
|
|
{
|
|
const size_t close = line.rfind(')');
|
|
if (close == std::string::npos) return false;
|
|
unsigned long long utime = 0, stime = 0;
|
|
// state ppid pgrp session tty_nr tpgid flags minflt cminflt majflt cmajflt utime stime
|
|
if (std::sscanf(line.c_str() + close + 1, " %*c %*d %ld %*d %*d %*d %*u %*u %*u %*u %*u %llu %llu",
|
|
&pgrp, &utime, &stime) != 3)
|
|
return false;
|
|
cpuTicks = utime + stime;
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* @brief Check if daemon process is running
|
|
*/
|
|
bool isRunning() const;
|
|
|
|
/**
|
|
* @brief Get current daemon state
|
|
*/
|
|
State getState() const { return state_; }
|
|
|
|
/**
|
|
* @brief Get daemon process memory usage (RSS) in MB
|
|
* @return Memory usage in MB, or 0 if unavailable
|
|
*/
|
|
double getMemoryUsageMB() const;
|
|
|
|
/**
|
|
* @brief Get last error message
|
|
*/
|
|
// Copy under lock: last_error_ is written from the monitor thread (setState on an unexpected exit)
|
|
// while the UI thread reads it — a reference would be a torn-read / use-after-free race (M-04).
|
|
std::string getLastError() const { std::lock_guard<std::mutex> lk(error_mutex_); return last_error_; }
|
|
|
|
/**
|
|
* @brief Get dragonxd process output (thread-safe copy)
|
|
*/
|
|
std::string getOutput() const {
|
|
std::lock_guard<std::mutex> lk(output_mutex_);
|
|
return process_output_;
|
|
}
|
|
|
|
/**
|
|
* @brief Get new output since a given offset (thread-safe).
|
|
* Returns the new text and advances offset. Offsets are absolute positions in everything ever
|
|
* appended (start from 0), so trimming old output never shifts them: a reader that falls behind a
|
|
* trim only loses the trimmed part, not output that is still buffered.
|
|
*/
|
|
std::string getOutputSince(size_t& offset) const {
|
|
std::lock_guard<std::mutex> lk(output_mutex_);
|
|
return outputSince(process_output_, output_total_, offset);
|
|
}
|
|
|
|
/** getOutputSince() on a buffer holding the LAST buffer.size() of `total` bytes ever appended. */
|
|
static std::string outputSince(const std::string& buffer, size_t total, size_t& offset)
|
|
{
|
|
const size_t base = total - buffer.size(); // absolute position of buffer[0]
|
|
if (offset < base) offset = base; // trimmed before it was read: resume at what's left
|
|
if (offset >= total) {
|
|
offset = total;
|
|
return {};
|
|
}
|
|
std::string result = buffer.substr(offset - base);
|
|
offset = total;
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* @brief Get current output size (thread-safe, no copy)
|
|
*/
|
|
size_t getOutputSize() const {
|
|
std::lock_guard<std::mutex> lk(output_mutex_);
|
|
return process_output_.size();
|
|
}
|
|
|
|
/**
|
|
* @brief Get last N lines of daemon output (thread-safe snapshot)
|
|
*/
|
|
std::vector<std::string> getRecentLines(int maxLines = 8) const;
|
|
|
|
/**
|
|
* @brief Extract the latest block height from daemon output (thread-safe).
|
|
* Parses the last "height=N" from UpdateTip lines without copying
|
|
* the entire output buffer. Returns -1 if no UpdateTip found.
|
|
*/
|
|
int getLastBlockHeight() const {
|
|
std::lock_guard<std::mutex> lk(output_mutex_);
|
|
// Search backwards from the end for "height="
|
|
size_t pos = process_output_.rfind("height=");
|
|
if (pos == std::string::npos) return -1;
|
|
pos += 7; // skip "height="
|
|
int h = 0;
|
|
for (size_t i = pos; i < process_output_.size(); ++i) {
|
|
char c = process_output_[i];
|
|
if (c >= '0' && c <= '9') h = h * 10 + (c - '0');
|
|
else break;
|
|
}
|
|
return h > 0 ? h : -1;
|
|
}
|
|
|
|
/**
|
|
* @brief Whether start() detected an existing daemon on the RPC port.
|
|
* When true the wallet should connect to it instead of showing an error.
|
|
*/
|
|
bool externalDaemonDetected() const { return external_daemon_detected_; }
|
|
// Clear the adopted-external latch before relaunching our OWN process (e.g. a wallet switch that
|
|
// stopped the adopted daemon), so the freshly spawned daemon is treated as owned. start() also
|
|
// clears it on the port-free fall-through, but only if not short-circuited by an early guard.
|
|
void clearExternalDaemonDetected() { external_daemon_detected_ = false; }
|
|
|
|
/**
|
|
* @brief Set callback for state changes
|
|
*/
|
|
void setStateCallback(StateCallback cb) { state_callback_ = cb; }
|
|
|
|
/**
|
|
* @brief Find dragonxd binary in standard locations
|
|
* @return Path to binary, or empty if not found
|
|
*/
|
|
static std::string findDaemonBinary();
|
|
|
|
/**
|
|
* @brief Check whether anything is listening on the default RPC port.
|
|
* Useful for detecting an externally-started daemon.
|
|
*/
|
|
static bool isRpcPortInUse();
|
|
|
|
/**
|
|
* @brief Get the chain parameters for dragonxd
|
|
* @return Command line arguments for dragonx chain
|
|
*/
|
|
static std::vector<std::string> getChainParams();
|
|
|
|
/**
|
|
* @brief Set debug logging categories (appended as -debug= flags on next start)
|
|
*/
|
|
void setDebugCategories(const std::set<std::string>& cats) { debug_categories_ = cats; }
|
|
const std::set<std::string>& getDebugCategories() const { return debug_categories_; }
|
|
|
|
/**
|
|
* @brief Set maximum peer connections (0 = use daemon default)
|
|
*/
|
|
void setMaxConnections(int v) { max_connections_ = v; }
|
|
void setStratumHosting(bool enabled, const std::string& allowIp) { stratum_enabled_ = enabled; stratum_allowip_ = allowIp; }
|
|
|
|
/**
|
|
* @brief Request a blockchain rescan on the next daemon start
|
|
*/
|
|
void setRescanOnNextStart(bool v) { rescan_on_next_start_ = v; }
|
|
bool rescanOnNextStart() const { return rescan_on_next_start_.load(); }
|
|
|
|
// Active wallet file (multi-wallet). Passed to the daemon as -wallet=<name> so it loads
|
|
// <datadir>/<name>; empty or "wallet.dat" keeps the daemon default (no arg). Must be a plain
|
|
// filename in the datadir — the daemon rejects paths.
|
|
void setWalletFile(const std::string& v) { wallet_file_ = v; }
|
|
std::string walletFile() const { return wallet_file_; }
|
|
|
|
/**
|
|
* @brief Request a wallet repair (-zapwallettxes=2) on the next daemon start. This deletes all
|
|
* wallet transaction/note records and rebuilds them from the chain (keys are kept); the
|
|
* daemon implicitly rescans afterwards. One-shot, like the rescan flag.
|
|
*/
|
|
void setZapOnNextStart(bool v) { zap_on_next_start_ = v; }
|
|
bool zapOnNextStart() const { return zap_on_next_start_.load(); }
|
|
|
|
// -salvagewallet: attempt to recover keys from a corrupt wallet.dat on startup (implies -rescan in
|
|
// the daemon). One-shot, consumed on the next start. Used to repair a wallet a switch flagged corrupt.
|
|
void setSalvageOnNextStart(bool v) { salvage_on_next_start_ = v; }
|
|
bool salvageOnNextStart() const { return salvage_on_next_start_.load(); }
|
|
|
|
// -reindex: rebuild the block index + chainstate from the raw blocks (blk*.dat) on startup. One-shot,
|
|
// consumed on the next start. Offered when the node aborts on an unreadable block database (a
|
|
// daemon-vs-chaindata format mismatch after an update, or a corrupt index). It implies a wallet
|
|
// rescan, so it's the block-DB analogue of -salvagewallet and coexists with the wallet-repair flags.
|
|
void setReindexOnNextStart(bool v) { reindex_on_next_start_ = v; }
|
|
bool reindexOnNextStart() const { return reindex_on_next_start_.load(); }
|
|
|
|
/**
|
|
* @brief One-shot isolated-datadir override for the NEXT start(): run the daemon against a
|
|
* different datadir (with its own DRAGONX.conf) plus the given extra args. Used by the
|
|
* "migrate to a seed wallet" flow to mint a fresh mnemonic wallet in a throwaway datadir
|
|
* without touching the real one. Consumed on the next start(); later starts are normal.
|
|
* The caller must serialize this with start() (no concurrent starts).
|
|
*/
|
|
void setNextStartOverride(const std::string& datadir, std::vector<std::string> extraArgs) {
|
|
override_datadir_ = datadir;
|
|
override_extra_args_ = std::move(extraArgs);
|
|
}
|
|
void clearNextStartOverride() { override_datadir_.clear(); override_extra_args_.clear(); }
|
|
|
|
/**
|
|
* @brief Skip the "default RPC port already in use → external daemon" bail in start().
|
|
* Set true only for an isolated instance running on its OWN (non-default) port
|
|
* alongside the main daemon (migrate-to-seed flow).
|
|
*/
|
|
void setSkipPortCheck(bool v) { skip_port_check_ = v; }
|
|
|
|
/**
|
|
* @brief True while ANY dragonxd process is running (by process name), regardless of who started it.
|
|
* Unlike isRpcPortInUse()/isRunning(), this reflects the actual PROCESS still being alive — a
|
|
* graceful shutdown stops accepting RPC (port reads "free") but keeps the datadir lock until the
|
|
* process exits, which can take up to ~90s. Use this to know a stopped node has FULLY released
|
|
* the datadir before starting a replacement. Matches the daemon binary name on all platforms.
|
|
*/
|
|
static bool isDaemonProcessRunning();
|
|
|
|
/**
|
|
* @brief Does a /proc entry (its comm + its exe link's basename) belong to a dragonxd? (Linux)
|
|
*
|
|
* comm alone misses a node in its final shutdown phase: Shutdown() renames the main thread to
|
|
* "hush-shutoff" while the process still holds the datadir lock. The exe link keeps the binary
|
|
* name, with " (deleted)" appended once the binary was replaced underneath it (reinstall/update).
|
|
*/
|
|
static bool isDaemonProcessIdentity(const std::string& comm, std::string exeName)
|
|
{
|
|
static const std::string kDeleted = " (deleted)";
|
|
if (exeName.size() > kDeleted.size() &&
|
|
exeName.compare(exeName.size() - kDeleted.size(), kDeleted.size(), kDeleted) == 0)
|
|
exeName.erase(exeName.size() - kDeleted.size());
|
|
return comm == "dragonxd" || exeName == "dragonxd";
|
|
}
|
|
|
|
// ---- Ownership record --------------------------------------------------------------------------
|
|
// <datadir>/obsidiandragon-dragonxd.pid names the node this wallet launched, so a later session that
|
|
// finds it still running (after "Leave running") knows it is ours: it may stop it gracefully and wait
|
|
// for it, never kill it. The start token (when that process started) defeats PID reuse and makes a
|
|
// record from before a reboot stale.
|
|
struct OwnershipRecord {
|
|
long pid = 0;
|
|
std::string startToken;
|
|
std::string exePath;
|
|
};
|
|
static std::string formatOwnershipRecord(const OwnershipRecord& r)
|
|
{
|
|
return "pid=" + std::to_string(r.pid) + "\nstart=" + r.startToken + "\nexe=" + r.exePath + "\n";
|
|
}
|
|
static bool parseOwnershipRecord(const std::string& text, OwnershipRecord& out)
|
|
{
|
|
OwnershipRecord r;
|
|
size_t pos = 0;
|
|
while (pos < text.size()) {
|
|
size_t end = text.find('\n', pos);
|
|
if (end == std::string::npos) end = text.size();
|
|
std::string line = text.substr(pos, end - pos);
|
|
pos = end + 1;
|
|
if (!line.empty() && line.back() == '\r') line.pop_back();
|
|
if (line.rfind("pid=", 0) == 0) r.pid = std::strtol(line.c_str() + 4, nullptr, 10);
|
|
else if (line.rfind("start=", 0) == 0) r.startToken = line.substr(6);
|
|
else if (line.rfind("exe=", 0) == 0) r.exePath = line.substr(4);
|
|
}
|
|
if (r.pid <= 0 || r.startToken.empty()) return false;
|
|
out = r;
|
|
return true;
|
|
}
|
|
/** Linux /proc/<pid>/stat: the process state and its start time (clock ticks after boot, field 22). */
|
|
static bool parseProcStatStart(const std::string& line, char& state, unsigned long long& startTicks)
|
|
{
|
|
const size_t close = line.rfind(')');
|
|
if (close == std::string::npos) return false;
|
|
// state ppid pgrp session tty_nr tpgid flags minflt cminflt majflt cmajflt utime stime cutime cstime
|
|
// priority nice num_threads itrealvalue starttime
|
|
return std::sscanf(line.c_str() + close + 1,
|
|
" %c %*d %*d %*d %*d %*d %*u %*u %*u %*u %*u %*u %*u %*d %*d %*d %*d %*d %*d %llu",
|
|
&state, &startTicks) == 2;
|
|
}
|
|
/**
|
|
* dragonxd shrinks a large debug.log at startup (it keeps the tail), so the file can become smaller
|
|
* than where we were reading. Resume at THIS run's startup banner — the first banner line stamped at or
|
|
* after `launchUtc` (UTC) — not at 0, which would replay the previous run's lines (old rebuild markers,
|
|
* old corruption errors) as if they were new. No such banner yet: skip to the end (it follows).
|
|
*/
|
|
static size_t shrinkResumeOffset(const std::string& content, std::int64_t launchUtc);
|
|
|
|
/** When process `pid` started, as an opaque token; "" if it isn't running (or is a zombie). */
|
|
static std::string processStartToken(long pid);
|
|
/** When process `pid` started, in UTC seconds; 0 if unknown. */
|
|
static std::int64_t processStartUtc(long pid);
|
|
/** Is process `pid` a dragonxd (by name / executable)? */
|
|
static bool processIsDaemon(long pid);
|
|
static std::string ownershipRecordPath(); // <datadir>/obsidiandragon-dragonxd.pid
|
|
static void writeOwnershipRecord(const std::string& path, const OwnershipRecord& r);
|
|
/** The record at `path`, if its process is still running; a stale or unreadable record is deleted. */
|
|
static bool liveOwnershipRecord(const std::string& path, OwnershipRecord* out = nullptr);
|
|
|
|
/**
|
|
* Does this DRAGONX.conf text set `key` on an active line ("key=value", spaces allowed around '=';
|
|
* '#' starts a comment)? Used before adding a flag of our own: a command-line flag overrides the conf,
|
|
* so the user's own setting must win.
|
|
*/
|
|
static bool confSetsKey(const std::string& confText, const std::string& key)
|
|
{
|
|
size_t pos = 0;
|
|
while (pos < confText.size()) {
|
|
size_t end = confText.find('\n', pos);
|
|
if (end == std::string::npos) end = confText.size();
|
|
std::string line = confText.substr(pos, end - pos);
|
|
pos = end + 1;
|
|
const size_t hash = line.find('#');
|
|
if (hash != std::string::npos) line.erase(hash);
|
|
const size_t eq = line.find('=');
|
|
if (eq == std::string::npos) continue;
|
|
size_t b = line.find_first_not_of(" \t\r");
|
|
size_t e = line.find_last_not_of(" \t\r", eq == 0 ? 0 : eq - 1);
|
|
if (b == std::string::npos || b >= eq || e == std::string::npos || e < b) continue;
|
|
if (line.compare(b, e - b + 1, key) == 0) return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/** Decision returned by evaluateDatadirLockGate(): whether start() may spawn now. */
|
|
struct StartLockGateDecision {
|
|
bool proceed = true; // false => bail before spawning
|
|
const char* errorMessage = ""; // set (a string literal) when proceed == false
|
|
};
|
|
|
|
/**
|
|
* @brief Pure decision for start(): bail because a previous dragonxd still holds the
|
|
* shared datadir lock? Isolated instances (skip_port_check_ / an active -datadir
|
|
* override) are exempt — they run their own throwaway datadir+port and can coexist
|
|
* with the main daemon. Does no process/fs I/O itself (the caller does the probing),
|
|
* so it is directly unit-testable; defined inline so tests need only this header.
|
|
*/
|
|
static StartLockGateDecision evaluateDatadirLockGate(bool skipPortCheck,
|
|
bool isolatedOverride,
|
|
bool stillRunningAfterWait)
|
|
{
|
|
if (skipPortCheck || isolatedOverride) return {true, ""};
|
|
if (stillRunningAfterWait) {
|
|
return {false,
|
|
"A previous dragonxd is still shutting down and holding the data "
|
|
"directory lock. Retrying shortly…"};
|
|
}
|
|
return {true, ""};
|
|
}
|
|
|
|
/** @brief Is an arbitrary TCP port currently in use on localhost? (used to pick a free port) */
|
|
static bool tcpPortInUse(int port);
|
|
|
|
/** Get number of consecutive daemon crashes (resets on successful start or manual reset) */
|
|
int getCrashCount() const { return crash_count_.load(); }
|
|
/** Reset crash counter (call on successful connection or manual restart) */
|
|
void resetCrashCount() { crash_count_ = 0; }
|
|
|
|
private:
|
|
void setState(State s, const std::string& message = "");
|
|
void monitorProcess();
|
|
void drainOutput(); // read any pending stdout into process_output_
|
|
void appendOutput(const char* data, size_t len); // append + trim (caller holds output_mutex_)
|
|
bool startProcess(const std::string& binary_path, const std::vector<std::string>& args);
|
|
|
|
std::atomic<State> state_{State::Stopped};
|
|
std::atomic<bool> external_daemon_detected_{false};
|
|
std::string last_error_;
|
|
mutable std::mutex error_mutex_; // protects last_error_ (written by main + monitor threads)
|
|
mutable std::mutex output_mutex_; // protects process_output_
|
|
std::string process_output_;
|
|
size_t output_total_ = 0; // bytes ever appended (the absolute end of process_output_)
|
|
StateCallback state_callback_;
|
|
|
|
// Process handle
|
|
#ifdef _WIN32
|
|
HANDLE process_handle_ = nullptr;
|
|
HANDLE stdout_read_ = nullptr; // pipe handle (Linux-style pipe, unused when tailing debug.log)
|
|
std::string launch_cmd_; // full command line used to launch daemon (for diagnostics)
|
|
std::string launch_binary_; // binary path used (for diagnostics)
|
|
std::string launch_workdir_; // working directory used (for diagnostics)
|
|
#else
|
|
pid_t process_pid_ = 0;
|
|
int stdout_fd_ = -1; // the child's stdout+stderr (crash diagnostics; the log itself is debug.log)
|
|
void tailDebugLog();
|
|
|
|
// Waits up to timeout_ms for `pid` to exit, draining its output; true once reaped. Only valid
|
|
// after monitor_thread_ is joined, so that stop() is the sole reaper.
|
|
bool reapWithin(pid_t pid, int timeout_ms, int& status);
|
|
#endif
|
|
|
|
// The node's output is its <datadir>/debug.log (timestamped; it outlives us if the node is left running).
|
|
std::string debug_log_path_;
|
|
size_t debug_log_offset_ = 0; // read offset into debug.log
|
|
std::int64_t launch_utc_ = 0; // when we launched it (to find this run's banner after a log shrink)
|
|
|
|
std::thread monitor_thread_;
|
|
std::atomic<bool> should_stop_{false};
|
|
std::atomic<bool> stop_requested_{false}; // a stop was asked for: the next exit is not a crash
|
|
std::set<std::string> debug_categories_;
|
|
int max_connections_ = 0; // 0 = daemon default
|
|
bool stratum_enabled_ = false; // -stratum: host a RandomX pool (v1.3.0+; older daemons ignore it)
|
|
std::string stratum_allowip_; // -stratumallowip subnet (empty = daemon default: loopback only)
|
|
std::string wallet_file_; // -wallet=<name> for the active wallet; empty/"wallet.dat" = default
|
|
std::atomic<int> crash_count_{0}; // consecutive crash counter
|
|
std::atomic<bool> rescan_on_next_start_{false}; // -rescan flag for next start
|
|
std::atomic<bool> zap_on_next_start_{false}; // -zapwallettxes=2 flag for next start
|
|
std::atomic<bool> salvage_on_next_start_{false}; // -salvagewallet flag for next start
|
|
std::atomic<bool> reindex_on_next_start_{false}; // -reindex flag for next start (rebuild block DB)
|
|
std::string override_datadir_; // one-shot: -datadir for the next start
|
|
std::vector<std::string> override_extra_args_; // one-shot: extra args for the next start
|
|
bool skip_port_check_ = false; // isolated instance on a non-default port
|
|
};
|
|
|
|
} // namespace daemon
|
|
} // namespace dragonx
|