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