// DragonX Wallet - ImGui Edition // Copyright 2024-2026 The Hush Developers // Released under the GPLv3 #include "pool_stats_service.h" #include namespace dragonx { namespace util { namespace { size_t writeStringCb(void* contents, size_t size, size_t nmemb, void* userp) { static_cast(userp)->append(static_cast(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(clientp); return (self && self->cancelRequested()) ? 1 : 0; } } // namespace PoolStatsService::~PoolStatsService() { cancel_requested_ = true; if (worker_.joinable()) worker_.join(); } void PoolStatsService::refresh(const std::vector& 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 pools) { std::map 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 lk(mutex_); snapshot_.byId = std::move(results); snapshot_.ready = true; } PoolStatsService::Snapshot PoolStatsService::snapshot() const { std::lock_guard lk(mutex_); return snapshot_; } } // namespace util } // namespace dragonx