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 : 0 : 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 : 0 : SERIALIZE_METHODS(DBVal, obj)
45 : : {
46 : 0 : READWRITE(obj.muhash);
47 : 0 : READWRITE(obj.transaction_output_count);
48 : 0 : READWRITE(obj.bogo_size);
49 : 0 : READWRITE(obj.total_amount);
50 : 0 : READWRITE(obj.total_subsidy);
51 : 0 : READWRITE(obj.total_unspendable_amount);
52 : 0 : READWRITE(obj.total_prevout_spent_amount);
53 : 0 : READWRITE(obj.total_new_outputs_ex_coinbase_amount);
54 : 0 : READWRITE(obj.total_coinbase_amount);
55 : 0 : READWRITE(obj.total_unspendables_genesis_block);
56 : 0 : READWRITE(obj.total_unspendables_bip30);
57 : 0 : READWRITE(obj.total_unspendables_scripts);
58 : 0 : READWRITE(obj.total_unspendables_unclaimed_rewards);
59 : 0 : }
60 : : };
61 : :
62 : : struct DBHeightKey {
63 : : int height;
64 : :
65 : 0 : explicit DBHeightKey(int height_in) : height(height_in) {}
66 : :
67 : : template <typename Stream>
68 : 0 : void Serialize(Stream& s) const
69 : : {
70 : 0 : ser_writedata8(s, DB_BLOCK_HEIGHT);
71 : 0 : ser_writedata32be(s, height);
72 : 0 : }
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 : 0 : CoinStatsIndex::CoinStatsIndex(std::unique_ptr<interfaces::Chain> chain, size_t n_cache_size, bool f_memory, bool f_wipe)
107 [ # # ]: 0 : : BaseIndex(std::move(chain), "coinstatsindex")
108 : : {
109 [ # # # # ]: 0 : fs::path path{gArgs.GetDataDirNet() / "indexes" / "coinstats"};
110 [ # # ]: 0 : fs::create_directories(path);
111 : :
112 [ # # # # : 0 : m_db = std::make_unique<CoinStatsIndex::DB>(path / "db", n_cache_size, f_memory, f_wipe);
# # ]
113 : 0 : }
114 : :
115 : 0 : bool CoinStatsIndex::CustomAppend(const interfaces::BlockInfo& block)
116 : : {
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 : 0 : std::pair<uint256, DBVal> read_out;
123 [ # # ]: 0 : if (!m_db->Read(DBHeightKey(block.height - 1), read_out)) {
124 : : return false;
125 : : }
126 : :
127 : 0 : uint256 expected_block_hash{*Assert(block.prev_hash)};
128 [ # # ]: 0 : if (read_out.first != expected_block_hash) {
129 [ # # # # ]: 0 : LogPrintf("WARNING: previous block header belongs to unexpected block %s; expected %s\n",
130 : : read_out.first.ToString(), expected_block_hash.ToString());
131 : :
132 [ # # ]: 0 : if (!m_db->Read(DBHashKey(expected_block_hash), read_out)) {
133 [ # # ]: 0 : LogError("%s: previous block header not found; expected %s\n",
134 : : __func__, expected_block_hash.ToString());
135 : 0 : return false;
136 : : }
137 : : }
138 : :
139 : : // Add the new utxos created from the block
140 [ # # ]: 0 : assert(block.data);
141 [ # # ]: 0 : for (size_t i = 0; i < block.data->vtx.size(); ++i) {
142 : 0 : const auto& tx{block.data->vtx.at(i)};
143 : :
144 : : // Skip duplicate txid coinbase transactions (BIP30).
145 [ # # # # ]: 0 : if (IsBIP30Unspendable(block.hash, block.height) && tx->IsCoinBase()) {
146 : 0 : m_total_unspendable_amount += block_subsidy;
147 : 0 : m_total_unspendables_bip30 += block_subsidy;
148 : 0 : continue;
149 : : }
150 : :
151 [ # # ]: 0 : for (uint32_t j = 0; j < tx->vout.size(); ++j) {
152 : 0 : const CTxOut& out{tx->vout[j]};
153 : 0 : Coin coin{out, block.height, tx->IsCoinBase()};
154 [ # # ]: 0 : COutPoint outpoint{tx->GetHash(), j};
155 : :
156 : : // Skip unspendable coins
157 [ # # ]: 0 : if (coin.out.scriptPubKey.IsUnspendable()) {
158 : 0 : m_total_unspendable_amount += coin.out.nValue;
159 : 0 : m_total_unspendables_scripts += coin.out.nValue;
160 : 0 : continue;
161 : : }
162 : :
163 [ # # ]: 0 : ApplyCoinHash(m_muhash, outpoint, coin);
164 : :
165 [ # # ]: 0 : if (tx->IsCoinBase()) {
166 : 0 : m_total_coinbase_amount += coin.out.nValue;
167 : : } else {
168 : 0 : m_total_new_outputs_ex_coinbase_amount += coin.out.nValue;
169 : : }
170 : :
171 : 0 : ++m_transaction_output_count;
172 : 0 : m_total_amount += coin.out.nValue;
173 [ # # ]: 0 : m_bogo_size += GetBogoSize(coin.out.scriptPubKey);
174 : 0 : }
175 : :
176 : : // The coinbase tx has no undo data since no former output is spent
177 [ # # ]: 0 : if (!tx->IsCoinBase()) {
178 : 0 : const auto& tx_undo{Assert(block.undo_data)->vtxundo.at(i - 1)};
179 : :
180 [ # # ]: 0 : for (size_t j = 0; j < tx_undo.vprevout.size(); ++j) {
181 : 0 : Coin coin{tx_undo.vprevout[j]};
182 [ # # ]: 0 : COutPoint outpoint{tx->vin[j].prevout.hash, tx->vin[j].prevout.n};
183 : :
184 [ # # ]: 0 : RemoveCoinHash(m_muhash, outpoint, coin);
185 : :
186 : 0 : m_total_prevout_spent_amount += coin.out.nValue;
187 : :
188 : 0 : --m_transaction_output_count;
189 : 0 : m_total_amount -= coin.out.nValue;
190 [ # # ]: 0 : m_bogo_size -= GetBogoSize(coin.out.scriptPubKey);
191 : 0 : }
192 : : }
193 : : }
194 : : } else {
195 : : // genesis block
196 : 0 : m_total_unspendable_amount += block_subsidy;
197 : 0 : m_total_unspendables_genesis_block += block_subsidy;
198 : : }
199 : :
200 : : // If spent prevouts + block subsidy are still a higher amount than
201 : : // new outputs + coinbase + current unspendable amount this means
202 : : // the miner did not claim the full block reward. Unclaimed block
203 : : // rewards are also unspendable.
204 : 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)};
205 : 0 : m_total_unspendable_amount += unclaimed_rewards;
206 : 0 : m_total_unspendables_unclaimed_rewards += unclaimed_rewards;
207 : :
208 : 0 : std::pair<uint256, DBVal> value;
209 : 0 : value.first = block.hash;
210 : 0 : value.second.transaction_output_count = m_transaction_output_count;
211 : 0 : value.second.bogo_size = m_bogo_size;
212 : 0 : value.second.total_amount = m_total_amount;
213 : 0 : value.second.total_subsidy = m_total_subsidy;
214 : 0 : value.second.total_unspendable_amount = m_total_unspendable_amount;
215 : 0 : value.second.total_prevout_spent_amount = m_total_prevout_spent_amount;
216 : 0 : value.second.total_new_outputs_ex_coinbase_amount = m_total_new_outputs_ex_coinbase_amount;
217 : 0 : value.second.total_coinbase_amount = m_total_coinbase_amount;
218 : 0 : value.second.total_unspendables_genesis_block = m_total_unspendables_genesis_block;
219 : 0 : value.second.total_unspendables_bip30 = m_total_unspendables_bip30;
220 : 0 : value.second.total_unspendables_scripts = m_total_unspendables_scripts;
221 : 0 : value.second.total_unspendables_unclaimed_rewards = m_total_unspendables_unclaimed_rewards;
222 : :
223 : 0 : uint256 out;
224 : 0 : m_muhash.Finalize(out);
225 : 0 : value.second.muhash = out;
226 : :
227 : : // Intentionally do not update DB_MUHASH here so it stays in sync with
228 : : // DB_BEST_BLOCK, and the index is not corrupted if there is an unclean shutdown.
229 : 0 : return m_db->Write(DBHeightKey(block.height), value);
230 : : }
231 : :
232 : 0 : [[nodiscard]] static bool CopyHeightIndexToHashIndex(CDBIterator& db_it, CDBBatch& batch,
233 : : const std::string& index_name, int height)
234 : : {
235 : 0 : DBHeightKey key{height};
236 : 0 : db_it.Seek(key);
237 : :
238 [ # # # # ]: 0 : if (!db_it.GetKey(key) || key.height != height) {
239 : 0 : LogError("%s: unexpected key in %s: expected (%c, %d)\n",
240 : : __func__, index_name, DB_BLOCK_HEIGHT, height);
241 : 0 : return false;
242 : : }
243 : :
244 : 0 : std::pair<uint256, DBVal> value;
245 [ # # ]: 0 : if (!db_it.GetValue(value)) {
246 : 0 : LogError("%s: unable to read value in %s at key (%c, %d)\n",
247 : : __func__, index_name, DB_BLOCK_HEIGHT, height);
248 : 0 : return false;
249 : : }
250 : :
251 : 0 : batch.Write(DBHashKey(value.first), std::move(value.second));
252 : 0 : return true;
253 : : }
254 : :
255 : 0 : bool CoinStatsIndex::CustomRemove(const interfaces::BlockInfo& block)
256 : : {
257 : 0 : CDBBatch batch(*m_db);
258 [ # # # # ]: 0 : std::unique_ptr<CDBIterator> db_it(m_db->NewIterator());
259 : :
260 : : // During a reorg, copy the block's hash digest from the height index to the hash index,
261 : : // ensuring it's still accessible after the height index entry is overwritten.
262 [ # # # # ]: 0 : if (!CopyHeightIndexToHashIndex(*db_it, batch, m_name, block.height)) {
263 : : return false;
264 : : }
265 : :
266 [ # # # # ]: 0 : if (!m_db->WriteBatch(batch)) return false;
267 : :
268 [ # # # # ]: 0 : if (!ReverseBlock(block)) {
269 : 0 : return false; // failure cause logged internally
270 : : }
271 : :
272 : : return true;
273 : 0 : }
274 : :
275 : 0 : static bool LookUpOne(const CDBWrapper& db, const interfaces::BlockRef& block, DBVal& result)
276 : : {
277 : : // First check if the result is stored under the height index and the value
278 : : // there matches the block hash. This should be the case if the block is on
279 : : // the active chain.
280 : 0 : std::pair<uint256, DBVal> read_out;
281 [ # # ]: 0 : if (!db.Read(DBHeightKey(block.height), read_out)) {
282 : : return false;
283 : : }
284 [ # # ]: 0 : if (read_out.first == block.hash) {
285 : 0 : result = std::move(read_out.second);
286 : 0 : return true;
287 : : }
288 : :
289 : : // If value at the height index corresponds to an different block, the
290 : : // result will be stored in the hash index.
291 : 0 : return db.Read(DBHashKey(block.hash), result);
292 : : }
293 : :
294 : 0 : std::optional<CCoinsStats> CoinStatsIndex::LookUpStats(const CBlockIndex& block_index) const
295 : : {
296 : 0 : CCoinsStats stats{block_index.nHeight, block_index.GetBlockHash()};
297 : 0 : stats.index_used = true;
298 : :
299 : 0 : DBVal entry;
300 [ # # ]: 0 : if (!LookUpOne(*m_db, {block_index.GetBlockHash(), block_index.nHeight}, entry)) {
301 : 0 : return std::nullopt;
302 : : }
303 : :
304 : 0 : stats.hashSerialized = entry.muhash;
305 : 0 : stats.nTransactionOutputs = entry.transaction_output_count;
306 : 0 : stats.nBogoSize = entry.bogo_size;
307 : 0 : stats.total_amount = entry.total_amount;
308 : 0 : stats.total_subsidy = entry.total_subsidy;
309 : 0 : stats.total_unspendable_amount = entry.total_unspendable_amount;
310 : 0 : stats.total_prevout_spent_amount = entry.total_prevout_spent_amount;
311 : 0 : stats.total_new_outputs_ex_coinbase_amount = entry.total_new_outputs_ex_coinbase_amount;
312 : 0 : stats.total_coinbase_amount = entry.total_coinbase_amount;
313 : 0 : stats.total_unspendables_genesis_block = entry.total_unspendables_genesis_block;
314 : 0 : stats.total_unspendables_bip30 = entry.total_unspendables_bip30;
315 : 0 : stats.total_unspendables_scripts = entry.total_unspendables_scripts;
316 : 0 : stats.total_unspendables_unclaimed_rewards = entry.total_unspendables_unclaimed_rewards;
317 : :
318 : 0 : return stats;
319 : : }
320 : :
321 : 0 : bool CoinStatsIndex::CustomInit(const std::optional<interfaces::BlockRef>& block)
322 : : {
323 [ # # ]: 0 : if (!m_db->Read(DB_MUHASH, m_muhash)) {
324 : : // Check that the cause of the read failure is that the key does not
325 : : // exist. Any other errors indicate database corruption or a disk
326 : : // failure, and starting the index would cause further corruption.
327 [ # # ]: 0 : if (m_db->Exists(DB_MUHASH)) {
328 : 0 : LogError("%s: Cannot read current %s state; index may be corrupted\n",
329 : : __func__, GetName());
330 : 0 : return false;
331 : : }
332 : : }
333 : :
334 [ # # ]: 0 : if (block) {
335 : 0 : DBVal entry;
336 [ # # ]: 0 : if (!LookUpOne(*m_db, *block, entry)) {
337 : 0 : LogError("%s: Cannot read current %s state; index may be corrupted\n",
338 : : __func__, GetName());
339 : 0 : return false;
340 : : }
341 : :
342 : 0 : uint256 out;
343 : 0 : m_muhash.Finalize(out);
344 [ # # ]: 0 : if (entry.muhash != out) {
345 : 0 : LogError("%s: Cannot read current %s state; index may be corrupted\n",
346 : : __func__, GetName());
347 : 0 : return false;
348 : : }
349 : :
350 : 0 : m_transaction_output_count = entry.transaction_output_count;
351 : 0 : m_bogo_size = entry.bogo_size;
352 : 0 : m_total_amount = entry.total_amount;
353 : 0 : m_total_subsidy = entry.total_subsidy;
354 : 0 : m_total_unspendable_amount = entry.total_unspendable_amount;
355 : 0 : m_total_prevout_spent_amount = entry.total_prevout_spent_amount;
356 : 0 : m_total_new_outputs_ex_coinbase_amount = entry.total_new_outputs_ex_coinbase_amount;
357 : 0 : m_total_coinbase_amount = entry.total_coinbase_amount;
358 : 0 : m_total_unspendables_genesis_block = entry.total_unspendables_genesis_block;
359 : 0 : m_total_unspendables_bip30 = entry.total_unspendables_bip30;
360 : 0 : m_total_unspendables_scripts = entry.total_unspendables_scripts;
361 : 0 : m_total_unspendables_unclaimed_rewards = entry.total_unspendables_unclaimed_rewards;
362 : : }
363 : :
364 : : return true;
365 : : }
366 : :
367 : 0 : bool CoinStatsIndex::CustomCommit(CDBBatch& batch)
368 : : {
369 : : // DB_MUHASH should always be committed in a batch together with DB_BEST_BLOCK
370 : : // to prevent an inconsistent state of the DB.
371 : 0 : batch.Write(DB_MUHASH, m_muhash);
372 : 0 : return true;
373 : : }
374 : :
375 : 0 : interfaces::Chain::NotifyOptions CoinStatsIndex::CustomOptions()
376 : : {
377 : 0 : interfaces::Chain::NotifyOptions options;
378 : 0 : options.connect_undo_data = true;
379 : 0 : options.disconnect_data = true;
380 : 0 : options.disconnect_undo_data = true;
381 : 0 : return options;
382 : : }
383 : :
384 : : // Reverse a single block as part of a reorg
385 : 0 : bool CoinStatsIndex::ReverseBlock(const interfaces::BlockInfo& block)
386 : : {
387 : 0 : std::pair<uint256, DBVal> read_out;
388 : :
389 : 0 : const CAmount block_subsidy{GetBlockSubsidy(block.height, Params().GetConsensus())};
390 : 0 : m_total_subsidy -= block_subsidy;
391 : :
392 : : // Ignore genesis block
393 [ # # ]: 0 : if (block.height > 0) {
394 [ # # ]: 0 : if (!m_db->Read(DBHeightKey(block.height - 1), read_out)) {
395 : : return false;
396 : : }
397 : :
398 : 0 : uint256 expected_block_hash{*block.prev_hash};
399 [ # # ]: 0 : if (read_out.first != expected_block_hash) {
400 [ # # # # ]: 0 : LogPrintf("WARNING: previous block header belongs to unexpected block %s; expected %s\n",
401 : : read_out.first.ToString(), expected_block_hash.ToString());
402 : :
403 [ # # ]: 0 : if (!m_db->Read(DBHashKey(expected_block_hash), read_out)) {
404 [ # # ]: 0 : LogError("%s: previous block header not found; expected %s\n",
405 : : __func__, expected_block_hash.ToString());
406 : 0 : return false;
407 : : }
408 : : }
409 : : }
410 : :
411 : : // Remove the new UTXOs that were created from the block
412 [ # # ]: 0 : assert(block.data);
413 [ # # ]: 0 : assert(block.undo_data);
414 [ # # ]: 0 : for (size_t i = 0; i < block.data->vtx.size(); ++i) {
415 : 0 : const auto& tx{block.data->vtx.at(i)};
416 : :
417 [ # # ]: 0 : for (uint32_t j = 0; j < tx->vout.size(); ++j) {
418 : 0 : const CTxOut& out{tx->vout[j]};
419 : 0 : COutPoint outpoint{tx->GetHash(), j};
420 : 0 : Coin coin{out, block.height, tx->IsCoinBase()};
421 : :
422 : : // Skip unspendable coins
423 [ # # ]: 0 : if (coin.out.scriptPubKey.IsUnspendable()) {
424 : 0 : m_total_unspendable_amount -= coin.out.nValue;
425 : 0 : m_total_unspendables_scripts -= coin.out.nValue;
426 : 0 : continue;
427 : : }
428 : :
429 [ # # ]: 0 : RemoveCoinHash(m_muhash, outpoint, coin);
430 : :
431 [ # # ]: 0 : if (tx->IsCoinBase()) {
432 : 0 : m_total_coinbase_amount -= coin.out.nValue;
433 : : } else {
434 : 0 : m_total_new_outputs_ex_coinbase_amount -= coin.out.nValue;
435 : : }
436 : :
437 : 0 : --m_transaction_output_count;
438 : 0 : m_total_amount -= coin.out.nValue;
439 [ # # ]: 0 : m_bogo_size -= GetBogoSize(coin.out.scriptPubKey);
440 : 0 : }
441 : :
442 : : // The coinbase tx has no undo data since no former output is spent
443 [ # # ]: 0 : if (!tx->IsCoinBase()) {
444 : 0 : const auto& tx_undo{block.undo_data->vtxundo.at(i - 1)};
445 : :
446 [ # # ]: 0 : for (size_t j = 0; j < tx_undo.vprevout.size(); ++j) {
447 : 0 : Coin coin{tx_undo.vprevout[j]};
448 [ # # ]: 0 : COutPoint outpoint{tx->vin[j].prevout.hash, tx->vin[j].prevout.n};
449 : :
450 [ # # ]: 0 : ApplyCoinHash(m_muhash, outpoint, coin);
451 : :
452 : 0 : m_total_prevout_spent_amount -= coin.out.nValue;
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 : : }
460 : :
461 : 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)};
462 : 0 : m_total_unspendable_amount -= unclaimed_rewards;
463 : 0 : m_total_unspendables_unclaimed_rewards -= unclaimed_rewards;
464 : :
465 : : // Check that the rolled back internal values are consistent with the DB read out
466 : 0 : uint256 out;
467 : 0 : m_muhash.Finalize(out);
468 : 0 : Assert(read_out.second.muhash == out);
469 : :
470 : 0 : Assert(m_transaction_output_count == read_out.second.transaction_output_count);
471 : 0 : Assert(m_total_amount == read_out.second.total_amount);
472 : 0 : Assert(m_bogo_size == read_out.second.bogo_size);
473 : 0 : Assert(m_total_subsidy == read_out.second.total_subsidy);
474 : 0 : Assert(m_total_unspendable_amount == read_out.second.total_unspendable_amount);
475 : 0 : Assert(m_total_prevout_spent_amount == read_out.second.total_prevout_spent_amount);
476 : 0 : Assert(m_total_new_outputs_ex_coinbase_amount == read_out.second.total_new_outputs_ex_coinbase_amount);
477 : 0 : Assert(m_total_coinbase_amount == read_out.second.total_coinbase_amount);
478 : 0 : Assert(m_total_unspendables_genesis_block == read_out.second.total_unspendables_genesis_block);
479 : 0 : Assert(m_total_unspendables_bip30 == read_out.second.total_unspendables_bip30);
480 : 0 : Assert(m_total_unspendables_scripts == read_out.second.total_unspendables_scripts);
481 : 0 : Assert(m_total_unspendables_unclaimed_rewards == read_out.second.total_unspendables_unclaimed_rewards);
482 : :
483 : 0 : return true;
484 : : }
|