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