Skip to content

Commit 35f73ce

Browse files
committed
coinstats: Move hasher codepath to kernel/coinstats
As mentioned in a previous commit, the hashing codepath can now be moved to a separate file. This decouples callers that only rely on the hashing codepath from the indexing one. This is key for libbitcoinkernel, which needs to have the CoinsStats hashing codepath for AssumeUTXO, but does not wish to be coupled with indexes. Note that only the .cpp file is split in this commit, the header files will be split in a subsequent commit and the #includes to node/coinstats.h will be adjusted to only #include the necessary headers.
1 parent b7634fe commit 35f73ce

File tree

4 files changed

+191
-176
lines changed

4 files changed

+191
-176
lines changed

src/Makefile.am

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,7 @@ libbitcoin_node_a_SOURCES = \
357357
index/coinstatsindex.cpp \
358358
index/txindex.cpp \
359359
init.cpp \
360+
kernel/coinstats.cpp \
360361
mapport.cpp \
361362
net.cpp \
362363
netgroup.cpp \
@@ -873,6 +874,7 @@ libbitcoinkernel_la_SOURCES = \
873874
index/base.cpp \
874875
index/coinstatsindex.cpp \
875876
init/common.cpp \
877+
kernel/coinstats.cpp \
876878
key.cpp \
877879
logging.cpp \
878880
node/blockstorage.cpp \

src/kernel/coinstats.cpp

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
// Copyright (c) 2022 The Bitcoin Core developers
2+
// Distributed under the MIT software license, see the accompanying
3+
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4+
5+
#include <node/coinstats.h>
6+
7+
#include <coins.h>
8+
#include <crypto/muhash.h>
9+
#include <hash.h>
10+
#include <serialize.h>
11+
#include <uint256.h>
12+
#include <util/overflow.h>
13+
#include <util/system.h>
14+
#include <validation.h>
15+
16+
#include <map>
17+
18+
namespace node {
19+
20+
CCoinsStats::CCoinsStats(int block_height, const uint256& block_hash)
21+
: nHeight(block_height),
22+
hashBlock(block_hash) {}
23+
24+
// Database-independent metric indicating the UTXO set size
25+
uint64_t GetBogoSize(const CScript& script_pub_key)
26+
{
27+
return 32 /* txid */ +
28+
4 /* vout index */ +
29+
4 /* height + coinbase */ +
30+
8 /* amount */ +
31+
2 /* scriptPubKey len */ +
32+
script_pub_key.size() /* scriptPubKey */;
33+
}
34+
35+
CDataStream TxOutSer(const COutPoint& outpoint, const Coin& coin) {
36+
CDataStream ss(SER_DISK, PROTOCOL_VERSION);
37+
ss << outpoint;
38+
ss << static_cast<uint32_t>(coin.nHeight * 2 + coin.fCoinBase);
39+
ss << coin.out;
40+
return ss;
41+
}
42+
43+
//! Warning: be very careful when changing this! assumeutxo and UTXO snapshot
44+
//! validation commitments are reliant on the hash constructed by this
45+
//! function.
46+
//!
47+
//! If the construction of this hash is changed, it will invalidate
48+
//! existing UTXO snapshots. This will not result in any kind of consensus
49+
//! failure, but it will force clients that were expecting to make use of
50+
//! assumeutxo to do traditional IBD instead.
51+
//!
52+
//! It is also possible, though very unlikely, that a change in this
53+
//! construction could cause a previously invalid (and potentially malicious)
54+
//! UTXO snapshot to be considered valid.
55+
static void ApplyHash(CHashWriter& ss, const uint256& hash, const std::map<uint32_t, Coin>& outputs)
56+
{
57+
for (auto it = outputs.begin(); it != outputs.end(); ++it) {
58+
if (it == outputs.begin()) {
59+
ss << hash;
60+
ss << VARINT(it->second.nHeight * 2 + it->second.fCoinBase ? 1u : 0u);
61+
}
62+
63+
ss << VARINT(it->first + 1);
64+
ss << it->second.out.scriptPubKey;
65+
ss << VARINT_MODE(it->second.out.nValue, VarIntMode::NONNEGATIVE_SIGNED);
66+
67+
if (it == std::prev(outputs.end())) {
68+
ss << VARINT(0u);
69+
}
70+
}
71+
}
72+
73+
static void ApplyHash(std::nullptr_t, const uint256& hash, const std::map<uint32_t, Coin>& outputs) {}
74+
75+
static void ApplyHash(MuHash3072& muhash, const uint256& hash, const std::map<uint32_t, Coin>& outputs)
76+
{
77+
for (auto it = outputs.begin(); it != outputs.end(); ++it) {
78+
COutPoint outpoint = COutPoint(hash, it->first);
79+
Coin coin = it->second;
80+
muhash.Insert(MakeUCharSpan(TxOutSer(outpoint, coin)));
81+
}
82+
}
83+
84+
static void ApplyStats(CCoinsStats& stats, const uint256& hash, const std::map<uint32_t, Coin>& outputs)
85+
{
86+
assert(!outputs.empty());
87+
stats.nTransactions++;
88+
for (auto it = outputs.begin(); it != outputs.end(); ++it) {
89+
stats.nTransactionOutputs++;
90+
if (stats.total_amount.has_value()) {
91+
stats.total_amount = CheckedAdd(*stats.total_amount, it->second.out.nValue);
92+
}
93+
stats.nBogoSize += GetBogoSize(it->second.out.scriptPubKey);
94+
}
95+
}
96+
97+
//! Calculate statistics about the unspent transaction output set
98+
template <typename T>
99+
static bool ComputeUTXOStats(CCoinsView* view, CCoinsStats& stats, T hash_obj, const std::function<void()>& interruption_point)
100+
{
101+
std::unique_ptr<CCoinsViewCursor> pcursor(view->Cursor());
102+
assert(pcursor);
103+
104+
PrepareHash(hash_obj, stats);
105+
106+
uint256 prevkey;
107+
std::map<uint32_t, Coin> outputs;
108+
while (pcursor->Valid()) {
109+
interruption_point();
110+
COutPoint key;
111+
Coin coin;
112+
if (pcursor->GetKey(key) && pcursor->GetValue(coin)) {
113+
if (!outputs.empty() && key.hash != prevkey) {
114+
ApplyStats(stats, prevkey, outputs);
115+
ApplyHash(hash_obj, prevkey, outputs);
116+
outputs.clear();
117+
}
118+
prevkey = key.hash;
119+
outputs[key.n] = std::move(coin);
120+
stats.coins_count++;
121+
} else {
122+
return error("%s: unable to read value", __func__);
123+
}
124+
pcursor->Next();
125+
}
126+
if (!outputs.empty()) {
127+
ApplyStats(stats, prevkey, outputs);
128+
ApplyHash(hash_obj, prevkey, outputs);
129+
}
130+
131+
FinalizeHash(hash_obj, stats);
132+
133+
stats.nDiskSize = view->EstimateSize();
134+
135+
return true;
136+
}
137+
138+
std::optional<CCoinsStats> ComputeUTXOStats(CoinStatsHashType hash_type, CCoinsView* view, BlockManager& blockman, const std::function<void()>& interruption_point)
139+
{
140+
CBlockIndex* pindex = WITH_LOCK(::cs_main, return blockman.LookupBlockIndex(view->GetBestBlock()));
141+
CCoinsStats stats{Assert(pindex)->nHeight, pindex->GetBlockHash()};
142+
143+
bool success = [&]() -> bool {
144+
switch (hash_type) {
145+
case(CoinStatsHashType::HASH_SERIALIZED): {
146+
CHashWriter ss(SER_GETHASH, PROTOCOL_VERSION);
147+
return ComputeUTXOStats(view, stats, ss, interruption_point);
148+
}
149+
case(CoinStatsHashType::MUHASH): {
150+
MuHash3072 muhash;
151+
return ComputeUTXOStats(view, stats, muhash, interruption_point);
152+
}
153+
case(CoinStatsHashType::NONE): {
154+
return ComputeUTXOStats(view, stats, nullptr, interruption_point);
155+
}
156+
} // no default case, so the compiler can warn about missing cases
157+
assert(false);
158+
}();
159+
160+
if (!success) {
161+
return std::nullopt;
162+
}
163+
return stats;
164+
}
165+
166+
// The legacy hash serializes the hashBlock
167+
static void PrepareHash(CHashWriter& ss, const CCoinsStats& stats)
168+
{
169+
ss << stats.hashBlock;
170+
}
171+
// MuHash does not need the prepare step
172+
static void PrepareHash(MuHash3072& muhash, CCoinsStats& stats) {}
173+
static void PrepareHash(std::nullptr_t, CCoinsStats& stats) {}
174+
175+
static void FinalizeHash(CHashWriter& ss, CCoinsStats& stats)
176+
{
177+
stats.hashSerialized = ss.GetHash();
178+
}
179+
static void FinalizeHash(MuHash3072& muhash, CCoinsStats& stats)
180+
{
181+
uint256 out;
182+
muhash.Finalize(out);
183+
stats.hashSerialized = out;
184+
}
185+
static void FinalizeHash(std::nullptr_t, CCoinsStats& stats) {}
186+
187+
} // namespace node

src/node/coinstats.cpp

Lines changed: 0 additions & 176 deletions
Original file line numberDiff line numberDiff line change
@@ -6,187 +6,11 @@
66
#include <node/coinstats.h>
77

88
#include <coins.h>
9-
#include <crypto/muhash.h>
10-
#include <hash.h>
119
#include <index/coinstatsindex.h>
1210
#include <optional>
13-
#include <serialize.h>
14-
#include <uint256.h>
15-
#include <util/overflow.h>
16-
#include <util/system.h>
1711
#include <validation.h>
1812

19-
#include <map>
20-
2113
namespace node {
22-
23-
CCoinsStats::CCoinsStats(int block_height, const uint256& block_hash)
24-
: nHeight(block_height),
25-
hashBlock(block_hash) {}
26-
27-
// Database-independent metric indicating the UTXO set size
28-
uint64_t GetBogoSize(const CScript& script_pub_key)
29-
{
30-
return 32 /* txid */ +
31-
4 /* vout index */ +
32-
4 /* height + coinbase */ +
33-
8 /* amount */ +
34-
2 /* scriptPubKey len */ +
35-
script_pub_key.size() /* scriptPubKey */;
36-
}
37-
38-
CDataStream TxOutSer(const COutPoint& outpoint, const Coin& coin) {
39-
CDataStream ss(SER_DISK, PROTOCOL_VERSION);
40-
ss << outpoint;
41-
ss << static_cast<uint32_t>(coin.nHeight * 2 + coin.fCoinBase);
42-
ss << coin.out;
43-
return ss;
44-
}
45-
46-
//! Warning: be very careful when changing this! assumeutxo and UTXO snapshot
47-
//! validation commitments are reliant on the hash constructed by this
48-
//! function.
49-
//!
50-
//! If the construction of this hash is changed, it will invalidate
51-
//! existing UTXO snapshots. This will not result in any kind of consensus
52-
//! failure, but it will force clients that were expecting to make use of
53-
//! assumeutxo to do traditional IBD instead.
54-
//!
55-
//! It is also possible, though very unlikely, that a change in this
56-
//! construction could cause a previously invalid (and potentially malicious)
57-
//! UTXO snapshot to be considered valid.
58-
static void ApplyHash(CHashWriter& ss, const uint256& hash, const std::map<uint32_t, Coin>& outputs)
59-
{
60-
for (auto it = outputs.begin(); it != outputs.end(); ++it) {
61-
if (it == outputs.begin()) {
62-
ss << hash;
63-
ss << VARINT(it->second.nHeight * 2 + it->second.fCoinBase ? 1u : 0u);
64-
}
65-
66-
ss << VARINT(it->first + 1);
67-
ss << it->second.out.scriptPubKey;
68-
ss << VARINT_MODE(it->second.out.nValue, VarIntMode::NONNEGATIVE_SIGNED);
69-
70-
if (it == std::prev(outputs.end())) {
71-
ss << VARINT(0u);
72-
}
73-
}
74-
}
75-
76-
static void ApplyHash(std::nullptr_t, const uint256& hash, const std::map<uint32_t, Coin>& outputs) {}
77-
78-
static void ApplyHash(MuHash3072& muhash, const uint256& hash, const std::map<uint32_t, Coin>& outputs)
79-
{
80-
for (auto it = outputs.begin(); it != outputs.end(); ++it) {
81-
COutPoint outpoint = COutPoint(hash, it->first);
82-
Coin coin = it->second;
83-
muhash.Insert(MakeUCharSpan(TxOutSer(outpoint, coin)));
84-
}
85-
}
86-
87-
static void ApplyStats(CCoinsStats& stats, const uint256& hash, const std::map<uint32_t, Coin>& outputs)
88-
{
89-
assert(!outputs.empty());
90-
stats.nTransactions++;
91-
for (auto it = outputs.begin(); it != outputs.end(); ++it) {
92-
stats.nTransactionOutputs++;
93-
if (stats.total_amount.has_value()) {
94-
stats.total_amount = CheckedAdd(*stats.total_amount, it->second.out.nValue);
95-
}
96-
stats.nBogoSize += GetBogoSize(it->second.out.scriptPubKey);
97-
}
98-
}
99-
100-
//! Calculate statistics about the unspent transaction output set
101-
template <typename T>
102-
static bool ComputeUTXOStats(CCoinsView* view, CCoinsStats& stats, T hash_obj, const std::function<void()>& interruption_point)
103-
{
104-
std::unique_ptr<CCoinsViewCursor> pcursor(view->Cursor());
105-
assert(pcursor);
106-
107-
PrepareHash(hash_obj, stats);
108-
109-
uint256 prevkey;
110-
std::map<uint32_t, Coin> outputs;
111-
while (pcursor->Valid()) {
112-
interruption_point();
113-
COutPoint key;
114-
Coin coin;
115-
if (pcursor->GetKey(key) && pcursor->GetValue(coin)) {
116-
if (!outputs.empty() && key.hash != prevkey) {
117-
ApplyStats(stats, prevkey, outputs);
118-
ApplyHash(hash_obj, prevkey, outputs);
119-
outputs.clear();
120-
}
121-
prevkey = key.hash;
122-
outputs[key.n] = std::move(coin);
123-
stats.coins_count++;
124-
} else {
125-
return error("%s: unable to read value", __func__);
126-
}
127-
pcursor->Next();
128-
}
129-
if (!outputs.empty()) {
130-
ApplyStats(stats, prevkey, outputs);
131-
ApplyHash(hash_obj, prevkey, outputs);
132-
}
133-
134-
FinalizeHash(hash_obj, stats);
135-
136-
stats.nDiskSize = view->EstimateSize();
137-
138-
return true;
139-
}
140-
141-
std::optional<CCoinsStats> ComputeUTXOStats(CoinStatsHashType hash_type, CCoinsView* view, BlockManager& blockman, const std::function<void()>& interruption_point)
142-
{
143-
CBlockIndex* pindex = WITH_LOCK(::cs_main, return blockman.LookupBlockIndex(view->GetBestBlock()));
144-
CCoinsStats stats{Assert(pindex)->nHeight, pindex->GetBlockHash()};
145-
146-
bool success = [&]() -> bool {
147-
switch (hash_type) {
148-
case(CoinStatsHashType::HASH_SERIALIZED): {
149-
CHashWriter ss(SER_GETHASH, PROTOCOL_VERSION);
150-
return ComputeUTXOStats(view, stats, ss, interruption_point);
151-
}
152-
case(CoinStatsHashType::MUHASH): {
153-
MuHash3072 muhash;
154-
return ComputeUTXOStats(view, stats, muhash, interruption_point);
155-
}
156-
case(CoinStatsHashType::NONE): {
157-
return ComputeUTXOStats(view, stats, nullptr, interruption_point);
158-
}
159-
} // no default case, so the compiler can warn about missing cases
160-
assert(false);
161-
}();
162-
163-
if (!success) {
164-
return std::nullopt;
165-
}
166-
return stats;
167-
}
168-
169-
// The legacy hash serializes the hashBlock
170-
static void PrepareHash(CHashWriter& ss, const CCoinsStats& stats)
171-
{
172-
ss << stats.hashBlock;
173-
}
174-
// MuHash does not need the prepare step
175-
static void PrepareHash(MuHash3072& muhash, CCoinsStats& stats) {}
176-
static void PrepareHash(std::nullptr_t, CCoinsStats& stats) {}
177-
178-
static void FinalizeHash(CHashWriter& ss, CCoinsStats& stats)
179-
{
180-
stats.hashSerialized = ss.GetHash();
181-
}
182-
static void FinalizeHash(MuHash3072& muhash, CCoinsStats& stats)
183-
{
184-
uint256 out;
185-
muhash.Finalize(out);
186-
stats.hashSerialized = out;
187-
}
188-
static void FinalizeHash(std::nullptr_t, CCoinsStats& stats) {}
189-
19014
std::optional<CCoinsStats> GetUTXOStats(CCoinsView* view, BlockManager& blockman, CoinStatsHashType hash_type, const std::function<void()>& interruption_point, const CBlockIndex* pindex, bool index_requested)
19115
{
19216
// Use CoinStatsIndex if it is requested and available and a hash_type of Muhash or None was requested

0 commit comments

Comments
 (0)