Skip to content

Commit b52e25c

Browse files
author
MarcoFalke
committed
Merge #19328: Add gettxoutsetinfo hash_type option
40506bf test: Test gettxouttsetinfo hash_type option (Fabian Jahr) f17a4d1 rpc: Add hash_type NONE to gettxoutsetinfo (Fabian Jahr) a712cf6 rpc: gettxoutsetinfo can specify hash_type (only legacy option for now) (Fabian Jahr) 605884e refactor: Extract GetBogoSize function (Fabian Jahr) Pull request description: This is another intermediate part of the Coinstats Index (tracked in #18000). Sjors suggested [here](bitcoin/bitcoin#18000 (comment)) that the part of the changes in #19145 that don't rely on the new `hash_type` muhash, i.e. that are for `hash_type=none`, could be merged separately from everything involving muhash. So these changes are extracted from #19145 here and can be merged without any other requirements. Building the index with no UTXO set hash is still valuable because `gettxoutsetinfo` can still be used to audit the `total_amount` for example. By itself this PR is not a huge improvement, `hash_type=none` is speeding up `gettxoutsetinfo` by about 10%, but it enables the implementation of an index on top of it in a follow-up and that means large parts of the index code of Coinstats Index can be merged while reviews for the hashing algorithm might take longer. ACKs for top commit: MarcoFalke: ACK 40506bf 🖨 Sjors: tACK 40506bf Tree-SHA512: 3964c2b8eed427511b1aa9b2ef285dff27dc4d1537d72c3911e435b6e6b40912232da4acb3a09bd19a0372ddffa44103388d8a650169d95a4a727b970d210add
2 parents f4301e9 + 40506bf commit b52e25c

File tree

7 files changed

+111
-18
lines changed

7 files changed

+111
-18
lines changed

src/node/coinstats.cpp

Lines changed: 61 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,23 @@
88
#include <coins.h>
99
#include <hash.h>
1010
#include <serialize.h>
11-
#include <validation.h>
1211
#include <uint256.h>
1312
#include <util/system.h>
13+
#include <validation.h>
1414

1515
#include <map>
1616

17-
static void ApplyStats(CCoinsStats &stats, CHashWriter& ss, const uint256& hash, const std::map<uint32_t, Coin>& outputs)
17+
static uint64_t GetBogoSize(const CScript& scriptPubKey)
18+
{
19+
return 32 /* txid */ +
20+
4 /* vout index */ +
21+
4 /* height + coinbase */ +
22+
8 /* amount */ +
23+
2 /* scriptPubKey len */ +
24+
scriptPubKey.size() /* scriptPubKey */;
25+
}
26+
27+
static void ApplyStats(CCoinsStats& stats, CHashWriter& ss, const uint256& hash, const std::map<uint32_t, Coin>& outputs)
1828
{
1929
assert(!outputs.empty());
2030
ss << hash;
@@ -26,26 +36,38 @@ static void ApplyStats(CCoinsStats &stats, CHashWriter& ss, const uint256& hash,
2636
ss << VARINT_MODE(output.second.out.nValue, VarIntMode::NONNEGATIVE_SIGNED);
2737
stats.nTransactionOutputs++;
2838
stats.nTotalAmount += output.second.out.nValue;
29-
stats.nBogoSize += 32 /* txid */ + 4 /* vout index */ + 4 /* height + coinbase */ + 8 /* amount */ +
30-
2 /* scriptPubKey len */ + output.second.out.scriptPubKey.size() /* scriptPubKey */;
39+
stats.nBogoSize += GetBogoSize(output.second.out.scriptPubKey);
3140
}
3241
ss << VARINT(0u);
3342
}
3443

44+
static void ApplyStats(CCoinsStats& stats, std::nullptr_t, const uint256& hash, const std::map<uint32_t, Coin>& outputs)
45+
{
46+
assert(!outputs.empty());
47+
stats.nTransactions++;
48+
for (const auto& output : outputs) {
49+
stats.nTransactionOutputs++;
50+
stats.nTotalAmount += output.second.out.nValue;
51+
stats.nBogoSize += GetBogoSize(output.second.out.scriptPubKey);
52+
}
53+
}
54+
3555
//! Calculate statistics about the unspent transaction output set
36-
bool GetUTXOStats(CCoinsView* view, CCoinsStats& stats, const std::function<void()>& interruption_point)
56+
template <typename T>
57+
static bool GetUTXOStats(CCoinsView* view, CCoinsStats& stats, T hash_obj, const std::function<void()>& interruption_point)
3758
{
3859
stats = CCoinsStats();
3960
std::unique_ptr<CCoinsViewCursor> pcursor(view->Cursor());
4061
assert(pcursor);
4162

42-
CHashWriter ss(SER_GETHASH, PROTOCOL_VERSION);
4363
stats.hashBlock = pcursor->GetBestBlock();
4464
{
4565
LOCK(cs_main);
4666
stats.nHeight = LookupBlockIndex(stats.hashBlock)->nHeight;
4767
}
48-
ss << stats.hashBlock;
68+
69+
PrepareHash(hash_obj, stats);
70+
4971
uint256 prevkey;
5072
std::map<uint32_t, Coin> outputs;
5173
while (pcursor->Valid()) {
@@ -54,7 +76,7 @@ bool GetUTXOStats(CCoinsView* view, CCoinsStats& stats, const std::function<void
5476
Coin coin;
5577
if (pcursor->GetKey(key) && pcursor->GetValue(coin)) {
5678
if (!outputs.empty() && key.hash != prevkey) {
57-
ApplyStats(stats, ss, prevkey, outputs);
79+
ApplyStats(stats, hash_obj, prevkey, outputs);
5880
outputs.clear();
5981
}
6082
prevkey = key.hash;
@@ -66,9 +88,38 @@ bool GetUTXOStats(CCoinsView* view, CCoinsStats& stats, const std::function<void
6688
pcursor->Next();
6789
}
6890
if (!outputs.empty()) {
69-
ApplyStats(stats, ss, prevkey, outputs);
91+
ApplyStats(stats, hash_obj, prevkey, outputs);
7092
}
71-
stats.hashSerialized = ss.GetHash();
93+
94+
FinalizeHash(hash_obj, stats);
95+
7296
stats.nDiskSize = view->EstimateSize();
7397
return true;
7498
}
99+
100+
bool GetUTXOStats(CCoinsView* view, CCoinsStats& stats, CoinStatsHashType hash_type, const std::function<void()>& interruption_point)
101+
{
102+
switch (hash_type) {
103+
case(CoinStatsHashType::HASH_SERIALIZED): {
104+
CHashWriter ss(SER_GETHASH, PROTOCOL_VERSION);
105+
return GetUTXOStats(view, stats, ss, interruption_point);
106+
}
107+
case(CoinStatsHashType::NONE): {
108+
return GetUTXOStats(view, stats, nullptr, interruption_point);
109+
}
110+
} // no default case, so the compiler can warn about missing cases
111+
assert(false);
112+
}
113+
114+
// The legacy hash serializes the hashBlock
115+
static void PrepareHash(CHashWriter& ss, CCoinsStats& stats)
116+
{
117+
ss << stats.hashBlock;
118+
}
119+
static void PrepareHash(std::nullptr_t, CCoinsStats& stats) {}
120+
121+
static void FinalizeHash(CHashWriter& ss, CCoinsStats& stats)
122+
{
123+
stats.hashSerialized = ss.GetHash();
124+
}
125+
static void FinalizeHash(std::nullptr_t, CCoinsStats& stats) {}

src/node/coinstats.h

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

1515
class CCoinsView;
1616

17+
enum class CoinStatsHashType {
18+
HASH_SERIALIZED,
19+
NONE,
20+
};
21+
1722
struct CCoinsStats
1823
{
1924
int nHeight{0};
@@ -30,6 +35,6 @@ struct CCoinsStats
3035
};
3136

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

3540
#endif // BITCOIN_NODE_COINSTATS_H

src/rpc/blockchain.cpp

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -975,7 +975,9 @@ static UniValue gettxoutsetinfo(const JSONRPCRequest& request)
975975
RPCHelpMan{"gettxoutsetinfo",
976976
"\nReturns statistics about the unspent transaction output set.\n"
977977
"Note this call may take some time.\n",
978-
{},
978+
{
979+
{"hash_type", RPCArg::Type::STR, /* default */ "hash_serialized_2", "Which UTXO set hash should be calculated. Options: 'hash_serialized_2' (the legacy algorithm), 'none'."},
980+
},
979981
RPCResult{
980982
RPCResult::Type::OBJ, "", "",
981983
{
@@ -984,7 +986,7 @@ static UniValue gettxoutsetinfo(const JSONRPCRequest& request)
984986
{RPCResult::Type::NUM, "transactions", "The number of transactions with unspent outputs"},
985987
{RPCResult::Type::NUM, "txouts", "The number of unspent transaction outputs"},
986988
{RPCResult::Type::NUM, "bogosize", "A meaningless metric for UTXO set size"},
987-
{RPCResult::Type::STR_HEX, "hash_serialized_2", "The serialized hash"},
989+
{RPCResult::Type::STR_HEX, "hash_serialized_2", "The serialized hash (only present if 'hash_serialized_2' hash_type is chosen)"},
988990
{RPCResult::Type::NUM, "disk_size", "The estimated size of the chainstate on disk"},
989991
{RPCResult::Type::STR_AMOUNT, "total_amount", "The total amount"},
990992
}},
@@ -999,14 +1001,18 @@ static UniValue gettxoutsetinfo(const JSONRPCRequest& request)
9991001
CCoinsStats stats;
10001002
::ChainstateActive().ForceFlushStateToDisk();
10011003

1004+
const CoinStatsHashType hash_type = ParseHashType(request.params[0], CoinStatsHashType::HASH_SERIALIZED);
1005+
10021006
CCoinsView* coins_view = WITH_LOCK(cs_main, return &ChainstateActive().CoinsDB());
1003-
if (GetUTXOStats(coins_view, stats, RpcInterruptionPoint)) {
1007+
if (GetUTXOStats(coins_view, stats, hash_type, RpcInterruptionPoint)) {
10041008
ret.pushKV("height", (int64_t)stats.nHeight);
10051009
ret.pushKV("bestblock", stats.hashBlock.GetHex());
10061010
ret.pushKV("transactions", (int64_t)stats.nTransactions);
10071011
ret.pushKV("txouts", (int64_t)stats.nTransactionOutputs);
10081012
ret.pushKV("bogosize", (int64_t)stats.nBogoSize);
1009-
ret.pushKV("hash_serialized_2", stats.hashSerialized.GetHex());
1013+
if (hash_type == CoinStatsHashType::HASH_SERIALIZED) {
1014+
ret.pushKV("hash_serialized_2", stats.hashSerialized.GetHex());
1015+
}
10101016
ret.pushKV("disk_size", stats.nDiskSize);
10111017
ret.pushKV("total_amount", ValueFromAmount(stats.nTotalAmount));
10121018
} else {
@@ -2317,7 +2323,7 @@ UniValue dumptxoutset(const JSONRPCRequest& request)
23172323

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

2320-
if (!GetUTXOStats(&::ChainstateActive().CoinsDB(), stats, RpcInterruptionPoint)) {
2326+
if (!GetUTXOStats(&::ChainstateActive().CoinsDB(), stats, CoinStatsHashType::NONE, RpcInterruptionPoint)) {
23212327
throw JSONRPCError(RPC_INTERNAL_ERROR, "Unable to read UTXO set");
23222328
}
23232329

@@ -2378,7 +2384,7 @@ static const CRPCCommand commands[] =
23782384
{ "blockchain", "getmempoolinfo", &getmempoolinfo, {} },
23792385
{ "blockchain", "getrawmempool", &getrawmempool, {"verbose"} },
23802386
{ "blockchain", "gettxout", &gettxout, {"txid","n","include_mempool"} },
2381-
{ "blockchain", "gettxoutsetinfo", &gettxoutsetinfo, {} },
2387+
{ "blockchain", "gettxoutsetinfo", &gettxoutsetinfo, {"hash_type"} },
23822388
{ "blockchain", "pruneblockchain", &pruneblockchain, {"height"} },
23832389
{ "blockchain", "savemempool", &savemempool, {} },
23842390
{ "blockchain", "verifychain", &verifychain, {"checklevel","nblocks"} },

src/rpc/util.cpp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,23 @@ 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 if (hash_type_input == "none") {
126+
return CoinStatsHashType::NONE;
127+
} else {
128+
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("%d is not a valid hash_type", hash_type_input));
129+
}
130+
}
131+
}
132+
116133
std::string HelpExampleCli(const std::string& methodname, const std::string& args)
117134
{
118135
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
}

test/functional/rpc_blockchain.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,17 @@ def _test_gettxoutsetinfo(self):
241241
del res['disk_size'], res3['disk_size']
242242
assert_equal(res, res3)
243243

244+
self.log.info("Test hash_type option for gettxoutsetinfo()")
245+
# Adding hash_type 'hash_serialized_2', which is the default, should
246+
# not change the result.
247+
res4 = node.gettxoutsetinfo(hash_type='hash_serialized_2')
248+
del res4['disk_size']
249+
assert_equal(res, res4)
250+
251+
# hash_type none should not return a UTXO set hash.
252+
res5 = node.gettxoutsetinfo(hash_type='none')
253+
assert 'hash_serialized_2' not in res5
254+
244255
def _test_getblockheader(self):
245256
node = self.nodes[0]
246257

0 commit comments

Comments
 (0)