diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index 56fda4c3a4..c57757f58a 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -1,2797 +1,2786 @@ // Copyright (c) 2010 Satoshi Nakamoto // Copyright (c) 2009-2016 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <rpc/blockchain.h> #include <amount.h> #include <chain.h> #include <chainparams.h> #include <checkpoints.h> #include <coins.h> #include <config.h> #include <consensus/validation.h> #include <core_io.h> #include <hash.h> #include <index/txindex.h> #include <key_io.h> #include <policy/policy.h> #include <primitives/transaction.h> #include <rpc/server.h> #include <rpc/util.h> #include <script/descriptor.h> #include <streams.h> #include <sync.h> #include <txdb.h> #include <txmempool.h> #include <util/strencodings.h> #include <util/system.h> #include <validation.h> #include <validationinterface.h> #include <versionbitsinfo.h> // For VersionBitsDeploymentInfo #include <warnings.h> #include <boost/thread/thread.hpp> // boost::thread::interrupt #include <cassert> #include <condition_variable> #include <cstdint> #include <memory> #include <mutex> struct CUpdatedBlock { uint256 hash; int height; }; static Mutex cs_blockchange; static std::condition_variable cond_blockchange; static CUpdatedBlock latestblock; /** * Calculate the difficulty for a given block index. */ double GetDifficulty(const CBlockIndex *blockindex) { assert(blockindex); int nShift = (blockindex->nBits >> 24) & 0xff; double dDiff = double(0x0000ffff) / double(blockindex->nBits & 0x00ffffff); while (nShift < 29) { dDiff *= 256.0; nShift++; } while (nShift > 29) { dDiff /= 256.0; nShift--; } return dDiff; } static int ComputeNextBlockAndDepth(const CBlockIndex *tip, const CBlockIndex *blockindex, const CBlockIndex *&next) { next = tip->GetAncestor(blockindex->nHeight + 1); if (next && next->pprev == blockindex) { return tip->nHeight - blockindex->nHeight + 1; } next = nullptr; return blockindex == tip ? 1 : -1; } UniValue blockheaderToJSON(const CBlockIndex *tip, const CBlockIndex *blockindex) { UniValue result(UniValue::VOBJ); result.pushKV("hash", blockindex->GetBlockHash().GetHex()); const CBlockIndex *pnext; int confirmations = ComputeNextBlockAndDepth(tip, blockindex, pnext); result.pushKV("confirmations", confirmations); result.pushKV("height", blockindex->nHeight); result.pushKV("version", blockindex->nVersion); result.pushKV("versionHex", strprintf("%08x", blockindex->nVersion)); result.pushKV("merkleroot", blockindex->hashMerkleRoot.GetHex()); result.pushKV("time", int64_t(blockindex->nTime)); result.pushKV("mediantime", int64_t(blockindex->GetMedianTimePast())); result.pushKV("nonce", uint64_t(blockindex->nNonce)); result.pushKV("bits", strprintf("%08x", blockindex->nBits)); result.pushKV("difficulty", GetDifficulty(blockindex)); result.pushKV("chainwork", blockindex->nChainWork.GetHex()); result.pushKV("nTx", uint64_t(blockindex->nTx)); if (blockindex->pprev) { result.pushKV("previousblockhash", blockindex->pprev->GetBlockHash().GetHex()); } if (pnext) { result.pushKV("nextblockhash", pnext->GetBlockHash().GetHex()); } return result; } UniValue blockToJSON(const CBlock &block, const CBlockIndex *tip, const CBlockIndex *blockindex, bool txDetails) { UniValue result(UniValue::VOBJ); result.pushKV("hash", blockindex->GetBlockHash().GetHex()); const CBlockIndex *pnext; int confirmations = ComputeNextBlockAndDepth(tip, blockindex, pnext); result.pushKV("confirmations", confirmations); result.pushKV("size", (int)::GetSerializeSize(block, PROTOCOL_VERSION)); result.pushKV("height", blockindex->nHeight); result.pushKV("version", block.nVersion); result.pushKV("versionHex", strprintf("%08x", block.nVersion)); result.pushKV("merkleroot", block.hashMerkleRoot.GetHex()); UniValue txs(UniValue::VARR); for (const auto &tx : block.vtx) { if (txDetails) { UniValue objTx(UniValue::VOBJ); TxToUniv(*tx, uint256(), objTx, true, RPCSerializationFlags()); txs.push_back(objTx); } else { txs.push_back(tx->GetId().GetHex()); } } result.pushKV("tx", txs); result.pushKV("time", block.GetBlockTime()); result.pushKV("mediantime", int64_t(blockindex->GetMedianTimePast())); result.pushKV("nonce", uint64_t(block.nNonce)); result.pushKV("bits", strprintf("%08x", block.nBits)); result.pushKV("difficulty", GetDifficulty(blockindex)); result.pushKV("chainwork", blockindex->nChainWork.GetHex()); result.pushKV("nTx", uint64_t(blockindex->nTx)); if (blockindex->pprev) { result.pushKV("previousblockhash", blockindex->pprev->GetBlockHash().GetHex()); } if (pnext) { result.pushKV("nextblockhash", pnext->GetBlockHash().GetHex()); } return result; } static UniValue getblockcount(const Config &config, const JSONRPCRequest &request) { if (request.fHelp || request.params.size() != 0) { throw std::runtime_error( RPCHelpMan{ "getblockcount", "\nReturns the number of blocks in the longest blockchain.\n", {}} - .ToString() + + .ToStringWithArgs() + "\nResult:\n" "n (numeric) The current block count\n" "\nExamples:\n" + HelpExampleCli("getblockcount", "") + HelpExampleRpc("getblockcount", "")); } LOCK(cs_main); return ::ChainActive().Height(); } static UniValue getbestblockhash(const Config &config, const JSONRPCRequest &request) { if (request.fHelp || request.params.size() != 0) { throw std::runtime_error( RPCHelpMan{"getbestblockhash", "\nReturns the hash of the best (tip) block in the " "longest blockchain.\n", {}} - .ToString() + + .ToStringWithArgs() + "\nResult:\n" "\"hex\" (string) the block hash hex-encoded\n" "\nExamples:\n" + HelpExampleCli("getbestblockhash", "") + HelpExampleRpc("getbestblockhash", "")); } LOCK(cs_main); return ::ChainActive().Tip()->GetBlockHash().GetHex(); } UniValue getfinalizedblockhash(const Config &config, const JSONRPCRequest &request) { if (request.fHelp || request.params.size() != 0) { throw std::runtime_error( RPCHelpMan{"getfinalizedblockhash", "\nReturns the hash of the currently finalized block\n", {}} - .ToString() + + .ToStringWithArgs() + "\nResult:\n" "\"hex\" (string) the block hash hex-encoded\n"); } LOCK(cs_main); const CBlockIndex *blockIndexFinalized = GetFinalizedBlock(); if (blockIndexFinalized) { return blockIndexFinalized->GetBlockHash().GetHex(); } return UniValue(UniValue::VSTR); } void RPCNotifyBlockChange(bool ibd, const CBlockIndex *pindex) { if (pindex) { std::lock_guard<std::mutex> lock(cs_blockchange); latestblock.hash = pindex->GetBlockHash(); latestblock.height = pindex->nHeight; } cond_blockchange.notify_all(); } static UniValue waitfornewblock(const Config &config, const JSONRPCRequest &request) { if (request.fHelp || request.params.size() > 1) { throw std::runtime_error( RPCHelpMan{"waitfornewblock", "\nWaits for a specific new block and returns useful " "info about it.\n" "\nReturns the current block on timeout or exit.\n", { - {"timeout", RPCArg::Type::NUM, true}, + {"timeout", RPCArg::Type::NUM, /* opt */ true, + /* default_val */ "0", + "Time in milliseconds to wait for a response. 0 " + "indicates no timeout."}, }} - .ToString() + - "\nArguments:\n" - "1. timeout (int, optional, default=0) Time in " - "milliseconds to wait for a response. 0 indicates " - "no timeout.\n" + .ToStringWithArgs() + "\nResult:\n" "{ (json object)\n" " \"hash\" : { (string) The blockhash\n" " \"height\" : { (int) Block height\n" "}\n" "\nExamples:\n" + HelpExampleCli("waitfornewblock", "1000") + HelpExampleRpc("waitfornewblock", "1000")); } int timeout = 0; if (!request.params[0].isNull()) { timeout = request.params[0].get_int(); } CUpdatedBlock block; { WAIT_LOCK(cs_blockchange, lock); block = latestblock; if (timeout) { cond_blockchange.wait_for( lock, std::chrono::milliseconds(timeout), [&block] { return latestblock.height != block.height || latestblock.hash != block.hash || !IsRPCRunning(); }); } else { cond_blockchange.wait(lock, [&block] { return latestblock.height != block.height || latestblock.hash != block.hash || !IsRPCRunning(); }); } block = latestblock; } UniValue ret(UniValue::VOBJ); ret.pushKV("hash", block.hash.GetHex()); ret.pushKV("height", block.height); return ret; } static UniValue waitforblock(const Config &config, const JSONRPCRequest &request) { if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) { throw std::runtime_error( RPCHelpMan{"waitforblock", "\nWaits for a specific new block and returns useful " "info about it.\n" "\nReturns the current block on timeout or exit.\n", { - {"blockhash", RPCArg::Type::STR_HEX, false}, - {"timeout", RPCArg::Type::NUM, true}, + {"blockhash", RPCArg::Type::STR_HEX, /* opt */ false, + /* default_val */ "", "Block hash to wait for."}, + {"timeout", RPCArg::Type::NUM, /* opt */ true, + /* default_val */ "0", + "Time in milliseconds to wait for a response. 0 " + "indicates no timeout."}, }} - .ToString() + - "\nArguments:\n" - "1. \"blockhash\" (required, string) Block hash to wait for.\n" - "2. timeout (int, optional, default=0) Time in milliseconds " - "to wait for a response. 0 indicates no timeout.\n" + .ToStringWithArgs() + "\nResult:\n" "{ (json object)\n" " \"hash\" : { (string) The blockhash\n" " \"height\" : { (int) Block height\n" "}\n" "\nExamples:\n" + HelpExampleCli("waitforblock", "\"0000000000079f8ef3d2c688c244eb7a4" "570b24c9ed7b4a8c619eb02596f8862\", " "1000") + HelpExampleRpc("waitforblock", "\"0000000000079f8ef3d2c688c244eb7a4" "570b24c9ed7b4a8c619eb02596f8862\", " "1000")); } int timeout = 0; BlockHash hash(ParseHashV(request.params[0], "blockhash")); if (!request.params[1].isNull()) { timeout = request.params[1].get_int(); } CUpdatedBlock block; { WAIT_LOCK(cs_blockchange, lock); if (timeout) { cond_blockchange.wait_for( lock, std::chrono::milliseconds(timeout), [&hash] { return latestblock.hash == hash || !IsRPCRunning(); }); } else { cond_blockchange.wait(lock, [&hash] { return latestblock.hash == hash || !IsRPCRunning(); }); } block = latestblock; } UniValue ret(UniValue::VOBJ); ret.pushKV("hash", block.hash.GetHex()); ret.pushKV("height", block.height); return ret; } static UniValue waitforblockheight(const Config &config, const JSONRPCRequest &request) { if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) { throw std::runtime_error( RPCHelpMan{"waitforblockheight", "\nWaits for (at least) block height and returns the " "height and hash\nof the current tip.\n" "\nReturns the current block on timeout or exit.\n", { - {"height", RPCArg::Type::NUM, false}, - {"timeout", RPCArg::Type::NUM, true}, + {"height", RPCArg::Type::NUM, /* opt */ false, + /* default_val */ "", "Block height to wait for."}, + {"timeout", RPCArg::Type::NUM, /* opt */ true, + /* default_val */ "0", + "Time in milliseconds to wait for a response. 0 " + "indicates no timeout."}, }} - .ToString() + - "\nArguments:\n" - "1. height (int, required) Block height to wait for (int)\n" - "2. timeout (int, optional, default=0) Time in milliseconds to " - "wait for a response. 0 indicates no timeout.\n" + .ToStringWithArgs() + "\nResult:\n" "{ (json object)\n" " \"hash\" : { (string) The blockhash\n" " \"height\" : { (int) Block height\n" "}\n" "\nExamples:\n" + HelpExampleCli("waitforblockheight", "\"100\", 1000") + HelpExampleRpc("waitforblockheight", "\"100\", 1000")); } int timeout = 0; int height = request.params[0].get_int(); if (!request.params[1].isNull()) { timeout = request.params[1].get_int(); } CUpdatedBlock block; { WAIT_LOCK(cs_blockchange, lock); if (timeout) { cond_blockchange.wait_for( lock, std::chrono::milliseconds(timeout), [&height] { return latestblock.height >= height || !IsRPCRunning(); }); } else { cond_blockchange.wait(lock, [&height] { return latestblock.height >= height || !IsRPCRunning(); }); } block = latestblock; } UniValue ret(UniValue::VOBJ); ret.pushKV("hash", block.hash.GetHex()); ret.pushKV("height", block.height); return ret; } static UniValue syncwithvalidationinterfacequeue(const Config &config, const JSONRPCRequest &request) { if (request.fHelp || request.params.size() > 0) { throw std::runtime_error( RPCHelpMan{ "syncwithvalidationinterfacequeue", "\nWaits for the validation interface queue to catch up on " "everything that was there when we entered this function.\n", {}} - .ToString() + + .ToStringWithArgs() + "\nExamples:\n" + HelpExampleCli("syncwithvalidationinterfacequeue", "") + HelpExampleRpc("syncwithvalidationinterfacequeue", "")); } SyncWithValidationInterfaceQueue(); return NullUniValue; } static UniValue getdifficulty(const Config &config, const JSONRPCRequest &request) { if (request.fHelp || request.params.size() != 0) { throw std::runtime_error( RPCHelpMan{"getdifficulty", "\nReturns the proof-of-work difficulty as a " "multiple of the minimum difficulty.\n", {}} - .ToString() + + .ToStringWithArgs() + "\nResult:\n" "n.nnn (numeric) the proof-of-work " "difficulty as a multiple of the minimum " "difficulty.\n" "\nExamples:\n" + HelpExampleCli("getdifficulty", "") + HelpExampleRpc("getdifficulty", "")); } LOCK(cs_main); return GetDifficulty(::ChainActive().Tip()); } static std::string EntryDescriptionString() { return " \"size\" : n, (numeric) transaction size.\n" " \"fee\" : n, (numeric) transaction fee in " + CURRENCY_UNIT + "(DEPRECATED)" + "\n" " \"modifiedfee\" : n, (numeric) transaction fee with fee " "deltas used for mining priority (DEPRECATED)\n" " \"time\" : n, (numeric) local time transaction " "entered pool in seconds since 1 Jan 1970 GMT\n" " \"height\" : n, (numeric) block height when " "transaction entered pool\n" " \"descendantcount\" : n, (numeric) number of in-mempool " "descendant transactions (including this one)\n" " \"descendantsize\" : n, (numeric) transaction size " "of in-mempool descendants (including this one)\n" " \"descendantfees\" : n, (numeric) modified fees (see above) " "of in-mempool descendants (including this one) (DEPRECATED)\n" " \"ancestorcount\" : n, (numeric) number of in-mempool " "ancestor transactions (including this one)\n" " \"ancestorsize\" : n, (numeric) transaction size " "of in-mempool ancestors (including this one)\n" " \"ancestorfees\" : n, (numeric) modified fees (see above) " "of in-mempool ancestors (including this one) (DEPRECATED)\n" " \"fees\" : {\n" " \"base\" : n, (numeric) transaction fee in " + CURRENCY_UNIT + "\n" " \"modified\" : n, (numeric) transaction fee with fee " "deltas used for mining priority in " + CURRENCY_UNIT + "\n" " \"ancestor\" : n, (numeric) modified fees (see above) " "of in-mempool ancestors (including this one) in " + CURRENCY_UNIT + "\n" " \"descendant\" : n, (numeric) modified fees (see above) " "of in-mempool descendants (including this one) in " + CURRENCY_UNIT + "\n" " }\n" " \"depends\" : [ (array) unconfirmed transactions " "used as inputs for this transaction\n" " \"transactionid\", (string) parent transaction id\n" " ... ]\n" " \"spentby\" : [ (array) unconfirmed transactions " "spending outputs from this transaction\n" " \"transactionid\", (string) child transaction id\n" " ... ]\n"; } static void entryToJSON(const CTxMemPool &pool, UniValue &info, const CTxMemPoolEntry &e) EXCLUSIVE_LOCKS_REQUIRED(pool.cs) { AssertLockHeld(pool.cs); UniValue fees(UniValue::VOBJ); fees.pushKV("base", ValueFromAmount(e.GetFee())); fees.pushKV("modified", ValueFromAmount(e.GetModifiedFee())); fees.pushKV("ancestor", ValueFromAmount(e.GetModFeesWithAncestors())); fees.pushKV("descendant", ValueFromAmount(e.GetModFeesWithDescendants())); info.pushKV("fees", fees); info.pushKV("size", (int)e.GetTxSize()); info.pushKV("fee", ValueFromAmount(e.GetFee())); info.pushKV("modifiedfee", ValueFromAmount(e.GetModifiedFee())); info.pushKV("time", e.GetTime()); info.pushKV("height", (int)e.GetHeight()); info.pushKV("descendantcount", e.GetCountWithDescendants()); info.pushKV("descendantsize", e.GetSizeWithDescendants()); info.pushKV("descendantfees", e.GetModFeesWithDescendants() / SATOSHI); info.pushKV("ancestorcount", e.GetCountWithAncestors()); info.pushKV("ancestorsize", e.GetSizeWithAncestors()); info.pushKV("ancestorfees", e.GetModFeesWithAncestors() / SATOSHI); const CTransaction &tx = e.GetTx(); std::set<std::string> setDepends; for (const CTxIn &txin : tx.vin) { if (pool.exists(txin.prevout.GetTxId())) { setDepends.insert(txin.prevout.GetTxId().ToString()); } } UniValue depends(UniValue::VARR); for (const std::string &dep : setDepends) { depends.push_back(dep); } info.pushKV("depends", depends); UniValue spent(UniValue::VARR); const CTxMemPool::txiter &it = pool.mapTx.find(tx.GetId()); const CTxMemPool::setEntries &setChildren = pool.GetMemPoolChildren(it); for (CTxMemPool::txiter childiter : setChildren) { spent.push_back(childiter->GetTx().GetId().ToString()); } info.pushKV("spentby", spent); } UniValue MempoolToJSON(const CTxMemPool &pool, bool verbose) { if (verbose) { LOCK(pool.cs); UniValue o(UniValue::VOBJ); for (const CTxMemPoolEntry &e : pool.mapTx) { const uint256 &txid = e.GetTx().GetId(); UniValue info(UniValue::VOBJ); entryToJSON(pool, info, e); o.pushKV(txid.ToString(), info); } return o; } else { std::vector<uint256> vtxids; pool.queryHashes(vtxids); UniValue a(UniValue::VARR); for (const uint256 &txid : vtxids) { a.push_back(txid.ToString()); } return a; } } static UniValue getrawmempool(const Config &config, const JSONRPCRequest &request) { if (request.fHelp || request.params.size() > 1) { throw std::runtime_error( RPCHelpMan{"getrawmempool", "\nReturns all transaction ids in memory pool as a json " - "array of string transaction ids.\n", + "array of string transaction ids.\n" + "\nHint: use getmempoolentry to fetch a specific " + "transaction from the mempool.\n", { - {"verbose", RPCArg::Type::BOOL, true}, + {"verbose", RPCArg::Type::BOOL, /* opt */ true, + /* default_val */ "false", + "True for a json object, false for array of " + "transaction ids"}, }} - .ToString() + - "\nHint: use getmempoolentry to fetch a specific transaction from " - "the mempool.\n" - "\nArguments:\n" - "1. verbose (boolean, optional, default=false) True for a json " - "object, false for array of transaction ids\n" + .ToStringWithArgs() + "\nResult: (for verbose = false):\n" "[ (json array of string)\n" " \"transactionid\" (string) The transaction id\n" " ,...\n" "]\n" "\nResult: (for verbose = true):\n" "{ (json object)\n" " \"transactionid\" : { (json object)\n" + EntryDescriptionString() + " }, ...\n" "}\n" "\nExamples:\n" + HelpExampleCli("getrawmempool", "true") + HelpExampleRpc("getrawmempool", "true")); } bool fVerbose = false; if (!request.params[0].isNull()) { fVerbose = request.params[0].get_bool(); } return MempoolToJSON(::g_mempool, fVerbose); } static UniValue getmempoolancestors(const Config &config, const JSONRPCRequest &request) { if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) { throw std::runtime_error( RPCHelpMan{"getmempoolancestors", "\nIf txid is in the mempool, returns all in-mempool " "ancestors.\n", { - {"txid", RPCArg::Type::STR_HEX, false}, - {"verbose", RPCArg::Type::BOOL, true}, + {"txid", RPCArg::Type::STR_HEX, /* opt */ false, + /* default_val */ "", + "The transaction id (must be in mempool)"}, + {"verbose", RPCArg::Type::BOOL, /* opt */ true, + /* default_val */ "false", + "True for a json object, false for array of " + "transaction ids"}, }} - .ToString() + - "\nArguments:\n" - "1. \"txid\" (string, required) The transaction id " - "(must be in mempool)\n" - "2. verbose (boolean, optional, default=false) " - "True for a json object, false for array of transaction ids\n" + .ToStringWithArgs() + "\nResult (for verbose = false):\n" "[ (json array of strings)\n" " \"transactionid\" (string) The transaction id of an " "in-mempool ancestor transaction\n" " ,...\n" "]\n" "\nResult (for verbose = true):\n" "{ (json object)\n" " \"transactionid\" : { (json object)\n" + EntryDescriptionString() + " }, ...\n" "}\n" "\nExamples:\n" + HelpExampleCli("getmempoolancestors", "\"mytxid\"") + HelpExampleRpc("getmempoolancestors", "\"mytxid\"")); } bool fVerbose = false; if (!request.params[1].isNull()) { fVerbose = request.params[1].get_bool(); } TxId txid(ParseHashV(request.params[0], "parameter 1")); LOCK(g_mempool.cs); CTxMemPool::txiter it = g_mempool.mapTx.find(txid); if (it == g_mempool.mapTx.end()) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction not in mempool"); } CTxMemPool::setEntries setAncestors; uint64_t noLimit = std::numeric_limits<uint64_t>::max(); std::string dummy; g_mempool.CalculateMemPoolAncestors(*it, setAncestors, noLimit, noLimit, noLimit, noLimit, dummy, false); if (!fVerbose) { UniValue o(UniValue::VARR); for (CTxMemPool::txiter ancestorIt : setAncestors) { o.push_back(ancestorIt->GetTx().GetId().ToString()); } return o; } else { UniValue o(UniValue::VOBJ); for (CTxMemPool::txiter ancestorIt : setAncestors) { const CTxMemPoolEntry &e = *ancestorIt; const TxId &_txid = e.GetTx().GetId(); UniValue info(UniValue::VOBJ); entryToJSON(::g_mempool, info, e); o.pushKV(_txid.ToString(), info); } return o; } } static UniValue getmempooldescendants(const Config &config, const JSONRPCRequest &request) { if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) { throw std::runtime_error( RPCHelpMan{"getmempooldescendants", "\nIf txid is in the mempool, returns all in-mempool " "descendants.\n", { - {"txid", RPCArg::Type::STR_HEX, false}, - {"verbose", RPCArg::Type::BOOL, true}, + {"txid", RPCArg::Type::STR_HEX, /* opt */ false, + /* default_val */ "", + "The transaction id (must be in mempool)"}, + {"verbose", RPCArg::Type::BOOL, /* opt */ true, + /* default_val */ "false", + "True for a json object, false for array of " + "transaction ids"}, }} - .ToString() + - "\nArguments:\n" - "1. \"txid\" (string, required) The transaction id " - "(must be in mempool)\n" - "2. verbose (boolean, optional, default=false) " - "True for a json object, false for array of transaction ids\n" + .ToStringWithArgs() + "\nResult (for verbose = false):\n" "[ (json array of strings)\n" " \"transactionid\" (string) The transaction id of an " "in-mempool descendant transaction\n" " ,...\n" "]\n" "\nResult (for verbose = true):\n" "{ (json object)\n" " \"transactionid\" : { (json object)\n" + EntryDescriptionString() + " }, ...\n" "}\n" "\nExamples:\n" + HelpExampleCli("getmempooldescendants", "\"mytxid\"") + HelpExampleRpc("getmempooldescendants", "\"mytxid\"")); } bool fVerbose = false; if (!request.params[1].isNull()) { fVerbose = request.params[1].get_bool(); } TxId txid(ParseHashV(request.params[0], "parameter 1")); LOCK(g_mempool.cs); CTxMemPool::txiter it = g_mempool.mapTx.find(txid); if (it == g_mempool.mapTx.end()) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction not in mempool"); } CTxMemPool::setEntries setDescendants; g_mempool.CalculateDescendants(it, setDescendants); // CTxMemPool::CalculateDescendants will include the given tx setDescendants.erase(it); if (!fVerbose) { UniValue o(UniValue::VARR); for (CTxMemPool::txiter descendantIt : setDescendants) { o.push_back(descendantIt->GetTx().GetId().ToString()); } return o; } else { UniValue o(UniValue::VOBJ); for (CTxMemPool::txiter descendantIt : setDescendants) { const CTxMemPoolEntry &e = *descendantIt; const TxId &_txid = e.GetTx().GetId(); UniValue info(UniValue::VOBJ); entryToJSON(::g_mempool, info, e); o.pushKV(_txid.ToString(), info); } return o; } } static UniValue getmempoolentry(const Config &config, const JSONRPCRequest &request) { if (request.fHelp || request.params.size() != 1) { throw std::runtime_error( RPCHelpMan{"getmempoolentry", "\nReturns mempool data for given transaction\n", { - {"txid", RPCArg::Type::STR_HEX, false}, + {"txid", RPCArg::Type::STR_HEX, /* opt */ false, + /* default_val */ "", + "The transaction id (must be in mempool)"}, }} - .ToString() + - "\nArguments:\n" - "1. \"txid\" (string, required) " - "The transaction id (must be in mempool)\n" + .ToStringWithArgs() + "\nResult:\n" "{ (json object)\n" + EntryDescriptionString() + "}\n" "\nExamples:\n" + HelpExampleCli("getmempoolentry", "\"mytxid\"") + HelpExampleRpc("getmempoolentry", "\"mytxid\"")); } TxId txid(ParseHashV(request.params[0], "parameter 1")); LOCK(g_mempool.cs); CTxMemPool::txiter it = g_mempool.mapTx.find(txid); if (it == g_mempool.mapTx.end()) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction not in mempool"); } const CTxMemPoolEntry &e = *it; UniValue info(UniValue::VOBJ); entryToJSON(::g_mempool, info, e); return info; } static UniValue getblockhash(const Config &config, const JSONRPCRequest &request) { if (request.fHelp || request.params.size() != 1) { throw std::runtime_error( RPCHelpMan{"getblockhash", "\nReturns hash of block in best-block-chain at height " "provided.\n", { - {"height", RPCArg::Type::NUM, false}, + {"height", RPCArg::Type::NUM, /* opt */ false, + /* default_val */ "", "The height index"}, }} - .ToString() + - "\nArguments:\n" - "1. height (numeric, required) The height index\n" + .ToStringWithArgs() + "\nResult:\n" "\"hash\" (string) The block hash\n" "\nExamples:\n" + HelpExampleCli("getblockhash", "1000") + HelpExampleRpc("getblockhash", "1000")); } LOCK(cs_main); int nHeight = request.params[0].get_int(); if (nHeight < 0 || nHeight > ::ChainActive().Height()) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Block height out of range"); } CBlockIndex *pblockindex = ::ChainActive()[nHeight]; return pblockindex->GetBlockHash().GetHex(); } static UniValue getblockheader(const Config &config, const JSONRPCRequest &request) { if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) { throw std::runtime_error( - RPCHelpMan{"getblockheader", - "\nIf verbose is false, returns a string that is " - "serialized, hex-encoded data for blockheader 'hash'.\n" - "If verbose is true, returns an Object with information " - "about blockheader <hash>.\n", - { - {"blockhash", RPCArg::Type::STR_HEX, false}, - {"verbose", RPCArg::Type::BOOL, true}, - }} - .ToString() + - "\nArguments:\n" - "1. \"blockhash\" (string, required) The block hash\n" - "2. verbose (boolean, optional, default=true) true for a " - "json object, false for the hex-encoded data\n" + RPCHelpMan{ + "getblockheader", + "\nIf verbose is false, returns a string that is " + "serialized, hex-encoded data for blockheader 'hash'.\n" + "If verbose is true, returns an Object with information " + "about blockheader <hash>.\n", + { + {"blockhash", RPCArg::Type::STR_HEX, /* opt */ false, + /* default_val */ "", "The block hash"}, + {"verbose", RPCArg::Type::BOOL, /* opt */ true, + /* default_val */ "true", + "true for a json object, false for the hex-encoded data"}, + }} + .ToStringWithArgs() + "\nResult (for verbose = true):\n" "{\n" " \"hash\" : \"hash\", (string) the block hash (same as " "provided)\n" " \"confirmations\" : n, (numeric) The number of confirmations, " "or -1 if the block is not on the main chain\n" " \"height\" : n, (numeric) The block height or index\n" " \"version\" : n, (numeric) The block version\n" " \"versionHex\" : \"00000000\", (string) The block version " "formatted in hexadecimal\n" " \"merkleroot\" : \"xxxx\", (string) The merkle root\n" " \"time\" : ttt, (numeric) The block time in seconds " "since epoch (Jan 1 1970 GMT)\n" " \"mediantime\" : ttt, (numeric) The median block time in " "seconds since epoch (Jan 1 1970 GMT)\n" " \"nonce\" : n, (numeric) The nonce\n" " \"bits\" : \"1d00ffff\", (string) The bits\n" " \"difficulty\" : x.xxx, (numeric) The difficulty\n" " \"chainwork\" : \"0000...1f3\" (string) Expected number of " "hashes required to produce the current chain (in hex)\n" " \"nTx\" : n, (numeric) The number of transactions " "in the block.\n" " \"previousblockhash\" : \"hash\", (string) The hash of the " "previous block\n" " \"nextblockhash\" : \"hash\", (string) The hash of the " "next block\n" "}\n" "\nResult (for verbose=false):\n" "\"data\" (string) A string that is serialized, " "hex-encoded data for block 'hash'.\n" "\nExamples:\n" + HelpExampleCli("getblockheader", "\"00000000c937983704a73af28acdec3" "7b049d214adbda81d7e2a3dd146f6ed09" "\"") + HelpExampleRpc("getblockheader", "\"00000000c937983704a73af28acdec3" "7b049d214adbda81d7e2a3dd146f6ed09" "\"")); } BlockHash hash(ParseHashV(request.params[0], "hash")); bool fVerbose = true; if (!request.params[1].isNull()) { fVerbose = request.params[1].get_bool(); } const CBlockIndex *pblockindex; const CBlockIndex *tip; { LOCK(cs_main); pblockindex = LookupBlockIndex(hash); tip = ::ChainActive().Tip(); } if (!pblockindex) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found"); } if (!fVerbose) { CDataStream ssBlock(SER_NETWORK, PROTOCOL_VERSION); ssBlock << pblockindex->GetBlockHeader(); std::string strHex = HexStr(ssBlock.begin(), ssBlock.end()); return strHex; } return blockheaderToJSON(tip, pblockindex); } static CBlock GetBlockChecked(const Config &config, const CBlockIndex *pblockindex) { CBlock block; if (IsBlockPruned(pblockindex)) { throw JSONRPCError(RPC_MISC_ERROR, "Block not available (pruned data)"); } if (!ReadBlockFromDisk(block, pblockindex, config.GetChainParams().GetConsensus())) { // Block not found on disk. This could be because we have the block // header in our index but don't have the block (for example if a // non-whitelisted node sends us an unrequested long chain of valid // blocks, we add the headers to our index, but don't accept the block). throw JSONRPCError(RPC_MISC_ERROR, "Block not found on disk"); } return block; } static UniValue getblock(const Config &config, const JSONRPCRequest &request) { if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) { throw std::runtime_error( RPCHelpMan{ "getblock", "\nIf verbosity is 0 or false, returns a string that is " "serialized, hex-encoded data for block 'hash'.\n" "If verbosity is 1 or true, returns an Object with information " "about block <hash>.\n" "If verbosity is 2, returns an Object with information about " "block <hash> and information about each transaction.\n", { - {"blockhash", RPCArg::Type::STR_HEX, false}, - {"verbosity", RPCArg::Type::NUM, true}, + {"blockhash", RPCArg::Type::STR_HEX, /* opt */ false, + /* default_val */ "", "The block hash"}, + {"verbosity", RPCArg::Type::NUM, /* opt */ true, + /* default_val */ "1", + "0 for hex-encoded data, 1 for a json object, and 2 for " + "json object with transaction data"}, }} - .ToString() + - "\nArguments:\n" - "1. \"blockhash\" (string, required) The block hash\n" - "2. verbosity (numeric, optional, default=1) 0 for " - "hex-encoded data, 1 for a json object, and 2 for json object with " - "transaction data\n" + .ToStringWithArgs() + "\nResult (for verbosity = 0):\n" "\"data\" (string) A string that is serialized, " "hex-encoded data for block 'hash'.\n" "\nResult (for verbosity = 1):\n" "{\n" " \"hash\" : \"hash\", (string) The block hash (same as " "provided)\n" " \"confirmations\" : n, (numeric) The number of confirmations, " "or -1 if the block is not on the main chain\n" " \"size\" : n, (numeric) The block size\n" " \"height\" : n, (numeric) The block height or index\n" " \"version\" : n, (numeric) The block version\n" " \"versionHex\" : \"00000000\", (string) The block version " "formatted in hexadecimal\n" " \"merkleroot\" : \"xxxx\", (string) The merkle root\n" " \"tx\" : [ (array of string) The transaction ids\n" " \"transactionid\" (string) The transaction id\n" " ,...\n" " ],\n" " \"time\" : ttt, (numeric) The block time in seconds " "since epoch (Jan 1 1970 GMT)\n" " \"mediantime\" : ttt, (numeric) The median block time in " "seconds since epoch (Jan 1 1970 GMT)\n" " \"nonce\" : n, (numeric) The nonce\n" " \"bits\" : \"1d00ffff\", (string) The bits\n" " \"difficulty\" : x.xxx, (numeric) The difficulty\n" " \"chainwork\" : \"xxxx\", (string) Expected number of hashes " "required to produce the chain up to this block (in hex)\n" " \"nTx\" : n, (numeric) The number of transactions " "in the block.\n" " \"previousblockhash\" : \"hash\", (string) The hash of the " "previous block\n" " \"nextblockhash\" : \"hash\" (string) The hash of the " "next block\n" "}\n" "\nResult (for verbosity = 2):\n" "{\n" " ..., Same output as verbosity = 1\n" " \"tx\" : [ (array of Objects) The transactions in " "the format of the getrawtransaction RPC; different from verbosity " "= 1 \"tx\" result\n" " ...\n" " ],\n" " ... Same output as verbosity = 1\n" "}\n" "\nExamples:\n" + HelpExampleCli("getblock", "\"00000000c937983704a73af28acdec37b049d" "214adbda81d7e2a3dd146f6ed09\"") + HelpExampleRpc("getblock", "\"00000000c937983704a73af28acdec37b049d" "214adbda81d7e2a3dd146f6ed09\"")); } LOCK(cs_main); BlockHash hash(ParseHashV(request.params[0], "blockhash")); int verbosity = 1; if (!request.params[1].isNull()) { if (request.params[1].isNum()) { verbosity = request.params[1].get_int(); } else { verbosity = request.params[1].get_bool() ? 1 : 0; } } const CBlockIndex *pblockindex = LookupBlockIndex(hash); if (!pblockindex) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found"); } const CBlock block = GetBlockChecked(config, pblockindex); if (verbosity <= 0) { CDataStream ssBlock(SER_NETWORK, PROTOCOL_VERSION | RPCSerializationFlags()); ssBlock << block; std::string strHex = HexStr(ssBlock.begin(), ssBlock.end()); return strHex; } return blockToJSON(block, ::ChainActive().Tip(), pblockindex, verbosity >= 2); } struct CCoinsStats { int nHeight; BlockHash hashBlock; uint64_t nTransactions; uint64_t nTransactionOutputs; uint64_t nBogoSize; uint256 hashSerialized; uint64_t nDiskSize; Amount nTotalAmount; CCoinsStats() : nHeight(0), nTransactions(0), nTransactionOutputs(0), nBogoSize(0), nDiskSize(0), nTotalAmount() {} }; static void ApplyStats(CCoinsStats &stats, CHashWriter &ss, const uint256 &hash, const std::map<uint32_t, Coin> &outputs) { assert(!outputs.empty()); ss << hash; ss << VARINT(outputs.begin()->second.GetHeight() * 2 + outputs.begin()->second.IsCoinBase()); stats.nTransactions++; for (const auto &output : outputs) { ss << VARINT(output.first + 1); ss << output.second.GetTxOut().scriptPubKey; ss << VARINT(output.second.GetTxOut().nValue / SATOSHI, VarIntMode::NONNEGATIVE_SIGNED); stats.nTransactionOutputs++; stats.nTotalAmount += output.second.GetTxOut().nValue; stats.nBogoSize += 32 /* txid */ + 4 /* vout index */ + 4 /* height + coinbase */ + 8 /* amount */ + 2 /* scriptPubKey len */ + output.second.GetTxOut().scriptPubKey.size() /* scriptPubKey */; } ss << VARINT(0u); } //! Calculate statistics about the unspent transaction output set static bool GetUTXOStats(CCoinsView *view, CCoinsStats &stats) { std::unique_ptr<CCoinsViewCursor> pcursor(view->Cursor()); assert(pcursor); CHashWriter ss(SER_GETHASH, PROTOCOL_VERSION); stats.hashBlock = pcursor->GetBestBlock(); { LOCK(cs_main); stats.nHeight = LookupBlockIndex(stats.hashBlock)->nHeight; } ss << stats.hashBlock; uint256 prevkey; std::map<uint32_t, Coin> outputs; while (pcursor->Valid()) { boost::this_thread::interruption_point(); COutPoint key; Coin coin; if (pcursor->GetKey(key) && pcursor->GetValue(coin)) { if (!outputs.empty() && key.GetTxId() != prevkey) { ApplyStats(stats, ss, prevkey, outputs); outputs.clear(); } prevkey = key.GetTxId(); outputs[key.GetN()] = std::move(coin); } else { return error("%s: unable to read value", __func__); } pcursor->Next(); } if (!outputs.empty()) { ApplyStats(stats, ss, prevkey, outputs); } stats.hashSerialized = ss.GetHash(); stats.nDiskSize = view->EstimateSize(); return true; } static UniValue pruneblockchain(const Config &config, const JSONRPCRequest &request) { if (request.fHelp || request.params.size() != 1) { throw std::runtime_error( - RPCHelpMan{"pruneblockchain", - "", - { - {"height", RPCArg::Type::NUM, false}, - }} - .ToString() + - "\nArguments:\n" - "1. \"height\" (numeric, required) The block height to prune " - "up to. May be set to a discrete height, or a unix timestamp\n" - " to prune blocks whose block time is at least 2 " - "hours older than the provided timestamp.\n" + RPCHelpMan{ + "pruneblockchain", + "", + { + {"height", RPCArg::Type::NUM, /* opt */ false, + /* default_val */ "", + "The block height to prune up to. May be set to a " + "discrete height, or a unix timestamp\n" + " to prune blocks whose block time is at " + "least 2 hours older than the provided timestamp."}, + }} + .ToStringWithArgs() + "\nResult:\n" "n (numeric) Height of the last block pruned.\n" "\nExamples:\n" + HelpExampleCli("pruneblockchain", "1000") + HelpExampleRpc("pruneblockchain", "1000")); } if (!fPruneMode) { throw JSONRPCError( RPC_MISC_ERROR, "Cannot prune blocks because node is not in prune mode."); } LOCK(cs_main); int heightParam = request.params[0].get_int(); if (heightParam < 0) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Negative block height."); } // Height value more than a billion is too high to be a block height, and // too low to be a block time (corresponds to timestamp from Sep 2001). if (heightParam > 1000000000) { // Add a 2 hour buffer to include blocks which might have had old // timestamps CBlockIndex *pindex = ::ChainActive().FindEarliestAtLeast(heightParam - TIMESTAMP_WINDOW); if (!pindex) { throw JSONRPCError( RPC_INVALID_PARAMETER, "Could not find block with at least the specified timestamp."); } heightParam = pindex->nHeight; } unsigned int height = (unsigned int)heightParam; unsigned int chainHeight = (unsigned int)::ChainActive().Height(); if (chainHeight < config.GetChainParams().PruneAfterHeight()) { throw JSONRPCError(RPC_MISC_ERROR, "Blockchain is too short for pruning."); } else if (height > chainHeight) { throw JSONRPCError( RPC_INVALID_PARAMETER, "Blockchain is shorter than the attempted prune height."); } else if (height > chainHeight - MIN_BLOCKS_TO_KEEP) { LogPrint(BCLog::RPC, "Attempt to prune blocks close to the tip. " "Retaining the minimum number of blocks.\n"); height = chainHeight - MIN_BLOCKS_TO_KEEP; } PruneBlockFilesManual(height); return uint64_t(height); } static UniValue gettxoutsetinfo(const Config &config, const JSONRPCRequest &request) { if (request.fHelp || request.params.size() != 0) { throw std::runtime_error( RPCHelpMan{"gettxoutsetinfo", "\nReturns statistics about the unspent transaction " "output set.\n" "Note this call may take some time.\n", {}} - .ToString() + + .ToStringWithArgs() + "\nResult:\n" "{\n" " \"height\":n, (numeric) The current block height (index)\n" " \"bestblock\": \"hex\", (string) the best block hash hex\n" " \"transactions\": n, (numeric) The number of transactions\n" " \"txouts\": n, (numeric) The number of output " "transactions\n" " \"bogosize\": n, (numeric) A database-independent " "metric for UTXO set size\n" " \"hash_serialized\": \"hash\", (string) The serialized hash\n" " \"disk_size\": n, (numeric) The estimated size of the " "chainstate on disk\n" " \"total_amount\": x.xxx (numeric) The total amount\n" "}\n" "\nExamples:\n" + HelpExampleCli("gettxoutsetinfo", "") + HelpExampleRpc("gettxoutsetinfo", "")); } UniValue ret(UniValue::VOBJ); CCoinsStats stats; FlushStateToDisk(); if (GetUTXOStats(pcoinsdbview.get(), stats)) { ret.pushKV("height", int64_t(stats.nHeight)); ret.pushKV("bestblock", stats.hashBlock.GetHex()); ret.pushKV("transactions", int64_t(stats.nTransactions)); ret.pushKV("txouts", int64_t(stats.nTransactionOutputs)); ret.pushKV("bogosize", int64_t(stats.nBogoSize)); ret.pushKV("hash_serialized", stats.hashSerialized.GetHex()); ret.pushKV("disk_size", stats.nDiskSize); ret.pushKV("total_amount", ValueFromAmount(stats.nTotalAmount)); } else { throw JSONRPCError(RPC_INTERNAL_ERROR, "Unable to read UTXO set"); } return ret; } UniValue gettxout(const Config &config, const JSONRPCRequest &request) { if (request.fHelp || request.params.size() < 2 || request.params.size() > 3) { throw std::runtime_error( RPCHelpMan{ "gettxout", "\nReturns details about an unspent transaction output.\n", { - {"txid", RPCArg::Type::STR_HEX, false}, - {"n", RPCArg::Type::NUM, false}, - {"include_mempool", RPCArg::Type::BOOL, true}, + {"txid", RPCArg::Type::STR_HEX, /* opt */ false, + /* default_val */ "", "The transaction id"}, + {"n", RPCArg::Type::NUM, /* opt */ false, + /* default_val */ "", "vout number"}, + {"include_mempool", RPCArg::Type::BOOL, /* opt */ true, + /* default_val */ "true", + "Whether to include the mempool. Note that an unspent " + "output that is spent in the mempool won't appear."}, }} - .ToString() + - "\nArguments:\n" - "1. \"txid\" (string, required) The transaction id\n" - "2. \"n\" (numeric, required) vout number\n" - "3. \"include_mempool\" (boolean, optional) Whether to include " - "the mempool. Default: true." - " Note that an unspent output that is spent in the mempool " - "won't appear.\n" + .ToStringWithArgs() + "\nResult:\n" "{\n" " \"bestblock\" : \"hash\", (string) the block hash\n" " \"confirmations\" : n, (numeric) The number of " "confirmations\n" " \"value\" : x.xxx, (numeric) The transaction value " "in " + CURRENCY_UNIT + "\n" " \"scriptPubKey\" : { (json object)\n" " \"asm\" : \"code\", (string) \n" " \"hex\" : \"hex\", (string) \n" " \"reqSigs\" : n, (numeric) Number of required " "signatures\n" " \"type\" : \"pubkeyhash\", (string) The type, eg pubkeyhash\n" " \"addresses\" : [ (array of string) array of " "bitcoin addresses\n" " \"address\" (string) bitcoin address\n" " ,...\n" " ]\n" " },\n" " \"coinbase\" : true|false (boolean) Coinbase or not\n" "}\n" "\nExamples:\n" "\nGet unspent transactions\n" + HelpExampleCli("listunspent", "") + "\nView the details\n" + HelpExampleCli("gettxout", "\"txid\" 1") + "\nAs a JSON-RPC call\n" + HelpExampleRpc("gettxout", "\"txid\", 1")); } LOCK(cs_main); UniValue ret(UniValue::VOBJ); TxId txid(ParseHashV(request.params[0], "txid")); int n = request.params[1].get_int(); COutPoint out(txid, n); bool fMempool = true; if (!request.params[2].isNull()) { fMempool = request.params[2].get_bool(); } Coin coin; if (fMempool) { LOCK(g_mempool.cs); CCoinsViewMemPool view(pcoinsTip.get(), g_mempool); if (!view.GetCoin(out, coin) || g_mempool.isSpent(out)) { return NullUniValue; } } else { if (!pcoinsTip->GetCoin(out, coin)) { return NullUniValue; } } const CBlockIndex *pindex = LookupBlockIndex(pcoinsTip->GetBestBlock()); ret.pushKV("bestblock", pindex->GetBlockHash().GetHex()); if (coin.GetHeight() == MEMPOOL_HEIGHT) { ret.pushKV("confirmations", 0); } else { ret.pushKV("confirmations", int64_t(pindex->nHeight - coin.GetHeight() + 1)); } ret.pushKV("value", ValueFromAmount(coin.GetTxOut().nValue)); UniValue o(UniValue::VOBJ); ScriptPubKeyToUniv(coin.GetTxOut().scriptPubKey, o, true); ret.pushKV("scriptPubKey", o); ret.pushKV("coinbase", coin.IsCoinBase()); return ret; } static UniValue verifychain(const Config &config, const JSONRPCRequest &request) { int nCheckLevel = gArgs.GetArg("-checklevel", DEFAULT_CHECKLEVEL); int nCheckDepth = gArgs.GetArg("-checkblocks", DEFAULT_CHECKBLOCKS); if (request.fHelp || request.params.size() > 2) { throw std::runtime_error( - RPCHelpMan{"verifychain", - "\nVerifies blockchain database.\n", - { - {"checklevel", RPCArg::Type::NUM, true}, - {"nblocks", RPCArg::Type::NUM, true}, - }} - .ToString() + - "\nArguments:\n" - "1. checklevel (numeric, optional, 0-4, default=" + - strprintf("%d", nCheckLevel) + - ") How thorough the block verification is.\n" - "2. nblocks (numeric, optional, default=" + - strprintf("%d", nCheckDepth) + - ", 0=all) The number of blocks to check.\n" + RPCHelpMan{ + "verifychain", + "\nVerifies blockchain database.\n", + { + {"checklevel", RPCArg::Type::NUM, /* opt */ true, + /* default_val */ strprintf("%d, range=0-4", nCheckLevel), + "How thorough the block verification is."}, + {"nblocks", RPCArg::Type::NUM, /* opt */ true, + /* default_val */ strprintf("%d, 0=all", nCheckDepth), + "The number of blocks to check."}, + }} + .ToStringWithArgs() + "\nResult:\n" "true|false (boolean) Verified or not\n" "\nExamples:\n" + HelpExampleCli("verifychain", "") + HelpExampleRpc("verifychain", "")); } LOCK(cs_main); if (!request.params[0].isNull()) { nCheckLevel = request.params[0].get_int(); } if (!request.params[1].isNull()) { nCheckDepth = request.params[1].get_int(); } return CVerifyDB().VerifyDB(config, pcoinsTip.get(), nCheckLevel, nCheckDepth); } static void BIP9SoftForkDescPushBack(UniValue &softforks, const Consensus::Params &consensusParams, Consensus::DeploymentPos id) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { // For BIP9 deployments. // Deployments (e.g. testdummy) with timeout value before Jan 1, 2009 are // hidden. A timeout value of 0 guarantees a softfork will never be // activated. This is used when merging logic to implement a proposed // softfork without a specified deployment schedule. if (consensusParams.vDeployments[id].nTimeout <= 1230768000) { return; } UniValue bip9(UniValue::VOBJ); const ThresholdState thresholdState = VersionBitsTipState(consensusParams, id); switch (thresholdState) { case ThresholdState::DEFINED: bip9.pushKV("status", "defined"); break; case ThresholdState::STARTED: bip9.pushKV("status", "started"); break; case ThresholdState::LOCKED_IN: bip9.pushKV("status", "locked_in"); break; case ThresholdState::ACTIVE: bip9.pushKV("status", "active"); break; case ThresholdState::FAILED: bip9.pushKV("status", "failed"); break; } if (ThresholdState::STARTED == thresholdState) { bip9.pushKV("bit", consensusParams.vDeployments[id].bit); } bip9.pushKV("start_time", consensusParams.vDeployments[id].nStartTime); bip9.pushKV("timeout", consensusParams.vDeployments[id].nTimeout); int64_t since_height = VersionBitsTipStateSinceHeight(consensusParams, id); bip9.pushKV("since", since_height); if (ThresholdState::STARTED == thresholdState) { UniValue statsUV(UniValue::VOBJ); BIP9Stats statsStruct = VersionBitsTipStatistics(consensusParams, id); statsUV.pushKV("period", statsStruct.period); statsUV.pushKV("threshold", statsStruct.threshold); statsUV.pushKV("elapsed", statsStruct.elapsed); statsUV.pushKV("count", statsStruct.count); statsUV.pushKV("possible", statsStruct.possible); bip9.pushKV("statistics", statsUV); } UniValue rv(UniValue::VOBJ); rv.pushKV("type", "bip9"); rv.pushKV("bip9", bip9); if (ThresholdState::ACTIVE == thresholdState) { rv.pushKV("height", since_height); } rv.pushKV("active", ThresholdState::ACTIVE == thresholdState); softforks.pushKV(VersionBitsDeploymentInfo[id].name, rv); } UniValue getblockchaininfo(const Config &config, const JSONRPCRequest &request) { if (request.fHelp || request.params.size() != 0) { throw std::runtime_error( RPCHelpMan{"getblockchaininfo", "Returns an object containing various state info " "regarding blockchain processing.\n", {}} - .ToString() + + .ToStringWithArgs() + "\nResult:\n" "{\n" " \"chain\": \"xxxx\", (string) current network name " "as defined in BIP70 (main, test, regtest)\n" " \"blocks\": xxxxxx, (numeric) the current number of " "blocks processed in the server\n" " \"headers\": xxxxxx, (numeric) the current number of " "headers we have validated\n" " \"bestblockhash\": \"...\", (string) the hash of the " "currently best block\n" " \"difficulty\": xxxxxx, (numeric) the current " "difficulty\n" " \"mediantime\": xxxxxx, (numeric) median time for the " "current best block\n" " \"verificationprogress\": xxxx, (numeric) estimate of " "verification progress [0..1]\n" " \"initialblockdownload\": xxxx, (bool) (debug information) " "estimate of whether this node is in Initial Block Download mode.\n" " \"chainwork\": \"xxxx\" (string) total amount of work " "in active chain, in hexadecimal\n" " \"size_on_disk\": xxxxxx, (numeric) the estimated size of " "the block and undo files on disk\n" " \"pruned\": xx, (boolean) if the blocks are " "subject to pruning\n" " \"pruneheight\": xxxxxx, (numeric) lowest-height " "complete block stored (only present if pruning is enabled)\n" " \"automatic_pruning\": xx, (boolean) whether automatic " "pruning is enabled (only present if pruning is enabled)\n" " \"prune_target_size\": xxxxxx, (numeric) the target size " "used by pruning (only present if automatic pruning is enabled)\n" " \"softforks\": { (object) status of softforks in " "progress\n" " \"xxxx\" : { (string) name of the softfork\n" " \"type\" : \"bip9\", (string) currently only set to " "\"bip9\"\n" " \"bip9\" : { (object) status of bip9 " "softforks (only for \"bip9\" type)\n" " \"status\": \"xxxx\", (string) one of \"defined\", " "\"started\", \"locked_in\", \"active\", \"failed\"\n" " \"bit\": xx, (numeric) the bit (0-28) in the " "block version field used to signal this softfork (only for " "\"started\" status)\n" " \"startTime\": xx, (numeric) the minimum median " "time past of a block at which the bit gains its meaning\n" " \"timeout\": xx, (numeric) the median time past " "of a block at which the deployment is considered failed if not " "yet locked in\n" " \"since\": xx, (numeric) height of the first " "block to which the status applies\n" " \"statistics\": { (object) numeric statistics " "about BIP9 signalling for a softfork (only for \"started\" " "status)\n" " \"period\": xx, (numeric) the length in blocks " "of the BIP9 signalling period \n" " \"threshold\": xx, (numeric) the number of blocks " "with the version bit set required to activate the feature \n" " \"elapsed\": xx, (numeric) the number of blocks " "elapsed since the beginning of the current period \n" " \"count\": xx, (numeric) the number of blocks " "with the version bit set in the current period \n" " \"possible\": xx (boolean) returns false if " "there are not enough blocks left in this period to pass " "activation threshold\n" " },\n" " \"active\": xx, (boolean) true if the rules are " "enforced for the mempool and the next block\n" " }\n" " }\n" " }\n" " \"warnings\" : \"...\", (string) any network and " "blockchain warnings.\n" "}\n" "\nExamples:\n" + HelpExampleCli("getblockchaininfo", "") + HelpExampleRpc("getblockchaininfo", "")); } LOCK(cs_main); const CChainParams &chainparams = config.GetChainParams(); const CBlockIndex *tip = ::ChainActive().Tip(); UniValue obj(UniValue::VOBJ); obj.pushKV("chain", chainparams.NetworkIDString()); obj.pushKV("blocks", int(::ChainActive().Height())); obj.pushKV("headers", pindexBestHeader ? pindexBestHeader->nHeight : -1); obj.pushKV("bestblockhash", tip->GetBlockHash().GetHex()); obj.pushKV("difficulty", double(GetDifficulty(tip))); obj.pushKV("mediantime", int64_t(tip->GetMedianTimePast())); obj.pushKV("verificationprogress", GuessVerificationProgress(Params().TxData(), tip)); obj.pushKV("initialblockdownload", IsInitialBlockDownload()); obj.pushKV("chainwork", tip->nChainWork.GetHex()); obj.pushKV("size_on_disk", CalculateCurrentUsage()); obj.pushKV("pruned", fPruneMode); if (fPruneMode) { const CBlockIndex *block = tip; assert(block); while (block->pprev && (block->pprev->nStatus.hasData())) { block = block->pprev; } obj.pushKV("pruneheight", block->nHeight); // if 0, execution bypasses the whole if block. bool automatic_pruning = (gArgs.GetArg("-prune", 0) != 1); obj.pushKV("automatic_pruning", automatic_pruning); if (automatic_pruning) { obj.pushKV("prune_target_size", nPruneTarget); } } UniValue softforks(UniValue::VOBJ); for (int i = 0; i < (int)Consensus::MAX_VERSION_BITS_DEPLOYMENTS; i++) { BIP9SoftForkDescPushBack(softforks, chainparams.GetConsensus(), Consensus::DeploymentPos(i)); } obj.pushKV("softforks", softforks); obj.pushKV("warnings", GetWarnings("statusbar")); return obj; } /** Comparison function for sorting the getchaintips heads. */ struct CompareBlocksByHeight { bool operator()(const CBlockIndex *a, const CBlockIndex *b) const { // Make sure that unequal blocks with the same height do not compare // equal. Use the pointers themselves to make a distinction. if (a->nHeight != b->nHeight) { return (a->nHeight > b->nHeight); } return a < b; } }; static UniValue getchaintips(const Config &config, const JSONRPCRequest &request) { if (request.fHelp || request.params.size() != 0) { throw std::runtime_error( RPCHelpMan{ "getchaintips", "Return information about all known tips in the block tree, " "including the main chain as well as orphaned branches.\n", {}} - .ToString() + + .ToStringWithArgs() + "\nResult:\n" "[\n" " {\n" " \"height\": xxxx, (numeric) height of the chain tip\n" " \"hash\": \"xxxx\", (string) block hash of the tip\n" " \"branchlen\": 0 (numeric) zero for main chain\n" " \"status\": \"active\" (string) \"active\" for the main " "chain\n" " },\n" " {\n" " \"height\": xxxx,\n" " \"hash\": \"xxxx\",\n" " \"branchlen\": 1 (numeric) length of branch " "connecting the tip to the main chain\n" " \"status\": \"xxxx\" (string) status of the chain " "(active, valid-fork, valid-headers, headers-only, invalid)\n" " }\n" "]\n" "Possible values for status:\n" "1. \"invalid\" This branch contains at least one " "invalid block\n" "2. \"parked\" This branch contains at least one " "parked block\n" "3. \"headers-only\" Not all blocks for this branch are " "available, but the headers are valid\n" "4. \"valid-headers\" All blocks are available for this " "branch, but they were never fully validated\n" "5. \"valid-fork\" This branch is not part of the " "active chain, but is fully validated\n" "6. \"active\" This is the tip of the active main " "chain, which is certainly valid\n" "\nExamples:\n" + HelpExampleCli("getchaintips", "") + HelpExampleRpc("getchaintips", "")); } LOCK(cs_main); /** * Idea: the set of chain tips is ::ChainActive().tip, plus orphan blocks * which do not have another orphan building off of them. Algorithm: * - Make one pass through mapBlockIndex, picking out the orphan blocks, * and also storing a set of the orphan block's pprev pointers. * - Iterate through the orphan blocks. If the block isn't pointed to by * another orphan, it is a chain tip. * - add ::ChainActive().Tip() */ std::set<const CBlockIndex *, CompareBlocksByHeight> setTips; std::set<const CBlockIndex *> setOrphans; std::set<const CBlockIndex *> setPrevs; for (const std::pair<const BlockHash, CBlockIndex *> &item : mapBlockIndex) { if (!::ChainActive().Contains(item.second)) { setOrphans.insert(item.second); setPrevs.insert(item.second->pprev); } } for (std::set<const CBlockIndex *>::iterator it = setOrphans.begin(); it != setOrphans.end(); ++it) { if (setPrevs.erase(*it) == 0) { setTips.insert(*it); } } // Always report the currently active tip. setTips.insert(::ChainActive().Tip()); /* Construct the output array. */ UniValue res(UniValue::VARR); for (const CBlockIndex *block : setTips) { UniValue obj(UniValue::VOBJ); obj.pushKV("height", block->nHeight); obj.pushKV("hash", block->phashBlock->GetHex()); const int branchLen = block->nHeight - ::ChainActive().FindFork(block)->nHeight; obj.pushKV("branchlen", branchLen); std::string status; if (::ChainActive().Contains(block)) { // This block is part of the currently active chain. status = "active"; } else if (block->nStatus.isInvalid()) { // This block or one of its ancestors is invalid. status = "invalid"; } else if (block->nStatus.isOnParkedChain()) { // This block or one of its ancestors is parked. status = "parked"; } else if (!block->HaveTxsDownloaded()) { // This block cannot be connected because full block data for it or // one of its parents is missing. status = "headers-only"; } else if (block->IsValid(BlockValidity::SCRIPTS)) { // This block is fully validated, but no longer part of the active // chain. It was probably the active block once, but was // reorganized. status = "valid-fork"; } else if (block->IsValid(BlockValidity::TREE)) { // The headers for this block are valid, but it has not been // validated. It was probably never part of the most-work chain. status = "valid-headers"; } else { // No clue. status = "unknown"; } obj.pushKV("status", status); res.push_back(obj); } return res; } UniValue MempoolInfoToJSON(const CTxMemPool &pool) { UniValue ret(UniValue::VOBJ); ret.pushKV("loaded", pool.IsLoaded()); ret.pushKV("size", (int64_t)pool.size()); ret.pushKV("bytes", (int64_t)pool.GetTotalTxSize()); ret.pushKV("usage", (int64_t)pool.DynamicMemoryUsage()); size_t maxmempool = gArgs.GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000; ret.pushKV("maxmempool", (int64_t)maxmempool); ret.pushKV( "mempoolminfee", ValueFromAmount(std::max(pool.GetMinFee(maxmempool), ::minRelayTxFee) .GetFeePerK())); ret.pushKV("minrelaytxfee", ValueFromAmount(::minRelayTxFee.GetFeePerK())); return ret; } static UniValue getmempoolinfo(const Config &config, const JSONRPCRequest &request) { if (request.fHelp || request.params.size() != 0) { throw std::runtime_error( RPCHelpMan{"getmempoolinfo", "\nReturns details on the active state of the TX memory " "pool.\n", {}} - .ToString() + + .ToStringWithArgs() + "\nResult:\n" "{\n" " \"loaded\": true|false (boolean) True if the mempool is " "fully loaded\n" " \"size\": xxxxx, (numeric) Current tx count\n" " \"bytes\": xxxxx, (numeric) Transaction size.\n" " \"usage\": xxxxx, (numeric) Total memory usage for " "the mempool\n" " \"maxmempool\": xxxxx, (numeric) Maximum memory usage " "for the mempool\n" " \"mempoolminfee\": xxxxx (numeric) Minimum fee rate in " + CURRENCY_UNIT + "/kB for tx to be accepted. Is the maximum of minrelaytxfee and " "minimum mempool fee\n" " \"minrelaytxfee\": xxxxx (numeric) Current minimum relay " "fee for transactions\n" "}\n" "\nExamples:\n" + HelpExampleCli("getmempoolinfo", "") + HelpExampleRpc("getmempoolinfo", "")); } return MempoolInfoToJSON(::g_mempool); } static UniValue preciousblock(const Config &config, const JSONRPCRequest &request) { if (request.fHelp || request.params.size() != 1) { throw std::runtime_error( RPCHelpMan{"preciousblock", "\nTreats a block as if it were received before others " "with the same work.\n" "\nA later preciousblock call can override the effect " "of an earlier one.\n" "\nThe effects of preciousblock are not retained across " "restarts.\n", { - {"blockhash", RPCArg::Type::STR_HEX, false}, + {"blockhash", RPCArg::Type::STR_HEX, /* opt */ false, + /* default_val */ "", + "the hash of the block to mark as precious"}, }} - .ToString() + - "\nArguments:\n" - "1. \"blockhash\" (string, required) the hash of the block to " - "mark as precious\n" + .ToStringWithArgs() + "\nResult:\n" "\nExamples:\n" + HelpExampleCli("preciousblock", "\"blockhash\"") + HelpExampleRpc("preciousblock", "\"blockhash\"")); } BlockHash hash(ParseHashV(request.params[0], "blockhash")); CBlockIndex *pblockindex; { LOCK(cs_main); pblockindex = LookupBlockIndex(hash); if (!pblockindex) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found"); } } CValidationState state; PreciousBlock(config, state, pblockindex); if (!state.IsValid()) { throw JSONRPCError(RPC_DATABASE_ERROR, state.GetRejectReason()); } return NullUniValue; } UniValue finalizeblock(const Config &config, const JSONRPCRequest &request) { if (request.fHelp || request.params.size() != 1) { throw std::runtime_error( RPCHelpMan{ "finalizeblock", "\nTreats a block as final. It cannot be reorged. Any chain\n" "that does not contain this block is invalid. Used on a less\n" "work chain, it can effectively PUTS YOU OUT OF CONSENSUS.\n" "USE WITH CAUTION!\n", { - {"blockhash", RPCArg::Type::STR_HEX, false}, + {"blockhash", RPCArg::Type::STR_HEX, /* opt */ false, + /* default_val */ "", + "the hash of the block to mark as invalid"}, }} - .ToString() + + .ToStringWithArgs() + "\nResult:\n" "\nExamples:\n" + HelpExampleCli("finalizeblock", "\"blockhash\"") + HelpExampleRpc("finalizeblock", "\"blockhash\"")); } std::string strHash = request.params[0].get_str(); BlockHash hash(uint256S(strHash)); CValidationState state; { LOCK(cs_main); CBlockIndex *pblockindex = LookupBlockIndex(hash); if (!pblockindex) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found"); } FinalizeBlockAndInvalidate(config, state, pblockindex); } if (state.IsValid()) { ActivateBestChain(config, state); } if (!state.IsValid()) { throw JSONRPCError(RPC_DATABASE_ERROR, FormatStateMessage(state)); } return NullUniValue; } static UniValue invalidateblock(const Config &config, const JSONRPCRequest &request) { if (request.fHelp || request.params.size() != 1) { throw std::runtime_error( RPCHelpMan{"invalidateblock", "\nPermanently marks a block as invalid, as if it " "violated a consensus rule.\n", { - {"blockhash", RPCArg::Type::STR_HEX, false}, + {"blockhash", RPCArg::Type::STR_HEX, /* opt */ false, + /* default_val */ "", + "the hash of the block to mark as invalid"}, }} - .ToString() + - "\nArguments:\n" - "1. \"blockhash\" (string, required) the hash of " - "the block to mark as invalid\n" + .ToStringWithArgs() + "\nResult:\n" "\nExamples:\n" + HelpExampleCli("invalidateblock", "\"blockhash\"") + HelpExampleRpc("invalidateblock", "\"blockhash\"")); } const BlockHash hash(ParseHashV(request.params[0], "blockhash")); CValidationState state; CBlockIndex *pblockindex; { LOCK(cs_main); pblockindex = LookupBlockIndex(hash); if (!pblockindex) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found"); } } InvalidateBlock(config, state, pblockindex); if (state.IsValid()) { ActivateBestChain(config, state); } if (!state.IsValid()) { throw JSONRPCError(RPC_DATABASE_ERROR, FormatStateMessage(state)); } return NullUniValue; } UniValue parkblock(const Config &config, const JSONRPCRequest &request) { if (request.fHelp || request.params.size() != 1) { throw std::runtime_error( - RPCHelpMan{"parkblock", - "\nMarks a block as parked.\n", - { - {"blockhash", RPCArg::Type::STR_HEX, false}, - }} - .ToString() + - "\nArguments:\n" - "1. \"blockhash\" (string, required) the " - "hash of the block to park\n" + RPCHelpMan{ + "parkblock", + "\nMarks a block as parked.\n", + { + {"blockhash", RPCArg::Type::STR_HEX, /* opt */ false, + /* default_val */ "", "the hash of the block to park"}, + }} + .ToStringWithArgs() + "\nResult:\n" "\nExamples:\n" + HelpExampleCli("parkblock", "\"blockhash\"") + HelpExampleRpc("parkblock", "\"blockhash\"")); } const std::string strHash = request.params[0].get_str(); const BlockHash hash(uint256S(strHash)); CValidationState state; CBlockIndex *pblockindex; { LOCK(cs_main); if (mapBlockIndex.count(hash) == 0) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found"); } pblockindex = mapBlockIndex[hash]; } ParkBlock(config, state, pblockindex); if (state.IsValid()) { ActivateBestChain(config, state); } if (!state.IsValid()) { throw JSONRPCError(RPC_DATABASE_ERROR, state.GetRejectReason()); } return NullUniValue; } static UniValue reconsiderblock(const Config &config, const JSONRPCRequest &request) { if (request.fHelp || request.params.size() != 1) { throw std::runtime_error( RPCHelpMan{ "reconsiderblock", "\nRemoves invalidity status of a block and its descendants, " "reconsider them for activation.\n" "This can be used to undo the effects of invalidateblock.\n", { - {"blockhash", RPCArg::Type::STR_HEX, false}, + {"blockhash", RPCArg::Type::STR_HEX, /* opt */ false, + /* default_val */ "", + "the hash of the block to reconsider"}, }} - .ToString() + - "\nArguments:\n" - "1. \"blockhash\" (string, required) the hash of the block to " - "reconsider\n" + .ToStringWithArgs() + "\nResult:\n" "\nExamples:\n" + HelpExampleCli("reconsiderblock", "\"blockhash\"") + HelpExampleRpc("reconsiderblock", "\"blockhash\"")); } const BlockHash hash(ParseHashV(request.params[0], "blockhash")); { LOCK(cs_main); CBlockIndex *pblockindex = LookupBlockIndex(hash); if (!pblockindex) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found"); } ResetBlockFailureFlags(pblockindex); } CValidationState state; ActivateBestChain(config, state); if (!state.IsValid()) { throw JSONRPCError(RPC_DATABASE_ERROR, FormatStateMessage(state)); } return NullUniValue; } UniValue unparkblock(const Config &config, const JSONRPCRequest &request) { if (request.fHelp || request.params.size() != 1) { throw std::runtime_error( - RPCHelpMan{"unparkblock", - "\nRemoves parked status of a block and its " - "descendants, reconsider them for activation.\n" - "This can be used to undo the effects of parkblock.\n", - { - {"blockhash", RPCArg::Type::STR_HEX, false}, - }} - .ToString() + - "\nArguments:\n" - "1. \"blockhash\" (string, required) the hash of the block to " - "unpark\n" + RPCHelpMan{ + "unparkblock", + "\nRemoves parked status of a block and its descendants, " + "reconsider them for activation.\n" + "This can be used to undo the effects of parkblock.\n", + { + {"blockhash", RPCArg::Type::STR_HEX, /* opt */ false, + /* default_val */ "", "the hash of the block to unpark"}, + }} + .ToStringWithArgs() + "\nResult:\n" "\nExamples:\n" + HelpExampleCli("unparkblock", "\"blockhash\"") + HelpExampleRpc("unparkblock", "\"blockhash\"")); } const std::string strHash = request.params[0].get_str(); const BlockHash hash(uint256S(strHash)); { LOCK(cs_main); if (mapBlockIndex.count(hash) == 0) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found"); } CBlockIndex *pblockindex = mapBlockIndex[hash]; UnparkBlockAndChildren(pblockindex); } CValidationState state; ActivateBestChain(config, state); if (!state.IsValid()) { throw JSONRPCError(RPC_DATABASE_ERROR, state.GetRejectReason()); } return NullUniValue; } static UniValue getchaintxstats(const Config &config, const JSONRPCRequest &request) { if (request.fHelp || request.params.size() > 2) { throw std::runtime_error( RPCHelpMan{"getchaintxstats", "\nCompute statistics about the total number and rate " "of transactions in the chain.\n", { - {"nblocks", RPCArg::Type::NUM, true}, - {"blockhash", RPCArg::Type::STR_HEX, true}, + {"nblocks", RPCArg::Type::NUM, /* opt */ true, + /* default_val */ "one month", + "Size of the window in number of blocks"}, + {"blockhash", RPCArg::Type::STR_HEX, /* opt */ true, + /* default_val */ "", + "The hash of the block that ends the window."}, }} - .ToString() + - "\nArguments:\n" - "1. nblocks (numeric, optional) Size of the window in number " - "of blocks (default: one month).\n" - "2. \"blockhash\" (string, optional) The hash of the block that " - "ends the window.\n" + .ToStringWithArgs() + "\nResult:\n" "{\n" " \"time\": xxxxx, (numeric) The " "timestamp for the final block in the window in UNIX format.\n" " \"txcount\": xxxxx, (numeric) The total " "number of transactions in the chain up to that point.\n" " \"window_final_block_hash\": \"...\", (string) The hash of " "the final block in the window.\n" " \"window_block_count\": xxxxx, (numeric) Size of " "the window in number of blocks.\n" " \"window_tx_count\": xxxxx, (numeric) The number " "of transactions in the window. Only returned if " "\"window_block_count\" is > 0.\n" " \"window_interval\": xxxxx, (numeric) The elapsed " "time in the window in seconds. Only returned if " "\"window_block_count\" is > 0.\n" " \"txrate\": x.xx, (numeric) The average " "rate of transactions per second in the window. Only returned if " "\"window_interval\" is > 0.\n" "}\n" "\nExamples:\n" + HelpExampleCli("getchaintxstats", "") + HelpExampleRpc("getchaintxstats", "2016")); } const CBlockIndex *pindex; // By default: 1 month int blockcount = 30 * 24 * 60 * 60 / config.GetChainParams().GetConsensus().nPowTargetSpacing; if (request.params[1].isNull()) { LOCK(cs_main); pindex = ::ChainActive().Tip(); } else { BlockHash hash(ParseHashV(request.params[1], "blockhash")); LOCK(cs_main); pindex = LookupBlockIndex(hash); if (!pindex) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found"); } if (!::ChainActive().Contains(pindex)) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Block is not in main chain"); } } assert(pindex != nullptr); if (request.params[0].isNull()) { blockcount = std::max(0, std::min(blockcount, pindex->nHeight - 1)); } else { blockcount = request.params[0].get_int(); if (blockcount < 0 || (blockcount > 0 && blockcount >= pindex->nHeight)) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid block count: " "should be between 0 and " "the block's height - 1"); } } const CBlockIndex *pindexPast = pindex->GetAncestor(pindex->nHeight - blockcount); int nTimeDiff = pindex->GetMedianTimePast() - pindexPast->GetMedianTimePast(); int nTxDiff = pindex->nChainTx - pindexPast->nChainTx; UniValue ret(UniValue::VOBJ); ret.pushKV("time", int64_t(pindex->nTime)); ret.pushKV("txcount", int64_t(pindex->nChainTx)); ret.pushKV("window_final_block_hash", pindex->GetBlockHash().GetHex()); ret.pushKV("window_block_count", blockcount); if (blockcount > 0) { ret.pushKV("window_tx_count", nTxDiff); ret.pushKV("window_interval", nTimeDiff); if (nTimeDiff > 0) { ret.pushKV("txrate", double(nTxDiff) / nTimeDiff); } } return ret; } template <typename T> static T CalculateTruncatedMedian(std::vector<T> &scores) { size_t size = scores.size(); if (size == 0) { return T(); } std::sort(scores.begin(), scores.end()); if (size % 2 == 0) { return (scores[size / 2 - 1] + scores[size / 2]) / 2; } else { return scores[size / 2]; } } template <typename T> static inline bool SetHasKeys(const std::set<T> &set) { return false; } template <typename T, typename Tk, typename... Args> static inline bool SetHasKeys(const std::set<T> &set, const Tk &key, const Args &... args) { return (set.count(key) != 0) || SetHasKeys(set, args...); } // outpoint (needed for the utxo index) + nHeight + fCoinBase static constexpr size_t PER_UTXO_OVERHEAD = sizeof(COutPoint) + sizeof(uint32_t) + sizeof(bool); static UniValue getblockstats(const Config &config, const JSONRPCRequest &request) { if (request.fHelp || request.params.size() < 1 || request.params.size() > 4) { throw std::runtime_error( RPCHelpMan{ "getblockstats", "\nCompute per block statistics for a given window. All " "amounts are in " + CURRENCY_UNIT + ".\n" "It won't work for some heights with pruning.\n" "It won't work without -txindex for utxo_size_inc, *fee or " "*feerate stats.\n", { - {"hash_or_height", RPCArg::Type::NUM, false}, + {"hash_or_height", + RPCArg::Type::NUM, + /* opt */ false, + /* default_val */ "", + "The block hash or height of the target block", + "", + {"", "string or numeric"}}, {"stats", RPCArg::Type::ARR, + /* opt */ true, + /* default_val */ "", + "Values to plot, by default all values (see result below)", { - {"height", RPCArg::Type::STR, true}, - {"time", RPCArg::Type::STR, true}, + {"height", RPCArg::Type::STR, /* opt */ true, + /* default_val */ "", "Selected statistic"}, + {"time", RPCArg::Type::STR, /* opt */ true, + /* default_val */ "", "Selected statistic"}, }, - true, "stats"}, }} - .ToString() + - "\nArguments:\n" - "1. \"hash_or_height\" (string or numeric, required) The block " - "hash or height of the target block\n" - "2. \"stats\" (array, optional) Values to plot, by " - "default all values (see result below)\n" - " [\n" - " \"height\", (string, optional) Selected statistic\n" - " \"time\", (string, optional) Selected statistic\n" - " ,...\n" - " ]\n" + .ToStringWithArgs() + "\nResult:\n" "{ (json object)\n" " \"avgfee\": x.xxx, (numeric) Average fee in the block\n" " \"avgfeerate\": x.xxx, (numeric) Average feerate (in " + CURRENCY_UNIT + " per byte)\n" " \"avgtxsize\": xxxxx, (numeric) Average transaction size\n" " \"blockhash\": xxxxx, (string) The block hash (to check " "for potential reorgs)\n" " \"height\": xxxxx, (numeric) The height of the block\n" " \"ins\": xxxxx, (numeric) The number of inputs " "(excluding coinbase)\n" " \"maxfee\": xxxxx, (numeric) Maximum fee in the block\n" " \"maxfeerate\": xxxxx, (numeric) Maximum feerate (in " + CURRENCY_UNIT + " per byte)\n" " \"maxtxsize\": xxxxx, (numeric) Maximum transaction size\n" " \"medianfee\": x.xxx, (numeric) Truncated median fee in " "the block\n" " \"medianfeerate\": x.xxx, (numeric) Truncated median feerate " "(in " + CURRENCY_UNIT + " per byte)\n" " \"mediantime\": xxxxx, (numeric) The block median time " "past\n" " \"mediantxsize\": xxxxx, (numeric) Truncated median " "transaction size\n" " \"minfee\": x.xxx, (numeric) Minimum fee in the block\n" " \"minfeerate\": xx.xx, (numeric) Minimum feerate (in " + CURRENCY_UNIT + " per byte)\n" " \"mintxsize\": xxxxx, (numeric) Minimum transaction size\n" " \"outs\": xxxxx, (numeric) The number of outputs\n" " \"subsidy\": x.xxx, (numeric) The block subsidy\n" " \"time\": xxxxx, (numeric) The block time\n" " \"total_out\": x.xxx, (numeric) Total amount in all " "outputs (excluding coinbase and thus reward [ie subsidy + " "totalfee])\n" " \"total_size\": xxxxx, (numeric) Total size of all " "non-coinbase transactions\n" " \"totalfee\": x.xxx, (numeric) The fee total\n" " \"txs\": xxxxx, (numeric) The number of " "transactions (excluding coinbase)\n" " \"utxo_increase\": xxxxx, (numeric) The increase/decrease in " "the number of unspent outputs\n" " \"utxo_size_inc\": xxxxx, (numeric) The increase/decrease in " "size for the utxo index (not discounting op_return and similar)\n" "}\n" "\nExamples:\n" + HelpExampleCli("getblockstats", "1000 '[\"minfeerate\",\"avgfeerate\"]'") + HelpExampleRpc("getblockstats", "1000 '[\"minfeerate\",\"avgfeerate\"]'")); } LOCK(cs_main); CBlockIndex *pindex; if (request.params[0].isNum()) { const int height = request.params[0].get_int(); const int current_tip = ::ChainActive().Height(); if (height < 0) { throw JSONRPCError( RPC_INVALID_PARAMETER, strprintf("Target block height %d is negative", height)); } if (height > current_tip) { throw JSONRPCError( RPC_INVALID_PARAMETER, strprintf("Target block height %d after current tip %d", height, current_tip)); } pindex = ::ChainActive()[height]; } else { const BlockHash hash(ParseHashV(request.params[0], "hash_or_height")); pindex = LookupBlockIndex(hash); if (!pindex) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found"); } if (!::ChainActive().Contains(pindex)) { throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Block is not in chain %s", Params().NetworkIDString())); } } assert(pindex != nullptr); std::set<std::string> stats; if (!request.params[1].isNull()) { const UniValue stats_univalue = request.params[1].get_array(); for (unsigned int i = 0; i < stats_univalue.size(); i++) { const std::string stat = stats_univalue[i].get_str(); stats.insert(stat); } } const CBlock block = GetBlockChecked(config, pindex); // Calculate everything if nothing selected (default) const bool do_all = stats.size() == 0; const bool do_mediantxsize = do_all || stats.count("mediantxsize") != 0; const bool do_medianfee = do_all || stats.count("medianfee") != 0; const bool do_medianfeerate = do_all || stats.count("medianfeerate") != 0; const bool loop_inputs = do_all || do_medianfee || do_medianfeerate || SetHasKeys(stats, "utxo_size_inc", "totalfee", "avgfee", "avgfeerate", "minfee", "maxfee", "minfeerate", "maxfeerate"); const bool loop_outputs = do_all || loop_inputs || stats.count("total_out"); const bool do_calculate_size = do_mediantxsize || loop_inputs || SetHasKeys(stats, "total_size", "avgtxsize", "mintxsize", "maxtxsize"); const int64_t blockMaxSize = config.GetMaxBlockSize(); Amount maxfee = Amount::zero(); Amount maxfeerate = Amount::zero(); Amount minfee = MAX_MONEY; Amount minfeerate = MAX_MONEY; Amount total_out = Amount::zero(); Amount totalfee = Amount::zero(); int64_t inputs = 0; int64_t maxtxsize = 0; int64_t mintxsize = blockMaxSize; int64_t outputs = 0; int64_t total_size = 0; int64_t utxo_size_inc = 0; std::vector<Amount> fee_array; std::vector<Amount> feerate_array; std::vector<int64_t> txsize_array; const Consensus::Params ¶ms = config.GetChainParams().GetConsensus(); for (const auto &tx : block.vtx) { outputs += tx->vout.size(); Amount tx_total_out = Amount::zero(); if (loop_outputs) { for (const CTxOut &out : tx->vout) { tx_total_out += out.nValue; utxo_size_inc += GetSerializeSize(out, PROTOCOL_VERSION) + PER_UTXO_OVERHEAD; } } if (tx->IsCoinBase()) { continue; } // Don't count coinbase's fake input inputs += tx->vin.size(); // Don't count coinbase reward total_out += tx_total_out; int64_t tx_size = 0; if (do_calculate_size) { tx_size = tx->GetTotalSize(); if (do_mediantxsize) { txsize_array.push_back(tx_size); } maxtxsize = std::max(maxtxsize, tx_size); mintxsize = std::min(mintxsize, tx_size); total_size += tx_size; } if (loop_inputs) { if (!g_txindex) { throw JSONRPCError(RPC_INVALID_PARAMETER, "One or more of the selected stats requires " "-txindex enabled"); } Amount tx_total_in = Amount::zero(); for (const CTxIn &in : tx->vin) { CTransactionRef tx_in; BlockHash hashBlock; if (!GetTransaction(in.prevout.GetTxId(), tx_in, params, hashBlock, false)) { throw JSONRPCError(RPC_INTERNAL_ERROR, std::string("Unexpected internal error " "(tx index seems corrupt)")); } CTxOut prevoutput = tx_in->vout[in.prevout.GetN()]; tx_total_in += prevoutput.nValue; utxo_size_inc -= GetSerializeSize(prevoutput, PROTOCOL_VERSION) + PER_UTXO_OVERHEAD; } Amount txfee = tx_total_in - tx_total_out; assert(MoneyRange(txfee)); if (do_medianfee) { fee_array.push_back(txfee); } maxfee = std::max(maxfee, txfee); minfee = std::min(minfee, txfee); totalfee += txfee; Amount feerate = txfee / tx_size; if (do_medianfeerate) { feerate_array.push_back(feerate); } maxfeerate = std::max(maxfeerate, feerate); minfeerate = std::min(minfeerate, feerate); } } UniValue ret_all(UniValue::VOBJ); ret_all.pushKV("avgfee", ValueFromAmount((block.vtx.size() > 1) ? totalfee / int((block.vtx.size() - 1)) : Amount::zero())); ret_all.pushKV("avgfeerate", ValueFromAmount((total_size > 0) ? totalfee / total_size : Amount::zero())); ret_all.pushKV("avgtxsize", (block.vtx.size() > 1) ? total_size / (block.vtx.size() - 1) : 0); ret_all.pushKV("blockhash", pindex->GetBlockHash().GetHex()); ret_all.pushKV("height", (int64_t)pindex->nHeight); ret_all.pushKV("ins", inputs); ret_all.pushKV("maxfee", ValueFromAmount(maxfee)); ret_all.pushKV("maxfeerate", ValueFromAmount(maxfeerate)); ret_all.pushKV("maxtxsize", maxtxsize); ret_all.pushKV("medianfee", ValueFromAmount(CalculateTruncatedMedian(fee_array))); ret_all.pushKV("medianfeerate", ValueFromAmount(CalculateTruncatedMedian(feerate_array))); ret_all.pushKV("mediantime", pindex->GetMedianTimePast()); ret_all.pushKV("mediantxsize", CalculateTruncatedMedian(txsize_array)); ret_all.pushKV( "minfee", ValueFromAmount((minfee == MAX_MONEY) ? Amount::zero() : minfee)); ret_all.pushKV("minfeerate", ValueFromAmount((minfeerate == MAX_MONEY) ? Amount::zero() : minfeerate)); ret_all.pushKV("mintxsize", mintxsize == blockMaxSize ? 0 : mintxsize); ret_all.pushKV("outs", outputs); ret_all.pushKV("subsidy", ValueFromAmount(GetBlockSubsidy( pindex->nHeight, Params().GetConsensus()))); ret_all.pushKV("time", pindex->GetBlockTime()); ret_all.pushKV("total_out", ValueFromAmount(total_out)); ret_all.pushKV("total_size", total_size); ret_all.pushKV("totalfee", ValueFromAmount(totalfee)); ret_all.pushKV("txs", (int64_t)block.vtx.size()); ret_all.pushKV("utxo_increase", outputs - inputs); ret_all.pushKV("utxo_size_inc", utxo_size_inc); if (do_all) { return ret_all; } UniValue ret(UniValue::VOBJ); for (const std::string &stat : stats) { const UniValue &value = ret_all[stat]; if (value.isNull()) { throw JSONRPCError( RPC_INVALID_PARAMETER, strprintf("Invalid selected statistic %s", stat)); } ret.pushKV(stat, value); } return ret; } static UniValue savemempool(const Config &config, const JSONRPCRequest &request) { if (request.fHelp || request.params.size() != 0) { throw std::runtime_error( RPCHelpMan{"savemempool", "\nDumps the mempool to disk. It will fail until the " "previous dump is fully loaded.\n", {}} - .ToString() + + .ToStringWithArgs() + "\nExamples:\n" + HelpExampleCli("savemempool", "") + HelpExampleRpc("savemempool", "")); } if (!::g_mempool.IsLoaded()) { throw JSONRPCError(RPC_MISC_ERROR, "The mempool was not loaded yet"); } if (!DumpMempool(::g_mempool)) { throw JSONRPCError(RPC_MISC_ERROR, "Unable to dump mempool to disk"); } return NullUniValue; } //! Search for a given set of pubkey scripts static bool FindScriptPubKey(std::atomic<int> &scan_progress, const std::atomic<bool> &should_abort, int64_t &count, CCoinsViewCursor *cursor, const std::set<CScript> &needles, std::map<COutPoint, Coin> &out_results) { scan_progress = 0; count = 0; while (cursor->Valid()) { COutPoint key; Coin coin; if (!cursor->GetKey(key) || !cursor->GetValue(coin)) { return false; } if (++count % 8192 == 0) { boost::this_thread::interruption_point(); if (should_abort) { // allow to abort the scan via the abort reference return false; } } if (count % 256 == 0) { // update progress reference every 256 item const TxId &txid = key.GetTxId(); uint32_t high = 0x100 * *txid.begin() + *(txid.begin() + 1); scan_progress = int(high * 100.0 / 65536.0 + 0.5); } if (needles.count(coin.GetTxOut().scriptPubKey)) { out_results.emplace(key, coin); } cursor->Next(); } scan_progress = 100; return true; } /** RAII object to prevent concurrency issue when scanning the txout set */ static std::mutex g_utxosetscan; static std::atomic<int> g_scan_progress; static std::atomic<bool> g_scan_in_progress; static std::atomic<bool> g_should_abort_scan; class CoinsViewScanReserver { private: bool m_could_reserve; public: explicit CoinsViewScanReserver() : m_could_reserve(false) {} bool reserve() { assert(!m_could_reserve); std::lock_guard<std::mutex> lock(g_utxosetscan); if (g_scan_in_progress) { return false; } g_scan_in_progress = true; m_could_reserve = true; return true; } ~CoinsViewScanReserver() { if (m_could_reserve) { std::lock_guard<std::mutex> lock(g_utxosetscan); g_scan_in_progress = false; } } }; static UniValue scantxoutset(const Config &config, const JSONRPCRequest &request) { if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) { throw std::runtime_error( - RPCHelpMan{"scantxoutset", - "\nEXPERIMENTAL warning: this call may be removed or " - "changed in future releases.\n" - "\nScans the unspent transaction output set for entries " - "that match certain output descriptors.\n" - "Examples of output descriptors are:\n" - " addr(<address>) Outputs whose " - "scriptPubKey corresponds to the specified address " - "(does not include P2PK)\n" - " raw(<hex script>) Outputs whose " - "scriptPubKey equals the specified hex scripts\n" - " combo(<pubkey>) P2PK and " - "P2PKH outputs for the given pubkey\n" - " pkh(<pubkey>) P2PKH outputs " - "for the given pubkey\n" - " sh(multi(<n>,<pubkey>,<pubkey>,...)) P2SH-multisig " - "outputs for the given threshold and pubkeys\n" - "\nIn the above, <pubkey> either refers to a fixed " - "public key in hexadecimal notation, or to an xpub/xprv " - "optionally followed by one\n" - "or more path elements separated by \"/\", and " - "optionally ending in \"/*\" (unhardened), or \"/*'\" " - "or \"/*h\" (hardened) to specify all\n" - "unhardened or hardened child keys.\n" - "In the latter case, a range needs to be specified by " - "below if different from 1000.\n" - "For more information on output descriptors, see the " - "documentation in the doc/descriptors.md file.\n", - { - {"action", RPCArg::Type::STR, false}, - {"scanobjects", - RPCArg::Type::ARR, - { - {"descriptor", - RPCArg::Type::OBJ, - { - {"desc", RPCArg::Type::STR, false}, - {"range", RPCArg::Type::NUM, true}, - }, - false, - "scanobjects"}, - }, - false}, - }} - .ToString() + - "\nArguments:\n" - "1. \"action\" (string, required) The action " - "to execute\n" - " \"start\" for starting a " - "scan\n" - " \"abort\" for aborting the " - "current scan (returns true when abort was successful)\n" - " \"status\" for progress " - "report (in %) of the current scan\n" - "2. \"scanobjects\" (array, required) Array of " - "scan objects\n" - " [ Every scan object is either a " - "string descriptor or an object:\n" - " \"descriptor\", (string, optional) An output " - "descriptor\n" - " { (object, optional) An object " - "with output descriptor and metadata\n" - " \"desc\": \"descriptor\", (string, required) An " - "output descriptor\n" - " \"range\": n, (numeric, optional) Up to " - "what child index HD chains should be explored (default: 1000)\n" - " },\n" - " ...\n" - " ]\n" + RPCHelpMan{ + "scantxoutset", + "\nEXPERIMENTAL warning: this call may be removed or changed " + "in future releases.\n" + "\nScans the unspent transaction output set for entries that " + "match certain output descriptors.\n" + "Examples of output descriptors are:\n" + " addr(<address>) Outputs whose " + "scriptPubKey corresponds to the specified address (does not " + "include P2PK)\n" + " raw(<hex script>) Outputs whose " + "scriptPubKey equals the specified hex scripts\n" + " combo(<pubkey>) P2PK and P2PKH " + "outputs for the given pubkey\n" + " pkh(<pubkey>) P2PKH outputs for " + "the given pubkey\n" + " sh(multi(<n>,<pubkey>,<pubkey>,...)) P2SH-multisig " + "outputs for the given threshold and pubkeys\n" + "\nIn the above, <pubkey> either refers to a fixed public key " + "in hexadecimal notation, or to an xpub/xprv optionally " + "followed by one\n" + "or more path elements separated by \"/\", and optionally " + "ending in \"/*\" (unhardened), or \"/*'\" or \"/*h\" " + "(hardened) to specify all\n" + "unhardened or hardened child keys.\n" + "In the latter case, a range needs to be specified by below if " + "different from 1000.\n" + "For more information on output descriptors, see the " + "documentation in the doc/descriptors.md file.\n", + { + {"action", RPCArg::Type::STR, /* opt */ false, + /* default_val */ "", + "The action to execute\n" + " \"start\" for " + "starting a scan\n" + " \"abort\" for " + "aborting the current scan (returns true when abort was " + "successful)\n" + " \"status\" for " + "progress report (in %) of the current scan"}, + {"scanobjects", + RPCArg::Type::ARR, + /* opt */ false, + /* default_val */ "", + "Array of scan objects\n" + " Every scan object is " + "either a string descriptor or an object:", + { + {"descriptor", RPCArg::Type::STR, /* opt */ true, + /* default_val */ "", "An output descriptor"}, + { + "", + RPCArg::Type::OBJ, + /* opt */ true, + /* default_val */ "", + "An object with output descriptor and metadata", + { + {"desc", RPCArg::Type::STR, /* opt */ false, + /* default_val */ "", "An output descriptor"}, + {"range", RPCArg::Type::NUM, /* opt */ true, + /* default_val */ "1000", + "Up to what child index HD chains should be " + "explored"}, + }, + }, + }, + "[scanobjects,...]"}, + }} + .ToStringWithArgs() + "\nResult:\n" "{\n" " \"unspents\": [\n" " {\n" " \"txid\" : \"transactionid\", (string) The transaction " "id\n" " \"vout\": n, (numeric) the vout value\n" " \"scriptPubKey\" : \"script\", (string) the script key\n" " \"amount\" : x.xxx, (numeric) The total amount " "in " + CURRENCY_UNIT + " of the unspent output\n" " \"height\" : n, (numeric) Height of the " "unspent transaction output\n" " }\n" " ,...], \n" " \"total_amount\" : x.xxx, (numeric) The total amount of " "all found unspent outputs in " + CURRENCY_UNIT + "\n" "]\n"); } RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VARR}); UniValue result(UniValue::VOBJ); if (request.params[0].get_str() == "status") { CoinsViewScanReserver reserver; if (reserver.reserve()) { // no scan in progress return NullUniValue; } result.pushKV("progress", g_scan_progress); return result; } else if (request.params[0].get_str() == "abort") { CoinsViewScanReserver reserver; if (reserver.reserve()) { // reserve was possible which means no scan was running return false; } // set the abort flag g_should_abort_scan = true; return true; } else if (request.params[0].get_str() == "start") { CoinsViewScanReserver reserver; if (!reserver.reserve()) { throw JSONRPCError( RPC_INVALID_PARAMETER, "Scan already in progress, use action \"abort\" or \"status\""); } std::set<CScript> needles; Amount total_in = Amount::zero(); // loop through the scan objects for (const UniValue &scanobject : request.params[1].get_array().getValues()) { std::string desc_str; int range = 1000; if (scanobject.isStr()) { desc_str = scanobject.get_str(); } else if (scanobject.isObject()) { UniValue desc_uni = find_value(scanobject, "desc"); if (desc_uni.isNull()) { throw JSONRPCError( RPC_INVALID_PARAMETER, "Descriptor needs to be provided in scan object"); } desc_str = desc_uni.get_str(); UniValue range_uni = find_value(scanobject, "range"); if (!range_uni.isNull()) { range = range_uni.get_int(); if (range < 0 || range > 1000000) { throw JSONRPCError(RPC_INVALID_PARAMETER, "range out of range"); } } } else { throw JSONRPCError( RPC_INVALID_PARAMETER, "Scan object needs to be either a string or an object"); } FlatSigningProvider provider; auto desc = Parse(desc_str, provider); if (!desc) { throw JSONRPCError( RPC_INVALID_ADDRESS_OR_KEY, strprintf("Invalid descriptor '%s'", desc_str)); } if (!desc->IsRange()) { range = 0; } for (int i = 0; i <= range; ++i) { std::vector<CScript> scripts; if (!desc->Expand(i, provider, scripts, provider)) { throw JSONRPCError( RPC_INVALID_ADDRESS_OR_KEY, strprintf( "Cannot derive script without private keys: '%s'", desc_str)); } needles.insert(scripts.begin(), scripts.end()); } } // Scan the unspent transaction output set for inputs UniValue unspents(UniValue::VARR); std::vector<CTxOut> input_txos; std::map<COutPoint, Coin> coins; g_should_abort_scan = false; g_scan_progress = 0; int64_t count = 0; std::unique_ptr<CCoinsViewCursor> pcursor; { LOCK(cs_main); FlushStateToDisk(); pcursor = std::unique_ptr<CCoinsViewCursor>(pcoinsdbview->Cursor()); assert(pcursor); } bool res = FindScriptPubKey(g_scan_progress, g_should_abort_scan, count, pcursor.get(), needles, coins); result.pushKV("success", res); result.pushKV("searched_items", count); for (const auto &it : coins) { const COutPoint &outpoint = it.first; const Coin &coin = it.second; const CTxOut &txo = coin.GetTxOut(); input_txos.push_back(txo); total_in += txo.nValue; UniValue unspent(UniValue::VOBJ); unspent.pushKV("txid", outpoint.GetTxId().GetHex()); unspent.pushKV("vout", int32_t(outpoint.GetN())); unspent.pushKV("scriptPubKey", HexStr(txo.scriptPubKey.begin(), txo.scriptPubKey.end())); unspent.pushKV("amount", ValueFromAmount(txo.nValue)); unspent.pushKV("height", int32_t(coin.GetHeight())); unspents.push_back(unspent); } result.pushKV("unspents", unspents); result.pushKV("total_amount", ValueFromAmount(total_in)); } else { throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid command"); } return result; } // clang-format off static const ContextFreeRPCCommand commands[] = { // category name actor (function) argNames // ------------------- ------------------------ ---------------------- ---------- { "blockchain", "getbestblockhash", getbestblockhash, {} }, { "blockchain", "getblock", getblock, {"blockhash","verbosity|verbose"} }, { "blockchain", "getblockchaininfo", getblockchaininfo, {} }, { "blockchain", "getblockcount", getblockcount, {} }, { "blockchain", "getblockhash", getblockhash, {"height"} }, { "blockchain", "getblockheader", getblockheader, {"blockhash","verbose"} }, { "blockchain", "getblockstats", getblockstats, {"hash_or_height","stats"} }, { "blockchain", "getchaintips", getchaintips, {} }, { "blockchain", "getchaintxstats", getchaintxstats, {"nblocks", "blockhash"} }, { "blockchain", "getdifficulty", getdifficulty, {} }, { "blockchain", "getmempoolancestors", getmempoolancestors, {"txid","verbose"} }, { "blockchain", "getmempooldescendants", getmempooldescendants, {"txid","verbose"} }, { "blockchain", "getmempoolentry", getmempoolentry, {"txid"} }, { "blockchain", "getmempoolinfo", getmempoolinfo, {} }, { "blockchain", "getrawmempool", getrawmempool, {"verbose"} }, { "blockchain", "gettxout", gettxout, {"txid","n","include_mempool"} }, { "blockchain", "gettxoutsetinfo", gettxoutsetinfo, {} }, { "blockchain", "pruneblockchain", pruneblockchain, {"height"} }, { "blockchain", "savemempool", savemempool, {} }, { "blockchain", "verifychain", verifychain, {"checklevel","nblocks"} }, { "blockchain", "preciousblock", preciousblock, {"blockhash"} }, { "blockchain", "scantxoutset", scantxoutset, {"action", "scanobjects"} }, /* Not shown in help */ { "hidden", "getfinalizedblockhash", getfinalizedblockhash, {} }, { "hidden", "finalizeblock", finalizeblock, {"blockhash"} }, { "hidden", "invalidateblock", invalidateblock, {"blockhash"} }, { "hidden", "parkblock", parkblock, {"blockhash"} }, { "hidden", "reconsiderblock", reconsiderblock, {"blockhash"} }, { "hidden", "syncwithvalidationinterfacequeue", syncwithvalidationinterfacequeue, {} }, { "hidden", "unparkblock", unparkblock, {"blockhash"} }, { "hidden", "waitfornewblock", waitfornewblock, {"timeout"} }, { "hidden", "waitforblock", waitforblock, {"blockhash","timeout"} }, { "hidden", "waitforblockheight", waitforblockheight, {"height","timeout"} }, }; // clang-format on void RegisterBlockchainRPCCommands(CRPCTable &t) { for (unsigned int vcidx = 0; vcidx < ARRAYLEN(commands); vcidx++) { t.appendCommand(commands[vcidx].name, &commands[vcidx]); } }