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:
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user