drg-xmrig: fork of xmrig-hac with unified pow-hash share model
Port miner113's RX_DRAGONX mining model into the RX_HUSH path so DragonX mining is identical in solo and pool mode: - CpuWorker: filter EVERY hash on SHA256D(header + RandomX solution) (the block-bearing pow-hash) instead of the RandomX hash; submit the full 32-byte nonce + rx_hash. Removes the fragile pool-mode dual-check that was dropping ~half of block candidates. - Job: 32-byte nonce for RX_HUSH in pool mode too (was solo-only). - JobResult: populate nonceBytes() on the standard 4-byte path. - Client: submit a variable-width nonce (32-byte for DragonX) with a dynamically laid-out temp buffer. Effect: shares and blocks use one metric, so the pool receives every block candidate (no under-submission gap) and the hashrate is block-relevant. Rebrand to drg-xmrig (version.h, build.sh, package.json, README) + add PROTOCOL.md wire spec. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -330,10 +330,16 @@ void xmrig::CpuWorker<N>::start()
|
||||
alignas(8) uint8_t current_solo_nonces[N * 32];
|
||||
for (size_t i = 0; i < N; ++i) {
|
||||
current_job_nonces[i] = readUnaligned(m_job.nonce(i));
|
||||
// Save solo nonces BEFORE they get incremented by nextRound()
|
||||
// Save the 32-byte nonce BEFORE nextRound() increments the counter.
|
||||
if (m_job.isSoloMining()) {
|
||||
memcpy(current_solo_nonces + i * 32, m_job.soloNonce(i), 32);
|
||||
}
|
||||
// RX_HUSH/DragonX pool mining: the 32-byte nonce lives in the blob at
|
||||
// [108:140] (4-byte counter + 28 bytes of pool-assigned extraNonce). Save it
|
||||
// verbatim so the submitted nonce matches the header that was hashed.
|
||||
else if (job.algorithm() == Algorithm::RX_HUSH) {
|
||||
memcpy(current_solo_nonces + i * 32, m_job.blob() + m_job.nonceOffset() + i * job.size(), 32);
|
||||
}
|
||||
}
|
||||
|
||||
# ifdef XMRIG_FEATURE_BENCHMARK
|
||||
@@ -412,78 +418,34 @@ void xmrig::CpuWorker<N>::start()
|
||||
}
|
||||
# endif
|
||||
|
||||
if (job.algorithm() == Algorithm::RX_HUSH && m_job.isSoloMining()) {
|
||||
// ── DRAGONX/HUSH solo mining: use double_sha256(173-byte header) for difficulty ──
|
||||
if (job.algorithm() == Algorithm::RX_HUSH) {
|
||||
// ── DRAGONX/HUSH dual-hash PoW (unified solo + pool) ──
|
||||
//
|
||||
// The daemon checks GetHash() = double_sha256(173 bytes) < target,
|
||||
// NOT the RandomX hash directly. We must filter shares the same way
|
||||
// so that every submitted share is a genuine block candidate.
|
||||
// The block/share metric is double_sha256(header + RandomX solution),
|
||||
// NOT the RandomX hash. Filter EVERY hash on this pow-hash so that shares
|
||||
// and blocks use the same metric: a block is simply a share that clears a
|
||||
// harder target, so the pool receives every block candidate (no gap).
|
||||
//
|
||||
// Reconstruct the 140-byte blob that was ACTUALLY hashed this round:
|
||||
// - bytes [0:108] are unchanged by nextRound() (only nonce changes)
|
||||
// - bytes [108:140] = saved nonce (current_solo_nonces, before nextRound)
|
||||
// Reconstruct the 140-byte header that was actually hashed this round:
|
||||
// - bytes [0:108] header base (unchanged by nextRound)
|
||||
// - bytes [108:140] the 32-byte nonce saved before nextRound:
|
||||
// solo: a random 32-byte nonce;
|
||||
// pool: a 4-byte counter + 28 bytes of pool-assigned extraNonce,
|
||||
// preserved verbatim from the blob.
|
||||
uint8_t blob_for_header[140];
|
||||
memcpy(blob_for_header, m_job.blob(), 108); // header base (unchanged)
|
||||
memcpy(blob_for_header + 108, current_solo_nonces + i * 32, 32); // saved 32-byte nonce
|
||||
memcpy(blob_for_header, m_job.blob(), 108);
|
||||
memcpy(blob_for_header + 108, current_solo_nonces + i * 32, 32);
|
||||
|
||||
// Compute PoW hash: double_sha256(blob[140] + 0x20 + rx_hash[32])
|
||||
// PoW hash = double_sha256(blob[140] + 0x20 + rx_hash[32])
|
||||
alignas(8) uint8_t pow_hash[32];
|
||||
dragonx_pow_hash(blob_for_header, m_hash + (i * 32), pow_hash);
|
||||
|
||||
// Compare last 8 bytes of pow_hash (same field as XMRig's standard check)
|
||||
// Compare last 8 bytes (same field as XMRig's standard difficulty check).
|
||||
const uint64_t pow_value = *reinterpret_cast<uint64_t*>(pow_hash + 24);
|
||||
if (pow_value < job.target()) {
|
||||
// Submit full 32-byte nonce + rx_hash as result
|
||||
// Submit the full 32-byte nonce + the RandomX hash as the 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.
|
||||
// Bytes 112-139 come from the pool blob and may contain a per-client
|
||||
// extraNonce1 (pool embeds it at offset 112 for nonce-space partitioning).
|
||||
// We must preserve those bytes so pool-side re-verification matches.
|
||||
uint8_t blob_for_header[140];
|
||||
memcpy(blob_for_header, m_job.blob(), 140); // full blob incl. extraNonce
|
||||
memcpy(blob_for_header + 108, ¤t_job_nonces[i], 4); // overwrite only bytes 108-111
|
||||
|
||||
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<uint64_t*>(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<uint64_t*>(m_hash + (i * 32) + 24);
|
||||
|
||||
Reference in New Issue
Block a user