Skip to content

Commit 3f166ec

Browse files
committed
rpc: gettxoutsetinfo can be requested for specific blockheights
1 parent 3c914d5 commit 3f166ec

File tree

3 files changed

+76
-47
lines changed

3 files changed

+76
-47
lines changed

src/node/coinstats.cpp

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -89,22 +89,24 @@ static void ApplyStats(CCoinsStats& stats, const uint256& hash, const std::map<u
8989

9090
//! Calculate statistics about the unspent transaction output set
9191
template <typename T>
92-
static bool GetUTXOStats(CCoinsView* view, BlockManager& blockman, CCoinsStats& stats, T hash_obj, const std::function<void()>& interruption_point)
92+
static bool GetUTXOStats(CCoinsView* view, BlockManager& blockman, CCoinsStats& stats, T hash_obj, const std::function<void()>& interruption_point, const CBlockIndex* pindex)
9393
{
9494
std::unique_ptr<CCoinsViewCursor> pcursor(view->Cursor());
9595
assert(pcursor);
96-
stats.hashBlock = pcursor->GetBestBlock();
97-
98-
const CBlockIndex* pindex;
99-
{
100-
LOCK(cs_main);
101-
assert(std::addressof(g_chainman.m_blockman) == std::addressof(blockman));
102-
pindex = blockman.LookupBlockIndex(stats.hashBlock);
103-
stats.nHeight = Assert(pindex)->nHeight;
96+
97+
if (!pindex) {
98+
{
99+
LOCK(cs_main);
100+
assert(std::addressof(g_chainman.m_blockman) == std::addressof(blockman));
101+
pindex = blockman.LookupBlockIndex(view->GetBestBlock());
102+
}
104103
}
104+
stats.nHeight = Assert(pindex)->nHeight;
105+
stats.hashBlock = pindex->GetBlockHash();
105106

106107
// Use CoinStatsIndex if it is available and a hash_type of Muhash or None was requested
107108
if ((stats.m_hash_type == CoinStatsHashType::MUHASH || stats.m_hash_type == CoinStatsHashType::NONE) && g_coin_stats_index) {
109+
stats.from_index = true;
108110
return g_coin_stats_index->LookUpStats(pindex, stats);
109111
}
110112

@@ -141,19 +143,19 @@ static bool GetUTXOStats(CCoinsView* view, BlockManager& blockman, CCoinsStats&
141143
return true;
142144
}
143145

144-
bool GetUTXOStats(CCoinsView* view, BlockManager& blockman, CCoinsStats& stats, const std::function<void()>& interruption_point)
146+
bool GetUTXOStats(CCoinsView* view, BlockManager& blockman, CCoinsStats& stats, const std::function<void()>& interruption_point, const CBlockIndex* pindex)
145147
{
146148
switch (stats.m_hash_type) {
147149
case(CoinStatsHashType::HASH_SERIALIZED): {
148150
CHashWriter ss(SER_GETHASH, PROTOCOL_VERSION);
149-
return GetUTXOStats(view, blockman, stats, ss, interruption_point);
151+
return GetUTXOStats(view, blockman, stats, ss, interruption_point, pindex);
150152
}
151153
case(CoinStatsHashType::MUHASH): {
152154
MuHash3072 muhash;
153-
return GetUTXOStats(view, blockman, stats, muhash, interruption_point);
155+
return GetUTXOStats(view, blockman, stats, muhash, interruption_point, pindex);
154156
}
155157
case(CoinStatsHashType::NONE): {
156-
return GetUTXOStats(view, blockman, stats, nullptr, interruption_point);
158+
return GetUTXOStats(view, blockman, stats, nullptr, interruption_point, pindex);
157159
}
158160
} // no default case, so the compiler can warn about missing cases
159161
assert(false);

src/node/coinstats.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,13 @@ struct CCoinsStats
3939
//! The number of coins contained.
4040
uint64_t coins_count{0};
4141

42+
bool from_index{false};
43+
4244
CCoinsStats(CoinStatsHashType hash_type) : m_hash_type(hash_type) {}
4345
};
4446

4547
//! Calculate statistics about the unspent transaction output set
46-
bool GetUTXOStats(CCoinsView* view, BlockManager& blockman, CCoinsStats& stats, const std::function<void()>& interruption_point = {});
48+
bool GetUTXOStats(CCoinsView* view, BlockManager& blockman, CCoinsStats& stats, const std::function<void()>& interruption_point = {}, const CBlockIndex* pindex = nullptr);
4749

4850
uint64_t GetBogoSize(const CScript& script_pub_key);
4951

src/rpc/blockchain.cpp

Lines changed: 58 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,35 @@ static int ComputeNextBlockAndDepth(const CBlockIndex* tip, const CBlockIndex* b
140140
return blockindex == tip ? 1 : -1;
141141
}
142142

143+
CBlockIndex* ParseHashOrHeight(const UniValue& param, ChainstateManager& chainman) {
144+
LOCK(::cs_main);
145+
CChain& active_chain = chainman.ActiveChain();
146+
147+
if (param.isNum()) {
148+
const int height{param.get_int()};
149+
if (height < 0) {
150+
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Target block height %d is negative", height));
151+
}
152+
const int current_tip{active_chain.Height()};
153+
if (height > current_tip) {
154+
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Target block height %d after current tip %d", height, current_tip));
155+
}
156+
157+
return active_chain[height];
158+
} else {
159+
const uint256 hash{ParseHashV(param, "hash_or_height")};
160+
CBlockIndex* pindex = chainman.m_blockman.LookupBlockIndex(hash);
161+
162+
if (!pindex) {
163+
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
164+
}
165+
if (!active_chain.Contains(pindex)) {
166+
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Block is not in chain %s", Params().NetworkIDString()));
167+
}
168+
return pindex;
169+
}
170+
}
171+
143172
UniValue blockheaderToJSON(const CBlockIndex* tip, const CBlockIndex* blockindex)
144173
{
145174
// Serialize passed information without accessing chain state of the active chain!
@@ -1069,31 +1098,41 @@ static RPCHelpMan gettxoutsetinfo()
10691098
{
10701099
return RPCHelpMan{"gettxoutsetinfo",
10711100
"\nReturns statistics about the unspent transaction output set.\n"
1072-
"Note this call may take some time.\n",
1101+
"Note this call may take some time if you are not using coinstatsindex.\n",
10731102
{
10741103
{"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'."},
1104+
{"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"}},
10751105
},
10761106
RPCResult{
10771107
RPCResult::Type::OBJ, "", "",
10781108
{
10791109
{RPCResult::Type::NUM, "height", "The block height (index) of the returned statistics"},
10801110
{RPCResult::Type::STR_HEX, "bestblock", "The hash of the block at which these statistics are calculated"},
1081-
{RPCResult::Type::NUM, "transactions", "The number of transactions with unspent outputs"},
10821111
{RPCResult::Type::NUM, "txouts", "The number of unspent transaction outputs"},
1083-
{RPCResult::Type::NUM, "bogosize", "A meaningless metric for UTXO set size"},
1112+
{RPCResult::Type::NUM, "bogosize", "Database-independent, meaningless metric indicating the UTXO set size"},
10841113
{RPCResult::Type::STR_HEX, "hash_serialized_2", /* optional */ true, "The serialized hash (only present if 'hash_serialized_2' hash_type is chosen)"},
10851114
{RPCResult::Type::STR_HEX, "muhash", /* optional */ true, "The serialized hash (only present if 'muhash' hash_type is chosen)"},
1115+
{RPCResult::Type::NUM, "transactions", "The number of transactions with unspent outputs (not available when coinstatsindex is used)"},
10861116
{RPCResult::Type::NUM, "disk_size", "The estimated size of the chainstate on disk"},
10871117
{RPCResult::Type::STR_AMOUNT, "total_amount", "The total amount of coins in the UTXO set"},
10881118
}},
10891119
RPCExamples{
1090-
HelpExampleCli("gettxoutsetinfo", "")
1091-
+ HelpExampleRpc("gettxoutsetinfo", "")
1120+
HelpExampleCli("gettxoutsetinfo", "") +
1121+
HelpExampleCli("gettxoutsetinfo", R"("none")") +
1122+
HelpExampleCli("gettxoutsetinfo", R"("none" 1000)") +
1123+
HelpExampleCli("gettxoutsetinfo", R"("none" '"00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09"')") +
1124+
HelpExampleRpc("gettxoutsetinfo", "") +
1125+
HelpExampleRpc("gettxoutsetinfo", R"("none")") +
1126+
HelpExampleRpc("gettxoutsetinfo", R"("none", 1000)") +
1127+
HelpExampleRpc("gettxoutsetinfo", R"("none", "00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09")")
10921128
},
10931129
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
10941130
{
10951131
UniValue ret(UniValue::VOBJ);
10961132

1133+
::ChainstateActive().ForceFlushStateToDisk();
1134+
CBlockIndex* pindex{nullptr};
1135+
10971136
const CoinStatsHashType hash_type{request.params[0].isNull() ? CoinStatsHashType::HASH_SERIALIZED : ParseHashType(request.params[0].get_str())};
10981137
CCoinsStats stats{hash_type};
10991138

@@ -1110,10 +1149,17 @@ static RPCHelpMan gettxoutsetinfo()
11101149
blockman = &active_chainstate.m_blockman;
11111150
}
11121151

1113-
if (GetUTXOStats(coins_view, *blockman, stats, node.rpc_interruption_point)) {
1152+
if (!request.params[1].isNull()) {
1153+
if (!g_coin_stats_index) {
1154+
throw JSONRPCError(RPC_INVALID_PARAMETER, "Querying specific block heights requires coinstatsindex");
1155+
}
1156+
1157+
pindex = ParseHashOrHeight(request.params[1], chainman);
1158+
}
1159+
1160+
if (GetUTXOStats(coins_view, *blockman, stats, node.rpc_interruption_point, pindex)) {
11141161
ret.pushKV("height", (int64_t)stats.nHeight);
11151162
ret.pushKV("bestblock", stats.hashBlock.GetHex());
1116-
ret.pushKV("transactions", (int64_t)stats.nTransactions);
11171163
ret.pushKV("txouts", (int64_t)stats.nTransactionOutputs);
11181164
ret.pushKV("bogosize", (int64_t)stats.nBogoSize);
11191165
if (hash_type == CoinStatsHashType::HASH_SERIALIZED) {
@@ -1122,7 +1168,10 @@ static RPCHelpMan gettxoutsetinfo()
11221168
if (hash_type == CoinStatsHashType::MUHASH) {
11231169
ret.pushKV("muhash", stats.hashSerialized.GetHex());
11241170
}
1125-
ret.pushKV("disk_size", stats.nDiskSize);
1171+
if (!stats.from_index) {
1172+
ret.pushKV("transactions", static_cast<int64_t>(stats.nTransactions));
1173+
ret.pushKV("disk_size", stats.nDiskSize);
1174+
}
11261175
ret.pushKV("total_amount", ValueFromAmount(stats.nTotalAmount));
11271176
} else {
11281177
if (g_coin_stats_index) {
@@ -1918,31 +1967,7 @@ static RPCHelpMan getblockstats()
19181967
{
19191968
ChainstateManager& chainman = EnsureAnyChainman(request.context);
19201969
LOCK(cs_main);
1921-
CChain& active_chain = chainman.ActiveChain();
1922-
1923-
CBlockIndex* pindex;
1924-
if (request.params[0].isNum()) {
1925-
const int height = request.params[0].get_int();
1926-
const int current_tip = active_chain.Height();
1927-
if (height < 0) {
1928-
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Target block height %d is negative", height));
1929-
}
1930-
if (height > current_tip) {
1931-
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Target block height %d after current tip %d", height, current_tip));
1932-
}
1933-
1934-
pindex = active_chain[height];
1935-
} else {
1936-
const uint256 hash(ParseHashV(request.params[0], "hash_or_height"));
1937-
pindex = chainman.m_blockman.LookupBlockIndex(hash);
1938-
if (!pindex) {
1939-
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
1940-
}
1941-
if (!active_chain.Contains(pindex)) {
1942-
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Block is not in chain %s", Params().NetworkIDString()));
1943-
}
1944-
}
1945-
1970+
CBlockIndex* pindex{ParseHashOrHeight(request.params[0], chainman)};
19461971
CHECK_NONFATAL(pindex != nullptr);
19471972

19481973
std::set<std::string> stats;

0 commit comments

Comments
 (0)