From 7d22bc2bb5f1c0d8c3981746afe6cb1d07f5ce40 Mon Sep 17 00:00:00 2001 From: DanS Date: Mon, 9 Mar 2026 23:52:34 -0500 Subject: [PATCH] Add dual SHA256D block check for pool mining mode Pool sends block_target (full 256-bit network target) with each job. Miner checks SHA256D(header + RandomX solution) for every hash against the block target, enabling block detection at full hashrate instead of only on submitted shares. --- src/backend/cpu/CpuWorker.cpp | 47 +++++++++++++++++++++++++++++++++ src/base/net/stratum/Client.cpp | 1 + src/base/net/stratum/Job.cpp | 40 ++++++++++++++++++++++++++++ src/base/net/stratum/Job.h | 7 +++++ 4 files changed, 95 insertions(+) diff --git a/src/backend/cpu/CpuWorker.cpp b/src/backend/cpu/CpuWorker.cpp index 331d3882..00a79d73 100644 --- a/src/backend/cpu/CpuWorker.cpp +++ b/src/backend/cpu/CpuWorker.cpp @@ -436,6 +436,53 @@ void xmrig::CpuWorker::start() // Submit full 32-byte nonce + rx_hash as result JobResults::submit(JobResult(job, current_solo_nonces + i * 32, m_hash + (i * 32))); } + } else if (job.algorithm() == Algorithm::RX_HUSH && !m_job.isSoloMining()) { + // ── DRAGONX/HUSH pool mining: dual check ── + // + // DragonX uses dual PoW: the block hash is SHA256D(header + RandomX solution), + // NOT the RandomX hash itself. We must check SHA256D for EVERY hash to detect + // blocks, and also check the RandomX hash for share difficulty. + // + // Reconstruct the 140-byte header with the 4-byte nonce at offset 108. + // In pool mode, bytes 112-139 of nNonce are zero (only 4-byte nonce used). + uint8_t blob_for_header[140]; + memcpy(blob_for_header, m_job.blob(), 108); // header base + memset(blob_for_header + 108, 0, 32); // clear 32-byte nonce field + memcpy(blob_for_header + 108, ¤t_job_nonces[i], 4); // 4-byte nonce at offset 108 + + bool submitted = false; + + // Check SHA256D for block detection if pool sent block_target + if (job.hasBlockTarget()) { + alignas(8) uint8_t pow_hash[32]; + dragonx_pow_hash(blob_for_header, m_hash + (i * 32), pow_hash); + + // Compare pow_hash <= block_target (both in uint256 internal byte order) + // byte[31] is MSB (most significant in arith terms), compare from there down + bool isBlock = true; + for (int b = 31; b >= 0; --b) { + if (pow_hash[b] < job.blockTarget()[b]) { + break; // pow_hash < target → is a block + } else if (pow_hash[b] > job.blockTarget()[b]) { + isBlock = false; + break; // pow_hash > target → not a block + } + } + + if (isBlock) { + // SHA256D meets block target — submit immediately + JobResults::submit(job, current_job_nonces[i], m_hash + (i * 32), nullptr); + submitted = true; + } + } + + // Also check RandomX hash for normal share difficulty (if not already submitted) + if (!submitted) { + const uint64_t value = *reinterpret_cast(m_hash + (i * 32) + 24); + if (value < job.target()) { + JobResults::submit(job, current_job_nonces[i], m_hash + (i * 32), nullptr); + } + } } else { // ── Standard XMRig path (Monero, CryptoNight, etc.) ── const uint64_t value = *reinterpret_cast(m_hash + (i * 32) + 24); diff --git a/src/base/net/stratum/Client.cpp b/src/base/net/stratum/Client.cpp index b412a543..f0110a96 100644 --- a/src/base/net/stratum/Client.cpp +++ b/src/base/net/stratum/Client.cpp @@ -418,6 +418,7 @@ bool xmrig::Client::parseJob(const rapidjson::Value ¶ms, int *code) } job.setSigKey(Json::getString(params, "sig_key")); + job.setBlockTarget(Json::getString(params, "block_target")); m_job.setClientId(m_rpcId); diff --git a/src/base/net/stratum/Job.cpp b/src/base/net/stratum/Job.cpp index ee5e0e31..6dd4aa24 100644 --- a/src/base/net/stratum/Job.cpp +++ b/src/base/net/stratum/Job.cpp @@ -115,6 +115,40 @@ bool xmrig::Job::setSeedHash(const char *hash) } +bool xmrig::Job::setBlockTarget(const char *target) +{ + if (!target) { + m_hasBlockTarget = false; + return false; + } + + const size_t len = strlen(target); + if (len != 64) { // 32 bytes = 64 hex chars + m_hasBlockTarget = false; + return false; + } + + // Parse 64-char hex string (display order) into uint256-compatible internal byte order. + // Display "00072f0f...000" → internal: data[31]=0x00, data[30]=0x07, data[29]=0x2f, ... + // This matches how Bitcoin/Zcash uint256 stores values (LSB at data[0], MSB at data[31]). + // Raw SHA256D output from OpenSSL also goes into uint256.data[] without reversal, + // so both hash and target are in the same internal byte order for comparison. + uint8_t tmp[32]; + if (!Cvt::fromHex(tmp, sizeof(tmp), target, len)) { + m_hasBlockTarget = false; + return false; + } + + // Reverse display order to internal byte order (LSB-first / little-endian uint256) + for (int i = 0; i < 32; ++i) { + m_blockTarget[i] = tmp[31 - i]; + } + + m_hasBlockTarget = true; + return true; +} + + bool xmrig::Job::setTarget(const char *target) { static auto parse = [](const char *target, size_t size, const Algorithm &algorithm) -> uint64_t { @@ -297,6 +331,9 @@ void xmrig::Job::copy(const Job &other) # endif m_hasMinerSignature = other.m_hasMinerSignature; + m_hasBlockTarget = other.m_hasBlockTarget; + m_isSoloMining = other.m_isSoloMining; + memcpy(m_blockTarget, other.m_blockTarget, sizeof(m_blockTarget)); } @@ -353,6 +390,9 @@ void xmrig::Job::move(Job &&other) # endif m_hasMinerSignature = other.m_hasMinerSignature; + m_hasBlockTarget = other.m_hasBlockTarget; + m_isSoloMining = other.m_isSoloMining; + memcpy(m_blockTarget, other.m_blockTarget, sizeof(m_blockTarget)); } diff --git a/src/base/net/stratum/Job.h b/src/base/net/stratum/Job.h index 61353ef0..3b524849 100644 --- a/src/base/net/stratum/Job.h +++ b/src/base/net/stratum/Job.h @@ -105,6 +105,11 @@ public: inline bool isSoloMining() const { return m_isSoloMining; } inline void setSoloMining(bool solo) { m_isSoloMining = solo; } + // DragonX block target for SHA256D PoW check (32 bytes, internal byte order) + inline bool hasBlockTarget() const { return m_hasBlockTarget; } + inline const uint8_t *blockTarget() const { return m_blockTarget; } + bool setBlockTarget(const char *target); + void setHushHeader(const uint8_t *header108); # ifdef XMRIG_PROXY_PROJECT @@ -172,6 +177,8 @@ private: uint8_t m_blob[kMaxBlobSize]{ 0 }; uint8_t m_index = 0; bool m_isSoloMining = false; + bool m_hasBlockTarget = false; + uint8_t m_blockTarget[32]{}; # ifdef XMRIG_PROXY_PROJECT char m_rawBlob[kMaxBlobSize * 2 + 8]{};