feat(mining): auto-balance driver, per-pool algo, live miner version
- app: periodic weighted-random auto-balance of the pool while pool-mining (~30-min cadence; restarts the miner only when the pick actually changes), plus snapshot + refresh accessors for the UI. - app_network: resolve the xmrig algo per pool at start (pool.dragonx.cc = rx/dragonx, pool.dragonx.is = rx/hush; custom hosts keep the setting). - xmrig_manager: schema-aware pool-side hashrate readout (fixes a silent 0 for Miningcore pools); expose the running miner's version from its HTTP API and a cached `xmrig --version` detection so the UI can show it before mining. - wallet_state: carry the running miner's version. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -8,6 +8,7 @@
|
||||
#include "xmrig_manager.h"
|
||||
#include "../resources/embedded_resources.h"
|
||||
|
||||
#include <cctype>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
@@ -22,6 +23,7 @@
|
||||
#include <curl/curl.h>
|
||||
|
||||
#include "../util/logger.h"
|
||||
#include "../util/pool_registry.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <winsock2.h>
|
||||
@@ -718,6 +720,11 @@ void XmrigManager::fetchStatsHttp() {
|
||||
|
||||
std::lock_guard<std::mutex> lk(stats_mutex_);
|
||||
|
||||
// Miner version (top-level in /2/summary) — lets the UI show the actually
|
||||
// running miner's version even when no release tag was persisted (bundled miner).
|
||||
if (resp.contains("version") && resp["version"].is_string())
|
||||
stats_.version = resp["version"].get<std::string>();
|
||||
|
||||
if (resp.contains("hashrate") && resp["hashrate"].contains("total")) {
|
||||
auto& total = resp["hashrate"]["total"];
|
||||
if (total.is_array() && total.size() >= 3) {
|
||||
@@ -768,10 +775,14 @@ void XmrigManager::fetchStatsHttp() {
|
||||
void XmrigManager::fetchPoolApiStats() {
|
||||
if (state_ != State::Running || pool_host_.empty()) return;
|
||||
|
||||
// Query the pool's public stats API
|
||||
std::string url = "https://" + pool_host_ + "/api/stats";
|
||||
std::string responseData;
|
||||
// Resolve the stats endpoint + JSON schema for this pool. Known pools carry their
|
||||
// own API shape (pool.dragonx.is = custom /api/stats; pool.dragonx.cc = Miningcore
|
||||
// /api/pools); unknown/custom hosts fall back to the .is convention.
|
||||
const util::KnownPool* known = util::findKnownPoolByUrl(pool_host_);
|
||||
const std::string url = known ? known->statsUrl
|
||||
: ("https://" + pool_host_ + "/api/stats");
|
||||
|
||||
std::string responseData;
|
||||
CURL* curl = curl_easy_init();
|
||||
if (!curl) return;
|
||||
|
||||
@@ -782,31 +793,99 @@ void XmrigManager::fetchPoolApiStats() {
|
||||
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT_MS, 3000L);
|
||||
curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1L);
|
||||
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
|
||||
// pool.dragonx.cc sits behind Cloudflare and 403s odd User-Agents.
|
||||
curl_easy_setopt(curl, CURLOPT_USERAGENT, "Mozilla/5.0 (compatible; ObsidianDragon)");
|
||||
curl_easy_setopt(curl, CURLOPT_ACCEPT_ENCODING, "");
|
||||
|
||||
CURLcode res = curl_easy_perform(curl);
|
||||
curl_easy_cleanup(curl);
|
||||
|
||||
if (res != CURLE_OK) return;
|
||||
|
||||
try {
|
||||
json resp = json::parse(responseData);
|
||||
bool ok = false;
|
||||
const double poolHR = util::parsePoolHashrate(
|
||||
known ? known->schema : util::PoolStatsSchema::DragonXIs,
|
||||
responseData, known ? known->miningcorePoolId : std::string{}, ok);
|
||||
if (!ok) return;
|
||||
|
||||
// Pool stats API format: { "pools": { "<name>": { "hashrate": ... } } }
|
||||
double poolHR = 0;
|
||||
if (resp.contains("pools") && resp["pools"].is_object()) {
|
||||
for (auto& [key, pool] : resp["pools"].items()) {
|
||||
if (pool.contains("hashrate") && pool["hashrate"].is_number()) {
|
||||
poolHR = pool["hashrate"].get<double>();
|
||||
break; // Use the first pool entry
|
||||
std::lock_guard<std::mutex> lk(stats_mutex_);
|
||||
stats_.pool_hashrate = poolHR;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Installed-miner version detection (`<binary> --version`, cached)
|
||||
// ============================================================================
|
||||
|
||||
namespace {
|
||||
std::mutex g_installed_ver_mutex;
|
||||
std::string g_installed_ver;
|
||||
std::atomic<bool> g_ver_detect_started{false};
|
||||
|
||||
// Extract the first "D.D[.D...]" version token from `--version` output (skips the
|
||||
// build date, which uses '-' separators). Returns e.g. "6.21.0", or "" if none.
|
||||
std::string parseMinerVersion(const std::string& out)
|
||||
{
|
||||
for (size_t i = 0; i < out.size(); ++i) {
|
||||
if (std::isdigit(static_cast<unsigned char>(out[i]))) {
|
||||
size_t j = i;
|
||||
int dots = 0;
|
||||
while (j < out.size() &&
|
||||
(std::isdigit(static_cast<unsigned char>(out[j])) || out[j] == '.')) {
|
||||
if (out[j] == '.') ++dots;
|
||||
++j;
|
||||
}
|
||||
if (dots >= 1 && (j - i) >= 3) {
|
||||
// Include a trailing build suffix like "-hac" / "-drg1" (e.g. "6.25.1-hac"),
|
||||
// matching what the running miner's API reports.
|
||||
size_t end = j;
|
||||
if (end < out.size() && out[end] == '-') {
|
||||
size_t k = end + 1;
|
||||
while (k < out.size() && std::isalnum(static_cast<unsigned char>(out[k]))) ++k;
|
||||
if (k > end + 1) end = k;
|
||||
}
|
||||
return out.substr(i, end - i);
|
||||
}
|
||||
i = j;
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void XmrigManager::startVersionDetection()
|
||||
{
|
||||
if (g_ver_detect_started.exchange(true)) return; // one-shot
|
||||
std::thread([]() {
|
||||
const std::string bin = findXmrigBinary();
|
||||
std::string ver;
|
||||
if (!bin.empty()) {
|
||||
const std::string cmd = "\"" + bin + "\" --version 2>&1";
|
||||
#ifdef _WIN32
|
||||
FILE* fp = _popen(cmd.c_str(), "r");
|
||||
#else
|
||||
FILE* fp = popen(cmd.c_str(), "r");
|
||||
#endif
|
||||
if (fp) {
|
||||
std::string out;
|
||||
char buf[256];
|
||||
size_t n;
|
||||
while ((n = fread(buf, 1, sizeof(buf), fp)) > 0) out.append(buf, n);
|
||||
#ifdef _WIN32
|
||||
_pclose(fp);
|
||||
#else
|
||||
pclose(fp);
|
||||
#endif
|
||||
ver = parseMinerVersion(out);
|
||||
}
|
||||
}
|
||||
std::lock_guard<std::mutex> lk(g_installed_ver_mutex);
|
||||
g_installed_ver = ver;
|
||||
}).detach();
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lk(stats_mutex_);
|
||||
stats_.pool_hashrate = poolHR;
|
||||
} catch (...) {
|
||||
// Malformed response — ignore
|
||||
}
|
||||
std::string XmrigManager::installedVersion()
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(g_installed_ver_mutex);
|
||||
return g_installed_ver;
|
||||
}
|
||||
|
||||
} // namespace daemon
|
||||
|
||||
@@ -43,6 +43,7 @@ public:
|
||||
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
|
||||
@@ -137,6 +138,18 @@ public:
|
||||
*/
|
||||
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);
|
||||
|
||||
Reference in New Issue
Block a user