aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFabian Jahr <fjahr@protonmail.com>2020-05-15 16:14:07 +0200
committerFabian Jahr <fjahr@protonmail.com>2021-04-19 20:30:59 +0200
commit3f166ecc125fce6ccd995687fa16572090a5d099 (patch)
tree0110f1a8cea9495fbe3be5f0467a4e2bf0083633
parent3c914d58ff323255b32e717d0ce28209ec0abdaa (diff)
rpc: gettxoutsetinfo can be requested for specific blockheights
-rw-r--r--src/node/coinstats.cpp28
-rw-r--r--src/node/coinstats.h4
-rw-r--r--src/rpc/blockchain.cpp91
3 files changed, 76 insertions, 47 deletions
diff --git a/src/node/coinstats.cpp b/src/node/coinstats.cpp
index dc9d32035d..eacd7e2ab5 100644
--- a/src/node/coinstats.cpp
+++ b/src/node/coinstats.cpp
@@ -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;
- {
- LOCK(cs_main);
- assert(std::addressof(g_chainman.m_blockman) == std::addressof(blockman));
- pindex = blockman.LookupBlockIndex(stats.hashBlock);
- stats.nHeight = Assert(pindex)->nHeight;
+
+ if (!pindex) {
+ {
+ LOCK(cs_main);
+ assert(std::addressof(g_chainman.m_blockman) == std::addressof(blockman));
+ 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);
diff --git a/src/node/coinstats.h b/src/node/coinstats.h
index 826df2fd73..e30b2778c5 100644
--- a/src/node/coinstats.h
+++ b/src/node/coinstats.h
@@ -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);
diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp
index dc6b7930b9..e6bf7f91b7 100644
--- a/src/rpc/blockchain.cpp
+++ b/src/rpc/blockchain.cpp
@@ -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());
}
- ret.pushKV("disk_size", stats.nDiskSize);
+ 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;