diff --git a/src/Makefile.am b/src/Makefile.am index 99a3b7390..375403565 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -370,6 +370,7 @@ libbitcoin_cnutils_a_SOURCES = \ cn_utils/cryptonote_basic/account.cpp \ cn_utils/crypto/random.c \ cn_utils/crypto/tree-hash.c \ + cn_utils/crypto/rx-slow-hash.c \ cn_utils/crypto/oaes_lib.c \ cn_utils/crypto/aesb.c \ cn_utils/crypto/hash-extra-groestl.c \ diff --git a/src/cn_utils/crypto/hash-ops.h b/src/cn_utils/crypto/hash-ops.h index 14f678e5e..416b9acf3 100644 --- a/src/cn_utils/crypto/hash-ops.h +++ b/src/cn_utils/crypto/hash-ops.h @@ -87,3 +87,11 @@ void hash_extra_jh(const void *data, size_t length, char *hash); void hash_extra_skein(const void *data, size_t length, char *hash); void tree_hash(const char (*hashes)[HASH_SIZE], size_t count, char *root_hash); + +#define RX_BLOCK_VERSION 12 +void rx_slow_hash_allocate_state(void); +void rx_slow_hash_free_state(void); +uint64_t rx_seedheight(const uint64_t height); +void rx_seedheights(const uint64_t height, uint64_t *seed_height, uint64_t *next_height); +void rx_slow_hash(const uint64_t mainheight, const uint64_t seedheight, const char *seedhash, const void *data, size_t length, char *hash, int miners, int is_alt); +void rx_reorg(const uint64_t split_height); diff --git a/src/cn_utils/crypto/rx-slow-hash.c b/src/cn_utils/crypto/rx-slow-hash.c new file mode 100644 index 000000000..ada372864 --- /dev/null +++ b/src/cn_utils/crypto/rx-slow-hash.c @@ -0,0 +1,357 @@ +// Copyright (c) 2019, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "randomx.h" +#include "c_threads.h" +#include "hash-ops.h" +#include "misc_log_ex.h" + +#define RX_LOGCAT "randomx" + +#if defined(_MSC_VER) +#define THREADV __declspec(thread) +#else +#define THREADV __thread +#endif + +typedef struct rx_state { + CTHR_MUTEX_TYPE rs_mutex; + char rs_hash[32]; + uint64_t rs_height; + randomx_cache *rs_cache; +} rx_state; + +static CTHR_MUTEX_TYPE rx_mutex = CTHR_MUTEX_INIT; +static CTHR_MUTEX_TYPE rx_dataset_mutex = CTHR_MUTEX_INIT; + +static rx_state rx_s[2] = {{CTHR_MUTEX_INIT,{0},0,0},{CTHR_MUTEX_INIT,{0},0,0}}; + +static randomx_dataset *rx_dataset; +static uint64_t rx_dataset_height; +static THREADV randomx_vm *rx_vm = NULL; +static THREADV int rx_toggle; + +static void local_abort(const char *msg) +{ + fprintf(stderr, "%s\n", msg); +#ifdef NDEBUG + _exit(1); +#else + abort(); +#endif +} + +/** + * @brief uses cpuid to determine if the CPU supports the AES instructions + * @return true if the CPU supports AES, false otherwise + */ + +static inline int force_software_aes(void) +{ + static int use = -1; + + if (use != -1) + return use; + + const char *env = getenv("MONERO_USE_SOFTWARE_AES"); + if (!env) { + use = 0; + } + else if (!strcmp(env, "0") || !strcmp(env, "no")) { + use = 0; + } + else { + use = 1; + } + return use; +} + +static void cpuid(int CPUInfo[4], int InfoType) +{ +#if defined(__x86_64__) + __asm __volatile__ + ( + "cpuid": + "=a" (CPUInfo[0]), + "=b" (CPUInfo[1]), + "=c" (CPUInfo[2]), + "=d" (CPUInfo[3]) : + "a" (InfoType), "c" (0) + ); +#endif +} +static inline int check_aes_hw(void) +{ +#if defined(__x86_64__) + int cpuid_results[4]; + static int supported = -1; + + if(supported >= 0) + return supported; + + cpuid(cpuid_results,1); + return supported = cpuid_results[2] & (1 << 25); +#else + return 0; +#endif +} + +static volatile int use_rx_jit_flag = -1; + +static inline int use_rx_jit(void) +{ +#if defined(__x86_64__) + + if (use_rx_jit_flag != -1) + return use_rx_jit_flag; + + const char *env = getenv("MONERO_USE_RX_JIT"); + if (!env) { + use_rx_jit_flag = 1; + } + else if (!strcmp(env, "0") || !strcmp(env, "no")) { + use_rx_jit_flag = 0; + } + else { + use_rx_jit_flag = 1; + } + return use_rx_jit_flag; +#else + return 0; +#endif +} + +#define SEEDHASH_EPOCH_BLOCKS 2048 /* Must be same as BLOCKS_SYNCHRONIZING_MAX_COUNT in cryptonote_config.h */ +#define SEEDHASH_EPOCH_LAG 64 + +void rx_reorg(const uint64_t split_height) { + int i; + CTHR_MUTEX_LOCK(rx_mutex); + for (i=0; i<2; i++) { + if (split_height < rx_s[i].rs_height) + rx_s[i].rs_height = 1; /* set to an invalid seed height */ + } + CTHR_MUTEX_UNLOCK(rx_mutex); +} + +uint64_t rx_seedheight(const uint64_t height) { + uint64_t s_height = (height <= SEEDHASH_EPOCH_BLOCKS+SEEDHASH_EPOCH_LAG) ? 0 : + (height - SEEDHASH_EPOCH_LAG - 1) & ~(SEEDHASH_EPOCH_BLOCKS-1); + return s_height; +} + +void rx_seedheights(const uint64_t height, uint64_t *seedheight, uint64_t *nextheight) { + *seedheight = rx_seedheight(height); + *nextheight = rx_seedheight(height + SEEDHASH_EPOCH_LAG); +} + +typedef struct seedinfo { + randomx_cache *si_cache; + unsigned long si_start; + unsigned long si_count; +} seedinfo; + +static CTHR_THREAD_RTYPE rx_seedthread(void *arg) { + seedinfo *si = arg; + randomx_init_dataset(rx_dataset, si->si_cache, si->si_start, si->si_count); + CTHR_THREAD_RETURN; +} + +static void rx_initdata(randomx_cache *rs_cache, const int miners, const uint64_t seedheight) { + if (miners > 1) { + unsigned long delta = randomx_dataset_item_count() / miners; + unsigned long start = 0; + int i; + seedinfo *si; + CTHR_THREAD_TYPE *st; + si = malloc(miners * sizeof(seedinfo)); + if (si == NULL) + local_abort("Couldn't allocate RandomX mining threadinfo"); + st = malloc(miners * sizeof(CTHR_THREAD_TYPE)); + if (st == NULL) { + free(si); + local_abort("Couldn't allocate RandomX mining threadlist"); + } + for (i=0; i seedheight) + is_alt = 1; + + toggle ^= (is_alt != 0); + if (toggle != rx_toggle) + changed = 1; + rx_toggle = toggle; + + rx_sp = &rx_s[toggle]; + CTHR_MUTEX_LOCK(rx_sp->rs_mutex); + CTHR_MUTEX_UNLOCK(rx_mutex); + + cache = rx_sp->rs_cache; + if (cache == NULL) { + if (use_rx_jit()) + flags |= RANDOMX_FLAG_JIT; + if (cache == NULL) { + cache = randomx_alloc_cache(flags | RANDOMX_FLAG_LARGE_PAGES); + if (cache == NULL) { + mdebug(RX_LOGCAT, "Couldn't use largePages for RandomX cache"); + cache = randomx_alloc_cache(flags); + } + if (cache == NULL) + local_abort("Couldn't allocate RandomX cache"); + } + } + if (rx_sp->rs_height != seedheight || rx_sp->rs_cache == NULL || memcmp(seedhash, rx_sp->rs_hash, sizeof(rx_sp->rs_hash))) { + randomx_init_cache(cache, seedhash, 32); + rx_sp->rs_cache = cache; + rx_sp->rs_height = seedheight; + memcpy(rx_sp->rs_hash, seedhash, sizeof(rx_sp->rs_hash)); + changed = 1; + } + if (rx_vm == NULL) { + randomx_flags flags = RANDOMX_FLAG_DEFAULT; + if (use_rx_jit()) { + flags |= RANDOMX_FLAG_JIT; + if (!miners) + flags |= RANDOMX_FLAG_SECURE; + } + if(!force_software_aes() && check_aes_hw()) + flags |= RANDOMX_FLAG_HARD_AES; + if (miners) { + CTHR_MUTEX_LOCK(rx_dataset_mutex); + if (rx_dataset == NULL) { + rx_dataset = randomx_alloc_dataset(RANDOMX_FLAG_LARGE_PAGES); + if (rx_dataset == NULL) { + mdebug(RX_LOGCAT, "Couldn't use largePages for RandomX dataset"); + rx_dataset = randomx_alloc_dataset(RANDOMX_FLAG_DEFAULT); + } + if (rx_dataset != NULL) + rx_initdata(rx_sp->rs_cache, miners, seedheight); + } + if (rx_dataset != NULL) + flags |= RANDOMX_FLAG_FULL_MEM; + else { + miners = 0; + mwarning(RX_LOGCAT, "Couldn't allocate RandomX dataset for miner"); + } + CTHR_MUTEX_UNLOCK(rx_dataset_mutex); + } + rx_vm = randomx_create_vm(flags | RANDOMX_FLAG_LARGE_PAGES, rx_sp->rs_cache, rx_dataset); + if(rx_vm == NULL) { //large pages failed + mdebug(RX_LOGCAT, "Couldn't use largePages for RandomX VM"); + rx_vm = randomx_create_vm(flags, rx_sp->rs_cache, rx_dataset); + } + if(rx_vm == NULL) {//fallback if everything fails + flags = RANDOMX_FLAG_DEFAULT | (miners ? RANDOMX_FLAG_FULL_MEM : 0); + rx_vm = randomx_create_vm(flags, rx_sp->rs_cache, rx_dataset); + } + if (rx_vm == NULL) + local_abort("Couldn't allocate RandomX VM"); + } else if (miners) { + CTHR_MUTEX_LOCK(rx_dataset_mutex); + if (rx_dataset != NULL && rx_dataset_height != seedheight) + rx_initdata(cache, miners, seedheight); + CTHR_MUTEX_UNLOCK(rx_dataset_mutex); + } else if (changed) { + randomx_vm_set_cache(rx_vm, rx_sp->rs_cache); + } + /* mainchain users can run in parallel */ + if (!is_alt) + CTHR_MUTEX_UNLOCK(rx_sp->rs_mutex); + randomx_calculate_hash(rx_vm, data, length, hash); + /* altchain slot users always get fully serialized */ + if (is_alt) + CTHR_MUTEX_UNLOCK(rx_sp->rs_mutex); +} + +void rx_slow_hash_allocate_state(void) { +} + +void rx_slow_hash_free_state(void) { + if (rx_vm != NULL) { + randomx_destroy_vm(rx_vm); + rx_vm = NULL; + } +} + +void rx_stop_mining(void) { + CTHR_MUTEX_LOCK(rx_dataset_mutex); + if (rx_dataset != NULL) { + randomx_dataset *rd = rx_dataset; + rx_dataset = NULL; + randomx_release_dataset(rd); + } + CTHR_MUTEX_UNLOCK(rx_dataset_mutex); +} diff --git a/src/cn_utils/crypto/slow-hash.c b/src/cn_utils/crypto/slow-hash.c index a41b00477..eb8c5cdba 100644 --- a/src/cn_utils/crypto/slow-hash.c +++ b/src/cn_utils/crypto/slow-hash.c @@ -743,7 +743,7 @@ BOOL SetLockPagesPrivilege(HANDLE hProcess, BOOL bEnable) * the allocated buffer. */ -void slow_hash_allocate_state(void) +void cn_slow_hash_allocate_state(void) { if(hp_state != NULL) return; @@ -800,10 +800,10 @@ void slow_hash_allocate_state(void) } /** - *@brief frees the state allocated by slow_hash_allocate_state + *@brief frees the state allocated by cn_slow_hash_allocate_state */ -void slow_hash_free_state(void) +void cn_slow_hash_free_state(void) { if(hp_state == NULL) return; @@ -891,7 +891,7 @@ void cn_slow_hash(const void *data, size_t length, char *hash, int variant, int // this isn't supposed to happen, but guard against it for now. if(hp_state == NULL) - slow_hash_allocate_state(); + cn_slow_hash_allocate_state(); /* CryptoNight Step 1: Use Keccak1600 to initialize the 'state' (and 'text') buffers from the data. */ if (prehashed) { @@ -1005,13 +1005,13 @@ void cn_slow_hash(const void *data, size_t length, char *hash, int variant, int } #elif !defined NO_AES && (defined(__arm__) || defined(__aarch64__)) -void slow_hash_allocate_state(void) +void cn_slow_hash_allocate_state(void) { // Do nothing, this is just to maintain compatibility with the upgraded slow-hash.c return; } -void slow_hash_free_state(void) +void cn_slow_hash_free_state(void) { // As above return; @@ -1578,13 +1578,13 @@ void cn_slow_hash(const void *data, size_t length, char *hash, int variant, int #define hp_jitfunc ((v4_random_math_JIT_func)NULL) -void slow_hash_allocate_state(void) +void cn_slow_hash_allocate_state(void) { // Do nothing, this is just to maintain compatibility with the upgraded slow-hash.c return; } -void slow_hash_free_state(void) +void cn_slow_hash_free_state(void) { // As above return; @@ -1761,3 +1761,15 @@ void cn_slow_hash(const void *data, size_t length, char *hash, int variant, int } #endif + +void slow_hash_allocate_state(void) +{ + cn_slow_hash_allocate_state(); + rx_slow_hash_allocate_state(); +} + +void slow_hash_free_state(void) +{ + cn_slow_hash_free_state(); + rx_slow_hash_free_state(); +} diff --git a/src/consensus/params.h b/src/consensus/params.h index 994ca1819..9cd8567fe 100644 --- a/src/consensus/params.h +++ b/src/consensus/params.h @@ -58,6 +58,8 @@ struct Params { int BIP65Height; /** Block height at which BIP66 becomes active */ int BIP66Height; + /** Block height at which RandomX becomes active */ + int RandomXHeight; /** * Minimum blocks including miner confirmation of the total of 2016 blocks in a retargeting period, * (nPowTargetTimespan / nPowTargetSpacing) which is also used for BIP9 deployments. @@ -75,7 +77,12 @@ struct Params { int64_t DifficultyAdjustmentInterval() const { return nPowTargetTimespan / nPowTargetSpacing; } uint256 nMinimumChainWork; uint256 defaultAssumeValid; - uint8_t GetCryptonoteMajorVersion() const { return 10; } + uint8_t GetCryptonoteMajorVersion(uint32_t height) const { + if (height >= RandomXHeight) { + return 12; + } + return 10; + } }; } // namespace Consensus diff --git a/src/miner.cpp b/src/miner.cpp index d36ade59c..eacaa37cc 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -176,7 +176,7 @@ std::unique_ptr BlockAssembler::CreateNewBlock(const CScript& sc UpdateTime(pblock, chainparams.GetConsensus(), pindexPrev); pblock->nBits = GetNextWorkRequired(pindexPrev, pblock, chainparams.GetConsensus()); pblock->nNonce = nHeight; // nNonce now holds the height for Cryptonight variant 4 - pblock->cnHeader.major_version = Params().GetConsensus().GetCryptonoteMajorVersion(); + pblock->cnHeader.major_version = Params().GetConsensus().GetCryptonoteMajorVersion(nHeight); pblock->cnHeader.prev_id = pblock->GetOriginalBlockHash(); pblocktemplate->vTxSigOpsCost[0] = WITNESS_SCALE_FACTOR * GetLegacySigOpCount(*pblock->vtx[0]); diff --git a/src/primitives/block.cpp b/src/primitives/block.cpp index 2e26b35bf..eed0c5981 100644 --- a/src/primitives/block.cpp +++ b/src/primitives/block.cpp @@ -11,6 +11,7 @@ #include #include #include +#include extern "C" void cn_slow_hash(const void *data, size_t length, char *hash, int variant, int prehashed, uint64_t height); extern "C" void cn_fast_hash(const void *data, size_t length, char *hash); @@ -50,7 +51,15 @@ uint256 CBlockHeader::GetPoWHash() const return thash; } cryptonote::blobdata blob = cryptonote::t_serializable_object_to_blob(cnHeader); - cn_slow_hash(blob.data(), blob.size(), BEGIN(thash), cnHeader.major_version - 6, 0, nNonce); + uint32_t height = nNonce; + if (cnHeader.major_version >= RX_BLOCK_VERSION) { + uint64_t seed_height; + seed_height = rx_seedheight(height); + CBlockIndex* pblockindex = chainActive[seed_height]; + rx_slow_hash(height, seed_height, (const char*)(pblockindex->GetBlockHash().begin()), blob.data(), blob.size(), BEGIN(thash), 0, 0); + } else { + cn_slow_hash(blob.data(), blob.size(), BEGIN(thash), cnHeader.major_version - 6, 0, height); + } return thash; } diff --git a/src/primitives/block.h b/src/primitives/block.h index 946c80de3..35731dfb4 100644 --- a/src/primitives/block.h +++ b/src/primitives/block.h @@ -11,6 +11,7 @@ #include #include +#include /** * This header is to store the proof-of-work of cryptonote mining. diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp index bd6bef141..42a7d6eb6 100644 --- a/src/rpc/mining.cpp +++ b/src/rpc/mining.cpp @@ -883,8 +883,7 @@ UniValue getblocktemplate(const JSONRPCRequest& request) cryptonote::block cn_block; // block_header - // const int cn_variant = b.major_version >= 7 ? b.major_version - 6 : 0; - cn_block.major_version = consensusParams.GetCryptonoteMajorVersion(); + cn_block.major_version = consensusParams.GetCryptonoteMajorVersion(pblock->nNonce); cn_block.minor_version = 0; cn_block.timestamp = pblock->GetBlockTime(); // The prev_id is used to store kevacoin block hash, as a proof of work. @@ -954,9 +953,16 @@ UniValue getblocktemplate(const JSONRPCRequest& request) result.push_back(Pair("blocktemplate_blob", hex_template_blob)); result.push_back(Pair("difficulty", (double)difficulty)); result.push_back(Pair("height", (uint64_t)height)); - result.push_back(Pair("prev_hash", pblock->hashPrevBlock.GetHex())); + result.push_back(Pair("prev_hash", pblock->hashPrevBlock.GetHex())); result.push_back(Pair("reserved_offset", (uint64_t)reserved_offset)); + if (cn_block.major_version >= RX_BLOCK_VERSION) { + uint64_t seed_height, next_height; + rx_seedheights(height, &seed_height, &next_height); + result.push_back(Pair("seed_hash", chainActive[seed_height]->GetBlockHash().GetHex())); + result.push_back(Pair("next_seed_hash", chainActive[next_height]->GetBlockHash().GetHex())); + } + // Kevacoin specific entries. Not used for now and may be useful in the future. result.push_back(Pair("rules", aRules)); result.push_back(Pair("vbavailable", vbavailable)); diff --git a/src/test/blockencodings_tests.cpp b/src/test/blockencodings_tests.cpp index 33e8a4fbb..579cdb48a 100644 --- a/src/test/blockencodings_tests.cpp +++ b/src/test/blockencodings_tests.cpp @@ -47,7 +47,7 @@ static CBlock BuildBlockTestCase() { bool mutated; block.hashMerkleRoot = BlockMerkleRoot(block, &mutated); assert(!mutated); - block.cnHeader.major_version = Params().GetConsensus().GetCryptonoteMajorVersion(); + block.cnHeader.major_version = Params().GetConsensus().GetCryptonoteMajorVersion(block.nNonce); block.cnHeader.prev_id = block.GetOriginalBlockHash(); while (!CheckProofOfWork(block.GetPoWHash(), block.nBits, Params().GetConsensus())) ++block.cnHeader.nonce; return block; @@ -294,7 +294,7 @@ BOOST_AUTO_TEST_CASE(EmptyBlockRoundTripTest) bool mutated; block.hashMerkleRoot = BlockMerkleRoot(block, &mutated); assert(!mutated); - block.cnHeader.major_version = Params().GetConsensus().GetCryptonoteMajorVersion(); + block.cnHeader.major_version = Params().GetConsensus().GetCryptonoteMajorVersion(block.nNonce); block.cnHeader.prev_id = block.GetOriginalHash(); while (!CheckProofOfWork(block.GetPoWHash(), block.nBits, Params().GetConsensus())) ++block.cnHeader.nonce; diff --git a/src/test/test_bitcoin.cpp b/src/test/test_bitcoin.cpp index af51b2259..f4518dc89 100644 --- a/src/test/test_bitcoin.cpp +++ b/src/test/test_bitcoin.cpp @@ -154,7 +154,7 @@ TestChain100Setup::CreateAndProcessBlock(const std::vector& IncrementExtraNonce(&block, chainActive.Tip(), extraNonce); } - block.cnHeader.major_version = Params().GetConsensus().GetCryptonoteMajorVersion(); + block.cnHeader.major_version = Params().GetConsensus().GetCryptonoteMajorVersion(block.nNonce); block.cnHeader.prev_id = block.GetOriginalBlockHash(); while (!CheckProofOfWork(block.GetPoWHash(), block.nBits, chainparams.GetConsensus())) ++block.cnHeader.nonce; diff --git a/src/test/validation_block_tests.cpp b/src/test/validation_block_tests.cpp index dbfa6c369..72d540107 100644 --- a/src/test/validation_block_tests.cpp +++ b/src/test/validation_block_tests.cpp @@ -74,7 +74,7 @@ std::shared_ptr FinalizeBlock(std::shared_ptr pblock) { pblock->hashMerkleRoot = BlockMerkleRoot(*pblock); - pblock->cnHeader.major_version = Params().GetConsensus().GetCryptonoteMajorVersion(); + pblock->cnHeader.major_version = Params().GetConsensus().GetCryptonoteMajorVersion(pblock->nNonce); pblock->cnHeader.prev_id = pblock->GetOriginalBlockHash(); while (!CheckProofOfWork(pblock->GetPoWHash(), pblock->nBits, Params().GetConsensus())) { ++(pblock->cnHeader.nonce); diff --git a/src/validation.cpp b/src/validation.cpp index 91232c27d..a8cb08a60 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -3039,7 +3039,8 @@ static bool FindUndoPos(CValidationState &state, int nFile, CDiskBlockPos &pos, static bool CheckBlockHeader(const CBlockHeader& block, CValidationState& state, const Consensus::Params& consensusParams, bool fCheckPOW = true) { - if (block.cnHeader.major_version != consensusParams.GetCryptonoteMajorVersion()) { + uint32_t height = block.nNonce; + if (block.cnHeader.major_version != consensusParams.GetCryptonoteMajorVersion(height)) { return state.Invalid(false, 0, "incorrect-major-version", "Cryptonote major version incorrect"); }