Skip to content

Commit d1758d3

Browse files
committed
Maintain UTXO Set hash using Muhash index
1 parent 307cee8 commit d1758d3

File tree

5 files changed

+39
-39
lines changed

5 files changed

+39
-39
lines changed

src/init.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#include <httpserver.h>
2323
#include <index/blockfilterindex.h>
2424
#include <index/txindex.h>
25+
#include <index/utxosethash.h>
2526
#include <interfaces/chain.h>
2627
#include <key.h>
2728
#include <miner.h>
@@ -166,6 +167,9 @@ void Interrupt(NodeContext& node)
166167
g_txindex->Interrupt();
167168
}
168169
ForEachBlockFilterIndex([](BlockFilterIndex& index) { index.Interrupt(); });
170+
if (g_utxo_set_hash) {
171+
g_utxo_set_hash->Interrupt();
172+
}
169173
}
170174

171175
void Shutdown(NodeContext& node)
@@ -198,6 +202,7 @@ void Shutdown(NodeContext& node)
198202
if (node.connman) node.connman->Stop();
199203
if (g_txindex) g_txindex->Stop();
200204
ForEachBlockFilterIndex([](BlockFilterIndex& index) { index.Stop(); });
205+
if (g_utxo_set_hash) g_utxo_set_hash->Stop();
201206

202207
StopTorControl();
203208

@@ -213,6 +218,7 @@ void Shutdown(NodeContext& node)
213218
node.banman.reset();
214219
g_txindex.reset();
215220
DestroyAllBlockFilterIndexes();
221+
g_utxo_set_hash.reset();
216222

217223
if (::mempool.IsLoaded() && gArgs.GetArg("-persistmempool", DEFAULT_PERSIST_MEMPOOL)) {
218224
DumpMempool(::mempool);
@@ -1440,6 +1446,7 @@ bool AppInitMain(NodeContext& node)
14401446
filter_index_cache = max_cache / n_indexes;
14411447
nTotalCache -= filter_index_cache * n_indexes;
14421448
}
1449+
int64_t utsh_cache = 0;
14431450
int64_t nCoinDBCache = std::min(nTotalCache / 2, (nTotalCache / 4) + (1 << 23)); // use 25%-50% of the remainder for disk cache
14441451
nCoinDBCache = std::min(nCoinDBCache, nMaxCoinsDBCache << 20); // cap total coins db cache
14451452
nTotalCache -= nCoinDBCache;
@@ -1658,6 +1665,9 @@ bool AppInitMain(NodeContext& node)
16581665
GetBlockFilterIndex(filter_type)->Start();
16591666
}
16601667

1668+
g_utxo_set_hash = MakeUnique<UtxoSetHash>(utsh_cache, false, fReindex);
1669+
g_utxo_set_hash->Start();
1670+
16611671
// ********************************************************* Step 9: load wallet
16621672
for (const auto& client : node.chain_clients) {
16631673
if (!client->load()) {

src/node/coinstats.cpp

Lines changed: 21 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -7,32 +7,23 @@
77

88
#include <coins.h>
99
#include <hash.h>
10+
#include <index/utxosethash.h>
1011
#include <serialize.h>
11-
#include <validation.h>
1212
#include <uint256.h>
1313
#include <util/system.h>
14+
#include <validation.h>
1415

1516
#include <map>
1617

1718
#include <boost/thread.hpp>
1819

19-
20-
static void ApplyStats(CCoinsStats &stats, CHashWriter& ss, const uint256& hash, const std::map<uint32_t, Coin>& outputs)
20+
static void ApplyStats(CCoinsStats &stats, const COutPoint outpoint, const Coin& coin)
2121
{
22-
assert(!outputs.empty());
23-
ss << hash;
24-
ss << VARINT(outputs.begin()->second.nHeight * 2 + outputs.begin()->second.fCoinBase ? 1u : 0u);
2522
stats.nTransactions++;
26-
for (const auto& output : outputs) {
27-
ss << VARINT(output.first + 1);
28-
ss << output.second.out.scriptPubKey;
29-
ss << VARINT(output.second.out.nValue, VarIntMode::NONNEGATIVE_SIGNED);
30-
stats.nTransactionOutputs++;
31-
stats.nTotalAmount += output.second.out.nValue;
32-
stats.nBogoSize += 32 /* txid */ + 4 /* vout index */ + 4 /* height + coinbase */ + 8 /* amount */ +
33-
2 /* scriptPubKey len */ + output.second.out.scriptPubKey.size() /* scriptPubKey */;
34-
}
35-
ss << VARINT(0u);
23+
stats.nTransactionOutputs++;
24+
stats.nTotalAmount += coin.out.nValue;
25+
stats.nBogoSize += 32 /* txid */ + 4 /* vout index */ + 4 /* height + coinbase */ + 8 /* amount */ +
26+
2 /* scriptPubKey len */ + coin.out.scriptPubKey.size() /* scriptPubKey */;
3627
}
3728

3829
//! Calculate statistics about the unspent transaction output set
@@ -41,35 +32,34 @@ bool GetUTXOStats(CCoinsView *view, CCoinsStats &stats)
4132
std::unique_ptr<CCoinsViewCursor> pcursor(view->Cursor());
4233
assert(pcursor);
4334

44-
CHashWriter ss(SER_GETHASH, PROTOCOL_VERSION);
35+
uint256 muhash_buf;
36+
const CBlockIndex* block_index;
4537
stats.hashBlock = pcursor->GetBestBlock();
4638
{
4739
LOCK(cs_main);
48-
stats.nHeight = LookupBlockIndex(stats.hashBlock)->nHeight;
40+
block_index = LookupBlockIndex(stats.hashBlock);
4941
}
50-
ss << stats.hashBlock;
51-
uint256 prevkey;
52-
std::map<uint32_t, Coin> outputs;
42+
43+
stats.nHeight = block_index->nHeight;
44+
45+
if (!g_utxo_set_hash->LookupHash(block_index, muhash_buf)) {
46+
return false;
47+
}
48+
49+
stats.hashSerialized = muhash_buf;
50+
5351
while (pcursor->Valid()) {
5452
boost::this_thread::interruption_point();
5553
COutPoint key;
5654
Coin coin;
5755
if (pcursor->GetKey(key) && pcursor->GetValue(coin)) {
58-
if (!outputs.empty() && key.hash != prevkey) {
59-
ApplyStats(stats, ss, prevkey, outputs);
60-
outputs.clear();
61-
}
62-
prevkey = key.hash;
63-
outputs[key.n] = std::move(coin);
56+
ApplyStats(stats, key, coin);
6457
} else {
6558
return error("%s: unable to read value", __func__);
6659
}
6760
pcursor->Next();
6861
}
69-
if (!outputs.empty()) {
70-
ApplyStats(stats, ss, prevkey, outputs);
71-
}
72-
stats.hashSerialized = ss.GetHash();
62+
7363
stats.nDiskSize = view->EstimateSize();
7464
return true;
7565
}

src/rpc/blockchain.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -977,7 +977,7 @@ static UniValue gettxoutsetinfo(const JSONRPCRequest& request)
977977
" \"transactions\": n, (numeric) The number of transactions with unspent outputs\n"
978978
" \"txouts\": n, (numeric) The number of unspent transaction outputs\n"
979979
" \"bogosize\": n, (numeric) A meaningless metric for UTXO set size\n"
980-
" \"hash_serialized_2\": \"hash\", (string) The serialized hash\n"
980+
" \"utxo_set_hash\": \"hash\", (string) The serialized hash\n"
981981
" \"disk_size\": n, (numeric) The estimated size of the chainstate on disk\n"
982982
" \"total_amount\": x.xxx (numeric) The total amount\n"
983983
"}\n"
@@ -1000,7 +1000,7 @@ static UniValue gettxoutsetinfo(const JSONRPCRequest& request)
10001000
ret.pushKV("transactions", (int64_t)stats.nTransactions);
10011001
ret.pushKV("txouts", (int64_t)stats.nTransactionOutputs);
10021002
ret.pushKV("bogosize", (int64_t)stats.nBogoSize);
1003-
ret.pushKV("hash_serialized_2", stats.hashSerialized.GetHex());
1003+
ret.pushKV("utxo_set_hash", stats.hashSerialized.GetHex());
10041004
ret.pushKV("disk_size", stats.nDiskSize);
10051005
ret.pushKV("total_amount", ValueFromAmount(stats.nTotalAmount));
10061006
} else {

test/functional/feature_dbcrash.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ def restart_node(self, node_index, expected_tip):
8989
# Any of these RPC calls could throw due to node crash
9090
self.start_node(node_index)
9191
self.nodes[node_index].waitforblock(expected_tip)
92-
utxo_hash = self.nodes[node_index].gettxoutsetinfo()['hash_serialized_2']
92+
utxo_hash = self.nodes[node_index].gettxoutsetinfo()['utxo_set_hash']
9393
return utxo_hash
9494
except:
9595
# An exception here should mean the node is about to crash.
@@ -134,7 +134,7 @@ def sync_node3blocks(self, block_hashes):
134134
If any nodes crash while updating, we'll compare utxo hashes to
135135
ensure recovery was successful."""
136136

137-
node3_utxo_hash = self.nodes[3].gettxoutsetinfo()['hash_serialized_2']
137+
node3_utxo_hash = self.nodes[3].gettxoutsetinfo()['utxo_set_hash']
138138

139139
# Retrieve all the blocks from node3
140140
blocks = []
@@ -176,12 +176,12 @@ def verify_utxo_hash(self):
176176
"""Verify that the utxo hash of each node matches node3.
177177
178178
Restart any nodes that crash while querying."""
179-
node3_utxo_hash = self.nodes[3].gettxoutsetinfo()['hash_serialized_2']
179+
node3_utxo_hash = self.nodes[3].gettxoutsetinfo()['utxo_set_hash']
180180
self.log.info("Verifying utxo hash matches for all nodes")
181181

182182
for i in range(3):
183183
try:
184-
nodei_utxo_hash = self.nodes[i].gettxoutsetinfo()['hash_serialized_2']
184+
nodei_utxo_hash = self.nodes[i].gettxoutsetinfo()['utxo_set_hash']
185185
except OSError:
186186
# probably a crash on db flushing
187187
nodei_utxo_hash = self.restart_node(i, self.nodes[3].getbestblockhash())

test/functional/rpc_blockchain.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,7 @@ def _test_gettxoutsetinfo(self):
216216
assert size > 6400
217217
assert size < 64000
218218
assert_equal(len(res['bestblock']), 64)
219-
assert_equal(len(res['hash_serialized_2']), 64)
219+
assert_equal(len(res['utxo_set_hash']), 64)
220220

221221
self.log.info("Test that gettxoutsetinfo() works for blockchain with just the genesis block")
222222
b1hash = node.getblockhash(1)
@@ -229,7 +229,7 @@ def _test_gettxoutsetinfo(self):
229229
assert_equal(res2['txouts'], 0)
230230
assert_equal(res2['bogosize'], 0),
231231
assert_equal(res2['bestblock'], node.getblockhash(0))
232-
assert_equal(len(res2['hash_serialized_2']), 64)
232+
assert_equal(len(res2['utxo_set_hash']), 64)
233233

234234
self.log.info("Test that gettxoutsetinfo() returns the same result after invalidate/reconsider block")
235235
node.reconsiderblock(b1hash)

0 commit comments

Comments
 (0)