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:
2026-06-06 18:07:46 -05:00
parent f5561c0dac
commit 946958b591
6 changed files with 745 additions and 0 deletions

View File

@@ -0,0 +1 @@
{"id":26,"tag_name":"v1.0.0","target_commitish":"master","name":"DRG-XMRig v1.0.0","body":"# DRG-XMRig 6.25.1-drg1 — Release Notes\r\n\r\n**Release date:** 2026-06-06\r\n**Source commit:** `8f711d7c`\r\n**Base:** XMRig 6.25.1 (drg fork — unified pow-hash share model, DragonX/`rx/dragonx` primary algo)\r\n\r\n## About this build\r\n\r\nDRG-XMRig is a fork of xmrig-hac with a unified pow-hash share model. This\r\nrelease makes `rx/dragonx` the primary algorithm name (with `rx/hush` retained\r\nas an alias).\r\n\r\n## Artifacts\r\n\r\n| Platform | File | Size |\r\n|----------|------|------|\r\n| Linux x86_64 | `drg-xmrig-6.25.1-drg1-linux-x64.zip` | 3.9 MiB |\r\n| Windows x86_64 | `drg-xmrig-6.25.1-drg1-win-x64.zip` | 2.7 MiB |\r\n\r\nEach archive contains the miner binary, a starter `config.json`, and `README.md`.\r\nThe Windows archive additionally includes `WinRing0x64.sys` (MSR driver required\r\nfor MSR mods and large-page tweaks).\r\n\r\n## Build configuration\r\n\r\nBoth binaries are **statically linked** release builds (`-DBUILD_STATIC=ON`,\r\n`CMAKE_BUILD_TYPE=Release`) with GPU backends disabled (CPU-only):\r\n\r\n- `WITH_OPENCL=OFF`, `WITH_CUDA=OFF`\r\n- Linux: built with GCC 11.4.0; `WITH_HWLOC=ON`\r\n- Windows: cross-compiled with MinGW-w64; `WITH_HWLOC=OFF`\r\n\r\nBundled dependencies:\r\n\r\n| Dependency | Linux | Windows |\r\n|------------|-------|---------|\r\n| libuv | 1.51.0 | 1.51.0 |\r\n| OpenSSL | 3.0.16 | 1.1.1w |\r\n| hwloc | 2.12.1 | — (disabled) |\r\n\r\n## Checksums (SHA-256)\r\n\r\n### Release archives\r\n\r\n```\r\nc121a078ee46943584aa6148cac26583409c832d28668cf38ba908e10214c9a6 drg-xmrig-6.25.1-drg1-linux-x64.zip\r\n225bd33f5d40af706800fe8e2242222beead65aed14126ed776c32e82119fa1e drg-xmrig-6.25.1-drg1-win-x64.zip\r\n```\r\n\r\n### Binaries (inside the archives)\r\n\r\n```\r\n37c178f743c269c1d9e18302cead0ed117ded2b5fe30910e836896b4abc20e57 xmrig (linux-x64)\r\n01223711eddea347eee394c4b6d265b9a3e5c13fe93204bc041c4399c9c758f8 xmrig.exe (win-x64)\r\n```\r\n\r\n### Verify\r\n\r\n```bash\r\n# Linux\r\nsha256sum -c \u003c\u003c'EOF'\r\nc121a078ee46943584aa6148cac26583409c832d28668cf38ba908e10214c9a6 drg-xmrig-6.25.1-drg1-linux-x64.zip\r\n225bd33f5d40af706800fe8e2242222beead65aed14126ed776c32e82119fa1e drg-xmrig-6.25.1-drg1-win-x64.zip\r\nEOF\r\n```\r\n\r\n```powershell\r\n# Windows (PowerShell)\r\nGet-FileHash .\\drg-xmrig-6.25.1-drg1-win-x64.zip -Algorithm SHA256\r\n```\r\n","url":"https://git.dragonx.is/api/v1/repos/DragonX/drg-xmrig/releases/26","html_url":"https://git.dragonx.is/DragonX/drg-xmrig/releases/tag/v1.0.0","tarball_url":"https://git.dragonx.is/DragonX/drg-xmrig/archive/v1.0.0.tar.gz","zipball_url":"https://git.dragonx.is/DragonX/drg-xmrig/archive/v1.0.0.zip","upload_url":"https://git.dragonx.is/api/v1/repos/DragonX/drg-xmrig/releases/26/assets","draft":false,"prerelease":false,"created_at":"2026-06-06T17:32:05-05:00","published_at":"2026-06-06T17:32:05-05:00","author":{"id":1,"login":"DanS","login_name":"","source_id":0,"full_name":"","email":"dans@noreply.localhost","avatar_url":"https://git.dragonx.is/avatars/ac3f843e96162174082a0b0e2b02b4d894ea793139c2e181e5971483e233a946","html_url":"https://git.dragonx.is/DanS","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2026-02-27T13:12:08-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":1,"username":"DanS"},"assets":[{"id":99,"name":"drg-xmrig-6.25.1-drg1-linux-x64.zip","size":4016958,"download_count":0,"created_at":"2026-06-06T17:49:10-05:00","uuid":"33e950e4-82e2-442f-a6a0-99fcdfad9f9f","browser_download_url":"https://git.dragonx.is/DragonX/drg-xmrig/releases/download/v1.0.0/drg-xmrig-6.25.1-drg1-linux-x64.zip"},{"id":98,"name":"drg-xmrig-6.25.1-drg1-win-x64.zip","size":2843361,"download_count":0,"created_at":"2026-06-06T17:49:09-05:00","uuid":"82f5bd5a-81d5-45b1-8e72-981eab5e9516","browser_download_url":"https://git.dragonx.is/DragonX/drg-xmrig/releases/download/v1.0.0/drg-xmrig-6.25.1-drg1-win-x64.zip"}]}

View File

@@ -20,6 +20,7 @@
#include "ui/windows/mining_tab_helpers.h"
#include "util/amount_format.h"
#include "util/payment_uri.h"
#include "util/xmrig_updater.h"
#include "wallet/lite_owned_string.h"
#include "wallet/lite_rollout_policy.h"
#include "wallet/lite_wallet_controller.h"
@@ -3959,6 +3960,127 @@ void testLiteWalletControllerRolloutGate()
}
}
// ── xmrig updater pure core (util/xmrig_updater.h) — driven by the real release fixture ──
static std::string readXmrigFixture()
{
const std::string path = std::string(DRAGONX_TEST_FIXTURE_DIR) + "/xmrig/release_latest.json";
std::ifstream f(path, std::ios::binary);
return std::string((std::istreambuf_iterator<char>(f)), std::istreambuf_iterator<char>());
}
void testXmrigReleaseParsing()
{
using namespace dragonx::util;
const auto rel = parseXmrigRelease(readXmrigFixture());
EXPECT_TRUE(rel.ok);
EXPECT_EQ(rel.tag, std::string("v1.0.0"));
EXPECT_EQ(rel.assets.size(), static_cast<std::size_t>(2));
// Both assets carry a name + a usable download URL.
for (const auto& a : rel.assets) {
EXPECT_TRUE(!a.name.empty());
EXPECT_TRUE(a.downloadUrl.rfind("https://", 0) == 0);
EXPECT_TRUE(a.size > 0);
}
// Garbage input fails closed.
EXPECT_FALSE(parseXmrigRelease("not json at all").ok);
EXPECT_FALSE(parseXmrigRelease("{}").ok); // no tag_name
}
void testXmrigAssetSelection()
{
using namespace dragonx::util;
const auto rel = parseXmrigRelease(readXmrigFixture());
const int linux = selectXmrigAsset(rel, "linux-x64");
const int win = selectXmrigAsset(rel, "win-x64");
EXPECT_TRUE(linux >= 0);
EXPECT_TRUE(win >= 0);
EXPECT_TRUE(linux != win);
EXPECT_TRUE(rel.assets[linux].name.find("linux-x64.zip") != std::string::npos);
EXPECT_TRUE(rel.assets[win].name.find("win-x64.zip") != std::string::npos);
// No macOS build is published -> graceful "not found".
EXPECT_EQ(selectXmrigAsset(rel, "macos-x64"), -1);
EXPECT_EQ(selectXmrigAsset(rel, "macos-arm64"), -1);
EXPECT_EQ(selectXmrigAsset(rel, ""), -1);
}
void testXmrigChecksumParsing()
{
using namespace dragonx::util;
const auto rel = parseXmrigRelease(readXmrigFixture());
const auto sums = parseXmrigChecksums(rel.body);
// Inner-binary checksums (what we verify before making the binary executable).
EXPECT_EQ(sums.at("xmrig"),
std::string("37c178f743c269c1d9e18302cead0ed117ded2b5fe30910e836896b4abc20e57"));
EXPECT_EQ(sums.at("xmrig.exe"),
std::string("01223711eddea347eee394c4b6d265b9a3e5c13fe93204bc041c4399c9c758f8"));
// Archive checksum, keyed by the asset filename.
EXPECT_EQ(sums.at("drg-xmrig-6.25.1-drg1-linux-x64.zip"),
std::string("c121a078ee46943584aa6148cac26583409c832d28668cf38ba908e10214c9a6"));
// Non-checksum prose lines are ignored.
EXPECT_TRUE(parseXmrigChecksums("just some text\nno hashes here").empty());
}
void testXmrigSha256AndBasenames()
{
using namespace dragonx::util;
// Known-answer: SHA-256("abc").
const std::string abc = "abc";
EXPECT_EQ(sha256Hex(abc.data(), abc.size()),
std::string("ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"));
// The selected asset's published archive checksum matches what the verifier will compute over
// the bytes (closes the loop between parse + hash). Empty input is the SHA-256 of "".
EXPECT_EQ(sha256Hex("", 0),
std::string("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"));
const auto linuxNames = xmrigExtractBasenames("linux-x64");
EXPECT_EQ(linuxNames.size(), static_cast<std::size_t>(1));
EXPECT_EQ(linuxNames.front(), std::string("xmrig"));
const auto winNames = xmrigExtractBasenames("win-x64");
EXPECT_EQ(winNames.size(), static_cast<std::size_t>(2));
EXPECT_EQ(winNames.front(), std::string("xmrig.exe")); // miner binary is always first
EXPECT_TRUE(std::find(winNames.begin(), winNames.end(), std::string("WinRing0x64.sys"))
!= winNames.end());
}
// Live end-to-end exercise of the XmrigUpdater WORKER (real network + curl + miniz). Env-gated so
// CI / offline runs skip it; run with DRAGONX_TEST_NETWORK=1 to hit git.dragonx.is. Verifies the
// full download -> archive-checksum -> extract/flatten -> inner-binary-checksum -> install path.
void testXmrigLiveInstall()
{
if (!std::getenv("DRAGONX_TEST_NETWORK")) {
std::printf("[skip] testXmrigLiveInstall (set DRAGONX_TEST_NETWORK=1 to run)\n");
return;
}
using namespace dragonx::util;
const std::string dir = "/tmp/obsidian-xmrig-live-test";
std::error_code ec; std::filesystem::remove_all(dir, ec);
XmrigUpdater up;
up.startInstall(dir);
for (int i = 0; i < 1200 && !up.isDone(); ++i) // up to ~120s for a ~4 MiB download
std::this_thread::sleep_for(std::chrono::milliseconds(100));
const auto p = up.getProgress();
EXPECT_TRUE(up.isDone());
EXPECT_TRUE(p.state == XmrigUpdater::State::Done);
if (p.state != XmrigUpdater::State::Done) {
std::printf("[testXmrigLiveInstall] failed: %s\n", p.error.c_str());
return;
}
// The miner binary was flattened out of the versioned subdir into the target dir, verified.
const std::string bin = dir + "/" + xmrigExtractBasenames(currentXmrigPlatformToken()).front();
EXPECT_TRUE(std::filesystem::exists(bin));
if (std::filesystem::exists(bin)) {
std::ifstream f(bin, std::ios::binary);
const std::string bytes((std::istreambuf_iterator<char>(f)), std::istreambuf_iterator<char>());
// On this (linux-x64) box the installed binary must match the published inner checksum.
if (currentXmrigPlatformToken() == "linux-x64")
EXPECT_EQ(sha256Hex(bytes.data(), bytes.size()),
std::string("37c178f743c269c1d9e18302cead0ed117ded2b5fe30910e836896b4abc20e57"));
}
std::filesystem::remove_all(dir, ec);
}
} // namespace
int main()
@@ -4012,6 +4134,11 @@ int main()
testLiteRolloutPolicyStagedRollout();
testLiteRolloutManifestLoader();
testLiteWalletControllerRolloutGate();
testXmrigReleaseParsing();
testXmrigAssetSelection();
testXmrigChecksumParsing();
testXmrigSha256AndBasenames();
testXmrigLiveInstall();
testGeneratedResourceBehavior();
if (g_failures != 0) {