Skip to content

Commit 792fbd2

Browse files
committed
Add UtxoSetHash file to manage UTXO set hashes
The UtxoSetHash is implemented as an index (subclass of BaseIndex) and maintains data in LevelDB. It keeps the muhash for each block in the database and also allows to search them by block hash or height. It also maintains the global Muhash object which is being modified as new blocks get added to the chain or as reorgs happen. This commit also adds a serialization method to the Muhash object to be able to persist it between restarts of the node.
1 parent df99b25 commit 792fbd2

File tree

4 files changed

+405
-0
lines changed

4 files changed

+405
-0
lines changed

src/Makefile.am

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ BITCOIN_CORE_H = \
135135
index/base.h \
136136
index/blockfilterindex.h \
137137
index/txindex.h \
138+
index/utxosethash.h \
138139
indirectmap.h \
139140
init.h \
140141
interfaces/chain.h \
@@ -270,6 +271,7 @@ libbitcoin_server_a_SOURCES = \
270271
index/base.cpp \
271272
index/blockfilterindex.cpp \
272273
index/txindex.cpp \
274+
index/utxosethash.cpp \
273275
interfaces/chain.cpp \
274276
interfaces/node.cpp \
275277
init.cpp \

src/crypto/muhash.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#endif
1111

1212
#include <stdint.h>
13+
#include <serialize.h>
1314

1415
struct Num3072 {
1516
#ifdef HAVE___INT128
@@ -47,6 +48,15 @@ class MuHash3072
4748

4849
/* Finalize into a 384-byte hash. Does not change this object's value. */
4950
void Finalize(unsigned char* hash384) noexcept;
51+
52+
ADD_SERIALIZE_METHODS;
53+
54+
template <typename Stream, typename Operation>
55+
inline void SerializationOp(Stream& s, Operation ser_action) {
56+
for(int i = 0; i<data.LIMBS; i++) {
57+
READWRITE(data.limbs[i]);
58+
}
59+
}
5060
};
5161

5262
#endif // BITCOIN_CRYPTO_MUHASH_H

src/index/utxosethash.cpp

Lines changed: 342 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,342 @@
1+
// Copyright (c) 2019 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 <chainparams.h>
6+
#include <coins.h>
7+
#include <crypto/muhash.h>
8+
#include <hash.h>
9+
#include <index/utxosethash.h>
10+
#include <serialize.h>
11+
#include <txdb.h>
12+
#include <undo.h>
13+
#include <validation.h>
14+
15+
constexpr char DB_BLOCK_HASH = 's';
16+
constexpr char DB_BLOCK_HEIGHT = 't';
17+
constexpr char DB_MUHASH = 'M';
18+
19+
namespace {
20+
21+
struct DBVal {
22+
uint256 muhash;
23+
24+
ADD_SERIALIZE_METHODS;
25+
26+
template <typename Stream, typename Operation>
27+
inline void SerializationOp(Stream& s, Operation ser_action) {
28+
READWRITE(muhash);
29+
}
30+
};
31+
32+
struct DBHeightKey {
33+
int height;
34+
35+
DBHeightKey() : height(0) {}
36+
explicit DBHeightKey(int height_in) : height(height_in) {}
37+
38+
template<typename Stream>
39+
void Serialize(Stream& s) const
40+
{
41+
ser_writedata8(s, DB_BLOCK_HEIGHT);
42+
ser_writedata32be(s, height);
43+
}
44+
45+
template<typename Stream>
46+
void Unserialize(Stream& s)
47+
{
48+
char prefix = ser_readdata8(s);
49+
if (prefix != DB_BLOCK_HEIGHT) {
50+
throw std::ios_base::failure("Invalid format for block filter index DB height key");
51+
}
52+
height = ser_readdata32be(s);
53+
}
54+
};
55+
56+
struct DBHashKey {
57+
uint256 block_hash;
58+
59+
explicit DBHashKey(const uint256& hash_in) : block_hash(hash_in) {}
60+
61+
ADD_SERIALIZE_METHODS;
62+
63+
template <typename Stream, typename Operation>
64+
inline void SerializationOp(Stream& s, Operation ser_action) {
65+
char prefix = DB_BLOCK_HASH;
66+
READWRITE(prefix);
67+
if (prefix != DB_BLOCK_HASH) {
68+
throw std::ios_base::failure("Invalid format for block filter index DB hash key");
69+
}
70+
71+
READWRITE(block_hash);
72+
}
73+
};
74+
75+
struct DBMuhash {
76+
MuHash3072 hash;
77+
78+
ADD_SERIALIZE_METHODS;
79+
80+
template <typename Stream, typename Operation>
81+
inline void SerializationOp(Stream& s, Operation ser_action) {
82+
READWRITE(hash);
83+
}
84+
};
85+
86+
}; // namespace
87+
88+
std::unique_ptr<UtxoSetHash> g_utxo_set_hash;
89+
90+
UtxoSetHash::UtxoSetHash(size_t n_cache_size, bool f_memory, bool f_wipe)
91+
{
92+
fs::path path = GetDataDir() / "indexes" / "utxo_set_hash";
93+
fs::create_directories(path);
94+
95+
m_db = MakeUnique<UtxoSetHash::DB>(path / "db", n_cache_size, f_memory, f_wipe);
96+
}
97+
98+
bool UtxoSetHash::Init()
99+
{
100+
if (!m_db->Read(DB_MUHASH, m_muhash)) {
101+
// Check that the cause of the read failure is that the key does not exist. Any other errors
102+
// indicate database corruption or a disk failure, and starting the index would cause
103+
// further corruption.
104+
if (m_db->Exists(DB_MUHASH)) {
105+
return error("%s: Cannot read current %s state; index may be corrupted",
106+
__func__, GetName());
107+
}
108+
109+
// If the DB_MUHASH is not set, initialize empty muhash
110+
m_muhash = MuHash3072();
111+
}
112+
113+
return BaseIndex::Init();
114+
}
115+
116+
bool UtxoSetHash::WriteBlock(const CBlock& block, const CBlockIndex* pindex)
117+
{
118+
CBlockUndo block_undo;
119+
120+
if (pindex->nHeight > 0) {
121+
if (!UndoReadFromDisk(block_undo, pindex)) {
122+
return false;
123+
}
124+
125+
std::pair<uint256, DBVal> read_out;
126+
if (!m_db->Read(DBHeightKey(pindex->nHeight - 1), read_out)) {
127+
return false;
128+
}
129+
130+
uint256 expected_block_hash = pindex->pprev->GetBlockHash();
131+
if (read_out.first != expected_block_hash) {
132+
return error("%s: previous block header belongs to unexpected block %s; expected %s",
133+
__func__, read_out.first.ToString(), expected_block_hash.ToString());
134+
}
135+
}
136+
137+
// Add the new utxos created from the block
138+
for (size_t i = 0; i < block.vtx.size(); ++i) {
139+
const auto& tx = block.vtx.at(i);
140+
141+
for (size_t j = 0; j < tx->vout.size(); ++j) {
142+
const CTxOut& out = tx->vout[j];
143+
COutPoint outpoint = COutPoint(tx->GetHash(), j);
144+
Coin coin = Coin(out, pindex->nHeight, tx->IsCoinBase());
145+
146+
TruncatedSHA512Writer ss(SER_DISK, 0);
147+
ss << outpoint;
148+
ss << (uint32_t)(coin.nHeight * 2 + coin.fCoinBase);
149+
ss << coin.out;
150+
m_muhash *= MuHash3072(ss.GetHash().begin());
151+
}
152+
153+
// The coinbase tx has no undo data since no former output is spent
154+
if (i > 0) {
155+
const auto& tx_undo = block_undo.vtxundo.at(i-1);
156+
157+
for (size_t j = 0; j < tx_undo.vprevout.size(); ++j) {
158+
Coin coin = tx_undo.vprevout[j];
159+
COutPoint outpoint = COutPoint(tx->vin[j].prevout.hash, tx->vin[j].prevout.n);
160+
161+
TruncatedSHA512Writer ss(SER_DISK, 0);
162+
ss << outpoint;
163+
ss << (uint32_t)(coin.nHeight * 2 + coin.fCoinBase);
164+
ss << coin.out;
165+
m_muhash /= MuHash3072(ss.GetHash().begin());
166+
}
167+
}
168+
}
169+
170+
std::pair<uint256, DBVal> value;
171+
value.first = pindex->GetBlockHash();
172+
value.second.muhash = currentHashInternal();
173+
174+
if (!m_db->Write(DBHeightKey(pindex->nHeight), value)) {
175+
return false;
176+
}
177+
178+
if (!m_db->Write(DB_MUHASH, m_muhash)) {
179+
return false;
180+
}
181+
182+
return true;
183+
}
184+
185+
static bool CopyHeightIndexToHashIndex(CDBIterator& db_it, CDBBatch& batch,
186+
const std::string& index_name,
187+
int start_height, int stop_height)
188+
{
189+
DBHeightKey key(start_height);
190+
db_it.Seek(key);
191+
192+
for (int height = start_height; height <= stop_height; ++height) {
193+
if (!db_it.GetKey(key) || key.height != height) {
194+
return error("%s: unexpected key in %s: expected (%c, %d)",
195+
__func__, index_name, DB_BLOCK_HEIGHT, height);
196+
}
197+
198+
std::pair<uint256, DBVal> value;
199+
if (!db_it.GetValue(value)) {
200+
return error("%s: unable to read value in %s at key (%c, %d)",
201+
__func__, index_name, DB_BLOCK_HEIGHT, height);
202+
}
203+
204+
batch.Write(DBHashKey(value.first), std::move(value.second));
205+
206+
db_it.Next();
207+
}
208+
return true;
209+
}
210+
211+
bool UtxoSetHash::Rewind(const CBlockIndex* current_tip, const CBlockIndex* new_tip)
212+
{
213+
assert(current_tip->GetAncestor(new_tip->nHeight) == new_tip);
214+
215+
CDBBatch batch(*m_db);
216+
std::unique_ptr<CDBIterator> db_it(m_db->NewIterator());
217+
218+
CBlockIndex* iter_tip = LookupBlockIndex(current_tip->GetBlockHash());
219+
auto& consensus_params = Params().GetConsensus();
220+
221+
do {
222+
CBlock block;
223+
224+
if (!ReadBlockFromDisk(block, iter_tip, consensus_params)) {
225+
return error("%s: Failed to read block %s from disk",
226+
__func__, iter_tip->GetBlockHash().ToString());
227+
}
228+
229+
ReverseBlock(block, iter_tip);
230+
231+
iter_tip = iter_tip->GetAncestor(iter_tip->nHeight - 1);
232+
} while (new_tip != iter_tip);
233+
234+
// During a reorg, we need to copy all hash digests for blocks that are getting disconnected from the
235+
// height index to the hash index so we can still find them when the height index entries are
236+
// overwritten.
237+
if (!CopyHeightIndexToHashIndex(*db_it, batch, m_name, new_tip->nHeight, current_tip->nHeight)) {
238+
return false;
239+
}
240+
241+
if (!m_db->WriteBatch(batch)) return false;
242+
243+
return BaseIndex::Rewind(current_tip, new_tip);
244+
}
245+
246+
static bool LookupOne(const CDBWrapper& db, const CBlockIndex* block_index, DBVal& result)
247+
{
248+
// First check if the result is stored under the height index and the value there matches the
249+
// block hash. This should be the case if the block is on the active chain.
250+
std::pair<uint256, DBVal> read_out;
251+
if (!db.Read(DBHeightKey(block_index->nHeight), read_out)) {
252+
return false;
253+
}
254+
if (read_out.first == block_index->GetBlockHash()) {
255+
result = std::move(read_out.second);
256+
return true;
257+
}
258+
259+
// If value at the height index corresponds to an different block, the result will be stored in
260+
// the hash index.
261+
return db.Read(DBHashKey(block_index->GetBlockHash()), result);
262+
}
263+
264+
bool UtxoSetHash::LookupHash(const CBlockIndex* block_index, uint256& utxo_set_hash) const
265+
{
266+
DBVal entry;
267+
if (!LookupOne(*m_db, block_index, entry)) {
268+
return false;
269+
}
270+
271+
utxo_set_hash = entry.muhash;
272+
return true;
273+
}
274+
275+
uint256 UtxoSetHash::currentHashInternal()
276+
{
277+
unsigned char out[384];
278+
m_muhash.Finalize(out);
279+
return (TruncatedSHA512Writer(SER_DISK, 0) << out).GetHash();
280+
}
281+
282+
// Reverse Block in case of reorg
283+
bool UtxoSetHash::ReverseBlock(const CBlock& block, const CBlockIndex* pindex)
284+
{
285+
CBlockUndo block_undo;
286+
287+
if (pindex->nHeight > 0) {
288+
if (!UndoReadFromDisk(block_undo, pindex)) {
289+
return false;
290+
}
291+
292+
std::pair<uint256, DBVal> read_out;
293+
if (!m_db->Read(DBHeightKey(pindex->nHeight - 1), read_out)) {
294+
return false;
295+
}
296+
297+
uint256 expected_block_hash = pindex->pprev->GetBlockHash();
298+
if (read_out.first != expected_block_hash) {
299+
return error("%s: previous block header belongs to unexpected block %s; expected %s",
300+
__func__, read_out.first.ToString(), expected_block_hash.ToString());
301+
}
302+
}
303+
304+
// Add the new utxos created from the block
305+
for (size_t i = 0; i < block.vtx.size(); ++i) {
306+
const auto& tx = block.vtx.at(i);
307+
308+
for (size_t j = 0; j < tx->vout.size(); ++j) {
309+
const CTxOut& out = tx->vout[j];
310+
COutPoint outpoint = COutPoint(tx->GetHash(), j);
311+
Coin coin = Coin(out, pindex->nHeight, tx->IsCoinBase());
312+
313+
TruncatedSHA512Writer ss(SER_DISK, 0);
314+
ss << outpoint;
315+
ss << (uint32_t)(coin.nHeight * 2 + coin.fCoinBase);
316+
ss << coin.out;
317+
m_muhash /= MuHash3072(ss.GetHash().begin());
318+
}
319+
320+
// The coinbase tx has no undo data since no former output is spent
321+
if (i > 0) {
322+
const auto& tx_undo = block_undo.vtxundo.at(i-1);
323+
324+
for (size_t j = 0; j < tx_undo.vprevout.size(); ++j) {
325+
Coin coin = tx_undo.vprevout[j];
326+
COutPoint outpoint = COutPoint(tx->vin[j].prevout.hash, tx->vin[j].prevout.n);
327+
328+
TruncatedSHA512Writer ss(SER_DISK, 0);
329+
ss << outpoint;
330+
ss << (uint32_t)(coin.nHeight * 2 + coin.fCoinBase);
331+
ss << coin.out;
332+
m_muhash *= MuHash3072(ss.GetHash().begin());
333+
}
334+
}
335+
}
336+
337+
if (!m_db->Write(DB_MUHASH, m_muhash)) {
338+
return false;
339+
}
340+
341+
return true;
342+
}

0 commit comments

Comments
 (0)