LCOV - code coverage report
Current view: top level - src/index - coinstatsindex.cpp (source / functions) Hit Total Coverage
Test: test_dash_coverage.info Lines: 135 289 46.7 %
Date: 2026-06-25 07:23:51 Functions: 18 27 66.7 %

          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 : }

Generated by: LCOV version 1.16