mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-02-02 09:46:52 -05:00
rpc: gettxoutsetinfo can be requested for specific blockheights
This commit is contained in:
parent
3c914d58ff
commit
3f166ecc12
3 changed files with 75 additions and 46 deletions
|
@ -89,22 +89,24 @@ static void ApplyStats(CCoinsStats& stats, const uint256& hash, const std::map<u
|
|||
|
||||
//! Calculate statistics about the unspent transaction output set
|
||||
template <typename T>
|
||||
static bool GetUTXOStats(CCoinsView* view, BlockManager& blockman, CCoinsStats& stats, T hash_obj, const std::function<void()>& interruption_point)
|
||||
static bool GetUTXOStats(CCoinsView* view, BlockManager& blockman, CCoinsStats& stats, T hash_obj, const std::function<void()>& interruption_point, const CBlockIndex* pindex)
|
||||
{
|
||||
std::unique_ptr<CCoinsViewCursor> pcursor(view->Cursor());
|
||||
assert(pcursor);
|
||||
stats.hashBlock = pcursor->GetBestBlock();
|
||||
|
||||
const CBlockIndex* pindex;
|
||||
if (!pindex) {
|
||||
{
|
||||
LOCK(cs_main);
|
||||
assert(std::addressof(g_chainman.m_blockman) == std::addressof(blockman));
|
||||
pindex = blockman.LookupBlockIndex(stats.hashBlock);
|
||||
stats.nHeight = Assert(pindex)->nHeight;
|
||||
pindex = blockman.LookupBlockIndex(view->GetBestBlock());
|
||||
}
|
||||
}
|
||||
stats.nHeight = Assert(pindex)->nHeight;
|
||||
stats.hashBlock = pindex->GetBlockHash();
|
||||
|
||||
// Use CoinStatsIndex if it is available and a hash_type of Muhash or None was requested
|
||||
if ((stats.m_hash_type == CoinStatsHashType::MUHASH || stats.m_hash_type == CoinStatsHashType::NONE) && g_coin_stats_index) {
|
||||
stats.from_index = true;
|
||||
return g_coin_stats_index->LookUpStats(pindex, stats);
|
||||
}
|
||||
|
||||
|
@ -141,19 +143,19 @@ static bool GetUTXOStats(CCoinsView* view, BlockManager& blockman, CCoinsStats&
|
|||
return true;
|
||||
}
|
||||
|
||||
bool GetUTXOStats(CCoinsView* view, BlockManager& blockman, CCoinsStats& stats, const std::function<void()>& interruption_point)
|
||||
bool GetUTXOStats(CCoinsView* view, BlockManager& blockman, CCoinsStats& stats, const std::function<void()>& interruption_point, const CBlockIndex* pindex)
|
||||
{
|
||||
switch (stats.m_hash_type) {
|
||||
case(CoinStatsHashType::HASH_SERIALIZED): {
|
||||
CHashWriter ss(SER_GETHASH, PROTOCOL_VERSION);
|
||||
return GetUTXOStats(view, blockman, stats, ss, interruption_point);
|
||||
return GetUTXOStats(view, blockman, stats, ss, interruption_point, pindex);
|
||||
}
|
||||
case(CoinStatsHashType::MUHASH): {
|
||||
MuHash3072 muhash;
|
||||
return GetUTXOStats(view, blockman, stats, muhash, interruption_point);
|
||||
return GetUTXOStats(view, blockman, stats, muhash, interruption_point, pindex);
|
||||
}
|
||||
case(CoinStatsHashType::NONE): {
|
||||
return GetUTXOStats(view, blockman, stats, nullptr, interruption_point);
|
||||
return GetUTXOStats(view, blockman, stats, nullptr, interruption_point, pindex);
|
||||
}
|
||||
} // no default case, so the compiler can warn about missing cases
|
||||
assert(false);
|
||||
|
|
|
@ -39,11 +39,13 @@ struct CCoinsStats
|
|||
//! The number of coins contained.
|
||||
uint64_t coins_count{0};
|
||||
|
||||
bool from_index{false};
|
||||
|
||||
CCoinsStats(CoinStatsHashType hash_type) : m_hash_type(hash_type) {}
|
||||
};
|
||||
|
||||
//! Calculate statistics about the unspent transaction output set
|
||||
bool GetUTXOStats(CCoinsView* view, BlockManager& blockman, CCoinsStats& stats, const std::function<void()>& interruption_point = {});
|
||||
bool GetUTXOStats(CCoinsView* view, BlockManager& blockman, CCoinsStats& stats, const std::function<void()>& interruption_point = {}, const CBlockIndex* pindex = nullptr);
|
||||
|
||||
uint64_t GetBogoSize(const CScript& script_pub_key);
|
||||
|
||||
|
|
|
@ -140,6 +140,35 @@ 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");
|
||||
}
|
||||
if (!active_chain.Contains(pindex)) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Block is not in chain %s", Params().NetworkIDString()));
|
||||
}
|
||||
return pindex;
|
||||
}
|
||||
}
|
||||
|
||||
UniValue blockheaderToJSON(const CBlockIndex* tip, const CBlockIndex* blockindex)
|
||||
{
|
||||
// Serialize passed information without accessing chain state of the active chain!
|
||||
|
@ -1069,31 +1098,41 @@ 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, 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"}},
|
||||
},
|
||||
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, "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"},
|
||||
{RPCResult::Type::STR_AMOUNT, "total_amount", "The total amount of coins in the UTXO set"},
|
||||
}},
|
||||
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);
|
||||
|
||||
::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};
|
||||
|
||||
|
@ -1110,10 +1149,17 @@ static RPCHelpMan gettxoutsetinfo()
|
|||
blockman = &active_chainstate.m_blockman;
|
||||
}
|
||||
|
||||
if (GetUTXOStats(coins_view, *blockman, stats, node.rpc_interruption_point)) {
|
||||
if (!request.params[1].isNull()) {
|
||||
if (!g_coin_stats_index) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Querying specific block heights requires coinstatsindex");
|
||||
}
|
||||
|
||||
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) {
|
||||
|
@ -1122,7 +1168,10 @@ static RPCHelpMan gettxoutsetinfo()
|
|||
if (hash_type == CoinStatsHashType::MUHASH) {
|
||||
ret.pushKV("muhash", stats.hashSerialized.GetHex());
|
||||
}
|
||||
if (!stats.from_index) {
|
||||
ret.pushKV("transactions", static_cast<int64_t>(stats.nTransactions));
|
||||
ret.pushKV("disk_size", stats.nDiskSize);
|
||||
}
|
||||
ret.pushKV("total_amount", ValueFromAmount(stats.nTotalAmount));
|
||||
} else {
|
||||
if (g_coin_stats_index) {
|
||||
|
@ -1918,31 +1967,7 @@ static RPCHelpMan getblockstats()
|
|||
{
|
||||
ChainstateManager& chainman = EnsureAnyChainman(request.context);
|
||||
LOCK(cs_main);
|
||||
CChain& active_chain = chainman.ActiveChain();
|
||||
|
||||
CBlockIndex* pindex;
|
||||
if (request.params[0].isNum()) {
|
||||
const int height = request.params[0].get_int();
|
||||
const int current_tip = active_chain.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 = active_chain[height];
|
||||
} else {
|
||||
const uint256 hash(ParseHashV(request.params[0], "hash_or_height"));
|
||||
pindex = chainman.m_blockman.LookupBlockIndex(hash);
|
||||
if (!pindex) {
|
||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
|
||||
}
|
||||
if (!active_chain.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;
|
||||
|
|
Loading…
Add table
Reference in a new issue