1 // Copyright (c) 2020-2021 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 <index/coinstatsindex.h>
9 #include <node/blockstorage.h>
10 #include <serialize.h>
11 #include <txdb.h>
12 #include <undo.h>
13 #include <validation.h>
14
15 static constexpr uint8_t DB_BLOCK_HASH{'s'};
16 static constexpr uint8_t DB_BLOCK_HEIGHT{'t'};
17 static constexpr uint8_t DB_MUHASH{'M'};
18
19 namespace {
20
21 struct DBVal {
22 uint256 muhash;
23 uint64_t transaction_output_count;
24 uint64_t bogo_size;
25 CAmount total_amount;
26 CAmount total_subsidy;
27 CAmount block_unspendable_amount;
28 CAmount block_prevout_spent_amount;
29 CAmount block_new_outputs_ex_coinbase_amount;
30 CAmount block_coinbase_amount;
31 CAmount unspendables_genesis_block;
32 CAmount unspendables_bip30;
33 CAmount unspendables_scripts;
34 CAmount unspendables_unclaimed_rewards;
35
SERIALIZE_METHODS__anon40d6c8790111::DBVal36 SERIALIZE_METHODS(DBVal, obj)
37 {
38 READWRITE(obj.muhash);
39 READWRITE(obj.transaction_output_count);
40 READWRITE(obj.bogo_size);
41 READWRITE(obj.total_amount);
42 READWRITE(obj.total_subsidy);
43 READWRITE(obj.block_unspendable_amount);
44 READWRITE(obj.block_prevout_spent_amount);
45 READWRITE(obj.block_new_outputs_ex_coinbase_amount);
46 READWRITE(obj.block_coinbase_amount);
47 READWRITE(obj.unspendables_genesis_block);
48 READWRITE(obj.unspendables_bip30);
49 READWRITE(obj.unspendables_scripts);
50 READWRITE(obj.unspendables_unclaimed_rewards);
51 }
52 };
53
54 struct DBHeightKey {
55 int height;
56
DBHeightKey__anon40d6c8790111::DBHeightKey57 explicit DBHeightKey(int height_in) : height(height_in) {}
58
59 template <typename Stream>
Serialize__anon40d6c8790111::DBHeightKey60 void Serialize(Stream& s) const
61 {
62 ser_writedata8(s, DB_BLOCK_HEIGHT);
63 ser_writedata32be(s, height);
64 }
65
66 template <typename Stream>
Unserialize__anon40d6c8790111::DBHeightKey67 void Unserialize(Stream& s)
68 {
69 const uint8_t prefix{ser_readdata8(s)};
70 if (prefix != DB_BLOCK_HEIGHT) {
71 throw std::ios_base::failure("Invalid format for coinstatsindex DB height key");
72 }
73 height = ser_readdata32be(s);
74 }
75 };
76
77 struct DBHashKey {
78 uint256 block_hash;
79
DBHashKey__anon40d6c8790111::DBHashKey80 explicit DBHashKey(const uint256& hash_in) : block_hash(hash_in) {}
81
SERIALIZE_METHODS__anon40d6c8790111::DBHashKey82 SERIALIZE_METHODS(DBHashKey, obj)
83 {
84 uint8_t prefix{DB_BLOCK_HASH};
85 READWRITE(prefix);
86 if (prefix != DB_BLOCK_HASH) {
87 throw std::ios_base::failure("Invalid format for coinstatsindex DB hash key");
88 }
89
90 READWRITE(obj.block_hash);
91 }
92 };
93
94 }; // namespace
95
96 std::unique_ptr<CoinStatsIndex> g_coin_stats_index;
97
CoinStatsIndex(size_t n_cache_size,bool f_memory,bool f_wipe)98 CoinStatsIndex::CoinStatsIndex(size_t n_cache_size, bool f_memory, bool f_wipe)
99 {
100 fs::path path{gArgs.GetDataDirNet() / "indexes" / "coinstats"};
101 fs::create_directories(path);
102
103 m_db = std::make_unique<CoinStatsIndex::DB>(path / "db", n_cache_size, f_memory, f_wipe);
104 }
105
WriteBlock(const CBlock & block,const CBlockIndex * pindex)106 bool CoinStatsIndex::WriteBlock(const CBlock& block, const CBlockIndex* pindex)
107 {
108 CBlockUndo block_undo;
109 const CAmount block_subsidy{GetBlockSubsidy(pindex->nHeight, Params().GetConsensus())};
110 m_total_subsidy += block_subsidy;
111
112 // Ignore genesis block
113 if (pindex->nHeight > 0) {
114 if (!UndoReadFromDisk(block_undo, pindex)) {
115 return false;
116 }
117
118 std::pair<uint256, DBVal> read_out;
119 if (!m_db->Read(DBHeightKey(pindex->nHeight - 1), read_out)) {
120 return false;
121 }
122
123 uint256 expected_block_hash{pindex->pprev->GetBlockHash()};
124 if (read_out.first != expected_block_hash) {
125 if (!m_db->Read(DBHashKey(expected_block_hash), read_out)) {
126 return error("%s: previous block header belongs to unexpected block %s; expected %s",
127 __func__, read_out.first.ToString(), expected_block_hash.ToString());
128 }
129 }
130
131 // TODO: Deduplicate BIP30 related code
132 bool is_bip30_block{(pindex->nHeight == 91722 && pindex->GetBlockHash() == uint256S("0x00000000000271a2dc26e7667f8419f2e15416dc6955e5a6c6cdf3f2574dd08e")) ||
133 (pindex->nHeight == 91812 && pindex->GetBlockHash() == uint256S("0x00000000000af0aed4792b1acee3d966af36cf5def14935db8de83d6f9306f2f"))};
134
135 // Add the new utxos created from the block
136 for (size_t i = 0; i < block.vtx.size(); ++i) {
137 const auto& tx{block.vtx.at(i)};
138
139 // Skip duplicate txid coinbase transactions (BIP30).
140 if (is_bip30_block && tx->IsCoinBase()) {
141 m_block_unspendable_amount += block_subsidy;
142 m_unspendables_bip30 += block_subsidy;
143 continue;
144 }
145
146 for (size_t j = 0; j < tx->vout.size(); ++j) {
147 const CTxOut& out{tx->vout[j]};
148 Coin coin{out, pindex->nHeight, tx->IsCoinBase()};
149 COutPoint outpoint{tx->GetHash(), static_cast<uint32_t>(j)};
150
151 // Skip unspendable coins
152 if (coin.out.scriptPubKey.IsUnspendable()) {
153 m_block_unspendable_amount += coin.out.nValue;
154 m_unspendables_scripts += coin.out.nValue;
155 continue;
156 }
157
158 m_muhash.Insert(MakeUCharSpan(TxOutSer(outpoint, coin)));
159
160 if (tx->IsCoinBase()) {
161 m_block_coinbase_amount += coin.out.nValue;
162 } else {
163 m_block_new_outputs_ex_coinbase_amount += coin.out.nValue;
164 }
165
166 ++m_transaction_output_count;
167 m_total_amount += coin.out.nValue;
168 m_bogo_size += GetBogoSize(coin.out.scriptPubKey);
169 }
170
171 // The coinbase tx has no undo data since no former output is spent
172 if (!tx->IsCoinBase()) {
173 const auto& tx_undo{block_undo.vtxundo.at(i - 1)};
174
175 for (size_t j = 0; j < tx_undo.vprevout.size(); ++j) {
176 Coin coin{tx_undo.vprevout[j]};
177 COutPoint outpoint{tx->vin[j].prevout.hash, tx->vin[j].prevout.n};
178
179 m_muhash.Remove(MakeUCharSpan(TxOutSer(outpoint, coin)));
180
181 m_block_prevout_spent_amount += coin.out.nValue;
182
183 --m_transaction_output_count;
184 m_total_amount -= coin.out.nValue;
185 m_bogo_size -= GetBogoSize(coin.out.scriptPubKey);
186 }
187 }
188 }
189 } else {
190 // genesis block
191 m_block_unspendable_amount += block_subsidy;
192 m_unspendables_genesis_block += block_subsidy;
193 }
194
195 // If spent prevouts + block subsidy are still a higher amount than
196 // new outputs + coinbase + current unspendable amount this means
197 // the miner did not claim the full block reward. Unclaimed block
198 // rewards are also unspendable.
199 const CAmount unclaimed_rewards{(m_block_prevout_spent_amount + m_total_subsidy) - (m_block_new_outputs_ex_coinbase_amount + m_block_coinbase_amount + m_block_unspendable_amount)};
200 m_block_unspendable_amount += unclaimed_rewards;
201 m_unspendables_unclaimed_rewards += unclaimed_rewards;
202
203 std::pair<uint256, DBVal> value;
204 value.first = pindex->GetBlockHash();
205 value.second.transaction_output_count = m_transaction_output_count;
206 value.second.bogo_size = m_bogo_size;
207 value.second.total_amount = m_total_amount;
208 value.second.total_subsidy = m_total_subsidy;
209 value.second.block_unspendable_amount = m_block_unspendable_amount;
210 value.second.block_prevout_spent_amount = m_block_prevout_spent_amount;
211 value.second.block_new_outputs_ex_coinbase_amount = m_block_new_outputs_ex_coinbase_amount;
212 value.second.block_coinbase_amount = m_block_coinbase_amount;
213 value.second.unspendables_genesis_block = m_unspendables_genesis_block;
214 value.second.unspendables_bip30 = m_unspendables_bip30;
215 value.second.unspendables_scripts = m_unspendables_scripts;
216 value.second.unspendables_unclaimed_rewards = m_unspendables_unclaimed_rewards;
217
218 uint256 out;
219 m_muhash.Finalize(out);
220 value.second.muhash = out;
221
222 return m_db->Write(DBHeightKey(pindex->nHeight), value) && m_db->Write(DB_MUHASH, m_muhash);
223 }
224
CopyHeightIndexToHashIndex(CDBIterator & db_it,CDBBatch & batch,const std::string & index_name,int start_height,int stop_height)225 static bool CopyHeightIndexToHashIndex(CDBIterator& db_it, CDBBatch& batch,
226 const std::string& index_name,
227 int start_height, int stop_height)
228 {
229 DBHeightKey key{start_height};
230 db_it.Seek(key);
231
232 for (int height = start_height; height <= stop_height; ++height) {
233 if (!db_it.GetKey(key) || key.height != height) {
234 return error("%s: unexpected key in %s: expected (%c, %d)",
235 __func__, index_name, DB_BLOCK_HEIGHT, height);
236 }
237
238 std::pair<uint256, DBVal> value;
239 if (!db_it.GetValue(value)) {
240 return error("%s: unable to read value in %s at key (%c, %d)",
241 __func__, index_name, DB_BLOCK_HEIGHT, height);
242 }
243
244 batch.Write(DBHashKey(value.first), std::move(value.second));
245
246 db_it.Next();
247 }
248 return true;
249 }
250
Rewind(const CBlockIndex * current_tip,const CBlockIndex * new_tip)251 bool CoinStatsIndex::Rewind(const CBlockIndex* current_tip, const CBlockIndex* new_tip)
252 {
253 assert(current_tip->GetAncestor(new_tip->nHeight) == new_tip);
254
255 CDBBatch batch(*m_db);
256 std::unique_ptr<CDBIterator> db_it(m_db->NewIterator());
257
258 // During a reorg, we need to copy all hash digests for blocks that are
259 // getting disconnected from the height index to the hash index so we can
260 // still find them when the height index entries are overwritten.
261 if (!CopyHeightIndexToHashIndex(*db_it, batch, m_name, new_tip->nHeight, current_tip->nHeight)) {
262 return false;
263 }
264
265 if (!m_db->WriteBatch(batch)) return false;
266
267 {
268 LOCK(cs_main);
269 CBlockIndex* iter_tip{m_chainstate->m_blockman.LookupBlockIndex(current_tip->GetBlockHash())};
270 const auto& consensus_params{Params().GetConsensus()};
271
272 do {
273 CBlock block;
274
275 if (!ReadBlockFromDisk(block, iter_tip, consensus_params)) {
276 return error("%s: Failed to read block %s from disk",
277 __func__, iter_tip->GetBlockHash().ToString());
278 }
279
280 ReverseBlock(block, iter_tip);
281
282 iter_tip = iter_tip->GetAncestor(iter_tip->nHeight - 1);
283 } while (new_tip != iter_tip);
284 }
285
286 return BaseIndex::Rewind(current_tip, new_tip);
287 }
288
LookUpOne(const CDBWrapper & db,const CBlockIndex * block_index,DBVal & result)289 static bool LookUpOne(const CDBWrapper& db, const CBlockIndex* block_index, DBVal& result)
290 {
291 // First check if the result is stored under the height index and the value
292 // there matches the block hash. This should be the case if the block is on
293 // the active chain.
294 std::pair<uint256, DBVal> read_out;
295 if (!db.Read(DBHeightKey(block_index->nHeight), read_out)) {
296 return false;
297 }
298 if (read_out.first == block_index->GetBlockHash()) {
299 result = std::move(read_out.second);
300 return true;
301 }
302
303 // If value at the height index corresponds to an different block, the
304 // result will be stored in the hash index.
305 return db.Read(DBHashKey(block_index->GetBlockHash()), result);
306 }
307
LookUpStats(const CBlockIndex * block_index,CCoinsStats & coins_stats) const308 bool CoinStatsIndex::LookUpStats(const CBlockIndex* block_index, CCoinsStats& coins_stats) const
309 {
310 DBVal entry;
311 if (!LookUpOne(*m_db, block_index, entry)) {
312 return false;
313 }
314
315 coins_stats.hashSerialized = entry.muhash;
316 coins_stats.nTransactionOutputs = entry.transaction_output_count;
317 coins_stats.nBogoSize = entry.bogo_size;
318 coins_stats.nTotalAmount = entry.total_amount;
319 coins_stats.total_subsidy = entry.total_subsidy;
320 coins_stats.block_unspendable_amount = entry.block_unspendable_amount;
321 coins_stats.block_prevout_spent_amount = entry.block_prevout_spent_amount;
322 coins_stats.block_new_outputs_ex_coinbase_amount = entry.block_new_outputs_ex_coinbase_amount;
323 coins_stats.block_coinbase_amount = entry.block_coinbase_amount;
324 coins_stats.unspendables_genesis_block = entry.unspendables_genesis_block;
325 coins_stats.unspendables_bip30 = entry.unspendables_bip30;
326 coins_stats.unspendables_scripts = entry.unspendables_scripts;
327 coins_stats.unspendables_unclaimed_rewards = entry.unspendables_unclaimed_rewards;
328
329 return true;
330 }
331
Init()332 bool CoinStatsIndex::Init()
333 {
334 if (!m_db->Read(DB_MUHASH, m_muhash)) {
335 // Check that the cause of the read failure is that the key does not
336 // exist. Any other errors indicate database corruption or a disk
337 // failure, and starting the index would cause further corruption.
338 if (m_db->Exists(DB_MUHASH)) {
339 return error("%s: Cannot read current %s state; index may be corrupted",
340 __func__, GetName());
341 }
342 }
343
344 if (BaseIndex::Init()) {
345 const CBlockIndex* pindex{CurrentIndex()};
346
347 if (pindex) {
348 DBVal entry;
349 if (!LookUpOne(*m_db, pindex, entry)) {
350 return false;
351 }
352
353 m_transaction_output_count = entry.transaction_output_count;
354 m_bogo_size = entry.bogo_size;
355 m_total_amount = entry.total_amount;
356 m_total_subsidy = entry.total_subsidy;
357 m_block_unspendable_amount = entry.block_unspendable_amount;
358 m_block_prevout_spent_amount = entry.block_prevout_spent_amount;
359 m_block_new_outputs_ex_coinbase_amount = entry.block_new_outputs_ex_coinbase_amount;
360 m_block_coinbase_amount = entry.block_coinbase_amount;
361 m_unspendables_genesis_block = entry.unspendables_genesis_block;
362 m_unspendables_bip30 = entry.unspendables_bip30;
363 m_unspendables_scripts = entry.unspendables_scripts;
364 m_unspendables_unclaimed_rewards = entry.unspendables_unclaimed_rewards;
365 }
366
367 return true;
368 }
369
370 return false;
371 }
372
373 // Reverse a single block as part of a reorg
ReverseBlock(const CBlock & block,const CBlockIndex * pindex)374 bool CoinStatsIndex::ReverseBlock(const CBlock& block, const CBlockIndex* pindex)
375 {
376 CBlockUndo block_undo;
377 std::pair<uint256, DBVal> read_out;
378
379 const CAmount block_subsidy{GetBlockSubsidy(pindex->nHeight, Params().GetConsensus())};
380 m_total_subsidy -= block_subsidy;
381
382 // Ignore genesis block
383 if (pindex->nHeight > 0) {
384 if (!UndoReadFromDisk(block_undo, pindex)) {
385 return false;
386 }
387
388 if (!m_db->Read(DBHeightKey(pindex->nHeight - 1), read_out)) {
389 return false;
390 }
391
392 uint256 expected_block_hash{pindex->pprev->GetBlockHash()};
393 if (read_out.first != expected_block_hash) {
394 if (!m_db->Read(DBHashKey(expected_block_hash), read_out)) {
395 return error("%s: previous block header belongs to unexpected block %s; expected %s",
396 __func__, read_out.first.ToString(), expected_block_hash.ToString());
397 }
398 }
399 }
400
401 // Remove the new UTXOs that were created from the block
402 for (size_t i = 0; i < block.vtx.size(); ++i) {
403 const auto& tx{block.vtx.at(i)};
404
405 for (size_t j = 0; j < tx->vout.size(); ++j) {
406 const CTxOut& out{tx->vout[j]};
407 COutPoint outpoint{tx->GetHash(), static_cast<uint32_t>(j)};
408 Coin coin{out, pindex->nHeight, tx->IsCoinBase()};
409
410 // Skip unspendable coins
411 if (coin.out.scriptPubKey.IsUnspendable()) {
412 m_block_unspendable_amount -= coin.out.nValue;
413 m_unspendables_scripts -= coin.out.nValue;
414 continue;
415 }
416
417 m_muhash.Remove(MakeUCharSpan(TxOutSer(outpoint, coin)));
418
419 if (tx->IsCoinBase()) {
420 m_block_coinbase_amount -= coin.out.nValue;
421 } else {
422 m_block_new_outputs_ex_coinbase_amount -= coin.out.nValue;
423 }
424
425 --m_transaction_output_count;
426 m_total_amount -= coin.out.nValue;
427 m_bogo_size -= GetBogoSize(coin.out.scriptPubKey);
428 }
429
430 // The coinbase tx has no undo data since no former output is spent
431 if (!tx->IsCoinBase()) {
432 const auto& tx_undo{block_undo.vtxundo.at(i - 1)};
433
434 for (size_t j = 0; j < tx_undo.vprevout.size(); ++j) {
435 Coin coin{tx_undo.vprevout[j]};
436 COutPoint outpoint{tx->vin[j].prevout.hash, tx->vin[j].prevout.n};
437
438 m_muhash.Insert(MakeUCharSpan(TxOutSer(outpoint, coin)));
439
440 m_block_prevout_spent_amount -= coin.out.nValue;
441
442 m_transaction_output_count++;
443 m_total_amount += coin.out.nValue;
444 m_bogo_size += GetBogoSize(coin.out.scriptPubKey);
445 }
446 }
447 }
448
449 const CAmount unclaimed_rewards{(m_block_new_outputs_ex_coinbase_amount + m_block_coinbase_amount + m_block_unspendable_amount) - (m_block_prevout_spent_amount + m_total_subsidy)};
450 m_block_unspendable_amount -= unclaimed_rewards;
451 m_unspendables_unclaimed_rewards -= unclaimed_rewards;
452
453 // Check that the rolled back internal values are consistent with the DB read out
454 uint256 out;
455 m_muhash.Finalize(out);
456 Assert(read_out.second.muhash == out);
457
458 Assert(m_transaction_output_count == read_out.second.transaction_output_count);
459 Assert(m_total_amount == read_out.second.total_amount);
460 Assert(m_bogo_size == read_out.second.bogo_size);
461 Assert(m_total_subsidy == read_out.second.total_subsidy);
462 Assert(m_block_unspendable_amount == read_out.second.block_unspendable_amount);
463 Assert(m_block_prevout_spent_amount == read_out.second.block_prevout_spent_amount);
464 Assert(m_block_new_outputs_ex_coinbase_amount == read_out.second.block_new_outputs_ex_coinbase_amount);
465 Assert(m_block_coinbase_amount == read_out.second.block_coinbase_amount);
466 Assert(m_unspendables_genesis_block == read_out.second.unspendables_genesis_block);
467 Assert(m_unspendables_bip30 == read_out.second.unspendables_bip30);
468 Assert(m_unspendables_scripts == read_out.second.unspendables_scripts);
469 Assert(m_unspendables_unclaimed_rewards == read_out.second.unspendables_unclaimed_rewards);
470
471 return m_db->Write(DB_MUHASH, m_muhash);
472 }
473