LCOV - code coverage report
Current view: top level - src/index - coinstatsindex.cpp (source / functions) Hit Total Coverage
Test: total_coverage.info Lines: 256 289 88.6 %
Date: 2026-06-25 07:23:43 Functions: 27 27 100.0 %

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

Generated by: LCOV version 1.16