// DragonX Wallet - ImGui Edition // Copyright 2024-2026 The Hush Developers // Released under the GPLv3 #pragma once #include #include #include #include #include #include #include #include #ifdef _WIN32 #include #else #include #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; 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 running daemon * @param wait_ms Maximum milliseconds to wait for graceful shutdown */ void stop(int wait_ms = 5000); /** * @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 */ const std::string& getLastError() const { return last_error_; } /** * @brief Get dragonxd process output (thread-safe copy) */ std::string getOutput() const { std::lock_guard lk(output_mutex_); return process_output_; } /** * @brief Get new output since a given offset (thread-safe). * Returns the new text and updates offset to the current size. */ std::string getOutputSince(size_t& offset) const { std::lock_guard lk(output_mutex_); if (offset >= process_output_.size()) { offset = process_output_.size(); return {}; } std::string result = process_output_.substr(offset); offset = process_output_.size(); return result; } /** * @brief Get current output size (thread-safe, no copy) */ size_t getOutputSize() const { std::lock_guard lk(output_mutex_); return process_output_.size(); } /** * @brief Get last N lines of daemon output (thread-safe snapshot) */ std::vector 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 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 getChainParams(); /** * @brief Set debug logging categories (appended as -debug= flags on next start) */ void setDebugCategories(const std::set& cats) { debug_categories_ = cats; } const std::set& getDebugCategories() const { return debug_categories_; } /** * @brief Set maximum peer connections (0 = use daemon default) */ void setMaxConnections(int v) { max_connections_ = v; } /** * @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= so it loads // /; 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(); } /** * @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 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 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& args); std::atomic state_{State::Stopped}; std::atomic external_daemon_detected_{false}; std::string last_error_; mutable std::mutex output_mutex_; // protects process_output_ std::string 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 debug_log_path_; // path to daemon's debug.log for output tailing size_t debug_log_offset_ = 0; // read offset into 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; #endif std::thread monitor_thread_; std::atomic should_stop_{false}; std::set debug_categories_; int max_connections_ = 0; // 0 = daemon default std::string wallet_file_; // -wallet= for the active wallet; empty/"wallet.dat" = default std::atomic crash_count_{0}; // consecutive crash counter std::atomic rescan_on_next_start_{false}; // -rescan flag for next start std::atomic zap_on_next_start_{false}; // -zapwallettxes=2 flag for next start std::string override_datadir_; // one-shot: -datadir for the next start std::vector 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