diff options
Diffstat (limited to 'src/rpc/blockchain.cpp')
-rw-r--r-- | src/rpc/blockchain.cpp | 391 |
1 files changed, 259 insertions, 132 deletions
diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index f27e9af0d9..81cb802357 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -14,6 +14,7 @@ #include <core_io.h> #include <hash.h> #include <index/blockfilterindex.h> +#include <index/coinstatsindex.h> #include <node/blockstorage.h> #include <node/coinstats.h> #include <node/context.h> @@ -56,7 +57,7 @@ static Mutex cs_blockchange; static std::condition_variable cond_blockchange; static CUpdatedBlock latestblock GUARDED_BY(cs_blockchange); -NodeContext& EnsureNodeContext(const std::any& context) +NodeContext& EnsureAnyNodeContext(const std::any& context) { auto node_context = util::AnyPtr<NodeContext>(context); if (!node_context) { @@ -65,33 +66,46 @@ NodeContext& EnsureNodeContext(const std::any& context) return *node_context; } -CTxMemPool& EnsureMemPool(const std::any& context) +CTxMemPool& EnsureMemPool(const NodeContext& node) { - const 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 std::any& context) +CTxMemPool& EnsureAnyMemPool(const std::any& context) +{ + return EnsureMemPool(EnsureAnyNodeContext(context)); +} + +ChainstateManager& EnsureChainman(const NodeContext& node) { - const NodeContext& node = EnsureNodeContext(context); if (!node.chainman) { throw JSONRPCError(RPC_INTERNAL_ERROR, "Node chainman not found"); } + WITH_LOCK(::cs_main, CHECK_NONFATAL(std::addressof(g_chainman) == std::addressof(*node.chainman))); return *node.chainman; } -CBlockPolicyEstimator& EnsureFeeEstimator(const std::any& context) +ChainstateManager& EnsureAnyChainman(const std::any& context) +{ + return EnsureChainman(EnsureAnyNodeContext(context)); +} + +CBlockPolicyEstimator& EnsureFeeEstimator(const NodeContext& node) { - NodeContext& node = EnsureNodeContext(context); if (!node.fee_estimator) { throw JSONRPCError(RPC_INTERNAL_ERROR, "Fee estimation disabled"); } return *node.fee_estimator; } +CBlockPolicyEstimator& EnsureAnyFeeEstimator(const std::any& context) +{ + return EnsureFeeEstimator(EnsureAnyNodeContext(context)); +} + /* Calculate the difficulty for a given block index. */ double GetDifficulty(const CBlockIndex* blockindex) @@ -126,6 +140,33 @@ static int ComputeNextBlockAndDepth(const CBlockIndex* tip, const CBlockIndex* b return blockindex == tip ? 1 : -1; } +CBlockIndex* ParseHashOrHeight(const UniValue& param, ChainstateManager& chainman) { + LOCK(::cs_main); + CChain& active_chain = chainman.ActiveChain(); + + if (param.isNum()) { + const int height{param.get_int()}; + if (height < 0) { + throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Target block height %d is negative", height)); + } + const int current_tip{active_chain.Height()}; + if (height > current_tip) { + throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Target block height %d after current tip %d", height, current_tip)); + } + + return active_chain[height]; + } else { + const uint256 hash{ParseHashV(param, "hash_or_height")}; + CBlockIndex* pindex = chainman.m_blockman.LookupBlockIndex(hash); + + if (!pindex) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found"); + } + + return pindex; + } +} + UniValue blockheaderToJSON(const CBlockIndex* tip, const CBlockIndex* blockindex) { // Serialize passed information without accessing chain state of the active chain! @@ -198,8 +239,9 @@ static RPCHelpMan getblockcount() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { + ChainstateManager& chainman = EnsureAnyChainman(request.context); LOCK(cs_main); - return ::ChainActive().Height(); + return chainman.ActiveChain().Height(); }, }; } @@ -217,8 +259,9 @@ static RPCHelpMan getbestblockhash() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { + ChainstateManager& chainman = EnsureAnyChainman(request.context); LOCK(cs_main); - return ::ChainActive().Tip()->GetBlockHash().GetHex(); + return chainman.ActiveChain().Tip()->GetBlockHash().GetHex(); }, }; } @@ -239,7 +282,7 @@ static 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, /* default */ "0", "Time in milliseconds to wait for a response. 0 indicates no timeout."}, + {"timeout", RPCArg::Type::NUM, RPCArg::Default{0}, "Time in milliseconds to wait for a response. 0 indicates no timeout."}, }, RPCResult{ RPCResult::Type::OBJ, "", "", @@ -282,7 +325,7 @@ static RPCHelpMan waitforblock() "\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."}, + {"timeout", RPCArg::Type::NUM, RPCArg::Default{0}, "Time in milliseconds to wait for a response. 0 indicates no timeout."}, }, RPCResult{ RPCResult::Type::OBJ, "", "", @@ -329,7 +372,7 @@ static RPCHelpMan waitforblockheight() "\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."}, + {"timeout", RPCArg::Type::NUM, RPCArg::Default{0}, "Time in milliseconds to wait for a response. 0 indicates no timeout."}, }, RPCResult{ RPCResult::Type::OBJ, "", "", @@ -398,8 +441,9 @@ static RPCHelpMan getdifficulty() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { + ChainstateManager& chainman = EnsureAnyChainman(request.context); LOCK(cs_main); - return GetDifficulty(::ChainActive().Tip()); + return GetDifficulty(chainman.ActiveChain().Tip()); }, }; } @@ -542,8 +586,8 @@ static RPCHelpMan getrawmempool() "\nReturns 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"}, - {"mempool_sequence", RPCArg::Type::BOOL, /* default */ "false", "If verbose=false, returns a json object with transaction list and mempool sequence number attached."}, + {"verbose", RPCArg::Type::BOOL, RPCArg::Default{false}, "True for a json object, false for array of transaction ids"}, + {"mempool_sequence", RPCArg::Type::BOOL, RPCArg::Default{false}, "If verbose=false, returns a json object with transaction list and mempool sequence number attached."}, }, { RPCResult{"for verbose = false", @@ -581,7 +625,7 @@ static RPCHelpMan getrawmempool() include_mempool_sequence = request.params[1].get_bool(); } - return MempoolToJSON(EnsureMemPool(request.context), fVerbose, include_mempool_sequence); + return MempoolToJSON(EnsureAnyMemPool(request.context), fVerbose, include_mempool_sequence); }, }; } @@ -592,7 +636,7 @@ static RPCHelpMan getmempoolancestors() "\nIf 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"}, + {"verbose", RPCArg::Type::BOOL, RPCArg::Default{false}, "True for a json object, false for array of transaction ids"}, }, { RPCResult{"for verbose = false", @@ -616,7 +660,7 @@ static RPCHelpMan getmempoolancestors() uint256 hash = ParseHashV(request.params[0], "parameter 1"); - const CTxMemPool& mempool = EnsureMemPool(request.context); + const CTxMemPool& mempool = EnsureAnyMemPool(request.context); LOCK(mempool.cs); CTxMemPool::txiter it = mempool.mapTx.find(hash); @@ -656,7 +700,7 @@ static RPCHelpMan getmempooldescendants() "\nIf 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"}, + {"verbose", RPCArg::Type::BOOL, RPCArg::Default{false}, "True for a json object, false for array of transaction ids"}, }, { RPCResult{"for verbose = false", @@ -680,7 +724,7 @@ static RPCHelpMan getmempooldescendants() uint256 hash = ParseHashV(request.params[0], "parameter 1"); - const CTxMemPool& mempool = EnsureMemPool(request.context); + const CTxMemPool& mempool = EnsureAnyMemPool(request.context); LOCK(mempool.cs); CTxMemPool::txiter it = mempool.mapTx.find(hash); @@ -732,7 +776,7 @@ static RPCHelpMan getmempoolentry() { uint256 hash = ParseHashV(request.params[0], "parameter 1"); - const CTxMemPool& mempool = EnsureMemPool(request.context); + const CTxMemPool& mempool = EnsureAnyMemPool(request.context); LOCK(mempool.cs); CTxMemPool::txiter it = mempool.mapTx.find(hash); @@ -763,13 +807,15 @@ static RPCHelpMan getblockhash() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { + ChainstateManager& chainman = EnsureAnyChainman(request.context); LOCK(cs_main); + const CChain& active_chain = chainman.ActiveChain(); int nHeight = request.params[0].get_int(); - if (nHeight < 0 || nHeight > ::ChainActive().Height()) + if (nHeight < 0 || nHeight > active_chain.Height()) throw JSONRPCError(RPC_INVALID_PARAMETER, "Block height out of range"); - CBlockIndex* pblockindex = ::ChainActive()[nHeight]; + CBlockIndex* pblockindex = active_chain[nHeight]; return pblockindex->GetBlockHash().GetHex(); }, }; @@ -782,7 +828,7 @@ static RPCHelpMan getblockheader() "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"}, + {"verbose", RPCArg::Type::BOOL, RPCArg::Default{true}, "true for a json object, false for the hex-encoded data"}, }, { RPCResult{"for verbose = true", @@ -822,9 +868,10 @@ static RPCHelpMan getblockheader() const CBlockIndex* pblockindex; const CBlockIndex* tip; { + ChainstateManager& chainman = EnsureAnyChainman(request.context); LOCK(cs_main); - pblockindex = g_chainman.m_blockman.LookupBlockIndex(hash); - tip = ::ChainActive().Tip(); + pblockindex = chainman.m_blockman.LookupBlockIndex(hash); + tip = chainman.ActiveChain().Tip(); } if (!pblockindex) { @@ -883,7 +930,7 @@ static RPCHelpMan getblock() "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"}, + {"verbosity|verbose", RPCArg::Type::NUM, RPCArg::Default{1}, "0 for hex-encoded data, 1 for a json object, and 2 for json object with transaction data"}, }, { RPCResult{"for verbosity = 0", @@ -936,19 +983,21 @@ static RPCHelpMan getblock() int verbosity = 1; if (!request.params[1].isNull()) { - if(request.params[1].isNum()) - verbosity = request.params[1].get_int(); - else + if (request.params[1].isBool()) { verbosity = request.params[1].get_bool() ? 1 : 0; + } else { + verbosity = request.params[1].get_int(); + } } CBlock block; const CBlockIndex* pblockindex; const CBlockIndex* tip; { + ChainstateManager& chainman = EnsureAnyChainman(request.context); LOCK(cs_main); - pblockindex = g_chainman.m_blockman.LookupBlockIndex(hash); - tip = ::ChainActive().Tip(); + pblockindex = chainman.m_blockman.LookupBlockIndex(hash); + tip = chainman.ActiveChain().Tip(); if (!pblockindex) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found"); @@ -988,7 +1037,10 @@ static RPCHelpMan pruneblockchain() if (!fPruneMode) throw JSONRPCError(RPC_MISC_ERROR, "Cannot prune blocks because node is not in prune mode."); + ChainstateManager& chainman = EnsureAnyChainman(request.context); LOCK(cs_main); + CChainState& active_chainstate = chainman.ActiveChainstate(); + CChain& active_chain = active_chainstate.m_chain; int heightParam = request.params[0].get_int(); if (heightParam < 0) @@ -998,7 +1050,7 @@ static RPCHelpMan pruneblockchain() // 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); + CBlockIndex* pindex = active_chain.FindEarliestAtLeast(heightParam - TIMESTAMP_WINDOW, 0); if (!pindex) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Could not find block with at least the specified timestamp."); } @@ -1006,7 +1058,7 @@ static RPCHelpMan pruneblockchain() } unsigned int height = (unsigned int) heightParam; - unsigned int chainHeight = (unsigned int) ::ChainActive().Height(); + unsigned int chainHeight = (unsigned int) active_chain.Height(); if (chainHeight < Params().PruneAfterHeight()) throw JSONRPCError(RPC_MISC_ERROR, "Blockchain is too short for pruning."); else if (height > chainHeight) @@ -1016,8 +1068,8 @@ static RPCHelpMan pruneblockchain() height = chainHeight - MIN_BLOCKS_TO_KEEP; } - PruneBlockFilesManual(::ChainstateActive(), height); - const CBlockIndex* block = ::ChainActive().Tip(); + PruneBlockFilesManual(active_chainstate, height); + const CBlockIndex* block = active_chain.Tip(); CHECK_NONFATAL(block); while (block->pprev && (block->pprev->nStatus & BLOCK_HAVE_DATA)) { block = block->pprev; @@ -1044,42 +1096,88 @@ static RPCHelpMan gettxoutsetinfo() { return RPCHelpMan{"gettxoutsetinfo", "\nReturns statistics about the unspent transaction output set.\n" - "Note this call may take some time.\n", + "Note this call may take some time if you are not using coinstatsindex.\n", { - {"hash_type", RPCArg::Type::STR, /* default */ "hash_serialized_2", "Which UTXO set hash should be calculated. Options: 'hash_serialized_2' (the legacy algorithm), 'muhash', 'none'."}, + {"hash_type", RPCArg::Type::STR, RPCArg::Default{"hash_serialized_2"}, "Which UTXO set hash should be calculated. Options: 'hash_serialized_2' (the legacy algorithm), 'muhash', 'none'."}, + {"hash_or_height", RPCArg::Type::NUM, RPCArg::Optional::OMITTED, "The block hash or height of the target height (only available with coinstatsindex).", "", {"", "string or numeric"}}, + {"use_index", RPCArg::Type::BOOL, RPCArg::Default{true}, "Use coinstatsindex, if available."}, }, RPCResult{ RPCResult::Type::OBJ, "", "", { {RPCResult::Type::NUM, "height", "The block height (index) of the returned statistics"}, {RPCResult::Type::STR_HEX, "bestblock", "The hash of the block at which these statistics are calculated"}, - {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::NUM, "bogosize", "Database-independent, meaningless metric indicating the UTXO set size"}, {RPCResult::Type::STR_HEX, "hash_serialized_2", /* optional */ true, "The serialized hash (only present if 'hash_serialized_2' hash_type is chosen)"}, {RPCResult::Type::STR_HEX, "muhash", /* optional */ true, "The serialized hash (only present if 'muhash' hash_type is chosen)"}, - {RPCResult::Type::NUM, "disk_size", "The estimated size of the chainstate on disk"}, + {RPCResult::Type::NUM, "transactions", "The number of transactions with unspent outputs (not available when coinstatsindex is used)"}, + {RPCResult::Type::NUM, "disk_size", "The estimated size of the chainstate on disk (not available when coinstatsindex is used)"}, {RPCResult::Type::STR_AMOUNT, "total_amount", "The total amount of coins in the UTXO set"}, + {RPCResult::Type::STR_AMOUNT, "total_unspendable_amount", "The total amount of coins permanently excluded from the UTXO set (only available if coinstatsindex is used)"}, + {RPCResult::Type::OBJ, "block_info", "Info on amounts in the block at this block height (only available if coinstatsindex is used)", + { + {RPCResult::Type::STR_AMOUNT, "prevout_spent", ""}, + {RPCResult::Type::STR_AMOUNT, "coinbase", ""}, + {RPCResult::Type::STR_AMOUNT, "new_outputs_ex_coinbase", ""}, + {RPCResult::Type::STR_AMOUNT, "unspendable", ""}, + {RPCResult::Type::OBJ, "unspendables", "Detailed view of the unspendable categories", + { + {RPCResult::Type::STR_AMOUNT, "genesis_block", ""}, + {RPCResult::Type::STR_AMOUNT, "bip30", "Transactions overridden by duplicates (no longer possible with BIP30)"}, + {RPCResult::Type::STR_AMOUNT, "scripts", "Amounts sent to scripts that are unspendable (for example OP_RETURN outputs)"}, + {RPCResult::Type::STR_AMOUNT, "unclaimed_rewards", "Fee rewards that miners did not claim in their coinbase transaction"}, + }} + }}, }}, RPCExamples{ - HelpExampleCli("gettxoutsetinfo", "") - + HelpExampleRpc("gettxoutsetinfo", "") + HelpExampleCli("gettxoutsetinfo", "") + + HelpExampleCli("gettxoutsetinfo", R"("none")") + + HelpExampleCli("gettxoutsetinfo", R"("none" 1000)") + + HelpExampleCli("gettxoutsetinfo", R"("none" '"00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09"')") + + HelpExampleRpc("gettxoutsetinfo", "") + + HelpExampleRpc("gettxoutsetinfo", R"("none")") + + HelpExampleRpc("gettxoutsetinfo", R"("none", 1000)") + + HelpExampleRpc("gettxoutsetinfo", R"("none", "00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09")") }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { UniValue ret(UniValue::VOBJ); - CCoinsStats stats; - ::ChainstateActive().ForceFlushStateToDisk(); - + CBlockIndex* pindex{nullptr}; const CoinStatsHashType hash_type{request.params[0].isNull() ? CoinStatsHashType::HASH_SERIALIZED : ParseHashType(request.params[0].get_str())}; + CCoinsStats stats{hash_type}; + stats.index_requested = request.params[2].isNull() || request.params[2].get_bool(); - CCoinsView* coins_view = WITH_LOCK(::cs_main, return &::ChainstateActive().CoinsDB()); - NodeContext& node = EnsureNodeContext(request.context); - if (GetUTXOStats(coins_view, WITH_LOCK(::cs_main, return std::ref(g_chainman.m_blockman)), stats, hash_type, node.rpc_interruption_point)) { + NodeContext& node = EnsureAnyNodeContext(request.context); + ChainstateManager& chainman = EnsureChainman(node); + CChainState& active_chainstate = chainman.ActiveChainstate(); + active_chainstate.ForceFlushStateToDisk(); + + CCoinsView* coins_view; + BlockManager* blockman; + { + LOCK(::cs_main); + coins_view = &active_chainstate.CoinsDB(); + blockman = &active_chainstate.m_blockman; + pindex = blockman->LookupBlockIndex(coins_view->GetBestBlock()); + } + + if (!request.params[1].isNull()) { + if (!g_coin_stats_index) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Querying specific block heights requires coinstatsindex"); + } + + if (stats.m_hash_type == CoinStatsHashType::HASH_SERIALIZED) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "hash_serialized_2 hash type cannot be queried for a specific block"); + } + + pindex = ParseHashOrHeight(request.params[1], chainman); + } + + if (GetUTXOStats(coins_view, *blockman, stats, node.rpc_interruption_point, pindex)) { 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) { @@ -1088,9 +1186,42 @@ static RPCHelpMan gettxoutsetinfo() if (hash_type == CoinStatsHashType::MUHASH) { ret.pushKV("muhash", stats.hashSerialized.GetHex()); } - ret.pushKV("disk_size", stats.nDiskSize); ret.pushKV("total_amount", ValueFromAmount(stats.nTotalAmount)); + if (!stats.index_used) { + ret.pushKV("transactions", static_cast<int64_t>(stats.nTransactions)); + ret.pushKV("disk_size", stats.nDiskSize); + } else { + ret.pushKV("total_unspendable_amount", ValueFromAmount(stats.block_unspendable_amount)); + + CCoinsStats prev_stats{hash_type}; + + if (pindex->nHeight > 0) { + GetUTXOStats(coins_view, WITH_LOCK(::cs_main, return std::ref(g_chainman.m_blockman)), prev_stats, node.rpc_interruption_point, pindex->pprev); + } + + UniValue block_info(UniValue::VOBJ); + block_info.pushKV("prevout_spent", ValueFromAmount(stats.block_prevout_spent_amount - prev_stats.block_prevout_spent_amount)); + block_info.pushKV("coinbase", ValueFromAmount(stats.block_coinbase_amount - prev_stats.block_coinbase_amount)); + block_info.pushKV("new_outputs_ex_coinbase", ValueFromAmount(stats.block_new_outputs_ex_coinbase_amount - prev_stats.block_new_outputs_ex_coinbase_amount)); + block_info.pushKV("unspendable", ValueFromAmount(stats.block_unspendable_amount - prev_stats.block_unspendable_amount)); + + UniValue unspendables(UniValue::VOBJ); + unspendables.pushKV("genesis_block", ValueFromAmount(stats.unspendables_genesis_block - prev_stats.unspendables_genesis_block)); + unspendables.pushKV("bip30", ValueFromAmount(stats.unspendables_bip30 - prev_stats.unspendables_bip30)); + unspendables.pushKV("scripts", ValueFromAmount(stats.unspendables_scripts - prev_stats.unspendables_scripts)); + unspendables.pushKV("unclaimed_rewards", ValueFromAmount(stats.unspendables_unclaimed_rewards - prev_stats.unspendables_unclaimed_rewards)); + block_info.pushKV("unspendables", unspendables); + + ret.pushKV("block_info", block_info); + } } else { + if (g_coin_stats_index) { + const IndexSummary summary{g_coin_stats_index->GetSummary()}; + + if (!summary.synced) { + throw JSONRPCError(RPC_INTERNAL_ERROR, strprintf("Unable to read UTXO set because coinstatsindex is still syncing. Current height: %d", summary.best_block_height)); + } + } throw JSONRPCError(RPC_INTERNAL_ERROR, "Unable to read UTXO set"); } return ret; @@ -1105,7 +1236,7 @@ static RPCHelpMan gettxout() { {"txid", RPCArg::Type::STR, 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."}, + {"include_mempool", RPCArg::Type::BOOL, RPCArg::Default{true}, "Whether to include the mempool. Note that an unspent output that is spent in the mempool won't appear."}, }, { RPCResult{"If the UTXO was not found", RPCResult::Type::NONE, "", ""}, @@ -1135,6 +1266,8 @@ static RPCHelpMan gettxout() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { + NodeContext& node = EnsureAnyNodeContext(request.context); + ChainstateManager& chainman = EnsureChainman(node); LOCK(cs_main); UniValue ret(UniValue::VOBJ); @@ -1147,10 +1280,11 @@ static RPCHelpMan gettxout() fMempool = request.params[2].get_bool(); Coin coin; - CCoinsViewCache* coins_view = &::ChainstateActive().CoinsTip(); + CChainState& active_chainstate = chainman.ActiveChainstate(); + CCoinsViewCache* coins_view = &active_chainstate.CoinsTip(); if (fMempool) { - const CTxMemPool& mempool = EnsureMemPool(request.context); + const CTxMemPool& mempool = EnsureMemPool(node); LOCK(mempool.cs); CCoinsViewMemPool view(coins_view, mempool); if (!view.GetCoin(out, coin) || mempool.isSpent(out)) { @@ -1162,7 +1296,7 @@ static RPCHelpMan gettxout() } } - const CBlockIndex* pindex = g_chainman.m_blockman.LookupBlockIndex(coins_view->GetBestBlock()); + const CBlockIndex* pindex = active_chainstate.m_blockman.LookupBlockIndex(coins_view->GetBestBlock()); ret.pushKV("bestblock", pindex->GetBlockHash().GetHex()); if (coin.nHeight == MEMPOOL_HEIGHT) { ret.pushKV("confirmations", 0); @@ -1185,9 +1319,9 @@ static RPCHelpMan verifychain() return RPCHelpMan{"verifychain", "\nVerifies blockchain database.\n", { - {"checklevel", RPCArg::Type::NUM, /* default */ strprintf("%d, range=0-4", DEFAULT_CHECKLEVEL), + {"checklevel", RPCArg::Type::NUM, RPCArg::DefaultHint{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."}, + {"nblocks", RPCArg::Type::NUM, RPCArg::DefaultHint{strprintf("%d, 0=all", DEFAULT_CHECKBLOCKS)}, "The number of blocks to check."}, }, RPCResult{ RPCResult::Type::BOOL, "", "Verified or not"}, @@ -1200,39 +1334,42 @@ static RPCHelpMan verifychain() 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()}; + ChainstateManager& chainman = EnsureAnyChainman(request.context); LOCK(cs_main); - return CVerifyDB().VerifyDB(Params(), ::ChainstateActive(), &::ChainstateActive().CoinsTip(), check_level, check_depth); + CChainState& active_chainstate = chainman.ActiveChainstate(); + return CVerifyDB().VerifyDB( + active_chainstate, Params(), active_chainstate.CoinsTip(), check_level, check_depth); }, }; } -static void BuriedForkDescPushBack(UniValue& softforks, const std::string &name, int height) EXCLUSIVE_LOCKS_REQUIRED(cs_main) +static void BuriedForkDescPushBack(UniValue& softforks, const std::string &name, int softfork_height, int tip_height) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { // For buried deployments. // A buried deployment is one where the height of the activation has been hardcoded into // the client implementation long after the consensus change has activated. See BIP 90. // Buried deployments with activation height value of // std::numeric_limits<int>::max() are disabled and thus hidden. - if (height == std::numeric_limits<int>::max()) return; + if (softfork_height == std::numeric_limits<int>::max()) return; UniValue rv(UniValue::VOBJ); rv.pushKV("type", "buried"); // getblockchaininfo reports the softfork as active from when the chain height is // one below the activation height - rv.pushKV("active", ::ChainActive().Tip()->nHeight + 1 >= height); - rv.pushKV("height", height); + rv.pushKV("active", tip_height + 1 >= softfork_height); + rv.pushKV("height", softfork_height); softforks.pushKV(name, rv); } -static void BIP9SoftForkDescPushBack(UniValue& softforks, const std::string &name, const Consensus::Params& consensusParams, Consensus::DeploymentPos id) EXCLUSIVE_LOCKS_REQUIRED(cs_main) +static void BIP9SoftForkDescPushBack(const CBlockIndex* active_chain_tip, UniValue& softforks, const std::string &name, const Consensus::Params& consensusParams, Consensus::DeploymentPos id) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { // For BIP9 deployments. // Deployments that are never active are hidden. if (consensusParams.vDeployments[id].nStartTime == Consensus::BIP9Deployment::NEVER_ACTIVE) return; UniValue bip9(UniValue::VOBJ); - const ThresholdState thresholdState = VersionBitsState(::ChainActive().Tip(), consensusParams, id, versionbitscache); + const ThresholdState thresholdState = VersionBitsState(active_chain_tip, consensusParams, id, versionbitscache); switch (thresholdState) { case ThresholdState::DEFINED: bip9.pushKV("status", "defined"); break; case ThresholdState::STARTED: bip9.pushKV("status", "started"); break; @@ -1246,12 +1383,12 @@ static void BIP9SoftForkDescPushBack(UniValue& softforks, const std::string &nam } bip9.pushKV("start_time", consensusParams.vDeployments[id].nStartTime); bip9.pushKV("timeout", consensusParams.vDeployments[id].nTimeout); - int64_t since_height = VersionBitsStateSinceHeight(::ChainActive().Tip(), consensusParams, id, versionbitscache); + int64_t since_height = VersionBitsStateSinceHeight(active_chain_tip, consensusParams, id, versionbitscache); bip9.pushKV("since", since_height); if (ThresholdState::STARTED == thresholdState) { UniValue statsUV(UniValue::VOBJ); - BIP9Stats statsStruct = VersionBitsStatistics(::ChainActive().Tip(), consensusParams, id); + BIP9Stats statsStruct = VersionBitsStatistics(active_chain_tip, consensusParams, id); statsUV.pushKV("period", statsStruct.period); statsUV.pushKV("threshold", statsStruct.threshold); statsUV.pushKV("elapsed", statsStruct.elapsed); @@ -1328,18 +1465,22 @@ RPCHelpMan getblockchaininfo() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { + ChainstateManager& chainman = EnsureAnyChainman(request.context); LOCK(cs_main); + CChainState& active_chainstate = chainman.ActiveChainstate(); - const CBlockIndex* tip = ::ChainActive().Tip(); + const CBlockIndex* tip = active_chainstate.m_chain.Tip(); + CHECK_NONFATAL(tip); + const int height = tip->nHeight; UniValue obj(UniValue::VOBJ); obj.pushKV("chain", Params().NetworkIDString()); - obj.pushKV("blocks", (int)::ChainActive().Height()); + obj.pushKV("blocks", 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("initialblockdownload", active_chainstate.IsInitialBlockDownload()); obj.pushKV("chainwork", tip->nChainWork.GetHex()); obj.pushKV("size_on_disk", CalculateCurrentUsage()); obj.pushKV("pruned", fPruneMode); @@ -1362,13 +1503,13 @@ RPCHelpMan getblockchaininfo() const Consensus::Params& consensusParams = Params().GetConsensus(); UniValue softforks(UniValue::VOBJ); - BuriedForkDescPushBack(softforks, "bip34", consensusParams.BIP34Height); - BuriedForkDescPushBack(softforks, "bip66", consensusParams.BIP66Height); - BuriedForkDescPushBack(softforks, "bip65", consensusParams.BIP65Height); - BuriedForkDescPushBack(softforks, "csv", consensusParams.CSVHeight); - BuriedForkDescPushBack(softforks, "segwit", consensusParams.SegwitHeight); - BIP9SoftForkDescPushBack(softforks, "testdummy", consensusParams, Consensus::DEPLOYMENT_TESTDUMMY); - BIP9SoftForkDescPushBack(softforks, "taproot", consensusParams, Consensus::DEPLOYMENT_TAPROOT); + BuriedForkDescPushBack(softforks, "bip34", consensusParams.BIP34Height, height); + BuriedForkDescPushBack(softforks, "bip66", consensusParams.BIP66Height, height); + BuriedForkDescPushBack(softforks, "bip65", consensusParams.BIP65Height, height); + BuriedForkDescPushBack(softforks, "csv", consensusParams.CSVHeight, height); + BuriedForkDescPushBack(softforks, "segwit", consensusParams.SegwitHeight, height); + BIP9SoftForkDescPushBack(tip, softforks, "testdummy", consensusParams, Consensus::DEPLOYMENT_TESTDUMMY); + BIP9SoftForkDescPushBack(tip, softforks, "taproot", consensusParams, Consensus::DEPLOYMENT_TAPROOT); obj.pushKV("softforks", softforks); obj.pushKV("warnings", GetWarnings(false).original); @@ -1419,8 +1560,9 @@ static RPCHelpMan getchaintips() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - ChainstateManager& chainman = EnsureChainman(request.context); + ChainstateManager& chainman = EnsureAnyChainman(request.context); LOCK(cs_main); + CChain& active_chain = chainman.ActiveChain(); /* * Idea: The set of chain tips is the active chain tip, plus orphan blocks which do not have another orphan building off of them. @@ -1434,7 +1576,7 @@ static RPCHelpMan getchaintips() std::set<const CBlockIndex*> setPrevs; for (const std::pair<const uint256, CBlockIndex*>& item : chainman.BlockIndex()) { - if (!chainman.ActiveChain().Contains(item.second)) { + if (!active_chain.Contains(item.second)) { setOrphans.insert(item.second); setPrevs.insert(item.second->pprev); } @@ -1447,7 +1589,7 @@ static RPCHelpMan getchaintips() } // Always report the currently active tip. - setTips.insert(chainman.ActiveChain().Tip()); + setTips.insert(active_chain.Tip()); /* Construct the output array. */ UniValue res(UniValue::VARR); @@ -1456,11 +1598,11 @@ static RPCHelpMan getchaintips() obj.pushKV("height", block->nHeight); obj.pushKV("hash", block->phashBlock->GetHex()); - const int branchLen = block->nHeight - chainman.ActiveChain().FindFork(block)->nHeight; + const int branchLen = block->nHeight - active_chain.FindFork(block)->nHeight; obj.pushKV("branchlen", branchLen); std::string status; - if (chainman.ActiveChain().Contains(block)) { + if (active_chain.Contains(block)) { // This block is part of the currently active chain. status = "active"; } else if (block->nStatus & BLOCK_FAILED_MASK) { @@ -1531,7 +1673,7 @@ static RPCHelpMan getmempoolinfo() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - return MempoolInfoToJSON(EnsureMemPool(request.context)); + return MempoolInfoToJSON(EnsureAnyMemPool(request.context)); }, }; } @@ -1555,16 +1697,17 @@ static RPCHelpMan preciousblock() uint256 hash(ParseHashV(request.params[0], "blockhash")); CBlockIndex* pblockindex; + ChainstateManager& chainman = EnsureAnyChainman(request.context); { LOCK(cs_main); - pblockindex = g_chainman.m_blockman.LookupBlockIndex(hash); + pblockindex = chainman.m_blockman.LookupBlockIndex(hash); if (!pblockindex) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found"); } } BlockValidationState state; - ::ChainstateActive().PreciousBlock(state, Params(), pblockindex); + chainman.ActiveChainstate().PreciousBlock(state, Params(), pblockindex); if (!state.IsValid()) { throw JSONRPCError(RPC_DATABASE_ERROR, state.ToString()); @@ -1592,18 +1735,19 @@ static RPCHelpMan invalidateblock() uint256 hash(ParseHashV(request.params[0], "blockhash")); BlockValidationState state; + ChainstateManager& chainman = EnsureAnyChainman(request.context); CBlockIndex* pblockindex; { LOCK(cs_main); - pblockindex = g_chainman.m_blockman.LookupBlockIndex(hash); + pblockindex = chainman.m_blockman.LookupBlockIndex(hash); if (!pblockindex) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found"); } } - ::ChainstateActive().InvalidateBlock(state, Params(), pblockindex); + chainman.ActiveChainstate().InvalidateBlock(state, Params(), pblockindex); if (state.IsValid()) { - ::ChainstateActive().ActivateBestChain(state, Params()); + chainman.ActiveChainstate().ActivateBestChain(state, Params()); } if (!state.IsValid()) { @@ -1630,20 +1774,21 @@ static RPCHelpMan reconsiderblock() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { + ChainstateManager& chainman = EnsureAnyChainman(request.context); uint256 hash(ParseHashV(request.params[0], "blockhash")); { LOCK(cs_main); - CBlockIndex* pblockindex = g_chainman.m_blockman.LookupBlockIndex(hash); + CBlockIndex* pblockindex = chainman.m_blockman.LookupBlockIndex(hash); if (!pblockindex) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found"); } - ::ChainstateActive().ResetBlockFailureFlags(pblockindex); + chainman.ActiveChainstate().ResetBlockFailureFlags(pblockindex); } BlockValidationState state; - ::ChainstateActive().ActivateBestChain(state, Params()); + chainman.ActiveChainstate().ActivateBestChain(state, Params()); if (!state.IsValid()) { throw JSONRPCError(RPC_DATABASE_ERROR, state.ToString()); @@ -1659,8 +1804,8 @@ static RPCHelpMan getchaintxstats() return RPCHelpMan{"getchaintxstats", "\nCompute 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."}, + {"nblocks", RPCArg::Type::NUM, RPCArg::DefaultHint{"one month"}, "Size of the window in number of blocks"}, + {"blockhash", RPCArg::Type::STR_HEX, RPCArg::DefaultHint{"chain tip"}, "The hash of the block that ends the window."}, }, RPCResult{ RPCResult::Type::OBJ, "", "", @@ -1680,20 +1825,21 @@ static RPCHelpMan getchaintxstats() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { + ChainstateManager& chainman = EnsureAnyChainman(request.context); const CBlockIndex* pindex; int blockcount = 30 * 24 * 60 * 60 / Params().GetConsensus().nPowTargetSpacing; // By default: 1 month if (request.params[1].isNull()) { LOCK(cs_main); - pindex = ::ChainActive().Tip(); + pindex = chainman.ActiveChain().Tip(); } else { uint256 hash(ParseHashV(request.params[1], "blockhash")); LOCK(cs_main); - pindex = g_chainman.m_blockman.LookupBlockIndex(hash); + pindex = chainman.m_blockman.LookupBlockIndex(hash); if (!pindex) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found"); } - if (!::ChainActive().Contains(pindex)) { + if (!chainman.ActiveChain().Contains(pindex)) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Block is not in main chain"); } } @@ -1806,7 +1952,7 @@ static RPCHelpMan getblockstats() "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)", + {"stats", RPCArg::Type::ARR, RPCArg::DefaultHint{"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"}, @@ -1861,31 +2007,9 @@ static RPCHelpMan getblockstats() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { + ChainstateManager& chainman = EnsureAnyChainman(request.context); 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 uint256 hash(ParseHashV(request.params[0], "hash_or_height")); - pindex = g_chainman.m_blockman.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())); - } - } - + CBlockIndex* pindex{ParseHashOrHeight(request.params[0], chainman)}; CHECK_NONFATAL(pindex != nullptr); std::set<std::string> stats; @@ -2072,7 +2196,7 @@ static RPCHelpMan savemempool() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - const CTxMemPool& mempool = EnsureMemPool(request.context); + const CTxMemPool& mempool = EnsureAnyMemPool(request.context); if (!mempool.IsLoaded()) { throw JSONRPCError(RPC_MISC_ERROR, "The mempool was not loaded yet"); @@ -2173,7 +2297,7 @@ static RPCHelpMan scantxoutset() {"", 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])"}, + {"range", RPCArg::Type::RANGE, RPCArg::Default{1000}, "The range of HD chain indexes to explore (either end or [begin,end])"}, }}, }, "[scanobjects,...]"}, @@ -2262,15 +2386,17 @@ static RPCHelpMan scantxoutset() int64_t count = 0; std::unique_ptr<CCoinsViewCursor> pcursor; CBlockIndex* tip; + NodeContext& node = EnsureAnyNodeContext(request.context); { + ChainstateManager& chainman = EnsureChainman(node); LOCK(cs_main); - ::ChainstateActive().ForceFlushStateToDisk(); - pcursor = std::unique_ptr<CCoinsViewCursor>(::ChainstateActive().CoinsDB().Cursor()); + CChainState& active_chainstate = chainman.ActiveChainstate(); + active_chainstate.ForceFlushStateToDisk(); + pcursor = std::unique_ptr<CCoinsViewCursor>(active_chainstate.CoinsDB().Cursor()); CHECK_NONFATAL(pcursor); - tip = ::ChainActive().Tip(); + tip = active_chainstate.m_chain.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); @@ -2310,7 +2436,7 @@ static RPCHelpMan getblockfilter() "\nRetrieve 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"}, + {"filtertype", RPCArg::Type::STR, RPCArg::Default{"basic"}, "The type name of the filter"}, }, RPCResult{ RPCResult::Type::OBJ, "", "", @@ -2343,8 +2469,9 @@ static RPCHelpMan getblockfilter() const CBlockIndex* block_index; bool block_was_connected; { + ChainstateManager& chainman = EnsureAnyChainman(request.context); LOCK(cs_main); - block_index = g_chainman.m_blockman.LookupBlockIndex(block_hash); + block_index = chainman.m_blockman.LookupBlockIndex(block_hash); if (!block_index) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found"); } @@ -2427,7 +2554,7 @@ static RPCHelpMan dumptxoutset() FILE* file{fsbridge::fopen(temppath, "wb")}; CAutoFile afile{file, SER_DISK, CLIENT_VERSION}; - NodeContext& node = EnsureNodeContext(request.context); + NodeContext& node = EnsureAnyNodeContext(request.context); UniValue result = CreateUTXOSnapshot(node, node.chainman->ActiveChainstate(), afile); fs::rename(temppath, path); @@ -2440,7 +2567,7 @@ static RPCHelpMan dumptxoutset() UniValue CreateUTXOSnapshot(NodeContext& node, CChainState& chainstate, CAutoFile& afile) { std::unique_ptr<CCoinsViewCursor> pcursor; - CCoinsStats stats; + CCoinsStats stats{CoinStatsHashType::NONE}; CBlockIndex* tip; { @@ -2460,12 +2587,12 @@ UniValue CreateUTXOSnapshot(NodeContext& node, CChainState& chainstate, CAutoFil chainstate.ForceFlushStateToDisk(); - if (!GetUTXOStats(&chainstate.CoinsDB(), chainstate.m_blockman, stats, CoinStatsHashType::NONE, node.rpc_interruption_point)) { + if (!GetUTXOStats(&chainstate.CoinsDB(), chainstate.m_blockman, stats, node.rpc_interruption_point)) { throw JSONRPCError(RPC_INTERNAL_ERROR, "Unable to read UTXO set"); } pcursor = std::unique_ptr<CCoinsViewCursor>(chainstate.CoinsDB().Cursor()); - tip = g_chainman.m_blockman.LookupBlockIndex(stats.hashBlock); + tip = chainstate.m_blockman.LookupBlockIndex(stats.hashBlock); CHECK_NONFATAL(tip); } |