// Copyright (c) 2009-2010 Satoshi Nakamoto
// Copyright (c) 2009-2014 The Bitcoin Core developers
// Copyright (c) 2016-2024 The Hush developers
// Distributed under the GPLv3 software license, see the accompanying
// file COPYING or https://www.gnu.org/licenses/gpl-3.0.en.html
/******************************************************************************
 * Copyright © 2014-2019 The SuperNET Developers.                             *
 *                                                                            *
 * See the AUTHORS, DEVELOPER-AGREEMENT and LICENSE files at                  *
 * the top-level directory of this distribution for the individual copyright  *
 * holder information and the developer policies on copyright and licensing.  *
 *                                                                            *
 * Unless otherwise agreed in a custom licensing agreement, no part of the    *
 * SuperNET software, including this file may be copied, modified, propagated *
 * or distributed except according to the terms contained in the LICENSE file *
 *                                                                            *
 * Removal or modification of this copyright notice is prohibited.            *
 *                                                                            *
 ******************************************************************************/
#include "pubkey.h"
#include "miner.h"
#ifdef ENABLE_MINING
#include "pow/tromp/equi_miner.h"
#endif

#include <atomic>
#include "amount.h"
#include "chainparams.h"
#include "consensus/consensus.h"
#include "consensus/upgrades.h"
#include "consensus/validation.h"
#ifdef ENABLE_MINING
#include "crypto/equihash.h"
#include "RandomX/src/randomx.h"
#endif
#include "hash.h"
#include "key_io.h"
#include "main.h"
#include "metrics.h"
#include "net.h"
#include "pow.h"
#include "primitives/transaction.h"
#include "random.h"
#include "timedata.h"
#include "ui_interface.h"
#include "util.h"
#include "utilmoneystr.h"
#ifdef ENABLE_WALLET
#include "wallet/wallet.h"
#endif
#include "zcash/Address.hpp"
#include "transaction_builder.h"
#include "sodium.h"
#include <boost/thread.hpp>
#include <boost/thread/shared_mutex.hpp>
#include <boost/tuple/tuple.hpp>
#ifdef ENABLE_MINING
#include <functional>
#endif
#include <mutex>

#define rxdebug(format, ...) if(fRandomXDebug) { fprintf(stderr, format, __func__, ## __VA_ARGS__ ); }

using namespace std;

//////////////////////////////////////////////////////////////////////////////
//
// BitcoinMiner
//

//
// Unconfirmed transactions in the memory pool often depend on other
// transactions in the memory pool. When we select transactions from the
// pool, we select by highest priority or fee rate, so we might consider
// transactions that depend on transactions that aren't yet in the block.
// The COrphan class keeps track of these 'temporary orphans' while
// CreateBlock is figuring out which transactions to include.
//
class COrphan
{
public:
    const CTransaction* ptx;
    set<uint256> setDependsOn;
    CFeeRate feeRate;
    double dPriority;

    COrphan(const CTransaction* ptxIn) : ptx(ptxIn), feeRate(0), dPriority(0)
    {
    }
};

uint64_t nLastBlockTx = 0;
uint64_t nLastBlockSize = 0;

// We want to sort transactions by priority and fee rate, so:
typedef boost::tuple<double, CFeeRate, const CTransaction*> TxPriority;
class TxPriorityCompare
{
    bool byFee;

public:
    TxPriorityCompare(bool _byFee) : byFee(_byFee) { }

    bool operator()(const TxPriority& a, const TxPriority& b)
    {
        if (byFee)
        {
            if (a.get<1>() == b.get<1>())
                return a.get<0>() < b.get<0>();
            return a.get<1>() < b.get<1>();
        }
        else
        {
            if (a.get<0>() == b.get<0>())
                return a.get<1>() < b.get<1>();
            return a.get<0>() < b.get<0>();
        }
    }
};

extern int8_t ASSETCHAINS_ADAPTIVEPOW;
extern uint32_t ASSETCHAINS_RANDOMX;
extern uint32_t ASSETCHAINS_MINOPRETURNFEE;
extern bool fRandomXDebug;
extern std::string devtax_scriptpub_for_height(uint32_t nHeight);

void UpdateTime(CBlockHeader* pblock, const Consensus::Params& consensusParams, const CBlockIndex* pindexPrev)
{
    if ( ASSETCHAINS_ADAPTIVEPOW <= 0 )
        pblock->nTime = std::max(pindexPrev->GetMedianTimePast()+1, GetTime());
    else pblock->nTime = std::max((int64_t)(pindexPrev->nTime+1), GetTime());

    // Updating time can change work required on testnet:
    if (ASSETCHAINS_ADAPTIVEPOW > 0 || consensusParams.nPowAllowMinDifficultyBlocksAfterHeight != boost::none)
    {
        pblock->nBits = GetNextWorkRequired(pindexPrev, pblock, consensusParams);
    }
}
#include "hush_defs.h"
#include "cc/CCinclude.h"

extern CCriticalSection cs_metrics;
void vcalc_sha256(char deprecated[(256 >> 3) * 2 + 1],uint8_t hash[256 >> 3],uint8_t *src,int32_t len);

uint32_t Mining_start,Mining_height;
int32_t My_notaryid = -1;
int32_t hush_chosennotary(int32_t *notaryidp,int32_t height,uint8_t *pubkey33,uint32_t timestamp);
int32_t hush_baseid(char *origbase);
int32_t hush_longestchain();
int64_t hush_block_unlocktime(uint32_t nHeight);
uint64_t the_commission(const CBlock *block,int32_t height);
int32_t hush_notaryvin(CMutableTransaction &txNew,uint8_t *notarypub33, void *ptr);
int32_t decode_hex(uint8_t *bytes,int32_t n,char *hex);
int32_t hush_is_notarytx(const CTransaction& tx);
uint64_t hush_notarypay(CMutableTransaction &txNew, std::vector<int8_t> &NotarizationNotaries, uint32_t timestamp, int32_t height, uint8_t *script, int32_t len);
int32_t hush_notaries(uint8_t pubkeys[64][33],int32_t height,uint32_t timestamp);
int32_t hush_getnotarizedheight(uint32_t timestamp,int32_t height, uint8_t *script, int32_t len);
CScript hush_mineropret(int32_t nHeight);
bool hush_appendACscriptpub();

CBlockTemplate* CreateNewBlock(CPubKey _pk,const CScript& _scriptPubKeyIn, int32_t gpucount, bool isStake)
{
    CScript scriptPubKeyIn(_scriptPubKeyIn);

    CPubKey pk;
    if ( _pk.size() != 33 )
    {
        pk = CPubKey();
        std::vector<std::vector<unsigned char>> vAddrs;
        txnouttype txT;
        if ( scriptPubKeyIn.size() > 0 && Solver(scriptPubKeyIn, txT, vAddrs))
        {
            if (txT == TX_PUBKEY)
                pk = CPubKey(vAddrs[0]);
        }
    } else pk = _pk;

    uint32_t blocktime; const CChainParams& chainparams = Params();
    bool fNotarizationBlock = false; std::vector<int8_t> NotarizationNotaries;
    
    // Create new block
    if ( gpucount < 0 )
        gpucount = HUSH_MAXGPUCOUNT;
    std::unique_ptr<CBlockTemplate> pblocktemplate(new CBlockTemplate());
    if(!pblocktemplate.get())
    {
        LogPrintf("%s: pblocktemplate.get() failure\n", __func__);
        return NULL;
    }
    CBlock *pblock = &pblocktemplate->block; // pointer for convenience
     // -regtest only: allow overriding block.nVersion with
    // -blockversion=N to test forking scenarios
    if (Params().MineBlocksOnDemand())
        pblock->nVersion = GetArg("-blockversion", pblock->nVersion);

    // Add dummy coinbase tx as first transaction
    pblock->vtx.push_back(CTransaction());
    pblocktemplate->vTxFees.push_back(-1); // updated at end
    pblocktemplate->vTxSigOps.push_back(-1); // updated at end

    // Largest block you're willing to create:
    unsigned int nBlockMaxSize = GetArg("-blockmaxsize", MAX_BLOCK_SIZE(1)); // MAX_BLOCK_SIZE(chainActive.LastTip()->GetHeight()+1));
    // Limit to betweeen 1K and MAX_BLOCK_SIZE-1K for sanity:
    nBlockMaxSize = std::max((unsigned int)1000, std::min((unsigned int)(MAX_BLOCK_SIZE(1)-1000), nBlockMaxSize));
    //nBlockMaxSize = std::max((unsigned int)1000, std::min((unsigned int)(MAX_BLOCK_SIZE(chainActive.LastTip()->GetHeight()+1)-1000), nBlockMaxSize));

    // How much of the block should be dedicated to high-priority transactions,
    // included regardless of the fees they pay
    const unsigned int nBlockPrioritySize = std::min( nBlockMaxSize, (unsigned int) GetArg("-blockprioritysize", DEFAULT_BLOCK_PRIORITY_SIZE) );
    // nBlockPrioritySize = std::min(nBlockMaxSize, nBlockPrioritySize);

    // Minimum block size you want to create; block will be filled with free transactions
    // until there are no more or the block reaches this size:
    const unsigned int nBlockMinSize = std::min(nBlockMaxSize,  (unsigned int) GetArg("-blockminsize", DEFAULT_BLOCK_MIN_SIZE));
    // nBlockMinSize = std::min(nBlockMaxSize, nBlockMinSize);


    // Collect memory pool transactions into the block
    CAmount nFees = 0;

    uint256 cbHash;
    
    boost::this_thread::interruption_point(); // exit thread before entering locks. 
    
    CBlockIndex* pindexPrev = 0;
    {
        // this should stop create block ever exiting until it has returned something. 
        boost::this_thread::disable_interruption();
        ENTER_CRITICAL_SECTION(cs_main);
        ENTER_CRITICAL_SECTION(mempool.cs);
        pindexPrev = chainActive.LastTip();
        const int nHeight = pindexPrev->GetHeight() + 1;
        const Consensus::Params &consensusParams = chainparams.GetConsensus();
        uint32_t consensusBranchId = CurrentEpochBranchId(nHeight, consensusParams);
        // Sapling NU is always active for height>=1
        const bool sapling = nHeight>=1 ? true : false; //NetworkUpgradeActive(nHeight, consensusParams, Consensus::UPGRADE_SAPLING);

        const int64_t nMedianTimePast = pindexPrev->GetMedianTimePast();
        uint32_t proposedTime = GetTime();


        if (proposedTime == nMedianTimePast)
        {
            // too fast or stuck, this addresses the too fast issue, while moving
            // forward as quickly as possible
            for (int i = 0; i < 100; i++)
            {
                proposedTime = GetTime();
                if (proposedTime == nMedianTimePast)
                    MilliSleep(10);
                else
                    break; // time advanced past the median; stop waiting
            }
        }
        pblock->nTime = GetTime();
        // Now we have the block time + height, we can get the active notaries.
        int8_t numSN = 0; uint8_t notarypubkeys[64][33] = {0};
        if ( ASSETCHAINS_NOTARY_PAY[0] != 0 )
        {
            // Only use speical miner for notary pay chains.
            numSN = hush_notaries(notarypubkeys, nHeight, pblock->nTime);
        }

        CCoinsViewCache view(pcoinsTip);
        uint32_t expired; uint64_t commission;
        
        SaplingMerkleTree sapling_tree;
        assert(view.GetSaplingAnchorAt(view.GetBestAnchor(SAPLING), sapling_tree));

        // Priority order to process transactions
        list<COrphan> vOrphan; // list memory doesn't move
        map<uint256, vector<COrphan*> > mapDependers;
        bool fPrintPriority = GetBoolArg("-printpriority", false);

        // This vector will be sorted into a priority queue:
        vector<TxPriority> vecPriority;
        vecPriority.reserve(mempool.mapTx.size() + 1);

        // now add transactions from the mempool
        int32_t Notarizations = 0; uint64_t txvalue;
        uint32_t large_zins  = 0;                  // number of ztxs with large number of inputs in block
        uint32_t large_zouts = 0;                  // number of ztxs with large number of outputs in block
        const uint32_t LARGE_ZINS_MAX        = 1;  // max ztxs with large zins per block
        const uint32_t LARGE_ZOUTS_MAX       = 1;  // max ztxs with large zouts per block
        const uint32_t LARGE_ZINS_THRESHOLD  = 50; // min number of zins to be considered large
        const uint32_t LARGE_ZOUTS_THRESHOLD = 10; // min number of zouts to be considered large
        for (CTxMemPool::indexed_transaction_set::iterator mi = mempool.mapTx.begin();
             mi != mempool.mapTx.end(); ++mi) {
            const CTransaction& tx = mi->GetTx();

            int64_t nLockTimeCutoff = (STANDARD_LOCKTIME_VERIFY_FLAGS & LOCKTIME_MEDIAN_TIME_PAST)
            ? nMedianTimePast
            : pblock->GetBlockTime();

            if (tx.IsCoinBase() || !IsFinalTx(tx, nHeight, nLockTimeCutoff) || IsExpiredTx(tx, nHeight))
            {
                LogPrint("mempool", "%s: coinbase.%d finaltx.%d expired.%d\n",__func__, tx.IsCoinBase(),IsFinalTx(tx, nHeight, nLockTimeCutoff),IsExpiredTx(tx, nHeight));
                continue;
            }
            txvalue = tx.GetValueOut();
            if ( HUSH_VALUETOOBIG(txvalue) != 0 )
                continue;

            COrphan* porphan = NULL;
            double dPriority = 0;
            CAmount nTotalIn = 0;
            bool fMissingInputs = false;
            bool fNotarization = false;
            std::vector<int8_t> TMP_NotarizationNotaries;

            {
                TMP_NotarizationNotaries.clear();
                bool fToCryptoAddress = false;
                if ( numSN != 0 && notarypubkeys[0][0] != 0 && hush_is_notarytx(tx) == 1 )
                    fToCryptoAddress = true;

                BOOST_FOREACH(const CTxIn& txin, tx.vin)
                {
                    // Read prev transaction
                    if (!view.HaveCoins(txin.prevout.hash))
                    {
                        // This should never happen; all transactions in the memory
                        // pool should connect to either transactions in the chain
                        // or other transactions in the memory pool.
                        if (!mempool.mapTx.count(txin.prevout.hash))
                        {
                            LogPrintf("ERROR: mempool transaction missing input\n");
                            // if (fDebug) assert("mempool transaction missing input" == 0);
                            fMissingInputs = true;
                            if (porphan)
                                vOrphan.pop_back();
                            break;
                        }

                        // Has to wait for dependencies
                        if (!porphan)
                        {
                            // Use list for automatic deletion
                            vOrphan.push_back(COrphan(&tx));
                            porphan = &vOrphan.back();
                        }
                        mapDependers[txin.prevout.hash].push_back(porphan);
                        porphan->setDependsOn.insert(txin.prevout.hash);
                        nTotalIn += mempool.mapTx.find(txin.prevout.hash)->GetTx().vout[txin.prevout.n].nValue;
                        continue;
                    }
                    const CCoins* coins = view.AccessCoins(txin.prevout.hash);
                    assert(coins);

                    CAmount nValueIn = coins->vout[txin.prevout.n].nValue;
                    nTotalIn += nValueIn;

                    int nConf = nHeight - coins->nHeight;
                    
                    uint8_t *script; int32_t scriptlen; uint256 hash; CTransaction tx1;
                    // loop over notaries array and extract index of signers.
                    if ( fToCryptoAddress && GetTransaction(txin.prevout.hash,tx1,hash,false) )
                    {
                        for (int8_t i = 0; i < numSN; i++) 
                        {
                            script = (uint8_t *)&tx1.vout[txin.prevout.n].scriptPubKey[0];
                            scriptlen = (int32_t)tx1.vout[txin.prevout.n].scriptPubKey.size();
                            if ( scriptlen == 35 && script[0] == 33 && script[34] == OP_CHECKSIG && memcmp(script+1,notarypubkeys[i],33) == 0 )
                            {
                                // We can add the index of each notary to vector, and clear it if this notarization is not valid later on.
                                TMP_NotarizationNotaries.push_back(i);                          
                            }
                        }
                    }
                    dPriority += (double)nValueIn * nConf;
                }
                if ( numSN != 0 && notarypubkeys[0][0] != 0 && TMP_NotarizationNotaries.size() >= numSN / 5 )
                {
                    // check a notary didnt sign twice (this would be an invalid notarization later on and cause problems)
                    std::set<int> checkdupes( TMP_NotarizationNotaries.begin(), TMP_NotarizationNotaries.end() );
                    if ( checkdupes.size() != TMP_NotarizationNotaries.size() )
                    {
                    } else fNotarization = true;
                }
                nTotalIn += tx.GetShieldedValueIn();
            }

            if (fMissingInputs) continue;

            // Priority is sum(valuein * age) / modified_txsize
            unsigned int nTxSize = ::GetSerializeSize(tx, SER_NETWORK, PROTOCOL_VERSION);
            dPriority = tx.ComputePriority(dPriority, nTxSize);

            uint256 hash = tx.GetHash();
            mempool.ApplyDeltas(hash, dPriority, nTotalIn);

            CFeeRate feeRate(nTotalIn-tx.GetValueOut(), nTxSize);

            if ( fNotarization ) {
                // Special miner for notary pay chains. Can only enter this if numSN/notarypubkeys is set higher up.
                if ( tx.vout.size() == 2 && tx.vout[1].nValue == 0 )
                {
                    // Get the OP_RETURN for the notarization
                    uint8_t *script = (uint8_t *)&tx.vout[1].scriptPubKey[0];
                    int32_t scriptlen = (int32_t)tx.vout[1].scriptPubKey.size();
                    if ( script[0] == OP_RETURN )
                    {
                        Notarizations++;
                        if ( Notarizations > 1 ) 
                        {
                            LogPrint("mempool", "%s: skipping notarization.%d\n",__func__, Notarizations);
                            // Any attempted notarization needs to be in its own block!
                            continue;
                        }
                        int32_t notarizedheight = hush_getnotarizedheight(pblock->nTime, nHeight, script, scriptlen);
                        if ( notarizedheight != 0 )
                        {
                            // this is the first one we see, add it to the block as TX1 
                            NotarizationNotaries = TMP_NotarizationNotaries;
                            dPriority = 1e16;
                            fNotarizationBlock = true;
                        }
                    }
                }
            } else if ( dPriority == 1e16 ) {
                dPriority -= 10;
                // make sure notarization is tx[1] in block. 
            }
            if (porphan) {
                porphan->dPriority = dPriority;
                porphan->feeRate = feeRate;
            } else {
                vecPriority.push_back(TxPriority(dPriority, feeRate, &(mi->GetTx())));
            }
        }

        // Collect transactions into block
        int64_t interest;
        uint64_t nBlockSize = 1000;
        uint64_t nBlockTx   = 0;
        int nBlockSigOps    = 100;
        bool fSortedByFee   = (nBlockPrioritySize <= 0);

        TxPriorityCompare comparer(fSortedByFee);
        std::make_heap(vecPriority.begin(), vecPriority.end(), comparer);


        while (!vecPriority.empty()) {
            // Take highest priority transaction off the priority queue:
            double dPriority = vecPriority.front().get<0>();
            CFeeRate feeRate = vecPriority.front().get<1>();
            const CTransaction& tx = *(vecPriority.front().get<2>());


            std::pop_heap(vecPriority.begin(), vecPriority.end(), comparer);
            vecPriority.pop_back();

            if(tx.vShieldedSpend.size() >= LARGE_ZINS_THRESHOLD && large_zins >= LARGE_ZINS_MAX) {
                LogPrintf("%s: skipping ztx %s with %d zins because there are already %d ztxs with large zins\n",
                    __func__, tx.GetHash().ToString().c_str(), tx.vShieldedSpend.size(), LARGE_ZINS_MAX);
                continue;
            }

            if(tx.vShieldedOutput.size() >= LARGE_ZOUTS_THRESHOLD && large_zouts >= LARGE_ZOUTS_MAX) {
                LogPrintf("%s: skipping ztx %s with %d zouts because there are already %d ztxs with large zouts\n",
                    __func__, tx.GetHash().ToString().c_str(), tx.vShieldedOutput.size(), LARGE_ZOUTS_MAX);
                continue;
            }

            // Size limits
            unsigned int nTxSize = ::GetSerializeSize(tx, SER_NETWORK, PROTOCOL_VERSION);


            if (nBlockSize + nTxSize >= nBlockMaxSize-512) // room for extra autotx
            {
                LogPrint("mempool", "%s: nBlockSize %d + %d nTxSize >= %d nBlockMaxSize\n",__func__, (int32_t)nBlockSize,(int32_t)nTxSize,(int32_t)nBlockMaxSize);
                continue;
            }

            // Legacy limits on sigOps:
            unsigned int nTxSigOps = GetLegacySigOpCount(tx);
            if (nBlockSigOps + nTxSigOps >= MAX_BLOCK_SIGOPS-1)
            {
                continue;
            }


            // Skip free transactions if we're past the minimum block size:
            const uint256& hash = tx.GetHash();
            double dPriorityDelta = 0;
            CAmount nFeeDelta = 0;
            mempool.ApplyDeltas(hash, dPriorityDelta, nFeeDelta);
            if (fSortedByFee && (dPriorityDelta <= 0) && (nFeeDelta <= 0) && (feeRate < ::minRelayTxFee) && (nBlockSize + nTxSize >= nBlockMinSize))
            {
                LogPrint("mempool", "%s: fee rate skip\n", __func__);
                continue;
            }
            // Prioritize by fee once past the priority size or we run out of high-priority transactions
            if (!fSortedByFee &&
                ((nBlockSize + nTxSize >= nBlockPrioritySize) || !AllowFree(dPriority)))
            {
                fSortedByFee = true;
                comparer = TxPriorityCompare(fSortedByFee);
                std::make_heap(vecPriority.begin(), vecPriority.end(), comparer);
            }

            if (!view.HaveInputs(tx))
            {
                continue;
            }
            CAmount nTxFees = view.GetValueIn(chainActive.LastTip()->GetHeight(),&interest,tx,chainActive.LastTip()->nTime)-tx.GetValueOut();

            // Opret spam limits
            CAmount opretMinFee = 1 * COIN;

            // Set with -ac_minopreturnfee
            if (ASSETCHAINS_MINOPRETURNFEE > 0) {
               opretMinFee = ASSETCHAINS_MINOPRETURNFEE;
            }

            {
                bool fSpamTx = false;
                unsigned int nTxSize = ::GetSerializeSize(tx, SER_NETWORK, PROTOCOL_VERSION);
                unsigned int nTxOpretSize = 0;

                // calc total oprets size
                BOOST_FOREACH(const CTxOut& txout, tx.vout) {
                    if (txout.scriptPubKey.IsOpReturn()) {
                        CScript::const_iterator it = txout.scriptPubKey.begin() + 1;
                        opcodetype op;
                        std::vector<uint8_t> opretData;
                        if (txout.scriptPubKey.GetOp(it, op, opretData)) {
                            nTxOpretSize += opretData.size();
                        }
                    }
                }

                if (nTxOpretSize > 0 && nTxFees < opretMinFee) {
                    fSpamTx = true;
                    std::cerr << __func__ << ": " << tx.GetHash().ToString() << " nTxSize=" << nTxSize << " nTxOpretSize=" << nTxOpretSize << " feeRate=" << feeRate.ToString() << " opretMinFee=" << opretMinFee << " nTxFees=" << nTxFees <<" fSpamTx=" << fSpamTx << std::endl;
                    continue;
                }
            }

            nTxSigOps += GetP2SHSigOpCount(tx, view);
            if (nBlockSigOps + nTxSigOps >= MAX_BLOCK_SIGOPS-1)
            {
                continue;
            }
            // Note that flags: we don't want to set mempool/IsStandard()
            // policy here, but we still have to ensure that the block we
            // create only contains transactions that are valid in new blocks.
            CValidationState state;
            PrecomputedTransactionData txdata(tx);
            if (!ContextualCheckInputs(tx, state, view, true, MANDATORY_SCRIPT_VERIFY_FLAGS, true, txdata, Params().GetConsensus(), consensusBranchId))
            {
                LogPrint("mempool", "%s: ContextualCheckInputs failure\n",__func__);
                continue;
            }
            UpdateCoins(tx, view, nHeight);

            BOOST_FOREACH(const OutputDescription &outDescription, tx.vShieldedOutput) {
                sapling_tree.append(outDescription.cm);
            }

            // Added
            pblock->vtx.push_back(tx);
            pblocktemplate->vTxFees.push_back(nTxFees);
            pblocktemplate->vTxSigOps.push_back(nTxSigOps);
            nBlockSize += nTxSize;
            ++nBlockTx;
            nBlockSigOps += nTxSigOps;
            nFees += nTxFees;

            if(tx.vShieldedOutput.size() >= LARGE_ZOUTS_THRESHOLD) {
                large_zouts++;
                LogPrintf("%s: txid=%s has large zouts=%d (%d large zouts in block)\n", __func__, tx.GetHash().ToString().c_str(),
                    tx.vShieldedOutput.size(), large_zouts );
            }

            if(tx.vShieldedSpend.size() >= LARGE_ZINS_THRESHOLD) {
                large_zins++;
                LogPrintf("%s: txid=%s has large zins=%d (%d large zins in block)\n", __func__, tx.GetHash().ToString().c_str(),
                    tx.vShieldedSpend.size(), large_zins );
            }

            if (fPrintPriority)
            {
                LogPrintf("priority %.1f fee %s txid %s\n",dPriority, feeRate.ToString(), tx.GetHash().ToString());
            }

            // Add transactions that depend on this one to the priority queue
            if (mapDependers.count(hash))
            {
                BOOST_FOREACH(COrphan* porphan, mapDependers[hash])
                {
                    if (!porphan->setDependsOn.empty())
                    {
                        porphan->setDependsOn.erase(hash);
                        if (porphan->setDependsOn.empty())
                        {
                            vecPriority.push_back(TxPriority(porphan->dPriority, porphan->feeRate, porphan->ptx));
                            std::push_heap(vecPriority.begin(), vecPriority.end(), comparer);
                        }
                    }
                }
            }
        }

        nLastBlockTx = nBlockTx;
        nLastBlockSize = nBlockSize;

        if ( ASSETCHAINS_ADAPTIVEPOW <= 0 )
            blocktime = 1 + std::max(pindexPrev->GetMedianTimePast()+1, GetTime());
        else blocktime = 1 + std::max((int64_t)(pindexPrev->nTime+1), GetTime());
        //pblock->nTime = blocktime + 1;
        pblock->nBits = GetNextWorkRequired(pindexPrev, pblock, Params().GetConsensus());

        LogPrintf("CreateNewBlock(): total size %u blocktime.%u nBits.%08x\n", nBlockSize,blocktime,pblock->nBits);
        
        // Create coinbase tx
        CMutableTransaction txNew = CreateNewContextualCMutableTransaction(consensusParams, nHeight);
        txNew.vin.resize(1);
        txNew.vin[0].prevout.SetNull();
        txNew.vin[0].scriptSig = CScript() << nHeight << OP_0;

        txNew.vout.resize(1);
        txNew.vout[0].scriptPubKey = scriptPubKeyIn;
        txNew.vout[0].nValue = GetBlockSubsidy(nHeight,consensusParams) + nFees;
        txNew.nExpiryHeight = 0;
        if ( ASSETCHAINS_ADAPTIVEPOW <= 0 )
            txNew.nLockTime = std::max(pindexPrev->GetMedianTimePast()+1, GetTime());
        else txNew.nLockTime = std::max((int64_t)(pindexPrev->nTime+1), GetTime());


        pblock->vtx[0] = txNew;

        // Create a local variable instead of modifying the global ASSETCHAINS_SCRIPTPUB
        auto assetchains_scriptpub = devtax_scriptpub_for_height(nHeight);

        if ( nHeight > 1 && (ASSETCHAINS_OVERRIDE_PUBKEY33[0] != 0 || assetchains_scriptpub.size() > 1) && (ASSETCHAINS_COMMISSION != 0 || ASSETCHAINS_FOUNDERS_REWARD != 0)  && (commission= the_commission((CBlock*)&pblocktemplate->block,(int32_t)nHeight)) != 0 )
        {
            int32_t i; uint8_t *ptr;
            txNew.vout.resize(2);
            txNew.vout[1].nValue = commission;
            if ( assetchains_scriptpub.size() > 1 )
            {
                static bool didinit = false;
                if ( !didinit && nHeight > HUSH_EARLYTXID_HEIGHT && HUSH_EARLYTXID != zeroid && hush_appendACscriptpub() )
                {
                    LogPrintf("appended ccopreturn to assetchains_scriptpub.%s\n", assetchains_scriptpub.c_str());
                    didinit = true;
                }
                //txNew.vout[1].scriptPubKey = CScript() << ParseHex();
                int32_t len = strlen(assetchains_scriptpub.c_str());
                len >>= 1;
                txNew.vout[1].scriptPubKey.resize(len);
                ptr = (uint8_t *)&txNew.vout[1].scriptPubKey[0];
                decode_hex(ptr,len,(char *)assetchains_scriptpub.c_str());
            } else {
                txNew.vout[1].scriptPubKey.resize(35);
                ptr = (uint8_t *)&txNew.vout[1].scriptPubKey[0];
                ptr[0] = 33;
                for (i=0; i<33; i++)
                {
                    ptr[i+1] = ASSETCHAINS_OVERRIDE_PUBKEY33[i];
                }
                ptr[34] = OP_CHECKSIG;
            }
        } else if ( (uint64_t)(txNew.vout[0].nValue) >= ASSETCHAINS_TIMELOCKGTE) {
			LogPrintf("timelocked chains not supported in this code!\n");
			LEAVE_CRITICAL_SECTION(cs_main);
			LEAVE_CRITICAL_SECTION(mempool.cs);
			return(0);
        } else if ( fNotarizationBlock && ASSETCHAINS_NOTARY_PAY[0] != 0 && pblock->vtx[1].vout.size() == 2 && pblock->vtx[1].vout[1].nValue == 0 ) {
            // Get the OP_RETURN for the notarization
            uint8_t *script = (uint8_t *)&pblock->vtx[1].vout[1].scriptPubKey[0];
            int32_t scriptlen = (int32_t)pblock->vtx[1].vout[1].scriptPubKey.size();
            if ( script[0] == OP_RETURN )
            {
                uint64_t totalsats = hush_notarypay(txNew, NotarizationNotaries, pblock->nTime, nHeight, script, scriptlen);
                if ( totalsats == 0 )
                {
                    LogPrintf("Could not create notary payment, trying again.\n");
                    // Release unconditionally to match the unconditional ENTER above. The old
                    // `if(!isStake)` guard leaked cs_main/mempool.cs on the isStake path (this
                    // still return(0)s), while the success and timelock paths always release.
                    LEAVE_CRITICAL_SECTION(cs_main);
                    LEAVE_CRITICAL_SECTION(mempool.cs);
                    return(0);
                }
            } else LogPrintf("vout 2 of notarization is not OP_RETURN scriptlen.%i\n", scriptlen);
        }
        if ( ASSETCHAINS_CBOPRET != 0 )
        {
            int32_t numv = (int32_t)txNew.vout.size();
            txNew.vout.resize(numv+1);
            txNew.vout[numv].nValue = 0;
            txNew.vout[numv].scriptPubKey = hush_mineropret(nHeight);
        }
        pblock->vtx[0] = txNew;
        pblocktemplate->vTxFees[0] = -nFees;

        // if not staking, setup nonce, otherwise, leave it alone
        if (!isStake || ASSETCHAINS_LWMAPOS == 0)
        {
            // Randomize nonce
            arith_uint256 nonce = UintToArith256(GetRandHash());

            // Clear the top 16 and bottom 16 or 24 bits (for local use as thread flags and counters)
            nonce <<= ASSETCHAINS_NONCESHIFT[ASSETCHAINS_ALGO];
            nonce >>= 16;
            pblock->nNonce = ArithToUint256(nonce);
        }

        // Fill in header
        pblock->hashPrevBlock  = pindexPrev->GetBlockHash();
        pblock->hashFinalSaplingRoot   = sapling_tree.root();

        // all PoS chains need this data in the block at all times
        if ( ASSETCHAINS_LWMAPOS || HUSH_MININGTHREADS > 0 )
        {
            UpdateTime(pblock, Params().GetConsensus(), pindexPrev);
            pblock->nBits = GetNextWorkRequired(pindexPrev, pblock, Params().GetConsensus());
        }
        pblock->nSolution.clear();
        pblocktemplate->vTxSigOps[0] = GetLegacySigOpCount(pblock->vtx[0]);

        if ( ASSETCHAINS_CC == 0 && pindexPrev != 0 && (IS_HUSH_NOTARY == 0 || My_notaryid < 0) )
        {
            CValidationState state;
            if ( !TestBlockValidity(state, *pblock, pindexPrev, false, false)) // invokes CC checks
            {
                // Release unconditionally to match the unconditional ENTER above. The old
                // `if(!isStake)` guard leaked cs_main/mempool.cs on the isStake path (this
                // still return(0)s), while the success and timelock paths always release.
                LEAVE_CRITICAL_SECTION(cs_main);
                LEAVE_CRITICAL_SECTION(mempool.cs);
                LogPrintf("%s: TestBlockValidity failed!\n", __func__);
                //throw std::runtime_error("CreateNewBlock(): TestBlockValidity failed"); // crashes the node, moved to GetBlockTemplate and issue return.
                return(0);
            }
        }
    }

    LEAVE_CRITICAL_SECTION(cs_main);
    LEAVE_CRITICAL_SECTION(mempool.cs);

    return pblocktemplate.release();
}

// Internal miner

#ifdef ENABLE_MINING

void IncrementExtraNonce(CBlock* pblock, CBlockIndex* pindexPrev, unsigned int& nExtraNonce)
{
    // Update nExtraNonce
    static uint256 hashPrevBlock;
    if (hashPrevBlock != pblock->hashPrevBlock)
    {
        nExtraNonce = 0;
        hashPrevBlock = pblock->hashPrevBlock;
    }
    ++nExtraNonce;
    const unsigned int nHeight = pindexPrev->GetHeight()+1; // Height first in coinbase required for block.version=2
    CMutableTransaction txCoinbase(pblock->vtx[0]);
    txCoinbase.vin[0].scriptSig = (CScript() << nHeight << CScriptNum(nExtraNonce)) + COINBASE_FLAGS;
    assert(txCoinbase.vin[0].scriptSig.size() <= 100);

    pblock->vtx[0] = txCoinbase;
    pblock->hashMerkleRoot = pblock->BuildMerkleTree();
}

#ifdef ENABLE_WALLET

// Internal miner
CBlockTemplate* CreateNewBlockWithKey(CReserveKey& reservekey, int32_t nHeight, int32_t gpucount, bool isStake)
{
    CPubKey pubkey; CScript scriptPubKey; uint8_t *script,*ptr; int32_t i,len;

    // Create a local variable instead of modifying the global assetchains_scriptpub
    auto assetchains_scriptpub = devtax_scriptpub_for_height(nHeight);
    if ( nHeight == 1 && ASSETCHAINS_COMMISSION != 0 && assetchains_scriptpub[assetchains_scriptpub.back()] != 49 && assetchains_scriptpub[assetchains_scriptpub.back()-1] != 51 )
    {
        if ( ASSETCHAINS_OVERRIDE_PUBKEY33[0] != 0 )
        {
            pubkey = ParseHex(ASSETCHAINS_OVERRIDE_PUBKEY);
            scriptPubKey = CScript() << ParseHex(HexStr(pubkey)) << OP_CHECKSIG;
        } else {
            len = strlen(assetchains_scriptpub.c_str());
            len >>= 1;
            scriptPubKey.resize(len);
            ptr = (uint8_t *)&scriptPubKey[0];
            decode_hex(ptr,len,(char *)assetchains_scriptpub.c_str());
        }
    } else if ( USE_EXTERNAL_PUBKEY != 0 ) {
        pubkey = ParseHex(NOTARY_PUBKEY);
        scriptPubKey = CScript() << ParseHex(HexStr(pubkey)) << OP_CHECKSIG;
    } else {
        {
            // Support mining with -disablewallet and minetolocalwallet=0
            if (!GetBoolArg("-disablewallet", false)) {
                // wallet enabled
                if (!reservekey.GetReservedKey(pubkey))
                    return NULL;
                scriptPubKey.clear();
                scriptPubKey = CScript() << ToByteVector(pubkey) << OP_CHECKSIG;
            } else {
                // wallet disabled
                CTxDestination dest = DecodeDestination(GetArg("-mineraddress", ""));
                char destaddr[65];
                if (IsValidDestination(dest)) {
                    // CKeyID keyID = boost::get<CKeyID>(dest);
                    // scriptPubKey = CScript() << OP_DUP << OP_HASH160 << ToByteVector(keyID) << OP_EQUALVERIFY << OP_CHECKSIG;
                    scriptPubKey = GetScriptForDestination(dest);
                    Getscriptaddress(destaddr,scriptPubKey);
                    LogPrintf("%s: wallet disabled with mineraddress=%s\n", __func__, destaddr);
                } else {
                    return NULL;
                }
             }
         }
     }
    return CreateNewBlock(pubkey, scriptPubKey, gpucount, isStake);
}

void hush_sendmessage(int32_t minpeers,int32_t maxpeers,const char *message,std::vector<uint8_t> payload)
{
    int32_t numsent = 0;
    LOCK(cs_vNodes);
    BOOST_FOREACH(CNode* pnode, vNodes)
    {
        if ( pnode->hSocket == INVALID_SOCKET )
            continue;
        if ( numsent < minpeers || (rand() % 10) == 0 )
        {
            pnode->PushMessage(message,payload);
            if ( numsent++ > maxpeers )
                break;
        }
    }
}

static bool ProcessBlockFound(CBlock* pblock, CWallet& wallet, CReserveKey& reservekey)
#else
static bool ProcessBlockFound(CBlock* pblock)
#endif // ENABLE_WALLET
{
    LogPrintf("%s\n", pblock->ToString());
    LogPrintf("generated %s height.%d\n", FormatMoney(pblock->vtx[0].vout[0].nValue),chainActive.LastTip()->GetHeight()+1);

    // Found a solution
    {
        LOCK(cs_main);
        if (pblock->hashPrevBlock != chainActive.LastTip()->GetBlockHash())
        {
            return error("HushMiner: generated block is stale");
        }
    }

    // Inform about the new block
    GetMainSignals().BlockFound(pblock->GetHash());

#ifdef ENABLE_WALLET
    // Remove key from key pool
    if ( IS_HUSH_NOTARY == 0 )
    {
        if (GetArg("-mineraddress", "").empty()) {
            // Remove key from key pool
            reservekey.KeepKey();
        }
    }
#endif

    // Process this block the same as if we had received it from another node
    CValidationState state;
    if (!ProcessNewBlock(1,chainActive.LastTip()->GetHeight()+1,state, NULL, pblock, true, NULL))
        return error("HushMiner: ProcessNewBlock, block not accepted");

    TrackMinedBlock(pblock->GetHash());
    return true;
}

int32_t hush_baseid(char *origbase);
int32_t hush_eligiblenotary(uint8_t pubkeys[66][33],int32_t *mids,uint32_t *blocktimes,int32_t *nonzpkeysp,int32_t height);
arith_uint256 hush_PoWtarget(int32_t *percPoSp,arith_uint256 target,int32_t height,int32_t goalperc);
int32_t FOUND_BLOCK,HUSH_MAYBEMINED;
extern int32_t HUSH_LASTMINED,HUSH_INSYNC;
int32_t roundrobin_delay;
arith_uint256 HASHTarget,HASHTarget_POW;

// wait for peers to connect
void waitForPeers(const CChainParams &chainparams)
{
    if (chainparams.MiningRequiresPeers())
    {
        bool fvNodesEmpty;
        {
            boost::this_thread::interruption_point();
            LOCK(cs_vNodes);
            fvNodesEmpty = vNodes.empty();
        }
        if (fvNodesEmpty || IsNotInSync())
        {
            int loops = 0, blockDiff = 0, newDiff = 0;

            do {
                if (fvNodesEmpty)
                {
                    MilliSleep(1000 + rand() % 4000);
                    boost::this_thread::interruption_point();
                    LOCK(cs_vNodes);
                    fvNodesEmpty = vNodes.empty();
                    loops = 0;
                    blockDiff = 0;
                }
                if ((newDiff = IsNotInSync()) > 1)
                {
                    if (blockDiff != newDiff)
                    {
                        blockDiff = newDiff;
                    }
                    else
                    {
                        if (++loops <= 10)
                        {
                            MilliSleep(1000);
                        }
                        else break;
                    }
                }
            } while (fvNodesEmpty || IsNotInSync());
            MilliSleep(100 + rand() % 400);
        }
    }
}

#ifdef ENABLE_WALLET
CBlockIndex *get_chainactive(int32_t height)
{
    if ( chainActive.LastTip() != 0 )
    {
        if ( height <= chainActive.LastTip()->GetHeight() )
        {
            LOCK(cs_main);
            return(chainActive[height]);
        }
    }
    return(0);
}


#endif

int32_t gotinvalid;

class RandomXSolverCanceledException : public std::exception
{
    virtual const char* what() const throw() {
        return "RandomX solver was canceled";
    }
};

enum RandomXSolverCancelCheck
{
    Reason1,
    Reason2
};

int GetRandomXInterval();
int GetRandomXBlockLag();

#ifdef _WIN32
#include <windows.h>
static void LogProcessMemory(const char* label) {
    // Use K32GetProcessMemoryInfo from kernel32.dll (available on Win7+)
    // to avoid linking psapi.lib
    typedef struct {
        DWORD  cb;
        DWORD  PageFaultCount;
        SIZE_T PeakWorkingSetSize;
        SIZE_T WorkingSetSize;
        SIZE_T QuotaPeakPagedPoolUsage;
        SIZE_T QuotaPagedPoolUsage;
        SIZE_T QuotaPeakNonPagedPoolUsage;
        SIZE_T QuotaNonPagedPoolUsage;
        SIZE_T PagefileUsage;
        SIZE_T PeakPagefileUsage;
        SIZE_T PrivateUsage;
    } PMC_EX;
    typedef BOOL (WINAPI *PFN)(HANDLE, PMC_EX*, DWORD);
    static PFN pfn = (PFN)GetProcAddress(GetModuleHandleA("kernel32.dll"), "K32GetProcessMemoryInfo");
    if (pfn) {
        PMC_EX pmc = {};
        pmc.cb = sizeof(pmc);
        if (pfn(GetCurrentProcess(), &pmc, sizeof(pmc))) {
            LogPrint("randomx", "MemDiag [%s]: WorkingSet=%.1fMB, PrivateUsage=%.1fMB, PagefileUsage=%.1fMB\n",
                      label,
                      pmc.WorkingSetSize / (1024.0 * 1024.0),
                      pmc.PrivateUsage / (1024.0 * 1024.0),
                      pmc.PagefileUsage / (1024.0 * 1024.0));
        }
    }
}
#else
static void LogProcessMemory(const char* label) {
    // Linux: read /proc/self/status
    FILE *f = fopen("/proc/self/status", "r");
    if (f) {
        char line[256];
        while (fgets(line, sizeof(line), f)) {
            if (strncmp(line, "VmRSS:", 6) == 0 || strncmp(line, "VmSize:", 7) == 0) {
                // Remove newline
                line[strlen(line)-1] = '\0';
                LogPrint("randomx", "MemDiag [%s]: %s\n", label, line);
            }
        }
        fclose(f);
    }
}
#endif

// 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;
        LogPrint("randomx", "RandomXDatasetManager: flags=0x%x (JIT=%d, HARD_AES=%d, FULL_MEM=%d, LARGE_PAGES=%d)\n",
                  (int)flags,
                  !!(flags & RANDOMX_FLAG_JIT), !!(flags & RANDOMX_FLAG_HARD_AES),
                  !!(flags & RANDOMX_FLAG_FULL_MEM), !!(flags & RANDOMX_FLAG_LARGE_PAGES));

        LogProcessMemory("before cache alloc");

        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;
                }
            }
        }
        LogProcessMemory("after cache alloc");

        // Try to allocate dataset with large pages first for better performance
        dataset = randomx_alloc_dataset(flags | RANDOMX_FLAG_LARGE_PAGES);
        if (dataset == nullptr) {
            LogPrintf("RandomXDatasetManager: dataset alloc failed with large pages, trying without...\n");
            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;
        LogProcessMemory("after dataset alloc");
        // Log the actual memory addresses to help diagnose sharing issues  
        uint8_t *datasetMemory = (uint8_t*)randomx_get_dataset_memory(dataset);
        size_t datasetSize = datasetItemCount * RANDOMX_DATASET_ITEM_SIZE;
        LogPrintf("RandomXDatasetManager: allocated shared dataset (%.2f GB, %lu items)\n",
                  datasetSize / (1024.0 * 1024.0 * 1024.0), datasetItemCount);
        LogPrint("randomx", "  - Dataset struct at: %p, memory at: %p\n", (void*)dataset, (void*)datasetMemory);
        LogPrint("randomx", "  - Item size: %d bytes; expected ~%.2f GB + ~2MB per mining thread\n",
                 RANDOMX_DATASET_ITEM_SIZE, datasetSize / (1024.0 * 1024.0 * 1024.0));
        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");
        LogProcessMemory("after dataset init");
    }

    // Creates a per-thread VM using the shared dataset.
    // Caller must hold a shared lock on datasetMtx.
    // The VM itself is small (~2MB scratchpad + ~84KB JIT code) — the ~2GB dataset is shared via pointer.
    // VMs should be created ONCE per thread and reused across blocks to avoid
    // heap fragmentation on Windows (repeated 2MB alloc/free causes address-space bloat).
    randomx_vm *CreateVM() {
        static std::atomic<int> vmCount{0};
        LogProcessMemory("before CreateVM");
        randomx_vm *vm = randomx_create_vm(flags, nullptr, dataset);
        if (vm != nullptr) {
            int id = ++vmCount;
            uint8_t *datasetMemory = (uint8_t*)randomx_get_dataset_memory(dataset);
            LogPrint("randomx", "RandomXDatasetManager: VM #%d created — VM at %p, shared dataset at %p\n",
                      id, (void*)vm, (void*)datasetMemory);
            LogPrint("randomx", "  Per-thread overhead: ~2MB scratchpad + ~84KB JIT (dataset NOT copied)\n");
            LogProcessMemory("after CreateVM");
        }
        return vm;
    }

    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
void static RandomXMiner(CWallet *pwallet)
#else
void static RandomXMiner()
#endif
{
    LogPrintf("HushRandomXMiner started\n");
    SetThreadPriority(THREAD_PRIORITY_LOWEST);
    RenameThread("hush-randomx");
    const CChainParams& chainparams = Params();

#ifdef ENABLE_WALLET
    // Each thread has its own key
    CReserveKey reservekey(pwallet);
#endif
    // Each thread has its own counter
    unsigned int nExtraNonce = 0;

    int32_t gpucount=HUSH_MAXGPUCOUNT;
    while ( (ASSETCHAIN_INIT == 0 || HUSH_INITDONE == 0) )
    {
        sleep(1);
        if ( hush_baseid(SMART_CHAIN_SYMBOL) < 0 )
            break;
    }

    std::mutex m_cs;
    bool cancelSolver = false;
    boost::signals2::connection c = uiInterface.NotifyBlockTip.connect(
                                                                       [&m_cs, &cancelSolver](const uint256& hashNewTip) mutable {
                                                                           std::lock_guard<std::mutex> lock{m_cs};
                                                                           cancelSolver = true;
                                                                       }
                                                                       );
    miningTimer.start();

    // Use the shared dataset manager — no per-thread dataset allocation
    if (g_rxDatasetManager == nullptr || !g_rxDatasetManager->initialized) {
        LogPrintf("HushRandomXMiner: shared dataset manager not initialized, aborting!\n");
        return;
    }

    char randomxHash[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
    // initial randomx key is unique to every Hush Arrakis Chain, and has at least 9 bytes (2^9=128 bits) of entropy
    // since magic is 4 bytes, rpc port is 4 bytes and smart chain symbol must be at least 1 character long
    snprintf(randomxKey, 81, "%08x%s%08x", ASSETCHAINS_MAGIC, SMART_CHAIN_SYMBOL, ASSETCHAINS_RPCPORT);

    // With the defaults of 1024 and 64
    // the key block will change every ~21.3 hours with a 75s block time
    // and every ~17 hours with the default 60s block time for HACs
    static int randomxInterval = GetRandomXInterval();
    // This lag is 80 mins for 75s blocktime and 64 mins for 60s (default) blocktime for HACs
    static int randomxBlockLag = GetRandomXBlockLag();
    randomx_vm *myVM = nullptr;

    try {
        
        rxdebug("%s: mining %s with randomx\n", SMART_CHAIN_SYMBOL);
       
        while (true)
        {
            rxdebug("%s: start mining loop on %s with nExtraNonce=%u\n", SMART_CHAIN_SYMBOL, nExtraNonce);

            if (chainparams.MiningRequiresPeers()) {
                //if ( ASSETCHAINS_SEED != 0 && chainActive.LastTip()->GetHeight() < 100 )
                //    break;
                // Busy-wait for the network to come online so we don't waste time mining
                // on an obsolete chain. In regtest mode we expect to fly solo.
                miningTimer.stop();
                do {
                    bool fvNodesEmpty;
                    {
                        //LOCK(cs_vNodes);
                        fvNodesEmpty = vNodes.empty();
                    }
                    if (!fvNodesEmpty )//&& !IsInitialBlockDownload())
                        break;
                    MilliSleep(15000);

                } while (true);
                miningTimer.start();
            }

            // Create new block
            unsigned int nTransactionsUpdatedLast = mempool.GetTransactionsUpdated();
            CBlockIndex* pindexPrev;
            {
                LOCK(cs_main);
                pindexPrev = chainActive.LastTip();
            }

            // If we don't have a valid chain tip to work from, wait and try again.
            if (pindexPrev == nullptr) {
                LogPrint("randomx", "%s: null pindexPrev, trying again...\n",__func__);
                MilliSleep(1000);
                continue;
            }

            if ( Mining_height != pindexPrev->GetHeight()+1 )
            {
                Mining_height = pindexPrev->GetHeight()+1;
                Mining_start = (uint32_t)time(NULL);
            }

            rxdebug("%s: using initial key, interval=%d, lag=%d, Mining_height=%u\n", randomxInterval, randomxBlockLag, Mining_height);
            // 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) {
                g_rxDatasetManager->UpdateKey(randomxKey, strlen(randomxKey));
                rxdebug("%s: updated shared dataset with initial key\n");
            } else {
                rxdebug("%s: calculating keyHeight with randomxInterval=%d\n", randomxInterval);
                const int keyHeight = ((Mining_height - randomxBlockLag) / randomxInterval) * randomxInterval;
                uint256 randomxBlockKey = chainActive[keyHeight]->GetBlockHash();
                g_rxDatasetManager->UpdateKey(&randomxBlockKey, sizeof randomxBlockKey);
                rxdebug("%s: updated shared dataset with keyHeight=%d, randomxBlockKey=%s\n", keyHeight, randomxBlockKey.ToString().c_str());
            }

            // Create a per-thread VM once and reuse across blocks.
            // The VM just stores a pointer to the shared dataset — the pointer
            // remains valid across key changes since UpdateKey rebuilds the dataset
            // contents in-place without reallocating.  Reusing the VM avoids
            // repeated 2MB scratchpad + 84KB JIT alloc/free churn that causes
            // Windows heap fragmentation and apparent memory growth per thread.
            if (myVM == nullptr) {
                // First iteration: acquire shared lock briefly to create VM
                boost::shared_lock<boost::shared_mutex> initLock(g_rxDatasetManager->datasetMtx);
                myVM = g_rxDatasetManager->CreateVM();
                if (myVM == nullptr) {
                    LogPrintf("RandomXMiner: Cannot create RandomX VM, aborting!\n");
                    return;
                }
            }

            // Acquire shared lock to prevent dataset rebuild while we're hashing
            boost::shared_lock<boost::shared_mutex> datasetLock(g_rxDatasetManager->datasetMtx);
#ifdef ENABLE_WALLET
            CBlockTemplate *ptr = CreateNewBlockWithKey(reservekey, pindexPrev->GetHeight()+1, gpucount, 0);
#else
            CBlockTemplate *ptr = CreateNewBlockWithKey();
#endif

            rxdebug("%s: created new block with Mining_start=%u\n",Mining_start);
            if ( ptr == 0 )
            {
                if ( !GetBoolArg("-gen",false))
                {
                    miningTimer.stop();
                    c.disconnect();
                    LogPrintf("HushRandomXMiner terminated\n");
                    return;
                }
                static uint32_t counter;
                if ( counter++ < 10 )
                    LogPrint("randomx", "RandomXMiner: created illegal blockB, retry with counter=%u\n", counter);
                sleep(1);
                continue;
            }
            rxdebug("%s: getting block template\n");

            unique_ptr<CBlockTemplate> pblocktemplate(ptr);
            if (!pblocktemplate.get())
            {
                if (GetArg("-mineraddress", "").empty()) {
                    LogPrintf("Error in HushRandomXMiner: Keypool ran out, please call keypoolrefill before restarting the mining thread\n");
                } else {
                    // Should never reach here, because -mineraddress validity is checked in init.cpp
                    LogPrintf("Error in HushRandomXMiner: Invalid -mineraddress\n");
                }
                return;
            }
            CBlock *pblock = &pblocktemplate->block;

            if ( ASSETCHAINS_REWARD[0] == 0 && !ASSETCHAINS_LASTERA )
            {
                if ( pblock->vtx.size() == 1 && pblock->vtx[0].vout.size() == 1 && Mining_height > ASSETCHAINS_MINHEIGHT )
                {
                    static uint32_t counter;
                    if ( counter++ < 10 )
                        LogPrint("randomx", "skip generating %s on-demand block, no tx avail\n",SMART_CHAIN_SYMBOL);
                    sleep(10);
                    continue;
                } else LogPrint("randomx", "%s vouts.%d mining.%d vs %d\n",SMART_CHAIN_SYMBOL,(int32_t)pblock->vtx[0].vout.size(),Mining_height,ASSETCHAINS_MINHEIGHT);
            }
            rxdebug("%s: incrementing extra nonce\n");
            IncrementExtraNonce(pblock, pindexPrev, nExtraNonce);
            LogPrintf("Running HushRandomXMiner with %u transactions in block (%u bytes)\n",pblock->vtx.size(),::GetSerializeSize(*pblock,SER_NETWORK,PROTOCOL_VERSION));

            // Search
            uint32_t savebits;
            int64_t nStart = GetTime();
            pblock->nBits  = GetNextWorkRequired(pindexPrev, pblock, Params().GetConsensus());
            savebits = pblock->nBits;
            HASHTarget = arith_uint256().SetCompact(savebits);
            roundrobin_delay = ROUNDROBIN_DELAY;
            Mining_start = 0;
            gotinvalid = 0;

            while (true)
            {
                if ( gotinvalid != 0 ) {
                    LogPrint("randomx", "RandomXMiner: gotinvalid=%d\n",gotinvalid);
                    break;
                }
                hush_longestchain();

                rxdebug("%s: solving with nNonce = %s\n",pblock->nNonce.ToString().c_str());
                arith_uint256 hashTarget;
                hashTarget = HASHTarget;

                CRandomXInput rxInput(pblocktemplate->block);
                CDataStream randomxInput(SER_NETWORK, PROTOCOL_VERSION);
                // Serialize block header without nSolution but with nNonce for deterministic RandomX input
                randomxInput << rxInput;

                rxdebug("%s: randomxKey=%s randomxInput=%s\n", randomxKey, HexStr(randomxInput).c_str());

                rxdebug("%s: calculating randomx hash\n");
                randomx_calculate_hash(myVM, &randomxInput[0], randomxInput.size(), randomxHash);
                rxdebug("%s: calculated randomx hash\n");

                rxdebug("%s: randomxHash=");
                if (fRandomXDebug) {
                    for (unsigned i = 0; i < RANDOMX_HASH_SIZE; ++i) {
                            printf("%02x", randomxHash[i] & 0xff);
                    }

                    printf("\n");
                }

                // Use randomx hash to build a valid block
                std::function<bool(std::vector<unsigned char>)> validBlock =
#ifdef ENABLE_WALLET
                [&pblock, &hashTarget, &pwallet, &reservekey, &m_cs, &cancelSolver, &chainparams]
#else
                [&pblock, &hashTarget, &m_cs, &cancelSolver, &chainparams]
#endif
                (std::vector<unsigned char> soln) {
                    int32_t z; arith_uint256 h; CBlock B;
                    // Write the solution to the hash and compute the result.
                    rxdebug("%s: Checking solution against target\n");
                    pblock->nSolution = soln;
                    solutionTargetChecks.increment();
                    B = *pblock;
                    h = UintToArith256(B.GetHash());

                    rxdebug("%s: h=");
                    if (fRandomXDebug) {
                        for (z=31; z>=0; z--)
                            fprintf(stderr,"%02x",((uint8_t *)&h)[z]);
                        fprintf(stderr," , hashTarget=");
                        for (z=31; z>=0; z--)
                            fprintf(stderr,"%02x",((uint8_t *)&hashTarget)[z]);
                        fprintf(stderr,"\n");
                    }

                    if ( h > hashTarget )
                    {
                        rxdebug("%s: h > hashTarget");
                        return false;
                    }

                    CValidationState state;
                    //{ LOCK(cs_main);
                    // 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 )
                    {
                        LogPrintf("RandomXMiner: TestBlockValidity FAILED at ht.%d, gotinvalid=1, state=%s\n",
                                  Mining_height, state.GetRejectReason());
                        gotinvalid = 1;
                        return(false);
                    }
                    //}
                    SetThreadPriority(THREAD_PRIORITY_NORMAL);
                    LogPrintf("HushRandomXMiner:\n");
                    LogPrintf("proof-of-work found  \n  hash: %s  \ntarget: %s\n", B.GetHash().GetHex(), HASHTarget.GetHex());
#ifdef ENABLE_WALLET
                    if (ProcessBlockFound(&B, *pwallet, reservekey)) {
#else
                        if (ProcessBlockFound(&B)) {
#endif
                            // Ignore chain updates caused by us
                            std::lock_guard<std::mutex> lock{m_cs};
                            cancelSolver = false;
                        }
                        SetThreadPriority(THREAD_PRIORITY_LOWEST);
                        // In regression test mode, stop mining after a block is found.
                        if (chainparams.MineBlocksOnDemand()) {
                            // Increment here because throwing skips the call below
                            // TODO: equivalent of ehSolverRuns.increment();
                            throw boost::thread_interrupted();
                        }
                        return true;
                    };
                    std::function<bool(RandomXSolverCancelCheck)> cancelled = [&m_cs, &cancelSolver](RandomXSolverCancelCheck pos) {
                        std::lock_guard<std::mutex> lock{m_cs};
                        return cancelSolver;
                    };

                    try { 
                        // Use the randomX hash as the block solution
                        std::vector<unsigned char> sol_char(randomxHash, randomxHash+32);
                        bool found = validBlock(sol_char);
                        if (found) {
                            rxdebug("%s: found solution!\n");
                            // If we find a POW solution, do not try other solutions
                            // because they become invalid as we created a new block in blockchain.
                            break;
                        } else {
                            rxdebug("%s: solution not found, validBlock=false\n");
                        }
                    } catch (RandomXSolverCanceledException&) {
                        LogPrintf("HushRandomXMiner solver canceled\n");
                        std::lock_guard<std::mutex> lock{m_cs};
                        cancelSolver = false;
                    }

                    boost::this_thread::interruption_point();

                    if (vNodes.empty() && chainparams.MiningRequiresPeers())
                    {
                        if ( Mining_height > ASSETCHAINS_MINHEIGHT )
                        {
                            LogPrint("randomx", "%s: no nodes, break\n", __func__);
                            break;
                        }
                    }
                    if ((UintToArith256(pblock->nNonce) & 0xffff) == 0xffff)
                    {
                        LogPrint("randomx", "%s: nonce & 0xffff == 0xffff, break\n", __func__);
                        break;
                    }
                    // Update nNonce and nTime
                    pblock->nNonce = ArithToUint256(UintToArith256(pblock->nNonce) + 1);
                    pblock->nBits = savebits;
                }

                // Release shared lock so UpdateKey can acquire exclusive lock for dataset rebuild
                // VM is kept alive — its dataset pointer remains valid across rebuilds
                datasetLock.unlock();
            }

   } catch (const boost::thread_interrupted&) {
       miningTimer.stop();
       c.disconnect();

       if (myVM != nullptr) {
           randomx_destroy_vm(myVM);
           myVM = nullptr;
           LogPrintf("%s: destroyed vm via thread interrupt\n", __func__);
       } else {
           LogPrintf("%s: WARNING myVM already null in thread interrupt handler, skipping destroy (would double-free)\n", __func__);
       }
       // Dataset and cache are owned by g_rxDatasetManager — do NOT release here

       LogPrintf("HushRandomXMiner terminated\n");
       throw;
   } catch (const std::runtime_error &e) {
       miningTimer.stop();
       c.disconnect();
       LogPrintf("RandomXMiner: runtime error: %s\n", e.what());

       if (myVM != nullptr) {
           randomx_destroy_vm(myVM);
           myVM = nullptr;
           LogPrintf("%s: destroyed vm because of error\n", __func__);
       }
       // Dataset and cache are owned by g_rxDatasetManager — do NOT release here

       return;
   }

   // Only destroy per-thread VM, dataset/cache are shared
   if (myVM != nullptr) {
       randomx_destroy_vm(myVM);
       myVM = nullptr;
   }
   miningTimer.stop();
   c.disconnect();
}

#ifdef ENABLE_WALLET
void static BitcoinMiner(CWallet *pwallet)
#else
void static BitcoinMiner()
#endif
{
    LogPrintf("HushMiner started\n");
    SetThreadPriority(THREAD_PRIORITY_LOWEST);
    RenameThread("hush-miner");
    const CChainParams& chainparams = Params();

#ifdef ENABLE_WALLET
    // Each thread has its own key
    CReserveKey reservekey(pwallet);
#endif

    // Each thread has its own counter
    unsigned int nExtraNonce = 0;

    unsigned int n = chainparams.EquihashN();
    unsigned int k = chainparams.EquihashK();
    uint8_t *script; uint64_t total; int32_t i,j,gpucount=HUSH_MAXGPUCOUNT,notaryid = -1;
    while ( (ASSETCHAIN_INIT == 0 || HUSH_INITDONE == 0) )
    {
        sleep(1);
        if ( hush_baseid(SMART_CHAIN_SYMBOL) < 0 )
            break;
    }
    if ( notaryid != My_notaryid )
        My_notaryid = notaryid;
    std::string solver;
    if ( ASSETCHAINS_NK[0] == 0 && ASSETCHAINS_NK[1] == 0 )
        solver = "tromp";
    else 
        solver = "default";
    assert(solver == "tromp" || solver == "default");
    LogPrint("pow", "Using Equihash solver \"%s\" with n = %u, k = %u\n", solver, n, k);
    if ( SMART_CHAIN_SYMBOL[0] != 0 )
        LogPrintf("notaryid.%d Mining.%s with %s\n",notaryid,SMART_CHAIN_SYMBOL,solver.c_str());
    std::mutex m_cs;
    bool cancelSolver = false;
    boost::signals2::connection c = uiInterface.NotifyBlockTip.connect(
                                                                       [&m_cs, &cancelSolver](const uint256& hashNewTip) mutable {
                                                                           std::lock_guard<std::mutex> lock{m_cs};
                                                                           cancelSolver = true;
                                                                       }
                                                                       );
    miningTimer.start();

    try {
        if ( SMART_CHAIN_SYMBOL[0] != 0 )
            LogPrintf("try %s Mining with %s\n",SMART_CHAIN_SYMBOL,solver.c_str());
        while (true)
        {
            if (chainparams.MiningRequiresPeers()) {
                //if ( ASSETCHAINS_SEED != 0 && chainActive.LastTip()->GetHeight() < 100 )
                //    break;
                // Busy-wait for the network to come online so we don't waste time mining
                // on an obsolete chain. In regtest mode we expect to fly solo.
                miningTimer.stop();
                do {
                    bool fvNodesEmpty;
                    {
                        //LOCK(cs_vNodes);
                        fvNodesEmpty = vNodes.empty();
                    }
                    if (!fvNodesEmpty )//&& !IsInitialBlockDownload())
                        break;
                    MilliSleep(15000);

                } while (true);
                miningTimer.start();
            }
            //
            // Create new block
            //
            unsigned int nTransactionsUpdatedLast = mempool.GetTransactionsUpdated();
            CBlockIndex* pindexPrev = chainActive.LastTip();

            // If we don't have a valid chain tip to work from, wait and try again.
            if (pindexPrev == nullptr) {
                LogPrint("pow", "%s: null pindexPrev, trying again...\n",__func__);
                MilliSleep(1000);
                continue;
            }

            if ( Mining_height != pindexPrev->GetHeight()+1 )
            {
                Mining_height = pindexPrev->GetHeight()+1;
                Mining_start = (uint32_t)time(NULL);
            }
            if ( SMART_CHAIN_SYMBOL[0] != 0 && ASSETCHAINS_STAKED == 0 )
            {
                //sleep(3);
            }

#ifdef ENABLE_WALLET
            // notaries always default to staking
            CBlockTemplate *ptr = CreateNewBlockWithKey(reservekey, pindexPrev->GetHeight()+1, gpucount, ASSETCHAINS_STAKED != 0 && HUSH_MININGTHREADS == 0);
#else
            CBlockTemplate *ptr = CreateNewBlockWithKey();
#endif
            if ( ptr == 0 )
            {
                if ( !GetBoolArg("-gen",false))
                {
                    miningTimer.stop();
                    c.disconnect();
                    LogPrintf("HushMiner terminated\n");
                    return;
                }
                static uint32_t counter;
                if ( counter++ < 10 && ASSETCHAINS_STAKED == 0 )
                    LogPrint("pow", "created illegal blockB, retry\n");
                sleep(1);
                continue;
            }
            unique_ptr<CBlockTemplate> pblocktemplate(ptr);
            if (!pblocktemplate.get())
            {
                if (GetArg("-mineraddress", "").empty()) {
                    LogPrintf("Error in HushMiner: Keypool ran out, please call keypoolrefill before restarting the mining thread\n");
                } else {
                    // Should never reach here, because -mineraddress validity is checked in init.cpp
                    LogPrintf("Error in HushMiner: Invalid -mineraddress\n");
                }
                return;
            }
            CBlock *pblock = &pblocktemplate->block;
            if ( SMART_CHAIN_SYMBOL[0] != 0 )
            {
                if ( ASSETCHAINS_REWARD[0] == 0 && !ASSETCHAINS_LASTERA )
                {
                    if ( pblock->vtx.size() == 1 && pblock->vtx[0].vout.size() == 1 && Mining_height > ASSETCHAINS_MINHEIGHT )
                    {
                        static uint32_t counter;
                        if ( counter++ < 10 )
                            LogPrint("pow", "skip generating %s on-demand block, no tx avail\n",SMART_CHAIN_SYMBOL);
                        sleep(10);
                        continue;
                    } else LogPrint("pow", "%s vouts.%d mining.%d vs %d\n",SMART_CHAIN_SYMBOL,(int32_t)pblock->vtx[0].vout.size(),Mining_height,ASSETCHAINS_MINHEIGHT);
                }
            }
            IncrementExtraNonce(pblock, pindexPrev, nExtraNonce);
            LogPrintf("Running HushMiner.%s with %u transactions in block (%u bytes)\n",solver.c_str(),pblock->vtx.size(),::GetSerializeSize(*pblock,SER_NETWORK,PROTOCOL_VERSION));

            // Search
            uint32_t savebits; int64_t nStart = GetTime();
            pblock->nBits         = GetNextWorkRequired(pindexPrev, pblock, Params().GetConsensus());
            savebits = pblock->nBits;
            HASHTarget = arith_uint256().SetCompact(savebits);
            roundrobin_delay = ROUNDROBIN_DELAY;
            Mining_start = 0;

            //else if ( ASSETCHAINS_ADAPTIVEPOW > 0 )
            //    HASHTarget_POW = hush_adaptivepow_target(Mining_height,HASHTarget,pblock->nTime);
            gotinvalid = 0;
            while (true)
            {
                if ( gotinvalid != 0 )
                    break;
                hush_longestchain();
                // Hash state
                HUSH_CHOSEN_ONE = 0;

                crypto_generichash_blake2b_state state;
                EhInitialiseState(n, k, state);
                // I = the block header minus nonce and solution.
                CEquihashInput I{*pblock};
                CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
                ss << I;
                // H(I||...
                crypto_generichash_blake2b_update(&state, (unsigned char*)&ss[0], ss.size());
                // H(I||V||...
                crypto_generichash_blake2b_state curr_state;
                curr_state = state;
                crypto_generichash_blake2b_update(&curr_state,pblock->nNonce.begin(),pblock->nNonce.size());
                // (x_1, x_2, ...) = A(I, V, n, k)
                LogPrint("pow", "Running Equihash solver \"%s\" with nNonce = %s\n",solver, pblock->nNonce.ToString());
                arith_uint256 hashTarget;
                if ( HUSH_MININGTHREADS > 0 && ASSETCHAINS_STAKED > 0 && ASSETCHAINS_STAKED < 100 && Mining_height > 10 )
                    hashTarget = HASHTarget_POW;
                //else if ( ASSETCHAINS_ADAPTIVEPOW > 0 )
                else hashTarget = HASHTarget;
                std::function<bool(std::vector<unsigned char>)> validBlock =
#ifdef ENABLE_WALLET
                [&pblock, &hashTarget, &pwallet, &reservekey, &m_cs, &cancelSolver, &chainparams]
#else
                [&pblock, &hashTarget, &m_cs, &cancelSolver, &chainparams]
#endif
                (std::vector<unsigned char> soln) {
                    int32_t z; arith_uint256 h; CBlock B;
                    // Write the solution to the hash and compute the result.
                    LogPrint("pow", "- Checking solution against target\n");
                    pblock->nSolution = soln;
                    solutionTargetChecks.increment();
                    B = *pblock;
                    h = UintToArith256(B.GetHash());
                    /*for (z=31; z>=16; z--)
                        fprintf(stderr,"%02x",((uint8_t *)&h)[z]);
                    fprintf(stderr," mined ");
                    for (z=31; z>=16; z--)
                        fprintf(stderr,"%02x",((uint8_t *)&HASHTarget)[z]);
                    fprintf(stderr," hashTarget ");
                    for (z=31; z>=16; z--)
                        fprintf(stderr,"%02x",((uint8_t *)&HASHTarget_POW)[z]);
                    fprintf(stderr," POW\n");*/
                    if ( h > hashTarget )
                    {
                        //if ( ASSETCHAINS_STAKED != 0 && HUSH_MININGTHREADS == 0 )
                          //  MilliSleep(30);
                        return false;
                    }
                    if ( IS_HUSH_NOTARY != 0 && B.nTime > GetTime() )
                    {
                        while ( GetTime() < B.nTime-2 )
                        {
                            sleep(1);
                            if ( chainActive.LastTip()->GetHeight() >= Mining_height )
                            {
                                LogPrint("pow", "new block arrived\n");
                                return(false);
                            }
                        }
                    }
                    if ( ASSETCHAINS_STAKED == 0 )
                    {
                        if ( IS_HUSH_NOTARY != 0 )
                        {
                            int32_t r;
                            if ( (r= ((Mining_height + NOTARY_PUBKEY33[16]) % 64) / 8) > 0 )
                                MilliSleep((rand() % (r * 1000)) + 1000);
                        }
                    }
                    CValidationState state;

                    //{ LOCK(cs_main);
                    if ( !TestBlockValidity(state,B, chainActive.LastTip(), true, false))
                    {
                        h = UintToArith256(B.GetHash());
                        //for (z=31; z>=0; z--)
                        gotinvalid = 1;
                        return(false);
                    }

                    //}
                    HUSH_CHOSEN_ONE = 1;
                    // Found a solution
                    SetThreadPriority(THREAD_PRIORITY_NORMAL);
                    LogPrintf("HushMiner:\n");
                    LogPrintf("proof-of-work found  \n  hash: %s  \ntarget: %s\n", B.GetHash().GetHex(), HASHTarget.GetHex());
#ifdef ENABLE_WALLET
                    if (ProcessBlockFound(&B, *pwallet, reservekey)) {
#else
                        if (ProcessBlockFound(&B)) {
#endif
                            // Ignore chain updates caused by us
                            std::lock_guard<std::mutex> lock{m_cs};
                            cancelSolver = false;
                        }
                        HUSH_CHOSEN_ONE = 0;
                        SetThreadPriority(THREAD_PRIORITY_LOWEST);
                        // In regression test mode, stop mining after a block is found.
                        if (chainparams.MineBlocksOnDemand()) {
                            // Increment here because throwing skips the call below
                            ehSolverRuns.increment();
                            throw boost::thread_interrupted();
                        }
                        return true;
                    };
                    std::function<bool(EhSolverCancelCheck)> cancelled = [&m_cs, &cancelSolver](EhSolverCancelCheck pos) {
                        std::lock_guard<std::mutex> lock{m_cs};
                        return cancelSolver;
                    };
                    // TODO: factor this out into a function with the same API for each solver.
                    if (solver == "tromp" ) { //&& notaryid >= 0 ) {
                        // Create solver and initialize it.
                        equi eq(1);
                        eq.setstate(&curr_state);

                        // Initialization done, start algo driver.
                        eq.digit0(0);
                        eq.xfull = eq.bfull = eq.hfull = 0;
                        eq.showbsizes(0);
                        for (u32 r = 1; r < WK; r++) {
                            (r&1) ? eq.digitodd(r, 0) : eq.digiteven(r, 0);
                            eq.xfull = eq.bfull = eq.hfull = 0;
                            eq.showbsizes(r);
                        }
                        eq.digitK(0);
                        ehSolverRuns.increment();

                        // Convert solution indices to byte array (decompress) and pass it to validBlock method.
                        for (size_t s = 0; s < std::min(MAXSOLS, eq.nsols); s++) {
                            LogPrint("pow", "Checking solution %d\n", s+1);
                            std::vector<eh_index> index_vector(PROOFSIZE);
                            for (size_t i = 0; i < PROOFSIZE; i++) {
                                index_vector[i] = eq.sols[s][i];
                            }
                            std::vector<unsigned char> sol_char = GetMinimalFromIndices(index_vector, DIGITBITS);

                            if (validBlock(sol_char)) {
                                // If we find a POW solution, do not try other solutions
                                // because they become invalid as we created a new block in blockchain.
                                break;
                            }
                        }
                    } else {
                        try {
                            // If we find a valid block, we rebuild
                            bool found = EhOptimisedSolve(n, k, curr_state, validBlock, cancelled);
                            ehSolverRuns.increment();
                            if (found) {
                                int32_t i; uint256 hash = pblock->GetHash();
                                //for (i=0; i<32; i++)
                                //FOUND_BLOCK = 1;
                                //HUSH_MAYBEMINED = Mining_height;
                                break;
                            }
                        } catch (EhSolverCancelledException&) {
                            LogPrint("pow", "Equihash solver cancelled\n");
                            std::lock_guard<std::mutex> lock{m_cs};
                            cancelSolver = false;
                        }
                    }

                    // Check for stop or if block needs to be rebuilt
                    boost::this_thread::interruption_point();
                    // Regtest mode doesn't require peers
                    /*if ( FOUND_BLOCK != 0 )
                    {
                        FOUND_BLOCK = 0;
                        fprintf(stderr,"FOUND_BLOCK!\n");
                        //sleep(2000);
                    } */
                    if (vNodes.empty() && chainparams.MiningRequiresPeers())
                    {
                        if ( SMART_CHAIN_SYMBOL[0] == 0 || Mining_height > ASSETCHAINS_MINHEIGHT )
                        {
                            LogPrint("pow", "no nodes, break\n");
                            break;
                        }
                    }
                    if ((UintToArith256(pblock->nNonce) & 0xffff) == 0xffff)
                    {
                        //if ( 0 && SMART_CHAIN_SYMBOL[0] != 0 )
                        LogPrint("pow", "0xffff, break\n");
                        break;
                    }
                    if (mempool.GetTransactionsUpdated() != nTransactionsUpdatedLast && GetTime() - nStart > 60)
                    {
                        if ( 0 && SMART_CHAIN_SYMBOL[0] != 0 )
                            fprintf(stderr,"timeout, break\n");
                        break;
                    }
                    if ( pindexPrev != chainActive.LastTip() )
                    {
                        if ( 0 && SMART_CHAIN_SYMBOL[0] != 0 )
                            fprintf(stderr,"Tip advanced, break\n");
                        break;
                    }
                    // Update nNonce and nTime
                    pblock->nNonce = ArithToUint256(UintToArith256(pblock->nNonce) + 1);
                    pblock->nBits = savebits;
                    if ( ASSETCHAINS_ADAPTIVEPOW > 0 )
                    {
                        UpdateTime(pblock, chainparams.GetConsensus(), pindexPrev);
                        HASHTarget.SetCompact(pblock->nBits);
                        hashTarget = HASHTarget;
                        savebits = pblock->nBits;
                    }
                    /*if ( NOTARY_PUBKEY33[0] == 0 )
                    {
                        int32_t percPoS;
                        UpdateTime(pblock, consensusParams, pindexPrev);
                        if (consensusParams.fPowAllowMinDifficultyBlocks)
                        {
                            // Changing pblock->nTime can change work required on testnet:
                            HASHTarget.SetCompact(pblock->nBits);
                            HASHTarget_POW = hush_PoWtarget(&percPoS,HASHTarget,Mining_height,ASSETCHAINS_STAKED);
                        }
                    }*/
                }
            }
        }
        catch (const boost::thread_interrupted&)
        {
            miningTimer.stop();
            c.disconnect();
            LogPrintf("HushMiner terminated\n");
            throw;
        }
        catch (const std::runtime_error &e)
        {
            miningTimer.stop();
            c.disconnect();
            LogPrintf("HushMiner runtime error: %s\n", e.what());
            return;
        }

        miningTimer.stop();
        c.disconnect();
    }

#ifdef ENABLE_WALLET
    void GenerateBitcoins(bool fGenerate, CWallet* pwallet, int nThreads)
#else
    void GenerateBitcoins(bool fGenerate, int nThreads)
#endif
    {
        static boost::thread_group* minerThreads = NULL;

        if (nThreads < 0)
            nThreads = GetNumCores();

        if (minerThreads != NULL)
        {
            minerThreads->interrupt_all();
            // Wait for all miner threads to fully terminate before destroying shared resources
            minerThreads->join_all();
            delete minerThreads;
            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)
            fprintf(stderr,"%s: nThreads.%d fGenerate.%d\n",__FUNCTION__, (int32_t)nThreads,fGenerate);

        if (nThreads == 0)
            return;
        if (!fGenerate)
            return;
        if (pwallet == NULL)
            return;

        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__);
                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++) {
#ifdef ENABLE_WALLET
            if ( ASSETCHAINS_ALGO == ASSETCHAINS_EQUIHASH ) {
                minerThreads->create_thread(boost::bind(&BitcoinMiner, pwallet));
            } else if (ASSETCHAINS_ALGO == ASSETCHAINS_RANDOMX ) {
                minerThreads->create_thread(boost::bind(&RandomXMiner, pwallet));
            }
#else
            if (ASSETCHAINS_ALGO == ASSETCHAINS_EQUIHASH ) {
                minerThreads->create_thread(&BitcoinMiner);
            } else if (ASSETCHAINS_ALGO == ASSETCHAINS_RANDOMX) {
                minerThreads->create_thread(&RandomXMiner);
            }
#endif
        }
    }

#endif // ENABLE_MINING
