0
0
Fork 0
mirror of https://github.com/bitcoin/bitcoin.git synced 2025-02-08 10:31:50 -05:00

rpc: gettxoutsetinfo can be requested for specific blockheights

This commit is contained in:
Fabian Jahr 2020-05-15 16:14:07 +02:00
parent 3c914d58ff
commit 3f166ecc12
No known key found for this signature in database
GPG key ID: F13D1E9D890798CD
3 changed files with 75 additions and 46 deletions

View file

@ -89,22 +89,24 @@ static void ApplyStats(CCoinsStats& stats, const uint256& hash, const std::map<u
//! Calculate statistics about the unspent transaction output set //! Calculate statistics about the unspent transaction output set
template <typename T> 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()); std::unique_ptr<CCoinsViewCursor> pcursor(view->Cursor());
assert(pcursor); assert(pcursor);
stats.hashBlock = pcursor->GetBestBlock();
const CBlockIndex* pindex; if (!pindex) {
{ {
LOCK(cs_main); LOCK(cs_main);
assert(std::addressof(g_chainman.m_blockman) == std::addressof(blockman)); assert(std::addressof(g_chainman.m_blockman) == std::addressof(blockman));
pindex = blockman.LookupBlockIndex(stats.hashBlock); pindex = blockman.LookupBlockIndex(view->GetBestBlock());
stats.nHeight = Assert(pindex)->nHeight;
} }
}
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 // 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) { 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); return g_coin_stats_index->LookUpStats(pindex, stats);
} }
@ -141,19 +143,19 @@ static bool GetUTXOStats(CCoinsView* view, BlockManager& blockman, CCoinsStats&
return true; 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) { switch (stats.m_hash_type) {
case(CoinStatsHashType::HASH_SERIALIZED): { case(CoinStatsHashType::HASH_SERIALIZED): {
CHashWriter ss(SER_GETHASH, PROTOCOL_VERSION); 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): { case(CoinStatsHashType::MUHASH): {
MuHash3072 muhash; MuHash3072 muhash;
return GetUTXOStats(view, blockman, stats, muhash, interruption_point); return GetUTXOStats(view, blockman, stats, muhash, interruption_point, pindex);
} }
case(CoinStatsHashType::NONE): { 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 } // no default case, so the compiler can warn about missing cases
assert(false); assert(false);

View file

@ -39,11 +39,13 @@ struct CCoinsStats
//! The number of coins contained. //! The number of coins contained.
uint64_t coins_count{0}; uint64_t coins_count{0};
bool from_index{false};
CCoinsStats(CoinStatsHashType hash_type) : m_hash_type(hash_type) {} CCoinsStats(CoinStatsHashType hash_type) : m_hash_type(hash_type) {}
}; };
//! Calculate statistics about the unspent transaction output set //! 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); uint64_t GetBogoSize(const CScript& script_pub_key);

View file

@ -140,6 +140,35 @@ static int ComputeNextBlockAndDepth(const CBlockIndex* tip, const CBlockIndex* b
return blockindex == tip ? 1 : -1; 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) UniValue blockheaderToJSON(const CBlockIndex* tip, const CBlockIndex* blockindex)
{ {
// Serialize passed information without accessing chain state of the active chain! // Serialize passed information without accessing chain state of the active chain!
@ -1069,31 +1098,41 @@ static RPCHelpMan gettxoutsetinfo()
{ {
return RPCHelpMan{"gettxoutsetinfo", return RPCHelpMan{"gettxoutsetinfo",
"\nReturns statistics about the unspent transaction output set.\n" "\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_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{
RPCResult::Type::OBJ, "", "", RPCResult::Type::OBJ, "", "",
{ {
{RPCResult::Type::NUM, "height", "The block height (index) of the returned statistics"}, {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::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, "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, "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::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::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"}, {RPCResult::Type::STR_AMOUNT, "total_amount", "The total amount of coins in the UTXO set"},
}}, }},
RPCExamples{ RPCExamples{
HelpExampleCli("gettxoutsetinfo", "") HelpExampleCli("gettxoutsetinfo", "") +
+ HelpExampleRpc("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 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{ {
UniValue ret(UniValue::VOBJ); 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())}; const CoinStatsHashType hash_type{request.params[0].isNull() ? CoinStatsHashType::HASH_SERIALIZED : ParseHashType(request.params[0].get_str())};
CCoinsStats stats{hash_type}; CCoinsStats stats{hash_type};
@ -1110,10 +1149,17 @@ static RPCHelpMan gettxoutsetinfo()
blockman = &active_chainstate.m_blockman; 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("height", (int64_t)stats.nHeight);
ret.pushKV("bestblock", stats.hashBlock.GetHex()); ret.pushKV("bestblock", stats.hashBlock.GetHex());
ret.pushKV("transactions", (int64_t)stats.nTransactions);
ret.pushKV("txouts", (int64_t)stats.nTransactionOutputs); ret.pushKV("txouts", (int64_t)stats.nTransactionOutputs);
ret.pushKV("bogosize", (int64_t)stats.nBogoSize); ret.pushKV("bogosize", (int64_t)stats.nBogoSize);
if (hash_type == CoinStatsHashType::HASH_SERIALIZED) { if (hash_type == CoinStatsHashType::HASH_SERIALIZED) {
@ -1122,7 +1168,10 @@ static RPCHelpMan gettxoutsetinfo()
if (hash_type == CoinStatsHashType::MUHASH) { if (hash_type == CoinStatsHashType::MUHASH) {
ret.pushKV("muhash", stats.hashSerialized.GetHex()); 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("disk_size", stats.nDiskSize);
}
ret.pushKV("total_amount", ValueFromAmount(stats.nTotalAmount)); ret.pushKV("total_amount", ValueFromAmount(stats.nTotalAmount));
} else { } else {
if (g_coin_stats_index) { if (g_coin_stats_index) {
@ -1918,31 +1967,7 @@ static RPCHelpMan getblockstats()
{ {
ChainstateManager& chainman = EnsureAnyChainman(request.context); ChainstateManager& chainman = EnsureAnyChainman(request.context);
LOCK(cs_main); LOCK(cs_main);
CChain& active_chain = chainman.ActiveChain(); CBlockIndex* pindex{ParseHashOrHeight(request.params[0], chainman)};
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()));
}
}
CHECK_NONFATAL(pindex != nullptr); CHECK_NONFATAL(pindex != nullptr);
std::set<std::string> stats; std::set<std::string> stats;