feat(mining): pool registry, schema-aware stats fetcher, pool-select setting
Data + service layer for supporting multiple mining pools: - pool_registry: KnownPool table (pool.dragonx.is + pool.dragonx.cc) carrying each pool's stratum host, xmrig algo, and stats-API schema; pure helpers findKnownPoolByUrl / resolvePoolAlgo / parsePoolHashrate / chooseWeightedPool (weighted-random by hashrate with incumbent stickiness). - pool_stats_service: background, schema-aware hashrate fetcher (libcurl, TLS, browser UA for Cloudflare) modelled on XmrigUpdater. - settings: pool_select_mode (manual | auto_balance), string-serialized. - i18n strings + unit tests (both stats schemas, algo resolution, weighted pick). Note: pool.dragonx.cc's stratum host is us.dragonx.cc:3333 — the .cc domain is only the Cloudflare-proxied web/API host and does not accept stratum on :3333. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
113
src/util/pool_stats_service.cpp
Normal file
113
src/util/pool_stats_service.cpp
Normal file
@@ -0,0 +1,113 @@
|
||||
// DragonX Wallet - ImGui Edition
|
||||
// Copyright 2024-2026 The Hush Developers
|
||||
// Released under the GPLv3
|
||||
|
||||
#include "pool_stats_service.h"
|
||||
|
||||
#include <curl/curl.h>
|
||||
|
||||
namespace dragonx {
|
||||
namespace util {
|
||||
|
||||
namespace {
|
||||
|
||||
size_t writeStringCb(void* contents, size_t size, size_t nmemb, void* userp)
|
||||
{
|
||||
static_cast<std::string*>(userp)->append(static_cast<char*>(contents), size * nmemb);
|
||||
return size * nmemb;
|
||||
}
|
||||
|
||||
// Returning non-zero asks libcurl to abort the transfer — used so shutdown doesn't
|
||||
// block on an in-flight fetch.
|
||||
int xferInfoCb(void* clientp, curl_off_t, curl_off_t, curl_off_t, curl_off_t)
|
||||
{
|
||||
const auto* self = static_cast<const PoolStatsService*>(clientp);
|
||||
return (self && self->cancelRequested()) ? 1 : 0;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
PoolStatsService::~PoolStatsService()
|
||||
{
|
||||
cancel_requested_ = true;
|
||||
if (worker_.joinable()) worker_.join();
|
||||
}
|
||||
|
||||
void PoolStatsService::refresh(const std::vector<KnownPool>& pools)
|
||||
{
|
||||
if (worker_running_.exchange(true)) return; // already refreshing
|
||||
if (worker_.joinable()) worker_.join(); // reap the previous finished worker
|
||||
worker_ = std::thread([this, pools]() {
|
||||
run(pools);
|
||||
worker_running_ = false;
|
||||
});
|
||||
}
|
||||
|
||||
std::string PoolStatsService::httpGet(const std::string& url)
|
||||
{
|
||||
CURL* curl = curl_easy_init();
|
||||
if (!curl) return {};
|
||||
|
||||
std::string result;
|
||||
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeStringCb);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &result);
|
||||
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
|
||||
// pool.dragonx.cc sits behind Cloudflare and 403s odd User-Agents — present a
|
||||
// browser-like UA and accept compressed responses.
|
||||
curl_easy_setopt(curl, CURLOPT_USERAGENT,
|
||||
"Mozilla/5.0 (compatible; ObsidianDragon)");
|
||||
curl_easy_setopt(curl, CURLOPT_ACCEPT_ENCODING, "");
|
||||
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 8L);
|
||||
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 5L);
|
||||
curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1L);
|
||||
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L);
|
||||
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 2L);
|
||||
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L);
|
||||
curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, xferInfoCb);
|
||||
curl_easy_setopt(curl, CURLOPT_XFERINFODATA, this);
|
||||
|
||||
struct curl_slist* hdrs = nullptr;
|
||||
hdrs = curl_slist_append(hdrs, "Accept: application/json");
|
||||
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, hdrs);
|
||||
|
||||
const CURLcode res = curl_easy_perform(curl);
|
||||
long httpCode = 0;
|
||||
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &httpCode);
|
||||
curl_slist_free_all(hdrs);
|
||||
curl_easy_cleanup(curl);
|
||||
|
||||
if (res != CURLE_OK || httpCode < 200 || httpCode >= 300) return {};
|
||||
return result;
|
||||
}
|
||||
|
||||
void PoolStatsService::run(std::vector<KnownPool> pools)
|
||||
{
|
||||
std::map<std::string, PoolHashrate> results;
|
||||
for (const auto& p : pools) {
|
||||
if (cancel_requested_.load()) break;
|
||||
PoolHashrate hr;
|
||||
hr.id = p.id;
|
||||
const std::string body = httpGet(p.statsUrl);
|
||||
if (!body.empty()) {
|
||||
bool ok = false;
|
||||
const double v = parsePoolHashrate(p.schema, body, p.miningcorePoolId, ok);
|
||||
hr.ok = ok;
|
||||
hr.hashrateHs = ok ? v : 0.0;
|
||||
}
|
||||
results[p.id] = hr;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lk(mutex_);
|
||||
snapshot_.byId = std::move(results);
|
||||
snapshot_.ready = true;
|
||||
}
|
||||
|
||||
PoolStatsService::Snapshot PoolStatsService::snapshot() const
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(mutex_);
|
||||
return snapshot_;
|
||||
}
|
||||
|
||||
} // namespace util
|
||||
} // namespace dragonx
|
||||
Reference in New Issue
Block a user