randomx validation enforcement with activation height

This commit is contained in:
dan_s
2026-02-17 16:43:41 -06:00
parent 74d66d8bf2
commit 0da68ebbc7
9 changed files with 166 additions and 5 deletions

View File

@@ -23,6 +23,7 @@
#include "script/standard.h"
#include "cc/CCinclude.h"
#include "sietch.h"
#include "pow.h"
// This is the address for pubkey = 0x000000000000000000000000000000000 (33 bytes)
// Funds sent to a burn address can never be spent because the pubkey is invalid
@@ -1693,6 +1694,11 @@ int32_t hush_checkPOW(int32_t slowflag,CBlock *pblock,int32_t height)
fprintf(stderr,"hush_checkPOW slowflag.%d ht.%d CheckEquihashSolution failed\n",slowflag,height);
return(-1);
}
if ( !CheckRandomXSolution(pblock, height) )
{
fprintf(stderr,"hush_checkPOW slowflag.%d ht.%d CheckRandomXSolution failed\n",slowflag,height);
return(-1);
}
hash = pblock->GetHash();
bnTarget.SetCompact(pblock->nBits,&fNegative,&fOverflow);
bhash = UintToArith256(hash);

View File

@@ -573,6 +573,7 @@ extern uint64_t ASSETCHAINS_SUPPLY, ASSETCHAINS_FOUNDERS_REWARD;
extern int32_t ASSETCHAINS_LWMAPOS, ASSETCHAINS_SAPLING, ASSETCHAINS_OVERWINTER,ASSETCHAINS_BLOCKTIME;
extern uint64_t ASSETCHAINS_TIMELOCKGTE;
extern uint32_t ASSETCHAINS_ALGO,ASSETCHAINS_EQUIHASH,ASSETCHAINS_RANDOMX, HUSH_INITDONE;
extern int32_t ASSETCHAINS_RANDOMX_VALIDATION;
extern int32_t HUSH_MININGTHREADS,HUSH_LONGESTCHAIN,ASSETCHAINS_SEED,IS_HUSH_NOTARY,USE_EXTERNAL_PUBKEY,HUSH_CHOSEN_ONE,HUSH_ON_DEMAND,HUSH_PASSPORT_INITDONE,ASSETCHAINS_STAKED,HUSH_NSPV;
extern uint64_t ASSETCHAINS_COMMISSION, ASSETCHAINS_LASTERA,ASSETCHAINS_CBOPRET;
extern uint64_t ASSETCHAINS_REWARD[ASSETCHAINS_MAX_ERAS+1], ASSETCHAINS_NOTARY_PAY[ASSETCHAINS_MAX_ERAS+1], ASSETCHAINS_TIMELOCKGTE, ASSETCHAINS_NONCEMASK[],ASSETCHAINS_NK[2];

View File

@@ -92,6 +92,7 @@ uint64_t ASSETCHAINS_NONCEMASK[] = {0xffff};
uint32_t ASSETCHAINS_NONCESHIFT[] = {32};
uint32_t ASSETCHAINS_HASHESPERROUND[] = {1};
uint32_t ASSETCHAINS_ALGO = _ASSETCHAINS_EQUIHASH;
int32_t ASSETCHAINS_RANDOMX_VALIDATION = -1; // activation height for RandomX validation (-1 = disabled)
// min diff returned from GetNextWorkRequired needs to be added here for each algo, so they can work with ac_staked.
uint32_t ASSETCHAINS_MINDIFF[] = {537857807};
int32_t ASSETCHAINS_LWMAPOS = 0; // percentage of blocks should be PoS

View File

@@ -1916,6 +1916,18 @@ void hush_args(char *argv0)
strncpy(SMART_CHAIN_SYMBOL,name.c_str(),sizeof(SMART_CHAIN_SYMBOL)-1);
const bool ishush3 = strncmp(SMART_CHAIN_SYMBOL, "HUSH3",5) == 0 ? true : false;
// Set RandomX validation activation height per chain
if (ASSETCHAINS_ALGO == ASSETCHAINS_RANDOMX) {
if (strncmp(SMART_CHAIN_SYMBOL, "DRAGONX", 7) == 0) {
ASSETCHAINS_RANDOMX_VALIDATION = 2838976; // TBD: set to coordinated upgrade height
} else if (strncmp(SMART_CHAIN_SYMBOL, "TUMIN", 5) == 0) {
ASSETCHAINS_RANDOMX_VALIDATION = 100; // TBD: set to coordinated upgrade height
} else {
ASSETCHAINS_RANDOMX_VALIDATION = 1; // all other RandomX HACs: enforce from height 1
}
printf("ASSETCHAINS_RANDOMX_VALIDATION set to %d for %s\n", ASSETCHAINS_RANDOMX_VALIDATION, SMART_CHAIN_SYMBOL);
}
ASSETCHAINS_LASTERA = GetArg("-ac_eras", 1);
if(ishush3) {
ASSETCHAINS_LASTERA = 3;

View File

@@ -4993,6 +4993,8 @@ bool CheckBlockHeader(int32_t *futureblockp,int32_t height,CBlockIndex *pindex,
{
if ( !CheckEquihashSolution(&blockhdr, Params()) )
return state.DoS(100, error("CheckBlockHeader(): Equihash solution invalid"),REJECT_INVALID, "invalid-solution");
if ( !CheckRandomXSolution(&blockhdr, height) )
return state.DoS(100, error("CheckBlockHeader(): RandomX solution invalid"),REJECT_INVALID, "invalid-randomx-solution");
}
// Check proof of work matches claimed amount
/*hush_index2pubkey33(pubkey33,pindex,height);

View File

@@ -1011,8 +1011,8 @@ enum RandomXSolverCancelCheck
Reason2
};
int GetRandomXInterval() { return GetArg("-ac_randomx_interval",1024); }
int GetRandomXBlockLag() { return GetArg("-ac_randomx_lag", 64); }
int GetRandomXInterval();
int GetRandomXBlockLag();
#ifdef ENABLE_WALLET
void static RandomXMiner(CWallet *pwallet)
@@ -1268,16 +1268,17 @@ void static RandomXMiner()
arith_uint256 hashTarget;
hashTarget = HASHTarget;
CRandomXInput rxInput(pblocktemplate->block);
CDataStream randomxInput(SER_NETWORK, PROTOCOL_VERSION);
// Use the current block as randomx input
randomxInput << pblocktemplate->block;
// Serialize block header without nSolution but with nNonce for deterministic RandomX input
randomxInput << rxInput;
// std::cerr << "RandomXMiner: randomxInput=" << HexStr(randomxInput) << "\n";
// fprintf(stderr,"RandomXMiner: created randomxKey=%s , randomxInput.size=%lu\n", randomxKey, randomxInput.size() ); //randomxInput);
rxdebug("%s: randomxKey=%s randomxInput=%s\n", randomxKey, HexStr(randomxInput).c_str());
rxdebug("%s: calculating randomx hash\n");
randomx_calculate_hash(myVM, &randomxInput, sizeof randomxInput, randomxHash);
randomx_calculate_hash(myVM, &randomxInput[0], randomxInput.size(), randomxHash);
rxdebug("%s: calculated randomx hash\n");
rxdebug("%s: randomxHash=");

View File

@@ -28,6 +28,8 @@
#include "uint256.h"
#include "util.h"
#include "sodium.h"
#include "RandomX/src/randomx.h"
#include <mutex>
#ifdef ENABLE_RUST
#include "librustzcash.h"
@@ -683,6 +685,106 @@ bool CheckEquihashSolution(const CBlockHeader *pblock, const CChainParams& param
return true;
}
int GetRandomXInterval() { return GetArg("-ac_randomx_interval", 1024); }
int GetRandomXBlockLag() { return GetArg("-ac_randomx_lag", 64); }
// Cached RandomX validation state — reused across calls, protected by mutex
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
CBlockIndex *hush_chainactive(int32_t height);
bool CheckRandomXSolution(const CBlockHeader *pblock, int32_t height)
{
// Only applies to RandomX chains
if (ASSETCHAINS_ALGO != ASSETCHAINS_RANDOMX)
return true;
// Disabled if activation height is negative
if (ASSETCHAINS_RANDOMX_VALIDATION < 0)
return true;
// Not yet at activation height
if (height < ASSETCHAINS_RANDOMX_VALIDATION)
return true;
// Do not affect initial block loading
extern int32_t HUSH_LOADINGBLOCKS;
if (HUSH_LOADINGBLOCKS != 0)
return true;
// nSolution must be exactly RANDOMX_HASH_SIZE (32) bytes
if (pblock->nSolution.size() != RANDOMX_HASH_SIZE) {
return error("CheckRandomXSolution(): nSolution size %u != expected %d at height %d",
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;
char computedHash[RANDOMX_HASH_SIZE];
{
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);
if (s_rxCache == nullptr) {
return error("CheckRandomXSolution(): failed to allocate RandomX cache");
}
randomx_init_cache(s_rxCache, rxKey.data(), rxKey.size());
s_rxCurrentKey = rxKey;
s_rxVM = randomx_create_vm(flags, s_rxCache, nullptr);
if (s_rxVM == nullptr) {
randomx_release_cache(s_rxCache);
s_rxCache = nullptr;
return error("CheckRandomXSolution(): failed to create RandomX VM");
}
} else if (s_rxCurrentKey != rxKey) {
randomx_init_cache(s_rxCache, rxKey.data(), rxKey.size());
s_rxCurrentKey = rxKey;
randomx_vm_set_cache(s_rxVM, s_rxCache);
}
randomx_calculate_hash(s_rxVM, &ss[0], ss.size(), computedHash);
}
// Compare computed hash against nSolution
if (memcmp(computedHash, pblock->nSolution.data(), RANDOMX_HASH_SIZE) != 0) {
return error("CheckRandomXSolution(): RandomX hash mismatch at height %d", height);
}
return true;
}
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);

View File

@@ -38,6 +38,15 @@ unsigned int CalculateNextWorkRequired(arith_uint256 bnAvg,
/** Check whether the Equihash solution in a block header is valid */
bool CheckEquihashSolution(const CBlockHeader *pblock, const CChainParams&);
/** Check whether a block header contains a valid RandomX solution */
bool CheckRandomXSolution(const CBlockHeader *pblock, int32_t height);
/** Return the RandomX key rotation interval in blocks */
int GetRandomXInterval();
/** Return the RandomX key change lag in blocks */
int GetRandomXBlockLag();
/** Check whether a block hash satisfies the proof-of-work requirement specified by nBits */
bool CheckProofOfWork(const CBlockHeader &blkHeader, uint8_t *pubkey33, int32_t height, const Consensus::Params& params);
CChainPower GetBlockProof(const CBlockIndex& block);

View File

@@ -237,6 +237,33 @@ public:
}
};
/**
* Custom serializer for CBlockHeader that includes nNonce but omits nSolution,
* for use as deterministic input to RandomX hashing.
*/
class CRandomXInput : private CBlockHeader
{
public:
CRandomXInput(const CBlockHeader &header)
{
CBlockHeader::SetNull();
*((CBlockHeader*)this) = header;
}
ADD_SERIALIZE_METHODS;
template <typename Stream, typename Operation>
inline void SerializationOp(Stream& s, Operation ser_action) {
READWRITE(this->nVersion);
READWRITE(hashPrevBlock);
READWRITE(hashMerkleRoot);
READWRITE(hashFinalSaplingRoot);
READWRITE(nTime);
READWRITE(nBits);
READWRITE(nNonce);
}
};
/** Describes a place in the block chain to another node such that if the
* other node doesn't have the same branch, it can find a recent common trunk.