diff --git a/.gitignore b/.gitignore index f010d3da8..873d3596b 100644 --- a/.gitignore +++ b/.gitignore @@ -167,3 +167,4 @@ REGTEST_7776 src/cc/librogue.so src/cc/games/prices src/cc/games/tetris +release-linux/ \ No newline at end of file diff --git a/src/hush_bitcoind.h b/src/hush_bitcoind.h index 218f09db7..424801ae0 100644 --- a/src/hush_bitcoind.h +++ b/src/hush_bitcoind.h @@ -1693,6 +1693,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); diff --git a/src/hush_defs.h b/src/hush_defs.h index 510a6309a..6fd33a8f9 100644 --- a/src/hush_defs.h +++ b/src/hush_defs.h @@ -568,6 +568,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]; diff --git a/src/hush_globals.h b/src/hush_globals.h index f7bbeb860..bfa1650b1 100644 --- a/src/hush_globals.h +++ b/src/hush_globals.h @@ -93,6 +93,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 diff --git a/src/hush_utils.h b/src/hush_utils.h index 1ca9db12d..23e230396 100644 --- a/src/hush_utils.h +++ b/src/hush_utils.h @@ -1902,6 +1902,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 = 1200; // 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; diff --git a/src/main.cpp b/src/main.cpp index 2ae864b3a..ea0045589 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -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); diff --git a/src/pow.cpp b/src/pow.cpp index 0ba332b07..c23ced293 100644 --- a/src/pow.cpp +++ b/src/pow.cpp @@ -28,6 +28,8 @@ #include "uint256.h" #include "util.h" #include "sodium.h" +#include "RandomX/src/randomx.h" +#include #ifdef ENABLE_RUST #include "librustzcash.h" @@ -683,6 +685,137 @@ bool CheckEquihashSolution(const CBlockHeader *pblock, const CChainParams& param return true; } +// Static objects for CheckRandomXSolution +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 + +// 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); + +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; + + // Skip when miner is validating its own block via TestBlockValidity + if (fSkipRandomXValidation) + 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 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) { + // 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; +} + 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); @@ -729,6 +862,8 @@ bool CheckProofOfWork(const CBlockHeader &blkHeader, uint8_t *pubkey33, int32_t if ( HUSH_LOADINGBLOCKS != 0 ) return true; + if ( SMART_CHAIN_SYMBOL[0] != 0 || height > 792000 ) + { if ( Params().NetworkIDString() != "regtest" ) { for (i=31; i>=0; i--) @@ -745,6 +880,7 @@ bool CheckProofOfWork(const CBlockHeader &blkHeader, uint8_t *pubkey33, int32_t fprintf(stderr," <- origpubkey\n"); } return false; + } } /*for (i=31; i>=0; i--) fprintf(stderr,"%02x",((uint8_t *)&hash)[i]); diff --git a/src/pow.h b/src/pow.h index 9a86bee64..6027c45f9 100644 --- a/src/pow.h +++ b/src/pow.h @@ -38,6 +38,18 @@ 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); + +/** 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 */ +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); diff --git a/src/primitives/block.h b/src/primitives/block.h index 245be24ed..bd0d429e8 100644 --- a/src/primitives/block.h +++ b/src/primitives/block.h @@ -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 + 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.