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