// 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 "pow.h"
#include "checkpoints.h"
#include "consensus/upgrades.h"
#include "arith_uint256.h"
#include "chain.h"
#include "chainparams.h"
#include "crypto/equihash.h"
#include "primitives/block.h"
#include "streams.h"
#include "uint256.h"
#include "util.h"
#include "sodium.h"
#include "RandomX/src/randomx.h"
#include <mutex>
#include <boost/thread/shared_mutex.hpp>
#include <boost/thread/locks.hpp>

#ifdef ENABLE_RUST
#include "librustzcash.h"
#endif // ENABLE_RUST
uint32_t hush_chainactive_timestamp();

#include "hush_defs.h"

unsigned int lwmaGetNextWorkRequired(const CBlockIndex* pindexLast, const CBlockHeader *pblock, const Consensus::Params& params);
unsigned int lwmaCalculateNextWorkRequired(const CBlockIndex* pindexLast, const Consensus::Params& params);

/* from zawy repo
 Preliminary code for super-fast increases in difficulty.
 Requires the ability to change the difficulty during the current block,
 based on the timestamp the miner selects. See my github issue #36 and KMD.
 Needs intr-block exponential decay function because
 this can make difficulty jump very high.
 Miners need to caclulate new difficulty with each second, or
 maybe 3 seconds.  FTL, MTP, and revert to local times must be small.
 MTP=1 if using Digishield. Out-of-sequence timestamps must be forbidden.
 1) bnTarget = Digishield() or other baseline DA
 2) bnTarget = RT_CST_RST()
 3) bnTarget = max(bnTarget,expdecay())
 RT_CST_RST() multiplies Recent Target(s), Current Solvetimes, &
 Recent SolveTime if RST had an unlikely 1/200 block chance of
 being too fast on accident. This estimates and adjusts for recent
 hashrate aggressively (lots of random error) but corrects the error by
 CST adjusting the difficulty during the block.
 It checks to see if there was an "active trigger" still in play which
 occurs when recent block emission rate has been too fast. Triggers
 are supposed to be active if emission rate has not slowed up enough
 to get back on track. It checks the longest range first because it's
 the least aggressive.
 T = target blocktime
 ts = timestamp vector, 62 elements, 62 is oldest  (elements needed are 50+W)
 ct = cumulative targets, 62 elements, 62 is oldest
 W = window size of recent solvetimes and targets to use that estimates hashrate
 numerator & deonominator needed for 1/200 possion estimator
 past = how far back in past to look for beginning of a trigger
 */

/* create ts and cw vectors
// Get bnTarget = Digishield();

arith_uint256 past = 50;

arith_uint256 W = 12;
arith_uint256 numerator = 12;
arith_uint256 denominator = 7;

// bnTarget = RT_CST_RST (bnTarget, ts, cw, numerator, denominator, W, T, past);

W = 6; top = 7; denominator = 3;

// bnTarget = RT_CST_RST (bnTarget, ts, cw, numerator, denominator, W, T, past);

W = 3; top = 1; denominator = 2;

bnTarget = RT_CST_RST (bnTarget, ts, cw, numerator, denominator, W, T, past);
*/

#define T ASSETCHAINS_BLOCKTIME
#define K ((int64_t)1000000)

// The proof-of-work limit for the active algorithm: Equihash chains use params.powLimit,
// everything else (DragonX = RandomX) uses params.powAlternate. Shared by the retarget
// functions below, where this selection was previously copy-pasted as an if/else.
static arith_uint256 PowLimitForAlgo(const Consensus::Params& params)
{
    return UintToArith256(ASSETCHAINS_ALGO == ASSETCHAINS_EQUIHASH ? params.powLimit : params.powAlternate);
}

arith_uint256 RT_CST_RST_outer(int32_t height,uint32_t nTime,arith_uint256 bnTarget,uint32_t *ts,arith_uint256 *ct,int32_t numerator,int32_t denominator,int32_t W,int32_t past)
{
    int64_t outerK; int32_t cmpval; arith_uint256 mintarget = bnTarget / arith_uint256(2);
    cmpval = (T * numerator)/denominator;
    if ( cmpval < 2 )
        cmpval = 2;
    if ( (ts[0] - ts[W]) < cmpval )
    {
        outerK = (K * (nTime-ts[0]) * (ts[0]-ts[W]) * denominator) / (numerator * (T * T));
        if ( outerK < K )
        {
            bnTarget = ct[0] / arith_uint256(K);
            bnTarget *= arith_uint256(outerK);
        }
        if ( bnTarget > mintarget )
            bnTarget = mintarget;
    }
    return(bnTarget);
}

arith_uint256 RT_CST_RST_target(int32_t height,uint32_t nTime,arith_uint256 bnTarget,uint32_t *ts,arith_uint256 *ct,int32_t width)
{
    int32_t i; int64_t innerK;
    bnTarget = ct[0];
    for (i=1; i<width; i++)
        bnTarget += ct[i];
    bnTarget /= arith_uint256(width * K);
    innerK = (K * (nTime-ts[0]) * (ts[0]-ts[width])) / (width * T * T);
    bnTarget *= arith_uint256(innerK);
    return(bnTarget);
}

arith_uint256 RT_CST_RST_inner(int32_t height,uint32_t nTime,arith_uint256 bnTarget,uint32_t *ts,arith_uint256 *ct,int32_t W,int32_t outeri)
{
    int32_t expected,elapsed,width = outeri+W; arith_uint256 mintarget,origtarget;
    expected = (width+1) * T;
    origtarget = bnTarget;
    if ( (elapsed= (ts[0] - ts[width])) < expected )
    {
        mintarget = (bnTarget / arith_uint256(101)) * arith_uint256(100);
        bnTarget = RT_CST_RST_target(height,nTime,bnTarget,ts,ct,W);
        if ( bnTarget == origtarget ) // force zawyflag to 1
            bnTarget = mintarget;
    }
    return(bnTarget);
}

arith_uint256 zawy_targetMA(arith_uint256 easy,arith_uint256 bnSum,int32_t num,int32_t numerator,int32_t divisor)
{
    bnSum /= arith_uint256(ASSETCHAINS_BLOCKTIME * num * num * divisor);
    bnSum *= arith_uint256(numerator);
    if ( bnSum > easy )
        bnSum = easy;
    return(bnSum);
}

int64_t zawy_exponential_val360000(int32_t num)
{
    int32_t i,n,modval; int64_t A = 1, B = 3600 * 100;
    if ( (n= (num/ASSETCHAINS_BLOCKTIME)) > 0 )
    {
        for (i=1; i<=n; i++)
            A *= 3;
    }
    if ( (modval= (num % ASSETCHAINS_BLOCKTIME)) != 0 )
    {
        B += (3600 * 110 * modval) / ASSETCHAINS_BLOCKTIME;
        B += (3600 * 60 * modval * modval) / (ASSETCHAINS_BLOCKTIME * ASSETCHAINS_BLOCKTIME);
    }
    return(A * B);
}

arith_uint256 zawy_exponential(arith_uint256 bnTarget,int32_t mult)
{
    bnTarget /= arith_uint256(100 * 3600);
    bnTarget *= arith_uint256(zawy_exponential_val360000(mult));
    return(bnTarget);
}

arith_uint256 zawy_ctB(arith_uint256 bnTarget,uint32_t solvetime)
{
    int64_t num;
    num = ((int64_t)1000 * solvetime * solvetime * 1000) / (T * T * 784);
    if ( num > 1 )
    {
        bnTarget /= arith_uint256(1000);
        bnTarget *= arith_uint256(num);
    }
    return(bnTarget);
}

arith_uint256 zawy_TSA_EMA(int32_t height,int32_t tipdiff,arith_uint256 prevTarget,int32_t solvetime)
{
    arith_uint256 A,B,C,bnTarget;
    if ( tipdiff < 4 )
        tipdiff = 4;
    tipdiff &= ~1;
    bnTarget = prevTarget / arith_uint256(K*T);
    A = bnTarget * arith_uint256(T);
    B = (bnTarget / arith_uint256(360000)) * arith_uint256(tipdiff * zawy_exponential_val360000(tipdiff/2));
    C = (bnTarget / arith_uint256(360000)) * arith_uint256(T * zawy_exponential_val360000(tipdiff/2));
    bnTarget = ((A + B - C) / arith_uint256(tipdiff)) * arith_uint256(K*T);
    return(bnTarget);
}

unsigned int GetNextWorkRequired(const CBlockIndex* pindexLast, const CBlockHeader *pblock, const Consensus::Params& params)
{
    //if (ASSETCHAINS_ALGO != ASSETCHAINS_EQUIHASH && ASSETCHAINS_STAKED == 0)
    if (ASSETCHAINS_ALGO != ASSETCHAINS_EQUIHASH && ASSETCHAINS_ALGO != ASSETCHAINS_RANDOMX) {
        LogPrint("pow","%s: using lwma for next work\n",__func__);
        return lwmaGetNextWorkRequired(pindexLast, pblock, params);
    }

    arith_uint256 bnLimit;
    bnLimit = PowLimitForAlgo(params);
    unsigned int nProofOfWorkLimit = bnLimit.GetCompact();
    // Genesis block
    if (pindexLast == NULL )
        return nProofOfWorkLimit;

    // DragonX difficulty reset at the RANDOMX_VALIDATION activation height.
    // The chain transitioned to a new binary at this height and difficulty was
    // reset to minimum (powLimit).  Without this, fresh-syncing nodes compute
    // a different nBits from GetNextWorkRequired (based on pre-reset blocks)
    // and reject the on-chain min-diff block, banning all seed nodes.
    if (ASSETCHAINS_RANDOMX_VALIDATION > 0 && pindexLast->GetHeight() + 1 == ASSETCHAINS_RANDOMX_VALIDATION) {
        LogPrintf("%s: difficulty reset to powLimit at height %d\n", __func__, ASSETCHAINS_RANDOMX_VALIDATION);
        return nProofOfWorkLimit;
    }

    //{
        // Comparing to pindexLast->nHeight with >= because this function
        // returns the work required for the block after pindexLast.
        //if (params.nPowAllowMinDifficultyBlocksAfterHeight != boost::none &&
        //    pindexLast->nHeight >= params.nPowAllowMinDifficultyBlocksAfterHeight.get())
        //{
            // Special difficulty rule for testnet:
            // If the new block's timestamp is more than 6 * 2.5 minutes
            // then allow mining of a min-difficulty block.
        //    if (pblock && pblock->GetBlockTime() > pindexLast->GetBlockTime() + params.nPowTargetSpacing * 6)
        //        return nProofOfWorkLimit;
        //}
    //}

    // Find the first block in the averaging interval
    const CBlockIndex* pindexFirst = pindexLast;
    arith_uint256 ct[64],ctinv[64],bnTmp,bnPrev,bnTarget,bnTarget2,bnTarget3,bnTarget6,bnTarget12,bnTot {0};
    uint32_t nbits,blocktime,ts[sizeof(ct)/sizeof(*ct)]; int32_t zflags[sizeof(ct)/sizeof(*ct)],i,diff,height=0,mult = 0,tipdiff = 0;
    memset(ts,0,sizeof(ts));
    memset(ct,0,sizeof(ct));
    memset(ctinv,0,sizeof(ctinv));
    memset(zflags,0,sizeof(zflags));
    if ( pindexLast != 0 )
        height = (int32_t)pindexLast->GetHeight() + 1;

    if ( ASSETCHAINS_ADAPTIVEPOW > 0 && pindexFirst != 0 && pblock != 0 && height >= (int32_t)(sizeof(ct)/sizeof(*ct)) )
    {
        tipdiff = (pblock->nTime - pindexFirst->nTime);
        mult = tipdiff - 7 * ASSETCHAINS_BLOCKTIME;
        bnPrev.SetCompact(pindexFirst->nBits);
        for (i=0; pindexFirst != 0 && i<(int32_t)(sizeof(ct)/sizeof(*ct)); i++)
        {
            zflags[i] = (pindexFirst->nBits & 3);
            ct[i].SetCompact(pindexFirst->nBits);
            ts[i] = pindexFirst->nTime;
            pindexFirst = pindexFirst->pprev;
        }
        for (i=0; pindexFirst != 0 && i<(int32_t)(sizeof(ct)/sizeof(*ct))-1; i++)
        {
            if ( zflags[i] == 1 || zflags[i] == 2 ) // I, O and if TSA made it harder
                ct[i] = zawy_ctB(ct[i],ts[i] - ts[i+1]);
        }
        if ( ASSETCHAINS_ADAPTIVEPOW == 2 ) // TSA
        {
            bnTarget = zawy_TSA_EMA(height,tipdiff,ct[0],ts[0] - ts[1]);
            nbits = bnTarget.GetCompact();
            nbits = (nbits & 0xfffffffc) | 0;
            return(nbits);
        }
    }
    pindexFirst = pindexLast;
    for (i = 0; pindexFirst && i < params.nPowAveragingWindow; i++)
    {
        bnTmp.SetCompact(pindexFirst->nBits);
        if ( ASSETCHAINS_ADAPTIVEPOW > 0 && pblock != 0 )
        {
            blocktime = pindexFirst->nTime;
            diff = (pblock->nTime - blocktime);
            if ( i < 6 )
            {
                diff -= (8+i)*ASSETCHAINS_BLOCKTIME;
                if ( diff > mult )
                {
                    mult = diff;
                }
            }
            if ( zflags[i] != 0 && zflags[0] == 0 ) // an RST block, but the most recent has no RST
                bnTmp = (bnTmp / arith_uint256(8)) * arith_uint256(7);
        }
        bnTot += bnTmp;
        pindexFirst = pindexFirst->pprev;
    }
    // Check we have enough blocks
    if (pindexFirst == NULL)
        return nProofOfWorkLimit;

    bool fNegative,fOverflow; int32_t zawyflag = 0; arith_uint256 easy,origtarget,bnAvg {bnTot / params.nPowAveragingWindow};
    nbits = CalculateNextWorkRequired(bnAvg, pindexLast->GetMedianTimePast(), pindexFirst->GetMedianTimePast(), params, height);

    if ( ASSETCHAINS_ADAPTIVEPOW > 0 )
    {
        bnTarget = arith_uint256().SetCompact(nbits);
        if ( height > (int32_t)(sizeof(ct)/sizeof(*ct)) && pblock != 0 && tipdiff > 0 )
        {
            easy.SetCompact(HUSH_MINDIFF_NBITS & (~3),&fNegative,&fOverflow);
            if ( pblock != 0 )
            {
                origtarget = bnTarget;
                if ( zflags[0] == 0 || zflags[0] == 3 )
                {
                    // 15 51 102 162 230 303 380 460 543 627 714 803 892 983 1075 These are the 0.5% per blk numerator constants for W=2 to 16 if denominator is 100. - zawy
                    if ( ASSETCHAINS_BLOCKTIME >= 60 && ASSETCHAINS_BLOCKTIME < 100 )
                        bnTarget = RT_CST_RST_outer(height,pblock->nTime,bnTarget,ts,ct,1,60,1,10);
                    else if ( ASSETCHAINS_BLOCKTIME >= 100 )
                        bnTarget = RT_CST_RST_outer(height,pblock->nTime,bnTarget,ts,ct,1,100,1,10);
                    if ( bnTarget < origtarget )
                        zawyflag = 2;
                    else
                    {
                        bnTarget = RT_CST_RST_outer(height,pblock->nTime,origtarget,ts,ct,15,100,2,20);
                        if ( bnTarget < origtarget )
                            zawyflag = 2;
                        else
                        {
                            bnTarget = RT_CST_RST_outer(height,pblock->nTime,origtarget,ts,ct,1,2,3,30);
                            if ( bnTarget < origtarget )
                                zawyflag = 2;
                            else
                            {
                                bnTarget = RT_CST_RST_outer(height,pblock->nTime,origtarget,ts,ct,7,3,6,40);
                                if ( bnTarget < origtarget )
                                    zawyflag = 2;
                                else
                                {
                                    bnTarget = RT_CST_RST_outer(height,pblock->nTime,origtarget,ts,ct,12,7,12,50);
                                    if ( bnTarget < origtarget )
                                        zawyflag = 2;
                                }
                            }
                        }
                    }
                }
                else
                {
                    for (i=0; i<50; i++)
                        if ( zflags[i] == 2 )
                            break;
                    if ( i < 10 )
                    {
                        bnTarget = RT_CST_RST_inner(height,pblock->nTime,bnTarget,ts,ct,1,i);
                        if ( bnTarget > origtarget )
                            bnTarget = origtarget;
                    }
                    if ( i < 20 )
                    {
                        bnTarget2 = RT_CST_RST_inner(height,pblock->nTime,bnTarget,ts,ct,2,i);
                        if ( bnTarget2 < bnTarget )
                            bnTarget = bnTarget2;
                    }
                    if ( i < 30 )
                    {
                        bnTarget3 = RT_CST_RST_inner(height,pblock->nTime,bnTarget,ts,ct,3,i);
                        if ( bnTarget3 < bnTarget )
                            bnTarget = bnTarget3;
                    }
                    if ( i < 40 )
                    {
                        bnTarget6 = RT_CST_RST_inner(height,pblock->nTime,bnTarget,ts,ct,6,i);
                        if ( bnTarget6 < bnTarget )
                            bnTarget = bnTarget6;
                    }
                    if ( i < 50 )
                    {
                        bnTarget12 = RT_CST_RST_inner(height,pblock->nTime,bnTarget,ts,ct,12,i);
                        if ( bnTarget12 < bnTarget)
                            bnTarget = bnTarget12;
                    }
                    if ( bnTarget != origtarget )
                        zawyflag = 1;
                }
            }
            if ( mult > 1 ) // e^mult case, jl777:  test of mult > 1 failed when it was int64_t???
            {
                origtarget = bnTarget;
                bnTarget = zawy_exponential(bnTarget,mult);
                if ( bnTarget < origtarget || bnTarget > easy )
                {
                    bnTarget = easy;
                    LogPrint("pow","cmp.%d mult.%d ht.%d -> easy target\n",mult>1,(int32_t)mult,height);
                    return(HUSH_MINDIFF_NBITS & (~3));
                }
            }
        }
        nbits = bnTarget.GetCompact();
        nbits = (nbits & 0xfffffffc) | zawyflag;
    }
    if(fDebug) {
        fprintf(stderr,"%s: nbits=%d\n", __func__, nbits);
    }
    return(nbits);
}

// Changing this requires changing many other things and
// might change consensus. Have fun -- Duke
// NOTE: This hardcoded AWT is legacy from the original HUSH3 mainnet. On DragonX the
// CalculateNextWorkRequired strncmp(SMART_CHAIN_SYMBOL,"HUSH3",...) check is never true
// (SMART_CHAIN_SYMBOL is "DRAGONX"), so this function is dead here and the params-derived
// AveragingWindowTimespan() is used instead. Kept as-is to avoid a consensus change.
int64_t AveragingWindowTimespan() {
    // used in const methods, beware!
    // This is the correct AWT for 75s blocktime, before block 340k
    // the correct value was 2550 when the blocktime was 150s
    int64_t AWT = 1275;
    return AWT;
}

unsigned int CalculateNextWorkRequired(arith_uint256 bnAvg,
                                       int64_t nLastBlockTime, int64_t nFirstBlockTime,
                                       const Consensus::Params& params,
                                       int32_t height)
{
    // Limit adjustment step and use medians to prevent time-warp attacks
    int64_t nActualTimespan = nLastBlockTime - nFirstBlockTime;
    LogPrint("pow", "  nActualTimespan = %d  before dampening\n", nActualTimespan);

    // Legacy branch: the original HUSH3 mainnet used the hardcoded AveragingWindowTimespan()
    // above; every other chain uses the params-derived value. On DragonX the symbol is
    // "DRAGONX", so this comparison is always false and the params value is used. The check is
    // kept (rather than removed) because it is part of consensus difficulty calculation.
    bool ishush3 = strncmp(SMART_CHAIN_SYMBOL, "HUSH3",5) == 0 ? true : false;
    int64_t AWT = ishush3 ? AveragingWindowTimespan() : params.AveragingWindowTimespan();

    nActualTimespan = AWT + (nActualTimespan - AWT)/4;
    LogPrint("pow", "  nActualTimespan = %d  before bounds\n", nActualTimespan);

    if ( ASSETCHAINS_ADAPTIVEPOW <= 0 ) {
        if (nActualTimespan < params.MinActualTimespan()) {
            if(fDebug) {
                fprintf(stderr,"%s: Adjusting nActualTimespan up to min value %li\n", __func__, params.MinActualTimespan() );
            }
            nActualTimespan = params.MinActualTimespan();
        }
        if (nActualTimespan > params.MaxActualTimespan()) {
            if(fDebug) {
                fprintf(stderr,"%s: Adjusting nActualTimespan down to max value %li\n", __func__, params.MaxActualTimespan() );
            }
            nActualTimespan = params.MaxActualTimespan();
        }
    }
    // Retarget
    arith_uint256 bnLimit;
    bnLimit = PowLimitForAlgo(params);

    const arith_uint256 bnPowLimit = bnLimit; //UintToArith256(params.powLimit);
    arith_uint256 bnNew {bnAvg};
    bnNew /= AWT;
    bnNew *= nActualTimespan;

    if (bnNew > bnPowLimit)
        bnNew = bnPowLimit;

    /// debug print
    LogPrint("pow", "GetNextWorkRequired RETARGET\n");
    LogPrint("pow", "AveragingWindowTimespan = %d nActualTimespan = %d\n", AWT, nActualTimespan);
    LogPrint("pow", "Current average: %08x  %s\n", bnAvg.GetCompact(), bnAvg.ToString());
    LogPrint("pow", "After:  %08x  %s\n", bnNew.GetCompact(), bnNew.ToString());
    if(fDebug) {
    fprintf(stderr, "%s: nbits Current average: %08x  %s\n", __func__, bnAvg.GetCompact(), bnAvg.ToString().c_str());
    fprintf(stderr, "%s: bits After:  %08x  %s\n", __func__, bnNew.GetCompact(), bnNew.ToString().c_str());
    fprintf(stderr,"%s: AWT=%lu ActualTimeSpan=%li MinActual=%li MaxActual=%li\n",__func__, AWT, nActualTimespan, params.MinActualTimespan(), params.MaxActualTimespan());
    }
    return bnNew.GetCompact();
}

// These LWMA difficulty functions are inherited from the Hush lineage and are only used when
// ASSETCHAINS_ALGO is neither Equihash nor RandomX (see the dispatch in GetNextWorkRequired).
// DragonX uses RandomX, so this LWMA path is not on DragonX's active difficulty codepath.
unsigned int lwmaGetNextWorkRequired(const CBlockIndex* pindexLast, const CBlockHeader *pblock, const Consensus::Params& params)
{
    return lwmaCalculateNextWorkRequired(pindexLast, params);
}

unsigned int lwmaCalculateNextWorkRequired(const CBlockIndex* pindexLast, const Consensus::Params& params)
{
    arith_uint256 nextTarget {0}, sumTarget {0}, bnTmp, bnLimit;
    bnLimit = PowLimitForAlgo(params);

    unsigned int nProofOfWorkLimit = bnLimit.GetCompact();

    // Find the first block in the averaging interval as we total the linearly weighted average
    const CBlockIndex* pindexFirst = pindexLast;
    const CBlockIndex* pindexNext;
    int64_t t = 0, solvetime, k = params.nLwmaAjustedWeight, N = params.nPowAveragingWindow;

    for (int i = 0, j = N - 1; pindexFirst && i < N; i++, j--) {
        pindexNext = pindexFirst;
        pindexFirst = pindexFirst->pprev;
        if (!pindexFirst)
            break;

        solvetime = pindexNext->GetBlockTime() - pindexFirst->GetBlockTime();

        // weighted sum
        t += solvetime * j;

        // Target sum divided by a factor, (k N^2).
        // The factor is a part of the final equation. However we divide 
        // here to avoid potential overflow.
        bnTmp.SetCompact(pindexNext->nBits);
        sumTarget += bnTmp / (k * N * N);
    }

    // Check we have enough blocks
    if (!pindexFirst)
        return nProofOfWorkLimit;

    // Keep t reasonable in case strange solvetimes occurred.
    if (t < N * k / 3)
        t = N * k / 3;

    bnTmp = bnLimit;
    nextTarget = t * sumTarget;
    if (nextTarget > bnTmp)
        nextTarget = bnTmp;

    return nextTarget.GetCompact();
}

bool DoesHashQualify(const CBlockIndex *pbindex)
{
    // if it fails hash test and PoW validation, consider it POS. it could also be invalid
    arith_uint256 hash = UintToArith256(pbindex->GetBlockHash());
    // to be considered POS, we first can't qualify as POW
    if (hash > hash.SetCompact(pbindex->nBits))
    {
        return false;
    }
    return true;
}

bool CheckEquihashSolution(const CBlockHeader *pblock, const CChainParams& params)
{
    if (ASSETCHAINS_ALGO != ASSETCHAINS_EQUIHASH)
        return true;
    
    if ( ASSETCHAINS_NK[0] != 0 && ASSETCHAINS_NK[1] != 0 && pblock->GetHash().ToString() == "027e3758c3a65b12aa1046462b486d0a63bfa1beae327897f56c5cfb7daaae71" )
        return true;

    unsigned int n = params.EquihashN();
    unsigned int k = params.EquihashK();

    if ( Params().NetworkIDString() == "regtest" )
        return(true);
    // Hash state
    crypto_generichash_blake2b_state state;
    EhInitialiseState(n, k, state);

    // I = the block header minus nonce and solution.
    CEquihashInput I{*pblock};
    // I||V
    CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
    ss << I;
    ss << pblock->nNonce;

    // H(I||V||...
    crypto_generichash_blake2b_update(&state, (unsigned char*)&ss[0], ss.size());

    bool isValid;
    EhIsValidSolution(n, k, state, pblock->nSolution, isValid);

    if (!isValid)
        return error("CheckEquihashSolution(): invalid solution");

    return true;
}

int GetRandomXInterval() { return GetArg("-ac_randomx_interval", 1024); }
int GetRandomXBlockLag() { return GetArg("-ac_randomx_lag", 64); }

// Cached RandomX validation state — reused across calls, protected by mutex
static std::mutex cs_randomx_validator;
static randomx_cache *s_rxCache = nullptr;
static randomx_vm *s_rxVM = nullptr;
static std::string s_rxCurrentKey; // tracks current key to avoid re-init
static int64_t nTimeRandomX = 0;   // cumulative RandomX validation time (us), reported under -debug=bench

// Thread-local flag: skip CheckRandomXSolution when the miner is validating its own block
// The miner already computed the correct RandomX hash — re-verifying with a separate
// cache+VM would allocate ~256MB extra memory and can trigger the OOM killer.
thread_local bool fSkipRandomXValidation = false;

void SetSkipRandomXValidation(bool skip) { fSkipRandomXValidation = skip; }
bool GetSkipRandomXValidation() { return fSkipRandomXValidation; }

CBlockIndex *hush_chainactive(int32_t height);

// Centralized predicate: does a block at this height actually require a RandomX hash check?
// Shared by CheckRandomXSolution (inline path) and the parallel pre-verify pool so the two can
// never drift. Returns false when the recompute is unnecessary:
//   - non-RandomX chain, or RandomX validation disabled (activation height < 0)
//   - below the RandomX activation height (those blocks used Equihash, validated elsewhere)
//   - during initial on-disk block loading / reindex (HUSH_LOADINGBLOCKS)
//   - below the last hardcoded checkpoint (chain pinned by checkpoint hash + linkage + work)
// Deliberately does NOT consider the thread-local fSkipRandomXValidation (miner self-check) — that
// is a property of the calling thread, handled only in the inline CheckRandomXSolution below.
bool RandomXValidationRequired(int32_t height)
{
    if (ASSETCHAINS_ALGO != ASSETCHAINS_RANDOMX)
        return false;
    if (ASSETCHAINS_RANDOMX_VALIDATION < 0)
        return false;
    if (height < ASSETCHAINS_RANDOMX_VALIDATION)
        return false;
    extern int32_t HUSH_LOADINGBLOCKS;
    if (HUSH_LOADINGBLOCKS != 0)
        return false;
    extern bool fCheckpointsEnabled;
    // Gate the RandomX skip on the last checkpoint actually LOCKED INTO this node's block index
    // (GetLastCheckpoint), NOT the static top checkpoint (GetTotalBlocksEstimate). The fork-rejection
    // guard uses this same in-index boundary, so a block below it is provably on the checkpoint-pinned
    // chain and cannot be a forged fork. Using the static boundary would, once checkpoints extend above
    // the RandomX activation height, leave a gap (in-index checkpoint .. static top) during IBD/eclipse
    // where a no-hashpower peer could get SHA256-grinded, RandomX-forged blocks accepted. Safe to walk
    // mapBlockIndex here: called only under cs_main (ActivateBestChainStep + inline CheckRandomXSolution).
    if (fCheckpointsEnabled) {
        CBlockIndex* pcheckpoint = Checkpoints::GetLastCheckpoint(Params().Checkpoints());
        if (pcheckpoint != NULL && height < pcheckpoint->GetHeight())
            return false;
    }
    return true;
}

// Serialize the RandomX hash input: the block header without nSolution (but with nNonce). Used by
// both the inline CheckRandomXSolution and the parallel pre-verify pool, so the bytes are identical.
std::vector<unsigned char> GetRandomXInput(const CBlockHeader& block)
{
    CRandomXInput rxInput(block);
    CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
    ss << rxInput;
    return std::vector<unsigned char>(ss.begin(), ss.end());
}

// Derive the RandomX key string for a block at `height`. Below interval+lag it is the chain-params
// initial key; otherwise the block hash at the key-rotation height. MUST be called under cs_main
// (reads chainActive via hush_chainactive). Returns empty if the key-height block is unavailable.
std::string GetRandomXKey(int32_t height)
{
    static int randomxInterval = GetRandomXInterval();
    static int randomxBlockLag = GetRandomXBlockLag();
    if (height < randomxInterval + randomxBlockLag) {
        char initialKey[82];
        snprintf(initialKey, 81, "%08x%s%08x", ASSETCHAINS_MAGIC, SMART_CHAIN_SYMBOL, ASSETCHAINS_RPCPORT);
        return std::string(initialKey, strlen(initialKey));
    }
    int keyHeight = ((height - randomxBlockLag) / randomxInterval) * randomxInterval;
    CBlockIndex *pKeyIndex = hush_chainactive(keyHeight);
    if (pKeyIndex == nullptr)
        return std::string();
    uint256 blockKey = pKeyIndex->GetBlockHash();
    return std::string((const char*)&blockKey, sizeof(blockKey));
}

bool CheckRandomXSolution(const CBlockHeader *pblock, int32_t height)
{
    // Centralized height gate (shared with the parallel pre-verify pool, Stage 0).
    if (!RandomXValidationRequired(height))
        return true;

    // Skip when the miner is validating its own freshly-mined block via TestBlockValidity
    // (thread-local; never set on the connect thread or the pre-verify worker threads).
    if (fSkipRandomXValidation)
        return true;

    // nSolution must be exactly RANDOMX_HASH_SIZE (32) bytes
    if (pblock->nSolution.size() != RANDOMX_HASH_SIZE) {
        return error("CheckRandomXSolution(): nSolution size %u != expected %d at height %d",
                     pblock->nSolution.size(), RANDOMX_HASH_SIZE, height);
    }

    // Derive the key (shared helper) and serialize the input (identical bytes to the pool path).
    std::string rxKey = GetRandomXKey(height);
    if (rxKey.empty())
        return error("CheckRandomXSolution(): cannot derive RandomX key for height %d", height);
    std::vector<unsigned char> ssInput = GetRandomXInput(*pblock);

    char computedHash[RANDOMX_HASH_SIZE];

    // Measurement (Track 1): isolate RandomX verification cost during IBD. The
    // expensive parts are the per-key cache (re)init (~every GetRandomXInterval()
    // blocks) and the hash computation itself; both happen under the lock below.
    int64_t nTimeRxStart = GetTimeMicros();
    bool fKeyInit = false;
    {
        std::lock_guard<std::mutex> lock(cs_randomx_validator);

        // Initialize cache + VM if needed, or re-init if key changed
        if (s_rxCache == nullptr) {
            randomx_flags flags = randomx_get_flags();
            // Try large pages for the 256MB validator cache: fewer TLB misses → ~15-30% faster
            // light-mode validation where the OS has hugepages configured. Falls back transparently
            // when unavailable, exactly as the miner does (miner.cpp:1097). Page size does not affect
            // the computed hash, so this is consensus-neutral.
            bool fLargePages = true;
            s_rxCache = randomx_alloc_cache(flags | RANDOMX_FLAG_LARGE_PAGES);
            if (s_rxCache == nullptr) {
                fLargePages = false;
                s_rxCache = randomx_alloc_cache(flags);
            }
            if (s_rxCache == nullptr) {
                return error("CheckRandomXSolution(): failed to allocate RandomX cache");
            }
            // Confirm the fast paths are active (JIT off would be ~9x slower; see randomx-benchmark).
            LogPrint("bench", "CheckRandomXSolution: RandomX flags=0x%x JIT=%d HARD_AES=%d largePages=%d\n",
                     (unsigned int)flags, !!(flags & RANDOMX_FLAG_JIT), !!(flags & RANDOMX_FLAG_HARD_AES), (int)fLargePages);
            randomx_init_cache(s_rxCache, rxKey.data(), rxKey.size());
            s_rxCurrentKey = rxKey;
            fKeyInit = true;
            s_rxVM = randomx_create_vm(flags, s_rxCache, nullptr);
            if (s_rxVM == nullptr) {
                randomx_release_cache(s_rxCache);
                s_rxCache = nullptr;
                return error("CheckRandomXSolution(): failed to create RandomX VM");
            }
        } else if (s_rxCurrentKey != rxKey) {
            randomx_init_cache(s_rxCache, rxKey.data(), rxKey.size());
            s_rxCurrentKey = rxKey;
            fKeyInit = true;
            randomx_vm_set_cache(s_rxVM, s_rxCache);
        }

        randomx_calculate_hash(s_rxVM, ssInput.data(), ssInput.size(), computedHash);
    }
    int64_t nTimeRxEnd = GetTimeMicros();
    nTimeRandomX += nTimeRxEnd - nTimeRxStart;
    LogPrint("bench", "      - RandomX verify ht=%d: %.2fms%s [%.2fs]\n",
             height, (nTimeRxEnd - nTimeRxStart) * 0.001,
             fKeyInit ? " (key-init)" : "", nTimeRandomX * 0.000001);

    // Compare computed hash against nSolution
    if (memcmp(computedHash, pblock->nSolution.data(), RANDOMX_HASH_SIZE) != 0) {
        // Debug: dump both hashes for diagnosis
        std::string computedHex, solutionHex;
        for (int i = 0; i < RANDOMX_HASH_SIZE; i++) {
            char buf[4];
            snprintf(buf, sizeof(buf), "%02x", (uint8_t)computedHash[i]);
            computedHex += buf;
            snprintf(buf, sizeof(buf), "%02x", pblock->nSolution[i]);
            solutionHex += buf;
        }
        LogPrintf("CheckRandomXSolution(): HASH MISMATCH at height %d\n", height);
        LogPrintf("  computed : %s\n", computedHex);
        LogPrintf("  nSolution: %s\n", solutionHex);
        LogPrintf("  rxKey size=%lu, input size=%lu, nNonce=%s\n",
                  rxKey.size(), ssInput.size(), pblock->nNonce.ToString());
        return false;
    }

    LogPrint("randomx", "CheckRandomXSolution(): valid at height %d\n", height);
    return true;
}

// ============================================================================================
// Parallel RandomX pre-verification pool (Stage 2).
// One shared light-mode cache (holding a single key at a time) + per-thread VMs, mirroring the
// miner's RandomXDatasetManager pattern (miner.cpp). The connect thread (ActivateBestChainStep)
// loads the cache key for a same-key group of about-to-be-connected blocks, dispatches them to
// this pool, and barrier-waits; each worker hashes on its own VM (sharing the read-only cache)
// and, on a match, sets the block's transient fRandomXVerified flag so the inline check in
// CheckBlockHeader can be skipped. The inline path remains the consensus authority for anything
// not pre-verified, so the pool can only ever flip false->true on a real hash match.
static boost::shared_mutex      g_rxvMutex;            // shared = hashing; exclusive = cache (re)init
static randomx_cache*           g_rxvCache = nullptr;  // shared, read-only during hashing
static std::string              g_rxvKey;              // key currently loaded into g_rxvCache
static randomx_flags            g_rxvFlags;
static thread_local randomx_vm* tls_rxvVM    = nullptr;
static thread_local std::string tls_rxvVMKey;

CCheckQueue<CRandomXCheck> rxCheckQueue(1); // batch size 1: each item is ~tens of ms

bool RandomXValidatorPrepareKey(const std::string& rxKey)
{
    boost::unique_lock<boost::shared_mutex> lock(g_rxvMutex);
    if (g_rxvCache == nullptr) {
        g_rxvFlags = randomx_get_flags();
        g_rxvCache = randomx_alloc_cache(g_rxvFlags | RANDOMX_FLAG_LARGE_PAGES);
        if (g_rxvCache == nullptr)
            g_rxvCache = randomx_alloc_cache(g_rxvFlags);
        if (g_rxvCache == nullptr) {
            LogPrintf("RandomXValidatorPrepareKey: cache alloc failed; parallel pre-verify disabled\n");
            return false;
        }
        randomx_init_cache(g_rxvCache, rxKey.data(), rxKey.size());
        g_rxvKey = rxKey;
        return true;
    }
    if (g_rxvKey != rxKey) {
        randomx_init_cache(g_rxvCache, rxKey.data(), rxKey.size());
        g_rxvKey = rxKey;
    }
    return true;
}

bool CRandomXCheck::operator()()
{
    boost::shared_lock<boost::shared_mutex> lock(g_rxvMutex);
    // The connect thread set the shared cache to one key before dispatching this group. If this
    // item's key doesn't match (e.g. a key-rotation straggler) or the cache is unavailable, skip it
    // and leave *presult false — the inline CheckRandomXSolution will verify it.
    if (g_rxvCache == nullptr || g_rxvKey != rxKey)
        return true;
    if (tls_rxvVM == nullptr) {
        tls_rxvVM = randomx_create_vm(g_rxvFlags, g_rxvCache, nullptr);
        if (tls_rxvVM == nullptr)
            return true; // cannot verify here -> inline fallback
        tls_rxvVMKey = g_rxvKey;
    } else if (tls_rxvVMKey != g_rxvKey) {
        // Cache was re-initialized to a new key since this VM last ran; rebind.
        randomx_vm_set_cache(tls_rxvVM, g_rxvCache);
        tls_rxvVMKey = g_rxvKey;
    }
    unsigned char h[RANDOMX_HASH_SIZE];
    randomx_calculate_hash(tls_rxvVM, input.data(), input.size(), h);
    if (memcmp(h, expected, RANDOMX_HASH_SIZE) == 0 && presult != nullptr)
        *presult = true;
    return true; // ALWAYS true: never short-circuit the queue; per-block result is in *presult
}

void ThreadRandomXVerify()
{
    RenameThread("hush-rxverify");
    rxCheckQueue.Thread();
}

void RandomXValidatorShutdown()
{
    boost::unique_lock<boost::shared_mutex> lock(g_rxvMutex);
    // Per-thread VMs are intentionally leaked (process exiting); release the shared cache.
    if (g_rxvCache != nullptr) {
        randomx_release_cache(g_rxvCache);
        g_rxvCache = nullptr;
    }
}

int32_t hush_chosennotary(int32_t *notaryidp,int32_t height,uint8_t *pubkey33,uint32_t timestamp);
int32_t hush_currentheight();
void hush_index2pubkey33(uint8_t *pubkey33,CBlockIndex *pindex,int32_t height);
bool hush_checkopret(CBlock *pblock, CScript &merkleroot);
CScript hush_makeopret(CBlock *pblock, bool fNew);
extern int32_t HUSH_CHOSEN_ONE;
extern char SMART_CHAIN_SYMBOL[HUSH_SMART_CHAIN_MAXLEN];
#define HUSH_DPOW_GAP 2000

int32_t hush_eligiblenotary(uint8_t pubkeys[66][33],int32_t *mids,uint32_t blocktimes[66],int32_t *nonzpkeysp,int32_t height);
int32_t HUSH_LOADINGBLOCKS = 1;

extern std::string NOTARY_PUBKEY;

bool CheckProofOfWork(const CBlockHeader &blkHeader, uint8_t *pubkey33, int32_t height, const Consensus::Params& params)
{
    extern int32_t HUSH_REWIND;
    uint256 hash;
    bool fNegative,fOverflow; uint8_t origpubkey33[33]; int32_t i,nonzpkeys=0,nonz=0,special=0,special2=0,notaryid=-1,flag = 0, mids[66]; uint32_t tiptime,blocktimes[66];
    arith_uint256 bnTarget; uint8_t pubkeys[66][33];
    //for (i=31; i>=0; i--)
    //    fprintf(stderr,"%02x",((uint8_t *)&hash)[i]);
    //fprintf(stderr," checkpow\n");
    memcpy(origpubkey33,pubkey33,33);
    memset(blocktimes,0,sizeof(blocktimes));
    tiptime = hush_chainactive_timestamp();
    bnTarget.SetCompact(blkHeader.nBits, &fNegative, &fOverflow);
    if ( height == 0 ) {
        height = hush_currentheight() + 1;
        //fprintf(stderr,"set height to %d\n",height);
    }
    arith_uint256 bnLimit = (height <= 1 || ASSETCHAINS_ALGO == ASSETCHAINS_EQUIHASH) ? UintToArith256(params.powLimit) : UintToArith256(params.powAlternate);
    if (fNegative || bnTarget == 0 || fOverflow || bnTarget > bnLimit)
        return error("CheckProofOfWork(): nBits below minimum work");
    if ( ASSETCHAINS_STAKED != 0 )
    {
        arith_uint256 bnMaxPoSdiff;
        bnTarget.SetCompact(HUSH_MINDIFF_NBITS,&fNegative,&fOverflow);
    }

    // Check proof of work matches claimed amount
    if ( UintToArith256(hash = blkHeader.GetHash()) > bnTarget )
    {
        // During initial block loading/sync, skip PoW validation for blocks
        // before RandomX validation height. After activation, always validate
        // to prevent injection of blocks with fake PoW.
        if ( HUSH_LOADINGBLOCKS != 0 ) {
            if (ASSETCHAINS_ALGO == ASSETCHAINS_RANDOMX && ASSETCHAINS_RANDOMX_VALIDATION > 0 && height >= ASSETCHAINS_RANDOMX_VALIDATION) {
                // Fall through to reject the block — do NOT skip validation after activation
            } else {
                return true;
            }
        }

        if ( SMART_CHAIN_SYMBOL[0] != 0 || height > 792000 )
        {
            if ( Params().NetworkIDString() != "regtest" )
            {
                for (i=31; i>=0; i--)
                    fprintf(stderr,"%02x",((uint8_t *)&hash)[i]);
                fprintf(stderr," hash vs ");
                for (i=31; i>=0; i--)
                    fprintf(stderr,"%02x",((uint8_t *)&bnTarget)[i]);
                fprintf(stderr," ht.%d special.%d special2.%d flag.%d notaryid.%d mod.%d error\n",height,special,special2,flag,notaryid,(height % 35));
                for (i=0; i<33; i++)
                    fprintf(stderr,"%02x",pubkey33[i]);
                fprintf(stderr," <- pubkey\n");
                for (i=0; i<33; i++)
                    fprintf(stderr,"%02x",origpubkey33[i]);
                fprintf(stderr," <- origpubkey\n");
            }
            return false;
        }
    }
    /*for (i=31; i>=0; i--)
     fprintf(stderr,"%02x",((uint8_t *)&hash)[i]);
     fprintf(stderr," hash vs ");
     for (i=31; i>=0; i--)
     fprintf(stderr,"%02x",((uint8_t *)&bnTarget)[i]);
     fprintf(stderr," height.%d notaryid.%d PoW valid\n",height,notaryid);*/
    return true;
}

CChainPower GetBlockProof(const CBlockIndex& block)
{
    arith_uint256 bnWorkTarget;

    bool fNegative;
    bool fOverflow;
    bnWorkTarget.SetCompact(block.nBits, &fNegative, &fOverflow);

    if (fNegative || fOverflow || bnWorkTarget == 0)
        return CChainPower(0);

    return CChainPower(0, (~bnWorkTarget / (bnWorkTarget + 1)) + 1);
}

int64_t GetBlockProofEquivalentTime(const CBlockIndex& to, const CBlockIndex& from, const CBlockIndex& tip, const Consensus::Params& params)
{
    arith_uint256 r;
    int sign = 1;
    if (to.chainPower.chainWork > from.chainPower.chainWork) {
        r = to.chainPower.chainWork - from.chainPower.chainWork;
    } else {
        r = from.chainPower.chainWork - to.chainPower.chainWork;
        sign = -1;
    }
    r = r * arith_uint256(params.nPowTargetSpacing) / GetBlockProof(tip).chainWork;
    if (r.bits() > 63) {
        return sign * std::numeric_limits<int64_t>::max();
    }
    return sign * r.GetLow64();
}
