Files
ObsidianDragon/src/daemon/xmrig_manager.h
T
DanSandClaude Opus 4.8 6ee81a5abe fix: security-audit remediation (15 findings), empty-wallet warning, and send/chat/console/shutdown UX
Security audit remediation (15 confirmed findings from the codebase audit):
- H-02: scrub+delete the decrypt-flow plaintext key export on ALL exit paths
  (RAII guard) and purge stale obsidiandecryptexport* files at startup.
- M-01/L-03/L-04/L-05/L-07: sodium_memzero the Set-PIN and encrypt-PIN worker
  passphrase/PIN copies, the RPC Basic-auth string (auth_), the exported/imported
  key buffers (App::wipeSecrets, called from ~App and before main's _Exit), and
  the first-run wizard "Skip" buffers.
- M-03/M-04/M-05/L-06: return locked COPIES from XmrigManager/EmbeddedDaemon
  getters (dedicated error_mutex_; DaemonController::lastError now by value),
  route xmrig last_error_ writes through a locked setter, and wrap
  shutdown_status_/wizard_stop_status_ in a locking GuardedStatus
  (wizard_stopping_external_ -> std::atomic).
- M-02: persist after a console send/shield/import in the lite backend.
- L-01: require the confirm click for z_shieldcoinbase/z_mergetoaddress.
- L-02: quote/escape each Windows daemon argv per the MSDN CommandLineToArgvW rules.
- L-08: pin json/tomlplusplus/libwebp FetchContent to immutable commit SHAs.
- I-01: extract updater archives from the already-verified in-memory buffer
  (no disk re-read TOCTOU).

Feature: warn once (full-node) when the active wallet loads empty while a sibling
wallet file in the datadir holds keys. A funded salvage wallet.<ts>.bak routes to
the recovery/Restore flow; a funded sibling .dat routes to the wallet manager.
Per-wallet-file dismissal; gated on synced + address-list-loaded to avoid false
positives on warm reconnect / spent-down wallets.

UX fixes:
- send: show the TOTAL balance (with a spendable "available" note) in the source
  dropdown and keep pending-change addresses visible.
- chat: insert emoji at the cursor position; restrict new-chat recipients to
  shielded (z) addresses.
- console: optional auto-focus of the command input on tab open (off by default).
- shutdown: when "stop external daemon" is on, keep the shutdown screen up until
  the external node actually exits, showing live status.

Adversarially reviewed; verified across full-node, lite, and Windows builds; tests pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-18 13:18:49 -05:00

196 lines
6.4 KiB
C++

// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
#pragma once
#include <string>
#include <atomic>
#include <thread>
#include <mutex>
#include <vector>
#include <cstdint>
#ifdef _WIN32
#include <winsock2.h>
#include <windows.h>
#else
#include <sys/types.h>
#endif
namespace dragonx {
namespace daemon {
/**
* @brief Manages the xmrig pool mining process
*
* Handles starting/stopping xmrig for pool mining, polling stats via its
* HTTP API, and capturing stdout for the log panel. Modelled on
* EmbeddedDaemon (same fork/pipe/monitor pattern, same #ifdef split).
*/
class XmrigManager {
public:
enum class State { Stopped, Starting, Running, Stopping, Error };
/// Live stats polled from xmrig HTTP API (/2/summary)
struct PoolStats {
double hashrate_10s = 0;
double hashrate_60s = 0;
double hashrate_15m = 0;
int64_t accepted = 0;
int64_t rejected = 0;
int64_t uptime_sec = 0;
double pool_diff = 0;
std::string pool_url;
std::string algo;
std::string version; // miner version reported by the running xmrig API
bool connected = false;
// Memory usage
int64_t memory_free = 0; // bytes
int64_t memory_total = 0; // bytes
int64_t memory_used = 0; // bytes (resident set size)
int threads_active = 0; // actual mining threads
// Pool-side hashrate (from pool stats API, not xmrig)
double pool_hashrate = 0;
};
/// User-facing config (maps 1:1 to UI fields / Settings)
struct Config {
std::string pool_url = "pool.dragonx.is:3433";
std::string wallet_address;
std::string worker_name = "x";
std::string algo = "rx/hush";
int threads = 0; // 0 = xmrig auto
bool tls = false;
bool hugepages = true;
};
XmrigManager();
~XmrigManager();
// Non-copyable
XmrigManager(const XmrigManager&) = delete;
XmrigManager& operator=(const XmrigManager&) = delete;
/**
* @brief Start xmrig with the given config.
* Generates a JSON config file, finds the binary, and spawns the process.
*/
bool start(const Config& cfg);
/**
* @brief Stop xmrig gracefully (SIGTERM → wait → SIGKILL).
*/
void stop(int wait_ms = 5000);
bool isRunning() const;
State getState() const { return state_.load(std::memory_order_relaxed); }
// Return COPIES under lock: stats_ and last_error_ are mutated by the monitor thread while the UI
// thread reads them, so handing out a reference is a torn-read / use-after-free race (M-03, M-04).
PoolStats getStats() const { std::lock_guard<std::mutex> lk(stats_mutex_); return stats_; }
std::string getLastError() const { std::lock_guard<std::mutex> lk(error_mutex_); return last_error_; }
/// Thread count requested at start() — available immediately, unlike
/// PoolStats::threads_active which requires an API response.
int getRequestedThreads() const { return threads_; }
/**
* @brief Get last N lines of xmrig stdout (thread-safe snapshot).
*/
std::vector<std::string> getRecentLines(int maxLines = 30) const;
/**
* @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<std::mutex> 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<std::mutex> lk(output_mutex_);
return process_output_.size();
}
/**
* @brief Poll the xmrig HTTP API for live stats.
* Lightweight — reads cached stats updated by the monitor thread.
* Called from App::update() every ~2 s while running.
*/
void pollStats();
/**
* @brief Get xmrig process memory usage in MB (from OS, not API).
*/
double getMemoryUsageMB() const;
/**
* @brief Find xmrig binary in standard locations.
*/
static std::string findXmrigBinary();
/**
* @brief Kick a one-shot background `<binary> --version` detection (idempotent).
* Lets the UI show the installed miner's version before mining is ever started.
*/
static void startVersionDetection();
/**
* @brief Cached installed-miner version parsed by startVersionDetection().
* Empty until detection completes (or if no binary/parse failed). Thread-safe.
*/
static std::string installedVersion();
private:
bool generateConfig(const Config& cfg, const std::string& outPath);
bool startProcess(const std::string& xmrigPath, const std::string& cfgPath, int threads);
void monitorProcess();
void drainOutput();
void appendOutput(const char* data, size_t len);
// Set last_error_ under error_mutex_ (writers run on both the main thread and the monitor thread).
void setLastError(std::string e) { std::lock_guard<std::mutex> lk(error_mutex_); last_error_ = std::move(e); }
void fetchStatsHttp(); // Blocking HTTP call — runs on monitor thread only
void fetchPoolApiStats(); // Fetch pool-side stats (hashrate) from pool HTTP API
std::atomic<State> state_{State::Stopped};
std::string last_error_;
mutable std::mutex error_mutex_; // guards last_error_ (written by main + monitor threads)
mutable std::mutex output_mutex_;
std::string process_output_;
// xmrig HTTP API credentials (random per session)
int api_port_ = 0;
std::string api_token_;
int threads_ = 0; // Thread count for mining
std::string pool_host_; // Pool hostname for stats API
PoolStats stats_;
mutable std::mutex stats_mutex_;
// Process handles
#ifdef _WIN32
HANDLE process_handle_ = nullptr;
HANDLE stdout_read_ = nullptr;
#else
pid_t process_pid_ = 0;
int stdout_fd_ = -1;
#endif
std::thread monitor_thread_;
std::atomic<bool> should_stop_{false};
};
} // namespace daemon
} // namespace dragonx