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 7512 : SERIALIZE_METHODS(DBVal, obj)
46 : {
47 2504 : READWRITE(obj.muhash);
48 2504 : READWRITE(obj.transaction_output_count);
49 2504 : READWRITE(obj.bogo_size);
50 2504 : READWRITE(obj.total_amount);
51 2504 : READWRITE(obj.total_subsidy);
52 2504 : READWRITE(obj.total_unspendable_amount);
53 2504 : READWRITE(obj.total_prevout_spent_amount);
54 2504 : READWRITE(obj.total_new_outputs_ex_coinbase_amount);
55 2504 : READWRITE(obj.total_coinbase_amount);
56 2504 : READWRITE(obj.total_unspendables_genesis_block);
57 2504 : READWRITE(obj.total_unspendables_bip30);
58 2504 : READWRITE(obj.total_unspendables_scripts);
59 2504 : READWRITE(obj.total_unspendables_unclaimed_rewards);
60 2504 : }
61 : };
62 :
63 : struct DBHeightKey {
64 : int height;
65 :
66 4798 : explicit DBHeightKey(int height_in) : height(height_in) {}
67 :
68 : template <typename Stream>
69 2399 : void Serialize(Stream& s) const
70 : {
71 2399 : ser_writedata8(s, DB_BLOCK_HEIGHT);
72 2399 : ser_writedata32be(s, height);
73 2399 : }
74 :
75 : template <typename Stream>
76 68 : void Unserialize(Stream& s)
77 : {
78 68 : const uint8_t prefix{ser_readdata8(s)};
79 68 : if (prefix != DB_BLOCK_HEIGHT) {
80 0 : throw std::ios_base::failure("Invalid format for coinstatsindex DB height key");
81 : }
82 68 : height = ser_readdata32be(s);
83 68 : }
84 : };
85 :
86 : struct DBHashKey {
87 : uint256 block_hash;
88 :
89 144 : explicit DBHashKey(const uint256& hash_in) : block_hash(hash_in) {}
90 :
91 216 : SERIALIZE_METHODS(DBHashKey, obj)
92 : {
93 72 : uint8_t prefix{DB_BLOCK_HASH};
94 72 : READWRITE(prefix);
95 72 : if (prefix != DB_BLOCK_HASH) {
96 0 : throw std::ios_base::failure("Invalid format for coinstatsindex DB hash key");
97 : }
98 :
99 72 : READWRITE(obj.block_hash);
100 72 : }
101 : };
102 :
103 : }; // namespace
104 :
105 : std::unique_ptr<CoinStatsIndex> g_coin_stats_index;
106 :
107 99 : CoinStatsIndex::CoinStatsIndex(size_t n_cache_size, bool f_memory, bool f_wipe)
108 66 : {
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 33 : }
114 :
115 1113 : bool CoinStatsIndex::WriteBlock(const CBlock& block, const CBlockIndex* pindex)
116 : {
117 1113 : CBlockUndo block_undo;
118 1113 : const CAmount block_subsidy{GetBlockSubsidy(pindex, Params().GetConsensus())};
119 1113 : m_total_subsidy += block_subsidy;
120 :
121 : // Ignore genesis block
122 1113 : if (pindex->nHeight > 0) {
123 1103 : if (!UndoReadFromDisk(block_undo, pindex)) {
124 0 : return false;
125 : }
126 :
127 1103 : std::pair<uint256, DBVal> read_out;
128 1103 : if (!m_db->Read(DBHeightKey(pindex->nHeight - 1), read_out)) {
129 0 : return false;
130 : }
131 :
132 1103 : uint256 expected_block_hash{pindex->pprev->GetBlockHash()};
133 1103 : 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 2224 : for (size_t i = 0; i < block.vtx.size(); ++i) {
145 1121 : const auto& tx{block.vtx.at(i)};
146 :
147 : // Skip duplicate txid coinbase transactions (BIP30).
148 1121 : 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 2254 : for (uint32_t j = 0; j < tx->vout.size(); ++j) {
155 1133 : const CTxOut& out{tx->vout[j]};
156 1133 : Coin coin{out, pindex->nHeight, tx->IsCoinBase()};
157 1133 : COutPoint outpoint{tx->GetHash(), j};
158 :
159 : // Skip unspendable coins
160 1133 : if (coin.out.scriptPubKey.IsUnspendable()) {
161 6 : m_total_unspendable_amount += coin.out.nValue;
162 6 : m_total_unspendables_scripts += coin.out.nValue;
163 6 : continue;
164 : }
165 :
166 1127 : m_muhash.Insert(MakeUCharSpan(TxOutSer(outpoint, coin)));
167 :
168 1127 : if (tx->IsCoinBase()) {
169 1109 : m_total_coinbase_amount += coin.out.nValue;
170 1109 : } else {
171 18 : m_total_new_outputs_ex_coinbase_amount += coin.out.nValue;
172 : }
173 :
174 1127 : ++m_transaction_output_count;
175 1127 : m_total_amount += coin.out.nValue;
176 1127 : m_bogo_size += GetBogoSize(coin.out.scriptPubKey);
177 1133 : }
178 :
179 : // The coinbase tx has no undo data since no former output is spent
180 1121 : if (!tx->IsCoinBase()) {
181 18 : const auto& tx_undo{block_undo.vtxundo.at(i - 1)};
182 :
183 36 : for (size_t j = 0; j < tx_undo.vprevout.size(); ++j) {
184 18 : Coin coin{tx_undo.vprevout[j]};
185 18 : COutPoint outpoint{tx->vin[j].prevout.hash, tx->vin[j].prevout.n};
186 :
187 18 : m_muhash.Remove(MakeUCharSpan(TxOutSer(outpoint, coin)));
188 :
189 18 : m_total_prevout_spent_amount += coin.out.nValue;
190 :
191 18 : --m_transaction_output_count;
192 18 : m_total_amount -= coin.out.nValue;
193 18 : m_bogo_size -= GetBogoSize(coin.out.scriptPubKey);
194 18 : }
195 18 : }
196 1121 : }
197 1103 : } else {
198 : // genesis block
199 10 : m_total_unspendable_amount += block_subsidy;
200 10 : 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 1113 : 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 1113 : m_total_unspendable_amount += unclaimed_rewards;
209 1113 : m_total_unspendables_unclaimed_rewards += unclaimed_rewards;
210 :
211 1113 : std::pair<uint256, DBVal> value;
212 1113 : value.first = pindex->GetBlockHash();
213 1113 : value.second.transaction_output_count = m_transaction_output_count;
214 1113 : value.second.bogo_size = m_bogo_size;
215 1113 : value.second.total_amount = m_total_amount;
216 1113 : value.second.total_subsidy = m_total_subsidy;
217 1113 : value.second.total_unspendable_amount = m_total_unspendable_amount;
218 1113 : value.second.total_prevout_spent_amount = m_total_prevout_spent_amount;
219 1113 : value.second.total_new_outputs_ex_coinbase_amount = m_total_new_outputs_ex_coinbase_amount;
220 1113 : value.second.total_coinbase_amount = m_total_coinbase_amount;
221 1113 : value.second.total_unspendables_genesis_block = m_total_unspendables_genesis_block;
222 1113 : value.second.total_unspendables_bip30 = m_total_unspendables_bip30;
223 1113 : value.second.total_unspendables_scripts = m_total_unspendables_scripts;
224 1113 : value.second.total_unspendables_unclaimed_rewards = m_total_unspendables_unclaimed_rewards;
225 :
226 1113 : uint256 out;
227 1113 : m_muhash.Finalize(out);
228 1113 : 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 1113 : return m_db->Write(DBHeightKey(pindex->nHeight), value);
233 1113 : }
234 :
235 34 : [[nodiscard]] static bool CopyHeightIndexToHashIndex(CDBIterator& db_it, CDBBatch& batch,
236 : const std::string& index_name,
237 : int start_height, int stop_height)
238 : {
239 34 : DBHeightKey key{start_height};
240 34 : db_it.Seek(key);
241 :
242 102 : for (int height = start_height; height <= stop_height; ++height) {
243 68 : 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 68 : std::pair<uint256, DBVal> value;
249 68 : 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 68 : batch.Write(DBHashKey(value.first), std::move(value.second));
255 :
256 68 : db_it.Next();
257 68 : }
258 34 : return true;
259 34 : }
260 :
261 34 : bool CoinStatsIndex::Rewind(const CBlockIndex* current_tip, const CBlockIndex* new_tip)
262 : {
263 34 : assert(current_tip->GetAncestor(new_tip->nHeight) == new_tip);
264 :
265 34 : CDBBatch batch(*m_db);
266 34 : 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 34 : if (!CopyHeightIndexToHashIndex(*db_it, batch, m_name, new_tip->nHeight, current_tip->nHeight)) {
272 0 : return false;
273 : }
274 :
275 34 : if (!m_db->WriteBatch(batch)) return false;
276 :
277 : {
278 34 : LOCK(cs_main);
279 34 : const CBlockIndex* iter_tip{m_chainstate->m_blockman.LookupBlockIndex(current_tip->GetBlockHash())};
280 34 : const auto& consensus_params{Params().GetConsensus()};
281 :
282 34 : do {
283 34 : CBlock block;
284 :
285 34 : 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 34 : if (!ReverseBlock(block, iter_tip)) {
291 0 : return false; // failure cause logged internally
292 : }
293 :
294 34 : iter_tip = iter_tip->GetAncestor(iter_tip->nHeight - 1);
295 34 : } while (new_tip != iter_tip);
296 34 : }
297 :
298 34 : return BaseIndex::Rewind(current_tip, new_tip);
299 34 : }
300 :
301 115 : 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 115 : std::pair<uint256, DBVal> read_out;
307 115 : if (!db.Read(DBHeightKey(block_index->nHeight), read_out)) {
308 1 : return false;
309 : }
310 114 : if (read_out.first == block_index->GetBlockHash()) {
311 110 : result = std::move(read_out.second);
312 110 : 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 4 : return db.Read(DBHashKey(block_index->GetBlockHash()), result);
318 115 : }
319 :
320 92 : std::optional<CCoinsStats> CoinStatsIndex::LookUpStats(const CBlockIndex* block_index) const
321 : {
322 92 : CCoinsStats stats{Assert(block_index)->nHeight, block_index->GetBlockHash()};
323 92 : stats.index_used = true;
324 :
325 92 : DBVal entry;
326 92 : if (!LookUpOne(*m_db, block_index, entry)) {
327 1 : return std::nullopt;
328 : }
329 :
330 91 : stats.hashSerialized = entry.muhash;
331 91 : stats.nTransactionOutputs = entry.transaction_output_count;
332 91 : stats.nBogoSize = entry.bogo_size;
333 91 : stats.total_amount = entry.total_amount;
334 91 : stats.total_subsidy = entry.total_subsidy;
335 91 : stats.total_unspendable_amount = entry.total_unspendable_amount;
336 91 : stats.total_prevout_spent_amount = entry.total_prevout_spent_amount;
337 91 : stats.total_new_outputs_ex_coinbase_amount = entry.total_new_outputs_ex_coinbase_amount;
338 91 : stats.total_coinbase_amount = entry.total_coinbase_amount;
339 91 : stats.total_unspendables_genesis_block = entry.total_unspendables_genesis_block;
340 91 : stats.total_unspendables_bip30 = entry.total_unspendables_bip30;
341 91 : stats.total_unspendables_scripts = entry.total_unspendables_scripts;
342 91 : stats.total_unspendables_unclaimed_rewards = entry.total_unspendables_unclaimed_rewards;
343 :
344 91 : return stats;
345 92 : }
346 :
347 33 : bool CoinStatsIndex::Init()
348 : {
349 33 : 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 10 : 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 10 : }
358 :
359 33 : if (!BaseIndex::Init()) return false;
360 :
361 33 : const CBlockIndex* pindex{CurrentIndex()};
362 :
363 33 : if (pindex) {
364 23 : DBVal entry;
365 23 : 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 23 : uint256 out;
370 23 : m_muhash.Finalize(out);
371 23 : if (entry.muhash != out) {
372 0 : return error("%s: Cannot read current %s state; index may be corrupted",
373 0 : __func__, GetName());
374 : }
375 23 : m_transaction_output_count = entry.transaction_output_count;
376 23 : m_bogo_size = entry.bogo_size;
377 23 : m_total_amount = entry.total_amount;
378 23 : m_total_subsidy = entry.total_subsidy;
379 23 : m_total_unspendable_amount = entry.total_unspendable_amount;
380 23 : m_total_prevout_spent_amount = entry.total_prevout_spent_amount;
381 23 : m_total_new_outputs_ex_coinbase_amount = entry.total_new_outputs_ex_coinbase_amount;
382 23 : m_total_coinbase_amount = entry.total_coinbase_amount;
383 23 : m_total_unspendables_genesis_block = entry.total_unspendables_genesis_block;
384 23 : m_total_unspendables_bip30 = entry.total_unspendables_bip30;
385 23 : m_total_unspendables_scripts = entry.total_unspendables_scripts;
386 23 : m_total_unspendables_unclaimed_rewards = entry.total_unspendables_unclaimed_rewards;
387 23 : }
388 :
389 33 : return true;
390 33 : }
391 :
392 152 : 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 152 : batch.Write(DB_MUHASH, m_muhash);
397 152 : return BaseIndex::CommitInternal(batch);
398 : }
399 :
400 : // Reverse a single block as part of a reorg
401 34 : bool CoinStatsIndex::ReverseBlock(const CBlock& block, const CBlockIndex* pindex)
402 : {
403 34 : CBlockUndo block_undo;
404 34 : std::pair<uint256, DBVal> read_out;
405 :
406 34 : const CAmount block_subsidy{GetBlockSubsidy(pindex, Params().GetConsensus())};
407 34 : m_total_subsidy -= block_subsidy;
408 :
409 : // Ignore genesis block
410 34 : if (pindex->nHeight > 0) {
411 34 : if (!UndoReadFromDisk(block_undo, pindex)) {
412 0 : return false;
413 : }
414 :
415 34 : if (!m_db->Read(DBHeightKey(pindex->nHeight - 1), read_out)) {
416 0 : return false;
417 : }
418 :
419 34 : uint256 expected_block_hash{pindex->pprev->GetBlockHash()};
420 34 : 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 34 : }
430 :
431 : // Remove the new UTXOs that were created from the block
432 74 : for (size_t i = 0; i < block.vtx.size(); ++i) {
433 40 : const auto& tx{block.vtx.at(i)};
434 :
435 84 : for (uint32_t j = 0; j < tx->vout.size(); ++j) {
436 44 : const CTxOut& out{tx->vout[j]};
437 44 : COutPoint outpoint{tx->GetHash(), j};
438 44 : Coin coin{out, pindex->nHeight, tx->IsCoinBase()};
439 :
440 : // Skip unspendable coins
441 44 : if (coin.out.scriptPubKey.IsUnspendable()) {
442 2 : m_total_unspendable_amount -= coin.out.nValue;
443 2 : m_total_unspendables_scripts -= coin.out.nValue;
444 2 : continue;
445 : }
446 :
447 42 : m_muhash.Remove(MakeUCharSpan(TxOutSer(outpoint, coin)));
448 :
449 42 : if (tx->IsCoinBase()) {
450 36 : m_total_coinbase_amount -= coin.out.nValue;
451 36 : } else {
452 6 : m_total_new_outputs_ex_coinbase_amount -= coin.out.nValue;
453 : }
454 :
455 42 : --m_transaction_output_count;
456 42 : m_total_amount -= coin.out.nValue;
457 42 : m_bogo_size -= GetBogoSize(coin.out.scriptPubKey);
458 44 : }
459 :
460 : // The coinbase tx has no undo data since no former output is spent
461 40 : if (!tx->IsCoinBase()) {
462 6 : const auto& tx_undo{block_undo.vtxundo.at(i - 1)};
463 :
464 12 : for (size_t j = 0; j < tx_undo.vprevout.size(); ++j) {
465 6 : Coin coin{tx_undo.vprevout[j]};
466 6 : COutPoint outpoint{tx->vin[j].prevout.hash, tx->vin[j].prevout.n};
467 :
468 6 : m_muhash.Insert(MakeUCharSpan(TxOutSer(outpoint, coin)));
469 :
470 6 : m_total_prevout_spent_amount -= coin.out.nValue;
471 :
472 6 : m_transaction_output_count++;
473 6 : m_total_amount += coin.out.nValue;
474 6 : m_bogo_size += GetBogoSize(coin.out.scriptPubKey);
475 6 : }
476 6 : }
477 40 : }
478 :
479 34 : 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 34 : m_total_unspendable_amount -= unclaimed_rewards;
481 34 : m_total_unspendables_unclaimed_rewards -= unclaimed_rewards;
482 :
483 : // Check that the rolled back internal values are consistent with the DB read out
484 34 : uint256 out;
485 34 : m_muhash.Finalize(out);
486 34 : Assert(read_out.second.muhash == out);
487 :
488 34 : Assert(m_transaction_output_count == read_out.second.transaction_output_count);
489 34 : Assert(m_total_amount == read_out.second.total_amount);
490 34 : Assert(m_bogo_size == read_out.second.bogo_size);
491 34 : Assert(m_total_subsidy == read_out.second.total_subsidy);
492 34 : Assert(m_total_unspendable_amount == read_out.second.total_unspendable_amount);
493 34 : Assert(m_total_prevout_spent_amount == read_out.second.total_prevout_spent_amount);
494 34 : Assert(m_total_new_outputs_ex_coinbase_amount == read_out.second.total_new_outputs_ex_coinbase_amount);
495 34 : Assert(m_total_coinbase_amount == read_out.second.total_coinbase_amount);
496 34 : Assert(m_total_unspendables_genesis_block == read_out.second.total_unspendables_genesis_block);
497 34 : Assert(m_total_unspendables_bip30 == read_out.second.total_unspendables_bip30);
498 34 : Assert(m_total_unspendables_scripts == read_out.second.total_unspendables_scripts);
499 34 : Assert(m_total_unspendables_unclaimed_rewards == read_out.second.total_unspendables_unclaimed_rewards);
500 :
501 34 : return true;
502 34 : }
|