TestBlockValidity() triggers CheckRandomXSolution() which allocates ~256MB of cache to recompute hash which is redundant since local mining thread would be checking it's own work against itself.
Rather than miner allocating its own dataset, a single shared dataset is allocated and shared accross all threads. This is explicitly supported by RandomX.
This commit is contained in:
271
src/miner.cpp
271
src/miner.cpp
@@ -51,6 +51,7 @@
|
|||||||
#include "transaction_builder.h"
|
#include "transaction_builder.h"
|
||||||
#include "sodium.h"
|
#include "sodium.h"
|
||||||
#include <boost/thread.hpp>
|
#include <boost/thread.hpp>
|
||||||
|
#include <boost/thread/shared_mutex.hpp>
|
||||||
#include <boost/tuple/tuple.hpp>
|
#include <boost/tuple/tuple.hpp>
|
||||||
#ifdef ENABLE_MINING
|
#ifdef ENABLE_MINING
|
||||||
#include <functional>
|
#include <functional>
|
||||||
@@ -1014,6 +1015,126 @@ enum RandomXSolverCancelCheck
|
|||||||
int GetRandomXInterval();
|
int GetRandomXInterval();
|
||||||
int GetRandomXBlockLag();
|
int GetRandomXBlockLag();
|
||||||
|
|
||||||
|
// Shared RandomX dataset manager — all miner threads share a single ~2GB dataset
|
||||||
|
// instead of each allocating their own. The dataset is read-only after initialization
|
||||||
|
// and RandomX explicitly supports multiple VMs sharing one dataset.
|
||||||
|
struct RandomXDatasetManager {
|
||||||
|
randomx_flags flags;
|
||||||
|
randomx_cache *cache;
|
||||||
|
randomx_dataset *dataset;
|
||||||
|
unsigned long datasetItemCount;
|
||||||
|
std::string currentKey;
|
||||||
|
std::mutex mtx; // protects Init/Shutdown/CreateVM
|
||||||
|
boost::shared_mutex datasetMtx; // readers-writer lock: shared for hashing, exclusive for rebuild
|
||||||
|
bool initialized;
|
||||||
|
|
||||||
|
RandomXDatasetManager() : flags(randomx_get_flags()), cache(nullptr), dataset(nullptr),
|
||||||
|
datasetItemCount(0), initialized(false) {}
|
||||||
|
|
||||||
|
bool Init() {
|
||||||
|
std::lock_guard<std::mutex> lock(mtx);
|
||||||
|
if (initialized) return true;
|
||||||
|
|
||||||
|
flags |= RANDOMX_FLAG_FULL_MEM;
|
||||||
|
|
||||||
|
cache = randomx_alloc_cache(flags | RANDOMX_FLAG_LARGE_PAGES | RANDOMX_FLAG_SECURE);
|
||||||
|
if (cache == nullptr) {
|
||||||
|
LogPrintf("RandomXDatasetManager: cache alloc failed with large pages, trying without...\n");
|
||||||
|
cache = randomx_alloc_cache(flags | RANDOMX_FLAG_SECURE);
|
||||||
|
if (cache == nullptr) {
|
||||||
|
LogPrintf("RandomXDatasetManager: cache alloc failed with secure, trying basic...\n");
|
||||||
|
}
|
||||||
|
cache = randomx_alloc_cache(flags);
|
||||||
|
if (cache == nullptr) {
|
||||||
|
LogPrintf("RandomXDatasetManager: cannot allocate cache!\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dataset = randomx_alloc_dataset(flags);
|
||||||
|
if (dataset == nullptr) {
|
||||||
|
LogPrintf("RandomXDatasetManager: cannot allocate dataset!\n");
|
||||||
|
randomx_release_cache(cache);
|
||||||
|
cache = nullptr;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
datasetItemCount = randomx_dataset_item_count();
|
||||||
|
initialized = true;
|
||||||
|
LogPrintf("RandomXDatasetManager: allocated shared cache + dataset (%lu items)\n", datasetItemCount);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize cache with a key and rebuild the dataset.
|
||||||
|
// Thread-safe: acquires exclusive lock so all hashing threads must finish first.
|
||||||
|
void UpdateKey(const void *key, size_t keySize) {
|
||||||
|
std::string newKey((const char*)key, keySize);
|
||||||
|
|
||||||
|
// Fast check with shared lock — skip if key hasn't changed
|
||||||
|
{
|
||||||
|
boost::shared_lock<boost::shared_mutex> readLock(datasetMtx);
|
||||||
|
if (newKey == currentKey) return; // already up to date
|
||||||
|
}
|
||||||
|
|
||||||
|
// Acquire exclusive lock — blocks until all hashing threads release their shared locks
|
||||||
|
boost::unique_lock<boost::shared_mutex> writeLock(datasetMtx);
|
||||||
|
// Double-check after acquiring exclusive lock (another thread may have rebuilt first)
|
||||||
|
if (newKey == currentKey) return;
|
||||||
|
|
||||||
|
LogPrintf("RandomXDatasetManager: updating key (size=%lu)\n", keySize);
|
||||||
|
randomx_init_cache(cache, key, keySize);
|
||||||
|
currentKey = newKey;
|
||||||
|
|
||||||
|
// Rebuild dataset using all available CPU threads
|
||||||
|
const int initThreadCount = std::thread::hardware_concurrency();
|
||||||
|
if (initThreadCount > 1) {
|
||||||
|
std::vector<std::thread> threads;
|
||||||
|
uint32_t startItem = 0;
|
||||||
|
const auto perThread = datasetItemCount / initThreadCount;
|
||||||
|
const auto remainder = datasetItemCount % initThreadCount;
|
||||||
|
for (int i = 0; i < initThreadCount; ++i) {
|
||||||
|
const auto count = perThread + (i == initThreadCount - 1 ? remainder : 0);
|
||||||
|
threads.push_back(std::thread(&randomx_init_dataset, dataset, cache, startItem, count));
|
||||||
|
startItem += count;
|
||||||
|
}
|
||||||
|
for (unsigned i = 0; i < threads.size(); ++i) {
|
||||||
|
threads[i].join();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
randomx_init_dataset(dataset, cache, 0, datasetItemCount);
|
||||||
|
}
|
||||||
|
LogPrintf("RandomXDatasetManager: dataset rebuilt\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates a per-thread VM using the shared dataset.
|
||||||
|
// Caller must hold a shared lock on datasetMtx.
|
||||||
|
randomx_vm *CreateVM() {
|
||||||
|
return randomx_create_vm(flags, nullptr, dataset);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Shutdown() {
|
||||||
|
std::lock_guard<std::mutex> lock(mtx);
|
||||||
|
if (dataset != nullptr) {
|
||||||
|
randomx_release_dataset(dataset);
|
||||||
|
dataset = nullptr;
|
||||||
|
}
|
||||||
|
if (cache != nullptr) {
|
||||||
|
randomx_release_cache(cache);
|
||||||
|
cache = nullptr;
|
||||||
|
}
|
||||||
|
initialized = false;
|
||||||
|
currentKey.clear();
|
||||||
|
LogPrintf("RandomXDatasetManager: shutdown complete\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
~RandomXDatasetManager() {
|
||||||
|
Shutdown();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Global shared dataset manager, created by GenerateBitcoins before spawning miner threads
|
||||||
|
static RandomXDatasetManager *g_rxDatasetManager = nullptr;
|
||||||
|
|
||||||
#ifdef ENABLE_WALLET
|
#ifdef ENABLE_WALLET
|
||||||
void static RandomXMiner(CWallet *pwallet)
|
void static RandomXMiner(CWallet *pwallet)
|
||||||
#else
|
#else
|
||||||
@@ -1050,33 +1171,12 @@ void static RandomXMiner()
|
|||||||
);
|
);
|
||||||
miningTimer.start();
|
miningTimer.start();
|
||||||
|
|
||||||
randomx_flags flags = randomx_get_flags();
|
// Use the shared dataset manager — no per-thread dataset allocation
|
||||||
flags |= RANDOMX_FLAG_FULL_MEM;
|
if (g_rxDatasetManager == nullptr || !g_rxDatasetManager->initialized) {
|
||||||
randomx_cache *randomxCache = randomx_alloc_cache(flags | RANDOMX_FLAG_LARGE_PAGES | RANDOMX_FLAG_SECURE );
|
LogPrintf("HushRandomXMiner: shared dataset manager not initialized, aborting!\n");
|
||||||
if (randomxCache == NULL) {
|
|
||||||
LogPrintf("RandomX cache is null, trying without large pages...\n");
|
|
||||||
randomxCache = randomx_alloc_cache(flags | RANDOMX_FLAG_SECURE);
|
|
||||||
if (randomxCache == NULL) {
|
|
||||||
LogPrintf("RandomX cache is null, trying without secure...\n");
|
|
||||||
}
|
|
||||||
randomxCache = randomx_alloc_cache(flags);
|
|
||||||
if (randomxCache == NULL) {
|
|
||||||
LogPrintf("RandomX cache is null, cannot mine!\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
rxdebug("%s: created randomx flags + cache\n");
|
|
||||||
randomx_dataset *randomxDataset = randomx_alloc_dataset(flags);
|
|
||||||
rxdebug("%s: created dataset\n");
|
|
||||||
|
|
||||||
if( randomxDataset == nullptr) {
|
|
||||||
LogPrintf("%s: allocating randomx dataset failed!\n", __func__);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto datasetItemCount = randomx_dataset_item_count();
|
|
||||||
rxdebug("%s: dataset items=%lu\n", datasetItemCount);
|
|
||||||
|
|
||||||
char randomxHash[RANDOMX_HASH_SIZE];
|
char randomxHash[RANDOMX_HASH_SIZE];
|
||||||
rxdebug("%s: created randomxHash of size %d\n", RANDOMX_HASH_SIZE);
|
rxdebug("%s: created randomxHash of size %d\n", RANDOMX_HASH_SIZE);
|
||||||
char randomxKey[82]; // randomx spec says keysize of >60 bytes is implementation-specific
|
char randomxKey[82]; // randomx spec says keysize of >60 bytes is implementation-specific
|
||||||
@@ -1147,44 +1247,23 @@ void static RandomXMiner()
|
|||||||
|
|
||||||
// fprintf(stderr,"RandomXMiner: using initial key with interval=%d and lag=%d\n", randomxInterval, randomxBlockLag);
|
// fprintf(stderr,"RandomXMiner: using initial key with interval=%d and lag=%d\n", randomxInterval, randomxBlockLag);
|
||||||
rxdebug("%s: using initial key, interval=%d, lag=%d, Mining_height=%u\n", randomxInterval, randomxBlockLag, Mining_height);
|
rxdebug("%s: using initial key, interval=%d, lag=%d, Mining_height=%u\n", randomxInterval, randomxBlockLag, Mining_height);
|
||||||
// Use the initial key at the start of the chain, until the first key block
|
// Update the shared dataset key — only one thread will actually rebuild,
|
||||||
|
// others will see the key is already current and skip.
|
||||||
if( (Mining_height) < randomxInterval + randomxBlockLag) {
|
if( (Mining_height) < randomxInterval + randomxBlockLag) {
|
||||||
randomx_init_cache(randomxCache, randomxKey, strlen(randomxKey));
|
g_rxDatasetManager->UpdateKey(randomxKey, strlen(randomxKey));
|
||||||
rxdebug("%s: initialized cache with initial key\n");
|
rxdebug("%s: updated shared dataset with initial key\n");
|
||||||
} else {
|
} else {
|
||||||
rxdebug("%s: calculating keyHeight with randomxInterval=%d\n", randomxInterval);
|
rxdebug("%s: calculating keyHeight with randomxInterval=%d\n", randomxInterval);
|
||||||
// At heights between intervals, we use the same block key and wait randomxBlockLag blocks until changing
|
|
||||||
const int keyHeight = ((Mining_height - randomxBlockLag) / randomxInterval) * randomxInterval;
|
const int keyHeight = ((Mining_height - randomxBlockLag) / randomxInterval) * randomxInterval;
|
||||||
uint256 randomxBlockKey = chainActive[keyHeight]->GetBlockHash();
|
uint256 randomxBlockKey = chainActive[keyHeight]->GetBlockHash();
|
||||||
|
g_rxDatasetManager->UpdateKey(&randomxBlockKey, sizeof randomxBlockKey);
|
||||||
randomx_init_cache(randomxCache, &randomxBlockKey, sizeof randomxBlockKey);
|
rxdebug("%s: updated shared dataset with keyHeight=%d, randomxBlockKey=%s\n", keyHeight, randomxBlockKey.ToString().c_str());
|
||||||
rxdebug("%s: initialized cache with keyHeight=%d, randomxBlockKey=%s\n", keyHeight, randomxBlockKey.ToString().c_str());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const int initThreadCount = std::thread::hardware_concurrency();
|
// Create a per-thread VM that uses the shared dataset (read-only, thread-safe)
|
||||||
if(initThreadCount > 1) {
|
// Acquire shared lock to prevent dataset rebuild while we're hashing
|
||||||
rxdebug("%s: initializing dataset with %d threads\n", initThreadCount);
|
boost::shared_lock<boost::shared_mutex> datasetLock(g_rxDatasetManager->datasetMtx);
|
||||||
std::vector<std::thread> threads;
|
myVM = g_rxDatasetManager->CreateVM();
|
||||||
uint32_t startItem = 0;
|
|
||||||
const auto perThread = datasetItemCount / initThreadCount;
|
|
||||||
const auto remainder = datasetItemCount % initThreadCount;
|
|
||||||
for (int i = 0; i < initThreadCount; ++i) {
|
|
||||||
const auto count = perThread + (i == initThreadCount - 1 ? remainder : 0);
|
|
||||||
threads.push_back(std::thread(&randomx_init_dataset, randomxDataset, randomxCache, startItem, count));
|
|
||||||
startItem += count;
|
|
||||||
}
|
|
||||||
for (unsigned i = 0; i < threads.size(); ++i) {
|
|
||||||
threads[i].join();
|
|
||||||
}
|
|
||||||
threads.clear();
|
|
||||||
} else {
|
|
||||||
rxdebug("%s: initializing dataset with 1 thread\n");
|
|
||||||
randomx_init_dataset(randomxDataset, randomxCache, 0, datasetItemCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
rxdebug("%s: dataset initialized\n");
|
|
||||||
|
|
||||||
myVM = randomx_create_vm(flags, nullptr, randomxDataset);
|
|
||||||
if(myVM == NULL) {
|
if(myVM == NULL) {
|
||||||
LogPrintf("RandomXMiner: Cannot create RandomX VM, aborting!\n");
|
LogPrintf("RandomXMiner: Cannot create RandomX VM, aborting!\n");
|
||||||
return;
|
return;
|
||||||
@@ -1325,14 +1404,28 @@ void static RandomXMiner()
|
|||||||
|
|
||||||
CValidationState state;
|
CValidationState state;
|
||||||
//{ LOCK(cs_main);
|
//{ LOCK(cs_main);
|
||||||
if ( !TestBlockValidity(state,B, chainActive.LastTip(), true, false))
|
// Skip RandomX re-validation during TestBlockValidity — we already
|
||||||
|
// computed the correct hash, and re-verifying allocates ~256MB which
|
||||||
|
// can trigger the OOM killer on memory-constrained systems.
|
||||||
|
SetSkipRandomXValidation(true);
|
||||||
|
bool fValid = TestBlockValidity(state,B, chainActive.LastTip(), true, false);
|
||||||
|
SetSkipRandomXValidation(false);
|
||||||
|
if ( !fValid )
|
||||||
{
|
{
|
||||||
h = UintToArith256(B.GetHash());
|
h = UintToArith256(B.GetHash());
|
||||||
fprintf(stderr,"RandomXMiner: Invalid randomx block mined, try again ");
|
fprintf(stderr,"RandomXMiner: TestBlockValidity FAILED at ht.%d nNonce=%s hash=",
|
||||||
|
Mining_height, pblock->nNonce.ToString().c_str());
|
||||||
for (z=31; z>=0; z--)
|
for (z=31; z>=0; z--)
|
||||||
fprintf(stderr,"%02x",((uint8_t *)&h)[z]);
|
fprintf(stderr,"%02x",((uint8_t *)&h)[z]);
|
||||||
gotinvalid = 1;
|
fprintf(stderr," nSolution.size=%lu\n", B.nSolution.size());
|
||||||
|
// Dump nSolution hex for comparison with validator
|
||||||
|
fprintf(stderr,"RandomXMiner: nSolution=");
|
||||||
|
for (unsigned i = 0; i < B.nSolution.size(); i++)
|
||||||
|
fprintf(stderr,"%02x", B.nSolution[i]);
|
||||||
fprintf(stderr,"\n");
|
fprintf(stderr,"\n");
|
||||||
|
LogPrintf("RandomXMiner: TestBlockValidity FAILED at ht.%d, gotinvalid=1, state=%s\n",
|
||||||
|
Mining_height, state.GetRejectReason());
|
||||||
|
gotinvalid = 1;
|
||||||
return(false);
|
return(false);
|
||||||
}
|
}
|
||||||
//}
|
//}
|
||||||
@@ -1401,20 +1494,32 @@ void static RandomXMiner()
|
|||||||
}
|
}
|
||||||
|
|
||||||
rxdebug("%s: going to destroy rx VM\n");
|
rxdebug("%s: going to destroy rx VM\n");
|
||||||
|
if (myVM != nullptr) {
|
||||||
randomx_destroy_vm(myVM);
|
randomx_destroy_vm(myVM);
|
||||||
rxdebug("%s: destroyed VM\n");
|
myVM = nullptr;
|
||||||
|
LogPrintf("RandomXMiner: destroyed VM after inner loop\n");
|
||||||
|
fprintf(stderr, "RandomXMiner: destroyed VM after inner loop\n");
|
||||||
|
} else {
|
||||||
|
LogPrintf("RandomXMiner: WARNING myVM already null after inner loop, skipping destroy (would double-free)\n");
|
||||||
|
fprintf(stderr, "RandomXMiner: WARNING myVM already null after inner loop, skipping destroy (would double-free)\n");
|
||||||
|
}
|
||||||
|
// Release shared lock so UpdateKey can acquire exclusive lock for dataset rebuild
|
||||||
|
datasetLock.unlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (const boost::thread_interrupted&) {
|
} catch (const boost::thread_interrupted&) {
|
||||||
miningTimer.stop();
|
miningTimer.stop();
|
||||||
c.disconnect();
|
c.disconnect();
|
||||||
|
|
||||||
|
if (myVM != nullptr) {
|
||||||
randomx_destroy_vm(myVM);
|
randomx_destroy_vm(myVM);
|
||||||
|
myVM = nullptr;
|
||||||
LogPrintf("%s: destroyed vm via thread interrupt\n", __func__);
|
LogPrintf("%s: destroyed vm via thread interrupt\n", __func__);
|
||||||
randomx_release_dataset(randomxDataset);
|
} else {
|
||||||
rxdebug("%s: released dataset via thread interrupt\n");
|
LogPrintf("%s: WARNING myVM already null in thread interrupt handler, skipping destroy (would double-free)\n", __func__);
|
||||||
randomx_release_cache(randomxCache);
|
fprintf(stderr, "%s: WARNING myVM already null in thread interrupt, would have double-freed!\n", __func__);
|
||||||
rxdebug("%s: released cache via thread interrupt\n");
|
}
|
||||||
|
// Dataset and cache are owned by g_rxDatasetManager — do NOT release here
|
||||||
|
|
||||||
LogPrintf("HushRandomXMiner terminated\n");
|
LogPrintf("HushRandomXMiner terminated\n");
|
||||||
throw;
|
throw;
|
||||||
@@ -1423,20 +1528,21 @@ void static RandomXMiner()
|
|||||||
c.disconnect();
|
c.disconnect();
|
||||||
fprintf(stderr,"RandomXMiner: runtime error: %s\n", e.what());
|
fprintf(stderr,"RandomXMiner: runtime error: %s\n", e.what());
|
||||||
|
|
||||||
|
if (myVM != nullptr) {
|
||||||
randomx_destroy_vm(myVM);
|
randomx_destroy_vm(myVM);
|
||||||
|
myVM = nullptr;
|
||||||
LogPrintf("%s: destroyed vm because of error\n", __func__);
|
LogPrintf("%s: destroyed vm because of error\n", __func__);
|
||||||
randomx_release_dataset(randomxDataset);
|
}
|
||||||
rxdebug("%s: released dataset because of error\n");
|
// Dataset and cache are owned by g_rxDatasetManager — do NOT release here
|
||||||
randomx_release_cache(randomxCache);
|
|
||||||
rxdebug("%s: released cache because of error\n");
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
randomx_release_dataset(randomxDataset);
|
// Only destroy per-thread VM, dataset/cache are shared
|
||||||
rxdebug("%s: released dataset in normal exit\n");
|
if (myVM != nullptr) {
|
||||||
randomx_release_cache(randomxCache);
|
randomx_destroy_vm(myVM);
|
||||||
rxdebug("%s: released cache in normal exit\n");
|
myVM = nullptr;
|
||||||
|
}
|
||||||
miningTimer.stop();
|
miningTimer.stop();
|
||||||
c.disconnect();
|
c.disconnect();
|
||||||
}
|
}
|
||||||
@@ -1882,6 +1988,14 @@ void static BitcoinMiner()
|
|||||||
minerThreads->interrupt_all();
|
minerThreads->interrupt_all();
|
||||||
delete minerThreads;
|
delete minerThreads;
|
||||||
minerThreads = NULL;
|
minerThreads = NULL;
|
||||||
|
|
||||||
|
// Shutdown shared RandomX dataset manager after all threads are done
|
||||||
|
if (g_rxDatasetManager != nullptr) {
|
||||||
|
g_rxDatasetManager->Shutdown();
|
||||||
|
delete g_rxDatasetManager;
|
||||||
|
g_rxDatasetManager = nullptr;
|
||||||
|
LogPrintf("%s: destroyed shared RandomX dataset manager\n", __func__);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(fDebug)
|
if(fDebug)
|
||||||
@@ -1896,6 +2010,21 @@ void static BitcoinMiner()
|
|||||||
|
|
||||||
minerThreads = new boost::thread_group();
|
minerThreads = new boost::thread_group();
|
||||||
|
|
||||||
|
// Initialize shared RandomX dataset manager before spawning miner threads
|
||||||
|
if (ASSETCHAINS_ALGO == ASSETCHAINS_RANDOMX) {
|
||||||
|
g_rxDatasetManager = new RandomXDatasetManager();
|
||||||
|
if (!g_rxDatasetManager->Init()) {
|
||||||
|
LogPrintf("%s: FATAL - Failed to initialize shared RandomX dataset manager\n", __func__);
|
||||||
|
fprintf(stderr, "%s: FATAL - Failed to initialize shared RandomX dataset manager\n", __func__);
|
||||||
|
delete g_rxDatasetManager;
|
||||||
|
g_rxDatasetManager = nullptr;
|
||||||
|
delete minerThreads;
|
||||||
|
minerThreads = NULL;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
LogPrintf("%s: shared RandomX dataset manager initialized\n", __func__);
|
||||||
|
}
|
||||||
|
|
||||||
for (int i = 0; i < nThreads; i++) {
|
for (int i = 0; i < nThreads; i++) {
|
||||||
#ifdef ENABLE_WALLET
|
#ifdef ENABLE_WALLET
|
||||||
if ( ASSETCHAINS_ALGO == ASSETCHAINS_EQUIHASH ) {
|
if ( ASSETCHAINS_ALGO == ASSETCHAINS_EQUIHASH ) {
|
||||||
|
|||||||
36
src/pow.cpp
36
src/pow.cpp
@@ -694,6 +694,13 @@ static randomx_cache *s_rxCache = nullptr;
|
|||||||
static randomx_vm *s_rxVM = nullptr;
|
static randomx_vm *s_rxVM = nullptr;
|
||||||
static std::string s_rxCurrentKey; // tracks current key to avoid re-init
|
static std::string s_rxCurrentKey; // tracks current key to avoid re-init
|
||||||
|
|
||||||
|
// Thread-local flag: skip CheckRandomXSolution when the miner is validating its own block
|
||||||
|
// The miner already computed the correct RandomX hash — re-verifying with a separate
|
||||||
|
// cache+VM would allocate ~256MB extra memory and can trigger the OOM killer.
|
||||||
|
thread_local bool fSkipRandomXValidation = false;
|
||||||
|
|
||||||
|
void SetSkipRandomXValidation(bool skip) { fSkipRandomXValidation = skip; }
|
||||||
|
|
||||||
CBlockIndex *hush_chainactive(int32_t height);
|
CBlockIndex *hush_chainactive(int32_t height);
|
||||||
|
|
||||||
bool CheckRandomXSolution(const CBlockHeader *pblock, int32_t height)
|
bool CheckRandomXSolution(const CBlockHeader *pblock, int32_t height)
|
||||||
@@ -715,6 +722,10 @@ bool CheckRandomXSolution(const CBlockHeader *pblock, int32_t height)
|
|||||||
if (HUSH_LOADINGBLOCKS != 0)
|
if (HUSH_LOADINGBLOCKS != 0)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
// Skip when miner is validating its own block via TestBlockValidity
|
||||||
|
if (fSkipRandomXValidation)
|
||||||
|
return true;
|
||||||
|
|
||||||
// nSolution must be exactly RANDOMX_HASH_SIZE (32) bytes
|
// nSolution must be exactly RANDOMX_HASH_SIZE (32) bytes
|
||||||
if (pblock->nSolution.size() != RANDOMX_HASH_SIZE) {
|
if (pblock->nSolution.size() != RANDOMX_HASH_SIZE) {
|
||||||
return error("CheckRandomXSolution(): nSolution size %u != expected %d at height %d",
|
return error("CheckRandomXSolution(): nSolution size %u != expected %d at height %d",
|
||||||
@@ -779,9 +790,32 @@ bool CheckRandomXSolution(const CBlockHeader *pblock, int32_t height)
|
|||||||
|
|
||||||
// Compare computed hash against nSolution
|
// Compare computed hash against nSolution
|
||||||
if (memcmp(computedHash, pblock->nSolution.data(), RANDOMX_HASH_SIZE) != 0) {
|
if (memcmp(computedHash, pblock->nSolution.data(), RANDOMX_HASH_SIZE) != 0) {
|
||||||
return error("CheckRandomXSolution(): RandomX hash mismatch at height %d", height);
|
// Debug: dump both hashes for diagnosis
|
||||||
|
std::string computedHex, solutionHex;
|
||||||
|
for (int i = 0; i < RANDOMX_HASH_SIZE; i++) {
|
||||||
|
char buf[4];
|
||||||
|
snprintf(buf, sizeof(buf), "%02x", (uint8_t)computedHash[i]);
|
||||||
|
computedHex += buf;
|
||||||
|
snprintf(buf, sizeof(buf), "%02x", pblock->nSolution[i]);
|
||||||
|
solutionHex += buf;
|
||||||
|
}
|
||||||
|
fprintf(stderr, "CheckRandomXSolution(): HASH MISMATCH at height %d\n", height);
|
||||||
|
fprintf(stderr, " computed : %s\n", computedHex.c_str());
|
||||||
|
fprintf(stderr, " nSolution: %s\n", solutionHex.c_str());
|
||||||
|
fprintf(stderr, " rxKey size=%lu, input size=%lu, nNonce=%s\n",
|
||||||
|
rxKey.size(), ss.size(), pblock->nNonce.ToString().c_str());
|
||||||
|
fprintf(stderr, " nSolution.size()=%lu, RANDOMX_HASH_SIZE=%d\n",
|
||||||
|
pblock->nSolution.size(), RANDOMX_HASH_SIZE);
|
||||||
|
// Also log to debug.log
|
||||||
|
LogPrintf("CheckRandomXSolution(): HASH MISMATCH at height %d\n", height);
|
||||||
|
LogPrintf(" computed : %s\n", computedHex);
|
||||||
|
LogPrintf(" nSolution: %s\n", solutionHex);
|
||||||
|
LogPrintf(" rxKey size=%lu, input size=%lu, nNonce=%s\n",
|
||||||
|
rxKey.size(), ss.size(), pblock->nNonce.ToString());
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LogPrint("randomx", "CheckRandomXSolution(): valid at height %d\n", height);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -41,6 +41,9 @@ bool CheckEquihashSolution(const CBlockHeader *pblock, const CChainParams&);
|
|||||||
/** Check whether a block header contains a valid RandomX solution */
|
/** Check whether a block header contains a valid RandomX solution */
|
||||||
bool CheckRandomXSolution(const CBlockHeader *pblock, int32_t height);
|
bool CheckRandomXSolution(const CBlockHeader *pblock, int32_t height);
|
||||||
|
|
||||||
|
/** Set thread-local flag to skip RandomX validation (used by miner during TestBlockValidity) */
|
||||||
|
void SetSkipRandomXValidation(bool skip);
|
||||||
|
|
||||||
/** Return the RandomX key rotation interval in blocks */
|
/** Return the RandomX key rotation interval in blocks */
|
||||||
int GetRandomXInterval();
|
int GetRandomXInterval();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user