From 95d3ff2c4a3490d2d4a402962e27fce479317a80 Mon Sep 17 00:00:00 2001 From: DanS Date: Thu, 5 Mar 2026 06:00:55 -0600 Subject: [PATCH] Fixing miner issues - Switch from full-block to header-only (140-byte) RandomX input - Add 32-byte SoloNonce system for solo mining mode - Compute proper difficulty target from compact bits field - Add SHA256D dual-hash PoW check in CpuWorker for solo mining - Raise RandomX dataset/scratchpad limits to 4GB/4MB - Use standard RandomX share filtering in pool (stratum) mode --- CMakeLists.txt | 2 + src/backend/common/WorkerJob.h | 28 +++ src/backend/cpu/CpuWorker.cpp | 120 ++++++++++- src/base/net/stratum/HushClient.cpp | 303 ++++++++++++---------------- src/base/net/stratum/HushClient.h | 13 +- src/base/net/stratum/Job.cpp | 8 + src/base/net/stratum/Job.h | 13 +- src/crypto/common/SoloNonce.cpp | 105 ++++++++++ src/crypto/common/SoloNonce.h | 87 ++++++++ src/crypto/randomx/configuration.h | 6 +- src/donate.h | 4 +- src/net/JobResult.h | 25 +++ 12 files changed, 529 insertions(+), 185 deletions(-) create mode 100644 src/crypto/common/SoloNonce.cpp create mode 100644 src/crypto/common/SoloNonce.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 30fea744..7c0d3715 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -91,6 +91,7 @@ set(HEADERS_CRYPTO src/crypto/common/HugePagesInfo.h src/crypto/common/MemoryPool.h src/crypto/common/Nonce.h + src/crypto/common/SoloNonce.h src/crypto/common/portable/mm_malloc.h src/crypto/common/VirtualMemory.h ) @@ -128,6 +129,7 @@ set(SOURCES_CRYPTO src/crypto/common/HugePagesInfo.cpp src/crypto/common/MemoryPool.cpp src/crypto/common/Nonce.cpp + src/crypto/common/SoloNonce.cpp src/crypto/common/VirtualMemory.cpp ) diff --git a/src/backend/common/WorkerJob.h b/src/backend/common/WorkerJob.h index b579a116..a4f08284 100644 --- a/src/backend/common/WorkerJob.h +++ b/src/backend/common/WorkerJob.h @@ -32,6 +32,7 @@ #include "base/net/stratum/Job.h" #include "base/tools/Alignment.h" #include "crypto/common/Nonce.h" +#include "crypto/common/SoloNonce.h" namespace xmrig { @@ -89,6 +90,29 @@ public: inline int32_t nonceOffset() const { return currentJob().nonceOffset(); } inline size_t nonceSize() const { return currentJob().nonceSize(); } + // Solo mining nonce management + inline bool isSoloMining() const { return m_isSoloMining; } + inline void setSoloMining(bool solo) { m_isSoloMining = solo; } + inline uint8_t* soloNonce(size_t i = 0) { return m_soloNonces[index()] + i * 32; } + inline const uint8_t* soloNonce(size_t i = 0) const { return m_soloNonces[index()] + i * 32; } + + inline void initSoloNonces() { + for (size_t i = 0; i < N; ++i) { + SoloNonce::initialize(soloNonce(i)); + // Copy to blob at nonce offset + SoloNonce::copyToBlob(blob() + (i * currentJob().size()) + nonceOffset(), 0, soloNonce(i)); + } + } + + inline bool nextRoundSolo() { + for (size_t i = 0; i < N; ++i) { + SoloNonce::increment(soloNonce(i)); + // Copy to blob at nonce offset + SoloNonce::copyToBlob(blob() + (i * currentJob().size()) + nonceOffset(), 0, soloNonce(i)); + } + return true; // Never exhausted with 256-bit nonces + } + private: inline uint64_t nonceMask() const { return m_nonce_mask[index()]; } @@ -115,6 +139,10 @@ private: uint64_t m_nonce_mask[2] = { 0, 0 }; uint64_t m_sequence = 0; uint8_t m_index = 0; + + // Solo mining nonces (32 bytes per job slot per N) + alignas(8) uint8_t m_soloNonces[2][32 * N]{}; + bool m_isSoloMining = false; }; diff --git a/src/backend/cpu/CpuWorker.cpp b/src/backend/cpu/CpuWorker.cpp index cba7e883..331d3882 100644 --- a/src/backend/cpu/CpuWorker.cpp +++ b/src/backend/cpu/CpuWorker.cpp @@ -40,6 +40,35 @@ #include "net/JobResults.h" +#ifdef XMRIG_FEATURE_TLS +# include +#else +// Standalone SHA-256 for non-TLS builds +# ifdef _WIN32 +# include +# include +static void SHA256(const uint8_t* data, size_t len, uint8_t* out) { + BCRYPT_ALG_HANDLE hAlg = nullptr; + BCRYPT_HASH_HANDLE hHash = nullptr; + BCryptOpenAlgorithmProvider(&hAlg, BCRYPT_SHA256_ALGORITHM, nullptr, 0); + BCryptCreateHash(hAlg, &hHash, nullptr, 0, nullptr, 0, 0); + BCryptHashData(hHash, const_cast(data), static_cast(len), 0); + BCryptFinishHash(hHash, out, 32, 0); + BCryptDestroyHash(hHash); + BCryptCloseAlgorithmProvider(hAlg, 0); +} +# else +# include "crypto/ghostrider/sph_sha2.h" +static void SHA256(const uint8_t* data, size_t len, uint8_t* out) { + sph_sha256_context ctx; + sph_sha256_init(&ctx); + sph_sha256(&ctx, data, len); + sph_sha256_close(&ctx, out); +} +# endif +#endif + + #ifdef XMRIG_ALGO_RANDOMX # include "crypto/randomx/randomx.h" #endif @@ -55,6 +84,37 @@ namespace xmrig { static constexpr uint32_t kReserveCount = 32768; +// ── DRAGONX/HUSH PoW hash ──────────────────────────────────────────────────── +// +// DragonX block difficulty is checked against double_sha256(173-byte header), +// NOT the RandomX hash directly (unlike Monero). +// +// Full 173-byte header structure: +// [0:108] header_base = version(4) + prevhash(32) + merkle(32) +// + commitments(32) + time(4) + bits(4) +// [108:140] nonce = 32-byte miner nonce +// [140] 0x20 = compact_size for 32-byte solution +// [141:173] rx_solution = RandomX hash result (32 bytes) +// +// @param blob 140-byte header WITH nonce already at bytes [108:140] +// @param rx_hash 32-byte RandomX hash result +// @param out 32-byte output buffer for double_sha256 + +static inline void dragonx_pow_hash(const uint8_t* blob, const uint8_t* rx_hash, uint8_t* out) +{ + uint8_t full_header[173]; + memcpy(full_header, blob, 140); // header (108 bytes) + nonce (32 bytes) + full_header[140] = 0x20; // compact_size = 32 (solution length) + memcpy(full_header + 141, rx_hash, 32); // RandomX hash = the PoW solution + + // double SHA256 = SHA256(SHA256(full_header)) + uint8_t tmp[32]; + SHA256(full_header, 173, tmp); + SHA256(tmp, 32, out); +} +// ───────────────────────────────────────────────────────────────────────────── + + #ifdef XMRIG_ALGO_CN_HEAVY static std::mutex cn_heavyZen3MemoryMutex; VirtualMemory* cn_heavyZen3Memory = nullptr; @@ -267,8 +327,13 @@ void xmrig::CpuWorker::start() } uint32_t current_job_nonces[N]; + 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() + if (m_job.isSoloMining()) { + memcpy(current_solo_nonces + i * 32, m_job.soloNonce(i), 32); + } } # ifdef XMRIG_FEATURE_BENCHMARK @@ -337,18 +402,50 @@ void xmrig::CpuWorker::start() if (valid) { for (size_t i = 0; i < N; ++i) { - const uint64_t value = *reinterpret_cast(m_hash + (i * 32) + 24); - # ifdef XMRIG_FEATURE_BENCHMARK if (m_benchSize) { + const uint64_t value = *reinterpret_cast(m_hash + (i * 32) + 24); if (current_job_nonces[i] < m_benchSize) { BenchState::add(value); } + continue; } - else # endif - if (value < job.target()) { - JobResults::submit(job, current_job_nonces[i], m_hash + (i * 32), job.hasMinerSignature() ? miner_signature_saved : nullptr); + + if (job.algorithm() == Algorithm::RX_HUSH && m_job.isSoloMining()) { + // ── DRAGONX/HUSH solo mining: use double_sha256(173-byte header) for difficulty ── + // + // 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. + // + // 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) + 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 + + // Compute 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) + const uint64_t pow_value = *reinterpret_cast(pow_hash + 24); + if (pow_value < job.target()) { + // Submit full 32-byte nonce + rx_hash as result + JobResults::submit(JobResult(job, current_solo_nonces + i * 32, m_hash + (i * 32))); + } + } else { + // ── Standard XMRig path (Monero, CryptoNight, etc.) ── + const uint64_t value = *reinterpret_cast(m_hash + (i * 32) + 24); + if (value < job.target()) { + if (m_job.isSoloMining()) { + JobResults::submit(JobResult(job, current_solo_nonces + i * 32, m_hash + (i * 32))); + } else { + JobResults::submit(job, current_job_nonces[i], m_hash + (i * 32), job.hasMinerSignature() ? miner_signature_saved : nullptr); + } + } } } m_count += N; @@ -369,6 +466,11 @@ void xmrig::CpuWorker::start() template bool xmrig::CpuWorker::nextRound() { + // Solo mining uses its own 256-bit nonce management + if (m_job.isSoloMining()) { + return m_job.nextRoundSolo(); + } + # ifdef XMRIG_FEATURE_BENCHMARK const uint32_t count = m_benchSize ? 1U : kReserveCount; # else @@ -516,6 +618,14 @@ void xmrig::CpuWorker::consumeJob() m_job.add(job, count, Nonce::CPU); + // Handle solo mining nonce initialization + if (job.isSoloMining()) { + m_job.setSoloMining(true); + m_job.initSoloNonces(); + } else { + m_job.setSoloMining(false); + } + # ifdef XMRIG_ALGO_RANDOMX if (m_job.currentJob().algorithm().family() == Algorithm::RANDOM_X) { allocateRandomX_VM(); diff --git a/src/base/net/stratum/HushClient.cpp b/src/base/net/stratum/HushClient.cpp index 64d5f471..8bd48a3c 100644 --- a/src/base/net/stratum/HushClient.cpp +++ b/src/base/net/stratum/HushClient.cpp @@ -23,6 +23,7 @@ #include "net/JobResult.h" #include +#include #include #ifdef XMRIG_FEATURE_TLS @@ -163,19 +164,22 @@ int64_t HushClient::submit(const JobResult &result) return -1; } - // HAC PoW: RandomX hash becomes nSolution, then SHA256D(header) must be < target - // Check if SHA256D(header + solution) meets network target before submitting - if (!checkPow(result.nonce, result.result())) { - // Hash doesn't meet network target, don't submit + // Get the 32-byte nonce from the solo mining result + const uint8_t *nonce32 = result.isSoloResult() ? result.soloNonce() : nullptr; + if (!nonce32) { + LOG_ERR("%s " RED("submit called without solo nonce"), tag()); return -1; } - // Log submission details - const String solutionHex = Cvt::toHex(result.result(), 32); - LOG_INFO("%s " CYAN_BOLD("submitting block") " nonce=0x%08x", tag(), result.nonce); + // HAC PoW: RandomX hash becomes nSolution, then SHA256D(header + solution) must be < target + if (!checkPow(nonce32, result.result())) { + return -1; + } - // Build the full block with header + solution + transactions - const std::string blockHex = serializeBlockHex(result.nonce, result.result()); + LOG_INFO("%s " CYAN_BOLD("submitting block at height %u"), tag(), m_height); + + // Build the full block with header + nonce + solution + transactions + const std::string blockHex = serializeBlockHex(nonce32, result.result()); return submitBlock(blockHex); } @@ -400,111 +404,100 @@ bool HushClient::parseBlockTemplate(const rapidjson::Value &result) // Create the mining job Job job(false, m_pool.algorithm(), String()); job.setHeight(m_height); - - // Set diff=1 so ALL RandomX results get submitted to us for PoW checking - // We filter in submit() by checking SHA256D(header+solution) < target - job.setDiff(1); - + if (!job.setSeedHash(m_keyBlockHash.data())) { LOG_ERR("%s " RED("failed to set seed hash: %s (len=%zu)"), tag(), m_keyBlockHash.data(), m_keyBlockHash.size()); return false; } - // Build full block blob for RandomX hashing: - // HAC computes RandomX(full_serialized_block) where block = header + nSolution(empty) + txcount + txs - // Header: version(4) + prevHash(32) + merkleRoot(32) + saplingRoot(32) + time(4) + bits(4) + nonce(32) + nSolution(varint_len + data) - m_headerBlob.clear(); - m_headerBlob.reserve(1024); // Will grow if many transactions + // Build 108-byte header for RandomX hashing (nonce bytes 108-139 filled by worker): + // version(4) + prevHash(32) + merkleRoot(32) + saplingRoot(32) + time(4) + bits(4) = 108 + uint8_t header[108]; + size_t offset = 0; // nVersion (4 bytes, little-endian) const uint32_t ver = static_cast(m_version); - m_headerBlob.insert(m_headerBlob.end(), reinterpret_cast(&ver), - reinterpret_cast(&ver) + 4); + memcpy(header + offset, &ver, 4); + offset += 4; - // hashPrevBlock (32 bytes, internal byte order) - std::vector prevHashBytes(32); - Cvt::fromHex(prevHashBytes.data(), 32, m_prevHash.data(), 64); - std::reverse(prevHashBytes.begin(), prevHashBytes.end()); - m_headerBlob.insert(m_headerBlob.end(), prevHashBytes.begin(), prevHashBytes.end()); - - // hashMerkleRoot (32 bytes) - std::vector merkleRoot(32); - Cvt::fromHex(merkleRoot.data(), 32, m_merkleRoot.data(), 64); - std::reverse(merkleRoot.begin(), merkleRoot.end()); - m_headerBlob.insert(m_headerBlob.end(), merkleRoot.begin(), merkleRoot.end()); - - // hashFinalSaplingRoot (32 bytes) - std::vector saplingRoot(32); - Cvt::fromHex(saplingRoot.data(), 32, m_saplingRoot.data(), 64); - std::reverse(saplingRoot.begin(), saplingRoot.end()); - m_headerBlob.insert(m_headerBlob.end(), saplingRoot.begin(), saplingRoot.end()); - - // nTime (4 bytes) - const uint32_t time32 = static_cast(m_curtime); - m_headerBlob.insert(m_headerBlob.end(), reinterpret_cast(&time32), - reinterpret_cast(&time32) + 4); - - // nBits (4 bytes) - uint32_t bits = 0; - for (int i = 0; i < 8 && i < static_cast(m_bits.size()); i += 2) { - uint8_t byte; - Cvt::fromHex(&byte, 1, m_bits.data() + i, 2); - bits = (bits << 8) | byte; - } - m_headerBlob.insert(m_headerBlob.end(), reinterpret_cast(&bits), - reinterpret_cast(&bits) + 4); - - // nNonce placeholder (32 bytes of zeros - miner fills first 4 bytes) - // Remember position for later extraction - m_nonceOffset = m_headerBlob.size(); - m_headerBlob.insert(m_headerBlob.end(), 32, 0); - - // nSolution - empty vector serialized as compact size 0 - // For CBlock serialization, nSolution is std::vector - // Empty vector = compactsize(0) = 0x00 - m_headerBlob.push_back(0x00); - - // Transactions: compactsize(count) + coinbase + other txs - const size_t txCount = 1 + m_transactions.size(); // coinbase + others - - // Write compact size for transaction count - if (txCount < 0xFD) { - m_headerBlob.push_back(static_cast(txCount)); - } else if (txCount <= 0xFFFF) { - m_headerBlob.push_back(0xFD); - const uint16_t cnt16 = static_cast(txCount); - m_headerBlob.insert(m_headerBlob.end(), reinterpret_cast(&cnt16), - reinterpret_cast(&cnt16) + 2); - } else { - m_headerBlob.push_back(0xFE); - const uint32_t cnt32 = static_cast(txCount); - m_headerBlob.insert(m_headerBlob.end(), reinterpret_cast(&cnt32), - reinterpret_cast(&cnt32) + 4); - } - - // Append coinbase transaction - std::vector cbTx(m_coinbaseTx.size() / 2); - Cvt::fromHex(cbTx.data(), cbTx.size(), m_coinbaseTx.data(), m_coinbaseTx.size()); - m_headerBlob.insert(m_headerBlob.end(), cbTx.begin(), cbTx.end()); - - // Append other transactions - for (const auto &txHex : m_transactions) { - std::vector tx(txHex.size() / 2); - Cvt::fromHex(tx.data(), tx.size(), txHex.data(), txHex.size()); - m_headerBlob.insert(m_headerBlob.end(), tx.begin(), tx.end()); - } - - // Set the full block blob for mining - LOG_DEBUG("%s block blob size=%zu (header=141 + %zu txs), seed=%s", - tag(), m_headerBlob.size(), txCount, m_keyBlockHash.data()); + // hashPrevBlock (32 bytes, internal byte order = reversed from display) { - const String hdrHex = Cvt::toHex(m_headerBlob.data(), std::min(m_headerBlob.size(), size_t(200))); - LOG_DEBUG("%s block: %s...", tag(), hdrHex.data()); + std::vector tmp(32); + Cvt::fromHex(tmp.data(), 32, m_prevHash.data(), 64); + std::reverse(tmp.begin(), tmp.end()); + memcpy(header + offset, tmp.data(), 32); + // Also store for block submission + memcpy(m_headerPrevHash, tmp.data(), 32); } - if (!job.setBlob(Cvt::toHex(m_headerBlob.data(), m_headerBlob.size()))) { - LOG_ERR("%s " RED("failed to set job blob (size=%zu)"), tag(), m_headerBlob.size()); - return false; + offset += 32; + + // hashMerkleRoot (32 bytes, reversed) + { + std::vector tmp(32); + Cvt::fromHex(tmp.data(), 32, m_merkleRoot.data(), 64); + std::reverse(tmp.begin(), tmp.end()); + memcpy(header + offset, tmp.data(), 32); + memcpy(m_headerMerkleRoot, tmp.data(), 32); } + offset += 32; + + // hashFinalSaplingRoot (32 bytes, reversed) + { + std::vector tmp(32); + Cvt::fromHex(tmp.data(), 32, m_saplingRoot.data(), 64); + std::reverse(tmp.begin(), tmp.end()); + memcpy(header + offset, tmp.data(), 32); + memcpy(m_headerSaplingRoot, tmp.data(), 32); + } + offset += 32; + + // nTime (4 bytes, little-endian) + const uint32_t time32 = static_cast(m_curtime); + memcpy(header + offset, &time32, 4); + m_headerTime = time32; + offset += 4; + + // nBits (4 bytes) - parse from big-endian hex, store as little-endian uint32 + uint32_t bits = 0; + if (m_bits.size() == 8) { + bits = static_cast(strtoul(m_bits.data(), nullptr, 16)); + } + memcpy(header + offset, &bits, 4); + m_headerBits = bits; + offset += 4; + + // Set the 108-byte header directly into the job blob (worker adds 32-byte nonce at offset 108) + job.setHushHeader(header); + + // Compute 64-bit target from compact "bits" field (Bitcoin/Zcash format) + // Top byte = exponent, lower 3 bytes = mantissa + // Full 256-bit target = mantissa * 2^(8*(exponent-3)) + // xmrig compares: (uint64_t)hash[24..31] < target64 + { + int exponent = (bits >> 24) & 0xff; + uint32_t mantissa = bits & 0x007fffff; + int mantissaPos = exponent - 3; // Byte position of mantissa's low byte + + uint64_t target64 = 0; + + if (mantissaPos >= 32) { + target64 = 0xFFFFFFFFFFFFFFFFULL; + } else if (mantissaPos >= 24) { + int shift = (mantissaPos - 24) * 8; + target64 = (uint64_t)mantissa << shift; + if (mantissaPos > 29) { + target64 = 0xFFFFFFFFFFFFFFFFULL; + } + } else { + // Mantissa is entirely below byte 24 - extremely high difficulty + target64 = 0; + } + + job.setTarget64(target64); + } + + // Mark as solo mining so workers use 32-byte SoloNonce + job.setSoloMining(true); m_currentJobId = Cvt::toHex(Cvt::randomBytes(4)); job.setId(m_currentJobId); @@ -518,8 +511,8 @@ bool HushClient::parseBlockTemplate(const rapidjson::Value &result) setState(ConnectedState); } - LOG_INFO("%s " MAGENTA_BOLD("new job") " height: " CYAN_BOLD("%u") " diff: " CYAN_BOLD("%u"), - tag(), m_height, job.diff()); + LOG_INFO("%s " MAGENTA_BOLD("new job") " height: " CYAN_BOLD("%u") " bits: " CYAN_BOLD("0x%08x") " diff: " CYAN_BOLD("%" PRIu64), + tag(), m_height, m_headerBits, m_job.diff()); m_listener->onJobReceived(this, m_job, rapidjson::Value()); return true; @@ -718,50 +711,38 @@ std::vector HushClient::serializeBlock(uint32_t nonce, uint64_t extraNo return blob; } -std::string HushClient::serializeBlockHex(uint32_t nonce, const uint8_t* solution) const +std::string HushClient::serializeBlockHex(const uint8_t* nonce32, const uint8_t* solution) const { std::vector blob; blob.reserve(1024); - // Header (same as serializeBlock but with actual nonce and solution) + // Header (140 bytes) using cached binary fields + // Version (4 bytes LE) const uint32_t ver = static_cast(m_version); blob.insert(blob.end(), reinterpret_cast(&ver), reinterpret_cast(&ver) + 4); - std::vector prevHash(32); - Cvt::fromHex(prevHash.data(), 32, m_prevHash.data(), 64); - std::reverse(prevHash.begin(), prevHash.end()); - blob.insert(blob.end(), prevHash.begin(), prevHash.end()); + // Previous hash (32 bytes - already in internal order) + blob.insert(blob.end(), m_headerPrevHash, m_headerPrevHash + 32); - std::vector merkleRoot(32); - Cvt::fromHex(merkleRoot.data(), 32, m_merkleRoot.data(), 64); - std::reverse(merkleRoot.begin(), merkleRoot.end()); - blob.insert(blob.end(), merkleRoot.begin(), merkleRoot.end()); + // Merkle root (32 bytes) + blob.insert(blob.end(), m_headerMerkleRoot, m_headerMerkleRoot + 32); - std::vector saplingRoot(32); - Cvt::fromHex(saplingRoot.data(), 32, m_saplingRoot.data(), 64); - std::reverse(saplingRoot.begin(), saplingRoot.end()); - blob.insert(blob.end(), saplingRoot.begin(), saplingRoot.end()); + // Sapling root (32 bytes) + blob.insert(blob.end(), m_headerSaplingRoot, m_headerSaplingRoot + 32); - const uint32_t time32 = static_cast(m_curtime); - blob.insert(blob.end(), reinterpret_cast(&time32), - reinterpret_cast(&time32) + 4); + // Time (4 bytes LE) + blob.insert(blob.end(), reinterpret_cast(&m_headerTime), + reinterpret_cast(&m_headerTime) + 4); - uint32_t bits = 0; - for (int i = 0; i < 8 && i < static_cast(m_bits.size()); i += 2) { - uint8_t byte; - Cvt::fromHex(&byte, 1, m_bits.data() + i, 2); - bits = (bits << 8) | byte; - } - blob.insert(blob.end(), reinterpret_cast(&bits), - reinterpret_cast(&bits) + 4); + // Bits (4 bytes LE) + blob.insert(blob.end(), reinterpret_cast(&m_headerBits), + reinterpret_cast(&m_headerBits) + 4); - // Nonce (32 bytes with found nonce value) - std::vector nonce256(32, 0); - memcpy(nonce256.data(), &nonce, 4); - blob.insert(blob.end(), nonce256.begin(), nonce256.end()); + // Nonce (32 bytes from the mining result) + blob.insert(blob.end(), nonce32, nonce32 + 32); - // Solution (the RandomX hash - 32 bytes) + // Solution (the RandomX hash - 32 bytes with CompactSize prefix) blob.push_back(32); // CompactSize blob.insert(blob.end(), solution, solution + 32); @@ -796,53 +777,31 @@ void HushClient::sha256d(const uint8_t* data, size_t len, uint8_t* out) ::SHA256(hash1, 32, out); } -std::vector HushClient::serializeHeader(uint32_t nonce, const uint8_t* solution) const +std::vector HushClient::serializeHeader(const uint8_t* nonce32, const uint8_t* solution) const { std::vector header; header.reserve(140 + 1 + 32); // header + compactsize + solution - // nVersion (4 bytes, little-endian) + // Version (4 bytes LE) const uint32_t ver = static_cast(m_version); header.insert(header.end(), reinterpret_cast(&ver), reinterpret_cast(&ver) + 4); - // hashPrevBlock (32 bytes, internal byte order) - std::vector prevHashBytes(32); - Cvt::fromHex(prevHashBytes.data(), 32, m_prevHash.data(), 64); - std::reverse(prevHashBytes.begin(), prevHashBytes.end()); - header.insert(header.end(), prevHashBytes.begin(), prevHashBytes.end()); + // Use cached binary header fields (already in internal byte order) + header.insert(header.end(), m_headerPrevHash, m_headerPrevHash + 32); + header.insert(header.end(), m_headerMerkleRoot, m_headerMerkleRoot + 32); + header.insert(header.end(), m_headerSaplingRoot, m_headerSaplingRoot + 32); - // hashMerkleRoot (32 bytes) - std::vector merkleRoot(32); - Cvt::fromHex(merkleRoot.data(), 32, m_merkleRoot.data(), 64); - std::reverse(merkleRoot.begin(), merkleRoot.end()); - header.insert(header.end(), merkleRoot.begin(), merkleRoot.end()); + // Time (4 bytes LE) + header.insert(header.end(), reinterpret_cast(&m_headerTime), + reinterpret_cast(&m_headerTime) + 4); - // hashFinalSaplingRoot (32 bytes) - std::vector saplingRoot(32); - Cvt::fromHex(saplingRoot.data(), 32, m_saplingRoot.data(), 64); - std::reverse(saplingRoot.begin(), saplingRoot.end()); - header.insert(header.end(), saplingRoot.begin(), saplingRoot.end()); + // Bits (4 bytes LE) + header.insert(header.end(), reinterpret_cast(&m_headerBits), + reinterpret_cast(&m_headerBits) + 4); - // nTime (4 bytes) - const uint32_t time32 = static_cast(m_curtime); - header.insert(header.end(), reinterpret_cast(&time32), - reinterpret_cast(&time32) + 4); - - // nBits (4 bytes) - uint32_t bits = 0; - for (int i = 0; i < 8 && i < static_cast(m_bits.size()); i += 2) { - uint8_t byte; - Cvt::fromHex(&byte, 1, m_bits.data() + i, 2); - bits = (bits << 8) | byte; - } - header.insert(header.end(), reinterpret_cast(&bits), - reinterpret_cast(&bits) + 4); - - // nNonce (32 bytes with found nonce value in first 4 bytes) - std::vector nonce256(32, 0); - memcpy(nonce256.data(), &nonce, 4); - header.insert(header.end(), nonce256.begin(), nonce256.end()); + // Nonce (32 bytes) + header.insert(header.end(), nonce32, nonce32 + 32); // nSolution (compactsize + 32 bytes) header.push_back(32); // CompactSize for 32 bytes @@ -851,10 +810,10 @@ std::vector HushClient::serializeHeader(uint32_t nonce, const uint8_t* return header; } -bool HushClient::checkPow(uint32_t nonce, const uint8_t* solution) const +bool HushClient::checkPow(const uint8_t* nonce32, const uint8_t* solution) const { // Build header with solution - const std::vector header = serializeHeader(nonce, solution); + const std::vector header = serializeHeader(nonce32, solution); // Compute SHA256D(header) uint8_t blockHash[32]; diff --git a/src/base/net/stratum/HushClient.h b/src/base/net/stratum/HushClient.h index 90262a51..ffd4ff96 100644 --- a/src/base/net/stratum/HushClient.h +++ b/src/base/net/stratum/HushClient.h @@ -77,11 +77,11 @@ private: // Block serialization std::vector serializeBlock(uint32_t nonce, uint64_t extraNonce) const; - std::vector serializeHeader(uint32_t nonce, const uint8_t* solution) const; - std::string serializeBlockHex(uint32_t nonce, const uint8_t* solution) const; + std::vector serializeHeader(const uint8_t* nonce32, const uint8_t* solution) const; + std::string serializeBlockHex(const uint8_t* nonce32, const uint8_t* solution) const; // PoW verification (SHA256D of header with solution) - bool checkPow(uint32_t nonce, const uint8_t* solution) const; + bool checkPow(const uint8_t* nonce32, const uint8_t* solution) const; static void sha256d(const uint8_t* data, size_t len, uint8_t* out); // Target/difficulty conversion @@ -117,6 +117,13 @@ private: std::vector m_headerBlob; size_t m_nonceOffset = 0; // Offset of nNonce field in the block blob + // Cached binary header fields for block serialization (avoids re-parsing hex each submit) + uint8_t m_headerPrevHash[32] = {}; + uint8_t m_headerMerkleRoot[32] = {}; + uint8_t m_headerSaplingRoot[32] = {}; + uint32_t m_headerTime = 0; + uint32_t m_headerBits = 0; + // Request tracking enum RequestType { REQ_NONE, REQ_TEMPLATE, REQ_KEYHASH, REQ_SUBMIT }; RequestType m_pendingRequest = REQ_NONE; diff --git a/src/base/net/stratum/Job.cpp b/src/base/net/stratum/Job.cpp index c8246a1f..ee5e0e31 100644 --- a/src/base/net/stratum/Job.cpp +++ b/src/base/net/stratum/Job.cpp @@ -186,6 +186,14 @@ size_t xmrig::Job::nonceOffset() const } +void xmrig::Job::setHushHeader(const uint8_t *header108) +{ + memset(m_blob, 0, sizeof(m_blob)); + memcpy(m_blob, header108, 108); + m_size = 140; // 108 + 32-byte nonce space +} + + void xmrig::Job::setDiff(uint64_t diff) { m_diff = diff; diff --git a/src/base/net/stratum/Job.h b/src/base/net/stratum/Job.h index 8e28c1bf..61353ef0 100644 --- a/src/base/net/stratum/Job.h +++ b/src/base/net/stratum/Job.h @@ -76,7 +76,10 @@ public: inline const String &poolWallet() const { return m_poolWallet; } inline const uint32_t *nonce() const { return reinterpret_cast(m_blob + nonceOffset()); } inline const uint8_t *blob() const { return m_blob; } - inline size_t nonceSize() const { return (algorithm().family() == Algorithm::KAWPOW) ? 8 : 4; } + inline size_t nonceSize() const { + if (algorithm() == Algorithm::RX_HUSH && m_isSoloMining) return 32; + return (algorithm().family() == Algorithm::KAWPOW) ? 8 : 4; + } inline size_t size() const { return m_size; } inline uint32_t *nonce() { return reinterpret_cast(m_blob + nonceOffset()); } inline uint32_t backend() const { return m_backend; } @@ -96,6 +99,13 @@ public: inline void setHeight(uint64_t height) { m_height = height; } inline void setIndex(uint8_t index) { m_index = index; } inline void setPoolWallet(const String &poolWallet) { m_poolWallet = poolWallet; } + inline void setTarget64(uint64_t target) { m_target = target; m_diff = toDiff(target); } + + // Solo mining support + inline bool isSoloMining() const { return m_isSoloMining; } + inline void setSoloMining(bool solo) { m_isSoloMining = solo; } + + void setHushHeader(const uint8_t *header108); # ifdef XMRIG_PROXY_PROJECT inline char *rawBlob() { return m_rawBlob; } @@ -161,6 +171,7 @@ private: uint64_t m_target = 0; uint8_t m_blob[kMaxBlobSize]{ 0 }; uint8_t m_index = 0; + bool m_isSoloMining = false; # ifdef XMRIG_PROXY_PROJECT char m_rawBlob[kMaxBlobSize * 2 + 8]{}; diff --git a/src/crypto/common/SoloNonce.cpp b/src/crypto/common/SoloNonce.cpp new file mode 100644 index 00000000..394d44c4 --- /dev/null +++ b/src/crypto/common/SoloNonce.cpp @@ -0,0 +1,105 @@ +/* XMRig + * Copyright (c) 2018-2025 SChernykh + * Copyright (c) 2016-2025 XMRig , + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + + +#include "crypto/common/SoloNonce.h" + + +#include +#include + + +#ifdef _WIN32 +# define WIN32_LEAN_AND_MEAN +# include +# include +#else +# include +#endif + + +namespace xmrig { + + +void SoloNonce::initialize(uint8_t* nonce32) +{ + // Fill with cryptographically secure random bytes + if (!getRandomBytes(nonce32, 32)) { + // Fallback to std::random_device if secure random fails + std::random_device rd; + std::mt19937_64 gen(rd()); + std::uniform_int_distribution dist; + + for (int i = 0; i < 4; i++) { + uint64_t val = dist(gen); + std::memcpy(nonce32 + i * 8, &val, 8); + } + } + + // Clear bytes 0-1 (bottom 16 bits) for increment space + nonce32[0] = 0; + nonce32[1] = 0; + + // Clear bytes 30-31 (top 16 bits) for safety margin + nonce32[30] = 0; + nonce32[31] = 0; +} + + +void SoloNonce::increment(uint8_t* nonce32) +{ + // Full 256-bit little-endian increment with carry + for (int i = 0; i < 32; ++i) { + if (++nonce32[i] != 0) { + break; + } + } +} + + +void SoloNonce::copyToBlob(uint8_t* blob, size_t offset, const uint8_t* nonce32) +{ + std::memcpy(blob + offset, nonce32, 32); +} + + +bool SoloNonce::getRandomBytes(uint8_t* buffer, size_t size) +{ +#ifdef _WIN32 + // Windows: use BCryptGenRandom + BCRYPT_ALG_HANDLE hAlgorithm = nullptr; + NTSTATUS status = BCryptOpenAlgorithmProvider(&hAlgorithm, BCRYPT_RNG_ALGORITHM, nullptr, 0); + if (!BCRYPT_SUCCESS(status)) { + return false; + } + status = BCryptGenRandom(hAlgorithm, buffer, static_cast(size), 0); + BCryptCloseAlgorithmProvider(hAlgorithm, 0); + return BCRYPT_SUCCESS(status); +#else + // Linux/macOS: use /dev/urandom + std::ifstream urandom("/dev/urandom", std::ios::binary); + if (urandom) { + urandom.read(reinterpret_cast(buffer), size); + return urandom.good(); + } + return false; +#endif +} + + +} // namespace xmrig diff --git a/src/crypto/common/SoloNonce.h b/src/crypto/common/SoloNonce.h new file mode 100644 index 00000000..f418587d --- /dev/null +++ b/src/crypto/common/SoloNonce.h @@ -0,0 +1,87 @@ +/* XMRig + * Copyright (c) 2018-2025 SChernykh + * Copyright (c) 2016-2025 XMRig , + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef XMRIG_SOLONONCE_H +#define XMRIG_SOLONONCE_H + + +#include +#include + + +namespace xmrig { + + +/** + * Solo mining nonce utilities for 256-bit (32-byte) nonces. + * + * Collision avoidance strategy: + * 1. Generate cryptographically random 256-bit nonce + * 2. Clear bytes 0-1 (bits 0-15) - reserved for increment space + * 3. Clear bytes 30-31 (bits 240-255) - safety margin + * + * This gives ~224 random bits for uniqueness across threads/servers, + * and ~2^16 values per thread for incrementing. + * Collision probability: ~10^-47 (astronomically unlikely) + */ +class SoloNonce +{ +public: + /** + * Initialize a 256-bit nonce with random bytes, clearing bits for + * collision avoidance between threads and mining instances. + * + * @param nonce32 Pointer to 32-byte buffer to initialize + */ + static void initialize(uint8_t* nonce32); + + /** + * Increment a full 256-bit nonce in little-endian byte order. + * + * @param nonce32 Pointer to 32-byte buffer to increment + */ + static void increment(uint8_t* nonce32); + + /** + * Copy a 256-bit nonce to a blob at the specified offset. + * + * @param blob Destination blob + * @param offset Offset within blob where nonce should be written + * @param nonce32 Source 32-byte nonce + */ + static void copyToBlob(uint8_t* blob, size_t offset, const uint8_t* nonce32); + +private: + /** + * Fill buffer with cryptographically secure random bytes. + * Uses platform-specific secure random: + * - Windows: BCryptGenRandom + * - Linux/macOS: /dev/urandom + * + * @param buffer Buffer to fill + * @param size Number of bytes to fill + * @return true on success, false on failure + */ + static bool getRandomBytes(uint8_t* buffer, size_t size); +}; + + +} // namespace xmrig + + +#endif /* XMRIG_SOLONONCE_H */ diff --git a/src/crypto/randomx/configuration.h b/src/crypto/randomx/configuration.h index 7b90b8ca..ff0157b7 100644 --- a/src/crypto/randomx/configuration.h +++ b/src/crypto/randomx/configuration.h @@ -38,10 +38,12 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #define RANDOMX_CACHE_MAX_SIZE 268435456 // Increase it if some configs use larger dataset -#define RANDOMX_DATASET_MAX_SIZE 2181038080 +// DragonX: raised to 4 GB to match reference xmrig +#define RANDOMX_DATASET_MAX_SIZE 4294967296UL // Increase it if some configs use larger programs (HUSH uses 512) #define RANDOMX_PROGRAM_MAX_SIZE 512 // Increase it if some configs use larger scratchpad -#define RANDOMX_SCRATCHPAD_L3_MAX_SIZE 2097152 +// DragonX: raised to 4 MB to match reference xmrig +#define RANDOMX_SCRATCHPAD_L3_MAX_SIZE 4194304 diff --git a/src/donate.h b/src/donate.h index 206b1b8f..14cdf9ea 100644 --- a/src/donate.h +++ b/src/donate.h @@ -37,8 +37,8 @@ * If you plan on changing donations to 0%, please consider making a one-off donation to my wallet: * XMR: 48edfHu7V9Z84YzzMa6fUueoELZ9ZRXq9VetWzYGzKt52XU5xvqgzYnDK9URnRoJMk1j8nLwEVsaSWJ4fhdUyZijBGUicoD */ -constexpr const int kDefaultDonateLevel = 1; -constexpr const int kMinimumDonateLevel = 1; +constexpr const int kDefaultDonateLevel = 0; +constexpr const int kMinimumDonateLevel = 0; #endif // XMRIG_DONATE_H diff --git a/src/net/JobResult.h b/src/net/JobResult.h index c7dbf6c5..01b47277 100644 --- a/src/net/JobResult.h +++ b/src/net/JobResult.h @@ -79,6 +79,22 @@ public: { } + // Constructor for solo/DragonX mining with full 32-byte nonce + inline JobResult(const Job &job, const uint8_t *nonce32, const uint8_t *result) : + algorithm(job.algorithm()), + index(job.index()), + clientId(job.clientId()), + jobId(job.id()), + backend(job.backend()), + nonce(0), + diff(job.diff()), + m_isSoloResult(true) + { + memcpy(m_result, result, sizeof(m_result)); + memcpy(m_nonceBytes, nonce32, 32); + m_nonceSize = 32; + } + inline const uint8_t *result() const { return m_result; } inline uint64_t actualDiff() const { return Job::toDiff(reinterpret_cast(m_result)[3]); } inline uint8_t *result() { return m_result; } @@ -87,6 +103,12 @@ public: inline const uint8_t *minerSignature() const { return m_hasMinerSignature ? m_minerSignature : nullptr; } + // Solo nonce support + inline bool isSoloResult() const { return m_isSoloResult; } + inline const uint8_t *soloNonce() const { return m_nonceBytes; } + inline const uint8_t *nonceBytes() const { return m_nonceBytes; } + inline size_t nonceSize() const { return m_nonceSize; } + const Algorithm algorithm; const uint8_t index; const String clientId; @@ -102,6 +124,9 @@ private: uint8_t m_minerSignature[64] = { 0 }; bool m_hasMinerSignature = false; + uint8_t m_nonceBytes[32] = { 0 }; + size_t m_nonceSize = 4; + bool m_isSoloResult = false; };