// Copyright (c) 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 "amount.h"
#include "chainparams.h"
#include "consensus/consensus.h"
#include "consensus/validation.h"
#include "core_io.h"
#ifdef ENABLE_MINING
#include "crypto/equihash.h"
#endif
#include "init.h"
#include "main.h"
#include "metrics.h"
#include "miner.h"
#include "net.h"
#include "pow.h"
#include "rpc/server.h"
#include "txmempool.h"
#include "util.h"
#include "validationinterface.h"
#ifdef ENABLE_WALLET
#include "wallet/wallet.h"
#endif

#include <stdint.h>

#include <boost/assign/list_of.hpp>

#include <univalue.h>

#include "compat/byteswap.h" // bswap_32 for the stratum wire fields (version/time/bits)
#ifndef WIN32
// stratummine (below) is a POSIX-only reference RandomX stratum miner used to exercise the pool
// path end-to-end. It reuses DragonX's own RandomX + GetRandomXInput so its hash is byte-identical
// to CheckRandomXSolution. Not built on Windows (raw POSIX sockets).
#include "RandomX/src/randomx.h"
#include <sys/select.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <unistd.h>
#endif

using namespace std;

#include "hush_defs.h"

extern int32_t ASSETCHAINS_FOUNDERS;
extern std::string devtax_scriptpub_for_height(uint32_t height);
extern std::string devtax_address_for_height(uint32_t height);
uint64_t the_commission(const CBlock *pblock,int32_t height);
int32_t hush_blockload(CBlock& block,CBlockIndex *pindex);
arith_uint256 hush_PoWtarget(int32_t *percPoSp,arith_uint256 target,int32_t height,int32_t goalperc);

/**
 * Return average network hashes per second based on the last 'lookup' blocks,
 * or over the difficulty averaging window if 'lookup' is nonpositive.
 * If 'height' is nonnegative, compute the estimate at the time when a given block was found.
 */
int64_t GetNetworkHashPS(int lookup, int height) 
{
    CBlockIndex *pb = chainActive.LastTip();

    if (height >= 0 && height < chainActive.Height())
        pb = chainActive[height];

    if (pb == NULL || !pb->GetHeight())
        return 0;

    // If lookup is nonpositive, then use difficulty averaging window.
    if (lookup <= 0)
        lookup = Params().GetConsensus().nPowAveragingWindow;

    // If lookup is larger than chain, then set it to chain length.
    if (lookup > pb->GetHeight())
        lookup = pb->GetHeight();

    CBlockIndex *pb0 = pb;
    int64_t minTime = pb0->GetBlockTime();
    int64_t maxTime = minTime;
    for (int i = 0; i < lookup; i++) {
        pb0 = pb0->pprev;
        int64_t time = pb0->GetBlockTime();
        minTime = std::min(time, minTime);
        maxTime = std::max(time, maxTime);
    }

    // In case there's a situation where minTime == maxTime, we don't want a divide by zero exception.
    if (minTime == maxTime)
        return 0;

    arith_uint256 workDiff = pb->chainPower.chainWork - pb0->chainPower.chainWork;
    int64_t timeDiff = maxTime - minTime;

    return (int64_t)(workDiff.getdouble() / timeDiff);
}

UniValue getlocalsolps(const UniValue& params, bool fHelp, const CPubKey& mypk)
{
    if (fHelp)
        throw runtime_error(
            "getlocalsolps\n"
            "\nReturns the average local solutions per second since this node was started.\n"
            "This is the same information shown on the metrics screen (if enabled).\n"
            "\nResult:\n"
            "xxx.xxxxx     (numeric) Solutions per second average\n"
            "\nExamples:\n"
            + HelpExampleCli("getlocalsolps", "")
            + HelpExampleRpc("getlocalsolps", "")
       );

    LOCK(cs_main);
    return GetLocalSolPS();
}

UniValue getnetworksolps(const UniValue& params, bool fHelp, const CPubKey& mypk)
{
    if (fHelp || params.size() > 2)
        throw runtime_error(
            "getnetworksolps ( blocks height )\n"
            "\nReturns the estimated network solutions per second based on the last n blocks.\n"
            "Pass in [blocks] to override # of blocks, -1 specifies over difficulty averaging window.\n"
            "Pass in [height] to estimate the network speed at the time when a certain block was found.\n"
            "\nArguments:\n"
            "1. blocks     (numeric, optional, default=120) The number of blocks, or -1 for blocks over difficulty averaging window.\n"
            "2. height     (numeric, optional, default=-1) To estimate at the time of the given height.\n"
            "\nResult:\n"
            "x             (numeric) Solutions per second estimated\n"
            "\nExamples:\n"
            + HelpExampleCli("getnetworksolps", "")
            + HelpExampleRpc("getnetworksolps", "")
       );

    LOCK(cs_main);
    return GetNetworkHashPS(params.size() > 0 ? params[0].get_int() : 120, params.size() > 1 ? params[1].get_int() : -1);
}

UniValue getnetworkhashps(const UniValue& params, bool fHelp, const CPubKey& mypk)
{
    if (fHelp || params.size() > 2)
        throw runtime_error(
            "getnetworkhashps ( blocks height )\n"
            "\nDEPRECATED - left for backwards-compatibility. Use getnetworksolps instead.\n"
            "\nReturns the estimated network solutions per second based on the last n blocks.\n"
            "Pass in [blocks] to override # of blocks, -1 specifies over difficulty averaging window.\n"
            "Pass in [height] to estimate the network speed at the time when a certain block was found.\n"
            "\nArguments:\n"
            "1. blocks     (numeric, optional, default=120) The number of blocks, or -1 for blocks over difficulty averaging window.\n"
            "2. height     (numeric, optional, default=-1) To estimate at the time of the given height.\n"
            "\nResult:\n"
            "x             (numeric) Solutions per second estimated\n"
            "\nExamples:\n"
            + HelpExampleCli("getnetworkhashps", "")
            + HelpExampleRpc("getnetworkhashps", "")
       );

    LOCK(cs_main);
    return GetNetworkHashPS(params.size() > 0 ? params[0].get_int() : 120, params.size() > 1 ? params[1].get_int() : -1);
}

#ifdef ENABLE_MINING
UniValue getgenerate(const UniValue& params, bool fHelp, const CPubKey& mypk)
{
    if (fHelp || params.size() != 0)
        throw runtime_error(
            "getgenerate\n"
            "\nReturn if the server is set to mine coins or not. The default is false.\n"
            "It is set with the command line argument -gen (or DRAGONX.conf setting gen).\n"
            "It can also be set with the setgenerate call.\n"
            "\nResult\n"
            "{\n"
            "  \"generate\": true|false     (boolean) If mining is on or off (see setgenerate)\n"
            "  \"numthreads\": n            (numeric) The processor limit for mining. (see setgenerate)\n"
            "}\n"
            "\nExamples:\n"
            + HelpExampleCli("getgenerate", "")
            + HelpExampleRpc("getgenerate", "")
        );

    LOCK(cs_main);
    UniValue obj(UniValue::VOBJ);
    obj.push_back(Pair("generate",         GetBoolArg("-gen", false) && GetBoolArg("-genproclimit", -1) != 0 ));
    obj.push_back(Pair("numthreads",       (int64_t)HUSH_MININGTHREADS));
    return obj;
}

extern uint8_t NOTARY_PUBKEY33[33];

//Value generate(const Array& params, bool fHelp)
UniValue generate(const UniValue& params, bool fHelp, const CPubKey& mypk)
{
    if (fHelp || params.size() < 1 || params.size() > 1)
        throw runtime_error(
            "generate numblocks\n"
            "\nMine blocks immediately (before the RPC call returns)\n"
            "\nNote: this function can only be used on the regtest network\n"
            "\nArguments:\n"
            "1. numblocks    (numeric) How many blocks are generated immediately.\n"
            "\nResult\n"
            "[ blockhashes ]     (array) hashes of blocks generated\n"
            "\nExamples:\n"
            "\nGenerate 11 blocks\n"
            + HelpExampleCli("generate", "11")
        );

    if (GetArg("-mineraddress", "").empty()) {
#ifdef ENABLE_WALLET
        if (!pwalletMain) {
            throw JSONRPCError(RPC_METHOD_NOT_FOUND, "Wallet disabled and -mineraddress not set");
        }
#else
        throw JSONRPCError(RPC_METHOD_NOT_FOUND, "Full node compiled without wallet and -mineraddress not set");
#endif
    }
    if (!Params().MineBlocksOnDemand())
    {
        if ( params[0].get_int() == 1 )
        {
            mapArgs["disablemining"] = "1";
            throw JSONRPCError(RPC_METHOD_NOT_FOUND, "Mining Disabled");
        }
        else 
        {
            mapArgs["disablemining"] = "0";
            throw JSONRPCError(RPC_METHOD_NOT_FOUND, "Mining Enabled");
        }
    }

    int nHeightStart = 0;
    int nHeightEnd = 0;
    int nHeight = 0;
    int nGenerate = params[0].get_int();
#ifdef ENABLE_WALLET
    CReserveKey reservekey(pwalletMain);
#endif

    {   // Don't keep cs_main locked
        LOCK(cs_main);
        nHeightStart = chainActive.Height();
        nHeight = nHeightStart;
        nHeightEnd = nHeightStart+nGenerate;
    }
    unsigned int nExtraNonce = 0;
    UniValue blockHashes(UniValue::VARR);
    unsigned int n = Params().EquihashN();
    unsigned int k = Params().EquihashK();
    uint64_t lastTime = 0;
    while (nHeight < nHeightEnd)
    {
        // Validation may fail if block generation is too fast
        if (GetTime() == lastTime) MilliSleep(1001);
        lastTime = GetTime();

#ifdef ENABLE_WALLET
        std::unique_ptr<CBlockTemplate> pblocktemplate(CreateNewBlockWithKey(reservekey,nHeight,HUSH_MAXGPUCOUNT));
#else
        std::unique_ptr<CBlockTemplate> pblocktemplate(CreateNewBlockWithKey());
#endif
        if (!pblocktemplate.get())
            throw JSONRPCError(RPC_INTERNAL_ERROR, "Wallet keypool empty");
        CBlock *pblock = &pblocktemplate->block;
        {
            LOCK(cs_main);
            IncrementExtraNonce(pblock, chainActive.LastTip(), nExtraNonce);
        }

        // Hash state
        crypto_generichash_blake2b_state eh_state;
        EhInitialiseState(n, k, eh_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(&eh_state, (unsigned char*)&ss[0], ss.size());

        while (true) {
            // Yes, there is a chance every nonce could fail to satisfy the -regtest
            // target -- 1 in 2^(2^256). That ain't gonna happen
            pblock->nNonce = ArithToUint256(UintToArith256(pblock->nNonce) + 1);

            // H(I||V||...
            crypto_generichash_blake2b_state curr_state;
            curr_state = eh_state;
            crypto_generichash_blake2b_update(&curr_state,
                                              pblock->nNonce.begin(),
                                              pblock->nNonce.size());

            // (x_1, x_2, ...) = A(I, V, n, k)
            std::function<bool(std::vector<unsigned char>)> validBlock =
                    [&pblock](std::vector<unsigned char> soln)
            {
                LOCK(cs_main);
                pblock->nSolution = soln;
                solutionTargetChecks.increment();
                return CheckProofOfWork(*pblock,NOTARY_PUBKEY33,chainActive.Height(),Params().GetConsensus());
            };
            bool found = EhBasicSolveUncancellable(n, k, curr_state, validBlock);
            ehSolverRuns.increment();
            if (found) {
                goto endloop;
            }
        }
endloop:
        CValidationState state;
        if (!ProcessNewBlock(1,chainActive.LastTip()->GetHeight()+1,state, NULL, pblock, true, NULL))
            throw JSONRPCError(RPC_INTERNAL_ERROR, "ProcessNewBlock, block not accepted");
        ++nHeight;
        blockHashes.push_back(pblock->GetHash().GetHex());
    }
    return blockHashes;
}


UniValue setgenerate(const UniValue& params, bool fHelp, const CPubKey& mypk)
{
    if (fHelp || params.size() < 1 || params.size() > 2)
        throw runtime_error(
            "setgenerate generate ( genproclimit )\n"
            "\nSet 'generate' true to turn mining/generation on or false turn both off.\n"
            "Mining is limited to 'genproclimit' processors, -1 is unlimited\n"
            "See the getgenerate call for the current setting.\n"
            "\nArguments:\n"
            "1. generate         (boolean, required) Set to true to turn on generation, off to turn off.\n"
            "2. genproclimit     (numeric, optional) Set processor limit when generation is on. Can be -1 for unlimited.\n"
            "\nExamples:\n"
            "\nSet the generation on with a limit of one processor\n"
            + HelpExampleCli("setgenerate", "true 1") +
            "\nCheck the setting\n"
            + HelpExampleCli("getgenerate", "") +
            "\nUsing json rpc\n"
            + HelpExampleRpc("setgenerate", "true, 1")
        );

    if (GetArg("-mineraddress", "").empty()) {
#ifdef ENABLE_WALLET
        if (!pwalletMain) {
            throw JSONRPCError(RPC_METHOD_NOT_FOUND, "Wallet disabled and -mineraddress not set");
        }
#else
        throw JSONRPCError(RPC_METHOD_NOT_FOUND, "Full node compiled without wallet and -mineraddress not set");
#endif
    }
    if (Params().MineBlocksOnDemand())
        throw JSONRPCError(RPC_METHOD_NOT_FOUND, "Use the generate method instead of setgenerate on this network");

    bool fGenerate = true;
    if (params.size() > 0)
        fGenerate = params[0].get_bool();

    int nGenProcLimit = GetArg("-genproclimit", 0);;
    if (params.size() > 1)
    {
        nGenProcLimit = params[1].get_int();
        //if (nGenProcLimit == 0)
        //    fGenerate = false;
    }

    HUSH_MININGTHREADS = (int32_t)nGenProcLimit;
	LogPrint("mining","%s:HUSH_MININGTHREADS=%d\n", __FUNCTION__, HUSH_MININGTHREADS);

    mapArgs["-gen"]           = (fGenerate ? "1" : "0");
    mapArgs ["-genproclimit"] = itostr(HUSH_MININGTHREADS);

#ifdef ENABLE_WALLET
    GenerateBitcoins(fGenerate, pwalletMain, nGenProcLimit);
#else
    GenerateBitcoins(fGenerate, nGenProcLimit);
#endif

    return NullUniValue;
}
#endif

CBlockIndex *hush_chainactive(int32_t height);
arith_uint256 zawy_ctB(arith_uint256 bnTarget,uint32_t solvetime);

UniValue genminingCSV(const UniValue& params, bool fHelp, const CPubKey& mypk)
{
    int32_t i,z,height; uint32_t solvetime,prevtime=0; FILE *fp; char str[65],str2[65],fname[256]; uint256 hash; arith_uint256 bnTarget; CBlockIndex *pindex; bool fNegative,fOverflow; UniValue result(UniValue::VOBJ);
    if (fHelp || params.size() != 0 )
        throw runtime_error("genminingCSV\n");
    LOCK(cs_main);
    sprintf(fname,"%s_mining.csv",SMART_CHAIN_SYMBOL[0] == 0 ? "DRAGONX" : SMART_CHAIN_SYMBOL);
    if ( (fp= fopen(fname,"wb")) != 0 )
    {
        fprintf(fp,"height,nTime,nBits,bnTarget,bnTargetB,diff,solvetime\n");
        height = hush_nextheight();
        for (i=0; i<height; i++)
        {
            if ( (pindex= hush_chainactive(i)) != 0 )
            {
                bnTarget.SetCompact(pindex->nBits,&fNegative,&fOverflow);
                solvetime = (prevtime==0) ? 0 : (int32_t)(pindex->nTime - prevtime);
                for (z=0; z<16; z++)
                    sprintf(&str[z<<1],"%02x",((uint8_t *)&bnTarget)[31-z]);
                str[32] = 0;
                //hash = pindex->GetBlockHash();
                memset(&hash,0,sizeof(hash));
                if ( i >= 64 && (pindex->nBits & 3) != 0 )
                    hash = ArithToUint256(zawy_ctB(bnTarget,solvetime));
                for (z=0; z<16; z++)
                    sprintf(&str2[z<<1],"%02x",((uint8_t *)&hash)[31-z]);
                str2[32] = 0; fprintf(fp,"%d,%u,%08x,%s,%s,%.1f,%d\n",i,pindex->nTime,pindex->nBits,str,str2,GetDifficulty(pindex),solvetime);
                prevtime = pindex->nTime;
            }
        }
        fclose(fp);
        result.push_back(Pair("result", "success"));
        result.push_back(Pair("created", fname));
    }
    else
    {
        result.push_back(Pair("result", "success"));
        result.push_back(Pair("error", "couldnt create mining.csv"));
        result.push_back(Pair("filename", fname));
    }
    return(result);
}
                            
UniValue getmininginfo(const UniValue& params, bool fHelp, const CPubKey& mypk)
{
    if (fHelp || params.size() != 0)
        throw runtime_error(
            "getmininginfo\n"
            "\nReturns a json object containing mining-related information."
            "\nResult:\n"
            "{\n"
            "  \"blocks\": nnn,             (numeric) The current block\n"
            "  \"currentblocksize\": nnn,   (numeric) The last block size\n"
            "  \"currentblocktx\": nnn,     (numeric) The last block transaction\n"
            "  \"difficulty\": xxx.xxxxx    (numeric) The current difficulty\n"
            "  \"errors\": \"...\"          (string) Current errors\n"
            "  \"generate\": true|false     (boolean) If the generation is on or off (see getgenerate or setgenerate calls)\n"
            "  \"genproclimit\": n          (numeric) The processor limit for generation. -1 if no generation. (see getgenerate or setgenerate calls)\n"
            "  \"localsolps\": xxx.xxxxx    (numeric) The average local solution rate in Sol/s since this node was started\n"
            "  \"networksolps\": x          (numeric) The estimated network solution rate in Sol/s\n"
            "  \"pooledtx\": n              (numeric) The size of the mem pool\n"
            "  \"testnet\": true|false      (boolean) If using testnet or not\n"
            "  \"chain\": \"xxxx\",         (string) current network name as defined in BIP70 (main, test, regtest)\n"
            "}\n"
            "\nExamples:\n"
            + HelpExampleCli("getmininginfo", "")
            + HelpExampleRpc("getmininginfo", "")
        );


    LOCK(cs_main);

    UniValue obj(UniValue::VOBJ);
    obj.push_back(Pair("blocks",           (int)chainActive.Height()));
    obj.push_back(Pair("currentblocksize", (uint64_t)nLastBlockSize));
    obj.push_back(Pair("currentblocktx",   (uint64_t)nLastBlockTx));
    obj.push_back(Pair("difficulty",       (double)GetNetworkDifficulty()));
    obj.push_back(Pair("errors",           GetWarnings("statusbar")));
    obj.push_back(Pair("genproclimit",     (int)GetArg("-genproclimit", -1)));
    // DragonX is RandomX-only; the Equihash sol/s reporting path was removed.
    obj.push_back(Pair("localhashps"  , GetBoolArg("-gen", false) ? getlocalsolps(params, false, mypk) : (double)0.0));
    obj.push_back(Pair("networkhashps",    getnetworksolps(params, false, mypk)));
    obj.push_back(Pair("pooledtx",         (uint64_t)mempool.size()));
    obj.push_back(Pair("testnet",          Params().TestnetToBeDeprecatedFieldRPC()));
    obj.push_back(Pair("chain",            Params().NetworkIDString()));
#ifdef ENABLE_MINING
    obj.push_back(Pair("generate",         GetBoolArg("-gen", false) && GetBoolArg("-genproclimit", -1) != 0 ));
    obj.push_back(Pair("numthreads",       (int64_t)HUSH_MININGTHREADS));
#endif
    return obj;
}


// NOTE: Unlike wallet RPC (which use BTC values), mining RPCs follow GBT (BIP 22) in using satoshi amounts
UniValue prioritisetransaction(const UniValue& params, bool fHelp, const CPubKey& mypk)
{
    if (fHelp || params.size() != 3)
        throw runtime_error(
            "prioritisetransaction <txid> <priority delta> <fee delta>\n"
            "Accepts the transaction into mined blocks at a higher (or lower) priority\n"
            "\nArguments:\n"
            "1. \"txid\"       (string, required) The transaction id.\n"
            "2. priority delta (numeric, required) The priority to add or subtract.\n"
            "                  The transaction selection algorithm considers the tx as it would have a higher priority.\n"
            "                  (priority of a transaction is calculated: coinage * value_in_satoshis / txsize) \n"
            "3. fee delta      (numeric, required) The fee value (in satoshis) to add (or subtract, if negative).\n"
            "                  The fee is not actually paid, only the algorithm for selecting transactions into a block\n"
            "                  considers the transaction as it would have paid a higher (or lower) fee.\n"
            "\nResult\n"
            "true              (boolean) Returns true\n"
            "\nExamples:\n"
            + HelpExampleCli("prioritisetransaction", "\"txid\" 0.0 10000")
            + HelpExampleRpc("prioritisetransaction", "\"txid\", 0.0, 10000")
        );

    LOCK(cs_main);

    uint256 hash = ParseHashStr(params[0].get_str(), "txid");
    CAmount nAmount = params[2].get_int64();

    mempool.PrioritiseTransaction(hash, params[0].get_str(), params[1].get_real(), nAmount);
    return true;
}


// NOTE: Assumes a conclusive result; if result is inconclusive, it must be handled by caller
static UniValue BIP22ValidationResult(const CValidationState& state)
{
    if (state.IsValid())
        return NullUniValue;

    std::string strRejectReason = state.GetRejectReason();
    if (state.IsError())
        throw JSONRPCError(RPC_VERIFY_ERROR, strRejectReason);
    if (state.IsInvalid())
    {
        if (strRejectReason.empty())
            return "rejected";
        return strRejectReason;
    }
    // Should be impossible
    return "valid?";
}

UniValue getblocktemplate(const UniValue& params, bool fHelp, const CPubKey& mypk)
{
    if (fHelp || params.size() > 1)
        throw runtime_error(
            "getblocktemplate ( \"jsonrequestobject\" )\n"
            "\nIf the request parameters include a 'mode' key, that is used to explicitly select between the default 'template' request or a 'proposal'.\n"
            "It returns data needed to construct a block to work on.\n"
            "See https://en.bitcoin.it/wiki/BIP_0022 for full specification.\n"

            "\nArguments:\n"
            "1. \"jsonrequestobject\"       (string, optional) A json object in the following spec\n"
            "     {\n"
            "       \"mode\":\"template\"    (string, optional) This must be set to \"template\", \"proposal\", \"disablecb\". Default is \"template\" \n"
            "       \"capabilities\":[       (array, optional) A list of strings\n"
            "           \"support\"           (string) client side supported feature, 'longpoll', 'coinbasetxn', 'coinbasevalue', 'proposal', 'serverlist', 'workid'\n"
            "           ,...\n"
            "         ]\n"
            "     }\n"
            "\n"

            "\nResult:\n"
            "{\n"
            "  \"version\" : n,                     (numeric) The block version\n"
            "  \"previousblockhash\" : \"xxxx\",    (string) The hash of current highest block\n"
            "  \"finalsaplingroothash\" : \"xxxx\", (string) The hash of the final sapling root\n"
            "  \"transactions\" : [                 (array) contents of non-coinbase transactions that should be included in the next block\n"
            "      {\n"
            "         \"data\" : \"xxxx\",          (string) transaction data encoded in hexadecimal (byte-for-byte)\n"
            "         \"hash\" : \"xxxx\",          (string) hash/id encoded in little-endian hexadecimal\n"
            "         \"depends\" : [              (array) array of numbers \n"
            "             n                        (numeric) transactions before this one (by 1-based index in 'transactions' list) that must be present in the final block if this one is\n"
            "             ,...\n"
            "         ],\n"
            "         \"fee\": n,                   (numeric) difference in value between transaction inputs and outputs (in Satoshis); for coinbase transactions, this is a negative Number of the total collected block fees (ie, not including the block subsidy); if key is not present, fee is unknown and clients MUST NOT assume there isn't one\n"
            "         \"sigops\" : n,               (numeric) total number of SigOps, as counted for purposes of block limits; if key is not present, sigop count is unknown and clients MUST NOT assume there aren't any\n"
            "         \"required\" : true|false     (boolean) if provided and true, this transaction must be in the final block\n"
            "      }\n"
            "      ,...\n"
            "  ],\n"
//            "  \"coinbasevalue\" : n,               (numeric) maximum allowable input to coinbase transaction, including the generation award and transaction fees (in Satoshis)\n"
            "  \"coinbasetxn\" : { ... },           (json object) information for coinbase transaction. Not present when mode=disablecb\n"
            "  \"target\" : \"xxxx\",               (string) The hash target\n"
            "  \"mintime\" : xxx,                   (numeric) The minimum timestamp appropriate for next block time in seconds since epoch (Jan 1 1970 GMT)\n"
            "  \"mutable\" : [                      (array of string) list of ways the block template may be changed \n"
            "     \"value\"                         (string) A way the block template may be changed, e.g. 'time', 'transactions', 'prevblock'\n"
            "     ,...\n"
            "  ],\n"
            "  \"noncerange\" : \"00000000ffffffff\",   (string) A range of valid nonces\n"
            "  \"sigoplimit\" : n,                 (numeric) limit of sigops in blocks\n"
            "  \"sizelimit\" : n,                  (numeric) limit of block size\n"
            "  \"curtime\" : ttt,                  (numeric) current timestamp in seconds since epoch (Jan 1 1970 GMT)\n"
            "  \"bits\" : \"xxx\",                 (string) compressed target of next block\n"
            "  \"height\" : n                      (numeric) The height of the next block\n"
            "}\n"

            "\nExamples:\n"
            + HelpExampleCli("getblocktemplate", "")
            + HelpExampleRpc("getblocktemplate", "")
         );

    LOCK(cs_main);

    // Wallet or miner address is required because we support coinbasetxn
    if (GetArg("-mineraddress", "").empty()) {
#ifdef ENABLE_WALLET
        if (!pwalletMain) {
            throw JSONRPCError(RPC_METHOD_NOT_FOUND, "Wallet disabled and -mineraddress not set");
        }
#else
        throw JSONRPCError(RPC_METHOD_NOT_FOUND, "Full node compiled without wallet and -mineraddress not set");
#endif
    }
    
    if ( GetArg("disablemining",false) )
        throw JSONRPCError(RPC_TYPE_ERROR, "Mining is Disabled");

    UniValue lpval = NullUniValue;
    // TODO: Re-enable coinbasevalue once a specification has been written
    bool coinbasetxn = true;
    std::string strMode;
    if (params.size() > 0)
    {
        const UniValue& oparam = params[0].get_obj();
        const UniValue& modeval = find_value(oparam, "mode");
        if (modeval.isStr())
            strMode = modeval.get_str();
        else if (modeval.isNull())
        {
            strMode = "template";
        }
        else
            throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid mode");
        lpval = find_value(oparam, "longpollid");

        if (strMode == "disablecb")
            coinbasetxn = false;

        if (strMode == "proposal")
        {
            const UniValue& dataval = find_value(oparam, "data");
            if (!dataval.isStr())
                throw JSONRPCError(RPC_TYPE_ERROR, "Missing data String key for proposal");

            CBlock block;
            if (!DecodeHexBlk(block, dataval.get_str()))
                throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "Block decode failed");

            uint256 hash = block.GetHash();
            BlockMap::iterator mi = mapBlockIndex.find(hash);
            if (mi != mapBlockIndex.end()) {
                CBlockIndex *pindex = mi->second;
                if (pindex)
                {
                    if (pindex->IsValid(BLOCK_VALID_SCRIPTS))
                        return "duplicate";
                    if (pindex->nStatus & BLOCK_FAILED_MASK)
                        return "duplicate-invalid";
                }
                return "duplicate-inconclusive";
            }

            CBlockIndex* const pindexPrev = chainActive.LastTip();
            // TestBlockValidity only supports blocks built on the current Tip
            if (block.hashPrevBlock != pindexPrev->GetBlockHash())
                return "inconclusive-not-best-prevblk";
            CValidationState state;
            TestBlockValidity(state, block, pindexPrev, false, true);
            return BIP22ValidationResult(state);
        }
    } else {
        strMode = "template";
    }

    bool fvNodesEmpty;
    {
        LOCK(cs_vNodes);
        fvNodesEmpty = vNodes.empty();
    }

    if (Params().MiningRequiresPeers() && fvNodesEmpty) {
        throw JSONRPCError(RPC_CLIENT_NOT_CONNECTED, "Hush has no peers yet!");
    }

    if (IsInitialBlockDownload()) {
        throw JSONRPCError(RPC_CLIENT_IN_INITIAL_DOWNLOAD, "Hush is downloading blocks...");
    }

    static unsigned int nTransactionsUpdatedLast;

    if (!lpval.isNull())
    {
        // Wait to respond until either the best block changes, OR a minute has passed and there are more transactions
        uint256 hashWatchedChain;
        boost::system_time checktxtime;
        unsigned int nTransactionsUpdatedLastLP;

        if (lpval.isStr())
        {
            // Format: <hashBestChain><nTransactionsUpdatedLast>
            std::string lpstr = lpval.get_str();

            hashWatchedChain.SetHex(lpstr.substr(0, 64));
            nTransactionsUpdatedLastLP = atoi64(lpstr.substr(64));
        }
        else
        {
            // NOTE: Spec does not specify behaviour for non-string longpollid, but this makes testing easier
            hashWatchedChain = chainActive.LastTip()->GetBlockHash();
            nTransactionsUpdatedLastLP = nTransactionsUpdatedLast;
        }

        // Release the wallet and main lock while waiting
        LEAVE_CRITICAL_SECTION(cs_main);
        {
            checktxtime = boost::get_system_time() + boost::posix_time::minutes(1);

            boost::unique_lock<boost::mutex> lock(csBestBlock);
            while (chainActive.LastTip()->GetBlockHash() == hashWatchedChain && IsRPCRunning())
            {
                if (!cvBlockChange.timed_wait(lock, checktxtime))
                {
                    // Timeout: Check transactions for update
                    if (mempool.GetTransactionsUpdated() != nTransactionsUpdatedLastLP)
                        break;
                    checktxtime += boost::posix_time::seconds(10);
                }
            }
        }
        ENTER_CRITICAL_SECTION(cs_main);

        if (!IsRPCRunning())
            throw JSONRPCError(RPC_CLIENT_NOT_CONNECTED, "Shutting down");
        // TODO: Maybe recheck connections/IBD and (if something wrong) send an expires-immediately template to stop miners?
    }

    // Update block
    static CBlockIndex* pindexPrev;
    static int64_t nStart;
    static CBlockTemplate* pblocktemplate;
    if (pindexPrev != chainActive.LastTip() ||
        (mempool.GetTransactionsUpdated() != nTransactionsUpdatedLast && GetTime() - nStart > 5))
    {
        // Clear pindexPrev so future calls make a new block, despite any failures from here on
        pindexPrev = NULL;

        // Store the pindexBest used before CreateNewBlockWithKey, to avoid races
        nTransactionsUpdatedLast = mempool.GetTransactionsUpdated();
        CBlockIndex* pindexPrevNew = chainActive.LastTip();
        nStart = GetTime();

        // Create new block
        if(pblocktemplate)
        {
            delete pblocktemplate;
            pblocktemplate = NULL;
        }
#ifdef ENABLE_WALLET
        CReserveKey reservekey(pwalletMain);
        LEAVE_CRITICAL_SECTION(cs_main);
        // MUST re-enter cs_main before letting an exception escape. The enclosing LOCK(cs_main) is
        // a scoped CMutexLock whose owns_lock is still true, so if CreateNewBlockWithKey throws
        // (any wallet/BDB fault: disk full, EMFILE, a corrupt wallet.dat) its destructor unlocks an
        // already-unlocked mutex during unwinding -> BOOST_VERIFY -> SIGABRT. Asserts cannot be
        // compiled out here (main.cpp #errors on NDEBUG), so this aborts the daemon instead of
        // returning the actionable error, and the abort happens inside unwinding so nothing is logged.
        try {
            pblocktemplate = CreateNewBlockWithKey(reservekey,pindexPrevNew->GetHeight()+1,HUSH_MAXGPUCOUNT,false);
        } catch (...) {
            ENTER_CRITICAL_SECTION(cs_main);
            throw;
        }
#else
        try {
            pblocktemplate = CreateNewBlockWithKey();
        } catch (...) {
            ENTER_CRITICAL_SECTION(cs_main);
            throw;
        }
#endif
        ENTER_CRITICAL_SECTION(cs_main);
        if (!pblocktemplate)
            throw std::runtime_error("CreateNewBlock(): create block failed");
            //throw JSONRPCError(RPC_OUT_OF_MEMORY, "Out of memory or no available utxo for staking");

        // Need to update only after we know CreateNewBlockWithKey succeeded
        pindexPrev = pindexPrevNew;
    }
    CBlock* pblock = &pblocktemplate->block; // pointer for convenience

    // Update nTime
    UpdateTime(pblock, Params().GetConsensus(), pindexPrev);
    pblock->nNonce = uint256();

    UniValue aCaps(UniValue::VARR); aCaps.push_back("proposal");

    UniValue txCoinbase = NullUniValue;
    UniValue transactions(UniValue::VARR);
    map<uint256, int64_t> setTxIndex;
    int i = 0;
    BOOST_FOREACH (const CTransaction& tx, pblock->vtx) {
        uint256 txHash = tx.GetHash();
        setTxIndex[txHash] = i++;

        //if (tx.IsCoinBase() && !coinbasetxn)
        //    continue;

        UniValue entry(UniValue::VOBJ);

        entry.push_back(Pair("data", EncodeHexTx(tx)));

        entry.push_back(Pair("hash", txHash.GetHex()));

        UniValue deps(UniValue::VARR);
        BOOST_FOREACH (const CTxIn &in, tx.vin)
        {
            if (setTxIndex.count(in.prevout.hash))
                deps.push_back(setTxIndex[in.prevout.hash]);
        }
        entry.push_back(Pair("depends", deps));

        int index_in_template = i - 1;
        entry.push_back(Pair("fee", pblocktemplate->vTxFees[index_in_template]));
        entry.push_back(Pair("sigops", pblocktemplate->vTxSigOps[index_in_template]));
        uint32_t nHeight = chainActive.LastTip()->GetHeight()+1;

        if (tx.IsCoinBase() && coinbasetxn == true ) {
            // Show founders' reward if it is required
            if (ASSETCHAINS_FOUNDERS && pblock->vtx[0].vout.size() > 1) {
                // Correct this if GetBlockTemplate changes the order
                entry.push_back(Pair("foundersreward", (int64_t)tx.vout[1].nValue));
                entry.push_back(Pair("foundersrewardaddress", devtax_address_for_height(nHeight)));
                entry.push_back(Pair("foundersrewardscriptpub", devtax_scriptpub_for_height(nHeight)));
            }
            CAmount nReward = GetBlockSubsidy(nHeight, Params().GetConsensus());
            entry.push_back(Pair("coinbasevalue", nReward));
            entry.push_back(Pair("required", true));
            txCoinbase = entry;
        } else {
            transactions.push_back(entry);
        }
    }

    UniValue aux(UniValue::VOBJ);
    aux.push_back(Pair("flags", HexStr(COINBASE_FLAGS.begin(), COINBASE_FLAGS.end())));

    arith_uint256 hashTarget = arith_uint256().SetCompact(pblock->nBits);

    static UniValue aMutable(UniValue::VARR);
    if (aMutable.empty())
    {
        aMutable.push_back("time");
        aMutable.push_back("transactions");
        aMutable.push_back("prevblock");
    }

    UniValue result(UniValue::VOBJ);
    result.push_back(Pair("capabilities", aCaps));
    result.push_back(Pair("version", pblock->nVersion));
    result.push_back(Pair("previousblockhash", pblock->hashPrevBlock.GetHex()));
    result.push_back(Pair("finalsaplingroothash", pblock->hashFinalSaplingRoot.GetHex()));
    result.push_back(Pair("transactions", transactions));
    if (coinbasetxn) {
        assert(txCoinbase.isObject());
        result.push_back(Pair("coinbasetxn", txCoinbase));
    } // else {
      //  result.push_back(Pair("coinbasevalue", (int64_t)pblock->vtx[0].vout[0].nValue));
    //}
    result.push_back(Pair("longpollid", chainActive.LastTip()->GetBlockHash().GetHex() + i64tostr(nTransactionsUpdatedLast)));
    result.push_back(Pair("target", hashTarget.GetHex()));
    result.push_back(Pair("mintime", (int64_t)pindexPrev->GetMedianTimePast()+1));
    result.push_back(Pair("mutable", aMutable));
    result.push_back(Pair("noncerange", "00000000ffffffff"));
    result.push_back(Pair("sigoplimit", (int64_t)MAX_BLOCK_SIGOPS));
    result.push_back(Pair("sizelimit", (int64_t)MAX_BLOCK_SIZE(chainActive.LastTip()->GetHeight()+1)));
    result.push_back(Pair("curtime", pblock->GetBlockTime()));
    result.push_back(Pair("bits", strprintf("%08x", pblock->nBits)));
    result.push_back(Pair("height", (int64_t)(pindexPrev->GetHeight()+1)));

    return result;
}


class submitblock_StateCatcher : public CValidationInterface
{
public:
    uint256 hash;
    bool found;
    CValidationState state;

    submitblock_StateCatcher(const uint256 &hashIn) : hash(hashIn), found(false), state() {};

protected:
    virtual void BlockChecked(const CBlock& block, const CValidationState& stateIn) {
        if (block.GetHash() != hash)
            return;
        found = true;
        state = stateIn;
    };
};

UniValue submitblock(const UniValue& params, bool fHelp, const CPubKey& mypk)
{
    if (fHelp || params.size() < 1 || params.size() > 2)
        throw runtime_error(
            "submitblock \"hexdata\" ( \"jsonparametersobject\" )\n"
            "\nAttempts to submit new block to network.\n"
            "The 'jsonparametersobject' parameter is currently ignored.\n"
            "See https://en.bitcoin.it/wiki/BIP_0022 for full specification.\n"

            "\nArguments\n"
            "1. \"hexdata\"    (string, required) the hex-encoded block data to submit\n"
            "2. \"jsonparametersobject\"     (string, optional) object of optional parameters\n"
            "    {\n"
            "      \"workid\" : \"id\"    (string, optional) if the server provided a workid, it MUST be included with submissions\n"
            "    }\n"
            "\nResult:\n"
            "\"duplicate\" - node already has valid copy of block\n"
            "\"duplicate-invalid\" - node already has block, but it is invalid\n"
            "\"duplicate-inconclusive\" - node already has block but has not validated it\n"
            "\"inconclusive\" - node has not validated the block, it may not be on the node's current best chain\n"
            "\"rejected\" - block was rejected as invalid\n"
            "For more information on submitblock parameters and results, see: https://github.com/bitcoin/bips/blob/master/bip-0022.mediawiki#block-submission\n"
            "\nExamples:\n"
            + HelpExampleCli("submitblock", "\"mydata\"")
            + HelpExampleRpc("submitblock", "\"mydata\"")
        );

    CBlock block;
    if (!DecodeHexBlk(block, params[0].get_str()))
        throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "Block decode failed");

    uint256 hash = block.GetHash();
    bool fBlockPresent = false;
    {
        LOCK(cs_main);
        BlockMap::iterator mi = mapBlockIndex.find(hash);
        if (mi != mapBlockIndex.end()) {
            CBlockIndex *pindex = mi->second;
            if (pindex)
            {
                if (pindex->IsValid(BLOCK_VALID_SCRIPTS))
                    return "duplicate";
                if (pindex->nStatus & BLOCK_FAILED_MASK)
                    return "duplicate-invalid";
                // Otherwise, we might only have the header - process the block before returning
                fBlockPresent = true;
            }
        }
    }

    CValidationState state;
    submitblock_StateCatcher sc(block.GetHash());
    RegisterValidationInterface(&sc);
    bool fAccepted = ProcessNewBlock(1,chainActive.LastTip()->GetHeight()+1,state, NULL, &block, true, NULL);
    UnregisterValidationInterface(&sc);
    if (fBlockPresent)
    {
        if (fAccepted && !sc.found)
            return "duplicate-inconclusive";
        return "duplicate";
    }
    if (fAccepted)
    {
        if (!sc.found)
            return "inconclusive";
        state = sc.state;
    }
    return BIP22ValidationResult(state);
}

UniValue estimatefee(const UniValue& params, bool fHelp, const CPubKey& mypk)
{
    if (fHelp || params.size() != 1)
        throw runtime_error(
            "estimatefee nblocks\n"
            "\nEstimates the approximate fee per kilobyte\n"
            "needed for a transaction to begin confirmation\n"
            "within nblocks blocks.\n"
            "\nArguments:\n"
            "1. nblocks     (numeric)\n"
            "\nResult:\n"
            "n :    (numeric) estimated fee-per-kilobyte\n"
            "\n"
            "-1.0 is returned if not enough transactions and\n"
            "blocks have been observed to make an estimate.\n"
            "\nExample:\n"
            + HelpExampleCli("estimatefee", "6")
            );

    RPCTypeCheck(params, boost::assign::list_of(UniValue::VNUM));

    int nBlocks = params[0].get_int();
    if (nBlocks < 1)
        nBlocks = 1;

    CFeeRate feeRate = mempool.estimateFee(nBlocks);
    if (feeRate == CFeeRate(0))
        return -1.0;

    return ValueFromAmount(feeRate.GetFeePerK());
}

UniValue estimatepriority(const UniValue& params, bool fHelp, const CPubKey& mypk)
{
    if (fHelp || params.size() != 1)
        throw runtime_error(
            "estimatepriority nblocks\n"
            "\nEstimates the approximate priority\n"
            "a zero-fee transaction needs to begin confirmation\n"
            "within nblocks blocks.\n"
            "\nArguments:\n"
            "1. nblocks     (numeric)\n"
            "\nResult:\n"
            "n :    (numeric) estimated priority\n"
            "\n"
            "-1.0 is returned if not enough transactions and\n"
            "blocks have been observed to make an estimate.\n"
            "\nExample:\n"
            + HelpExampleCli("estimatepriority", "6")
            );

    RPCTypeCheck(params, boost::assign::list_of(UniValue::VNUM));

    int nBlocks = params[0].get_int();
    if (nBlocks < 1)
        nBlocks = 1;

    return mempool.estimatePriority(nBlocks);
}

UniValue getblocksubsidy(const UniValue& params, bool fHelp, const CPubKey& mypk)
{
    if (fHelp || params.size() > 1)
        throw runtime_error(
            "getblocksubsidy height\n"
            "\nReturns block subsidy reward, taking into account the mining slow start and the founders reward, of block at index provided.\n"
            "\nArguments:\n"
            "1. height         (numeric, optional) The block height.  If not provided, defaults to the current height of the chain.\n"
            "\nResult:\n"
            "{\n"
            "  \"miner\" : x.xxx           (numeric) The mining reward amount in DRAGONX.\n"
            "  \"ac_pubkey\" : x.xxx       (numeric) The mining reward amount in DRAGONX.\n"
            "}\n"
            "\nExamples:\n"
            + HelpExampleCli("getblocksubsidy", "1000")
            + HelpExampleRpc("getblocksubsidy", "1000")
        );

    LOCK(cs_main);
    int nHeight = (params.size()==1) ? params[0].get_int() : chainActive.Height();
    if (nHeight < 0)
        throw JSONRPCError(RPC_INVALID_PARAMETER, "Block height out of range");
    
    CAmount nFoundersReward = 0;
    CAmount nReward = GetBlockSubsidy(nHeight, Params().GetConsensus());
    UniValue result(UniValue::VOBJ);
    result.push_back(Pair("miner", ValueFromAmount(nReward)));
    
    // the value of ASSETCHAINS_SCRIPTPUB is not used here, this is just a check if it exists
    // so we don't need to call devtax_scriptpub_for_height()
    if ( strlen(ASSETCHAINS_OVERRIDE_PUBKEY.c_str()) == 66 || ASSETCHAINS_SCRIPTPUB.size() > 1 )
    {
        if ( ASSETCHAINS_FOUNDERS == 0 && ASSETCHAINS_COMMISSION != 0 )
        {
            // ac comission chains need the block to exist to calulate the reward.
            if ( nHeight <= chainActive.Height() )
            {
                CBlockIndex* pblockIndex = chainActive[nHeight];
                CBlock block;
                if ( hush_blockload(block, pblockIndex) == 0 )
                    nFoundersReward = the_commission(&block, nHeight);
            }
        }
        else if ( ASSETCHAINS_FOUNDERS != 0 )
        {
            // Assetchains founders chains have a fixed reward so can be calculated at any given height.
            nFoundersReward = the_commission(0, nHeight);
        }
        result.push_back(Pair("ac_pubkey", ValueFromAmount(nFoundersReward)));
    }
    return result;
}


#ifndef WIN32
extern uint32_t ASSETCHAINS_ALGO, ASSETCHAINS_RANDOMX; // hush_defs.h — active PoW algorithm

// Send one newline-terminated JSON line on a blocking socket.
static bool StratumMinerSend(int fd, const std::string& s)
{
    std::string line = s;
    if (line.empty() || line.back() != '\n') line += '\n';
    size_t off = 0;
    while (off < line.size()) {
        ssize_t n = send(fd, line.data() + off, line.size() - off, 0);
        if (n <= 0) return false;
        off += (size_t)n;
    }
    return true;
}

// Wait up to timeout_ms for data, then split all completed lines out of buf into out.
// Returns false only on socket error/close (a timeout with no data is success with out empty).
static bool StratumMinerRecvLines(int fd, std::string& buf, int timeout_ms, std::vector<std::string>& out)
{
    fd_set rfds; FD_ZERO(&rfds); FD_SET(fd, &rfds);
    struct timeval tv; tv.tv_sec = timeout_ms / 1000; tv.tv_usec = (timeout_ms % 1000) * 1000;
    int r = select(fd + 1, &rfds, NULL, NULL, &tv);
    if (r < 0) return false;
    if (r == 0) return true;
    char tmp[8192];
    ssize_t n = recv(fd, tmp, sizeof(tmp), 0);
    if (n <= 0) return false;
    buf.append(tmp, tmp + n);
    size_t pos;
    while ((pos = buf.find('\n')) != std::string::npos) {
        std::string line = buf.substr(0, pos);
        buf.erase(0, pos + 1);
        if (!line.empty() && line.back() == '\r') line.pop_back();
        if (!line.empty()) out.push_back(line);
    }
    return true;
}

// Reference RandomX stratum miner (test utility): connect to a DragonX stratum server, subscribe +
// authorize, receive work + the per-height RandomX key, then vary the block nNonce, hash with
// RandomX (byte-identical to CheckRandomXSolution via GetRandomXInput), and submit a 32-byte
// solution when the block hash meets target. Exists to validate the -stratum RandomX pool path.
//! Upper bound on how long stratummine will hold an RPC worker thread (and a 256 MB RandomX
//! cache). An unbounded caller-supplied deadline pins both indefinitely.
static const int64_t MAX_STRATUMMINE_TIMEOUT = 3600;

//! Parse a 64-char hex field from mining.notify into a uint256.
//! These fields come from whatever host the operator pointed us at, and both primitives below are
//! unforgiving: uint256's vector constructor asserts on a wrong-size input (uint256.cpp:30, and
//! NDEBUG is never defined for this build so the assert is live in release), while ParseHex
//! silently truncates at the first non-hex character. A short or garbled field would therefore
//! abort the daemon rather than be rejected. Returns false instead.
static bool StratumHex256(const std::string& hex, uint256& out)
{
    if (hex.size() != 64 || !IsHex(hex))
        return false;
    std::vector<unsigned char> v = ParseHex(hex);
    if (v.size() != 32)
        return false;
    out = uint256(v);
    return true;
}

UniValue stratummine(const UniValue& params, bool fHelp, const CPubKey& mypk)
{
    if (fHelp || params.size() < 2 || params.size() > 4)
        throw runtime_error(
            "stratummine \"host\" port ( \"address\" timeout )\n"
            "\nReference RandomX stratum miner: connect to a DragonX stratum server, solve RandomX,\n"
            "and submit until one share/block is accepted or the timeout elapses. For testing -stratum.\n"
            "\nArguments:\n"
            "1. \"host\"    (string, required) stratum server host or IP\n"
            "2. port      (numeric, required) stratum server port\n"
            "3. \"address\" (string, optional, default=\"x\") payout R-address, or \"x\" for the server default\n"
            "4. timeout   (numeric, optional, default=120) seconds to mine before giving up\n"
            "\nResult: {\"found\":bool,\"accepted\":bool,\"hash\":\"..\",\"hashes\":n,\"seconds\":n}\n");

    if (ASSETCHAINS_ALGO != ASSETCHAINS_RANDOMX)
        throw JSONRPCError(RPC_MISC_ERROR, "stratummine only supports RandomX chains");

    // This is a reference miner for exercising -stratum, not a production facility: it dials an
    // operator-supplied host, blocks an RPC worker for the whole run, and parses whatever that host
    // chooses to send back. Require an explicit opt-in, and exempt regtest so the test suite can
    // still drive it. NOTE: do NOT gate this on fExperimentalMode -- that defaults to TRUE
    // (init.cpp:1195), so it would leave the RPC exposed on every node and the gate would be a no-op.
    if (!GetBoolArg("-stratummine", false) && Params().NetworkIDString() != "regtest")
        throw JSONRPCError(RPC_MISC_ERROR,
            "stratummine is a test-only reference miner and is disabled by default; "
            "restart with -stratummine to enable it");

    const std::string host = params[0].get_str();
    const int port = params[1].get_int();
    const std::string addr = params.size() > 2 ? params[2].get_str() : "x";
    int64_t timeout = params.size() > 3 ? params[3].get_int64() : 120;
    if (timeout < 1)
        timeout = 1;
    if (timeout > MAX_STRATUMMINE_TIMEOUT)
        timeout = MAX_STRATUMMINE_TIMEOUT;
    const int64_t deadline = GetTime() + timeout;

    // connect (blocking TCP)
    struct addrinfo hints; memset(&hints, 0, sizeof(hints));
    hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM;
    struct addrinfo* ai = NULL;
    if (getaddrinfo(host.c_str(), strprintf("%d", port).c_str(), &hints, &ai) != 0 || !ai)
        throw JSONRPCError(RPC_CLIENT_NOT_CONNECTED, strprintf("cannot resolve %s:%d", host, port));
    int fd = -1;
    for (struct addrinfo* p = ai; p; p = p->ai_next) {
        fd = socket(p->ai_family, p->ai_socktype, p->ai_protocol);
        if (fd < 0) continue;
        if (connect(fd, p->ai_addr, p->ai_addrlen) == 0) break;
        close(fd); fd = -1;
    }
    freeaddrinfo(ai);
    if (fd < 0)
        throw JSONRPCError(RPC_CLIENT_NOT_CONNECTED, strprintf("cannot connect to %s:%d", host, port));
    { int one = 1; setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (char*)&one, sizeof(one)); }

    StratumMinerSend(fd, "{\"id\":1,\"method\":\"mining.subscribe\",\"params\":[\"dragonx-refminer/1.0\"]}");
    StratumMinerSend(fd, strprintf("{\"id\":2,\"method\":\"mining.authorize\",\"params\":[\"%s\",\"x\"]}", addr));

    // state accumulated from the server
    std::vector<unsigned char> extranonce1;
    std::string rxKey;
    bool haveKey = false, haveTarget = false, haveJob = false;
    arith_uint256 poolTarget;
    std::string jobId, timeHex;
    uint32_t nVersion = 4, nTime = 0, nBits = 0;
    uint256 hashPrevBlock, hashMerkleRoot, hashReserved;

    auto processLine = [&](const std::string& line) {
      // Every get_str()/get_int() below throws on a type mismatch, and this lambda runs inside the
      // window where the RandomX cache and VM are allocated and the socket is open -- all of which
      // are released only on the normal path. A malformed server message must therefore never
      // escape from here, or it leaks 256 MB and the fd on its way out.
      try {
        UniValue v;
        if (!v.read(line)) return;
        const UniValue& id = find_value(v, "id");
        const UniValue& result = find_value(v, "result");
        if (id.isNum() && id.get_int() == 1 && result.isArray() && result.size() >= 2 && result[1].isStr())
            extranonce1 = ParseHex(result[1].get_str());
        const UniValue& method = find_value(v, "method");
        if (!method.isStr()) return;
        const UniValue& p = find_value(v, "params");
        if (!p.isArray()) return;
        const std::string m = method.get_str();
        if (m == "mining.set_randomx_key" && p.size() >= 1) {
            std::vector<unsigned char> kb = ParseHex(p[0].get_str());
            rxKey.assign(kb.begin(), kb.end());
            haveKey = true;
        } else if (m == "mining.set_target" && p.size() >= 1) {
            poolTarget = UintToArith256(uint256S(p[0].get_str()));
            haveTarget = true;
        } else if (m == "mining.notify" && p.size() >= 7) {
            // Validate every fixed-width field before committing any of it, so a malformed job is
            // ignored outright rather than half-applied over the previous one.
            uint256 prev, merkle, reserved;
            if (!StratumHex256(p[2].get_str(), prev) ||
                !StratumHex256(p[3].get_str(), merkle) ||
                !StratumHex256(p[4].get_str(), reserved))
                return;
            jobId          = p[0].get_str();
            nVersion       = bswap_32((uint32_t)strtoul(p[1].get_str().c_str(), NULL, 16));
            hashPrevBlock  = prev;
            hashMerkleRoot = merkle;
            hashReserved   = reserved;
            timeHex        = p[5].get_str();
            nTime          = bswap_32((uint32_t)strtoul(timeHex.c_str(), NULL, 16));
            nBits          = bswap_32((uint32_t)strtoul(p[6].get_str().c_str(), NULL, 16));
            haveJob = true;
        }
      } catch (const std::exception&) {
        // Malformed message from the server: ignore the line and keep mining.
      }
    };

    std::string buf;
    for (int i = 0; i < 120 && !(haveJob && haveTarget && haveKey && !extranonce1.empty()); i++) {
        std::vector<std::string> lines;
        if (!StratumMinerRecvLines(fd, buf, 250, lines)) { close(fd); throw JSONRPCError(RPC_CLIENT_NOT_CONNECTED, "stratum connection closed during handshake"); }
        for (const std::string& l : lines) processLine(l);
        if (GetTime() > deadline) break;
    }
    if (!(haveJob && haveTarget && haveKey && !extranonce1.empty())) {
        close(fd);
        throw JSONRPCError(RPC_MISC_ERROR, "did not receive complete RandomX work (need job + target + randomx key + extranonce)");
    }

    randomx_flags flags = randomx_get_flags();
    randomx_cache* cache = randomx_alloc_cache(flags);
    if (!cache) { close(fd); throw JSONRPCError(RPC_MISC_ERROR, "randomx_alloc_cache failed"); }
    randomx_init_cache(cache, rxKey.data(), rxKey.size());
    std::string vmKey = rxKey;
    randomx_vm* vm = randomx_create_vm(flags, cache, NULL);
    if (!vm) { randomx_release_cache(cache); close(fd); throw JSONRPCError(RPC_MISC_ERROR, "randomx_create_vm failed"); }

    UniValue res(UniValue::VOBJ);
    bool found = false, accepted = false, submitted = false;
    uint64_t hashes = 0, en2ctr = 0;
    std::string foundHash;
    const int64_t started = GetTime();

    while (GetTime() <= deadline && !found) {
        std::string prevJob = jobId;
        std::vector<std::string> lines;
        if (!StratumMinerRecvLines(fd, buf, 0, lines)) break;
        for (const std::string& l : lines) processLine(l);
        if (jobId != prevJob) en2ctr = 0; // new tip -> restart the nonce search
        if (rxKey != vmKey) { randomx_init_cache(cache, rxKey.data(), rxKey.size()); randomx_vm_set_cache(vm, cache); vmKey = rxKey; }

        arith_uint256 blockTarget; bool fNeg, fOver;
        blockTarget.SetCompact(nBits, &fNeg, &fOver);
        // Mine to the harder of (block target, pool share target) so a solution is a real block AND
        // passes the server's low-diff share check.
        arith_uint256 tgt = (haveTarget && poolTarget < blockTarget) ? poolTarget : blockTarget;

        CBlockHeader hdr;
        hdr.nVersion = nVersion;
        hdr.hashPrevBlock = hashPrevBlock;
        hdr.hashMerkleRoot = hashMerkleRoot;
        hdr.hashFinalSaplingRoot = hashReserved;
        hdr.nTime = nTime;
        hdr.nBits = nBits;

        for (int i = 0; i < 2000 && GetTime() <= deadline; i++) {
            std::vector<unsigned char> nonce = extranonce1;
            nonce.resize(32, 0);
            for (int b = 0; b < 8; b++) nonce[8 + b] = (unsigned char)((en2ctr >> (8 * b)) & 0xff);
            en2ctr++; hashes++;
            hdr.nNonce = uint256(nonce);
            std::vector<unsigned char> input = GetRandomXInput(hdr);
            unsigned char h[RANDOMX_HASH_SIZE];
            randomx_calculate_hash(vm, input.data(), input.size(), h);
            hdr.nSolution.assign(h, h + RANDOMX_HASH_SIZE);
            if (UintToArith256(hdr.GetHash()) <= tgt) {
                std::vector<unsigned char> en2(nonce.begin() + 8, nonce.end());
                std::string submit = strprintf(
                    "{\"id\":4,\"method\":\"mining.submit\",\"params\":[\"%s\",\"%s\",\"%s\",\"%s\",\"%s\"]}",
                    addr, jobId, timeHex, HexStr(en2), HexStr(hdr.nSolution));
                StratumMinerSend(fd, submit);
                submitted = true;
                foundHash = hdr.GetHash().ToString();
                bool sawResult = false;
                for (int k = 0; k < 40 && !sawResult; k++) {
                    std::vector<std::string> rl;
                    if (!StratumMinerRecvLines(fd, buf, 250, rl)) break;
                    for (const std::string& l : rl) {
                        processLine(l);
                        UniValue rv; if (!rv.read(l)) continue;
                        const UniValue& rid = find_value(rv, "id");
                        if (rid.isNum() && rid.get_int() == 4) {
                            sawResult = true;
                            const UniValue& r = find_value(rv, "result");
                            accepted = r.isBool() ? r.get_bool() : find_value(rv, "error").isNull();
                        }
                    }
                }
                found = true;
                break;
            }
        }
    }

    randomx_destroy_vm(vm);
    randomx_release_cache(cache);
    close(fd);

    res.push_back(Pair("found", found));
    res.push_back(Pair("submitted", submitted));
    res.push_back(Pair("accepted", accepted));
    res.push_back(Pair("hashes", (uint64_t)hashes));
    res.push_back(Pair("seconds", (int64_t)(GetTime() - started)));
    if (!foundHash.empty()) res.push_back(Pair("hash", foundHash));
    return res;
}
#endif // !WIN32

static const CRPCCommand commands[] =
{ //  category              name                      actor (function)         okSafeMode
  //  --------------------- ------------------------  -----------------------  ----------
#ifndef WIN32
    { "mining",             "stratummine",            &stratummine,            false },
#endif
    { "mining",             "getlocalsolps",          &getlocalsolps,          true  },
    { "mining",             "getnetworksolps",        &getnetworksolps,        true  },
    { "mining",             "getnetworkhashps",       &getnetworkhashps,       true  },
    { "mining",             "getmininginfo",          &getmininginfo,          true  },
    { "mining",             "prioritisetransaction",  &prioritisetransaction,  true  },
    { "mining",             "getblocktemplate",       &getblocktemplate,       true  },
    { "mining",             "submitblock",            &submitblock,            true  },
    { "mining",             "getblocksubsidy",        &getblocksubsidy,        true  },

#ifdef ENABLE_MINING
    { "generating",         "getgenerate",            &getgenerate,            true  },
    { "generating",         "setgenerate",            &setgenerate,            true  },
    { "generating",         "generate",               &generate,               true  },
#endif

    { "util",               "estimatefee",            &estimatefee,            true  },
    { "util",               "estimatepriority",       &estimatepriority,       true  },
};

void RegisterMiningRPCCommands(CRPCTable &tableRPC)
{
    for (unsigned int vcidx = 0; vcidx < ARRAYLEN(commands); vcidx++)
        tableRPC.appendCommand(commands[vcidx].name, &commands[vcidx]);
}
