Skip to content

Commit a712cf6

Browse files
committed
rpc: gettxoutsetinfo can specify hash_type (only legacy option for now)
1 parent 605884e commit a712cf6

File tree

6 files changed

+69
-15
lines changed

6 files changed

+69
-15
lines changed

src/node/coinstats.cpp

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ static uint64_t GetBogoSize(const CScript& scriptPubKey)
2424
scriptPubKey.size() /* scriptPubKey */;
2525
}
2626

27-
static void ApplyStats(CCoinsStats &stats, CHashWriter& ss, const uint256& hash, const std::map<uint32_t, Coin>& outputs)
27+
static void ApplyStats(CCoinsStats& stats, CHashWriter& ss, const uint256& hash, const std::map<uint32_t, Coin>& outputs)
2828
{
2929
assert(!outputs.empty());
3030
ss << hash;
@@ -42,19 +42,21 @@ static void ApplyStats(CCoinsStats &stats, CHashWriter& ss, const uint256& hash,
4242
}
4343

4444
//! Calculate statistics about the unspent transaction output set
45-
bool GetUTXOStats(CCoinsView* view, CCoinsStats& stats, const std::function<void()>& interruption_point)
45+
template <typename T>
46+
static bool GetUTXOStats(CCoinsView* view, CCoinsStats& stats, T hash_obj, const std::function<void()>& interruption_point)
4647
{
4748
stats = CCoinsStats();
4849
std::unique_ptr<CCoinsViewCursor> pcursor(view->Cursor());
4950
assert(pcursor);
5051

51-
CHashWriter ss(SER_GETHASH, PROTOCOL_VERSION);
5252
stats.hashBlock = pcursor->GetBestBlock();
5353
{
5454
LOCK(cs_main);
5555
stats.nHeight = LookupBlockIndex(stats.hashBlock)->nHeight;
5656
}
57-
ss << stats.hashBlock;
57+
58+
PrepareHash(hash_obj, stats);
59+
5860
uint256 prevkey;
5961
std::map<uint32_t, Coin> outputs;
6062
while (pcursor->Valid()) {
@@ -63,7 +65,7 @@ bool GetUTXOStats(CCoinsView* view, CCoinsStats& stats, const std::function<void
6365
Coin coin;
6466
if (pcursor->GetKey(key) && pcursor->GetValue(coin)) {
6567
if (!outputs.empty() && key.hash != prevkey) {
66-
ApplyStats(stats, ss, prevkey, outputs);
68+
ApplyStats(stats, hash_obj, prevkey, outputs);
6769
outputs.clear();
6870
}
6971
prevkey = key.hash;
@@ -75,9 +77,33 @@ bool GetUTXOStats(CCoinsView* view, CCoinsStats& stats, const std::function<void
7577
pcursor->Next();
7678
}
7779
if (!outputs.empty()) {
78-
ApplyStats(stats, ss, prevkey, outputs);
80+
ApplyStats(stats, hash_obj, prevkey, outputs);
7981
}
80-
stats.hashSerialized = ss.GetHash();
82+
83+
FinalizeHash(hash_obj, stats);
84+
8185
stats.nDiskSize = view->EstimateSize();
8286
return true;
8387
}
88+
89+
bool GetUTXOStats(CCoinsView* view, CCoinsStats& stats, CoinStatsHashType hash_type, const std::function<void()>& interruption_point)
90+
{
91+
switch (hash_type) {
92+
case(CoinStatsHashType::HASH_SERIALIZED): {
93+
CHashWriter ss(SER_GETHASH, PROTOCOL_VERSION);
94+
return GetUTXOStats(view, stats, ss, interruption_point);
95+
}
96+
} // no default case, so the compiler can warn about missing cases
97+
assert(false);
98+
}
99+
100+
// The legacy hash serializes the hashBlock
101+
static void PrepareHash(CHashWriter& ss, CCoinsStats& stats)
102+
{
103+
ss << stats.hashBlock;
104+
}
105+
106+
static void FinalizeHash(CHashWriter& ss, CCoinsStats& stats)
107+
{
108+
stats.hashSerialized = ss.GetHash();
109+
}

src/node/coinstats.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@
1414

1515
class CCoinsView;
1616

17+
enum class CoinStatsHashType {
18+
HASH_SERIALIZED,
19+
};
20+
1721
struct CCoinsStats
1822
{
1923
int nHeight{0};
@@ -30,6 +34,6 @@ struct CCoinsStats
3034
};
3135

3236
//! Calculate statistics about the unspent transaction output set
33-
bool GetUTXOStats(CCoinsView* view, CCoinsStats& stats, const std::function<void()>& interruption_point = {});
37+
bool GetUTXOStats(CCoinsView* view, CCoinsStats& stats, const CoinStatsHashType hash_type, const std::function<void()>& interruption_point = {});
3438

3539
#endif // BITCOIN_NODE_COINSTATS_H

src/rpc/blockchain.cpp

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -972,7 +972,9 @@ static UniValue gettxoutsetinfo(const JSONRPCRequest& request)
972972
RPCHelpMan{"gettxoutsetinfo",
973973
"\nReturns statistics about the unspent transaction output set.\n"
974974
"Note this call may take some time.\n",
975-
{},
975+
{
976+
{"hash_type", RPCArg::Type::STR, /* default */ "hash_serialized_2", "Which UTXO set hash should be calculated. Options: 'hash_serialized_2' (the legacy algorithm)."},
977+
},
976978
RPCResult{
977979
RPCResult::Type::OBJ, "", "",
978980
{
@@ -981,7 +983,7 @@ static UniValue gettxoutsetinfo(const JSONRPCRequest& request)
981983
{RPCResult::Type::NUM, "transactions", "The number of transactions with unspent outputs"},
982984
{RPCResult::Type::NUM, "txouts", "The number of unspent transaction outputs"},
983985
{RPCResult::Type::NUM, "bogosize", "A meaningless metric for UTXO set size"},
984-
{RPCResult::Type::STR_HEX, "hash_serialized_2", "The serialized hash"},
986+
{RPCResult::Type::STR_HEX, "hash_serialized_2", "The serialized hash (only present if 'hash_serialized_2' hash_type is chosen)"},
985987
{RPCResult::Type::NUM, "disk_size", "The estimated size of the chainstate on disk"},
986988
{RPCResult::Type::STR_AMOUNT, "total_amount", "The total amount"},
987989
}},
@@ -996,14 +998,18 @@ static UniValue gettxoutsetinfo(const JSONRPCRequest& request)
996998
CCoinsStats stats;
997999
::ChainstateActive().ForceFlushStateToDisk();
9981000

1001+
const CoinStatsHashType hash_type = ParseHashType(request.params[0], CoinStatsHashType::HASH_SERIALIZED);
1002+
9991003
CCoinsView* coins_view = WITH_LOCK(cs_main, return &ChainstateActive().CoinsDB());
1000-
if (GetUTXOStats(coins_view, stats, RpcInterruptionPoint)) {
1004+
if (GetUTXOStats(coins_view, stats, hash_type, RpcInterruptionPoint)) {
10011005
ret.pushKV("height", (int64_t)stats.nHeight);
10021006
ret.pushKV("bestblock", stats.hashBlock.GetHex());
10031007
ret.pushKV("transactions", (int64_t)stats.nTransactions);
10041008
ret.pushKV("txouts", (int64_t)stats.nTransactionOutputs);
10051009
ret.pushKV("bogosize", (int64_t)stats.nBogoSize);
1006-
ret.pushKV("hash_serialized_2", stats.hashSerialized.GetHex());
1010+
if (hash_type == CoinStatsHashType::HASH_SERIALIZED) {
1011+
ret.pushKV("hash_serialized_2", stats.hashSerialized.GetHex());
1012+
}
10071013
ret.pushKV("disk_size", stats.nDiskSize);
10081014
ret.pushKV("total_amount", ValueFromAmount(stats.nTotalAmount));
10091015
} else {
@@ -2316,7 +2322,7 @@ UniValue dumptxoutset(const JSONRPCRequest& request)
23162322

23172323
::ChainstateActive().ForceFlushStateToDisk();
23182324

2319-
if (!GetUTXOStats(&::ChainstateActive().CoinsDB(), stats, RpcInterruptionPoint)) {
2325+
if (!GetUTXOStats(&::ChainstateActive().CoinsDB(), stats, CoinStatsHashType::HASH_SERIALIZED, RpcInterruptionPoint)) {
23202326
throw JSONRPCError(RPC_INTERNAL_ERROR, "Unable to read UTXO set");
23212327
}
23222328

@@ -2377,7 +2383,7 @@ static const CRPCCommand commands[] =
23772383
{ "blockchain", "getmempoolinfo", &getmempoolinfo, {} },
23782384
{ "blockchain", "getrawmempool", &getrawmempool, {"verbose"} },
23792385
{ "blockchain", "gettxout", &gettxout, {"txid","n","include_mempool"} },
2380-
{ "blockchain", "gettxoutsetinfo", &gettxoutsetinfo, {} },
2386+
{ "blockchain", "gettxoutsetinfo", &gettxoutsetinfo, {"hash_type"} },
23812387
{ "blockchain", "pruneblockchain", &pruneblockchain, {"height"} },
23822388
{ "blockchain", "savemempool", &savemempool, {} },
23832389
{ "blockchain", "verifychain", &verifychain, {"checklevel","nblocks"} },

src/rpc/util.cpp

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,21 @@ std::vector<unsigned char> ParseHexO(const UniValue& o, std::string strKey)
113113
return ParseHexV(find_value(o, strKey), strKey);
114114
}
115115

116+
CoinStatsHashType ParseHashType(const UniValue& param, const CoinStatsHashType default_type)
117+
{
118+
if (param.isNull()) {
119+
return default_type;
120+
} else {
121+
std::string hash_type_input = param.get_str();
122+
123+
if (hash_type_input == "hash_serialized_2") {
124+
return CoinStatsHashType::HASH_SERIALIZED;
125+
} else {
126+
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("%d is not a valid hash_type", hash_type_input));
127+
}
128+
}
129+
}
130+
116131
std::string HelpExampleCli(const std::string& methodname, const std::string& args)
117132
{
118133
return "> bitcoin-cli " + methodname + " " + args + "\n";

src/rpc/util.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#ifndef BITCOIN_RPC_UTIL_H
66
#define BITCOIN_RPC_UTIL_H
77

8+
#include <node/coinstats.h>
89
#include <node/transaction.h>
910
#include <outputtype.h>
1011
#include <protocol.h>
@@ -77,6 +78,8 @@ extern uint256 ParseHashO(const UniValue& o, std::string strKey);
7778
extern std::vector<unsigned char> ParseHexV(const UniValue& v, std::string strName);
7879
extern std::vector<unsigned char> ParseHexO(const UniValue& o, std::string strKey);
7980

81+
CoinStatsHashType ParseHashType(const UniValue& param, const CoinStatsHashType default_type);
82+
8083
extern CAmount AmountFromValue(const UniValue& value);
8184
extern std::string HelpExampleCli(const std::string& methodname, const std::string& args);
8285
extern std::string HelpExampleRpc(const std::string& methodname, const std::string& args);

src/test/fuzz/coins_view.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,7 @@ void test_one_input(const std::vector<uint8_t>& buffer)
278278
CCoinsStats stats;
279279
bool expected_code_path = false;
280280
try {
281-
(void)GetUTXOStats(&coins_view_cache, stats);
281+
(void)GetUTXOStats(&coins_view_cache, stats, CoinStatsHashType::HASH_SERIALIZED);
282282
} catch (const std::logic_error&) {
283283
expected_code_path = true;
284284
}

0 commit comments

Comments
 (0)