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
This commit is contained in:
@@ -23,6 +23,7 @@
|
||||
#include "net/JobResult.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cinttypes>
|
||||
#include <cstring>
|
||||
|
||||
#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<uint32_t>(m_version);
|
||||
m_headerBlob.insert(m_headerBlob.end(), reinterpret_cast<const uint8_t*>(&ver),
|
||||
reinterpret_cast<const uint8_t*>(&ver) + 4);
|
||||
memcpy(header + offset, &ver, 4);
|
||||
offset += 4;
|
||||
|
||||
// hashPrevBlock (32 bytes, internal byte order)
|
||||
std::vector<uint8_t> 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<uint8_t> 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<uint8_t> 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<uint32_t>(m_curtime);
|
||||
m_headerBlob.insert(m_headerBlob.end(), reinterpret_cast<const uint8_t*>(&time32),
|
||||
reinterpret_cast<const uint8_t*>(&time32) + 4);
|
||||
|
||||
// nBits (4 bytes)
|
||||
uint32_t bits = 0;
|
||||
for (int i = 0; i < 8 && i < static_cast<int>(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<const uint8_t*>(&bits),
|
||||
reinterpret_cast<const uint8_t*>(&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<unsigned char>
|
||||
// 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<uint8_t>(txCount));
|
||||
} else if (txCount <= 0xFFFF) {
|
||||
m_headerBlob.push_back(0xFD);
|
||||
const uint16_t cnt16 = static_cast<uint16_t>(txCount);
|
||||
m_headerBlob.insert(m_headerBlob.end(), reinterpret_cast<const uint8_t*>(&cnt16),
|
||||
reinterpret_cast<const uint8_t*>(&cnt16) + 2);
|
||||
} else {
|
||||
m_headerBlob.push_back(0xFE);
|
||||
const uint32_t cnt32 = static_cast<uint32_t>(txCount);
|
||||
m_headerBlob.insert(m_headerBlob.end(), reinterpret_cast<const uint8_t*>(&cnt32),
|
||||
reinterpret_cast<const uint8_t*>(&cnt32) + 4);
|
||||
}
|
||||
|
||||
// Append coinbase transaction
|
||||
std::vector<uint8_t> 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<uint8_t> 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<uint8_t> 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<uint8_t> 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<uint8_t> 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<uint32_t>(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<uint32_t>(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<uint8_t> 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<uint8_t> 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<uint32_t>(m_version);
|
||||
blob.insert(blob.end(), reinterpret_cast<const uint8_t*>(&ver),
|
||||
reinterpret_cast<const uint8_t*>(&ver) + 4);
|
||||
|
||||
std::vector<uint8_t> 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<uint8_t> 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<uint8_t> 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<uint32_t>(m_curtime);
|
||||
blob.insert(blob.end(), reinterpret_cast<const uint8_t*>(&time32),
|
||||
reinterpret_cast<const uint8_t*>(&time32) + 4);
|
||||
// Time (4 bytes LE)
|
||||
blob.insert(blob.end(), reinterpret_cast<const uint8_t*>(&m_headerTime),
|
||||
reinterpret_cast<const uint8_t*>(&m_headerTime) + 4);
|
||||
|
||||
uint32_t bits = 0;
|
||||
for (int i = 0; i < 8 && i < static_cast<int>(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<const uint8_t*>(&bits),
|
||||
reinterpret_cast<const uint8_t*>(&bits) + 4);
|
||||
// Bits (4 bytes LE)
|
||||
blob.insert(blob.end(), reinterpret_cast<const uint8_t*>(&m_headerBits),
|
||||
reinterpret_cast<const uint8_t*>(&m_headerBits) + 4);
|
||||
|
||||
// Nonce (32 bytes with found nonce value)
|
||||
std::vector<uint8_t> 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<uint8_t> HushClient::serializeHeader(uint32_t nonce, const uint8_t* solution) const
|
||||
std::vector<uint8_t> HushClient::serializeHeader(const uint8_t* nonce32, const uint8_t* solution) const
|
||||
{
|
||||
std::vector<uint8_t> header;
|
||||
header.reserve(140 + 1 + 32); // header + compactsize + solution
|
||||
|
||||
// nVersion (4 bytes, little-endian)
|
||||
// Version (4 bytes LE)
|
||||
const uint32_t ver = static_cast<uint32_t>(m_version);
|
||||
header.insert(header.end(), reinterpret_cast<const uint8_t*>(&ver),
|
||||
reinterpret_cast<const uint8_t*>(&ver) + 4);
|
||||
|
||||
// hashPrevBlock (32 bytes, internal byte order)
|
||||
std::vector<uint8_t> 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<uint8_t> 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<const uint8_t*>(&m_headerTime),
|
||||
reinterpret_cast<const uint8_t*>(&m_headerTime) + 4);
|
||||
|
||||
// hashFinalSaplingRoot (32 bytes)
|
||||
std::vector<uint8_t> 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<const uint8_t*>(&m_headerBits),
|
||||
reinterpret_cast<const uint8_t*>(&m_headerBits) + 4);
|
||||
|
||||
// nTime (4 bytes)
|
||||
const uint32_t time32 = static_cast<uint32_t>(m_curtime);
|
||||
header.insert(header.end(), reinterpret_cast<const uint8_t*>(&time32),
|
||||
reinterpret_cast<const uint8_t*>(&time32) + 4);
|
||||
|
||||
// nBits (4 bytes)
|
||||
uint32_t bits = 0;
|
||||
for (int i = 0; i < 8 && i < static_cast<int>(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<const uint8_t*>(&bits),
|
||||
reinterpret_cast<const uint8_t*>(&bits) + 4);
|
||||
|
||||
// nNonce (32 bytes with found nonce value in first 4 bytes)
|
||||
std::vector<uint8_t> 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<uint8_t> 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<uint8_t> header = serializeHeader(nonce, solution);
|
||||
const std::vector<uint8_t> header = serializeHeader(nonce32, solution);
|
||||
|
||||
// Compute SHA256D(header)
|
||||
uint8_t blockHash[32];
|
||||
|
||||
Reference in New Issue
Block a user