diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index 24062eb62..5f45d1681 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -1,2995 +1,2995 @@ // Copyright (c) 2010 Satoshi Nakamoto // Copyright (c) 2009-2019 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 <blockdb.h> #include <blockfilter.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/blockfilterindex.h> #include <network.h> #include <node/coinstats.h> #include <node/context.h> #include <node/utxo_snapshot.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 <txdb.h> #include <txmempool.h> #include <undo.h> #include <util/ref.h> #include <util/strencodings.h> #include <util/system.h> #include <util/translation.h> #include <validation.h> #include <validationinterface.h> #include <versionbitsinfo.h> // For VersionBitsDeploymentInfo #include <warnings.h> #include <condition_variable> #include <cstdint> #include <memory> #include <mutex> struct CUpdatedBlock { BlockHash hash; int height; }; static Mutex cs_blockchange; static std::condition_variable cond_blockchange; static CUpdatedBlock latestblock GUARDED_BY(cs_blockchange); NodeContext &EnsureNodeContext(const util::Ref &context) { if (!context.Has<NodeContext>()) { throw JSONRPCError(RPC_INTERNAL_ERROR, "Node context not found"); } return context.Get<NodeContext>(); } CTxMemPool &EnsureMemPool(const util::Ref &context) { NodeContext &node = EnsureNodeContext(context); if (!node.mempool) { throw JSONRPCError(RPC_CLIENT_MEMPOOL_DISABLED, "Mempool disabled or instance not found"); } return *node.mempool; } ChainstateManager &EnsureChainman(const util::Ref &context) { NodeContext &node = EnsureNodeContext(context); if (!node.chainman) { throw JSONRPCError(RPC_INTERNAL_ERROR, "Node chainman not found"); } return *node.chainman; } /** * Calculate the difficulty for a given block index. */ double GetDifficulty(const CBlockIndex *blockindex) { CHECK_NONFATAL(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) { // Serialize passed information without accessing chain state of the active // chain! // For performance reasons AssertLockNotHeld(cs_main); 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) { // Serialize passed information without accessing chain state of the active // chain! // For performance reasons AssertLockNotHeld(cs_main); 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) { RPCHelpMan{ "getblockcount", "\nReturns the height of the most-work fully-validated chain.\n" "The genesis block has height 0.\n", {}, RPCResult{RPCResult::Type::NUM, "", "The current block count"}, RPCExamples{HelpExampleCli("getblockcount", "") + HelpExampleRpc("getblockcount", "")}, } .Check(request); LOCK(cs_main); return ::ChainActive().Height(); } static UniValue getbestblockhash(const Config &config, const JSONRPCRequest &request) { RPCHelpMan{ "getbestblockhash", "Returns the hash of the best (tip) block in the " "most-work fully-validated chain.\n", {}, RPCResult{RPCResult::Type::STR_HEX, "", "the block hash, hex-encoded"}, RPCExamples{HelpExampleCli("getbestblockhash", "") + HelpExampleRpc("getbestblockhash", "")}, } .Check(request); LOCK(cs_main); return ::ChainActive().Tip()->GetBlockHash().GetHex(); } UniValue getfinalizedblockhash(const Config &config, const JSONRPCRequest &request) { RPCHelpMan{ "getfinalizedblockhash", "Returns the hash of the currently finalized block\n", {}, RPCResult{RPCResult::Type::STR_HEX, "", "the block hash, hex-encoded"}, RPCExamples{HelpExampleCli("getfinalizedblockhash", "") + HelpExampleRpc("getfinalizedblockhash", "")}, } .Check(request); LOCK(cs_main); const CBlockIndex *blockIndexFinalized = ::ChainstateActive().GetFinalizedBlock(); if (blockIndexFinalized) { return blockIndexFinalized->GetBlockHash().GetHex(); } return UniValue(UniValue::VSTR); } void RPCNotifyBlockChange(const CBlockIndex *pindex) { if (pindex) { LOCK(cs_blockchange); latestblock.hash = pindex->GetBlockHash(); latestblock.height = pindex->nHeight; } cond_blockchange.notify_all(); } static UniValue waitfornewblock(const Config &config, const JSONRPCRequest &request) { RPCHelpMan{ "waitfornewblock", "Waits 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, /* default */ "0", "Time in milliseconds to wait for a response. 0 indicates no " "timeout."}, }, RPCResult{RPCResult::Type::OBJ, "", "", { {RPCResult::Type::STR_HEX, "hash", "The blockhash"}, {RPCResult::Type::NUM, "height", "Block height"}, }}, RPCExamples{HelpExampleCli("waitfornewblock", "1000") + HelpExampleRpc("waitfornewblock", "1000")}, } .Check(request); 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]() EXCLUSIVE_LOCKS_REQUIRED(cs_blockchange) { return latestblock.height != block.height || latestblock.hash != block.hash || !IsRPCRunning(); }); } else { cond_blockchange.wait( lock, [&block]() EXCLUSIVE_LOCKS_REQUIRED(cs_blockchange) { 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) { RPCHelpMan{ "waitforblock", "Waits 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, RPCArg::Optional::NO, "Block hash to wait for."}, {"timeout", RPCArg::Type::NUM, /* default */ "0", "Time in milliseconds to wait for a response. 0 indicates no " "timeout."}, }, RPCResult{RPCResult::Type::OBJ, "", "", { {RPCResult::Type::STR_HEX, "hash", "The blockhash"}, {RPCResult::Type::NUM, "height", "Block height"}, }}, RPCExamples{HelpExampleCli("waitforblock", "\"0000000000079f8ef3d2c688c244eb7a4570b24c9" "ed7b4a8c619eb02596f8862\" 1000") + HelpExampleRpc("waitforblock", "\"0000000000079f8ef3d2c688c244eb7a4570b24c9" "ed7b4a8c619eb02596f8862\", 1000")}, } .Check(request); 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]() EXCLUSIVE_LOCKS_REQUIRED(cs_blockchange) { return latestblock.hash == hash || !IsRPCRunning(); }); } else { cond_blockchange.wait( lock, [&hash]() EXCLUSIVE_LOCKS_REQUIRED(cs_blockchange) { 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) { RPCHelpMan{ "waitforblockheight", "Waits 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, RPCArg::Optional::NO, "Block height to wait for."}, {"timeout", RPCArg::Type::NUM, /* default */ "0", "Time in milliseconds to wait for a response. 0 indicates no " "timeout."}, }, RPCResult{RPCResult::Type::OBJ, "", "", { {RPCResult::Type::STR_HEX, "hash", "The blockhash"}, {RPCResult::Type::NUM, "height", "Block height"}, }}, RPCExamples{HelpExampleCli("waitforblockheight", "100 1000") + HelpExampleRpc("waitforblockheight", "100, 1000")}, } .Check(request); 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]() EXCLUSIVE_LOCKS_REQUIRED(cs_blockchange) { return latestblock.height >= height || !IsRPCRunning(); }); } else { cond_blockchange.wait( lock, [&height]() EXCLUSIVE_LOCKS_REQUIRED(cs_blockchange) { 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) { RPCHelpMan{ "syncwithvalidationinterfacequeue", "Waits for the validation interface queue to catch up on everything " "that was there when we entered this function.\n", {}, RPCResult{RPCResult::Type::NONE, "", ""}, RPCExamples{HelpExampleCli("syncwithvalidationinterfacequeue", "") + HelpExampleRpc("syncwithvalidationinterfacequeue", "")}, } .Check(request); SyncWithValidationInterfaceQueue(); return NullUniValue; } static UniValue getdifficulty(const Config &config, const JSONRPCRequest &request) { RPCHelpMan{ "getdifficulty", "Returns the proof-of-work difficulty as a multiple of the minimum " "difficulty.\n", {}, RPCResult{RPCResult::Type::NUM, "", "the proof-of-work difficulty as a multiple of the minimum " "difficulty."}, RPCExamples{HelpExampleCli("getdifficulty", "") + HelpExampleRpc("getdifficulty", "")}, } .Check(request); LOCK(cs_main); return GetDifficulty(::ChainActive().Tip()); } static std::vector<RPCResult> MempoolEntryDescription() { const auto &ticker = Currency::get().ticker; return { RPCResult{RPCResult::Type::NUM, "size", "transaction size."}, RPCResult{RPCResult::Type::STR_AMOUNT, "fee", "transaction fee in " + ticker + " (DEPRECATED)"}, RPCResult{RPCResult::Type::STR_AMOUNT, "modifiedfee", "transaction fee with fee deltas used for mining priority " "(DEPRECATED)"}, RPCResult{RPCResult::Type::NUM_TIME, "time", "local time transaction entered pool in seconds since 1 Jan " "1970 GMT"}, RPCResult{RPCResult::Type::NUM, "height", "block height when transaction entered pool"}, RPCResult{RPCResult::Type::NUM, "descendantcount", "number of in-mempool descendant transactions (including " "this one)"}, RPCResult{RPCResult::Type::NUM, "descendantsize", "transaction size of in-mempool descendants " "(including this one)"}, RPCResult{RPCResult::Type::STR_AMOUNT, "descendantfees", "modified fees (see above) of in-mempool descendants " "(including this one) (DEPRECATED)"}, RPCResult{ RPCResult::Type::NUM, "ancestorcount", "number of in-mempool ancestor transactions (including this one)"}, RPCResult{ RPCResult::Type::NUM, "ancestorsize", "transaction size of in-mempool ancestors (including this one)"}, RPCResult{RPCResult::Type::STR_AMOUNT, "ancestorfees", "modified fees (see above) of in-mempool ancestors " "(including this one) (DEPRECATED)"}, RPCResult{RPCResult::Type::OBJ, "fees", "", { RPCResult{RPCResult::Type::STR_AMOUNT, "base", "transaction fee in " + ticker}, RPCResult{RPCResult::Type::STR_AMOUNT, "modified", "transaction fee with fee deltas used for " "mining priority in " + ticker}, RPCResult{RPCResult::Type::STR_AMOUNT, "ancestor", "modified fees (see above) of in-mempool " "ancestors (including this one) in " + ticker}, RPCResult{RPCResult::Type::STR_AMOUNT, "descendant", "modified fees (see above) of in-mempool " "descendants (including this one) in " + ticker}, }}, RPCResult{ RPCResult::Type::ARR, "depends", "unconfirmed transactions used as inputs for this transaction", {RPCResult{RPCResult::Type::STR_HEX, "transactionid", "parent transaction id"}}}, RPCResult{ RPCResult::Type::ARR, "spentby", "unconfirmed transactions spending outputs from this transaction", {RPCResult{RPCResult::Type::STR_HEX, "transactionid", "child transaction id"}}}, RPCResult{RPCResult::Type::BOOL, "unbroadcast", "Whether this transaction is currently unbroadcast (initial " "broadcast not yet acknowledged by any peers)"}, }; } 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", e.GetFee()); fees.pushKV("modified", e.GetModifiedFee()); fees.pushKV("ancestor", e.GetModFeesWithAncestors()); fees.pushKV("descendant", e.GetModFeesWithDescendants()); info.pushKV("fees", fees); info.pushKV("size", (int)e.GetTxSize()); info.pushKV("fee", e.GetFee()); info.pushKV("modifiedfee", e.GetModifiedFee()); info.pushKV("time", count_seconds(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); info.pushKV("unbroadcast", pool.IsUnbroadcastTx(tx.GetId())); } 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); // Mempool has unique entries so there is no advantage in using // UniValue::pushKV, which checks if the key already exists in O(N). // UniValue::__pushKV is used instead which currently is O(1). 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) { RPCHelpMan{ "getrawmempool", "Returns all transaction ids in memory pool as a json array of " "string transaction ids.\n" "\nHint: use getmempoolentry to fetch a specific transaction from the " "mempool.\n", { {"verbose", RPCArg::Type::BOOL, /* default */ "false", "True for a json object, false for array of transaction ids"}, }, { RPCResult{"for verbose = false", RPCResult::Type::ARR, "", "", { {RPCResult::Type::STR_HEX, "", "The transaction id"}, }}, RPCResult{"for verbose = true", RPCResult::Type::OBJ_DYN, "", "", { {RPCResult::Type::OBJ, "transactionid", "", MempoolEntryDescription()}, }}, }, RPCExamples{HelpExampleCli("getrawmempool", "true") + HelpExampleRpc("getrawmempool", "true")}, } .Check(request); bool fVerbose = false; if (!request.params[0].isNull()) { fVerbose = request.params[0].get_bool(); } return MempoolToJSON(EnsureMemPool(request.context), fVerbose); } static UniValue getmempoolancestors(const Config &config, const JSONRPCRequest &request) { RPCHelpMan{ "getmempoolancestors", "If txid is in the mempool, returns all in-mempool ancestors.\n", { {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id (must be in mempool)"}, {"verbose", RPCArg::Type::BOOL, /* default */ "false", "True for a json object, false for array of transaction ids"}, }, { RPCResult{ "for verbose = false", RPCResult::Type::ARR, "", "", {{RPCResult::Type::STR_HEX, "", "The transaction id of an in-mempool ancestor transaction"}}}, RPCResult{"for verbose = true", RPCResult::Type::OBJ_DYN, "", "", { {RPCResult::Type::OBJ, "transactionid", "", MempoolEntryDescription()}, }}, }, RPCExamples{HelpExampleCli("getmempoolancestors", "\"mytxid\"") + HelpExampleRpc("getmempoolancestors", "\"mytxid\"")}, } .Check(request); bool fVerbose = false; if (!request.params[1].isNull()) { fVerbose = request.params[1].get_bool(); } TxId txid(ParseHashV(request.params[0], "parameter 1")); const CTxMemPool &mempool = EnsureMemPool(request.context); LOCK(mempool.cs); CTxMemPool::txiter it = mempool.mapTx.find(txid); if (it == 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; 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(mempool, info, e); o.pushKV(_txid.ToString(), info); } return o; } } static UniValue getmempooldescendants(const Config &config, const JSONRPCRequest &request) { RPCHelpMan{ "getmempooldescendants", "If txid is in the mempool, returns all in-mempool descendants.\n", { {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id (must be in mempool)"}, {"verbose", RPCArg::Type::BOOL, /* default */ "false", "True for a json object, false for array of transaction ids"}, }, { RPCResult{"for verbose = false", RPCResult::Type::ARR, "", "", {{RPCResult::Type::STR_HEX, "", "The transaction id of an in-mempool descendant " "transaction"}}}, RPCResult{"for verbose = true", RPCResult::Type::OBJ_DYN, "", "", { {RPCResult::Type::OBJ, "transactionid", "", MempoolEntryDescription()}, }}, }, RPCExamples{HelpExampleCli("getmempooldescendants", "\"mytxid\"") + HelpExampleRpc("getmempooldescendants", "\"mytxid\"")}, } .Check(request); bool fVerbose = false; if (!request.params[1].isNull()) { fVerbose = request.params[1].get_bool(); } TxId txid(ParseHashV(request.params[0], "parameter 1")); const CTxMemPool &mempool = EnsureMemPool(request.context); LOCK(mempool.cs); CTxMemPool::txiter it = mempool.mapTx.find(txid); if (it == mempool.mapTx.end()) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction not in mempool"); } CTxMemPool::setEntries setDescendants; 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(mempool, info, e); o.pushKV(_txid.ToString(), info); } return o; } } static UniValue getmempoolentry(const Config &config, const JSONRPCRequest &request) { RPCHelpMan{ "getmempoolentry", "Returns mempool data for given transaction\n", { {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id (must be in mempool)"}, }, RPCResult{RPCResult::Type::OBJ, "", "", MempoolEntryDescription()}, RPCExamples{HelpExampleCli("getmempoolentry", "\"mytxid\"") + HelpExampleRpc("getmempoolentry", "\"mytxid\"")}, } .Check(request); TxId txid(ParseHashV(request.params[0], "parameter 1")); const CTxMemPool &mempool = EnsureMemPool(request.context); LOCK(mempool.cs); CTxMemPool::txiter it = mempool.mapTx.find(txid); if (it == mempool.mapTx.end()) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction not in mempool"); } const CTxMemPoolEntry &e = *it; UniValue info(UniValue::VOBJ); entryToJSON(mempool, info, e); return info; } static UniValue getblockhash(const Config &config, const JSONRPCRequest &request) { RPCHelpMan{ "getblockhash", "Returns hash of block in best-block-chain at height provided.\n", { {"height", RPCArg::Type::NUM, RPCArg::Optional::NO, "The height index"}, }, RPCResult{RPCResult::Type::STR_HEX, "", "The block hash"}, RPCExamples{HelpExampleCli("getblockhash", "1000") + HelpExampleRpc("getblockhash", "1000")}, } .Check(request); 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) { RPCHelpMan{ "getblockheader", "If 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, RPCArg::Optional::NO, "The block hash"}, {"verbose", RPCArg::Type::BOOL, /* default */ "true", "true for a json object, false for the hex-encoded data"}, }, { RPCResult{ "for verbose = true", RPCResult::Type::OBJ, "", "", { {RPCResult::Type::STR_HEX, "hash", "the block hash (same as provided)"}, {RPCResult::Type::NUM, "confirmations", "The number of confirmations, or -1 if the block is not " "on the main chain"}, {RPCResult::Type::NUM, "height", "The block height or index"}, {RPCResult::Type::NUM, "version", "The block version"}, {RPCResult::Type::STR_HEX, "versionHex", "The block version formatted in hexadecimal"}, {RPCResult::Type::STR_HEX, "merkleroot", "The merkle root"}, {RPCResult::Type::NUM_TIME, "time", "The block time expressed in " + UNIX_EPOCH_TIME}, {RPCResult::Type::NUM_TIME, "mediantime", "The median block time expressed in " + UNIX_EPOCH_TIME}, {RPCResult::Type::NUM, "nonce", "The nonce"}, {RPCResult::Type::STR_HEX, "bits", "The bits"}, {RPCResult::Type::NUM, "difficulty", "The difficulty"}, {RPCResult::Type::STR_HEX, "chainwork", "Expected number of hashes required to produce the " "current chain"}, {RPCResult::Type::NUM, "nTx", "The number of transactions in the block"}, {RPCResult::Type::STR_HEX, "previousblockhash", "The hash of the previous block"}, {RPCResult::Type::STR_HEX, "nextblockhash", "The hash of the next block"}, }}, RPCResult{"for verbose=false", RPCResult::Type::STR_HEX, "", "A string that is serialized, hex-encoded data for block " "'hash'"}, }, RPCExamples{HelpExampleCli("getblockheader", "\"00000000c937983704a73af28acdec37b049d214a" "dbda81d7e2a3dd146f6ed09\"") + HelpExampleRpc("getblockheader", "\"00000000c937983704a73af28acdec37b049d214a" "dbda81d7e2a3dd146f6ed09\"")}, } .Check(request); 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); 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 not yet have the block or did not accept the // block. throw JSONRPCError(RPC_MISC_ERROR, "Block not found on disk"); } return block; } static CBlockUndo GetUndoChecked(const CBlockIndex *pblockindex) { CBlockUndo blockUndo; if (IsBlockPruned(pblockindex)) { throw JSONRPCError(RPC_MISC_ERROR, "Undo data not available (pruned data)"); } if (!UndoReadFromDisk(blockUndo, pblockindex)) { throw JSONRPCError(RPC_MISC_ERROR, "Can't read undo data from disk"); } return blockUndo; } static UniValue getblock(const Config &config, const JSONRPCRequest &request) { RPCHelpMan{ "getblock", "If 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, RPCArg::Optional::NO, "The block hash"}, {"verbosity|verbose", RPCArg::Type::NUM, /* default */ "1", "0 for hex-encoded data, 1 for a json object, and 2 for json " "object with transaction data"}, }, { RPCResult{"for verbosity = 0", RPCResult::Type::STR_HEX, "", "A string that is serialized, hex-encoded data for block " "'hash'"}, RPCResult{ "for verbosity = 1", RPCResult::Type::OBJ, "", "", { {RPCResult::Type::STR_HEX, "hash", "the block hash (same as provided)"}, {RPCResult::Type::NUM, "confirmations", "The number of confirmations, or -1 if the block is not " "on the main chain"}, {RPCResult::Type::NUM, "size", "The block size"}, {RPCResult::Type::NUM, "height", "The block height or index"}, {RPCResult::Type::NUM, "version", "The block version"}, {RPCResult::Type::STR_HEX, "versionHex", "The block version formatted in hexadecimal"}, {RPCResult::Type::STR_HEX, "merkleroot", "The merkle root"}, {RPCResult::Type::ARR, "tx", "The transaction ids", {{RPCResult::Type::STR_HEX, "", "The transaction id"}}}, {RPCResult::Type::NUM_TIME, "time", "The block time expressed in " + UNIX_EPOCH_TIME}, {RPCResult::Type::NUM_TIME, "mediantime", "The median block time expressed in " + UNIX_EPOCH_TIME}, {RPCResult::Type::NUM, "nonce", "The nonce"}, {RPCResult::Type::STR_HEX, "bits", "The bits"}, {RPCResult::Type::NUM, "difficulty", "The difficulty"}, {RPCResult::Type::STR_HEX, "chainwork", "Expected number of hashes required to produce the chain " "up to this block (in hex)"}, {RPCResult::Type::NUM, "nTx", "The number of transactions in the block"}, {RPCResult::Type::STR_HEX, "previousblockhash", "The hash of the previous block"}, {RPCResult::Type::STR_HEX, "nextblockhash", "The hash of the next block"}, }}, RPCResult{"for verbosity = 2", RPCResult::Type::OBJ, "", "", { {RPCResult::Type::ELISION, "", "Same output as verbosity = 1"}, {RPCResult::Type::ARR, "tx", "", { {RPCResult::Type::OBJ, "", "", { {RPCResult::Type::ELISION, "", "The transactions in the format of the " "getrawtransaction RPC. Different from " "verbosity = 1 \"tx\" result"}, }}, }}, {RPCResult::Type::ELISION, "", "Same output as verbosity = 1"}, }}, }, RPCExamples{ HelpExampleCli("getblock", "\"00000000c937983704a73af28acdec37b049d" "214adbda81d7e2a3dd146f6ed09\"") + HelpExampleRpc("getblock", "\"00000000c937983704a73af28acdec37b049d" "214adbda81d7e2a3dd146f6ed09\"")}, } .Check(request); 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; } } CBlock block; 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"); } block = GetBlockChecked(config, pblockindex); } if (verbosity <= 0) { CDataStream ssBlock(SER_NETWORK, PROTOCOL_VERSION | RPCSerializationFlags()); ssBlock << block; std::string strHex = HexStr(ssBlock); return strHex; } return blockToJSON(block, tip, pblockindex, verbosity >= 2); } static UniValue pruneblockchain(const Config &config, const JSONRPCRequest &request) { RPCHelpMan{ "pruneblockchain", "", { {"height", RPCArg::Type::NUM, RPCArg::Optional::NO, "The block height to prune up to. May be set to a discrete " "height, or to a " + UNIX_EPOCH_TIME + "\n" " to prune blocks whose block time is at " "least 2 hours older than the provided timestamp."}, }, RPCResult{RPCResult::Type::NUM, "", "Height of the last block pruned"}, RPCExamples{HelpExampleCli("pruneblockchain", "1000") + HelpExampleRpc("pruneblockchain", "1000")}, } .Check(request); 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, 0); 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); const CBlockIndex *block = ::ChainActive().Tip(); CHECK_NONFATAL(block); while (block->pprev && (block->pprev->nStatus.hasData())) { block = block->pprev; } return uint64_t(block->nHeight); } static UniValue gettxoutsetinfo(const Config &config, const JSONRPCRequest &request) { RPCHelpMan{ "gettxoutsetinfo", "Returns statistics about the unspent transaction output set.\n" "Note this call may take some time.\n", { {"hash_type", RPCArg::Type::STR, /* default */ "hash_serialized", "Which UTXO set hash should be calculated. Options: " "'hash_serialized' (the legacy algorithm), 'none'."}, }, RPCResult{RPCResult::Type::OBJ, "", "", { {RPCResult::Type::NUM, "height", "The current block height (index)"}, {RPCResult::Type::STR_HEX, "bestblock", "The hash of the block at the tip of the chain"}, {RPCResult::Type::NUM, "transactions", "The number of transactions with unspent outputs"}, {RPCResult::Type::NUM, "txouts", "The number of unspent transaction outputs"}, {RPCResult::Type::NUM, "bogosize", "A meaningless metric for UTXO set size"}, {RPCResult::Type::STR_HEX, "hash_serialized", "The serialized hash (only present if 'hash_serialized' " "hash_type is chosen)"}, {RPCResult::Type::NUM, "disk_size", "The estimated size of the chainstate on disk"}, {RPCResult::Type::STR_AMOUNT, "total_amount", "The total amount"}, }}, RPCExamples{HelpExampleCli("gettxoutsetinfo", "") + HelpExampleRpc("gettxoutsetinfo", "")}, } .Check(request); UniValue ret(UniValue::VOBJ); CCoinsStats stats; ::ChainstateActive().ForceFlushStateToDisk(); const CoinStatsHashType hash_type = ParseHashType(request.params[0], CoinStatsHashType::HASH_SERIALIZED); CCoinsView *coins_view = WITH_LOCK(cs_main, return &ChainstateActive().CoinsDB()); NodeContext &node = EnsureNodeContext(request.context); if (GetUTXOStats(coins_view, stats, hash_type, node.rpc_interruption_point)) { 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)); if (hash_type == CoinStatsHashType::HASH_SERIALIZED) { ret.pushKV("hash_serialized", stats.hashSerialized.GetHex()); } ret.pushKV("disk_size", stats.nDiskSize); ret.pushKV("total_amount", stats.nTotalAmount); } else { throw JSONRPCError(RPC_INTERNAL_ERROR, "Unable to read UTXO set"); } return ret; } UniValue gettxout(const Config &config, const JSONRPCRequest &request) { RPCHelpMan{ "gettxout", "Returns details about an unspent transaction output.\n", { {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id"}, {"n", RPCArg::Type::NUM, RPCArg::Optional::NO, "vout number"}, {"include_mempool", RPCArg::Type::BOOL, /* default */ "true", "Whether to include the mempool. Note that an unspent output that " "is spent in the mempool won't appear."}, }, RPCResult{ RPCResult::Type::OBJ, "", "", { {RPCResult::Type::STR_HEX, "bestblock", "The hash of the block at the tip of the chain"}, {RPCResult::Type::NUM, "confirmations", "The number of confirmations"}, {RPCResult::Type::STR_AMOUNT, "value", "The transaction value in " + Currency::get().ticker}, {RPCResult::Type::OBJ, "scriptPubKey", "", { {RPCResult::Type::STR_HEX, "asm", ""}, {RPCResult::Type::STR_HEX, "hex", ""}, {RPCResult::Type::NUM, "reqSigs", "Number of required signatures"}, {RPCResult::Type::STR_HEX, "type", "The type, eg pubkeyhash"}, {RPCResult::Type::ARR, "addresses", "array of bitcoin addresses", {{RPCResult::Type::STR, "address", "bitcoin address"}}}, }}, {RPCResult::Type::BOOL, "coinbase", "Coinbase or not"}, }}, RPCExamples{"\nGet unspent transactions\n" + HelpExampleCli("listunspent", "") + "\nView the details\n" + HelpExampleCli("gettxout", "\"txid\" 1") + "\nAs a JSON-RPC call\n" + HelpExampleRpc("gettxout", "\"txid\", 1")}, } .Check(request); 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; CCoinsViewCache *coins_view = &::ChainstateActive().CoinsTip(); if (fMempool) { const CTxMemPool &mempool = EnsureMemPool(request.context); LOCK(mempool.cs); CCoinsViewMemPool view(coins_view, mempool); if (!view.GetCoin(out, coin) || mempool.isSpent(out)) { return NullUniValue; } } else { if (!coins_view->GetCoin(out, coin)) { return NullUniValue; } } const CBlockIndex *pindex = LookupBlockIndex(coins_view->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", 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) { RPCHelpMan{ "verifychain", "Verifies blockchain database.\n", { {"checklevel", RPCArg::Type::NUM, /* default */ strprintf("%d, range=0-4", DEFAULT_CHECKLEVEL), strprintf("How thorough the block verification is:\n - %s", Join(CHECKLEVEL_DOC, "\n- "))}, {"nblocks", RPCArg::Type::NUM, /* default */ strprintf("%d, 0=all", DEFAULT_CHECKBLOCKS), "The number of blocks to check."}, }, RPCResult{RPCResult::Type::BOOL, "", "Verified or not"}, RPCExamples{HelpExampleCli("verifychain", "") + HelpExampleRpc("verifychain", "")}, } .Check(request); const int check_level(request.params[0].isNull() ? DEFAULT_CHECKLEVEL : request.params[0].get_int()); const int check_depth{request.params[1].isNull() ? DEFAULT_CHECKBLOCKS : request.params[1].get_int()}; LOCK(cs_main); return CVerifyDB().VerifyDB(config, &::ChainstateActive().CoinsTip(), check_level, check_depth); } 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) { RPCHelpMan{ "getblockchaininfo", "Returns an object containing various state info regarding blockchain " "processing.\n", {}, RPCResult{ RPCResult::Type::OBJ, "", "", { {RPCResult::Type::STR, "chain", "current network name (main, test, regtest)"}, {RPCResult::Type::NUM, "blocks", "the height of the most-work fully-validated chain. The " "genesis block has height 0"}, {RPCResult::Type::NUM, "headers", "the current number of headers we have validated"}, {RPCResult::Type::STR, "bestblockhash", "the hash of the currently best block"}, {RPCResult::Type::NUM, "difficulty", "the current difficulty"}, {RPCResult::Type::NUM, "mediantime", "median time for the current best block"}, {RPCResult::Type::NUM, "verificationprogress", "estimate of verification progress [0..1]"}, {RPCResult::Type::BOOL, "initialblockdownload", "(debug information) estimate of whether this node is in " "Initial Block Download mode"}, {RPCResult::Type::STR_HEX, "chainwork", "total amount of work in active chain, in hexadecimal"}, {RPCResult::Type::NUM, "size_on_disk", "the estimated size of the block and undo files on disk"}, {RPCResult::Type::BOOL, "pruned", "if the blocks are subject to pruning"}, {RPCResult::Type::NUM, "pruneheight", "lowest-height complete block stored (only present if pruning " "is enabled)"}, {RPCResult::Type::BOOL, "automatic_pruning", "whether automatic pruning is enabled (only present if " "pruning is enabled)"}, {RPCResult::Type::NUM, "prune_target_size", "the target size used by pruning (only present if automatic " "pruning is enabled)"}, {RPCResult::Type::OBJ_DYN, "softforks", "status of softforks", { {RPCResult::Type::OBJ, "xxxx", "name of the softfork", { {RPCResult::Type::STR, "type", "one of \"buried\", \"bip9\""}, {RPCResult::Type::OBJ, "bip9", "status of bip9 softforks (only for \"bip9\" type)", { {RPCResult::Type::STR, "status", "one of \"defined\", \"started\", " "\"locked_in\", \"active\", \"failed\""}, {RPCResult::Type::NUM, "bit", "the bit (0-28) in the block version field " "used to signal this softfork (only for " "\"started\" status)"}, {RPCResult::Type::NUM_TIME, "start_time", "the minimum median time past of a block at " "which the bit gains its meaning"}, {RPCResult::Type::NUM_TIME, "timeout", "the median time past of a block at which the " "deployment is considered failed if not yet " "locked in"}, {RPCResult::Type::NUM, "since", "height of the first block to which the status " "applies"}, {RPCResult::Type::OBJ, "statistics", "numeric statistics about BIP9 signalling for " "a softfork", { {RPCResult::Type::NUM, "period", "the length in blocks of the BIP9 " "signalling period"}, {RPCResult::Type::NUM, "threshold", "the number of blocks with the version " "bit set required to activate the " "feature"}, {RPCResult::Type::NUM, "elapsed", "the number of blocks elapsed since the " "beginning of the current period"}, {RPCResult::Type::NUM, "count", "the number of blocks with the version " "bit set in the current period"}, {RPCResult::Type::BOOL, "possible", "returns false if there are not enough " "blocks left in this period to pass " "activation threshold"}, }}, }}, {RPCResult::Type::NUM, "height", "height of the first block which the rules are or " "will be enforced (only for \"buried\" type, or " "\"bip9\" type with \"active\" status)"}, {RPCResult::Type::BOOL, "active", "true if the rules are enforced for the mempool and " "the next block"}, }}, }}, {RPCResult::Type::STR, "warnings", "any network and blockchain warnings"}, }}, RPCExamples{HelpExampleCli("getblockchaininfo", "") + HelpExampleRpc("getblockchaininfo", "")}, } .Check(request); 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", ::ChainstateActive().IsInitialBlockDownload()); obj.pushKV("chainwork", tip->nChainWork.GetHex()); obj.pushKV("size_on_disk", CalculateCurrentUsage()); obj.pushKV("pruned", fPruneMode); if (fPruneMode) { const CBlockIndex *block = tip; CHECK_NONFATAL(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(false).original); 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) { RPCHelpMan{ "getchaintips", "Return information about all known tips in the block tree, including " "the main chain as well as orphaned branches.\n", {}, RPCResult{ RPCResult::Type::ARR, "", "", {{RPCResult::Type::OBJ, "", "", { {RPCResult::Type::NUM, "height", "height of the chain tip"}, {RPCResult::Type::STR_HEX, "hash", "block hash of the tip"}, {RPCResult::Type::NUM, "branchlen", "zero for main chain, otherwise length of branch connecting " "the tip to the main chain"}, {RPCResult::Type::STR, "status", "status of the chain, \"active\" for the main chain\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"}, }}}}, RPCExamples{HelpExampleCli("getchaintips", "") + HelpExampleRpc("getchaintips", "")}, } .Check(request); ChainstateManager &chainman = EnsureChainman(request.context); LOCK(cs_main); /** * Idea: The set of chain tips is the active chain tip, plus orphan blocks * which do not have another orphan building off of them. Algorithm: * - Make one pass through BlockIndex(), 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 the active chain 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 : chainman.BlockIndex()) { if (!chainman.ActiveChain().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(chainman.ActiveChain().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 - chainman.ActiveChain().FindFork(block)->nHeight; obj.pushKV("branchlen", branchLen); std::string status; if (chainman.ActiveChain().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) { // Make sure this call is atomic in the pool. LOCK(pool.cs); 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", std::max(pool.GetMinFee(maxmempool), ::minRelayTxFee).GetFeePerK()); ret.pushKV("minrelaytxfee", ::minRelayTxFee.GetFeePerK()); ret.pushKV("unbroadcastcount", uint64_t{pool.GetUnbroadcastTxs().size()}); return ret; } static UniValue getmempoolinfo(const Config &config, const JSONRPCRequest &request) { RPCHelpMan{ "getmempoolinfo", "Returns details on the active state of the TX memory pool.\n", {}, RPCResult{ RPCResult::Type::OBJ, "", "", { {RPCResult::Type::BOOL, "loaded", "True if the mempool is fully loaded"}, {RPCResult::Type::NUM, "size", "Current tx count"}, {RPCResult::Type::NUM, "bytes", "Sum of all transaction sizes"}, {RPCResult::Type::NUM, "usage", "Total memory usage for the mempool"}, {RPCResult::Type::NUM, "maxmempool", "Maximum memory usage for the mempool"}, {RPCResult::Type::STR_AMOUNT, "mempoolminfee", "Minimum fee rate in " + Currency::get().ticker + "/kB for tx to be accepted. Is the maximum of " "minrelaytxfee and minimum mempool fee"}, {RPCResult::Type::STR_AMOUNT, "minrelaytxfee", "Current minimum relay fee for transactions"}, {RPCResult::Type::NUM, "unbroadcastcount", "Current number of transactions that haven't passed initial " "broadcast yet"}, }}, RPCExamples{HelpExampleCli("getmempoolinfo", "") + HelpExampleRpc("getmempoolinfo", "")}, } .Check(request); return MempoolInfoToJSON(EnsureMemPool(request.context)); } static UniValue preciousblock(const Config &config, const JSONRPCRequest &request) { RPCHelpMan{ "preciousblock", "Treats 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, RPCArg::Optional::NO, "the hash of the block to mark as precious"}, }, RPCResult{RPCResult::Type::NONE, "", ""}, RPCExamples{HelpExampleCli("preciousblock", "\"blockhash\"") + HelpExampleRpc("preciousblock", "\"blockhash\"")}, } .Check(request); 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"); } } BlockValidationState 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) { RPCHelpMan{ "finalizeblock", "Treats 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 PUT YOU OUT OF CONSENSUS.\n" "USE WITH CAUTION!\n", { {"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "the hash of the block to mark as invalid"}, }, RPCResult{RPCResult::Type::NONE, "", ""}, RPCExamples{HelpExampleCli("finalizeblock", "\"blockhash\"") + HelpExampleRpc("finalizeblock", "\"blockhash\"")}, } .Check(request); std::string strHash = request.params[0].get_str(); BlockHash hash(uint256S(strHash)); BlockValidationState state; CBlockIndex *pblockindex = nullptr; { LOCK(cs_main); pblockindex = LookupBlockIndex(hash); if (!pblockindex) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found"); } } // end of locked cs_main scope ::ChainstateActive().FinalizeBlock(config, state, pblockindex); if (state.IsValid()) { ActivateBestChain(config, state); } if (!state.IsValid()) { throw JSONRPCError(RPC_DATABASE_ERROR, state.ToString()); } return NullUniValue; } static UniValue invalidateblock(const Config &config, const JSONRPCRequest &request) { RPCHelpMan{ "invalidateblock", "Permanently marks a block as invalid, as if it violated a consensus " "rule.\n", { {"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "the hash of the block to mark as invalid"}, }, RPCResult{RPCResult::Type::NONE, "", ""}, RPCExamples{HelpExampleCli("invalidateblock", "\"blockhash\"") + HelpExampleRpc("invalidateblock", "\"blockhash\"")}, } .Check(request); const BlockHash hash(ParseHashV(request.params[0], "blockhash")); BlockValidationState state; CBlockIndex *pblockindex; { LOCK(cs_main); pblockindex = LookupBlockIndex(hash); if (!pblockindex) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found"); } } ::ChainstateActive().InvalidateBlock(config, state, pblockindex); if (state.IsValid()) { ActivateBestChain(config, state); } if (!state.IsValid()) { throw JSONRPCError(RPC_DATABASE_ERROR, state.ToString()); } return NullUniValue; } UniValue parkblock(const Config &config, const JSONRPCRequest &request) { RPCHelpMan{ "parkblock", "Marks a block as parked.\n", { {"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "the hash of the block to park"}, }, RPCResult{RPCResult::Type::NONE, "", ""}, RPCExamples{HelpExampleCli("parkblock", "\"blockhash\"") + HelpExampleRpc("parkblock", "\"blockhash\"")}, } .Check(request); const std::string strHash = request.params[0].get_str(); const BlockHash hash(uint256S(strHash)); BlockValidationState state; CBlockIndex *pblockindex = nullptr; { LOCK(cs_main); pblockindex = LookupBlockIndex(hash); if (!pblockindex) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found"); } } ::ChainstateActive().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) { RPCHelpMan{ "reconsiderblock", "Removes invalidity status of a block, its ancestors and its" "descendants, reconsider them for activation.\n" "This can be used to undo the effects of invalidateblock.\n", { {"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "the hash of the block to reconsider"}, }, RPCResult{RPCResult::Type::NONE, "", ""}, RPCExamples{HelpExampleCli("reconsiderblock", "\"blockhash\"") + HelpExampleRpc("reconsiderblock", "\"blockhash\"")}, } .Check(request); 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); } BlockValidationState state; ActivateBestChain(config, state); if (!state.IsValid()) { throw JSONRPCError(RPC_DATABASE_ERROR, state.ToString()); } return NullUniValue; } UniValue unparkblock(const Config &config, const JSONRPCRequest &request) { RPCHelpMan{ "unparkblock", "Removes 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, RPCArg::Optional::NO, "the hash of the block to unpark"}, }, RPCResult{RPCResult::Type::NONE, "", ""}, RPCExamples{HelpExampleCli("unparkblock", "\"blockhash\"") + HelpExampleRpc("unparkblock", "\"blockhash\"")}, } .Check(request); const std::string strHash = request.params[0].get_str(); const BlockHash hash(uint256S(strHash)); { LOCK(cs_main); CBlockIndex *pblockindex = LookupBlockIndex(hash); if (!pblockindex) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found"); } UnparkBlockAndChildren(pblockindex); } BlockValidationState 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) { RPCHelpMan{ "getchaintxstats", "Compute statistics about the total number and rate of transactions " "in the chain.\n", { {"nblocks", RPCArg::Type::NUM, /* default */ "one month", "Size of the window in number of blocks"}, {"blockhash", RPCArg::Type::STR_HEX, /* default */ "chain tip", "The hash of the block that ends the window."}, }, RPCResult{RPCResult::Type::OBJ, "", "", { {RPCResult::Type::NUM_TIME, "time", "The timestamp for the final block in the window, " "expressed in " + UNIX_EPOCH_TIME}, {RPCResult::Type::NUM, "txcount", "The total number of transactions in the chain up to " "that point"}, {RPCResult::Type::STR_HEX, "window_final_block_hash", "The hash of the final block in the window"}, {RPCResult::Type::NUM, "window_final_block_height", "The height of the final block in the window."}, {RPCResult::Type::NUM, "window_block_count", "Size of the window in number of blocks"}, {RPCResult::Type::NUM, "window_tx_count", "The number of transactions in the window. Only " "returned if \"window_block_count\" is > 0"}, {RPCResult::Type::NUM, "window_interval", "The elapsed time in the window in seconds. Only " "returned if \"window_block_count\" is > 0"}, {RPCResult::Type::NUM, "txrate", "The average rate of transactions per second in the " "window. Only returned if \"window_interval\" is > 0"}, }}, RPCExamples{HelpExampleCli("getchaintxstats", "") + HelpExampleRpc("getchaintxstats", "2016")}, } .Check(request); 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"); } } CHECK_NONFATAL(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->GetChainTxCount() - pindexPast->GetChainTxCount(); UniValue ret(UniValue::VOBJ); ret.pushKV("time", pindex->GetBlockTime()); ret.pushKV("txcount", pindex->GetChainTxCount()); ret.pushKV("window_final_block_hash", pindex->GetBlockHash().GetHex()); ret.pushKV("window_final_block_height", pindex->nHeight); 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) { const auto &ticker = Currency::get().ticker; RPCHelpMan{ "getblockstats", "Compute per block statistics for a given window. All amounts are " "in " + ticker + ".\n" "It won't work for some heights with pruning.\n", { {"hash_or_height", RPCArg::Type::NUM, RPCArg::Optional::NO, "The block hash or height of the target block", "", {"", "string or numeric"}}, {"stats", RPCArg::Type::ARR, /* default */ "all values", "Values to plot (see result below)", { {"height", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "Selected statistic"}, {"time", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "Selected statistic"}, }, "stats"}, }, RPCResult{ RPCResult::Type::OBJ, "", "", { {RPCResult::Type::NUM, "avgfee", "Average fee in the block"}, {RPCResult::Type::NUM, "avgfeerate", "Average feerate (in satoshis per virtual byte)"}, {RPCResult::Type::NUM, "avgtxsize", "Average transaction size"}, {RPCResult::Type::STR_HEX, "blockhash", "The block hash (to check for potential reorgs)"}, {RPCResult::Type::NUM, "height", "The height of the block"}, {RPCResult::Type::NUM, "ins", "The number of inputs (excluding coinbase)"}, {RPCResult::Type::NUM, "maxfee", "Maximum fee in the block"}, {RPCResult::Type::NUM, "maxfeerate", "Maximum feerate (in satoshis per virtual byte)"}, {RPCResult::Type::NUM, "maxtxsize", "Maximum transaction size"}, {RPCResult::Type::NUM, "medianfee", "Truncated median fee in the block"}, {RPCResult::Type::NUM, "medianfeerate", "Truncated median feerate (in " + ticker + " per byte)"}, {RPCResult::Type::NUM, "mediantime", "The block median time past"}, {RPCResult::Type::NUM, "mediantxsize", "Truncated median transaction size"}, {RPCResult::Type::NUM, "minfee", "Minimum fee in the block"}, {RPCResult::Type::NUM, "minfeerate", "Minimum feerate (in satoshis per virtual byte)"}, {RPCResult::Type::NUM, "mintxsize", "Minimum transaction size"}, {RPCResult::Type::NUM, "outs", "The number of outputs"}, {RPCResult::Type::NUM, "subsidy", "The block subsidy"}, {RPCResult::Type::NUM, "time", "The block time"}, {RPCResult::Type::NUM, "total_out", "Total amount in all outputs (excluding coinbase and thus " "reward [ie subsidy + totalfee])"}, {RPCResult::Type::NUM, "total_size", "Total size of all non-coinbase transactions"}, {RPCResult::Type::NUM, "totalfee", "The fee total"}, {RPCResult::Type::NUM, "txs", - "The number of transactions (excluding coinbase)"}, + "The number of transactions (including coinbase)"}, {RPCResult::Type::NUM, "utxo_increase", "The increase/decrease in the number of unspent outputs"}, {RPCResult::Type::NUM, "utxo_size_inc", "The increase/decrease in size for the utxo index (not " "discounting op_return and similar)"}, }}, RPCExamples{ HelpExampleCli( "getblockstats", R"('"00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09"' '["minfeerate","avgfeerate"]')") + HelpExampleCli("getblockstats", R"(1000 '["minfeerate","avgfeerate"]')") + HelpExampleRpc( "getblockstats", R"("00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09", ["minfeerate","avgfeerate"])") + HelpExampleRpc("getblockstats", R"(1000, ["minfeerate","avgfeerate"])")}, } .Check(request); 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())); } } CHECK_NONFATAL(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); const CBlockUndo blockUndo = GetUndoChecked(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; for (size_t i = 0; i < block.vtx.size(); ++i) { const auto &tx = block.vtx.at(i); 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) { Amount tx_total_in = Amount::zero(); const auto &txundo = blockUndo.vtxundo.at(i - 1); for (const Coin &coin : txundo.vprevout) { const CTxOut &prevoutput = coin.GetTxOut(); tx_total_in += prevoutput.nValue; utxo_size_inc -= GetSerializeSize(prevoutput, PROTOCOL_VERSION) + PER_UTXO_OVERHEAD; } Amount txfee = tx_total_in - tx_total_out; CHECK_NONFATAL(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", block.vtx.size() > 1 ? (totalfee / int((block.vtx.size() - 1))) : Amount::zero()); ret_all.pushKV("avgfeerate", 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", maxfee); ret_all.pushKV("maxfeerate", maxfeerate); ret_all.pushKV("maxtxsize", maxtxsize); ret_all.pushKV("medianfee", CalculateTruncatedMedian(fee_array)); ret_all.pushKV("medianfeerate", CalculateTruncatedMedian(feerate_array)); ret_all.pushKV("mediantime", pindex->GetMedianTimePast()); ret_all.pushKV("mediantxsize", CalculateTruncatedMedian(txsize_array)); ret_all.pushKV("minfee", minfee == MAX_MONEY ? Amount::zero() : minfee); ret_all.pushKV("minfeerate", minfeerate == MAX_MONEY ? Amount::zero() : minfeerate); ret_all.pushKV("mintxsize", mintxsize == blockMaxSize ? 0 : mintxsize); ret_all.pushKV("outs", outputs); ret_all.pushKV("subsidy", GetBlockSubsidy(pindex->nHeight, Params().GetConsensus())); ret_all.pushKV("time", pindex->GetBlockTime()); ret_all.pushKV("total_out", total_out); ret_all.pushKV("total_size", total_size); ret_all.pushKV("totalfee", 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) { RPCHelpMan{ "savemempool", "Dumps the mempool to disk. It will fail until the previous dump is " "fully loaded.\n", {}, RPCResult{RPCResult::Type::NONE, "", ""}, RPCExamples{HelpExampleCli("savemempool", "") + HelpExampleRpc("savemempool", "")}, } .Check(request); const CTxMemPool &mempool = EnsureMemPool(request.context); if (!mempool.IsLoaded()) { throw JSONRPCError(RPC_MISC_ERROR, "The mempool was not loaded yet"); } if (!DumpMempool(mempool)) { throw JSONRPCError(RPC_MISC_ERROR, "Unable to dump mempool to disk"); } return NullUniValue; } namespace { //! 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, std::function<void()> &interruption_point) { 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) { 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; } } // namespace /** RAII object to prevent concurrency issue when scanning the txout set */ 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() { CHECK_NONFATAL(!m_could_reserve); if (g_scan_in_progress.exchange(true)) { return false; } m_could_reserve = true; return true; } ~CoinsViewScanReserver() { if (m_could_reserve) { g_scan_in_progress = false; } } }; static UniValue scantxoutset(const Config &config, const JSONRPCRequest &request) { const auto &ticker = Currency::get().ticker; RPCHelpMan{ "scantxoutset", "EXPERIMENTAL 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, RPCArg::Optional::NO, "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, RPCArg::Optional::OMITTED, "Array of scan objects. Required for \"start\" action\n" " Every scan object is either a " "string descriptor or an object:", { {"descriptor", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "An output descriptor"}, { "", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "An object with output descriptor and metadata", { {"desc", RPCArg::Type::STR, RPCArg::Optional::NO, "An output descriptor"}, {"range", RPCArg::Type::RANGE, /* default */ "1000", "The range of HD chain indexes to explore (either " "end or [begin,end])"}, }, }, }, "[scanobjects,...]"}, }, RPCResult{ RPCResult::Type::OBJ, "", "", { {RPCResult::Type::BOOL, "success", "Whether the scan was completed"}, {RPCResult::Type::NUM, "txouts", "The number of unspent transaction outputs scanned"}, {RPCResult::Type::NUM, "height", "The current block height (index)"}, {RPCResult::Type::STR_HEX, "bestblock", "The hash of the block at the tip of the chain"}, {RPCResult::Type::ARR, "unspents", "", { {RPCResult::Type::OBJ, "", "", { {RPCResult::Type::STR_HEX, "txid", "The transaction id"}, {RPCResult::Type::NUM, "vout", "The vout value"}, {RPCResult::Type::STR_HEX, "scriptPubKey", "The script key"}, {RPCResult::Type::STR, "desc", "A specialized descriptor for the matched " "scriptPubKey"}, {RPCResult::Type::STR_AMOUNT, "amount", "The total amount in " + ticker + " of the unspent output"}, {RPCResult::Type::NUM, "height", "Height of the unspent transaction output"}, }}, }}, {RPCResult::Type::STR_AMOUNT, "total_amount", "The total amount of all found unspent outputs in " + ticker}, }}, RPCExamples{""}, } .Check(request); 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.load()); 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\""); } if (request.params.size() < 2) { throw JSONRPCError( RPC_MISC_ERROR, "scanobjects argument is required for the start action"); } std::set<CScript> needles; std::map<CScript, std::string> descriptors; Amount total_in = Amount::zero(); // loop through the scan objects for (const UniValue &scanobject : request.params[1].get_array().getValues()) { FlatSigningProvider provider; auto scripts = EvalDescriptorStringOrObject(scanobject, provider); for (const auto &script : scripts) { std::string inferred = InferDescriptor(script, provider)->ToString(); needles.emplace(script); descriptors.emplace(std::move(script), std::move(inferred)); } } // 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; CBlockIndex *tip; { LOCK(cs_main); ::ChainstateActive().ForceFlushStateToDisk(); pcursor = std::unique_ptr<CCoinsViewCursor>( ::ChainstateActive().CoinsDB().Cursor()); CHECK_NONFATAL(pcursor); tip = ::ChainActive().Tip(); CHECK_NONFATAL(tip); } NodeContext &node = EnsureNodeContext(request.context); bool res = FindScriptPubKey(g_scan_progress, g_should_abort_scan, count, pcursor.get(), needles, coins, node.rpc_interruption_point); result.pushKV("success", res); result.pushKV("txouts", count); result.pushKV("height", tip->nHeight); result.pushKV("bestblock", tip->GetBlockHash().GetHex()); 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)); unspent.pushKV("desc", descriptors[txo.scriptPubKey]); unspent.pushKV("amount", txo.nValue); unspent.pushKV("height", int32_t(coin.GetHeight())); unspents.push_back(unspent); } result.pushKV("unspents", unspents); result.pushKV("total_amount", total_in); } else { throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid command"); } return result; } static UniValue getblockfilter(const Config &config, const JSONRPCRequest &request) { RPCHelpMan{ "getblockfilter", "Retrieve a BIP 157 content filter for a particular block.\n", { {"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The hash of the block"}, {"filtertype", RPCArg::Type::STR, /*default*/ "basic", "The type name of the filter"}, }, RPCResult{RPCResult::Type::OBJ, "", "", { {RPCResult::Type::STR_HEX, "filter", "the hex-encoded filter data"}, {RPCResult::Type::STR_HEX, "header", "the hex-encoded filter header"}, }}, RPCExamples{ HelpExampleCli("getblockfilter", "\"00000000c937983704a73af28acdec37b049d214a" "dbda81d7e2a3dd146f6ed09\" \"basic\"") + HelpExampleRpc("getblockfilter", "\"00000000c937983704a73af28acdec37b049d214adbda81d7" "e2a3dd146f6ed09\", \"basic\"")}} .Check(request); const BlockHash block_hash(ParseHashV(request.params[0], "blockhash")); std::string filtertype_name = "basic"; if (!request.params[1].isNull()) { filtertype_name = request.params[1].get_str(); } BlockFilterType filtertype; if (!BlockFilterTypeByName(filtertype_name, filtertype)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Unknown filtertype"); } BlockFilterIndex *index = GetBlockFilterIndex(filtertype); if (!index) { throw JSONRPCError(RPC_MISC_ERROR, "Index is not enabled for filtertype " + filtertype_name); } const CBlockIndex *block_index; bool block_was_connected; { LOCK(cs_main); block_index = LookupBlockIndex(block_hash); if (!block_index) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found"); } block_was_connected = block_index->IsValid(BlockValidity::SCRIPTS); } bool index_ready = index->BlockUntilSyncedToCurrentChain(); BlockFilter filter; uint256 filter_header; if (!index->LookupFilter(block_index, filter) || !index->LookupFilterHeader(block_index, filter_header)) { int err_code; std::string errmsg = "Filter not found."; if (!block_was_connected) { err_code = RPC_INVALID_ADDRESS_OR_KEY; errmsg += " Block was not connected to active chain."; } else if (!index_ready) { err_code = RPC_MISC_ERROR; errmsg += " Block filters are still in the process of being indexed."; } else { err_code = RPC_INTERNAL_ERROR; errmsg += " This error is unexpected and indicates index corruption."; } throw JSONRPCError(err_code, errmsg); } UniValue ret(UniValue::VOBJ); ret.pushKV("filter", HexStr(filter.GetEncodedFilter())); ret.pushKV("header", filter_header.GetHex()); return ret; } /** * Serialize the UTXO set to a file for loading elsewhere. * * @see SnapshotMetadata */ static UniValue dumptxoutset(const Config &config, const JSONRPCRequest &request) { RPCHelpMan{ "dumptxoutset", "\nWrite the serialized UTXO set to disk.\n", { {"path", RPCArg::Type::STR, RPCArg::Optional::NO, /* default_val */ "", "path to the output file. If relative, will be prefixed by " "datadir."}, }, RPCResult{RPCResult::Type::OBJ, "", "", { {RPCResult::Type::NUM, "coins_written", "the number of coins written in the snapshot"}, {RPCResult::Type::STR_HEX, "base_hash", "the hash of the base of the snapshot"}, {RPCResult::Type::NUM, "base_height", "the height of the base of the snapshot"}, {RPCResult::Type::STR, "path", "the absolute path that the snapshot was written to"}, }}, RPCExamples{HelpExampleCli("dumptxoutset", "utxo.dat")}} .Check(request); fs::path path = fs::absolute(request.params[0].get_str(), GetDataDir()); // Write to a temporary path and then move into `path` on completion // to avoid confusion due to an interruption. fs::path temppath = fs::absolute(request.params[0].get_str() + ".incomplete", GetDataDir()); if (fs::exists(path)) { throw JSONRPCError( RPC_INVALID_PARAMETER, path.string() + " already exists. If you are sure this is what you want, " "move it out of the way first"); } FILE *file{fsbridge::fopen(temppath, "wb")}; CAutoFile afile{file, SER_DISK, CLIENT_VERSION}; std::unique_ptr<CCoinsViewCursor> pcursor; CCoinsStats stats; CBlockIndex *tip; NodeContext &node = EnsureNodeContext(request.context); { // We need to lock cs_main to ensure that the coinsdb isn't written to // between (i) flushing coins cache to disk (coinsdb), (ii) getting // stats based upon the coinsdb, and (iii) constructing a cursor to the // coinsdb for use below this block. // // Cursors returned by leveldb iterate over snapshots, so the contents // of the pcursor will not be affected by simultaneous writes during // use below this block. // // See discussion here: // https://github.com/bitcoin/bitcoin/pull/15606#discussion_r274479369 // LOCK(::cs_main); ::ChainstateActive().ForceFlushStateToDisk(); if (!GetUTXOStats(&::ChainstateActive().CoinsDB(), stats, CoinStatsHashType::NONE, node.rpc_interruption_point)) { throw JSONRPCError(RPC_INTERNAL_ERROR, "Unable to read UTXO set"); } pcursor = std::unique_ptr<CCoinsViewCursor>( ::ChainstateActive().CoinsDB().Cursor()); tip = LookupBlockIndex(stats.hashBlock); CHECK_NONFATAL(tip); } SnapshotMetadata metadata{tip->GetBlockHash(), stats.coins_count, uint64_t(tip->GetChainTxCount())}; afile << metadata; COutPoint key; Coin coin; unsigned int iter{0}; while (pcursor->Valid()) { if (iter % 5000 == 0) { node.rpc_interruption_point(); } ++iter; if (pcursor->GetKey(key) && pcursor->GetValue(coin)) { afile << key; afile << coin; } pcursor->Next(); } afile.fclose(); fs::rename(temppath, path); UniValue result(UniValue::VOBJ); result.pushKV("coins_written", stats.coins_count); result.pushKV("base_hash", tip->GetBlockHash().ToString()); result.pushKV("base_height", tip->nHeight); result.pushKV("path", path.string()); return result; } void RegisterBlockchainRPCCommands(CRPCTable &t) { // clang-format off static const CRPCCommand 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, {"hash_type"} }, { "blockchain", "pruneblockchain", pruneblockchain, {"height"} }, { "blockchain", "savemempool", savemempool, {} }, { "blockchain", "verifychain", verifychain, {"checklevel","nblocks"} }, { "blockchain", "preciousblock", preciousblock, {"blockhash"} }, { "blockchain", "scantxoutset", scantxoutset, {"action", "scanobjects"} }, { "blockchain", "getblockfilter", getblockfilter, {"blockhash", "filtertype"} }, /* 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", "dumptxoutset", dumptxoutset, {"path"} }, { "hidden", "unparkblock", unparkblock, {"blockhash"} }, { "hidden", "waitfornewblock", waitfornewblock, {"timeout"} }, { "hidden", "waitforblock", waitforblock, {"blockhash","timeout"} }, { "hidden", "waitforblockheight", waitforblockheight, {"height","timeout"} }, }; // clang-format on for (const auto &c : commands) { t.appendCommand(c.name, &c); } }