feat(mining): xmrig updater service — fetch/verify/install the latest miner from Gitea
Adds util/XmrigUpdater: a background-thread service (mirrors util/Bootstrap) that pulls the latest DRG-XMRig release from the project's Gitea, verifies it, and installs the miner binary into the daemon directory. Service layer only; the mining-tab UI hook comes next. Flow: GET /api/v1/repos/DragonX/drg-xmrig/releases/latest -> pick the asset matching this platform (…-linux-x64.zip / …-win-x64.zip; no macOS build -> graceful "unavailable") -> download (libcurl, TLS verified) -> verify the archive SHA-256 -> extract with miniz, flattening the versioned subdir the archive nests the binary in -> verify the extracted binary's SHA-256 in memory before writing it -> atomic install (+chmod +x on POSIX). On Windows also extracts WinRing0x64.sys; config.json/README.md are skipped. Security (download-and-execute): TLS is verified, and BOTH the archive and the inner binary are checked against the SHA-256 checksums published in the release body (parsed as "<hex> <name>" lines) — install is refused on a missing or mismatched checksum. Split into a pure core (xmrig_updater_core.cpp: release parse, asset/platform match, checksum parse, SHA-256) and the curl/miniz worker (xmrig_updater.cpp). The core is unit-tested against a real captured release fixture (tests/fixtures/xmrig/release_latest.json); an env-gated (DRAGONX_TEST_NETWORK=1) integration test exercises the worker live and was verified end-to-end on linux-x64 (inner binary SHA-256 matches the published value). Both variants build; suite passes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
148
src/util/xmrig_updater_core.cpp
Normal file
148
src/util/xmrig_updater_core.cpp
Normal file
@@ -0,0 +1,148 @@
|
||||
// DragonX Wallet - ImGui Edition
|
||||
// Copyright 2024-2026 The Hush Developers
|
||||
// Released under the GPLv3
|
||||
//
|
||||
// Pure (no-I/O) core of the xmrig updater: release-JSON parsing, platform/asset matching, checksum
|
||||
// parsing, and SHA-256 hashing. Split from xmrig_updater.cpp (the libcurl/miniz worker) so it can
|
||||
// be unit-tested in the test binary, which links libsodium + nlohmann/json but not curl/miniz.
|
||||
|
||||
#include "xmrig_updater.h"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <sodium.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <sstream>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
namespace dragonx {
|
||||
namespace util {
|
||||
|
||||
namespace {
|
||||
|
||||
std::string toLower(std::string s)
|
||||
{
|
||||
std::transform(s.begin(), s.end(), s.begin(),
|
||||
[](unsigned char c) { return static_cast<char>(std::tolower(c)); });
|
||||
return s;
|
||||
}
|
||||
|
||||
bool isHex64(const std::string& s)
|
||||
{
|
||||
if (s.size() != 64) return false;
|
||||
for (unsigned char c : s)
|
||||
if (!std::isxdigit(c)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
XmrigRelease parseXmrigRelease(const std::string& jsonStr)
|
||||
{
|
||||
XmrigRelease r;
|
||||
try {
|
||||
const json j = json::parse(jsonStr);
|
||||
if (j.contains("tag_name") && j["tag_name"].is_string())
|
||||
r.tag = j["tag_name"].get<std::string>();
|
||||
if (j.contains("body") && j["body"].is_string())
|
||||
r.body = j["body"].get<std::string>();
|
||||
if (j.contains("assets") && j["assets"].is_array()) {
|
||||
for (const auto& a : j["assets"]) {
|
||||
if (!a.is_object()) continue;
|
||||
XmrigReleaseAsset asset;
|
||||
if (a.contains("name") && a["name"].is_string())
|
||||
asset.name = a["name"].get<std::string>();
|
||||
if (a.contains("browser_download_url") && a["browser_download_url"].is_string())
|
||||
asset.downloadUrl = a["browser_download_url"].get<std::string>();
|
||||
if (a.contains("size") && a["size"].is_number_integer())
|
||||
asset.size = a["size"].get<long long>();
|
||||
if (!asset.name.empty() && !asset.downloadUrl.empty())
|
||||
r.assets.push_back(std::move(asset));
|
||||
}
|
||||
}
|
||||
if (r.tag.empty()) { r.error = "release JSON has no tag_name"; return r; }
|
||||
r.ok = true;
|
||||
} catch (const std::exception& e) {
|
||||
r.error = std::string("failed to parse release JSON: ") + e.what();
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
std::string currentXmrigPlatformToken()
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
return "win-x64";
|
||||
#elif defined(__APPLE__)
|
||||
#if defined(__aarch64__) || defined(__arm64__)
|
||||
return "macos-arm64";
|
||||
#else
|
||||
return "macos-x64";
|
||||
#endif
|
||||
#elif defined(__linux__)
|
||||
#if defined(__aarch64__)
|
||||
return "linux-arm64";
|
||||
#else
|
||||
return "linux-x64";
|
||||
#endif
|
||||
#else
|
||||
return "";
|
||||
#endif
|
||||
}
|
||||
|
||||
int selectXmrigAsset(const XmrigRelease& release, const std::string& platformToken)
|
||||
{
|
||||
if (platformToken.empty()) return -1;
|
||||
const std::string needle = "-" + toLower(platformToken) + ".zip";
|
||||
for (std::size_t i = 0; i < release.assets.size(); ++i) {
|
||||
const std::string n = toLower(release.assets[i].name);
|
||||
if (n.size() >= needle.size() &&
|
||||
n.compare(n.size() - needle.size(), needle.size(), needle) == 0)
|
||||
return static_cast<int>(i);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::map<std::string, std::string> parseXmrigChecksums(const std::string& body)
|
||||
{
|
||||
std::map<std::string, std::string> out;
|
||||
std::istringstream in(body);
|
||||
std::string line;
|
||||
while (std::getline(in, line)) {
|
||||
std::istringstream ls(line);
|
||||
std::string hash, name;
|
||||
if (!(ls >> hash)) continue;
|
||||
if (!isHex64(hash)) continue;
|
||||
if (!(ls >> name)) continue;
|
||||
// First whitespace-delimited token after the hash is the key (zip name, or "xmrig"/
|
||||
// "xmrig.exe" for the inner-binary block; any trailing "(linux-x64)" annotation is ignored).
|
||||
out[name] = toLower(hash);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
std::string sha256Hex(const void* data, std::size_t len)
|
||||
{
|
||||
unsigned char hash[crypto_hash_sha256_BYTES];
|
||||
if (crypto_hash_sha256(hash, static_cast<const unsigned char*>(data), len) != 0)
|
||||
return {};
|
||||
static const char* kHex = "0123456789abcdef";
|
||||
std::string out;
|
||||
out.reserve(crypto_hash_sha256_BYTES * 2);
|
||||
for (unsigned char c : hash) {
|
||||
out.push_back(kHex[c >> 4]);
|
||||
out.push_back(kHex[c & 0x0F]);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
std::vector<std::string> xmrigExtractBasenames(const std::string& platformToken)
|
||||
{
|
||||
if (platformToken.rfind("win", 0) == 0)
|
||||
return {"xmrig.exe", "WinRing0x64.sys"};
|
||||
return {"xmrig"};
|
||||
}
|
||||
|
||||
} // namespace util
|
||||
} // namespace dragonx
|
||||
Reference in New Issue
Block a user