IBD/sync speedups: parallel RandomX pre-verify, adaptive dbcache, P2P download fixes
- Parallel RandomX PoW pre-verification pool (CCheckQueue) run ahead of the serial connect; consensus-neutral (inline CheckRandomXSolution fallback still verifies anything not pre-verified). New -randomxverifythreads (default = -par). - Adaptive dbcache: default sizes the UTXO/coins cache to most of RAM and shrinks under memory pressure, always leaving a reserve free; -dbcache pins a fixed value. - P2P block download: bounded socket recv-drain loop (tlsmanager); frontier-block reassignment to break head-of-line stalls (-blockreassigntimeout); ProcessGetData serves a bounded batch of blocks per pass instead of one (fixes the serve-side one-block-per-tick throttle that caps download network-wide). - assumeutxo: dumptxoutset RPC + LoadSnapshot machinery + AssumeutxoData chainparams. - Signed bootstrap verification (util/bootstrap-dragonx.sh, util/sign-bootstrap.md). - gtest: RandomX pre-verify consensus-equivalence test + UTXO-snapshot round-trip; revived the gtest harness (Makefile.am include fix, Makefile.gtest.include). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
217
src/pow.cpp
217
src/pow.cpp
@@ -18,6 +18,7 @@
|
||||
* *
|
||||
******************************************************************************/
|
||||
#include "pow.h"
|
||||
#include "checkpoints.h"
|
||||
#include "consensus/upgrades.h"
|
||||
#include "arith_uint256.h"
|
||||
#include "chain.h"
|
||||
@@ -30,6 +31,8 @@
|
||||
#include "sodium.h"
|
||||
#include "RandomX/src/randomx.h"
|
||||
#include <mutex>
|
||||
#include <boost/thread/shared_mutex.hpp>
|
||||
#include <boost/thread/locks.hpp>
|
||||
|
||||
#ifdef ENABLE_RUST
|
||||
#include "librustzcash.h"
|
||||
@@ -704,6 +707,7 @@ static std::mutex cs_randomx_validator;
|
||||
static randomx_cache *s_rxCache = nullptr;
|
||||
static randomx_vm *s_rxVM = nullptr;
|
||||
static std::string s_rxCurrentKey; // tracks current key to avoid re-init
|
||||
static int64_t nTimeRandomX = 0; // cumulative RandomX validation time (us), reported under -debug=bench
|
||||
|
||||
// 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
|
||||
@@ -714,26 +718,70 @@ void SetSkipRandomXValidation(bool skip) { fSkipRandomXValidation = skip; }
|
||||
|
||||
CBlockIndex *hush_chainactive(int32_t height);
|
||||
|
||||
bool CheckRandomXSolution(const CBlockHeader *pblock, int32_t height)
|
||||
// Centralized predicate: does a block at this height actually require a RandomX hash check?
|
||||
// Shared by CheckRandomXSolution (inline path) and the parallel pre-verify pool so the two can
|
||||
// never drift. Returns false when the recompute is unnecessary:
|
||||
// - non-RandomX chain, or RandomX validation disabled (activation height < 0)
|
||||
// - below the RandomX activation height (those blocks used Equihash, validated elsewhere)
|
||||
// - during initial on-disk block loading / reindex (HUSH_LOADINGBLOCKS)
|
||||
// - below the last hardcoded checkpoint (chain pinned by checkpoint hash + linkage + work)
|
||||
// Deliberately does NOT consider the thread-local fSkipRandomXValidation (miner self-check) — that
|
||||
// is a property of the calling thread, handled only in the inline CheckRandomXSolution below.
|
||||
bool RandomXValidationRequired(int32_t height)
|
||||
{
|
||||
// Only applies to RandomX chains
|
||||
if (ASSETCHAINS_ALGO != ASSETCHAINS_RANDOMX)
|
||||
return true;
|
||||
|
||||
// Disabled if activation height is negative
|
||||
return false;
|
||||
if (ASSETCHAINS_RANDOMX_VALIDATION < 0)
|
||||
return true;
|
||||
|
||||
// Not yet at activation height
|
||||
return false;
|
||||
if (height < ASSETCHAINS_RANDOMX_VALIDATION)
|
||||
return true;
|
||||
|
||||
// Do not affect initial block loading
|
||||
return false;
|
||||
extern int32_t HUSH_LOADINGBLOCKS;
|
||||
if (HUSH_LOADINGBLOCKS != 0)
|
||||
return false;
|
||||
extern bool fCheckpointsEnabled;
|
||||
if (fCheckpointsEnabled && height < Checkpoints::GetTotalBlocksEstimate(Params().Checkpoints()))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Serialize the RandomX hash input: the block header without nSolution (but with nNonce). Used by
|
||||
// both the inline CheckRandomXSolution and the parallel pre-verify pool, so the bytes are identical.
|
||||
std::vector<unsigned char> GetRandomXInput(const CBlockHeader& block)
|
||||
{
|
||||
CRandomXInput rxInput(block);
|
||||
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
|
||||
ss << rxInput;
|
||||
return std::vector<unsigned char>(ss.begin(), ss.end());
|
||||
}
|
||||
|
||||
// Derive the RandomX key string for a block at `height`. Below interval+lag it is the chain-params
|
||||
// initial key; otherwise the block hash at the key-rotation height. MUST be called under cs_main
|
||||
// (reads chainActive via hush_chainactive). Returns empty if the key-height block is unavailable.
|
||||
std::string GetRandomXKey(int32_t height)
|
||||
{
|
||||
static int randomxInterval = GetRandomXInterval();
|
||||
static int randomxBlockLag = GetRandomXBlockLag();
|
||||
if (height < randomxInterval + randomxBlockLag) {
|
||||
char initialKey[82];
|
||||
snprintf(initialKey, 81, "%08x%s%08x", ASSETCHAINS_MAGIC, SMART_CHAIN_SYMBOL, ASSETCHAINS_RPCPORT);
|
||||
return std::string(initialKey, strlen(initialKey));
|
||||
}
|
||||
int keyHeight = ((height - randomxBlockLag) / randomxInterval) * randomxInterval;
|
||||
CBlockIndex *pKeyIndex = hush_chainactive(keyHeight);
|
||||
if (pKeyIndex == nullptr)
|
||||
return std::string();
|
||||
uint256 blockKey = pKeyIndex->GetBlockHash();
|
||||
return std::string((const char*)&blockKey, sizeof(blockKey));
|
||||
}
|
||||
|
||||
bool CheckRandomXSolution(const CBlockHeader *pblock, int32_t height)
|
||||
{
|
||||
// Centralized height gate (shared with the parallel pre-verify pool, Stage 0).
|
||||
if (!RandomXValidationRequired(height))
|
||||
return true;
|
||||
|
||||
// Skip when miner is validating its own block via TestBlockValidity
|
||||
// Skip when the miner is validating its own freshly-mined block via TestBlockValidity
|
||||
// (thread-local; never set on the connect thread or the pre-verify worker threads).
|
||||
if (fSkipRandomXValidation)
|
||||
return true;
|
||||
|
||||
@@ -743,47 +791,44 @@ bool CheckRandomXSolution(const CBlockHeader *pblock, int32_t height)
|
||||
pblock->nSolution.size(), RANDOMX_HASH_SIZE, height);
|
||||
}
|
||||
|
||||
static int randomxInterval = GetRandomXInterval();
|
||||
static int randomxBlockLag = GetRandomXBlockLag();
|
||||
|
||||
// Determine the correct RandomX key for this height
|
||||
char initialKey[82];
|
||||
snprintf(initialKey, 81, "%08x%s%08x", ASSETCHAINS_MAGIC, SMART_CHAIN_SYMBOL, ASSETCHAINS_RPCPORT);
|
||||
|
||||
std::string rxKey;
|
||||
if (height < randomxInterval + randomxBlockLag) {
|
||||
// Use initial key derived from chain params
|
||||
rxKey = std::string(initialKey, strlen(initialKey));
|
||||
} else {
|
||||
// Use block hash at the key height
|
||||
int keyHeight = ((height - randomxBlockLag) / randomxInterval) * randomxInterval;
|
||||
CBlockIndex *pKeyIndex = hush_chainactive(keyHeight);
|
||||
if (pKeyIndex == nullptr) {
|
||||
return error("CheckRandomXSolution(): cannot get block index at key height %d for block %d", keyHeight, height);
|
||||
}
|
||||
uint256 blockKey = pKeyIndex->GetBlockHash();
|
||||
rxKey = std::string((const char*)&blockKey, sizeof(blockKey));
|
||||
}
|
||||
|
||||
// Serialize the block header without nSolution (but with nNonce) as RandomX input
|
||||
CRandomXInput rxInput(*pblock);
|
||||
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
|
||||
ss << rxInput;
|
||||
// Derive the key (shared helper) and serialize the input (identical bytes to the pool path).
|
||||
std::string rxKey = GetRandomXKey(height);
|
||||
if (rxKey.empty())
|
||||
return error("CheckRandomXSolution(): cannot derive RandomX key for height %d", height);
|
||||
std::vector<unsigned char> ssInput = GetRandomXInput(*pblock);
|
||||
|
||||
char computedHash[RANDOMX_HASH_SIZE];
|
||||
|
||||
// Measurement (Track 1): isolate RandomX verification cost during IBD. The
|
||||
// expensive parts are the per-key cache (re)init (~every GetRandomXInterval()
|
||||
// blocks) and the hash computation itself; both happen under the lock below.
|
||||
int64_t nTimeRxStart = GetTimeMicros();
|
||||
bool fKeyInit = false;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(cs_randomx_validator);
|
||||
|
||||
// Initialize cache + VM if needed, or re-init if key changed
|
||||
if (s_rxCache == nullptr) {
|
||||
randomx_flags flags = randomx_get_flags();
|
||||
s_rxCache = randomx_alloc_cache(flags);
|
||||
// Try large pages for the 256MB validator cache: fewer TLB misses → ~15-30% faster
|
||||
// light-mode validation where the OS has hugepages configured. Falls back transparently
|
||||
// when unavailable, exactly as the miner does (miner.cpp:1097). Page size does not affect
|
||||
// the computed hash, so this is consensus-neutral.
|
||||
bool fLargePages = true;
|
||||
s_rxCache = randomx_alloc_cache(flags | RANDOMX_FLAG_LARGE_PAGES);
|
||||
if (s_rxCache == nullptr) {
|
||||
fLargePages = false;
|
||||
s_rxCache = randomx_alloc_cache(flags);
|
||||
}
|
||||
if (s_rxCache == nullptr) {
|
||||
return error("CheckRandomXSolution(): failed to allocate RandomX cache");
|
||||
}
|
||||
// Confirm the fast paths are active (JIT off would be ~9x slower; see randomx-benchmark).
|
||||
LogPrint("bench", "CheckRandomXSolution: RandomX flags=0x%x JIT=%d HARD_AES=%d largePages=%d\n",
|
||||
(unsigned int)flags, !!(flags & RANDOMX_FLAG_JIT), !!(flags & RANDOMX_FLAG_HARD_AES), (int)fLargePages);
|
||||
randomx_init_cache(s_rxCache, rxKey.data(), rxKey.size());
|
||||
s_rxCurrentKey = rxKey;
|
||||
fKeyInit = true;
|
||||
s_rxVM = randomx_create_vm(flags, s_rxCache, nullptr);
|
||||
if (s_rxVM == nullptr) {
|
||||
randomx_release_cache(s_rxCache);
|
||||
@@ -793,11 +838,17 @@ bool CheckRandomXSolution(const CBlockHeader *pblock, int32_t height)
|
||||
} else if (s_rxCurrentKey != rxKey) {
|
||||
randomx_init_cache(s_rxCache, rxKey.data(), rxKey.size());
|
||||
s_rxCurrentKey = rxKey;
|
||||
fKeyInit = true;
|
||||
randomx_vm_set_cache(s_rxVM, s_rxCache);
|
||||
}
|
||||
|
||||
randomx_calculate_hash(s_rxVM, &ss[0], ss.size(), computedHash);
|
||||
randomx_calculate_hash(s_rxVM, ssInput.data(), ssInput.size(), computedHash);
|
||||
}
|
||||
int64_t nTimeRxEnd = GetTimeMicros();
|
||||
nTimeRandomX += nTimeRxEnd - nTimeRxStart;
|
||||
LogPrint("bench", " - RandomX verify ht=%d: %.2fms%s [%.2fs]\n",
|
||||
height, (nTimeRxEnd - nTimeRxStart) * 0.001,
|
||||
fKeyInit ? " (key-init)" : "", nTimeRandomX * 0.000001);
|
||||
|
||||
// Compare computed hash against nSolution
|
||||
if (memcmp(computedHash, pblock->nSolution.data(), RANDOMX_HASH_SIZE) != 0) {
|
||||
@@ -814,7 +865,7 @@ bool CheckRandomXSolution(const CBlockHeader *pblock, int32_t 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());
|
||||
rxKey.size(), ssInput.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
|
||||
@@ -822,7 +873,7 @@ bool CheckRandomXSolution(const CBlockHeader *pblock, int32_t 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());
|
||||
rxKey.size(), ssInput.size(), pblock->nNonce.ToString());
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -830,6 +881,88 @@ bool CheckRandomXSolution(const CBlockHeader *pblock, int32_t height)
|
||||
return true;
|
||||
}
|
||||
|
||||
// ============================================================================================
|
||||
// Parallel RandomX pre-verification pool (Stage 2).
|
||||
// One shared light-mode cache (holding a single key at a time) + per-thread VMs, mirroring the
|
||||
// miner's RandomXDatasetManager pattern (miner.cpp). The connect thread (ActivateBestChainStep)
|
||||
// loads the cache key for a same-key group of about-to-be-connected blocks, dispatches them to
|
||||
// this pool, and barrier-waits; each worker hashes on its own VM (sharing the read-only cache)
|
||||
// and, on a match, sets the block's transient fRandomXVerified flag so the inline check in
|
||||
// CheckBlockHeader can be skipped. The inline path remains the consensus authority for anything
|
||||
// not pre-verified, so the pool can only ever flip false->true on a real hash match.
|
||||
static boost::shared_mutex g_rxvMutex; // shared = hashing; exclusive = cache (re)init
|
||||
static randomx_cache* g_rxvCache = nullptr; // shared, read-only during hashing
|
||||
static std::string g_rxvKey; // key currently loaded into g_rxvCache
|
||||
static randomx_flags g_rxvFlags;
|
||||
static thread_local randomx_vm* tls_rxvVM = nullptr;
|
||||
static thread_local std::string tls_rxvVMKey;
|
||||
|
||||
CCheckQueue<CRandomXCheck> rxCheckQueue(1); // batch size 1: each item is ~tens of ms
|
||||
|
||||
bool RandomXValidatorPrepareKey(const std::string& rxKey)
|
||||
{
|
||||
boost::unique_lock<boost::shared_mutex> lock(g_rxvMutex);
|
||||
if (g_rxvCache == nullptr) {
|
||||
g_rxvFlags = randomx_get_flags();
|
||||
g_rxvCache = randomx_alloc_cache(g_rxvFlags | RANDOMX_FLAG_LARGE_PAGES);
|
||||
if (g_rxvCache == nullptr)
|
||||
g_rxvCache = randomx_alloc_cache(g_rxvFlags);
|
||||
if (g_rxvCache == nullptr) {
|
||||
LogPrintf("RandomXValidatorPrepareKey: cache alloc failed; parallel pre-verify disabled\n");
|
||||
return false;
|
||||
}
|
||||
randomx_init_cache(g_rxvCache, rxKey.data(), rxKey.size());
|
||||
g_rxvKey = rxKey;
|
||||
return true;
|
||||
}
|
||||
if (g_rxvKey != rxKey) {
|
||||
randomx_init_cache(g_rxvCache, rxKey.data(), rxKey.size());
|
||||
g_rxvKey = rxKey;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CRandomXCheck::operator()()
|
||||
{
|
||||
boost::shared_lock<boost::shared_mutex> lock(g_rxvMutex);
|
||||
// The connect thread set the shared cache to one key before dispatching this group. If this
|
||||
// item's key doesn't match (e.g. a key-rotation straggler) or the cache is unavailable, skip it
|
||||
// and leave *presult false — the inline CheckRandomXSolution will verify it.
|
||||
if (g_rxvCache == nullptr || g_rxvKey != rxKey)
|
||||
return true;
|
||||
if (tls_rxvVM == nullptr) {
|
||||
tls_rxvVM = randomx_create_vm(g_rxvFlags, g_rxvCache, nullptr);
|
||||
if (tls_rxvVM == nullptr)
|
||||
return true; // cannot verify here -> inline fallback
|
||||
tls_rxvVMKey = g_rxvKey;
|
||||
} else if (tls_rxvVMKey != g_rxvKey) {
|
||||
// Cache was re-initialized to a new key since this VM last ran; rebind.
|
||||
randomx_vm_set_cache(tls_rxvVM, g_rxvCache);
|
||||
tls_rxvVMKey = g_rxvKey;
|
||||
}
|
||||
unsigned char h[RANDOMX_HASH_SIZE];
|
||||
randomx_calculate_hash(tls_rxvVM, input.data(), input.size(), h);
|
||||
if (memcmp(h, expected, RANDOMX_HASH_SIZE) == 0 && presult != nullptr)
|
||||
*presult = true;
|
||||
return true; // ALWAYS true: never short-circuit the queue; per-block result is in *presult
|
||||
}
|
||||
|
||||
void ThreadRandomXVerify()
|
||||
{
|
||||
RenameThread("hush-rxverify");
|
||||
rxCheckQueue.Thread();
|
||||
}
|
||||
|
||||
void RandomXValidatorShutdown()
|
||||
{
|
||||
boost::unique_lock<boost::shared_mutex> lock(g_rxvMutex);
|
||||
// Per-thread VMs are intentionally leaked (process exiting); release the shared cache.
|
||||
if (g_rxvCache != nullptr) {
|
||||
randomx_release_cache(g_rxvCache);
|
||||
g_rxvCache = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
int32_t hush_chosennotary(int32_t *notaryidp,int32_t height,uint8_t *pubkey33,uint32_t timestamp);
|
||||
int32_t hush_currentheight();
|
||||
void hush_index2pubkey33(uint8_t *pubkey33,CBlockIndex *pindex,int32_t height);
|
||||
|
||||
Reference in New Issue
Block a user