{"vulnerability": "CVE-2012-1909", "sightings": [{"uuid": "1d32c554-6467-4ac2-a206-ed5005cd437f", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2012-1909", "type": "seen", "source": "https://gist.github.com/LarryRuane/7a1ea41744a1068e069a91304ca29c4a", "content": "diff --git a/src/coins.cpp b/src/coins.cpp\nindex c403e006c8..fa829a267f 100644\n--- a/src/coins.cpp\n+++ b/src/coins.cpp\n@@ -1,409 +1,439 @@\n // Copyright (c) 2012-present The Bitcoin Core developers\n // Distributed under the MIT software license, see the accompanying\n // file COPYING or http://www.opensource.org/licenses/mit-license.php.\n \n #include \n \n #include \n #include \n #include \n #include \n #include \n \n TRACEPOINT_SEMAPHORE(utxocache, add);\n TRACEPOINT_SEMAPHORE(utxocache, spent);\n TRACEPOINT_SEMAPHORE(utxocache, uncache);\n \n CoinsViewEmpty&amp; CoinsViewEmpty::Get()\n {\n     static CoinsViewEmpty instance;\n     return instance;\n }\n \n std::optional CCoinsViewCache::PeekCoin(const COutPoint&amp; outpoint) const\n {\n     if (auto it{cacheCoins.find(outpoint)}; it != cacheCoins.end()) {\n-        return it-&gt;second.coin.IsSpent() ? std::nullopt : std::optional{it-&gt;second.coin};\n+        return it-&gt;second.coin ? std::optional{*it-&gt;second.coin} : std::nullopt;\n     }\n     return base-&gt;PeekCoin(outpoint);\n }\n \n CCoinsViewCache::CCoinsViewCache(CCoinsView* in_base, bool deterministic) :\n     CCoinsViewBacked(in_base), m_deterministic(deterministic),\n     cacheCoins(0, SaltedOutpointHasher(/*deterministic=*/deterministic), CCoinsMap::key_equal{}, &amp;m_cache_coins_memory_resource)\n {\n     m_sentinel.second.SelfRef(m_sentinel);\n }\n \n size_t CCoinsViewCache::DynamicMemoryUsage() const {\n     return memusage::DynamicUsage(cacheCoins) + cachedCoinsUsage;\n }\n \n+//! Move a coin into a cache entry, overwriting any existing coin.\n+void CCoinsViewCache::MoveCoin(CCoinsCacheEntry&amp; dest_entry, Coin&amp;&amp; source_coin) const\n+{\n+    if (dest_entry.coin) {\n+        // Deconstruct the existing unspent coin, but for efficiency use its existing memory for the new coin.\n+        Assume(TrySub(cachedCoinsUsage, dest_entry.coin-&gt;DynamicMemoryUsage()));\n+        dest_entry.coin-&gt;~Coin();\n+    } else {\n+        // Existing spent (non-)coin will now become the given unspent coin.\n+        dest_entry.coin = static_cast(m_cache_coins_memory_resource.Allocate(sizeof(Coin), alignof(Coin)));\n+    }\n+    new (dest_entry.coin) Coin(std::move(source_coin));\n+    cachedCoinsUsage += dest_entry.coin-&gt;DynamicMemoryUsage();\n+}\n+\n+//! Free (deallocate) a coin, this cache entry becomes spent (coin is nullptr).\n+void CCoinsViewCache::FreeCoin(CCoinsCacheEntry&amp; entry) const noexcept\n+{\n+    assert(entry.coin);\n+    Assume(TrySub(cachedCoinsUsage, entry.coin-&gt;DynamicMemoryUsage()));\n+    entry.coin-&gt;~Coin();\n+    m_cache_coins_memory_resource.Deallocate(entry.coin, sizeof(Coin), alignof(Coin));\n+    entry.coin = nullptr;\n+}\n+\n+void CCoinsViewCache::FreeAllCoins() const noexcept\n+{\n+    for (auto&amp; entry : cacheCoins) if (entry.second.coin) FreeCoin(entry.second);\n+}\n+\n std::optional CCoinsViewCache::FetchCoinFromBase(const COutPoint&amp; outpoint) const\n {\n     return base-&gt;GetCoin(outpoint);\n }\n \n CCoinsMap::iterator CCoinsViewCache::FetchCoin(const COutPoint &amp;outpoint) const {\n     const auto [ret, inserted] = cacheCoins.try_emplace(outpoint);\n     if (inserted) {\n         if (auto coin{FetchCoinFromBase(outpoint)}) {\n-            ret-&gt;second.coin = std::move(*coin);\n-            cachedCoinsUsage += ret-&gt;second.coin.DynamicMemoryUsage();\n-            Assert(!ret-&gt;second.coin.IsSpent());\n+            Assert(!coin.value().IsSpent());\n+            MoveCoin(ret-&gt;second, std::move(coin.value()));\n         } else {\n             cacheCoins.erase(ret);\n             return cacheCoins.end();\n         }\n     }\n     return ret;\n }\n \n std::optional CCoinsViewCache::GetCoin(const COutPoint&amp; outpoint) const\n {\n-    if (auto it{FetchCoin(outpoint)}; it != cacheCoins.end() &amp;&amp; !it-&gt;second.coin.IsSpent()) return it-&gt;second.coin;\n+    if (auto it{FetchCoin(outpoint)}; it != cacheCoins.end() &amp;&amp; it-&gt;second.coin &amp;&amp; !it-&gt;second.coin-&gt;IsSpent()) return *it-&gt;second.coin;\n     return std::nullopt;\n }\n \n void CCoinsViewCache::AddCoin(const COutPoint &amp;outpoint, Coin&amp;&amp; coin, bool possible_overwrite) {\n     assert(!coin.IsSpent());\n     if (coin.out.scriptPubKey.IsUnspendable()) return;\n     CCoinsMap::iterator it;\n     bool inserted;\n     std::tie(it, inserted) = cacheCoins.emplace(std::piecewise_construct, std::forward_as_tuple(outpoint), std::tuple&lt;&gt;());\n     bool fresh = false;\n     if (!possible_overwrite) {\n-        if (!it-&gt;second.coin.IsSpent()) {\n+        if (!it-&gt;second.IsSpent()) {\n             throw std::logic_error(\"Attempted to overwrite an unspent coin (when possible_overwrite is false)\");\n         }\n         // If the coin exists in this cache as a spent coin and is DIRTY, then\n         // its spentness hasn't been flushed to the parent cache. We're\n         // re-adding the coin to this cache now but we can't mark it as FRESH.\n         // If we mark it FRESH and then spend it before the cache is flushed\n         // we would remove it from this cache and would never flush spentness\n         // to the parent cache.\n         //\n         // Re-adding a spent coin can happen in the case of a re-org (the coin\n         // is 'spent' when the block adding it is disconnected and then\n         // re-added when it is also added in a newly connected block).\n         //\n         // If the coin doesn't exist in the current cache, or is spent but not\n         // DIRTY, then it can be marked FRESH.\n         fresh = !it-&gt;second.IsDirty();\n     }\n     if (!inserted) {\n         Assume(TrySub(m_dirty_count, it-&gt;second.IsDirty()));\n-        Assume(TrySub(cachedCoinsUsage, it-&gt;second.coin.DynamicMemoryUsage()));\n     }\n-    it-&gt;second.coin = std::move(coin);\n+    MoveCoin(it-&gt;second, std::move(coin));\n     CCoinsCacheEntry::SetDirty(*it, m_sentinel);\n     ++m_dirty_count;\n     if (fresh) CCoinsCacheEntry::SetFresh(*it, m_sentinel);\n-    cachedCoinsUsage += it-&gt;second.coin.DynamicMemoryUsage();\n     TRACEPOINT(utxocache, add,\n            outpoint.hash.data(),\n            (uint32_t)outpoint.n,\n-           (uint32_t)it-&gt;second.coin.nHeight,\n-           (int64_t)it-&gt;second.coin.out.nValue,\n-           (bool)it-&gt;second.coin.IsCoinBase());\n+           (uint32_t)it-&gt;second.coin-&gt;nHeight,\n+           (int64_t)it-&gt;second.coin-&gt;out.nValue,\n+           (bool)it-&gt;second.coin-&gt;IsCoinBase());\n }\n \n void CCoinsViewCache::EmplaceCoinInternalDANGER(COutPoint&amp;&amp; outpoint, Coin&amp;&amp; coin) {\n-    const auto mem_usage{coin.DynamicMemoryUsage()};\n-    auto [it, inserted] = cacheCoins.try_emplace(std::move(outpoint), std::move(coin));\n+    auto [it, inserted] = cacheCoins.try_emplace(std::move(outpoint));\n     if (inserted) {\n+        MoveCoin(it-&gt;second, std::move(coin));\n         CCoinsCacheEntry::SetDirty(*it, m_sentinel);\n         ++m_dirty_count;\n-        cachedCoinsUsage += mem_usage;\n     }\n }\n \n void AddCoins(CCoinsViewCache&amp; cache, const CTransaction &amp;tx, int nHeight, bool check_for_overwrite) {\n     bool fCoinbase = tx.IsCoinBase();\n     const Txid&amp; txid = tx.GetHash();\n     for (size_t i = 0; i &lt; tx.vout.size(); ++i) {\n         bool overwrite = check_for_overwrite ? cache.HaveCoin(COutPoint(txid, i)) : fCoinbase;\n         // Coinbase transactions can always be overwritten, in order to correctly\n         // deal with the pre-BIP30 occurrences of duplicate coinbase transactions.\n         cache.AddCoin(COutPoint(txid, i), Coin(tx.vout[i], nHeight, fCoinbase), overwrite);\n     }\n }\n \n bool CCoinsViewCache::SpendCoin(const COutPoint &amp;outpoint, Coin* moveout) {\n     CCoinsMap::iterator it = FetchCoin(outpoint);\n     if (it == cacheCoins.end()) return false;\n     Assume(TrySub(m_dirty_count, it-&gt;second.IsDirty()));\n-    Assume(TrySub(cachedCoinsUsage, it-&gt;second.coin.DynamicMemoryUsage()));\n     TRACEPOINT(utxocache, spent,\n            outpoint.hash.data(),\n            (uint32_t)outpoint.n,\n-           (uint32_t)it-&gt;second.coin.nHeight,\n-           (int64_t)it-&gt;second.coin.out.nValue,\n-           (bool)it-&gt;second.coin.IsCoinBase());\n+           (uint32_t)(it-&gt;second.coin ? it-&gt;second.coin-&gt;.nHeight : 0),\n+           (int64_t)(it-&gt;second.coin ? it-&gt;second.coin-&gt;.out.nValue : -1),\n+           (bool)(it-&gt;second.coin ? it-&gt;second.coin-&gt;IsCoinBase() : false));\n     if (moveout) {\n-        *moveout = std::move(it-&gt;second.coin);\n+        *moveout = std::move(it-&gt;second.coin ? *it-&gt;second.coin : Coin{});\n     }\n+    if (it-&gt;second.coin) FreeCoin(it-&gt;second);\n     if (it-&gt;second.IsFresh()) {\n         cacheCoins.erase(it);\n     } else {\n         CCoinsCacheEntry::SetDirty(*it, m_sentinel);\n         ++m_dirty_count;\n-        it-&gt;second.coin.Clear();\n     }\n     return true;\n }\n \n static const Coin coinEmpty;\n \n const Coin&amp; CCoinsViewCache::AccessCoin(const COutPoint &amp;outpoint) const {\n     CCoinsMap::const_iterator it = FetchCoin(outpoint);\n-    if (it == cacheCoins.end()) {\n+    if (it == cacheCoins.end() || !it-&gt;second.coin) {\n         return coinEmpty;\n     } else {\n-        return it-&gt;second.coin;\n+        return *it-&gt;second.coin;\n     }\n }\n \n bool CCoinsViewCache::HaveCoin(const COutPoint&amp; outpoint) const\n {\n     CCoinsMap::const_iterator it = FetchCoin(outpoint);\n-    return (it != cacheCoins.end() &amp;&amp; !it-&gt;second.coin.IsSpent());\n+    return it != cacheCoins.end() &amp;&amp; !it-&gt;second.IsSpent();\n }\n \n bool CCoinsViewCache::HaveCoinInCache(const COutPoint &amp;outpoint) const {\n     CCoinsMap::const_iterator it = cacheCoins.find(outpoint);\n-    return (it != cacheCoins.end() &amp;&amp; !it-&gt;second.coin.IsSpent());\n+    return it != cacheCoins.end() &amp;&amp; !it-&gt;second.IsSpent();\n }\n \n uint256 CCoinsViewCache::GetBestBlock() const {\n     if (m_block_hash.IsNull())\n         m_block_hash = base-&gt;GetBestBlock();\n     return m_block_hash;\n }\n \n void CCoinsViewCache::SetBestBlock(const uint256&amp; in_block_hash)\n {\n     m_block_hash = in_block_hash;\n }\n \n void CCoinsViewCache::BatchWrite(CoinsViewCacheCursor&amp; cursor, const uint256&amp; in_block_hash)\n {\n     for (auto it{cursor.Begin()}; it != cursor.End(); it = cursor.NextAndMaybeErase(*it)) {\n+        CCoinsCacheEntry&amp; source_entry{it-&gt;second};\n         if (!it-&gt;second.IsDirty()) { // TODO a cursor can only contain dirty entries\n             continue;\n         }\n         auto [itUs, inserted]{cacheCoins.try_emplace(it-&gt;first)};\n+        CCoinsCacheEntry&amp; dest_entry{itUs-&gt;second};\n+        const auto MoveOrCopyCoin{[this, &amp;cursor, &amp;source_entry, &amp;dest_entry]() -&gt; void {\n+            if (cursor.WillClear()) {\n+                // Since this entry will be erased,\n+                // we can move the coin into us instead of copying it\n+                MoveCoin(dest_entry, std::move(*source_entry.coin));\n+                source_entry.coin = nullptr;\n+            } else {\n+                // Make a full copy, leaving the existing coin unchanged.\n+                // Note that if source_entry.coin is nullptr (spent), dest_entry\n+                // is already initialized correctly as a spent coin.\n+                Coin source_coin(*source_entry.coin);\n+                MoveCoin(dest_entry, std::move(source_coin));\n+            }\n+        }};\n         if (inserted) {\n-            if (it-&gt;second.IsFresh() &amp;&amp; it-&gt;second.coin.IsSpent()) {\n+            if (source_entry.IsFresh() &amp;&amp; source_entry.IsSpent()) {\n+                if (dest_entry.coin) FreeCoin(dest_entry);\n                 cacheCoins.erase(itUs); // TODO fresh coins should have been removed at spend\n             } else {\n                 // The parent cache does not have an entry, while the child cache does.\n                 // Move the data up and mark it as dirty.\n-                CCoinsCacheEntry&amp; entry{itUs-&gt;second};\n-                assert(entry.coin.DynamicMemoryUsage() == 0);\n-                if (cursor.WillErase(*it)) {\n-                    // Since this entry will be erased,\n-                    // we can move the coin into us instead of copying it\n-                    entry.coin = std::move(it-&gt;second.coin);\n-                } else {\n-                    entry.coin = it-&gt;second.coin;\n-                }\n+                if (source_entry.coin) MoveOrCopyCoin();\n                 CCoinsCacheEntry::SetDirty(*itUs, m_sentinel);\n                 ++m_dirty_count;\n-                cachedCoinsUsage += entry.coin.DynamicMemoryUsage();\n                 // We can mark it FRESH in the parent if it was FRESH in the child\n                 // Otherwise it might have just been flushed from the parent's cache\n                 // and already exist in the grandparent\n-                if (it-&gt;second.IsFresh()) CCoinsCacheEntry::SetFresh(*itUs, m_sentinel);\n+                if (source_entry.IsFresh()) CCoinsCacheEntry::SetFresh(*itUs, m_sentinel);\n             }\n         } else {\n             // Found the entry in the parent cache\n-            if (it-&gt;second.IsFresh() &amp;&amp; !itUs-&gt;second.coin.IsSpent()) {\n+            if (source_entry.IsFresh() &amp;&amp; !dest_entry.IsSpent()) {\n                 // The coin was marked FRESH in the child cache, but the coin\n                 // exists in the parent cache. If this ever happens, it means\n                 // the FRESH flag was misapplied and there is a logic error in\n                 // the calling code.\n                 throw std::logic_error(\"FRESH flag misapplied to coin that exists in parent cache\");\n             }\n \n-            if (itUs-&gt;second.IsFresh() &amp;&amp; it-&gt;second.coin.IsSpent()) {\n+            if (dest_entry.IsFresh() &amp;&amp; source_entry.IsSpent()) {\n                 // The grandparent cache does not have an entry, and the coin\n                 // has been spent. We can just delete it from the parent cache.\n-                Assume(TrySub(m_dirty_count, itUs-&gt;second.IsDirty()));\n-                Assume(TrySub(cachedCoinsUsage, itUs-&gt;second.coin.DynamicMemoryUsage()));\n+                Assume(TrySub(m_dirty_count, dest_entry.IsDirty()));\n+                if (dest_entry.coin) FreeCoin(dest_entry);\n                 cacheCoins.erase(itUs);\n             } else {\n                 // A normal modification.\n-                Assume(TrySub(cachedCoinsUsage, itUs-&gt;second.coin.DynamicMemoryUsage()));\n-                if (cursor.WillErase(*it)) {\n-                    // Since this entry will be erased,\n-                    // we can move the coin into us instead of copying it\n-                    itUs-&gt;second.coin = std::move(it-&gt;second.coin);\n-                } else {\n-                    itUs-&gt;second.coin = it-&gt;second.coin;\n-                }\n-                cachedCoinsUsage += itUs-&gt;second.coin.DynamicMemoryUsage();\n-                if (!itUs-&gt;second.IsDirty()) {\n+                if (source_entry.coin) {\n+                    MoveOrCopyCoin();\n+                } else if (dest_entry.coin) FreeCoin(dest_entry);\n+\n+                if (!dest_entry.IsDirty()) {\n                     CCoinsCacheEntry::SetDirty(*itUs, m_sentinel);\n                     ++m_dirty_count;\n                 }\n                 // NOTE: It isn't safe to mark the coin as FRESH in the parent\n                 // cache. If it already existed and was spent in the parent\n                 // cache then marking it FRESH would prevent that spentness\n                 // from being flushed to the grandparent.\n             }\n         }\n     }\n     SetBestBlock(in_block_hash);\n }\n \n void CCoinsViewCache::Flush(bool reallocate_cache)\n {\n-    auto cursor{CoinsViewCacheCursor(m_dirty_count, m_sentinel, cacheCoins, /*will_erase=*/true)};\n+    auto cursor{CoinsViewCacheCursor(m_dirty_count, m_sentinel, cacheCoins, m_cache_coins_memory_resource, /*will_erase=*/true)};\n     base-&gt;BatchWrite(cursor, m_block_hash);\n     Assume(m_dirty_count == 0);\n+    FreeAllCoins();\n     cacheCoins.clear();\n     if (reallocate_cache) {\n         ReallocateCache();\n     }\n     cachedCoinsUsage = 0;\n }\n \n void CCoinsViewCache::Sync()\n {\n-    auto cursor{CoinsViewCacheCursor(m_dirty_count, m_sentinel, cacheCoins, /*will_erase=*/false)};\n+    auto cursor{CoinsViewCacheCursor(m_dirty_count, m_sentinel, cacheCoins, m_cache_coins_memory_resource, /*will_erase=*/false)};\n     base-&gt;BatchWrite(cursor, m_block_hash);\n     Assume(m_dirty_count == 0);\n     if (m_sentinel.second.Next() != &amp;m_sentinel) {\n         /* BatchWrite must clear flags of all entries */\n         throw std::logic_error(\"Not all unspent flagged entries were cleared\");\n     }\n }\n \n void CCoinsViewCache::Reset() noexcept\n {\n+    FreeAllCoins();\n     cacheCoins.clear();\n     cachedCoinsUsage = 0;\n     m_dirty_count = 0;\n     SetBestBlock(uint256::ZERO);\n }\n \n void CCoinsViewCache::Uncache(const COutPoint&amp; hash)\n {\n     CCoinsMap::iterator it = cacheCoins.find(hash);\n     if (it != cacheCoins.end() &amp;&amp; !it-&gt;second.IsDirty()) {\n-        Assume(TrySub(cachedCoinsUsage, it-&gt;second.coin.DynamicMemoryUsage()));\n         TRACEPOINT(utxocache, uncache,\n                hash.hash.data(),\n                (uint32_t)hash.n,\n-               (uint32_t)it-&gt;second.coin.nHeight,\n-               (int64_t)it-&gt;second.coin.out.nValue,\n-               (bool)it-&gt;second.coin.IsCoinBase());\n+               (uint32_t)(it-&gt;second.coin ? it-&gt;second.coin.nHeight : 0),\n+               (int64_t)(it-&gt;second.coin ? it-&gt;second.coin-&gt;out.nValue : -1),\n+               (bool)(it-&gt;second.coin ? it-&gt;second.coin-&gt;IsCoinBase() : false));\n+        if (it-&gt;second.coin) FreeCoin(it-&gt;second);\n         cacheCoins.erase(it);\n     }\n }\n \n unsigned int CCoinsViewCache::GetCacheSize() const {\n     return cacheCoins.size();\n }\n \n bool CCoinsViewCache::HaveInputs(const CTransaction&amp; tx) const\n {\n     if (!tx.IsCoinBase()) {\n         for (unsigned int i = 0; i &lt; tx.vin.size(); i++) {\n             if (!HaveCoin(tx.vin[i].prevout)) {\n                 return false;\n             }\n         }\n     }\n     return true;\n }\n \n void CCoinsViewCache::ReallocateCache()\n {\n     // Cache should be empty when we're calling this.\n     assert(cacheCoins.size() == 0);\n     cacheCoins.~CCoinsMap();\n     m_cache_coins_memory_resource.~CCoinsMapMemoryResource();\n     ::new (&amp;m_cache_coins_memory_resource) CCoinsMapMemoryResource{};\n     ::new (&amp;cacheCoins) CCoinsMap{0, SaltedOutpointHasher{/*deterministic=*/m_deterministic}, CCoinsMap::key_equal{}, &amp;m_cache_coins_memory_resource};\n }\n \n void CCoinsViewCache::SanityCheck() const\n {\n     size_t recomputed_usage = 0;\n     size_t count_dirty = 0;\n     for (const auto&amp; [_, entry] : cacheCoins) {\n-        if (entry.coin.IsSpent()) {\n+        if (entry.IsSpent()) {\n             assert(entry.IsDirty() &amp;&amp; !entry.IsFresh()); // A spent coin must be dirty and cannot be fresh\n         } else {\n             assert(entry.IsDirty() || !entry.IsFresh()); // An unspent coin must not be fresh if not dirty\n         }\n \n         // Recompute cachedCoinsUsage.\n-        recomputed_usage += entry.coin.DynamicMemoryUsage();\n+        if (entry.coin) recomputed_usage += entry.coin-&gt;DynamicMemoryUsage();\n \n         // Count the number of entries we expect in the linked list.\n         if (entry.IsDirty()) ++count_dirty;\n     }\n     // Iterate over the linked list of flagged entries.\n     size_t count_linked = 0;\n     for (auto it = m_sentinel.second.Next(); it != &amp;m_sentinel; it = it-&gt;second.Next()) {\n         // Verify linked list integrity.\n         assert(it-&gt;second.Next()-&gt;second.Prev() == it);\n         assert(it-&gt;second.Prev()-&gt;second.Next() == it);\n         // Verify they are actually flagged.\n         assert(it-&gt;second.IsDirty());\n         // Count the number of entries actually in the list.\n         ++count_linked;\n     }\n     assert(count_dirty == count_linked &amp;&amp; count_dirty == m_dirty_count);\n     assert(recomputed_usage == cachedCoinsUsage);\n }\n \n static const uint64_t MIN_TRANSACTION_OUTPUT_WEIGHT{WITNESS_SCALE_FACTOR * ::GetSerializeSize(CTxOut())};\n static const uint64_t MAX_OUTPUTS_PER_BLOCK{MAX_BLOCK_WEIGHT / MIN_TRANSACTION_OUTPUT_WEIGHT};\n \n const Coin&amp; AccessByTxid(const CCoinsViewCache&amp; view, const Txid&amp; txid)\n {\n     COutPoint iter(txid, 0);\n     while (iter.n &lt; MAX_OUTPUTS_PER_BLOCK) {\n         const Coin&amp; alternate = view.AccessCoin(iter);\n         if (!alternate.IsSpent()) return alternate;\n         ++iter.n;\n     }\n     return coinEmpty;\n }\n \n template \n static ReturnType ExecuteBackedWrapper(Func func, const std::vector&gt;&amp; err_callbacks)\n {\n     try {\n         return func();\n     } catch(const std::runtime_error&amp; e) {\n         for (const auto&amp; f : err_callbacks) {\n             f();\n         }\n         LogError(\"Error reading from database: %s\\n\", e.what());\n         // Starting the shutdown sequence and returning false to the caller would be\n         // interpreted as 'entry not found' (as opposed to unable to read data), and\n         // could lead to invalid interpretation. Just exit immediately, as we can't\n         // continue anyway, and all writes should be atomic.\n         std::abort();\n     }\n }\n \n std::optional CCoinsViewErrorCatcher::GetCoin(const COutPoint&amp; outpoint) const\n {\n     return ExecuteBackedWrapper&gt;([&amp;]() { return CCoinsViewBacked::GetCoin(outpoint); }, m_err_callbacks);\n }\n \n bool CCoinsViewErrorCatcher::HaveCoin(const COutPoint&amp; outpoint) const\n {\n     return ExecuteBackedWrapper([&amp;]() { return CCoinsViewBacked::HaveCoin(outpoint); }, m_err_callbacks);\n }\n \n std::optional CCoinsViewErrorCatcher::PeekCoin(const COutPoint&amp; outpoint) const\n {\n     return ExecuteBackedWrapper&gt;([&amp;]() { return CCoinsViewBacked::PeekCoin(outpoint); }, m_err_callbacks);\n }\ndiff --git a/src/coins.h b/src/coins.h\nindex ae7f34f465..135ff5381d 100644\n--- a/src/coins.h\n+++ b/src/coins.h\n@@ -1,616 +1,665 @@\n // Copyright (c) 2009-2010 Satoshi Nakamoto\n // Copyright (c) 2009-present The Bitcoin Core developers\n // Distributed under the MIT software license, see the accompanying\n // file COPYING or http://www.opensource.org/licenses/mit-license.php.\n \n #ifndef BITCOIN_COINS_H\n #define BITCOIN_COINS_H\n \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n \n #include \n #include \n \n #include \n #include \n \n /**\n  * A UTXO entry.\n  *\n  * Serialized format:\n  * - VARINT((height &lt;&lt; 1) | (coinbase ? 1 : 0))\n  * - the non-spent CTxOut (via TxOutCompression)\n  */\n class Coin\n {\n public:\n     //! unspent transaction output\n     CTxOut out;\n \n     //! whether containing transaction was a coinbase\n     bool fCoinBase : 1;\n \n     //! at which height this containing transaction was included in the active block chain\n     uint32_t nHeight : 31;\n \n     //! construct a Coin from a CTxOut and height/coinbase information.\n     Coin(CTxOut&amp;&amp; outIn, int nHeightIn, bool fCoinBaseIn) : out(std::move(outIn)), fCoinBase(fCoinBaseIn), nHeight(nHeightIn) {}\n     Coin(const CTxOut&amp; outIn, int nHeightIn, bool fCoinBaseIn) : out(outIn), fCoinBase(fCoinBaseIn),nHeight(nHeightIn) {}\n \n     void Clear() {\n         out.SetNull();\n         fCoinBase = false;\n         nHeight = 0;\n     }\n \n     //! empty constructor\n     Coin() : fCoinBase(false), nHeight(0) { }\n \n     bool IsCoinBase() const {\n         return fCoinBase;\n     }\n \n     template\n     void Serialize(Stream &amp;s) const {\n         assert(!IsSpent());\n         uint32_t code{(uint32_t{nHeight} &lt;&lt; 1) | uint32_t{fCoinBase}};\n         ::Serialize(s, VARINT(code));\n         ::Serialize(s, Using(out));\n     }\n \n     template\n     void Unserialize(Stream &amp;s) {\n         uint32_t code = 0;\n         ::Unserialize(s, VARINT(code));\n         nHeight = code &gt;&gt; 1;\n         fCoinBase = code &amp; 1;\n         ::Unserialize(s, Using(out));\n     }\n \n     /** Either this coin never existed (see e.g. coinEmpty in coins.cpp), or it\n       * did exist and has been spent.\n       */\n     bool IsSpent() const {\n         return out.IsNull();\n     }\n \n     size_t DynamicMemoryUsage() const {\n         return memusage::DynamicUsage(out.scriptPubKey);\n     }\n };\n \n struct CCoinsCacheEntry;\n using CoinsCachePair = std::pair;\n \n /**\n  * A Coin in one level of the coins database caching hierarchy.\n  *\n  * A coin can either be:\n  * - unspent or spent (in which case the Coin object will be nulled out - see Coin.Clear())\n  * - DIRTY or not DIRTY\n  * - FRESH or not FRESH\n  *\n  * Out of these 2^3 = 8 states, only some combinations are valid:\n  * - unspent, FRESH, DIRTY (e.g. a new coin created in the cache)\n  * - unspent, not FRESH, DIRTY (e.g. a coin changed in the cache during a reorg)\n  * - unspent, not FRESH, not DIRTY (e.g. an unspent coin fetched from the parent cache)\n  * - spent, not FRESH, DIRTY (e.g. a coin is spent and spentness needs to be flushed to the parent)\n  */\n struct CCoinsCacheEntry\n {\n private:\n     /**\n      * These are used to create a doubly linked list of flagged entries.\n      * They are set in SetDirty, SetFresh, and unset in SetClean.\n      * A flagged entry is any entry that is either DIRTY, FRESH, or both.\n      *\n      * DIRTY entries are tracked so that only modified entries can be passed to\n      * the parent cache for batch writing. This is a performance optimization\n      * compared to giving all entries in the cache to the parent and having the\n      * parent scan for only modified entries.\n      */\n     CoinsCachePair* m_prev{nullptr};\n     CoinsCachePair* m_next{nullptr};\n     uint8_t m_flags{0};\n \n     //! Adding a flag requires a reference to the sentinel of the flagged pair linked list.\n     static void AddFlags(uint8_t flags, CoinsCachePair&amp; pair, CoinsCachePair&amp; sentinel) noexcept\n     {\n         Assume(flags &amp; (DIRTY | FRESH));\n         if (!pair.second.m_flags) {\n             Assume(!pair.second.m_prev &amp;&amp; !pair.second.m_next);\n             pair.second.m_prev = sentinel.second.m_prev;\n             pair.second.m_next = &amp;sentinel;\n             sentinel.second.m_prev = &amp;pair;\n             pair.second.m_prev-&gt;second.m_next = &amp;pair;\n         }\n         Assume(pair.second.m_prev &amp;&amp; pair.second.m_next);\n         pair.second.m_flags |= flags;\n     }\n \n public:\n-    Coin coin; // The actual cached data.\n+    /**\n+     * Pointer to the actual coin, pool-allocated, nullptr if spent (or freshly initialized).\n+     * */\n+    Coin* coin{nullptr};\n \n     enum Flags {\n         /**\n          * DIRTY means the CCoinsCacheEntry is potentially different from the\n          * version in the parent cache. Failure to mark a coin as DIRTY when\n          * it is potentially different from the parent cache will cause a\n          * consensus failure, since the coin's state won't get written to the\n          * parent when the cache is flushed.\n          */\n         DIRTY = (1 &lt;&lt; 0),\n         /**\n          * FRESH means the parent cache does not have this coin or that it is a\n          * spent coin in the parent cache. If a FRESH coin in the cache is\n          * later spent, it can be deleted entirely and doesn't ever need to be\n          * flushed to the parent. This is a performance optimization. Marking a\n          * coin as FRESH when it exists unspent in the parent cache will cause a\n          * consensus failure, since it might not be deleted from the parent\n          * when this cache is flushed.\n          */\n         FRESH = (1 &lt;&lt; 1),\n     };\n \n     CCoinsCacheEntry() noexcept = default;\n-    explicit CCoinsCacheEntry(Coin&amp;&amp; coin_) noexcept : coin(std::move(coin_)) {}\n     ~CCoinsCacheEntry()\n     {\n+        Assume(coin == nullptr);\n+        if (coin) coin-&gt;~Coin();\n         SetClean();\n     }\n+    // copying an entry would copy the coin pointer!\n+    CCoinsCacheEntry(const CCoinsCacheEntry&amp;) = delete;\n+\n+    // Move Constructor\n+    CCoinsCacheEntry(CCoinsCacheEntry&amp;&amp; other) noexcept \n+        : m_flags(other.m_flags), coin(other.coin)\n+    {\n+        other.coin = nullptr; // Ensure the source is nulled\n+    }\n+\n+    // Move Assignment\n+    CCoinsCacheEntry&amp; operator=(CCoinsCacheEntry&amp;&amp; other) noexcept\n+    {\n+        if (this != &amp;other) {\n+            Assume(!coin);\n+            coin = other.coin;\n+            m_flags = other.m_flags;\n+            other.coin = nullptr;\n+        }\n+        return *this;\n+    }\n \n     static void SetDirty(CoinsCachePair&amp; pair, CoinsCachePair&amp; sentinel) noexcept { AddFlags(DIRTY, pair, sentinel); }\n     static void SetFresh(CoinsCachePair&amp; pair, CoinsCachePair&amp; sentinel) noexcept { AddFlags(FRESH, pair, sentinel); }\n \n     void SetClean() noexcept\n     {\n         if (!m_flags) return;\n         m_next-&gt;second.m_prev = m_prev;\n         m_prev-&gt;second.m_next = m_next;\n         m_flags = 0;\n         m_prev = m_next = nullptr;\n     }\n     bool IsDirty() const noexcept { return m_flags &amp; DIRTY; }\n     bool IsFresh() const noexcept { return m_flags &amp; FRESH; }\n \n+    // It's acceptable for higher-level code to directly test if coin is nullptr.\n+    bool IsSpent() const noexcept\n+    {\n+        return !coin || coin-&gt;IsSpent();\n+    }\n+\n     //! Only call Next when this entry is DIRTY, FRESH, or both\n     CoinsCachePair* Next() const noexcept\n     {\n         Assume(m_flags);\n         return m_next;\n     }\n \n     //! Only call Prev when this entry is DIRTY, FRESH, or both\n     CoinsCachePair* Prev() const noexcept\n     {\n         Assume(m_flags);\n         return m_prev;\n     }\n \n     //! Only use this for initializing the linked list sentinel\n     void SelfRef(CoinsCachePair&amp; pair) noexcept\n     {\n         Assume(&amp;pair.second == this);\n         m_prev = &amp;pair;\n         m_next = &amp;pair;\n         // Set sentinel to DIRTY so we can call Next on it\n         m_flags = DIRTY;\n     }\n };\n \n /**\n  * PoolAllocator's MAX_BLOCK_SIZE_BYTES parameter here uses sizeof the data, and adds the size\n  * of 4 pointers. We do not know the exact node size used in the std::unordered_node implementation\n  * because it is implementation defined. Most implementations have an overhead of 1 or 2 pointers,\n  * so nodes can be connected in a linked list, and in some cases the hash value is stored as well.\n  * Using an additional sizeof(void*)*4 for MAX_BLOCK_SIZE_BYTES should thus be sufficient so that\n  * all implementations can allocate the nodes from the PoolAllocator.\n  */\n using CCoinsMap = std::unordered_map,\n                                      PoolAllocator&gt;;\n \n using CCoinsMapMemoryResource = CCoinsMap::allocator_type::ResourceType;\n \n /** Cursor for iterating over CoinsView state */\n class CCoinsViewCursor\n {\n public:\n     CCoinsViewCursor(const uint256&amp; in_block_hash) : block_hash(in_block_hash) {}\n     virtual ~CCoinsViewCursor() = default;\n \n     virtual bool GetKey(COutPoint &amp;key) const = 0;\n     virtual bool GetValue(Coin &amp;coin) const = 0;\n \n     virtual bool Valid() const = 0;\n     virtual void Next() = 0;\n \n     //! Get best block at the time this cursor was created\n     const uint256&amp; GetBestBlock() const { return block_hash; }\n private:\n     uint256 block_hash;\n };\n \n /**\n  * Cursor for iterating over the linked list of flagged entries in CCoinsViewCache.\n  *\n  * This is a helper struct to encapsulate the diverging logic between a non-erasing\n  * CCoinsViewCache::Sync and an erasing CCoinsViewCache::Flush. This allows the receiver\n  * of CCoinsView::BatchWrite to iterate through the flagged entries without knowing\n  * the caller's intent.\n  *\n- * However, the receiver can still call CoinsViewCacheCursor::WillErase to see if the\n+ * However, the receiver can still call CoinsViewCacheCursor::WillClear to see if the\n  * caller will erase the entry after BatchWrite returns. If so, the receiver can\n  * perform optimizations such as moving the coin out of the CCoinsCachEntry instead\n  * of copying it.\n  */\n struct CoinsViewCacheCursor\n {\n-    //! If will_erase is not set, iterating through the cursor will erase spent coins from the map,\n+    //! If will_clear is not set, iterating through the cursor will erase spent coins from the map,\n     //! and other coins will be unflagged (removing them from the linked list).\n-    //! If will_erase is set, the underlying map and linked list will not be modified,\n+    //! If will_clear is set, the underlying map and linked list will not be modified,\n     //! as the caller is expected to wipe the entire map anyway.\n-    //! This is an optimization compared to erasing all entries as the cursor iterates them when will_erase is set.\n+    //! This is an optimization compared to erasing all entries as the cursor iterates them when will_clear is set.\n     //! Calling CCoinsMap::clear() afterwards is faster because a CoinsCachePair cannot be coerced back into a\n     //! CCoinsMap::iterator to be erased, and must therefore be looked up again by key in the CCoinsMap before being erased.\n     CoinsViewCacheCursor(size_t&amp; dirty_count LIFETIMEBOUND,\n                          CoinsCachePair&amp; sentinel LIFETIMEBOUND,\n                          CCoinsMap&amp; map LIFETIMEBOUND,\n-                         bool will_erase) noexcept\n-        : m_dirty_count(dirty_count), m_sentinel(sentinel), m_map(map), m_will_erase(will_erase) {}\n+                         CCoinsMapMemoryResource&amp; memory LIFETIMEBOUND,\n+                         bool will_clear) noexcept\n+        : m_dirty_count(dirty_count), m_sentinel(sentinel), m_map(map), m_memory(memory), m_will_clear(will_clear) {}\n \n     inline CoinsCachePair* Begin() const noexcept { return m_sentinel.second.Next(); }\n     inline CoinsCachePair* End() const noexcept { return &amp;m_sentinel; }\n \n     //! Return the next entry after current, possibly erasing current\n     inline CoinsCachePair* NextAndMaybeErase(CoinsCachePair&amp; current) noexcept\n     {\n         const auto next_entry{current.second.Next()};\n         Assume(TrySub(m_dirty_count, current.second.IsDirty()));\n         // If we are not going to erase the cache, we must still erase spent entries.\n         // Otherwise, clear the state of the entry.\n-        if (!m_will_erase) {\n-            if (current.second.coin.IsSpent()) {\n-                assert(current.second.coin.DynamicMemoryUsage() == 0); // scriptPubKey was already cleared in SpendCoin\n+        if (!m_will_clear) {\n+            if (current.second.IsSpent()) {\n+                // there is no coin to deallocate\n                 m_map.erase(current.first);\n             } else {\n                 current.second.SetClean();\n             }\n+        } else if (current.second.coin) {\n+            // Clearing the map doesn't delete the coins; that must be done explicitly.\n+            m_memory.Deallocate(current.second.coin, sizeof(Coin), alignof(Coin));\n+            current.second.coin = nullptr;\n         }\n         return next_entry;\n     }\n \n-    inline bool WillErase(CoinsCachePair&amp; current) const noexcept { return m_will_erase || current.second.coin.IsSpent(); }\n+    bool WillClear() const noexcept { return m_will_clear; }\n     size_t GetDirtyCount() const noexcept { return m_dirty_count; }\n     size_t GetTotalCount() const noexcept { return m_map.size(); }\n private:\n     size_t&amp; m_dirty_count;\n     CoinsCachePair&amp; m_sentinel;\n     CCoinsMap&amp; m_map;\n-    bool m_will_erase;\n+    CCoinsMapMemoryResource&amp; m_memory;\n+    bool m_will_clear;\n };\n \n /** Pure abstract view on the open txout dataset. */\n class CCoinsView\n {\n public:\n     //! As we use CCoinsViews polymorphically, have a virtual destructor\n     virtual ~CCoinsView() = default;\n \n     //! Retrieve the Coin (unspent transaction output) for a given outpoint.\n     //! May populate the cache. Use PeekCoin() to perform a non-caching lookup.\n     virtual std::optional GetCoin(const COutPoint&amp; outpoint) const = 0;\n \n     //! Retrieve the Coin (unspent transaction output) for a given outpoint, without caching results.\n     //! Does not populate the cache. Use GetCoin() to cache the result.\n     virtual std::optional PeekCoin(const COutPoint&amp; outpoint) const = 0;\n \n     //! Just check whether a given outpoint is unspent.\n     //! May populate the cache. Use PeekCoin() to perform a non-caching lookup.\n     virtual bool HaveCoin(const COutPoint&amp; outpoint) const = 0;\n \n     //! Retrieve the block hash whose state this CCoinsView currently represents\n     virtual uint256 GetBestBlock() const = 0;\n \n     //! Retrieve the range of blocks that may have been only partially written.\n     //! If the database is in a consistent state, the result is the empty vector.\n     //! Otherwise, a two-element vector is returned consisting of the new and\n     //! the old block hash, in that order.\n     virtual std::vector GetHeadBlocks() const = 0;\n \n     //! Do a bulk modification (multiple Coin changes + BestBlock change).\n     //! The passed cursor is used to iterate through the coins.\n     virtual void BatchWrite(CoinsViewCacheCursor&amp; cursor, const uint256&amp; block_hash) = 0;\n \n     //! Get a cursor to iterate over the whole state. Implementations may return nullptr.\n     virtual std::unique_ptr Cursor() const = 0;\n \n     //! Estimate database size\n     virtual size_t EstimateSize() const = 0;\n };\n \n /** Noop coins view. */\n class CoinsViewEmpty : public CCoinsView\n {\n protected:\n     CoinsViewEmpty() = default;\n \n public:\n     static CoinsViewEmpty&amp; Get();\n \n     CoinsViewEmpty(const CoinsViewEmpty&amp;) = delete;\n     CoinsViewEmpty&amp; operator=(const CoinsViewEmpty&amp;) = delete;\n \n     std::optional GetCoin(const COutPoint&amp;) const override { return {}; }\n     std::optional PeekCoin(const COutPoint&amp; outpoint) const override { return GetCoin(outpoint); }\n     bool HaveCoin(const COutPoint&amp; outpoint) const override { return !!GetCoin(outpoint); }\n     uint256 GetBestBlock() const override { return {}; }\n     std::vector GetHeadBlocks() const override { return {}; }\n     void BatchWrite(CoinsViewCacheCursor&amp; cursor, const uint256&amp;) override\n     {\n         for (auto it{cursor.Begin()}; it != cursor.End(); it = cursor.NextAndMaybeErase(*it)) { }\n     }\n     std::unique_ptr Cursor() const override { return {}; }\n     size_t EstimateSize() const override { return 0; }\n };\n \n /** CCoinsView backed by another CCoinsView */\n class CCoinsViewBacked : public CCoinsView\n {\n protected:\n     CCoinsView* base;\n \n public:\n     explicit CCoinsViewBacked(CCoinsView* in_view) : base{Assert(in_view)} {}\n \n     void SetBackend(CCoinsView&amp; in_view) { base = &amp;in_view; }\n \n     std::optional GetCoin(const COutPoint&amp; outpoint) const override { return base-&gt;GetCoin(outpoint); }\n     std::optional PeekCoin(const COutPoint&amp; outpoint) const override { return base-&gt;PeekCoin(outpoint); }\n     bool HaveCoin(const COutPoint&amp; outpoint) const override { return base-&gt;HaveCoin(outpoint); }\n     uint256 GetBestBlock() const override { return base-&gt;GetBestBlock(); }\n     std::vector GetHeadBlocks() const override { return base-&gt;GetHeadBlocks(); }\n     void BatchWrite(CoinsViewCacheCursor&amp; cursor, const uint256&amp; block_hash) override { base-&gt;BatchWrite(cursor, block_hash); }\n     std::unique_ptr Cursor() const override { return base-&gt;Cursor(); }\n     size_t EstimateSize() const override { return base-&gt;EstimateSize(); }\n };\n \n \n /** CCoinsView that adds a memory cache for transactions to another CCoinsView */\n class CCoinsViewCache : public CCoinsViewBacked\n {\n private:\n     const bool m_deterministic;\n \n protected:\n     /**\n      * Make mutable so that we can \"fill the cache\" even from Get-methods\n      * declared as \"const\".\n      */\n     mutable uint256 m_block_hash;\n     mutable CCoinsMapMemoryResource m_cache_coins_memory_resource{};\n     /* The starting sentinel of the flagged entry circular doubly linked list. */\n     mutable CoinsCachePair m_sentinel;\n     mutable CCoinsMap cacheCoins;\n \n     /* Cached dynamic memory usage for the inner Coin objects. */\n     mutable size_t cachedCoinsUsage{0};\n     /* Running count of dirty Coin cache entries. */\n     mutable size_t m_dirty_count{0};\n \n     /**\n      * Discard all modifications made to this cache without flushing to the base view.\n      * This can be used to efficiently reuse a cache instance across multiple operations.\n      */\n     void Reset() noexcept;\n \n     /* Fetch the coin from base. Used for cache misses in FetchCoin. */\n     virtual std::optional FetchCoinFromBase(const COutPoint&amp; outpoint) const;\n \n public:\n     CCoinsViewCache(CCoinsView* in_base, bool deterministic = false);\n \n     /**\n      * By deleting the copy constructor, we prevent accidentally using it when one intends to create a cache on top of a base cache.\n      */\n     CCoinsViewCache(const CCoinsViewCache &amp;) = delete;\n \n+    // Coins need to be deconstructed explicitly.\n+    ~CCoinsViewCache() { FreeAllCoins(); }\n+\n     // Standard CCoinsView methods\n     std::optional GetCoin(const COutPoint&amp; outpoint) const override;\n     std::optional PeekCoin(const COutPoint&amp; outpoint) const override;\n     bool HaveCoin(const COutPoint&amp; outpoint) const override;\n     uint256 GetBestBlock() const override;\n     void SetBestBlock(const uint256&amp; block_hash);\n     void BatchWrite(CoinsViewCacheCursor&amp; cursor, const uint256&amp; block_hash) override;\n     std::unique_ptr Cursor() const override {\n         throw std::logic_error(\"CCoinsViewCache cursor iteration not supported.\");\n     }\n \n     /**\n      * Check if we have the given utxo already loaded in this cache.\n      * The semantics are the same as HaveCoin(), but no calls to\n      * the backing CCoinsView are made.\n      */\n     bool HaveCoinInCache(const COutPoint &amp;outpoint) const;\n \n     /**\n      * Return a reference to Coin in the cache, or coinEmpty if not found. This is\n      * more efficient than GetCoin.\n      *\n      * Generally, do not hold the reference returned for more than a short scope.\n      * While the current implementation allows for modifications to the contents\n      * of the cache while holding the reference, this behavior should not be relied\n      * on! To be safe, best to not hold the returned reference through any other\n      * calls to this cache.\n      */\n     const Coin&amp; AccessCoin(const COutPoint &amp;output) const;\n \n     /**\n      * Add a coin. Set possible_overwrite to true if an unspent version may\n      * already exist in the cache.\n      */\n     void AddCoin(const COutPoint&amp; outpoint, Coin&amp;&amp; coin, bool possible_overwrite);\n \n     /**\n      * Emplace a coin into cacheCoins without performing any checks, marking\n      * the emplaced coin as dirty.\n      *\n      * NOT FOR GENERAL USE. Used only when loading coins from a UTXO snapshot.\n      * @sa ChainstateManager::PopulateAndValidateSnapshot()\n      */\n     void EmplaceCoinInternalDANGER(COutPoint&amp;&amp; outpoint, Coin&amp;&amp; coin);\n \n     /**\n      * Spend a coin. Pass moveto in order to get the deleted data.\n      * If no unspent output exists for the passed outpoint, this call\n      * has no effect.\n      */\n     bool SpendCoin(const COutPoint &amp;outpoint, Coin* moveto = nullptr);\n \n+    //! Move a coin into a cache entry, overwriting any existing coin.\n+    void MoveCoin(CCoinsCacheEntry&amp; entry, Coin&amp;&amp; coin) const;\n+\n+    //! Free (deallocate) a coin (TODO - is noexcept ok here?)\n+    void FreeCoin(CCoinsCacheEntry&amp; entry) const noexcept;\n+\n+    //! Call FreeCoin() on all the coins within this cache.\n+    void FreeAllCoins() const noexcept;\n+\n     /**\n      * Push the modifications applied to this cache to its base and wipe local state.\n      * Failure to call this method or Sync() before destruction will cause the changes\n      * to be forgotten.\n      * If reallocate_cache is false, the cache will retain the same memory footprint\n      * after flushing and should be destroyed to deallocate.\n      */\n     void Flush(bool reallocate_cache = true);\n \n     /**\n      * Push the modifications applied to this cache to its base while retaining\n      * the contents of this cache (except for spent coins, which we erase).\n      * Failure to call this method or Flush() before destruction will cause the changes\n      * to be forgotten.\n      */\n     void Sync();\n \n     /**\n      * Removes the UTXO with the given outpoint from the cache, if it is\n      * not modified.\n      */\n     void Uncache(const COutPoint &amp;outpoint);\n \n     //! Size of the cache (in number of transaction outputs)\n     unsigned int GetCacheSize() const;\n \n     //! Number of dirty cache entries (transaction outputs)\n     size_t GetDirtyCount() const noexcept { return m_dirty_count; }\n \n     //! Calculate the size of the cache (in bytes)\n     size_t DynamicMemoryUsage() const;\n \n     //! Check whether all prevouts of the transaction are present in the UTXO set represented by this view\n     bool HaveInputs(const CTransaction&amp; tx) const;\n \n     //! Force a reallocation of the cache map. This is required when downsizing\n     //! the cache because the map's allocator may be hanging onto a lot of\n     //! memory despite having called .clear().\n     //!\n     //! See: https://stackoverflow.com/questions/42114044/how-to-release-unordered-map-memory\n     void ReallocateCache();\n \n     //! Run an internal sanity check on the cache data structure. */\n     void SanityCheck() const;\n \n     class ResetGuard\n     {\n     private:\n         friend CCoinsViewCache;\n         CCoinsViewCache&amp; m_cache;\n         explicit ResetGuard(CCoinsViewCache&amp; cache LIFETIMEBOUND) noexcept : m_cache{cache} {}\n \n     public:\n         ResetGuard(const ResetGuard&amp;) = delete;\n         ResetGuard&amp; operator=(const ResetGuard&amp;) = delete;\n         ResetGuard(ResetGuard&amp;&amp;) = delete;\n         ResetGuard&amp; operator=(ResetGuard&amp;&amp;) = delete;\n \n         ~ResetGuard() { m_cache.Reset(); }\n     };\n \n     //! Create a scoped guard that will call `Reset()` on this cache when it goes out of scope.\n     [[nodiscard]] ResetGuard CreateResetGuard() noexcept { return ResetGuard{*this}; }\n \n private:\n     /**\n      * @note this is marked const, but may actually append to `cacheCoins`, increasing\n      * memory usage.\n      */\n     CCoinsMap::iterator FetchCoin(const COutPoint &amp;outpoint) const;\n };\n \n /**\n  * CCoinsViewCache overlay that avoids populating/mutating parent cache layers on cache misses.\n  *\n  * This is achieved by fetching coins from the base view using PeekCoin() instead of GetCoin(),\n  * so intermediate CCoinsViewCache layers are not filled.\n  *\n  * Used during ConnectBlock() as an ephemeral, resettable top-level view that is flushed only\n  * on success, so invalid blocks don't pollute the underlying cache.\n  */\n class CoinsViewOverlay : public CCoinsViewCache\n {\n private:\n     std::optional FetchCoinFromBase(const COutPoint&amp; outpoint) const override\n     {\n         return base-&gt;PeekCoin(outpoint);\n     }\n \n public:\n     using CCoinsViewCache::CCoinsViewCache;\n };\n \n //! Utility function to add all of a transaction's outputs to a cache.\n //! When check is false, this assumes that overwrites are only possible for coinbase transactions.\n //! When check is true, the underlying view may be queried to determine whether an addition is\n //! an overwrite.\n // TODO: pass in a boolean to limit these possible overwrites to known\n // (pre-BIP34) cases.\n void AddCoins(CCoinsViewCache&amp; cache, const CTransaction&amp; tx, int nHeight, bool check = false);\n \n //! Utility function to find any unspent output with a given txid.\n //! This function can be quite expensive because in the event of a transaction\n //! which is not found in the cache, it can cause up to MAX_OUTPUTS_PER_BLOCK\n //! lookups to database, so it should be used with care.\n const Coin&amp; AccessByTxid(const CCoinsViewCache&amp; cache, const Txid&amp; txid);\n \n /**\n  * This is a minimally invasive approach to shutdown on LevelDB read errors from the\n  * chainstate, while keeping user interface out of the common library, which is shared\n  * between bitcoind, and bitcoin-qt and non-server tools.\n  *\n  * Writes do not need similar protection, as failure to write is handled by the caller.\n */\n class CCoinsViewErrorCatcher final : public CCoinsViewBacked\n {\n public:\n     explicit CCoinsViewErrorCatcher(CCoinsView* view) : CCoinsViewBacked(view) {}\n \n     void AddReadErrCallback(std::function f) {\n         m_err_callbacks.emplace_back(std::move(f));\n     }\n \n     std::optional GetCoin(const COutPoint&amp; outpoint) const override;\n     bool HaveCoin(const COutPoint&amp; outpoint) const override;\n     std::optional PeekCoin(const COutPoint&amp; outpoint) const override;\n \n private:\n     /** A list of callbacks to execute upon leveldb read error. */\n     std::vector&gt; m_err_callbacks;\n \n };\n \n #endif // BITCOIN_COINS_H\ndiff --git a/src/test/coins_tests.cpp b/src/test/coins_tests.cpp\nindex 14ccb1c443..9137814355 100644\n--- a/src/test/coins_tests.cpp\n+++ b/src/test/coins_tests.cpp\n@@ -1,1194 +1,1226 @@\n // Copyright (c) 2014-present The Bitcoin Core developers\n // Distributed under the MIT software license, see the accompanying\n // file COPYING or http://www.opensource.org/licenses/mit-license.php.\n \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n \n #include \n #include \n #include \n #include \n \n #include \n \n using namespace util::hex_literals;\n \n int ApplyTxInUndo(Coin&amp;&amp; undo, CCoinsViewCache&amp; view, const COutPoint&amp; out);\n void UpdateCoins(const CTransaction&amp; tx, CCoinsViewCache&amp; inputs, CTxUndo &amp;txundo, int nHeight);\n \n namespace\n {\n //! equality test\n bool operator==(const Coin &amp;a, const Coin &amp;b) {\n     // Empty Coin objects are always equal.\n     if (a.IsSpent() &amp;&amp; b.IsSpent()) return true;\n     return a.fCoinBase == b.fCoinBase &amp;&amp;\n            a.nHeight == b.nHeight &amp;&amp;\n            a.out == b.out;\n }\n \n class CCoinsViewTest : public CoinsViewEmpty\n {\n     FastRandomContext&amp; m_rng;\n     uint256 hashBestBlock_;\n     std::map map_;\n \n public:\n     explicit CCoinsViewTest(FastRandomContext&amp; rng) : m_rng{rng} {}\n \n     std::optional GetCoin(const COutPoint&amp; outpoint) const override\n     {\n         if (auto it{map_.find(outpoint)}; it != map_.end() &amp;&amp; !it-&gt;second.IsSpent()) return it-&gt;second;\n         return std::nullopt;\n     }\n \n     uint256 GetBestBlock() const override { return hashBestBlock_; }\n \n     void BatchWrite(CoinsViewCacheCursor&amp; cursor, const uint256&amp; block_hash) override\n     {\n         for (auto it{cursor.Begin()}; it != cursor.End(); it = cursor.NextAndMaybeErase(*it)){\n             if (it-&gt;second.IsDirty()) {\n                 // Same optimization used in CCoinsViewDB is to only write dirty entries.\n-                map_[it-&gt;first] = it-&gt;second.coin;\n-                if (it-&gt;second.coin.IsSpent() &amp;&amp; m_rng.randrange(3) == 0) {\n+                map_[it-&gt;first] = it-&gt;second.coin ? *it-&gt;second.coin : Coin{};\n+                if (it-&gt;second.IsSpent() &amp;&amp; m_rng.randrange(3) == 0) {\n                     // Randomly delete empty entries on write.\n                     map_.erase(it-&gt;first);\n                 }\n             }\n         }\n         if (!block_hash.IsNull())\n             hashBestBlock_ = block_hash;\n     }\n };\n \n class CCoinsViewCacheTest : public CCoinsViewCache\n {\n public:\n     explicit CCoinsViewCacheTest(CCoinsView* _base) : CCoinsViewCache(_base) {}\n \n     void SelfTest(bool sanity_check = true) const\n     {\n         // Manually recompute the dynamic usage of the whole data, and compare it.\n         size_t ret = memusage::DynamicUsage(cacheCoins);\n         size_t count = 0;\n         for (const auto&amp; entry : cacheCoins) {\n-            ret += entry.second.coin.DynamicMemoryUsage();\n+            if (entry.second.coin) ret += entry.second.coin-&gt;DynamicMemoryUsage();\n             ++count;\n         }\n         BOOST_CHECK_EQUAL(GetCacheSize(), count);\n         BOOST_CHECK_EQUAL(DynamicMemoryUsage(), ret);\n         if (sanity_check) {\n             SanityCheck();\n         }\n     }\n \n     CCoinsMap&amp; map() const { return cacheCoins; }\n     CoinsCachePair&amp; sentinel() const { return m_sentinel; }\n+    CCoinsMapMemoryResource&amp; resource() { return m_cache_coins_memory_resource; }\n     size_t&amp; usage() const { return cachedCoinsUsage; }\n     size_t&amp; dirty() const { return m_dirty_count; }\n };\n \n } // namespace\n \n static const unsigned int NUM_SIMULATION_ITERATIONS = 40000;\n \n struct CacheTest : BasicTestingSetup {\n // This is a large randomized insert/remove simulation test on a variable-size\n // stack of caches on top of CCoinsViewTest.\n //\n // It will randomly create/update/delete Coin entries to a tip of caches, with\n // txids picked from a limited list of random 256-bit hashes. Occasionally, a\n // new tip is added to the stack of caches, or the tip is flushed and removed.\n //\n // During the process, booleans are kept to make sure that the randomized\n // operation hits all branches.\n //\n // If fake_best_block is true, assign a random uint256 to mock the recording\n // of best block on flush. This is necessary when using CCoinsViewDB as the base,\n // otherwise we'll hit an assertion in BatchWrite.\n //\n void SimulationTest(CCoinsView* base, bool fake_best_block)\n {\n     // Various coverage trackers.\n     bool removed_all_caches = false;\n     bool reached_4_caches = false;\n     bool added_an_entry = false;\n     bool added_an_unspendable_entry = false;\n     bool removed_an_entry = false;\n     bool updated_an_entry = false;\n     bool found_an_entry = false;\n     bool missed_an_entry = false;\n     bool uncached_an_entry = false;\n     bool flushed_without_erase = false;\n \n     // A simple map to track what we expect the cache stack to represent.\n     std::map result;\n \n     // The cache stack.\n     std::vector&gt; stack; // A stack of CCoinsViewCaches on top.\n     stack.push_back(std::make_unique(base)); // Start with one cache.\n \n     // Use a limited set of random transaction ids, so we do test overwriting entries.\n     std::vector txids;\n     txids.resize(NUM_SIMULATION_ITERATIONS / 8);\n     for (unsigned int i = 0; i &lt; txids.size(); i++) {\n         txids[i] = Txid::FromUint256(m_rng.rand256());\n     }\n \n     for (unsigned int i = 0; i &lt; NUM_SIMULATION_ITERATIONS; i++) {\n         // Do a random modification.\n         {\n             auto txid = txids[m_rng.randrange(txids.size())]; // txid we're going to modify in this iteration.\n             Coin&amp; coin = result[COutPoint(txid, 0)];\n \n             // Determine whether to test HaveCoin before or after Access* (or both). As these functions\n             // can influence each other's behaviour by pulling things into the cache, all combinations\n             // are tested.\n             bool test_havecoin_before = m_rng.randbits(2) == 0;\n             bool test_havecoin_after = m_rng.randbits(2) == 0;\n \n             bool result_havecoin = test_havecoin_before ? stack.back()-&gt;HaveCoin(COutPoint(txid, 0)) : false;\n \n             // Infrequently, test usage of AccessByTxid instead of AccessCoin - the\n             // former just delegates to the latter and returns the first unspent in a txn.\n             const Coin&amp; entry = (m_rng.randrange(500) == 0) ?\n                 AccessByTxid(*stack.back(), txid) : stack.back()-&gt;AccessCoin(COutPoint(txid, 0));\n             BOOST_CHECK(coin == entry);\n \n             if (test_havecoin_before) {\n                 BOOST_CHECK(result_havecoin == !entry.IsSpent());\n             }\n \n             if (test_havecoin_after) {\n                 bool ret = stack.back()-&gt;HaveCoin(COutPoint(txid, 0));\n                 BOOST_CHECK(ret == !entry.IsSpent());\n             }\n \n             if (m_rng.randrange(5) == 0 || coin.IsSpent()) {\n                 Coin newcoin;\n                 newcoin.out.nValue = RandMoney(m_rng);\n                 newcoin.nHeight = 1;\n \n                 // Infrequently test adding unspendable coins.\n                 if (m_rng.randrange(16) == 0 &amp;&amp; coin.IsSpent()) {\n                     newcoin.out.scriptPubKey.assign(1 + m_rng.randbits(6), OP_RETURN);\n                     BOOST_CHECK(newcoin.out.scriptPubKey.IsUnspendable());\n                     added_an_unspendable_entry = true;\n                 } else {\n                     // Random sizes so we can test memory usage accounting\n                     newcoin.out.scriptPubKey.assign(m_rng.randbits(6), 0);\n                     (coin.IsSpent() ? added_an_entry : updated_an_entry) = true;\n                     coin = newcoin;\n                 }\n                 if (COutPoint op(txid, 0); !stack.back()-&gt;map().contains(op) &amp;&amp; !newcoin.out.scriptPubKey.IsUnspendable() &amp;&amp; m_rng.randbool()) {\n                     stack.back()-&gt;EmplaceCoinInternalDANGER(std::move(op), std::move(newcoin));\n                 } else {\n                     stack.back()-&gt;AddCoin(op, std::move(newcoin), /*possible_overwrite=*/!coin.IsSpent() || m_rng.randbool());\n                 }\n             } else {\n                 // Spend the coin.\n                 removed_an_entry = true;\n                 coin.Clear();\n                 BOOST_CHECK(stack.back()-&gt;SpendCoin(COutPoint(txid, 0)));\n             }\n         }\n \n         // Once every 10 iterations, remove a random entry from the cache\n         if (m_rng.randrange(10) == 0) {\n             COutPoint out(txids[m_rng.rand32() % txids.size()], 0);\n             int cacheid = m_rng.rand32() % stack.size();\n             stack[cacheid]-&gt;Uncache(out);\n             uncached_an_entry |= !stack[cacheid]-&gt;HaveCoinInCache(out);\n         }\n \n         // Once every 1000 iterations and at the end, verify the full cache.\n         if (m_rng.randrange(1000) == 1 || i == NUM_SIMULATION_ITERATIONS - 1) {\n             for (const auto&amp; entry : result) {\n                 bool have = stack.back()-&gt;HaveCoin(entry.first);\n                 const Coin&amp; coin = stack.back()-&gt;AccessCoin(entry.first);\n                 BOOST_CHECK(have == !coin.IsSpent());\n                 BOOST_CHECK(coin == entry.second);\n                 if (coin.IsSpent()) {\n                     missed_an_entry = true;\n                 } else {\n                     BOOST_CHECK(stack.back()-&gt;HaveCoinInCache(entry.first));\n                     found_an_entry = true;\n                 }\n             }\n             for (const auto&amp; test : stack) {\n                 test-&gt;SelfTest();\n             }\n         }\n \n         if (m_rng.randrange(100) == 0) {\n             // Every 100 iterations, flush an intermediate cache\n             if (stack.size() &gt; 1 &amp;&amp; m_rng.randbool() == 0) {\n                 unsigned int flushIndex = m_rng.randrange(stack.size() - 1);\n                 if (fake_best_block) stack[flushIndex]-&gt;SetBestBlock(m_rng.rand256());\n                 bool should_erase = m_rng.randrange(4) &lt; 3;\n                 should_erase ? stack[flushIndex]-&gt;Flush() : stack[flushIndex]-&gt;Sync();\n                 flushed_without_erase |= !should_erase;\n             }\n         }\n         if (m_rng.randrange(100) == 0) {\n             // Every 100 iterations, change the cache stack.\n             if (stack.size() &gt; 0 &amp;&amp; m_rng.randbool() == 0) {\n                 //Remove the top cache\n                 if (fake_best_block) stack.back()-&gt;SetBestBlock(m_rng.rand256());\n                 bool should_erase = m_rng.randrange(4) &lt; 3;\n                 should_erase ? stack.back()-&gt;Flush() : stack.back()-&gt;Sync();\n                 flushed_without_erase |= !should_erase;\n                 stack.pop_back();\n             }\n             if (stack.size() == 0 || (stack.size() &lt; 4 &amp;&amp; m_rng.randbool())) {\n                 //Add a new cache\n                 CCoinsView* tip = base;\n                 if (stack.size() &gt; 0) {\n                     tip = stack.back().get();\n                 } else {\n                     removed_all_caches = true;\n                 }\n                 stack.push_back(std::make_unique(tip));\n                 if (stack.size() == 4) {\n                     reached_4_caches = true;\n                 }\n             }\n         }\n     }\n \n     // Verify coverage.\n     BOOST_CHECK(removed_all_caches);\n     BOOST_CHECK(reached_4_caches);\n     BOOST_CHECK(added_an_entry);\n     BOOST_CHECK(added_an_unspendable_entry);\n     BOOST_CHECK(removed_an_entry);\n     BOOST_CHECK(updated_an_entry);\n     BOOST_CHECK(found_an_entry);\n     BOOST_CHECK(missed_an_entry);\n     BOOST_CHECK(uncached_an_entry);\n     BOOST_CHECK(flushed_without_erase);\n }\n }; // struct CacheTest\n \n BOOST_FIXTURE_TEST_SUITE(coins_tests_base, BasicTestingSetup)\n \n // Run the above simulation for multiple base types.\n BOOST_FIXTURE_TEST_CASE(coins_cache_base_simulation_test, CacheTest)\n {\n     CCoinsViewTest base{m_rng};\n     SimulationTest(&amp;base, false);\n }\n \n BOOST_AUTO_TEST_SUITE_END()\n \n BOOST_FIXTURE_TEST_SUITE(coins_tests_dbbase, BasicTestingSetup)\n \n BOOST_FIXTURE_TEST_CASE(coins_cache_dbbase_simulation_test, CacheTest)\n {\n     CCoinsViewDB db_base{{.path = \"test\", .cache_bytes = 8_MiB, .memory_only = true}, {}};\n     SimulationTest(&amp;db_base, true);\n }\n \n BOOST_AUTO_TEST_SUITE_END()\n \n BOOST_FIXTURE_TEST_SUITE(coins_tests, BasicTestingSetup)\n \n struct UpdateTest : BasicTestingSetup {\n // Store of all necessary tx and undo data for next test\n typedef std::map&gt; UtxoData;\n UtxoData utxoData;\n \n UtxoData::iterator FindRandomFrom(const std::set &amp;utxoSet) {\n     assert(utxoSet.size());\n     auto utxoSetIt = utxoSet.lower_bound(COutPoint(Txid::FromUint256(m_rng.rand256()), 0));\n     if (utxoSetIt == utxoSet.end()) {\n         utxoSetIt = utxoSet.begin();\n     }\n     auto utxoDataIt = utxoData.find(*utxoSetIt);\n     assert(utxoDataIt != utxoData.end());\n     return utxoDataIt;\n }\n }; // struct UpdateTest\n \n \n // This test is similar to the previous test\n // except the emphasis is on testing the functionality of UpdateCoins\n // random txs are created and UpdateCoins is used to update the cache stack\n // In particular it is tested that spending a duplicate coinbase tx\n // has the expected effect (the other duplicate is overwritten at all cache levels)\n BOOST_FIXTURE_TEST_CASE(updatecoins_simulation_test, UpdateTest)\n {\n     SeedRandomForTest(SeedRand::ZEROS);\n \n     bool spent_a_duplicate_coinbase = false;\n     // A simple map to track what we expect the cache stack to represent.\n     std::map result;\n \n     // The cache stack.\n     CCoinsViewTest base{m_rng}; // A CCoinsViewTest at the bottom.\n     std::vector&gt; stack; // A stack of CCoinsViewCaches on top.\n     stack.push_back(std::make_unique(&amp;base)); // Start with one cache.\n \n     // Track the txids we've used in various sets\n     std::set coinbase_coins;\n     std::set disconnected_coins;\n     std::set duplicate_coins;\n     std::set utxoset;\n \n     for (unsigned int i = 0; i &lt; NUM_SIMULATION_ITERATIONS; i++) {\n         uint32_t randiter = m_rng.rand32();\n \n         // 19/20 txs add a new transaction\n         if (randiter % 20 &lt; 19) {\n             CMutableTransaction tx;\n             tx.vin.resize(1);\n             tx.vout.resize(1);\n             tx.vout[0].nValue = i; //Keep txs unique unless intended to duplicate\n             tx.vout[0].scriptPubKey.assign(m_rng.rand32() &amp; 0x3F, 0); // Random sizes so we can test memory usage accounting\n             const int height{int(m_rng.rand32() &gt;&gt; 1)};\n             Coin old_coin;\n \n             // 2/20 times create a new coinbase\n             if (randiter % 20 &lt; 2 || coinbase_coins.size() &lt; 10) {\n                 // 1/10 of those times create a duplicate coinbase\n                 if (m_rng.randrange(10) == 0 &amp;&amp; coinbase_coins.size()) {\n                     auto utxod = FindRandomFrom(coinbase_coins);\n                     // Reuse the exact same coinbase\n                     tx = CMutableTransaction{std::get&lt;0&gt;(utxod-&gt;second)};\n                     // shouldn't be available for reconnection if it's been duplicated\n                     disconnected_coins.erase(utxod-&gt;first);\n \n                     duplicate_coins.insert(utxod-&gt;first);\n                 }\n                 else {\n                     coinbase_coins.insert(COutPoint(tx.GetHash(), 0));\n                 }\n                 assert(CTransaction(tx).IsCoinBase());\n             }\n \n             // 17/20 times reconnect previous or add a regular tx\n             else {\n \n                 COutPoint prevout;\n                 // 1/20 times reconnect a previously disconnected tx\n                 if (randiter % 20 == 2 &amp;&amp; disconnected_coins.size()) {\n                     auto utxod = FindRandomFrom(disconnected_coins);\n                     tx = CMutableTransaction{std::get&lt;0&gt;(utxod-&gt;second)};\n                     prevout = tx.vin[0].prevout;\n                     if (!CTransaction(tx).IsCoinBase() &amp;&amp; !utxoset.contains(prevout)) {\n                         disconnected_coins.erase(utxod-&gt;first);\n                         continue;\n                     }\n \n                     // If this tx is already IN the UTXO, then it must be a coinbase, and it must be a duplicate\n                     if (utxoset.contains(utxod-&gt;first)) {\n                         assert(CTransaction(tx).IsCoinBase());\n                         assert(duplicate_coins.contains(utxod-&gt;first));\n                     }\n                     disconnected_coins.erase(utxod-&gt;first);\n                 }\n \n                 // 16/20 times create a regular tx\n                 else {\n                     auto utxod = FindRandomFrom(utxoset);\n                     prevout = utxod-&gt;first;\n \n                     // Construct the tx to spend the coins of prevouthash\n                     tx.vin[0].prevout = prevout;\n                     assert(!CTransaction(tx).IsCoinBase());\n                 }\n                 // In this simple test coins only have two states, spent or unspent, save the unspent state to restore\n                 old_coin = result[prevout];\n                 // Update the expected result of prevouthash to know these coins are spent\n                 result[prevout].Clear();\n \n                 utxoset.erase(prevout);\n \n                 // The test is designed to ensure spending a duplicate coinbase will work properly\n                 // if that ever happens and not resurrect the previously overwritten coinbase\n                 if (duplicate_coins.contains(prevout)) {\n                     spent_a_duplicate_coinbase = true;\n                 }\n \n             }\n             // Update the expected result to know about the new output coins\n             assert(tx.vout.size() == 1);\n             const COutPoint outpoint(tx.GetHash(), 0);\n             result[outpoint] = Coin{tx.vout[0], height, CTransaction{tx}.IsCoinBase()};\n \n             // Call UpdateCoins on the top cache\n             CTxUndo undo;\n             UpdateCoins(CTransaction{tx}, *(stack.back()), undo, height);\n \n             // Update the utxo set for future spends\n             utxoset.insert(outpoint);\n \n             // Track this tx and undo info to use later\n             utxoData.emplace(outpoint, std::make_tuple(tx,undo,old_coin));\n         } else if (utxoset.size()) {\n             //1/20 times undo a previous transaction\n             auto utxod = FindRandomFrom(utxoset);\n \n             CTransaction &amp;tx = std::get&lt;0&gt;(utxod-&gt;second);\n             CTxUndo &amp;undo = std::get&lt;1&gt;(utxod-&gt;second);\n             Coin &amp;orig_coin = std::get&lt;2&gt;(utxod-&gt;second);\n \n             // Update the expected result\n             // Remove new outputs\n             result[utxod-&gt;first].Clear();\n             // If not coinbase restore prevout\n             if (!tx.IsCoinBase()) {\n                 result[tx.vin[0].prevout] = orig_coin;\n             }\n \n             // Disconnect the tx from the current UTXO\n             // See code in DisconnectBlock\n             // remove outputs\n             BOOST_CHECK(stack.back()-&gt;SpendCoin(utxod-&gt;first));\n             // restore inputs\n             if (!tx.IsCoinBase()) {\n                 const COutPoint &amp;out = tx.vin[0].prevout;\n                 Coin coin = undo.vprevout[0];\n                 ApplyTxInUndo(std::move(coin), *(stack.back()), out);\n             }\n             // Store as a candidate for reconnection\n             disconnected_coins.insert(utxod-&gt;first);\n \n             // Update the utxoset\n             utxoset.erase(utxod-&gt;first);\n             if (!tx.IsCoinBase())\n                 utxoset.insert(tx.vin[0].prevout);\n         }\n \n         // Once every 1000 iterations and at the end, verify the full cache.\n         if (m_rng.randrange(1000) == 1 || i == NUM_SIMULATION_ITERATIONS - 1) {\n             for (const auto&amp; entry : result) {\n                 bool have = stack.back()-&gt;HaveCoin(entry.first);\n                 const Coin&amp; coin = stack.back()-&gt;AccessCoin(entry.first);\n                 BOOST_CHECK(have == !coin.IsSpent());\n                 BOOST_CHECK(coin == entry.second);\n             }\n         }\n \n         // One every 10 iterations, remove a random entry from the cache\n         if (utxoset.size() &gt; 1 &amp;&amp; m_rng.randrange(30) == 0) {\n             stack[m_rng.rand32() % stack.size()]-&gt;Uncache(FindRandomFrom(utxoset)-&gt;first);\n         }\n         if (disconnected_coins.size() &gt; 1 &amp;&amp; m_rng.randrange(30) == 0) {\n             stack[m_rng.rand32() % stack.size()]-&gt;Uncache(FindRandomFrom(disconnected_coins)-&gt;first);\n         }\n         if (duplicate_coins.size() &gt; 1 &amp;&amp; m_rng.randrange(30) == 0) {\n             stack[m_rng.rand32() % stack.size()]-&gt;Uncache(FindRandomFrom(duplicate_coins)-&gt;first);\n         }\n \n         if (m_rng.randrange(100) == 0) {\n             // Every 100 iterations, flush an intermediate cache\n             if (stack.size() &gt; 1 &amp;&amp; m_rng.randbool() == 0) {\n                 unsigned int flushIndex = m_rng.randrange(stack.size() - 1);\n                 stack[flushIndex]-&gt;Flush();\n             }\n         }\n         if (m_rng.randrange(100) == 0) {\n             // Every 100 iterations, change the cache stack.\n             if (stack.size() &gt; 0 &amp;&amp; m_rng.randbool() == 0) {\n                 stack.back()-&gt;Flush();\n                 stack.pop_back();\n             }\n             if (stack.size() == 0 || (stack.size() &lt; 4 &amp;&amp; m_rng.randbool())) {\n                 CCoinsView* tip = &amp;base;\n                 if (stack.size() &gt; 0) {\n                     tip = stack.back().get();\n                 }\n                 stack.push_back(std::make_unique(tip));\n             }\n         }\n     }\n \n     // Verify coverage.\n     BOOST_CHECK(spent_a_duplicate_coinbase);\n }\n \n BOOST_AUTO_TEST_CASE(ccoins_serialization)\n {\n     // Good example\n     Coin cc1;\n     SpanReader{\"97f23c835800816115944e077fe7c803cfa57f29b36bf87c1d35\"_hex} &gt;&gt; cc1;\n     BOOST_CHECK_EQUAL(cc1.IsCoinBase(), false);\n     BOOST_CHECK_EQUAL(cc1.nHeight, 203998U);\n     BOOST_CHECK_EQUAL(cc1.out.nValue, CAmount{60000000000});\n     BOOST_CHECK_EQUAL(HexStr(cc1.out.scriptPubKey), HexStr(GetScriptForDestination(PKHash(uint160(\"816115944e077fe7c803cfa57f29b36bf87c1d35\"_hex_u8)))));\n \n     // Good example\n     Coin cc2;\n     SpanReader{\"8ddf77bbd123008c988f1a4a4de2161e0f50aac7f17e7f9555caa4\"_hex} &gt;&gt; cc2;\n     BOOST_CHECK_EQUAL(cc2.IsCoinBase(), true);\n     BOOST_CHECK_EQUAL(cc2.nHeight, 120891U);\n     BOOST_CHECK_EQUAL(cc2.out.nValue, 110397);\n     BOOST_CHECK_EQUAL(HexStr(cc2.out.scriptPubKey), HexStr(GetScriptForDestination(PKHash(uint160(\"8c988f1a4a4de2161e0f50aac7f17e7f9555caa4\"_hex_u8)))));\n \n     // Smallest possible example\n     Coin cc3;\n     SpanReader{\"000006\"_hex} &gt;&gt; cc3;\n     BOOST_CHECK_EQUAL(cc3.IsCoinBase(), false);\n     BOOST_CHECK_EQUAL(cc3.nHeight, 0U);\n     BOOST_CHECK_EQUAL(cc3.out.nValue, 0);\n     BOOST_CHECK_EQUAL(cc3.out.scriptPubKey.size(), 0U);\n \n     // scriptPubKey that ends beyond the end of the stream\n     try {\n         Coin cc4;\n         SpanReader{\"000007\"_hex} &gt;&gt; cc4;\n         BOOST_CHECK_MESSAGE(false, \"We should have thrown\");\n     } catch (const std::ios_base::failure&amp;) {\n     }\n \n     // Very large scriptPubKey (3*10^9 bytes) past the end of the stream\n     DataStream tmp{};\n     uint64_t x = 3000000000ULL;\n     tmp &lt;&lt; VARINT(x);\n     BOOST_CHECK_EQUAL(HexStr(tmp), \"8a95c0bb00\");\n     try {\n         Coin cc5;\n         SpanReader{\"00008a95c0bb00\"_hex} &gt;&gt; cc5;\n         BOOST_CHECK_MESSAGE(false, \"We should have thrown\");\n     } catch (const std::ios_base::failure&amp;) {\n     }\n }\n \n const static COutPoint OUTPOINT;\n constexpr CAmount SPENT {-1};\n constexpr CAmount ABSENT{-2};\n constexpr CAmount VALUE1{100};\n constexpr CAmount VALUE2{200};\n constexpr CAmount VALUE3{300};\n \n struct CoinEntry {\n     enum class State { CLEAN, DIRTY, FRESH, DIRTY_FRESH };\n \n     const CAmount value;\n     const State state;\n \n     constexpr CoinEntry(const CAmount v, const State s) : value{v}, state{s} {}\n \n     bool operator==(const CoinEntry&amp; o) const = default;\n     friend std::ostream&amp; operator&lt;&lt;(std::ostream&amp; os, const CoinEntry&amp; e) { return os &lt;&lt; e.value &lt;&lt; \", \" &lt;&lt; e.state; }\n \n     constexpr bool IsDirtyFresh() const { return state == State::DIRTY_FRESH; }\n     constexpr bool IsDirty() const { return state == State::DIRTY || IsDirtyFresh(); }\n     constexpr bool IsFresh() const { return state == State::FRESH || IsDirtyFresh(); }\n \n     static constexpr State ToState(const bool is_dirty, const bool is_fresh) {\n         if (is_dirty &amp;&amp; is_fresh) return State::DIRTY_FRESH;\n         if (is_dirty) return State::DIRTY;\n         if (is_fresh) return State::FRESH;\n         return State::CLEAN;\n     }\n };\n \n using MaybeCoin   = std::optional;\n using CoinOrError = std::variant;\n \n constexpr MaybeCoin MISSING           {std::nullopt};\n constexpr MaybeCoin SPENT_DIRTY       {{SPENT,  CoinEntry::State::DIRTY}};\n constexpr MaybeCoin SPENT_DIRTY_FRESH {{SPENT,  CoinEntry::State::DIRTY_FRESH}};\n constexpr MaybeCoin SPENT_FRESH       {{SPENT,  CoinEntry::State::FRESH}};\n constexpr MaybeCoin SPENT_CLEAN       {{SPENT,  CoinEntry::State::CLEAN}};\n constexpr MaybeCoin VALUE1_DIRTY      {{VALUE1, CoinEntry::State::DIRTY}};\n constexpr MaybeCoin VALUE1_DIRTY_FRESH{{VALUE1, CoinEntry::State::DIRTY_FRESH}};\n constexpr MaybeCoin VALUE1_FRESH      {{VALUE1, CoinEntry::State::FRESH}};\n constexpr MaybeCoin VALUE1_CLEAN      {{VALUE1, CoinEntry::State::CLEAN}};\n constexpr MaybeCoin VALUE2_DIRTY      {{VALUE2, CoinEntry::State::DIRTY}};\n constexpr MaybeCoin VALUE2_DIRTY_FRESH{{VALUE2, CoinEntry::State::DIRTY_FRESH}};\n constexpr MaybeCoin VALUE2_FRESH      {{VALUE2, CoinEntry::State::FRESH}};\n constexpr MaybeCoin VALUE2_CLEAN      {{VALUE2, CoinEntry::State::CLEAN}};\n constexpr MaybeCoin VALUE3_DIRTY      {{VALUE3, CoinEntry::State::DIRTY}};\n constexpr MaybeCoin VALUE3_DIRTY_FRESH{{VALUE3, CoinEntry::State::DIRTY_FRESH}};\n \n constexpr auto EX_OVERWRITE_UNSPENT{\"Attempted to overwrite an unspent coin (when possible_overwrite is false)\"};\n constexpr auto EX_FRESH_MISAPPLIED {\"FRESH flag misapplied to coin that exists in parent cache\"};\n \n static void SetCoinsValue(const CAmount value, Coin&amp; coin)\n {\n     assert(value != ABSENT);\n     coin.Clear();\n     assert(coin.IsSpent());\n     if (value != SPENT) {\n         coin.out.nValue = value;\n         coin.nHeight = 1;\n         assert(!coin.IsSpent());\n     }\n }\n \n-static size_t InsertCoinsMapEntry(CCoinsMap&amp; map, CoinsCachePair&amp; sentinel, const CoinEntry&amp; cache_coin)\n+static void FreeCoin(CCoinsMapMemoryResource&amp; resource, CCoinsCacheEntry&amp; entry)\n+{\n+    if (!entry.coin) return;\n+    entry.coin-&gt;~Coin();\n+    resource.Deallocate(entry.coin, sizeof(Coin), alignof(Coin));\n+    entry.coin = nullptr;\n+}\n+\n+// Add an (empty) coin to a cache entry. Unlike in the production code, a cache\n+// entry can point to a spent (empty) coin.\n+static void AddCoin(CCoinsMapMemoryResource&amp; resource, CCoinsCacheEntry&amp; entry)\n+{\n+    if (entry.coin) FreeCoin(resource, entry);\n+    assert(!entry.coin);\n+    entry.coin = static_cast(resource.Allocate(sizeof(Coin), alignof(Coin)));\n+    new (entry.coin) Coin();\n+}\n+        \n+static void FreeAllCoins(CCoinsMap&amp; map, CCoinsMapMemoryResource&amp; resource)\n+{\n+    for (auto&amp; entry : map) if (entry.second.coin) FreeCoin(resource, entry.second);\n+}\n+\n+static size_t InsertCoinsMapEntry(CCoinsMap&amp; map, CoinsCachePair&amp; sentinel, CCoinsMapMemoryResource&amp; resource, const CoinEntry&amp; cache_coin)\n {\n     CCoinsCacheEntry entry;\n-    SetCoinsValue(cache_coin.value, entry.coin);\n+    AddCoin(resource, entry);\n+    SetCoinsValue(cache_coin.value, *entry.coin);\n+    //auto [iter, inserted] = map.emplace(std::piecewise_construct, std::forward_as_tuple(OUTPOINT), std::forward_as_tuple(std::move(entry)));\n     auto [iter, inserted] = map.emplace(OUTPOINT, std::move(entry));\n     assert(inserted);\n     if (cache_coin.IsDirty()) CCoinsCacheEntry::SetDirty(*iter, sentinel);\n     if (cache_coin.IsFresh()) CCoinsCacheEntry::SetFresh(*iter, sentinel);\n-    return iter-&gt;second.coin.DynamicMemoryUsage();\n+    return iter-&gt;second.coin-&gt;DynamicMemoryUsage();\n }\n \n static MaybeCoin GetCoinsMapEntry(const CCoinsMap&amp; map, const COutPoint&amp; outp = OUTPOINT)\n {\n     if (auto it{map.find(outp)}; it != map.end()) {\n         return CoinEntry{\n-            it-&gt;second.coin.IsSpent() ? SPENT : it-&gt;second.coin.out.nValue,\n+            (!it-&gt;second.coin || it-&gt;second.coin-&gt;IsSpent()) ? SPENT : it-&gt;second.coin-&gt;out.nValue,\n             CoinEntry::ToState(it-&gt;second.IsDirty(), it-&gt;second.IsFresh())};\n     }\n     return MISSING;\n }\n \n static void WriteCoinsViewEntry(CCoinsView&amp; view, const MaybeCoin&amp; cache_coin)\n {\n     CoinsCachePair sentinel{};\n     sentinel.second.SelfRef(sentinel);\n     CCoinsMapMemoryResource resource;\n     CCoinsMap map{0, CCoinsMap::hasher{}, CCoinsMap::key_equal{}, &amp;resource};\n-    if (cache_coin) InsertCoinsMapEntry(map, sentinel, *cache_coin);\n+    if (cache_coin) InsertCoinsMapEntry(map, sentinel, resource, *cache_coin);\n     size_t dirty_count{cache_coin &amp;&amp; cache_coin-&gt;IsDirty()};\n-    auto cursor{CoinsViewCacheCursor(dirty_count, sentinel, map, /*will_erase=*/true)};\n+    auto cursor{CoinsViewCacheCursor(dirty_count, sentinel, map, resource, /*will_erase=*/true)};\n     view.BatchWrite(cursor, {});\n     BOOST_CHECK_EQUAL(dirty_count, 0U);\n+    FreeAllCoins(map, resource);\n }\n \n class SingleEntryCacheTest\n {\n public:\n     SingleEntryCacheTest(const CAmount base_value, const MaybeCoin&amp; cache_coin)\n     {\n         auto base_cache_coin{base_value == ABSENT ? MISSING : CoinEntry{base_value, CoinEntry::State::DIRTY}};\n         WriteCoinsViewEntry(base, base_cache_coin);\n         if (cache_coin) {\n-            cache.usage() += InsertCoinsMapEntry(cache.map(), cache.sentinel(), *cache_coin);\n+            cache.usage() += InsertCoinsMapEntry(cache.map(), cache.sentinel(), cache.resource(), *cache_coin);\n             cache.dirty() += cache_coin-&gt;IsDirty();\n         }\n     }\n \n+    ~SingleEntryCacheTest() noexcept\n+    {\n+        FreeAllCoins(cache.map(), cache.resource());\n+    }\n+\n     CCoinsViewCacheTest base{&amp;CoinsViewEmpty::Get()};\n     CCoinsViewCacheTest cache{&amp;base};\n };\n \n static void CheckAccessCoin(const CAmount base_value, const MaybeCoin&amp; cache_coin, const MaybeCoin&amp; expected)\n {\n     SingleEntryCacheTest test{base_value, cache_coin};\n     auto&amp; coin = test.cache.AccessCoin(OUTPOINT);\n     BOOST_CHECK_EQUAL(coin.IsSpent(), !test.cache.GetCoin(OUTPOINT));\n     test.cache.SelfTest(/*sanity_check=*/false);\n     BOOST_CHECK_EQUAL(GetCoinsMapEntry(test.cache.map()), expected);\n }\n \n BOOST_AUTO_TEST_CASE(ccoins_access)\n {\n     /* Check AccessCoin behavior, requesting a coin from a cache view layered on\n      * top of a base view, and checking the resulting entry in the cache after\n      * the access.\n      *                  Base        Cache               Expected\n      */\n     for (auto base_value : {ABSENT, SPENT, VALUE1}) {\n         CheckAccessCoin(base_value, MISSING,            base_value == VALUE1 ? VALUE1_CLEAN : MISSING);\n \n         CheckAccessCoin(base_value, SPENT_CLEAN,        SPENT_CLEAN       );\n         CheckAccessCoin(base_value, SPENT_FRESH,        SPENT_FRESH       );\n         CheckAccessCoin(base_value, SPENT_DIRTY,        SPENT_DIRTY       );\n         CheckAccessCoin(base_value, SPENT_DIRTY_FRESH,  SPENT_DIRTY_FRESH );\n \n         CheckAccessCoin(base_value, VALUE2_CLEAN,       VALUE2_CLEAN      );\n         CheckAccessCoin(base_value, VALUE2_FRESH,       VALUE2_FRESH      );\n         CheckAccessCoin(base_value, VALUE2_DIRTY,       VALUE2_DIRTY      );\n         CheckAccessCoin(base_value, VALUE2_DIRTY_FRESH, VALUE2_DIRTY_FRESH);\n     }\n }\n \n static void CheckSpendCoins(const CAmount base_value, const MaybeCoin&amp; cache_coin, const MaybeCoin&amp; expected)\n {\n     SingleEntryCacheTest test{base_value, cache_coin};\n     test.cache.SpendCoin(OUTPOINT);\n     test.cache.SelfTest();\n     BOOST_CHECK_EQUAL(GetCoinsMapEntry(test.cache.map()), expected);\n }\n \n BOOST_AUTO_TEST_CASE(ccoins_spend)\n {\n     /* Check SpendCoin behavior, requesting a coin from a cache view layered on\n      * top of a base view, spending, and then checking\n      * the resulting entry in the cache after the modification.\n      *                  Base        Cache               Expected\n      */\n     for (auto base_value : {ABSENT, SPENT, VALUE1}) {\n         CheckSpendCoins(base_value, MISSING,            base_value == VALUE1 ? SPENT_DIRTY : MISSING);\n \n         CheckSpendCoins(base_value, SPENT_CLEAN,        SPENT_DIRTY);\n         CheckSpendCoins(base_value, SPENT_FRESH,        MISSING    );\n         CheckSpendCoins(base_value, SPENT_DIRTY,        SPENT_DIRTY);\n         CheckSpendCoins(base_value, SPENT_DIRTY_FRESH,  MISSING    );\n \n         CheckSpendCoins(base_value, VALUE2_CLEAN,       SPENT_DIRTY);\n         CheckSpendCoins(base_value, VALUE2_FRESH,       MISSING    );\n         CheckSpendCoins(base_value, VALUE2_DIRTY,       SPENT_DIRTY);\n         CheckSpendCoins(base_value, VALUE2_DIRTY_FRESH, MISSING    );\n     }\n }\n \n static void CheckAddCoin(const CAmount base_value, const MaybeCoin&amp; cache_coin, const CAmount modify_value, const CoinOrError&amp; expected, const bool coinbase)\n {\n     SingleEntryCacheTest test{base_value, cache_coin};\n     bool possible_overwrite{coinbase};\n     auto add_coin{[&amp;] { test.cache.AddCoin(OUTPOINT, Coin{CTxOut{modify_value, CScript{}}, 1, coinbase}, possible_overwrite); }};\n     if (auto* expected_coin{std::get_if(&amp;expected)}) {\n         add_coin();\n         test.cache.SelfTest();\n         BOOST_CHECK_EQUAL(GetCoinsMapEntry(test.cache.map()), *expected_coin);\n     } else {\n         BOOST_CHECK_EXCEPTION(add_coin(), std::logic_error, HasReason(std::get(expected)));\n     }\n }\n \n BOOST_AUTO_TEST_CASE(ccoins_add)\n {\n     /* Check AddCoin behavior, requesting a new coin from a cache view,\n      * writing a modification to the coin, and then checking the resulting\n      * entry in the cache after the modification. Verify behavior with the\n      * AddCoin coinbase argument set to false, and to true.\n      *               Base        Cache               Write   Expected              Coinbase\n      */\n     for (auto base_value : {ABSENT, SPENT, VALUE1}) {\n         CheckAddCoin(base_value, MISSING,            VALUE3, VALUE3_DIRTY_FRESH,   false);\n         CheckAddCoin(base_value, MISSING,            VALUE3, VALUE3_DIRTY,         true );\n \n         CheckAddCoin(base_value, SPENT_CLEAN,        VALUE3, VALUE3_DIRTY_FRESH,   false);\n         CheckAddCoin(base_value, SPENT_CLEAN,        VALUE3, VALUE3_DIRTY,         true );\n         CheckAddCoin(base_value, SPENT_FRESH,        VALUE3, VALUE3_DIRTY_FRESH,   false);\n         CheckAddCoin(base_value, SPENT_FRESH,        VALUE3, VALUE3_DIRTY_FRESH,   true );\n         CheckAddCoin(base_value, SPENT_DIRTY,        VALUE3, VALUE3_DIRTY,         false);\n         CheckAddCoin(base_value, SPENT_DIRTY,        VALUE3, VALUE3_DIRTY,         true );\n         CheckAddCoin(base_value, SPENT_DIRTY_FRESH,  VALUE3, VALUE3_DIRTY_FRESH,   false);\n         CheckAddCoin(base_value, SPENT_DIRTY_FRESH,  VALUE3, VALUE3_DIRTY_FRESH,   true );\n \n         CheckAddCoin(base_value, VALUE2_CLEAN,       VALUE3, EX_OVERWRITE_UNSPENT, false);\n         CheckAddCoin(base_value, VALUE2_CLEAN,       VALUE3, VALUE3_DIRTY,         true );\n         CheckAddCoin(base_value, VALUE2_FRESH,       VALUE3, EX_OVERWRITE_UNSPENT, false);\n         CheckAddCoin(base_value, VALUE2_FRESH,       VALUE3, VALUE3_DIRTY_FRESH,   true );\n         CheckAddCoin(base_value, VALUE2_DIRTY,       VALUE3, EX_OVERWRITE_UNSPENT, false);\n         CheckAddCoin(base_value, VALUE2_DIRTY,       VALUE3, VALUE3_DIRTY,         true );\n         CheckAddCoin(base_value, VALUE2_DIRTY_FRESH, VALUE3, EX_OVERWRITE_UNSPENT, false);\n         CheckAddCoin(base_value, VALUE2_DIRTY_FRESH, VALUE3, VALUE3_DIRTY_FRESH,   true );\n     }\n }\n \n static void CheckWriteCoins(const MaybeCoin&amp; parent, const MaybeCoin&amp; child, const CoinOrError&amp; expected)\n {\n     SingleEntryCacheTest test{ABSENT, parent};\n     auto write_coins{[&amp;] { WriteCoinsViewEntry(test.cache, child); }};\n     if (auto* expected_coin{std::get_if(&amp;expected)}) {\n         write_coins();\n         test.cache.SelfTest(/*sanity_check=*/false);\n         BOOST_CHECK_EQUAL(GetCoinsMapEntry(test.cache.map()), *expected_coin);\n     } else {\n         BOOST_CHECK_EXCEPTION(write_coins(), std::logic_error, HasReason(std::get(expected)));\n     }\n }\n \n BOOST_AUTO_TEST_CASE(ccoins_write)\n {\n     /* Check BatchWrite behavior, flushing one entry from a child cache to a\n      * parent cache, and checking the resulting entry in the parent cache\n      * after the write.\n      *              Parent              Child               Expected\n      */\n     CheckWriteCoins(MISSING,            MISSING,            MISSING            );\n     CheckWriteCoins(MISSING,            SPENT_DIRTY,        SPENT_DIRTY        );\n     CheckWriteCoins(MISSING,            SPENT_DIRTY_FRESH,  MISSING            );\n     CheckWriteCoins(MISSING,            VALUE2_DIRTY,       VALUE2_DIRTY       );\n     CheckWriteCoins(MISSING,            VALUE2_DIRTY_FRESH, VALUE2_DIRTY_FRESH );\n     CheckWriteCoins(SPENT_CLEAN,        MISSING,            SPENT_CLEAN        );\n     CheckWriteCoins(SPENT_FRESH,        MISSING,            SPENT_FRESH        );\n     CheckWriteCoins(SPENT_DIRTY,        MISSING,            SPENT_DIRTY        );\n     CheckWriteCoins(SPENT_DIRTY_FRESH,  MISSING,            SPENT_DIRTY_FRESH  );\n \n     CheckWriteCoins(SPENT_CLEAN,        SPENT_DIRTY,        SPENT_DIRTY        );\n     CheckWriteCoins(SPENT_CLEAN,        SPENT_DIRTY_FRESH,  SPENT_DIRTY        );\n     CheckWriteCoins(SPENT_FRESH,        SPENT_DIRTY,        MISSING            );\n     CheckWriteCoins(SPENT_FRESH,        SPENT_DIRTY_FRESH,  MISSING            );\n     CheckWriteCoins(SPENT_DIRTY,        SPENT_DIRTY,        SPENT_DIRTY        );\n     CheckWriteCoins(SPENT_DIRTY,        SPENT_DIRTY_FRESH,  SPENT_DIRTY        );\n     CheckWriteCoins(SPENT_DIRTY_FRESH,  SPENT_DIRTY,        MISSING            );\n     CheckWriteCoins(SPENT_DIRTY_FRESH,  SPENT_DIRTY_FRESH,  MISSING            );\n \n     CheckWriteCoins(SPENT_CLEAN,        VALUE2_DIRTY,       VALUE2_DIRTY       );\n     CheckWriteCoins(SPENT_CLEAN,        VALUE2_DIRTY_FRESH, VALUE2_DIRTY       );\n     CheckWriteCoins(SPENT_FRESH,        VALUE2_DIRTY,       VALUE2_DIRTY_FRESH );\n     CheckWriteCoins(SPENT_FRESH,        VALUE2_DIRTY_FRESH, VALUE2_DIRTY_FRESH );\n     CheckWriteCoins(SPENT_DIRTY,        VALUE2_DIRTY,       VALUE2_DIRTY       );\n     CheckWriteCoins(SPENT_DIRTY,        VALUE2_DIRTY_FRESH, VALUE2_DIRTY       );\n     CheckWriteCoins(SPENT_DIRTY_FRESH,  VALUE2_DIRTY,       VALUE2_DIRTY_FRESH );\n     CheckWriteCoins(SPENT_DIRTY_FRESH,  VALUE2_DIRTY_FRESH, VALUE2_DIRTY_FRESH );\n \n     CheckWriteCoins(VALUE1_CLEAN,       MISSING,            VALUE1_CLEAN       );\n     CheckWriteCoins(VALUE1_FRESH,       MISSING,            VALUE1_FRESH       );\n     CheckWriteCoins(VALUE1_DIRTY,       MISSING,            VALUE1_DIRTY       );\n     CheckWriteCoins(VALUE1_DIRTY_FRESH, MISSING,            VALUE1_DIRTY_FRESH );\n     CheckWriteCoins(VALUE1_CLEAN,       SPENT_DIRTY,        SPENT_DIRTY        );\n     CheckWriteCoins(VALUE1_CLEAN,       SPENT_DIRTY_FRESH,  EX_FRESH_MISAPPLIED);\n     CheckWriteCoins(VALUE1_FRESH,       SPENT_DIRTY,        MISSING            );\n     CheckWriteCoins(VALUE1_FRESH,       SPENT_DIRTY_FRESH,  EX_FRESH_MISAPPLIED);\n     CheckWriteCoins(VALUE1_DIRTY,       SPENT_DIRTY,        SPENT_DIRTY        );\n     CheckWriteCoins(VALUE1_DIRTY,       SPENT_DIRTY_FRESH,  EX_FRESH_MISAPPLIED);\n     CheckWriteCoins(VALUE1_DIRTY_FRESH, SPENT_DIRTY,        MISSING            );\n     CheckWriteCoins(VALUE1_DIRTY_FRESH, SPENT_DIRTY_FRESH,  EX_FRESH_MISAPPLIED);\n \n     CheckWriteCoins(VALUE1_CLEAN,       VALUE2_DIRTY,       VALUE2_DIRTY       );\n     CheckWriteCoins(VALUE1_CLEAN,       VALUE2_DIRTY_FRESH, EX_FRESH_MISAPPLIED);\n     CheckWriteCoins(VALUE1_FRESH,       VALUE2_DIRTY,       VALUE2_DIRTY_FRESH );\n     CheckWriteCoins(VALUE1_FRESH,       VALUE2_DIRTY_FRESH, EX_FRESH_MISAPPLIED);\n     CheckWriteCoins(VALUE1_DIRTY,       VALUE2_DIRTY,       VALUE2_DIRTY       );\n     CheckWriteCoins(VALUE1_DIRTY,       VALUE2_DIRTY_FRESH, EX_FRESH_MISAPPLIED);\n     CheckWriteCoins(VALUE1_DIRTY_FRESH, VALUE2_DIRTY,       VALUE2_DIRTY_FRESH );\n     CheckWriteCoins(VALUE1_DIRTY_FRESH, VALUE2_DIRTY_FRESH, EX_FRESH_MISAPPLIED);\n \n     // The checks above omit cases where the child state is not DIRTY, since\n     // they would be too repetitive (the parent cache is never updated in these\n     // cases). The loop below covers these cases and makes sure the parent cache\n     // is always left unchanged.\n     for (const MaybeCoin&amp; parent : {MISSING,\n                                     SPENT_CLEAN, SPENT_DIRTY, SPENT_FRESH, SPENT_DIRTY_FRESH,\n                                     VALUE1_CLEAN, VALUE1_DIRTY, VALUE1_FRESH, VALUE1_DIRTY_FRESH}) {\n         for (const MaybeCoin&amp; child : {MISSING,\n                                        SPENT_CLEAN, SPENT_FRESH,\n                                        VALUE2_CLEAN, VALUE2_FRESH}) {\n             auto expected{CoinOrError{parent}}; // TODO test failure cases as well\n             CheckWriteCoins(parent, child, expected);\n         }\n     }\n }\n \n struct FlushTest : BasicTestingSetup {\n Coin MakeCoin()\n {\n     Coin coin;\n     coin.out.nValue = m_rng.rand32();\n     coin.nHeight = m_rng.randrange(4096);\n     coin.fCoinBase = false;\n     return coin;\n }\n \n \n //! For CCoinsViewCache instances backed by either another cache instance or\n //! leveldb, test cache behavior and flag state (DIRTY/FRESH) by\n //!\n //! 1. Adding a random coin to the child-most cache,\n //! 2. Flushing all caches (without erasing),\n //! 3. Ensure the entry still exists in the cache and has been written to parent,\n //! 4. (if `do_erasing_flush`) Flushing the caches again (with erasing),\n //! 5. (if `do_erasing_flush`) Ensure the entry has been written to the parent and is no longer in the cache,\n //! 6. Spend the coin, ensure it no longer exists in the parent.\n //!\n void TestFlushBehavior(\n     CCoinsViewCacheTest* view,\n     CCoinsViewDB&amp; base,\n     std::vector&gt;&amp; all_caches,\n     bool do_erasing_flush)\n {\n     size_t cache_usage;\n     size_t cache_size;\n \n     auto flush_all = [this, &amp;all_caches](bool erase) {\n         // Flush in reverse order to ensure that flushes happen from children up.\n         for (auto i = all_caches.rbegin(); i != all_caches.rend(); ++i) {\n             auto&amp; cache = *i;\n             cache-&gt;SanityCheck();\n             // block_hash must be filled before flushing to disk; value is\n             // unimportant here. This is normally done during connect/disconnect block.\n             cache-&gt;SetBestBlock(m_rng.rand256());\n             erase ? cache-&gt;Flush() : cache-&gt;Sync();\n         }\n     };\n \n     Txid txid = Txid::FromUint256(m_rng.rand256());\n     COutPoint outp = COutPoint(txid, 0);\n     Coin coin = MakeCoin();\n     // Ensure the coins views haven't seen this coin before.\n     BOOST_CHECK(!base.HaveCoin(outp));\n     BOOST_CHECK(!view-&gt;HaveCoin(outp));\n \n     // --- 1. Adding a random coin to the child cache\n     //\n     view-&gt;AddCoin(outp, Coin(coin), false);\n \n     cache_usage = view-&gt;DynamicMemoryUsage();\n     cache_size = view-&gt;map().size();\n \n     // `base` shouldn't have coin (no flush yet) but `view` should have cached it.\n     BOOST_CHECK(!base.HaveCoin(outp));\n     BOOST_CHECK(view-&gt;HaveCoin(outp));\n \n     BOOST_CHECK_EQUAL(GetCoinsMapEntry(view-&gt;map(), outp), CoinEntry(coin.out.nValue, CoinEntry::State::DIRTY_FRESH));\n \n     // --- 2. Flushing all caches (without erasing)\n     //\n     flush_all(/*erase=*/ false);\n \n     // CoinsMap usage should be unchanged since we didn't erase anything.\n     BOOST_CHECK_EQUAL(cache_usage, view-&gt;DynamicMemoryUsage());\n     BOOST_CHECK_EQUAL(cache_size, view-&gt;map().size());\n \n     // --- 3. Ensuring the entry still exists in the cache and has been written to parent\n     //\n     BOOST_CHECK_EQUAL(GetCoinsMapEntry(view-&gt;map(), outp), CoinEntry(coin.out.nValue, CoinEntry::State::CLEAN)); // State should have been wiped.\n \n     // Both views should now have the coin.\n     BOOST_CHECK(base.HaveCoin(outp));\n     BOOST_CHECK(view-&gt;HaveCoin(outp));\n \n     if (do_erasing_flush) {\n         // --- 4. Flushing the caches again (with erasing)\n         //\n         flush_all(/*erase=*/ true);\n \n         // Memory does not necessarily go down due to the map using a memory pool\n         BOOST_TEST(view-&gt;DynamicMemoryUsage() &lt;= cache_usage);\n         // Size of the cache must go down though\n         BOOST_TEST(view-&gt;map().size() &lt; cache_size);\n \n         // --- 5. Ensuring the entry is no longer in the cache\n         //\n         BOOST_CHECK(!GetCoinsMapEntry(view-&gt;map(), outp));\n         view-&gt;AccessCoin(outp);\n         BOOST_CHECK_EQUAL(GetCoinsMapEntry(view-&gt;map(), outp), CoinEntry(coin.out.nValue, CoinEntry::State::CLEAN));\n     }\n \n     // Can't overwrite an entry without specifying that an overwrite is\n     // expected.\n     BOOST_CHECK_THROW(\n         view-&gt;AddCoin(outp, Coin(coin), /*possible_overwrite=*/ false),\n         std::logic_error);\n \n     // --- 6. Spend the coin.\n     //\n     BOOST_CHECK(view-&gt;SpendCoin(outp));\n \n     // The coin should be in the cache, but spent and marked dirty.\n     BOOST_CHECK_EQUAL(GetCoinsMapEntry(view-&gt;map(), outp), SPENT_DIRTY);\n     BOOST_CHECK(!view-&gt;HaveCoin(outp)); // Coin should be considered spent in `view`.\n     BOOST_CHECK(base.HaveCoin(outp));  // But coin should still be unspent in `base`.\n \n     flush_all(/*erase=*/ false);\n \n     // Coin should be considered spent in both views.\n     BOOST_CHECK(!view-&gt;HaveCoin(outp));\n     BOOST_CHECK(!base.HaveCoin(outp));\n \n     // Spent coin should not be spendable.\n     BOOST_CHECK(!view-&gt;SpendCoin(outp));\n \n     // --- Bonus check: ensure that a coin added to the base view via one cache\n     //     can be spent by another cache which has never seen it.\n     //\n     txid = Txid::FromUint256(m_rng.rand256());\n     outp = COutPoint(txid, 0);\n     coin = MakeCoin();\n     BOOST_CHECK(!base.HaveCoin(outp));\n     BOOST_CHECK(!all_caches[0]-&gt;HaveCoin(outp));\n     BOOST_CHECK(!all_caches[1]-&gt;HaveCoin(outp));\n \n     all_caches[0]-&gt;AddCoin(outp, std::move(coin), false);\n     all_caches[0]-&gt;Sync();\n     BOOST_CHECK(base.HaveCoin(outp));\n     BOOST_CHECK(all_caches[0]-&gt;HaveCoin(outp));\n     BOOST_CHECK(!all_caches[1]-&gt;HaveCoinInCache(outp));\n \n     BOOST_CHECK(all_caches[1]-&gt;SpendCoin(outp));\n     flush_all(/*erase=*/ false);\n     BOOST_CHECK(!base.HaveCoin(outp));\n     BOOST_CHECK(!all_caches[0]-&gt;HaveCoin(outp));\n     BOOST_CHECK(!all_caches[1]-&gt;HaveCoin(outp));\n \n     flush_all(/*erase=*/ true); // Erase all cache content.\n \n     // --- Bonus check 2: ensure that a FRESH, spent coin is deleted by Sync()\n     //\n     txid = Txid::FromUint256(m_rng.rand256());\n     outp = COutPoint(txid, 0);\n     coin = MakeCoin();\n     CAmount coin_val = coin.out.nValue;\n     BOOST_CHECK(!base.HaveCoin(outp));\n     BOOST_CHECK(!all_caches[0]-&gt;HaveCoin(outp));\n     BOOST_CHECK(!all_caches[1]-&gt;HaveCoin(outp));\n \n     // Add and spend from same cache without flushing.\n     all_caches[0]-&gt;AddCoin(outp, std::move(coin), false);\n \n     // Coin should be FRESH in the cache.\n     BOOST_CHECK_EQUAL(GetCoinsMapEntry(all_caches[0]-&gt;map(), outp), CoinEntry(coin_val, CoinEntry::State::DIRTY_FRESH));\n     // Base shouldn't have seen coin.\n     BOOST_CHECK(!base.HaveCoin(outp));\n \n     BOOST_CHECK(all_caches[0]-&gt;SpendCoin(outp));\n     all_caches[0]-&gt;Sync();\n \n     // Ensure there is no sign of the coin after spend/flush.\n     BOOST_CHECK(!GetCoinsMapEntry(all_caches[0]-&gt;map(), outp));\n     BOOST_CHECK(!all_caches[0]-&gt;HaveCoinInCache(outp));\n     BOOST_CHECK(!base.HaveCoin(outp));\n }\n }; // struct FlushTest\n \n BOOST_FIXTURE_TEST_CASE(ccoins_flush_behavior, FlushTest)\n {\n     // Create two in-memory caches atop a leveldb view.\n     CCoinsViewDB base{{.path = \"test\", .cache_bytes = 8_MiB, .memory_only = true}, {}};\n     std::vector&gt; caches;\n     caches.push_back(std::make_unique(&amp;base));\n     caches.push_back(std::make_unique(caches.back().get()));\n \n     for (const auto&amp; view : caches) {\n         TestFlushBehavior(view.get(), base, caches, /*do_erasing_flush=*/false);\n         TestFlushBehavior(view.get(), base, caches, /*do_erasing_flush=*/true);\n     }\n }\n \n BOOST_AUTO_TEST_CASE(coins_resource_is_used)\n {\n     CCoinsMapMemoryResource resource;\n     PoolResourceTester::CheckAllDataAccountedFor(resource);\n \n     {\n         CCoinsMap map{0, CCoinsMap::hasher{}, CCoinsMap::key_equal{}, &amp;resource};\n         BOOST_TEST(memusage::DynamicUsage(map) &gt;= resource.ChunkSizeBytes());\n \n         map.reserve(1000);\n \n         // The resource has preallocated a chunk, so we should have space for at several nodes without the need to allocate anything else.\n         const auto usage_before = memusage::DynamicUsage(map);\n \n         COutPoint out_point{};\n         for (size_t i = 0; i &lt; 1000; ++i) {\n             out_point.n = i;\n             map[out_point];\n         }\n         BOOST_TEST(usage_before == memusage::DynamicUsage(map));\n     }\n \n     PoolResourceTester::CheckAllDataAccountedFor(resource);\n }\n \n BOOST_AUTO_TEST_CASE(ccoins_addcoin_exception_keeps_usage_balanced)\n {\n     CCoinsViewCacheTest cache{&amp;CoinsViewEmpty::Get()};\n \n     const COutPoint outpoint{Txid::FromUint256(m_rng.rand256()), m_rng.rand32()};\n \n     const Coin coin1{CTxOut{m_rng.randrange(10), CScript{} &lt;&lt; m_rng.randbytes(CScriptBase::STATIC_SIZE + 1)}, 1, false};\n     cache.AddCoin(outpoint, Coin{coin1}, /*possible_overwrite=*/false);\n     cache.SelfTest();\n \n     const Coin coin2{CTxOut{m_rng.randrange(20), CScript{} &lt;&lt; m_rng.randbytes(CScriptBase::STATIC_SIZE + 2)}, 2, false};\n     BOOST_CHECK_THROW(cache.AddCoin(outpoint, Coin{coin2}, /*possible_overwrite=*/false), std::logic_error);\n     cache.SelfTest();\n \n     BOOST_CHECK(cache.AccessCoin(outpoint) == coin1);\n }\n \n BOOST_AUTO_TEST_CASE(ccoins_emplace_duplicate_keeps_usage_balanced)\n {\n     CCoinsViewCacheTest cache{&amp;CoinsViewEmpty::Get()};\n \n     const COutPoint outpoint{Txid::FromUint256(m_rng.rand256()), m_rng.rand32()};\n \n     const Coin coin1{CTxOut{m_rng.randrange(10), CScript{} &lt;&lt; m_rng.randbytes(CScriptBase::STATIC_SIZE + 1)}, 1, false};\n     cache.EmplaceCoinInternalDANGER(COutPoint{outpoint}, Coin{coin1});\n     cache.SelfTest();\n \n     const Coin coin2{CTxOut{m_rng.randrange(20), CScript{} &lt;&lt; m_rng.randbytes(CScriptBase::STATIC_SIZE + 2)}, 2, false};\n     cache.EmplaceCoinInternalDANGER(COutPoint{outpoint}, Coin{coin2});\n     cache.SelfTest();\n \n     BOOST_CHECK(cache.AccessCoin(outpoint) == coin1);\n }\n \n BOOST_AUTO_TEST_CASE(ccoins_reset_guard)\n {\n     CCoinsViewTest root{m_rng};\n     CCoinsViewCache root_cache{&amp;root};\n     uint256 base_best_block{m_rng.rand256()};\n     root_cache.SetBestBlock(base_best_block);\n     root_cache.Flush();\n \n     CCoinsViewCache cache{&amp;root};\n \n     const COutPoint outpoint{Txid::FromUint256(m_rng.rand256()), m_rng.rand32()};\n \n     const Coin coin{CTxOut{m_rng.randrange(10), CScript{} &lt;&lt; m_rng.randbytes(CScriptBase::STATIC_SIZE + 1)}, 1, false};\n     cache.EmplaceCoinInternalDANGER(COutPoint{outpoint}, Coin{coin});\n     BOOST_CHECK_EQUAL(cache.GetDirtyCount(), 1U);\n \n     uint256 cache_best_block{m_rng.rand256()};\n     cache.SetBestBlock(cache_best_block);\n \n     {\n         const auto reset_guard{cache.CreateResetGuard()};\n         BOOST_CHECK(cache.AccessCoin(outpoint) == coin);\n         BOOST_CHECK(!cache.AccessCoin(outpoint).IsSpent());\n         BOOST_CHECK_EQUAL(cache.GetCacheSize(), 1);\n         BOOST_CHECK_EQUAL(cache.GetDirtyCount(), 1);\n         BOOST_CHECK_EQUAL(cache.GetBestBlock(), cache_best_block);\n         BOOST_CHECK(!root_cache.HaveCoinInCache(outpoint));\n     }\n \n     BOOST_CHECK(cache.AccessCoin(outpoint).IsSpent());\n     BOOST_CHECK_EQUAL(cache.GetCacheSize(), 0);\n     BOOST_CHECK_EQUAL(cache.GetDirtyCount(), 0);\n     BOOST_CHECK_EQUAL(cache.GetBestBlock(), base_best_block);\n     BOOST_CHECK(!root_cache.HaveCoinInCache(outpoint));\n \n     // Using a reset guard again is idempotent\n     {\n         const auto reset_guard{cache.CreateResetGuard()};\n     }\n \n     BOOST_CHECK(cache.AccessCoin(outpoint).IsSpent());\n     BOOST_CHECK_EQUAL(cache.GetCacheSize(), 0);\n     BOOST_CHECK_EQUAL(cache.GetDirtyCount(), 0U);\n     BOOST_CHECK_EQUAL(cache.GetBestBlock(), base_best_block);\n     BOOST_CHECK(!root_cache.HaveCoinInCache(outpoint));\n \n     // Flush should be a no-op after reset.\n     cache.Flush();\n     BOOST_CHECK_EQUAL(cache.GetDirtyCount(), 0U);\n }\n \n BOOST_AUTO_TEST_CASE(ccoins_peekcoin)\n {\n     CCoinsViewTest base{m_rng};\n \n     // Populate the base view with a coin.\n     const COutPoint outpoint{Txid::FromUint256(m_rng.rand256()), m_rng.rand32()};\n     const Coin coin{CTxOut{m_rng.randrange(10), CScript{}}, 1, false};\n     {\n         CCoinsViewCache cache{&amp;base};\n         cache.AddCoin(outpoint, Coin{coin}, /*possible_overwrite=*/false);\n         cache.Flush();\n     }\n \n     // Verify PeekCoin can read through the cache stack without mutating the intermediate cache.\n     CCoinsViewCacheTest main_cache{&amp;base};\n     const auto fetched{main_cache.PeekCoin(outpoint)};\n     BOOST_CHECK(fetched.has_value());\n     BOOST_CHECK(*fetched == coin);\n     BOOST_CHECK(!main_cache.HaveCoinInCache(outpoint));\n }\n \n BOOST_AUTO_TEST_SUITE_END()\ndiff --git a/src/test/fuzz/coinscache_sim.cpp b/src/test/fuzz/coinscache_sim.cpp\nindex 9d41a6c058..1dd2091876 100644\n--- a/src/test/fuzz/coinscache_sim.cpp\n+++ b/src/test/fuzz/coinscache_sim.cpp\n@@ -1,466 +1,466 @@\n // Copyright (c) 2023-present The Bitcoin Core developers\n // Distributed under the MIT software license, see the accompanying\n // file COPYING or http://www.opensource.org/licenses/mit-license.php.\n \n #include \n #include \n #include \n #include \n #include \n #include \n \n #include \n #include \n #include \n #include \n #include \n \n namespace {\n \n /** Number of distinct COutPoint values used in this test. */\n constexpr uint32_t NUM_OUTPOINTS = 256;\n /** Number of distinct Coin values used in this test (ignoring nHeight). */\n constexpr uint32_t NUM_COINS = 256;\n /** Maximum number CCoinsViewCache objects used in this test. */\n constexpr uint32_t MAX_CACHES = 4;\n /** Data type large enough to hold NUM_COINS-1. */\n using coinidx_type = uint8_t;\n \n struct PrecomputedData\n {\n     //! Randomly generated COutPoint values.\n     COutPoint outpoints[NUM_OUTPOINTS];\n \n     //! Randomly generated Coin values.\n     Coin coins[NUM_COINS];\n \n     PrecomputedData()\n     {\n         static const uint8_t PREFIX_O[1] = {'o'}; /** Hash prefix for outpoint hashes. */\n         static const uint8_t PREFIX_S[1] = {'s'}; /** Hash prefix for coins scriptPubKeys. */\n         static const uint8_t PREFIX_M[1] = {'m'}; /** Hash prefix for coins nValue/fCoinBase. */\n \n         for (uint32_t i = 0; i &lt; NUM_OUTPOINTS; ++i) {\n             uint32_t idx = (i * 1200U) &gt;&gt; 12; /* Map 3 or 4 entries to same txid. */\n             const uint8_t ser[4] = {uint8_t(idx), uint8_t(idx &gt;&gt; 8), uint8_t(idx &gt;&gt; 16), uint8_t(idx &gt;&gt; 24)};\n             uint256 txid;\n             CSHA256().Write(PREFIX_O, 1).Write(ser, sizeof(ser)).Finalize(txid.begin());\n             outpoints[i].hash = Txid::FromUint256(txid);\n             outpoints[i].n = i;\n         }\n \n         for (uint32_t i = 0; i &lt; NUM_COINS; ++i) {\n             const uint8_t ser[4] = {uint8_t(i), uint8_t(i &gt;&gt; 8), uint8_t(i &gt;&gt; 16), uint8_t(i &gt;&gt; 24)};\n             uint256 hash;\n             CSHA256().Write(PREFIX_S, 1).Write(ser, sizeof(ser)).Finalize(hash.begin());\n             /* Convert hash to scriptPubkeys (of different lengths, so SanityCheck's cached memory\n              * usage check has a chance to detect mismatches). */\n             switch (i % 5U) {\n             case 0: /* P2PKH */\n                 coins[i].out.scriptPubKey.resize(25);\n                 coins[i].out.scriptPubKey[0] = OP_DUP;\n                 coins[i].out.scriptPubKey[1] = OP_HASH160;\n                 coins[i].out.scriptPubKey[2] = 20;\n                 std::copy(hash.begin(), hash.begin() + 20, coins[i].out.scriptPubKey.begin() + 3);\n                 coins[i].out.scriptPubKey[23] = OP_EQUALVERIFY;\n                 coins[i].out.scriptPubKey[24] = OP_CHECKSIG;\n                 break;\n             case 1: /* P2SH */\n                 coins[i].out.scriptPubKey.resize(23);\n                 coins[i].out.scriptPubKey[0] = OP_HASH160;\n                 coins[i].out.scriptPubKey[1] = 20;\n                 std::copy(hash.begin(), hash.begin() + 20, coins[i].out.scriptPubKey.begin() + 2);\n                 coins[i].out.scriptPubKey[12] = OP_EQUAL;\n                 break;\n             case 2: /* P2WPKH */\n                 coins[i].out.scriptPubKey.resize(22);\n                 coins[i].out.scriptPubKey[0] = OP_0;\n                 coins[i].out.scriptPubKey[1] = 20;\n                 std::copy(hash.begin(), hash.begin() + 20, coins[i].out.scriptPubKey.begin() + 2);\n                 break;\n             case 3: /* P2WSH */\n                 coins[i].out.scriptPubKey.resize(34);\n                 coins[i].out.scriptPubKey[0] = OP_0;\n                 coins[i].out.scriptPubKey[1] = 32;\n                 std::copy(hash.begin(), hash.begin() + 32, coins[i].out.scriptPubKey.begin() + 2);\n                 break;\n             case 4: /* P2TR */\n                 coins[i].out.scriptPubKey.resize(34);\n                 coins[i].out.scriptPubKey[0] = OP_1;\n                 coins[i].out.scriptPubKey[1] = 32;\n                 std::copy(hash.begin(), hash.begin() + 32, coins[i].out.scriptPubKey.begin() + 2);\n                 break;\n             }\n             /* Hash again to construct nValue and fCoinBase. */\n             CSHA256().Write(PREFIX_M, 1).Write(ser, sizeof(ser)).Finalize(hash.begin());\n             coins[i].out.nValue = CAmount(hash.GetUint64(0) % MAX_MONEY);\n             coins[i].fCoinBase = (hash.GetUint64(1) &amp; 7) == 0;\n             coins[i].nHeight = 0; /* Real nHeight used in simulation is set dynamically. */\n         }\n     }\n };\n \n enum class EntryType : uint8_t\n {\n     /* This entry in the cache does not exist (so we'd have to look in the parent cache). */\n     NONE,\n \n     /* This entry in the cache corresponds to an unspent coin. */\n     UNSPENT,\n \n     /* This entry in the cache corresponds to a spent coin. */\n     SPENT,\n };\n \n struct CacheEntry\n {\n     /* Type of entry. */\n     EntryType entrytype;\n \n     /* Index in the coins array this entry corresponds to (only if entrytype == UNSPENT). */\n     coinidx_type coinidx;\n \n     /* nHeight value for this entry (so the coins[coinidx].nHeight value is ignored; only if entrytype == UNSPENT). */\n     uint32_t height;\n };\n \n struct CacheLevel\n {\n     CacheEntry entry[NUM_OUTPOINTS];\n \n     void Wipe() {\n         for (uint32_t i = 0; i &lt; NUM_OUTPOINTS; ++i) {\n             entry[i].entrytype = EntryType::NONE;\n         }\n     }\n };\n \n /** Class for the base of the hierarchy (roughly simulating a memory-backed CCoinsViewDB).\n  *\n  * The initial state consists of the empty UTXO set.\n  */\n class CoinsViewBottom final : public CoinsViewEmpty\n {\n     std::map m_data;\n \n public:\n     std::optional GetCoin(const COutPoint&amp; outpoint) const final\n     {\n         if (auto it{m_data.find(outpoint)}; it != m_data.end()) {\n             assert(!it-&gt;second.IsSpent());\n             return it-&gt;second;\n         }\n         return std::nullopt;\n     }\n \n     void BatchWrite(CoinsViewCacheCursor&amp; cursor, const uint256&amp;) final\n     {\n         for (auto it{cursor.Begin()}; it != cursor.End(); it = cursor.NextAndMaybeErase(*it)) {\n             if (it-&gt;second.IsDirty()) {\n                 if (it-&gt;second.coin.IsSpent()) {\n                     m_data.erase(it-&gt;first);\n                 } else {\n-                    if (cursor.WillErase(*it)) {\n+                    if (cursor.WillClear(*it)) {\n                         m_data[it-&gt;first] = std::move(it-&gt;second.coin);\n                     } else {\n                         m_data[it-&gt;first] = it-&gt;second.coin;\n                     }\n                 }\n             } else {\n                 /* For non-dirty entries being written, compare them with what we have. */\n                 auto it2 = m_data.find(it-&gt;first);\n                 if (it-&gt;second.coin.IsSpent()) {\n                     assert(it2 == m_data.end());\n                 } else {\n                     assert(it2 != m_data.end());\n                     assert(it-&gt;second.coin.out == it2-&gt;second.out);\n                     assert(it-&gt;second.coin.fCoinBase == it2-&gt;second.fCoinBase);\n                     assert(it-&gt;second.coin.nHeight == it2-&gt;second.nHeight);\n                 }\n             }\n         }\n     }\n };\n \n } // namespace\n \n FUZZ_TARGET(coinscache_sim)\n {\n     /** Precomputed COutPoint and CCoins values. */\n     static const PrecomputedData data;\n \n     /** Dummy coinsview instance (base of the hierarchy). */\n     CoinsViewBottom bottom;\n     /** Real CCoinsViewCache objects. */\n     std::vector&gt; caches;\n     /** Simulated cache data (sim_caches[0] matches bottom, sim_caches[i+1] matches caches[i]). */\n     CacheLevel sim_caches[MAX_CACHES + 1];\n     /** Current height in the simulation. */\n     uint32_t current_height = 1U;\n \n     // Initialize bottom simulated cache.\n     sim_caches[0].Wipe();\n \n     /** Helper lookup function in the simulated cache stack. */\n     auto lookup = [&amp;](uint32_t outpointidx, int sim_idx = -1) -&gt; std::optional&gt; {\n         uint32_t cache_idx = sim_idx == -1 ? caches.size() : sim_idx;\n         while (true) {\n             const auto&amp; entry = sim_caches[cache_idx].entry[outpointidx];\n             if (entry.entrytype == EntryType::UNSPENT) {\n                 return {{entry.coinidx, entry.height}};\n             } else if (entry.entrytype == EntryType::SPENT) {\n                 return std::nullopt;\n             };\n             if (cache_idx == 0) break;\n             --cache_idx;\n         }\n         return std::nullopt;\n     };\n \n     /** Flush changes in top cache to the one below. */\n     auto flush = [&amp;]() {\n         assert(caches.size() &gt;= 1);\n         auto&amp; cache = sim_caches[caches.size()];\n         auto&amp; prev_cache = sim_caches[caches.size() - 1];\n         for (uint32_t outpointidx = 0; outpointidx &lt; NUM_OUTPOINTS; ++outpointidx) {\n             if (cache.entry[outpointidx].entrytype != EntryType::NONE) {\n                 prev_cache.entry[outpointidx] = cache.entry[outpointidx];\n                 cache.entry[outpointidx].entrytype = EntryType::NONE;\n             }\n         }\n     };\n \n     // Main simulation loop: read commands from the fuzzer input, and apply them\n     // to both the real cache stack and the simulation.\n     FuzzedDataProvider provider(buffer.data(), buffer.size());\n     LIMITED_WHILE(provider.remaining_bytes(), 10000) {\n         // Every operation (except \"Change height\") moves current height forward,\n         // so it functions as a kind of epoch, making ~all UTXOs unique.\n         ++current_height;\n         // Make sure there is always at least one CCoinsViewCache.\n         if (caches.empty()) {\n             caches.emplace_back(new CCoinsViewCache(&amp;bottom, /*deterministic=*/true));\n             sim_caches[caches.size()].Wipe();\n         }\n \n         // Execute command.\n         CallOneOf(\n             provider,\n \n             [&amp;]() { // PeekCoin/GetCoin\n                 uint32_t outpointidx = provider.ConsumeIntegralInRange(0, NUM_OUTPOINTS - 1);\n                 // Look up in simulation data.\n                 auto sim = lookup(outpointidx);\n                 // Look up in real caches.\n                 auto realcoin = provider.ConsumeBool() ?\n                     caches.back()-&gt;PeekCoin(data.outpoints[outpointidx]) :\n                     caches.back()-&gt;GetCoin(data.outpoints[outpointidx]);\n                 // Compare results.\n                 if (!sim.has_value()) {\n                     assert(!realcoin);\n                 } else {\n                     assert(realcoin &amp;&amp; !realcoin-&gt;IsSpent());\n                     const auto&amp; simcoin = data.coins[sim-&gt;first];\n                     assert(realcoin-&gt;out == simcoin.out);\n                     assert(realcoin-&gt;fCoinBase == simcoin.fCoinBase);\n                     assert(realcoin-&gt;nHeight == sim-&gt;second);\n                 }\n             },\n \n             [&amp;]() { // HaveCoin\n                 uint32_t outpointidx = provider.ConsumeIntegralInRange(0, NUM_OUTPOINTS - 1);\n                 // Look up in simulation data.\n                 auto sim = lookup(outpointidx);\n                 // Look up in real caches.\n                 auto real = caches.back()-&gt;HaveCoin(data.outpoints[outpointidx]);\n                 // Compare results.\n                 assert(sim.has_value() == real);\n             },\n \n             [&amp;]() { // HaveCoinInCache\n                 uint32_t outpointidx = provider.ConsumeIntegralInRange(0, NUM_OUTPOINTS - 1);\n                 // Invoke on real cache (there is no equivalent in simulation, so nothing to compare result with).\n                 (void)caches.back()-&gt;HaveCoinInCache(data.outpoints[outpointidx]);\n             },\n \n             [&amp;]() { // AccessCoin\n                 uint32_t outpointidx = provider.ConsumeIntegralInRange(0, NUM_OUTPOINTS - 1);\n                 // Look up in simulation data.\n                 auto sim = lookup(outpointidx);\n                 // Look up in real caches.\n                 const auto&amp; realcoin = caches.back()-&gt;AccessCoin(data.outpoints[outpointidx]);\n                 // Compare results.\n                 if (!sim.has_value()) {\n                     assert(realcoin.IsSpent());\n                 } else {\n                     assert(!realcoin.IsSpent());\n                     const auto&amp; simcoin = data.coins[sim-&gt;first];\n                     assert(simcoin.out == realcoin.out);\n                     assert(simcoin.fCoinBase == realcoin.fCoinBase);\n                     assert(realcoin.nHeight == sim-&gt;second);\n                 }\n             },\n \n             [&amp;]() { // AddCoin (only possible_overwrite if necessary)\n                 uint32_t outpointidx = provider.ConsumeIntegralInRange(0, NUM_OUTPOINTS - 1);\n                 uint32_t coinidx = provider.ConsumeIntegralInRange(0, NUM_COINS - 1);\n                 // Look up in simulation data (to know whether we must set possible_overwrite or not).\n                 auto sim = lookup(outpointidx);\n                 // Invoke on real caches.\n                 Coin coin = data.coins[coinidx];\n                 coin.nHeight = current_height;\n                 caches.back()-&gt;AddCoin(data.outpoints[outpointidx], std::move(coin), sim.has_value());\n                 // Apply to simulation data.\n                 auto&amp; entry = sim_caches[caches.size()].entry[outpointidx];\n                 entry.entrytype = EntryType::UNSPENT;\n                 entry.coinidx = coinidx;\n                 entry.height = current_height;\n             },\n \n             [&amp;]() { // AddCoin (always possible_overwrite)\n                 uint32_t outpointidx = provider.ConsumeIntegralInRange(0, NUM_OUTPOINTS - 1);\n                 uint32_t coinidx = provider.ConsumeIntegralInRange(0, NUM_COINS - 1);\n                 // Invoke on real caches.\n                 Coin coin = data.coins[coinidx];\n                 coin.nHeight = current_height;\n                 caches.back()-&gt;AddCoin(data.outpoints[outpointidx], std::move(coin), true);\n                 // Apply to simulation data.\n                 auto&amp; entry = sim_caches[caches.size()].entry[outpointidx];\n                 entry.entrytype = EntryType::UNSPENT;\n                 entry.coinidx = coinidx;\n                 entry.height = current_height;\n             },\n \n             [&amp;]() { // SpendCoin (moveto = nullptr)\n                 uint32_t outpointidx = provider.ConsumeIntegralInRange(0, NUM_OUTPOINTS - 1);\n                 // Invoke on real caches.\n                 caches.back()-&gt;SpendCoin(data.outpoints[outpointidx], nullptr);\n                 // Apply to simulation data.\n                 sim_caches[caches.size()].entry[outpointidx].entrytype = EntryType::SPENT;\n             },\n \n             [&amp;]() { // SpendCoin (with moveto)\n                 uint32_t outpointidx = provider.ConsumeIntegralInRange(0, NUM_OUTPOINTS - 1);\n                 // Look up in simulation data (to compare the returned *moveto with).\n                 auto sim = lookup(outpointidx);\n                 // Invoke on real caches.\n                 Coin realcoin;\n                 caches.back()-&gt;SpendCoin(data.outpoints[outpointidx], &amp;realcoin);\n                 // Apply to simulation data.\n                 sim_caches[caches.size()].entry[outpointidx].entrytype = EntryType::SPENT;\n                 // Compare *moveto with the value expected based on simulation data.\n                 if (!sim.has_value()) {\n                     assert(realcoin.IsSpent());\n                 } else {\n                     assert(!realcoin.IsSpent());\n                     const auto&amp; simcoin = data.coins[sim-&gt;first];\n                     assert(simcoin.out == realcoin.out);\n                     assert(simcoin.fCoinBase == realcoin.fCoinBase);\n                     assert(realcoin.nHeight == sim-&gt;second);\n                 }\n             },\n \n             [&amp;]() { // Uncache\n                 uint32_t outpointidx = provider.ConsumeIntegralInRange(0, NUM_OUTPOINTS - 1);\n                 // Apply to real caches (there is no equivalent in our simulation).\n                 caches.back()-&gt;Uncache(data.outpoints[outpointidx]);\n             },\n \n             [&amp;]() { // Add a cache level (if not already at the max).\n                 if (caches.size() != MAX_CACHES) {\n                     // Apply to real caches.\n                     if (provider.ConsumeBool()) {\n                         caches.emplace_back(new CCoinsViewCache(&amp;*caches.back(), /*deterministic=*/true));\n                     } else {\n                         caches.emplace_back(new CoinsViewOverlay(&amp;*caches.back(), /*deterministic=*/true));\n                     }\n                     // Apply to simulation data.\n                     sim_caches[caches.size()].Wipe();\n                 }\n             },\n \n             [&amp;]() { // Remove a cache level.\n                 // Apply to real caches (this reduces caches.size(), implicitly doing the same on the simulation data).\n                 caches.back()-&gt;SanityCheck();\n                 caches.pop_back();\n             },\n \n             [&amp;]() { // Flush.\n                 // Apply to simulation data.\n                 flush();\n                 // Apply to real caches.\n                 caches.back()-&gt;Flush(/*reallocate_cache=*/provider.ConsumeBool());\n             },\n \n             [&amp;]() { // Sync.\n                 // Apply to simulation data (note that in our simulation, syncing and flushing is the same thing).\n                 flush();\n                 // Apply to real caches.\n                 caches.back()-&gt;Sync();\n             },\n \n             [&amp;]() { // Reset.\n                 sim_caches[caches.size()].Wipe();\n                 // Apply to real caches.\n                 {\n                     const auto reset_guard{caches.back()-&gt;CreateResetGuard()};\n                 }\n             },\n \n             [&amp;]() { // GetCacheSize\n                 (void)caches.back()-&gt;GetCacheSize();\n             },\n \n             [&amp;]() { // DynamicMemoryUsage\n                 (void)caches.back()-&gt;DynamicMemoryUsage();\n             },\n \n             [&amp;]() { // Change height\n                 current_height = provider.ConsumeIntegralInRange(1, current_height - 1);\n             }\n         );\n     }\n \n     // Sanity check all the remaining caches\n     for (const auto&amp; cache : caches) {\n         cache-&gt;SanityCheck();\n     }\n \n     // Full comparison between caches and simulation data, from bottom to top,\n     // as AccessCoin on a higher cache may affect caches below it.\n     for (unsigned sim_idx = 1; sim_idx &lt;= caches.size(); ++sim_idx) {\n         auto&amp; cache = *caches[sim_idx - 1];\n         size_t cache_size = 0;\n \n         for (uint32_t outpointidx = 0; outpointidx &lt; NUM_OUTPOINTS; ++outpointidx) {\n             cache_size += cache.HaveCoinInCache(data.outpoints[outpointidx]);\n             const auto&amp; real = cache.AccessCoin(data.outpoints[outpointidx]);\n             auto sim = lookup(outpointidx, sim_idx);\n             if (!sim.has_value()) {\n                 assert(real.IsSpent());\n             } else {\n                 assert(!real.IsSpent());\n                 assert(real.out == data.coins[sim-&gt;first].out);\n                 assert(real.fCoinBase == data.coins[sim-&gt;first].fCoinBase);\n                 assert(real.nHeight == sim-&gt;second);\n             }\n         }\n \n         // HaveCoinInCache ignores spent coins, so GetCacheSize() may exceed it. */\n         assert(cache.GetCacheSize() &gt;= cache_size);\n     }\n \n     // Compare the bottom coinsview (not a CCoinsViewCache) with sim_cache[0].\n     for (uint32_t outpointidx = 0; outpointidx &lt; NUM_OUTPOINTS; ++outpointidx) {\n         auto realcoin = bottom.GetCoin(data.outpoints[outpointidx]);\n         auto sim = lookup(outpointidx, 0);\n         if (!sim.has_value()) {\n             assert(!realcoin);\n         } else {\n             assert(realcoin &amp;&amp; !realcoin-&gt;IsSpent());\n             assert(realcoin-&gt;out == data.coins[sim-&gt;first].out);\n             assert(realcoin-&gt;fCoinBase == data.coins[sim-&gt;first].fCoinBase);\n             assert(realcoin-&gt;nHeight == sim-&gt;second);\n         }\n     }\n }\ndiff --git a/src/txdb.cpp b/src/txdb.cpp\nindex a41dfd1657..eda445a76f 100644\n--- a/src/txdb.cpp\n+++ b/src/txdb.cpp\n@@ -1,249 +1,251 @@\n // Copyright (c) 2009-2010 Satoshi Nakamoto\n // Copyright (c) 2009-present The Bitcoin Core developers\n // Distributed under the MIT software license, see the accompanying\n // file COPYING or http://www.opensource.org/licenses/mit-license.php.\n \n #include \n \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n \n #include \n #include \n #include \n #include \n \n static constexpr uint8_t DB_COIN{'C'};\n static constexpr uint8_t DB_BEST_BLOCK{'B'};\n static constexpr uint8_t DB_HEAD_BLOCKS{'H'};\n // Keys used in previous version that might still be found in the DB:\n static constexpr uint8_t DB_COINS{'c'};\n \n // Threshold for warning when writing this many dirty cache entries to disk.\n static constexpr size_t WARN_FLUSH_COINS_COUNT{10'000'000};\n \n bool CCoinsViewDB::NeedsUpgrade()\n {\n     std::unique_ptr cursor{m_db-&gt;NewIterator()};\n     // DB_COINS was deprecated in v0.15.0, commit\n     // 1088b02f0ccd7358d2b7076bb9e122d59d502d02\n     cursor-&gt;Seek(std::make_pair(DB_COINS, uint256{}));\n     return cursor-&gt;Valid();\n }\n \n namespace {\n \n struct CoinEntry {\n     COutPoint* outpoint;\n     uint8_t key{DB_COIN};\n     explicit CoinEntry(const COutPoint* ptr) : outpoint(const_cast(ptr)) {}\n \n     SERIALIZE_METHODS(CoinEntry, obj) { READWRITE(obj.key, obj.outpoint-&gt;hash, VARINT(obj.outpoint-&gt;n)); }\n };\n \n } // namespace\n \n CCoinsViewDB::CCoinsViewDB(DBParams db_params, CoinsViewOptions options) :\n     m_db_params{std::move(db_params)},\n     m_options{std::move(options)},\n     m_db{std::make_unique(m_db_params)} { }\n \n void CCoinsViewDB::ResizeCache(size_t new_cache_size)\n {\n     // We can't do this operation with an in-memory DB since we'll lose all the coins upon\n     // reset.\n     if (!m_db_params.memory_only) {\n         // Have to do a reset first to get the original `m_db` state to release its\n         // filesystem lock.\n         m_db.reset();\n         m_db_params.cache_bytes = new_cache_size;\n         m_db_params.wipe_data = false;\n         m_db = std::make_unique(m_db_params);\n     }\n }\n \n std::optional CCoinsViewDB::GetCoin(const COutPoint&amp; outpoint) const\n {\n     if (Coin coin; m_db-&gt;Read(CoinEntry(&amp;outpoint), coin)) {\n         Assert(!coin.IsSpent()); // The UTXO database should never contain spent coins\n         return coin;\n     }\n     return std::nullopt;\n }\n \n std::optional CCoinsViewDB::PeekCoin(const COutPoint&amp; outpoint) const\n {\n     return GetCoin(outpoint);\n }\n \n bool CCoinsViewDB::HaveCoin(const COutPoint&amp; outpoint) const\n {\n     return m_db-&gt;Exists(CoinEntry(&amp;outpoint));\n }\n \n uint256 CCoinsViewDB::GetBestBlock() const {\n     uint256 hashBestChain;\n     if (!m_db-&gt;Read(DB_BEST_BLOCK, hashBestChain))\n         return uint256();\n     return hashBestChain;\n }\n \n std::vector CCoinsViewDB::GetHeadBlocks() const {\n     std::vector vhashHeadBlocks;\n     if (!m_db-&gt;Read(DB_HEAD_BLOCKS, vhashHeadBlocks)) {\n         return std::vector();\n     }\n     return vhashHeadBlocks;\n }\n \n void CCoinsViewDB::BatchWrite(CoinsViewCacheCursor&amp; cursor, const uint256&amp; block_hash)\n {\n     CDBBatch batch(*m_db);\n     size_t count = 0;\n     const size_t dirty_count{cursor.GetDirtyCount()};\n     assert(!block_hash.IsNull());\n \n     uint256 old_tip = GetBestBlock();\n     if (old_tip.IsNull()) {\n         // We may be in the middle of replaying.\n         std::vector old_heads = GetHeadBlocks();\n         if (old_heads.size() == 2) {\n             if (old_heads[0] != block_hash) {\n                 LogError(\"The coins database detected an inconsistent state, likely due to a previous crash or shutdown. You will need to restart bitcoind with the -reindex-chainstate or -reindex configuration option.\\n\");\n             }\n             assert(old_heads[0] == block_hash);\n             old_tip = old_heads[1];\n         }\n     }\n \n     if (dirty_count &gt; WARN_FLUSH_COINS_COUNT) LogWarning(\"Flushing large (%d entries) UTXO set to disk, it may take several minutes\", dirty_count);\n     LOG_TIME_MILLIS_WITH_CATEGORY(strprintf(\"write coins cache to disk (%d out of %d cached coins)\",\n         dirty_count, cursor.GetTotalCount()), BCLog::BENCH);\n \n     // In the first batch, mark the database as being in the middle of a\n     // transition from old_tip to block_hash.\n     // A vector is used for future extensibility, as we may want to support\n     // interrupting after partial writes from multiple independent reorgs.\n     batch.Erase(DB_BEST_BLOCK);\n     batch.Write(DB_HEAD_BLOCKS, Vector(block_hash, old_tip));\n \n+    size_t spent_count{0};\n     for (auto it{cursor.Begin()}; it != cursor.End();) {\n         if (it-&gt;second.IsDirty()) {\n             CoinEntry entry(&amp;it-&gt;first);\n-            if (it-&gt;second.coin.IsSpent()) {\n+            if (it-&gt;second.IsSpent()) {\n                 batch.Erase(entry);\n+                ++spent_count;\n             } else {\n-                batch.Write(entry, it-&gt;second.coin);\n+                batch.Write(entry, *it-&gt;second.coin);\n             }\n         }\n         count++;\n         it = cursor.NextAndMaybeErase(*it);\n         if (batch.ApproximateSize() &gt; m_options.batch_write_bytes) {\n             LogDebug(BCLog::COINDB, \"Writing partial batch of %.2f MiB\\n\", batch.ApproximateSize() / double(1_MiB));\n \n             m_db-&gt;WriteBatch(batch);\n             batch.Clear();\n             if (m_options.simulate_crash_ratio) {\n                 static FastRandomContext rng;\n                 if (rng.randrange(m_options.simulate_crash_ratio) == 0) {\n                     LogError(\"Simulating a crash. Goodbye.\");\n                     _Exit(0);\n                 }\n             }\n         }\n     }\n \n     // In the last batch, mark the database as consistent with block_hash again.\n     batch.Erase(DB_HEAD_BLOCKS);\n     batch.Write(DB_BEST_BLOCK, block_hash);\n \n     LogDebug(BCLog::COINDB, \"Writing final batch of %.2f MiB\\n\", batch.ApproximateSize() / double(1_MiB));\n     m_db-&gt;WriteBatch(batch);\n-    LogDebug(BCLog::COINDB, \"Committed %u changed transaction outputs (out of %u) to coin database...\", (unsigned int)dirty_count, (unsigned int)count);\n+    LogDebug(BCLog::COINDB, \"Committed %u changed (%u spent) transaction outputs (out of %u) to coin database...\", (unsigned int)dirty_count, (unsigned int)spent_count, (unsigned int)count);\n }\n \n size_t CCoinsViewDB::EstimateSize() const\n {\n     return m_db-&gt;EstimateSize(DB_COIN, uint8_t(DB_COIN + 1));\n }\n \n /** Specialization of CCoinsViewCursor to iterate over a CCoinsViewDB */\n class CCoinsViewDBCursor: public CCoinsViewCursor\n {\n public:\n     // Prefer using CCoinsViewDB::Cursor() since we want to perform some\n     // cache warmup on instantiation.\n     CCoinsViewDBCursor(CDBIterator* pcursorIn, const uint256&amp; in_block_hash):\n         CCoinsViewCursor(in_block_hash), pcursor(pcursorIn) {}\n     ~CCoinsViewDBCursor() = default;\n \n     bool GetKey(COutPoint &amp;key) const override;\n     bool GetValue(Coin &amp;coin) const override;\n \n     bool Valid() const override;\n     void Next() override;\n \n private:\n     std::unique_ptr pcursor;\n     std::pair keyTmp;\n \n     friend class CCoinsViewDB;\n };\n \n std::unique_ptr CCoinsViewDB::Cursor() const\n {\n     auto i = std::make_unique(\n         const_cast(*m_db).NewIterator(), GetBestBlock());\n     /* It seems that there are no \"const iterators\" for LevelDB.  Since we\n        only need read operations on it, use a const-cast to get around\n        that restriction.  */\n     i-&gt;pcursor-&gt;Seek(DB_COIN);\n     // Cache key of first record\n     if (i-&gt;pcursor-&gt;Valid()) {\n         CoinEntry entry(&amp;i-&gt;keyTmp.second);\n         i-&gt;pcursor-&gt;GetKey(entry);\n         i-&gt;keyTmp.first = entry.key;\n     } else {\n         i-&gt;keyTmp.first = 0; // Make sure Valid() and GetKey() return false\n     }\n     return i;\n }\n \n bool CCoinsViewDBCursor::GetKey(COutPoint &amp;key) const\n {\n     // Return cached key\n     if (keyTmp.first == DB_COIN) {\n         key = keyTmp.second;\n         return true;\n     }\n     return false;\n }\n \n bool CCoinsViewDBCursor::GetValue(Coin &amp;coin) const\n {\n     return pcursor-&gt;GetValue(coin);\n }\n \n bool CCoinsViewDBCursor::Valid() const\n {\n     return keyTmp.first == DB_COIN;\n }\n \n void CCoinsViewDBCursor::Next()\n {\n     pcursor-&gt;Next();\n     CoinEntry entry(&amp;keyTmp.second);\n     if (!pcursor-&gt;Valid() || !pcursor-&gt;GetKey(entry)) {\n         keyTmp.first = 0; // Invalidate cached key after last record so that Valid() and GetKey() return false\n     } else {\n         keyTmp.first = entry.key;\n     }\n }\ndiff --git a/src/validation.cpp b/src/validation.cpp\nindex f85a834f2a..6b64438d70 100644\n--- a/src/validation.cpp\n+++ b/src/validation.cpp\n@@ -1772,2000 +1772,2000 @@ PackageMempoolAcceptResult MemPoolAccept::AcceptPackage(const Package&amp; package,\n MempoolAcceptResult AcceptToMemoryPool(Chainstate&amp; active_chainstate, const CTransactionRef&amp; tx,\n                                        int64_t accept_time, bool bypass_limits, bool test_accept)\n {\n     AssertLockHeld(::cs_main);\n     const CChainParams&amp; chainparams{active_chainstate.m_chainman.GetParams()};\n     assert(active_chainstate.GetMempool() != nullptr);\n     CTxMemPool&amp; pool{*active_chainstate.GetMempool()};\n \n     std::vector coins_to_uncache;\n \n     auto args = MemPoolAccept::ATMPArgs::SingleAccept(chainparams, accept_time, bypass_limits, coins_to_uncache, test_accept);\n     MempoolAcceptResult result = MemPoolAccept(pool, active_chainstate).AcceptSingleTransactionAndCleanup(tx, args);\n \n     if (result.m_result_type != MempoolAcceptResult::ResultType::VALID) {\n         // Remove coins that were not present in the coins cache before calling\n         // AcceptSingleTransaction(); this is to prevent memory DoS in case we receive a large\n         // number of invalid transactions that attempt to overrun the in-memory coins cache\n         // (`CCoinsViewCache::cacheCoins`).\n \n         for (const COutPoint&amp; hashTx : coins_to_uncache)\n             active_chainstate.CoinsTip().Uncache(hashTx);\n         TRACEPOINT(mempool, rejected,\n                 tx-&gt;GetHash().data(),\n                 result.m_state.GetRejectReason().c_str()\n         );\n     }\n     // After we've (potentially) uncached entries, ensure our coins cache is still within its size limits\n     BlockValidationState state_dummy;\n     active_chainstate.FlushStateToDisk(state_dummy, FlushStateMode::PERIODIC);\n     return result;\n }\n \n PackageMempoolAcceptResult ProcessNewPackage(Chainstate&amp; active_chainstate, CTxMemPool&amp; pool,\n                                                    const Package&amp; package, bool test_accept, const std::optional&amp; client_maxfeerate)\n {\n     AssertLockHeld(cs_main);\n     assert(!package.empty());\n     assert(std::all_of(package.cbegin(), package.cend(), [](const auto&amp; tx){return tx != nullptr;}));\n \n     std::vector coins_to_uncache;\n     const CChainParams&amp; chainparams = active_chainstate.m_chainman.GetParams();\n     auto result = [&amp;]() EXCLUSIVE_LOCKS_REQUIRED(cs_main) {\n         AssertLockHeld(cs_main);\n         if (test_accept) {\n             auto args = MemPoolAccept::ATMPArgs::PackageTestAccept(chainparams, GetTime(), coins_to_uncache);\n             return MemPoolAccept(pool, active_chainstate).AcceptMultipleTransactionsAndCleanup(package, args);\n         } else {\n             auto args = MemPoolAccept::ATMPArgs::PackageChildWithParents(chainparams, GetTime(), coins_to_uncache, client_maxfeerate);\n             return MemPoolAccept(pool, active_chainstate).AcceptPackage(package, args);\n         }\n     }();\n \n     // Uncache coins pertaining to transactions that were not submitted to the mempool.\n     if (test_accept || result.m_state.IsInvalid()) {\n         for (const COutPoint&amp; hashTx : coins_to_uncache) {\n             active_chainstate.CoinsTip().Uncache(hashTx);\n         }\n     }\n     // Ensure the coins cache is still within limits.\n     BlockValidationState state_dummy;\n     active_chainstate.FlushStateToDisk(state_dummy, FlushStateMode::PERIODIC);\n     return result;\n }\n \n CAmount GetBlockSubsidy(int nHeight, const Consensus::Params&amp; consensusParams)\n {\n     int halvings = nHeight / consensusParams.nSubsidyHalvingInterval;\n     // Force block reward to zero when right shift is undefined.\n     if (halvings &gt;= 64)\n         return 0;\n \n     CAmount nSubsidy = 50 * COIN;\n     // Subsidy is cut in half every 210,000 blocks which will occur approximately every 4 years.\n     nSubsidy &gt;&gt;= halvings;\n     return nSubsidy;\n }\n \n CoinsViews::CoinsViews(DBParams db_params, CoinsViewOptions options)\n     : m_dbview{std::move(db_params), std::move(options)},\n       m_catcherview(&amp;m_dbview) {}\n \n void CoinsViews::InitCache()\n {\n     AssertLockHeld(::cs_main);\n     m_cacheview = std::make_unique(&amp;m_catcherview);\n     m_connect_block_view = std::make_unique(&amp;*m_cacheview);\n }\n \n Chainstate::Chainstate(\n     CTxMemPool* mempool,\n     BlockManager&amp; blockman,\n     ChainstateManager&amp; chainman,\n     std::optional from_snapshot_blockhash)\n     : m_mempool(mempool),\n       m_blockman(blockman),\n       m_chainman(chainman),\n       m_assumeutxo(from_snapshot_blockhash ? Assumeutxo::UNVALIDATED : Assumeutxo::VALIDATED),\n       m_from_snapshot_blockhash(from_snapshot_blockhash) {}\n \n fs::path Chainstate::StoragePath() const\n {\n     fs::path path{m_chainman.m_options.datadir / \"chainstate\"};\n     if (m_from_snapshot_blockhash) {\n         path += node::SNAPSHOT_CHAINSTATE_SUFFIX;\n     }\n     return path;\n }\n \n const CBlockIndex* Chainstate::SnapshotBase() const\n {\n     if (!m_from_snapshot_blockhash) return nullptr;\n     if (!m_cached_snapshot_base) m_cached_snapshot_base = Assert(m_chainman.m_blockman.LookupBlockIndex(*m_from_snapshot_blockhash));\n     return m_cached_snapshot_base;\n }\n \n const CBlockIndex* Chainstate::TargetBlock() const\n {\n     if (!m_target_blockhash) return nullptr;\n     if (!m_cached_target_block) m_cached_target_block = Assert(m_chainman.m_blockman.LookupBlockIndex(*m_target_blockhash));\n     return m_cached_target_block;\n }\n \n void Chainstate::SetTargetBlock(CBlockIndex* block)\n {\n     if (block) {\n         m_target_blockhash = block-&gt;GetBlockHash();\n     } else {\n         m_target_blockhash.reset();\n     }\n     m_cached_target_block = block;\n }\n \n void Chainstate::SetTargetBlockHash(uint256 block_hash)\n {\n     m_target_blockhash = block_hash;\n     m_cached_target_block = nullptr;\n }\n \n void Chainstate::InitCoinsDB(\n     size_t cache_size_bytes,\n     bool in_memory,\n     bool should_wipe)\n {\n     m_coins_views = std::make_unique(\n         DBParams{\n             .path = StoragePath(),\n             .cache_bytes = cache_size_bytes,\n             .memory_only = in_memory,\n             .wipe_data = should_wipe,\n             .obfuscate = true,\n             .options = m_chainman.m_options.coins_db},\n         m_chainman.m_options.coins_view);\n \n     m_coinsdb_cache_size_bytes = cache_size_bytes;\n }\n \n void Chainstate::InitCoinsCache(size_t cache_size_bytes)\n {\n     AssertLockHeld(::cs_main);\n     assert(m_coins_views != nullptr);\n     m_coinstip_cache_size_bytes = cache_size_bytes;\n     m_coins_views-&gt;InitCache();\n }\n \n // Lock-free: depends on `m_cached_is_ibd`, which is latched by `UpdateIBDStatus()`.\n bool ChainstateManager::IsInitialBlockDownload() const noexcept\n {\n     return m_cached_is_ibd.load(std::memory_order_relaxed);\n }\n \n void Chainstate::CheckForkWarningConditions()\n {\n     AssertLockHeld(cs_main);\n \n     if (this-&gt;GetRole().historical) {\n         return;\n     }\n \n     if (m_chainman.m_best_invalid &amp;&amp; m_chainman.m_best_invalid-&gt;nChainWork &gt; m_chain.Tip()-&gt;nChainWork + (GetBlockProof(*m_chain.Tip()) * 6)) {\n         LogWarning(\"Found invalid chain more than 6 blocks longer than our best chain. This could be due to database corruption or consensus incompatibility with peers.\");\n         m_chainman.GetNotifications().warningSet(\n             kernel::Warning::LARGE_WORK_INVALID_CHAIN,\n             _(\"Warning: Found invalid chain more than 6 blocks longer than our best chain. This could be due to database corruption or consensus incompatibility with peers.\"));\n     } else {\n         m_chainman.GetNotifications().warningUnset(kernel::Warning::LARGE_WORK_INVALID_CHAIN);\n     }\n }\n \n // Called both upon regular invalid block discovery *and* InvalidateBlock\n void Chainstate::InvalidChainFound(CBlockIndex* pindexNew)\n {\n     AssertLockHeld(cs_main);\n     if (!m_chainman.m_best_invalid || pindexNew-&gt;nChainWork &gt; m_chainman.m_best_invalid-&gt;nChainWork) {\n         m_chainman.m_best_invalid = pindexNew;\n     }\n     SetBlockFailureFlags(pindexNew);\n     if (m_chainman.m_best_header != nullptr &amp;&amp; m_chainman.m_best_header-&gt;GetAncestor(pindexNew-&gt;nHeight) == pindexNew) {\n         m_chainman.RecalculateBestHeader();\n     }\n \n     LogInfo(\"%s: invalid block=%s height=%d log2_work=%f date=%s\", __func__,\n       pindexNew-&gt;GetBlockHash().ToString(), pindexNew-&gt;nHeight,\n       log(pindexNew-&gt;nChainWork.getdouble())/log(2.0), FormatISO8601DateTime(pindexNew-&gt;GetBlockTime()));\n     CBlockIndex *tip = m_chain.Tip();\n     assert (tip);\n     LogInfo(\"%s: current best=%s height=%d log2_work=%f date=%s\", __func__,\n       tip-&gt;GetBlockHash().ToString(), m_chain.Height(), log(tip-&gt;nChainWork.getdouble())/log(2.0),\n       FormatISO8601DateTime(tip-&gt;GetBlockTime()));\n     CheckForkWarningConditions();\n }\n \n // Same as InvalidChainFound, above, except not called directly from InvalidateBlock,\n // which does its own setBlockIndexCandidates management.\n void Chainstate::InvalidBlockFound(CBlockIndex* pindex, const BlockValidationState&amp; state)\n {\n     AssertLockHeld(cs_main);\n     if (state.GetResult() != BlockValidationResult::BLOCK_MUTATED) {\n         pindex-&gt;nStatus |= BLOCK_FAILED_VALID;\n         m_blockman.m_dirty_blockindex.insert(pindex);\n         setBlockIndexCandidates.erase(pindex);\n         InvalidChainFound(pindex);\n     }\n }\n \n void UpdateCoins(const CTransaction&amp; tx, CCoinsViewCache&amp; inputs, CTxUndo &amp;txundo, int nHeight)\n {\n     // mark inputs spent\n     if (!tx.IsCoinBase()) {\n         txundo.vprevout.reserve(tx.vin.size());\n         for (const CTxIn &amp;txin : tx.vin) {\n             txundo.vprevout.emplace_back();\n             bool is_spent = inputs.SpendCoin(txin.prevout, &amp;txundo.vprevout.back());\n             assert(is_spent);\n         }\n     }\n     // add outputs\n     AddCoins(inputs, tx, nHeight);\n }\n \n std::optional&gt; CScriptCheck::operator()() {\n     const CScript &amp;scriptSig = ptxTo-&gt;vin[nIn].scriptSig;\n     const CScriptWitness *witness = &amp;ptxTo-&gt;vin[nIn].scriptWitness;\n     ScriptError error{SCRIPT_ERR_UNKNOWN_ERROR};\n     if (VerifyScript(scriptSig, m_tx_out.scriptPubKey, witness, m_flags, CachingTransactionSignatureChecker(ptxTo, nIn, m_tx_out.nValue, cacheStore, *m_signature_cache, *txdata), &amp;error)) {\n         return std::nullopt;\n     } else {\n         auto debug_str = strprintf(\"input %i of %s (wtxid %s), spending %s:%i\", nIn, ptxTo-&gt;GetHash().ToString(), ptxTo-&gt;GetWitnessHash().ToString(), ptxTo-&gt;vin[nIn].prevout.hash.ToString(), ptxTo-&gt;vin[nIn].prevout.n);\n         return std::make_pair(error, std::move(debug_str));\n     }\n }\n \n ValidationCache::ValidationCache(const size_t script_execution_cache_bytes, const size_t signature_cache_bytes)\n     : m_signature_cache{signature_cache_bytes}\n {\n     // Setup the salted hasher\n     uint256 nonce = GetRandHash();\n     // We want the nonce to be 64 bytes long to force the hasher to process\n     // this chunk, which makes later hash computations more efficient. We\n     // just write our 32-byte entropy twice to fill the 64 bytes.\n     m_script_execution_cache_hasher.Write(nonce.begin(), 32);\n     m_script_execution_cache_hasher.Write(nonce.begin(), 32);\n \n     const auto [num_elems, approx_size_bytes] = m_script_execution_cache.setup_bytes(script_execution_cache_bytes);\n     LogInfo(\"Using %zu MiB out of %zu MiB requested for script execution cache, able to store %zu elements\",\n               approx_size_bytes &gt;&gt; 20, script_execution_cache_bytes &gt;&gt; 20, num_elems);\n }\n \n /**\n  * Check whether all of this transaction's input scripts succeed.\n  *\n  * This involves ECDSA signature checks so can be computationally intensive. This function should\n  * only be called after the cheap sanity checks in CheckTxInputs passed.\n  *\n  * If pvChecks is not nullptr, script checks are pushed onto it instead of being performed inline. Any\n  * script checks which are not necessary (eg due to script execution cache hits) are, obviously,\n  * not pushed onto pvChecks/run.\n  *\n  * Setting cacheSigStore/cacheFullScriptStore to false will remove elements from the corresponding cache\n  * which are matched. This is useful for checking blocks where we will likely never need the cache\n  * entry again.\n  *\n  * Note that we may set state.reason to NOT_STANDARD for extra soft-fork flags in flags, block-checking\n  * callers should probably reset it to CONSENSUS in such cases.\n  *\n  * Non-static (and redeclared) in src/test/txvalidationcache_tests.cpp\n  */\n bool CheckInputScripts(const CTransaction&amp; tx, TxValidationState&amp; state,\n                        const CCoinsViewCache&amp; inputs, script_verify_flags flags, bool cacheSigStore,\n                        bool cacheFullScriptStore, PrecomputedTransactionData&amp; txdata,\n                        ValidationCache&amp; validation_cache,\n                        std::vector* pvChecks)\n {\n     if (tx.IsCoinBase()) return true;\n \n     if (pvChecks) {\n         pvChecks-&gt;reserve(tx.vin.size());\n     }\n \n     // First check if script executions have been cached with the same\n     // flags. Note that this assumes that the inputs provided are\n     // correct (ie that the transaction hash which is in tx's prevouts\n     // properly commits to the scriptPubKey in the inputs view of that\n     // transaction).\n     uint256 hashCacheEntry;\n     CSHA256 hasher = validation_cache.ScriptExecutionCacheHasher();\n     hasher.Write(UCharCast(tx.GetWitnessHash().begin()), 32).Write((unsigned char*)&amp;flags, sizeof(flags)).Finalize(hashCacheEntry.begin());\n     AssertLockHeld(cs_main); //TODO: Remove this requirement by making CuckooCache not require external locks\n     if (validation_cache.m_script_execution_cache.contains(hashCacheEntry, !cacheFullScriptStore)) {\n         return true;\n     }\n \n     if (!txdata.m_spent_outputs_ready) {\n         std::vector spent_outputs;\n         spent_outputs.reserve(tx.vin.size());\n \n         for (const auto&amp; txin : tx.vin) {\n             const COutPoint&amp; prevout = txin.prevout;\n             const Coin&amp; coin = inputs.AccessCoin(prevout);\n             assert(!coin.IsSpent());\n             spent_outputs.emplace_back(coin.out);\n         }\n         txdata.Init(tx, std::move(spent_outputs));\n     }\n     assert(txdata.m_spent_outputs.size() == tx.vin.size());\n \n     for (unsigned int i = 0; i &lt; tx.vin.size(); i++) {\n \n         // We very carefully only pass in things to CScriptCheck which\n         // are clearly committed to by tx' witness hash. This provides\n         // a sanity check that our caching is not introducing consensus\n         // failures through additional data in, eg, the coins being\n         // spent being checked as a part of CScriptCheck.\n \n         // Verify signature\n         CScriptCheck check(txdata.m_spent_outputs[i], tx, validation_cache.m_signature_cache, i, flags, cacheSigStore, &amp;txdata);\n         if (pvChecks) {\n             pvChecks-&gt;emplace_back(std::move(check));\n         } else if (auto result = check(); result.has_value()) {\n             // Tx failures never trigger disconnections/bans.\n             // This is so that network splits aren't triggered\n             // either due to non-consensus relay policies (such as\n             // non-standard DER encodings or non-null dummy\n             // arguments) or due to new consensus rules introduced in\n             // soft forks.\n             if (flags &amp; STANDARD_NOT_MANDATORY_VERIFY_FLAGS) {\n                 return state.Invalid(TxValidationResult::TX_NOT_STANDARD, strprintf(\"mempool-script-verify-flag-failed (%s)\", ScriptErrorString(result-&gt;first)), result-&gt;second);\n             } else {\n                 return state.Invalid(TxValidationResult::TX_CONSENSUS, strprintf(\"block-script-verify-flag-failed (%s)\", ScriptErrorString(result-&gt;first)), result-&gt;second);\n             }\n         }\n     }\n \n     if (cacheFullScriptStore &amp;&amp; !pvChecks) {\n         // We executed all of the provided scripts, and were told to\n         // cache the result. Do so now.\n         validation_cache.m_script_execution_cache.insert(hashCacheEntry);\n     }\n \n     return true;\n }\n \n bool FatalError(Notifications&amp; notifications, BlockValidationState&amp; state, const bilingual_str&amp; message)\n {\n     notifications.fatalError(message);\n     return state.Error(message.original);\n }\n \n /**\n  * Restore the UTXO in a Coin at a given COutPoint\n  * @param undo The Coin to be restored.\n  * @param view The coins view to which to apply the changes.\n  * @param out The out point that corresponds to the tx input.\n  * @return A DisconnectResult as an int\n  */\n int ApplyTxInUndo(Coin&amp;&amp; undo, CCoinsViewCache&amp; view, const COutPoint&amp; out)\n {\n     bool fClean = true;\n \n     if (view.HaveCoin(out)) fClean = false; // overwriting transaction output\n \n     if (undo.nHeight == 0) {\n         // Missing undo metadata (height and coinbase). Older versions included this\n         // information only in undo records for the last spend of a transactions'\n         // outputs. This implies that it must be present for some other output of the same tx.\n         const Coin&amp; alternate = AccessByTxid(view, out.hash);\n         if (!alternate.IsSpent()) {\n             undo.nHeight = alternate.nHeight;\n             undo.fCoinBase = alternate.fCoinBase;\n         } else {\n             return DISCONNECT_FAILED; // adding output for transaction without known metadata\n         }\n     }\n     // If the coin already exists as an unspent coin in the cache, then the\n     // possible_overwrite parameter to AddCoin must be set to true. We have\n     // already checked whether an unspent coin exists above using HaveCoin, so\n     // we don't need to guess. When fClean is false, an unspent coin already\n     // existed and it is an overwrite.\n     view.AddCoin(out, std::move(undo), !fClean);\n \n     return fClean ? DISCONNECT_OK : DISCONNECT_UNCLEAN;\n }\n \n /** Undo the effects of this block (with given index) on the UTXO set represented by coins.\n  *  When FAILED is returned, view is left in an indeterminate state. */\n DisconnectResult Chainstate::DisconnectBlock(const CBlock&amp; block, const CBlockIndex* pindex, CCoinsViewCache&amp; view)\n {\n     AssertLockHeld(::cs_main);\n     bool fClean = true;\n \n     CBlockUndo blockUndo;\n     if (!m_blockman.ReadBlockUndo(blockUndo, *pindex)) {\n         LogError(\"DisconnectBlock(): failure reading undo data\\n\");\n         return DISCONNECT_FAILED;\n     }\n \n     if (blockUndo.vtxundo.size() + 1 != block.vtx.size()) {\n         LogError(\"DisconnectBlock(): block and undo data inconsistent\\n\");\n         return DISCONNECT_FAILED;\n     }\n \n     // Ignore blocks that contain transactions which are 'overwritten' by later transactions,\n     // unless those are already completely spent.\n     // See https://github.com/bitcoin/bitcoin/issues/22596 for additional information.\n     // Note: the blocks specified here are different than the ones used in ConnectBlock because DisconnectBlock\n     // unwinds the blocks in reverse. As a result, the inconsistency is not discovered until the earlier\n     // blocks with the duplicate coinbase transactions are disconnected.\n     bool fEnforceBIP30 = !((pindex-&gt;nHeight==91722 &amp;&amp; pindex-&gt;GetBlockHash() == uint256{\"00000000000271a2dc26e7667f8419f2e15416dc6955e5a6c6cdf3f2574dd08e\"}) ||\n                            (pindex-&gt;nHeight==91812 &amp;&amp; pindex-&gt;GetBlockHash() == uint256{\"00000000000af0aed4792b1acee3d966af36cf5def14935db8de83d6f9306f2f\"}));\n \n     // undo transactions in reverse order\n     for (int i = block.vtx.size() - 1; i &gt;= 0; i--) {\n         const CTransaction &amp;tx = *(block.vtx[i]);\n         Txid hash = tx.GetHash();\n         bool is_coinbase = tx.IsCoinBase();\n         bool is_bip30_exception = (is_coinbase &amp;&amp; !fEnforceBIP30);\n \n         // Check that all outputs are available and match the outputs in the block itself\n         // exactly.\n         for (size_t o = 0; o &lt; tx.vout.size(); o++) {\n             if (!tx.vout[o].scriptPubKey.IsUnspendable()) {\n                 COutPoint out(hash, o);\n                 Coin coin;\n                 bool is_spent = view.SpendCoin(out, &amp;coin);\n                 if (!is_spent || tx.vout[o] != coin.out || pindex-&gt;nHeight != coin.nHeight || is_coinbase != coin.IsCoinBase()) {\n                     if (!is_bip30_exception) {\n                         fClean = false; // transaction output mismatch\n                     }\n                 }\n             }\n         }\n \n         // restore inputs\n         if (i &gt; 0) { // not coinbases\n             CTxUndo &amp;txundo = blockUndo.vtxundo[i-1];\n             if (txundo.vprevout.size() != tx.vin.size()) {\n                 LogError(\"DisconnectBlock(): transaction and undo data inconsistent\\n\");\n                 return DISCONNECT_FAILED;\n             }\n             for (unsigned int j = tx.vin.size(); j &gt; 0;) {\n                 --j;\n                 const COutPoint&amp; out = tx.vin[j].prevout;\n                 int res = ApplyTxInUndo(std::move(txundo.vprevout[j]), view, out);\n                 if (res == DISCONNECT_FAILED) return DISCONNECT_FAILED;\n                 fClean = fClean &amp;&amp; res != DISCONNECT_UNCLEAN;\n             }\n             // At this point, all of txundo.vprevout should have been moved out.\n         }\n     }\n \n     // move best block pointer to prevout block\n     view.SetBestBlock(pindex-&gt;pprev-&gt;GetBlockHash());\n \n     return fClean ? DISCONNECT_OK : DISCONNECT_UNCLEAN;\n }\n \n script_verify_flags GetBlockScriptFlags(const CBlockIndex&amp; block_index, const ChainstateManager&amp; chainman)\n {\n     const Consensus::Params&amp; consensusparams = chainman.GetConsensus();\n \n     // BIP16 didn't become active until Apr 1 2012 (on mainnet, and\n     // retroactively applied to testnet)\n     // However, only one historical block violated the P2SH rules (on both\n     // mainnet and testnet).\n     // Similarly, only one historical block violated the TAPROOT rules on\n     // mainnet.\n     // For simplicity, always leave P2SH+WITNESS+TAPROOT on except for the two\n     // violating blocks.\n     script_verify_flags flags{SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_WITNESS | SCRIPT_VERIFY_TAPROOT};\n     const auto it{consensusparams.script_flag_exceptions.find(*Assert(block_index.phashBlock))};\n     if (it != consensusparams.script_flag_exceptions.end()) {\n         flags = it-&gt;second;\n     }\n \n     // Enforce the DERSIG (BIP66) rule\n     if (DeploymentActiveAt(block_index, chainman, Consensus::DEPLOYMENT_DERSIG)) {\n         flags |= SCRIPT_VERIFY_DERSIG;\n     }\n \n     // Enforce CHECKLOCKTIMEVERIFY (BIP65)\n     if (DeploymentActiveAt(block_index, chainman, Consensus::DEPLOYMENT_CLTV)) {\n         flags |= SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY;\n     }\n \n     // Enforce CHECKSEQUENCEVERIFY (BIP112)\n     if (DeploymentActiveAt(block_index, chainman, Consensus::DEPLOYMENT_CSV)) {\n         flags |= SCRIPT_VERIFY_CHECKSEQUENCEVERIFY;\n     }\n \n     // Enforce BIP147 NULLDUMMY (activated simultaneously with segwit)\n     if (DeploymentActiveAt(block_index, chainman, Consensus::DEPLOYMENT_SEGWIT)) {\n         flags |= SCRIPT_VERIFY_NULLDUMMY;\n     }\n \n     return flags;\n }\n \n \n /** Apply the effects of this block (with given index) on the UTXO set represented by coins.\n  *  Validity checks that depend on the UTXO set are also done; ConnectBlock()\n  *  can fail if those validity checks fail (among other reasons). */\n bool Chainstate::ConnectBlock(const CBlock&amp; block, BlockValidationState&amp; state, CBlockIndex* pindex,\n                                CCoinsViewCache&amp; view, bool fJustCheck)\n {\n     AssertLockHeld(cs_main);\n     assert(pindex);\n \n     uint256 block_hash{block.GetHash()};\n     assert(*pindex-&gt;phashBlock == block_hash);\n \n     const auto time_start{SteadyClock::now()};\n     const CChainParams&amp; params{m_chainman.GetParams()};\n \n     // Check it again in case a previous version let a bad block in\n     // NOTE: We don't currently (re-)invoke ContextualCheckBlock() or\n     // ContextualCheckBlockHeader() here. This means that if we add a new\n     // consensus rule that is enforced in one of those two functions, then we\n     // may have let in a block that violates the rule prior to updating the\n     // software, and we would NOT be enforcing the rule here. Fully solving\n     // upgrade from one software version to the next after a consensus rule\n     // change is potentially tricky and issue-specific (see NeedsRedownload()\n     // for one approach that was used for BIP 141 deployment).\n     // Also, currently the rule against blocks more than 2 hours in the future\n     // is enforced in ContextualCheckBlockHeader(); we wouldn't want to\n     // re-enforce that rule here (at least until we make it impossible for\n     // the clock to go backward).\n     if (!CheckBlock(block, state, params.GetConsensus(), !fJustCheck, !fJustCheck)) {\n         if (state.GetResult() == BlockValidationResult::BLOCK_MUTATED) {\n             // We don't write down blocks to disk if they may have been\n             // corrupted, so this should be impossible unless we're having hardware\n             // problems.\n             return FatalError(m_chainman.GetNotifications(), state, _(\"Corrupt block found indicating potential hardware failure.\"));\n         }\n         LogError(\"%s: Consensus::CheckBlock: %s\\n\", __func__, state.ToString());\n         return false;\n     }\n \n     // verify that the view's current state corresponds to the previous block\n     uint256 hashPrevBlock = pindex-&gt;pprev == nullptr ? uint256() : pindex-&gt;pprev-&gt;GetBlockHash();\n     assert(hashPrevBlock == view.GetBestBlock());\n \n     m_chainman.num_blocks_total++;\n \n     // Special case for the genesis block, skipping connection of its transactions\n     // (its coinbase is unspendable)\n     if (block_hash == params.GetConsensus().hashGenesisBlock) {\n         if (!fJustCheck)\n             view.SetBestBlock(pindex-&gt;GetBlockHash());\n         return true;\n     }\n \n     const char* script_check_reason;\n     if (m_chainman.AssumedValidBlock().IsNull()) {\n         script_check_reason = \"assumevalid=0 (always verify)\";\n     } else {\n         constexpr int64_t TWO_WEEKS_IN_SECONDS{60 * 60 * 24 * 7 * 2};\n         // We've been configured with the hash of a block which has been externally verified to have a valid history.\n         // A suitable default value is included with the software and updated from time to time.  Because validity\n         //  relative to a piece of software is an objective fact these defaults can be easily reviewed.\n         // This setting doesn't force the selection of any particular chain but makes validating some faster by\n         //  effectively caching the result of part of the verification.\n         BlockMap::const_iterator it{m_blockman.m_block_index.find(m_chainman.AssumedValidBlock())};\n         if (it == m_blockman.m_block_index.end()) {\n             script_check_reason = \"assumevalid hash not in headers\";\n         } else if (it-&gt;second.GetAncestor(pindex-&gt;nHeight) != pindex) {\n             script_check_reason = (pindex-&gt;nHeight &gt; it-&gt;second.nHeight) ? \"block height above assumevalid height\" : \"block not in assumevalid chain\";\n         } else if (m_chainman.m_best_header-&gt;GetAncestor(pindex-&gt;nHeight) != pindex) {\n             script_check_reason = \"block not in best header chain\";\n         } else if (m_chainman.m_best_header-&gt;nChainWork &lt; m_chainman.MinimumChainWork()) {\n             script_check_reason = \"best header chainwork below minimumchainwork\";\n         } else if (GetBlockProofEquivalentTime(*m_chainman.m_best_header, *pindex, *m_chainman.m_best_header, params.GetConsensus()) &lt;= TWO_WEEKS_IN_SECONDS) {\n             script_check_reason = \"block too recent relative to best header\";\n         } else {\n             // This block is a member of the assumed verified chain and an ancestor of the best header.\n             // Script verification is skipped when connecting blocks under the\n             //  assumevalid block. Assuming the assumevalid block is valid this\n             //  is safe because block merkle hashes are still computed and checked,\n             // Of course, if an assumed valid block is invalid due to false scriptSigs\n             //  this optimization would allow an invalid chain to be accepted.\n             // The equivalent time check discourages hash power from extorting the network via DOS attack\n             //  into accepting an invalid block through telling users they must manually set assumevalid.\n             //  Requiring a software change or burying the invalid block, regardless of the setting, makes\n             //  it hard to hide the implication of the demand. This also avoids having release candidates\n             //  that are hardly doing any signature verification at all in testing without having to\n             //  artificially set the default assumed verified block further back.\n             // The test against the minimum chain work prevents the skipping when denied access to any chain at\n             //  least as good as the expected chain.\n             script_check_reason = nullptr;\n         }\n     }\n \n     const auto time_1{SteadyClock::now()};\n     m_chainman.time_check += time_1 - time_start;\n     LogDebug(BCLog::BENCH, \"    - Sanity checks: %.2fms [%.2fs (%.2fms/blk)]\\n\",\n              Ticks(time_1 - time_start),\n              Ticks(m_chainman.time_check),\n              Ticks(m_chainman.time_check) / m_chainman.num_blocks_total);\n \n     // Do not allow blocks that contain transactions which 'overwrite' older transactions,\n     // unless those are already completely spent.\n     // If such overwrites are allowed, coinbases and transactions depending upon those\n     // can be duplicated to remove the ability to spend the first instance -- even after\n     // being sent to another address.\n     // See BIP30, CVE-2012-1909, and https://r6.ca/blog/20120206T005236Z.html for more information.\n     // This rule was originally applied to all blocks with a timestamp after March 15, 2012, 0:00 UTC.\n     // Now that the whole chain is irreversibly beyond that time it is applied to all blocks except the\n     // two in the chain that violate it. This prevents exploiting the issue against nodes during their\n     // initial block download.\n     bool fEnforceBIP30 = !IsBIP30Repeat(*pindex);\n \n     // Once BIP34 activated it was not possible to create new duplicate coinbases and thus other than starting\n     // with the 2 existing duplicate coinbase pairs, not possible to create overwriting txs.  But by the\n     // time BIP34 activated, in each of the existing pairs the duplicate coinbase had overwritten the first\n     // before the first had been spent.  Since those coinbases are sufficiently buried it's no longer possible to create further\n     // duplicate transactions descending from the known pairs either.\n     // If we're on the known chain at height greater than where BIP34 activated, we can save the db accesses needed for the BIP30 check.\n \n     // BIP34 requires that a block at height X (block X) has its coinbase\n     // scriptSig start with a CScriptNum of X (indicated height X).  The above\n     // logic of no longer requiring BIP30 once BIP34 activates is flawed in the\n     // case that there is a block X before the BIP34 height of 227,931 which has\n     // an indicated height Y where Y is greater than X.  The coinbase for block\n     // X would also be a valid coinbase for block Y, which could be a BIP30\n     // violation.  An exhaustive search of all mainnet coinbases before the\n     // BIP34 height which have an indicated height greater than the block height\n     // reveals many occurrences. The 3 lowest indicated heights found are\n     // 209,921, 490,897, and 1,983,702 and thus coinbases for blocks at these 3\n     // heights would be the first opportunity for BIP30 to be violated.\n \n     // The search reveals a great many blocks which have an indicated height\n     // greater than 1,983,702, so we simply remove the optimization to skip\n     // BIP30 checking for blocks at height 1,983,702 or higher.  Before we reach\n     // that block in another 25 years or so, we should take advantage of a\n     // future consensus change to do a new and improved version of BIP34 that\n     // will actually prevent ever creating any duplicate coinbases in the\n     // future.\n     static constexpr int BIP34_IMPLIES_BIP30_LIMIT = 1983702;\n \n     // There is no potential to create a duplicate coinbase at block 209,921\n     // because this is still before the BIP34 height and so explicit BIP30\n     // checking is still active.\n \n     // The final case is block 176,684 which has an indicated height of\n     // 490,897. Unfortunately, this issue was not discovered until about 2 weeks\n     // before block 490,897 so there was not much opportunity to address this\n     // case other than to carefully analyze it and determine it would not be a\n     // problem. Block 490,897 was, in fact, mined with a different coinbase than\n     // block 176,684, but it is important to note that even if it hadn't been or\n     // is remined on an alternate fork with a duplicate coinbase, we would still\n     // not run into a BIP30 violation.  This is because the coinbase for 176,684\n     // is spent in block 185,956 in transaction\n     // d4f7fbbf92f4a3014a230b2dc70b8058d02eb36ac06b4a0736d9d60eaa9e8781.  This\n     // spending transaction can't be duplicated because it also spends coinbase\n     // 0328dd85c331237f18e781d692c92de57649529bd5edf1d01036daea32ffde29.  This\n     // coinbase has an indicated height of over 4.2 billion, and wouldn't be\n     // duplicatable until that height, and it's currently impossible to create a\n     // chain that long. Nevertheless we may wish to consider a future soft fork\n     // which retroactively prevents block 490,897 from creating a duplicate\n     // coinbase. The two historical BIP30 violations often provide a confusing\n     // edge case when manipulating the UTXO and it would be simpler not to have\n     // another edge case to deal with.\n \n     // testnet3 has no blocks before the BIP34 height with indicated heights\n     // post BIP34 before approximately height 486,000,000. After block\n     // 1,983,702 testnet3 starts doing unnecessary BIP30 checking again.\n     assert(pindex-&gt;pprev);\n     CBlockIndex* pindexBIP34height = pindex-&gt;pprev-&gt;GetAncestor(params.GetConsensus().BIP34Height);\n     //Only continue to enforce if we're below BIP34 activation height or the block hash at that height doesn't correspond.\n     fEnforceBIP30 = fEnforceBIP30 &amp;&amp; (!pindexBIP34height || !(pindexBIP34height-&gt;GetBlockHash() == params.GetConsensus().BIP34Hash));\n \n     // TODO: Remove BIP30 checking from block height 1,983,702 on, once we have a\n     // consensus change that ensures coinbases at those heights cannot\n     // duplicate earlier coinbases.\n     if (fEnforceBIP30 || pindex-&gt;nHeight &gt;= BIP34_IMPLIES_BIP30_LIMIT) {\n         for (const auto&amp; tx : block.vtx) {\n             for (size_t o = 0; o &lt; tx-&gt;vout.size(); o++) {\n                 if (view.HaveCoin(COutPoint(tx-&gt;GetHash(), o))) {\n                     state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, \"bad-txns-BIP30\",\n                                   \"tried to overwrite transaction\");\n                 }\n             }\n         }\n     }\n \n     // Enforce BIP68 (sequence locks)\n     int nLockTimeFlags = 0;\n     if (DeploymentActiveAt(*pindex, m_chainman, Consensus::DEPLOYMENT_CSV)) {\n         nLockTimeFlags |= LOCKTIME_VERIFY_SEQUENCE;\n     }\n \n     // Get the script flags for this block\n     script_verify_flags flags{GetBlockScriptFlags(*pindex, m_chainman)};\n \n     const auto time_2{SteadyClock::now()};\n     m_chainman.time_forks += time_2 - time_1;\n     LogDebug(BCLog::BENCH, \"    - Fork checks: %.2fms [%.2fs (%.2fms/blk)]\\n\",\n              Ticks(time_2 - time_1),\n              Ticks(m_chainman.time_forks),\n              Ticks(m_chainman.time_forks) / m_chainman.num_blocks_total);\n \n     const bool fScriptChecks{!!script_check_reason};\n     const kernel::ChainstateRole role{GetRole()};\n     if (script_check_reason != m_last_script_check_reason_logged &amp;&amp; role.validated &amp;&amp; !role.historical) {\n         if (fScriptChecks) {\n             LogInfo(\"Enabling script verification at block #%d (%s): %s.\",\n                     pindex-&gt;nHeight, block_hash.ToString(), script_check_reason);\n         } else {\n             LogInfo(\"Disabling script verification at block #%d (%s).\",\n                     pindex-&gt;nHeight, block_hash.ToString());\n         }\n         m_last_script_check_reason_logged = script_check_reason;\n     }\n \n     CBlockUndo blockundo;\n \n     // Precomputed transaction data pointers must not be invalidated\n     // until after `control` has run the script checks (potentially\n     // in multiple threads). Preallocate the vector size so a new allocation\n     // doesn't invalidate pointers into the vector, and keep txsdata in scope\n     // for as long as `control`.\n     std::optional&gt; control;\n     if (auto&amp; queue = m_chainman.GetCheckQueue(); queue.HasThreads() &amp;&amp; fScriptChecks) control.emplace(queue);\n \n     std::vector txsdata(block.vtx.size());\n \n     std::vector prevheights;\n     CAmount nFees = 0;\n     int nInputs = 0;\n     int64_t nSigOpsCost = 0;\n     blockundo.vtxundo.reserve(block.vtx.size() - 1);\n     for (unsigned int i = 0; i &lt; block.vtx.size(); i++)\n     {\n         if (!state.IsValid()) break;\n         const CTransaction &amp;tx = *(block.vtx[i]);\n \n         nInputs += tx.vin.size();\n \n         if (!tx.IsCoinBase())\n         {\n             CAmount txfee = 0;\n             TxValidationState tx_state;\n             if (!Consensus::CheckTxInputs(tx, tx_state, view, pindex-&gt;nHeight, txfee)) {\n                 // Any transaction validation failure in ConnectBlock is a block consensus failure\n                 state.Invalid(BlockValidationResult::BLOCK_CONSENSUS,\n                               tx_state.GetRejectReason(),\n                               tx_state.GetDebugMessage() + \" in transaction \" + tx.GetHash().ToString());\n                 break;\n             }\n             nFees += txfee;\n             if (!MoneyRange(nFees)) {\n                 state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, \"bad-txns-accumulated-fee-outofrange\",\n                               \"accumulated fee in the block out of range\");\n                 break;\n             }\n \n             // Check that transaction is BIP68 final\n             // BIP68 lock checks (as opposed to nLockTime checks) must\n             // be in ConnectBlock because they require the UTXO set\n             prevheights.resize(tx.vin.size());\n             for (size_t j = 0; j &lt; tx.vin.size(); j++) {\n                 prevheights[j] = view.AccessCoin(tx.vin[j].prevout).nHeight;\n             }\n \n             if (!SequenceLocks(tx, nLockTimeFlags, prevheights, *pindex)) {\n                 state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, \"bad-txns-nonfinal\",\n                               \"contains a non-BIP68-final transaction \" + tx.GetHash().ToString());\n                 break;\n             }\n         }\n \n         // GetTransactionSigOpCost counts 3 types of sigops:\n         // * legacy (always)\n         // * p2sh (when P2SH enabled in flags and excludes coinbase)\n         // * witness (when witness enabled in flags and excludes coinbase)\n         nSigOpsCost += GetTransactionSigOpCost(tx, view, flags);\n         if (nSigOpsCost &gt; MAX_BLOCK_SIGOPS_COST) {\n             state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, \"bad-blk-sigops\", \"too many sigops\");\n             break;\n         }\n \n         if (!tx.IsCoinBase() &amp;&amp; fScriptChecks)\n         {\n             bool fCacheResults = fJustCheck; /* Don't cache results if we're actually connecting blocks (still consult the cache, though) */\n             bool tx_ok;\n             TxValidationState tx_state;\n             // If CheckInputScripts is called with a pointer to a checks vector, the resulting checks are appended to it. In that case\n             // they need to be added to control which runs them asynchronously. Otherwise, CheckInputScripts runs the checks before returning.\n             if (control) {\n                 std::vector vChecks;\n                 tx_ok = CheckInputScripts(tx, tx_state, view, flags, fCacheResults, fCacheResults, txsdata[i], m_chainman.m_validation_cache, &amp;vChecks);\n                 if (tx_ok) control-&gt;Add(std::move(vChecks));\n             } else {\n                 tx_ok = CheckInputScripts(tx, tx_state, view, flags, fCacheResults, fCacheResults, txsdata[i], m_chainman.m_validation_cache);\n             }\n             if (!tx_ok) {\n                 // Any transaction validation failure in ConnectBlock is a block consensus failure\n                 state.Invalid(BlockValidationResult::BLOCK_CONSENSUS,\n                               tx_state.GetRejectReason(), tx_state.GetDebugMessage());\n                 break;\n             }\n         }\n \n         CTxUndo undoDummy;\n         if (i &gt; 0) {\n             blockundo.vtxundo.emplace_back();\n         }\n         UpdateCoins(tx, view, i == 0 ? undoDummy : blockundo.vtxundo.back(), pindex-&gt;nHeight);\n     }\n     const auto time_3{SteadyClock::now()};\n     m_chainman.time_connect += time_3 - time_2;\n     LogDebug(BCLog::BENCH, \"      - Connect %u transactions: %.2fms (%.3fms/tx, %.3fms/txin) [%.2fs (%.2fms/blk)]\\n\", (unsigned)block.vtx.size(),\n              Ticks(time_3 - time_2), Ticks(time_3 - time_2) / block.vtx.size(),\n              nInputs &lt;= 1 ? 0 : Ticks(time_3 - time_2) / (nInputs - 1),\n              Ticks(m_chainman.time_connect),\n              Ticks(m_chainman.time_connect) / m_chainman.num_blocks_total);\n \n     CAmount blockReward = nFees + GetBlockSubsidy(pindex-&gt;nHeight, params.GetConsensus());\n     if (block.vtx[0]-&gt;GetValueOut() &gt; blockReward &amp;&amp; state.IsValid()) {\n         state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, \"bad-cb-amount\",\n                       strprintf(\"coinbase pays too much (actual=%d vs limit=%d)\", block.vtx[0]-&gt;GetValueOut(), blockReward));\n     }\n     if (control) {\n         auto parallel_result = control-&gt;Complete();\n         if (parallel_result.has_value() &amp;&amp; state.IsValid()) {\n             state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, strprintf(\"block-script-verify-flag-failed (%s)\", ScriptErrorString(parallel_result-&gt;first)), parallel_result-&gt;second);\n         }\n     }\n     if (!state.IsValid()) {\n         LogInfo(\"Block validation error: %s\", state.ToString());\n         return false;\n     }\n     const auto time_4{SteadyClock::now()};\n     m_chainman.time_verify += time_4 - time_2;\n     LogDebug(BCLog::BENCH, \"    - Verify %u txins: %.2fms (%.3fms/txin) [%.2fs (%.2fms/blk)]\\n\", nInputs - 1,\n              Ticks(time_4 - time_2),\n              nInputs &lt;= 1 ? 0 : Ticks(time_4 - time_2) / (nInputs - 1),\n              Ticks(m_chainman.time_verify),\n              Ticks(m_chainman.time_verify) / m_chainman.num_blocks_total);\n \n     if (fJustCheck) {\n         return true;\n     }\n \n     if (!m_blockman.WriteBlockUndo(blockundo, state, *pindex)) {\n         return false;\n     }\n \n     const auto time_5{SteadyClock::now()};\n     m_chainman.time_undo += time_5 - time_4;\n     LogDebug(BCLog::BENCH, \"    - Write undo data: %.2fms [%.2fs (%.2fms/blk)]\\n\",\n              Ticks(time_5 - time_4),\n              Ticks(m_chainman.time_undo),\n              Ticks(m_chainman.time_undo) / m_chainman.num_blocks_total);\n \n     if (!pindex-&gt;IsValid(BLOCK_VALID_SCRIPTS)) {\n         pindex-&gt;RaiseValidity(BLOCK_VALID_SCRIPTS);\n         m_blockman.m_dirty_blockindex.insert(pindex);\n     }\n \n     // add this block to the view's block chain\n     view.SetBestBlock(pindex-&gt;GetBlockHash());\n \n     const auto time_6{SteadyClock::now()};\n     m_chainman.time_index += time_6 - time_5;\n     LogDebug(BCLog::BENCH, \"    - Index writing: %.2fms [%.2fs (%.2fms/blk)]\\n\",\n              Ticks(time_6 - time_5),\n              Ticks(m_chainman.time_index),\n              Ticks(m_chainman.time_index) / m_chainman.num_blocks_total);\n \n     TRACEPOINT(validation, block_connected,\n         block_hash.data(),\n         pindex-&gt;nHeight,\n         block.vtx.size(),\n         nInputs,\n         nSigOpsCost,\n         Ticks(time_5 - time_start)\n     );\n \n     return true;\n }\n \n CoinsCacheSizeState Chainstate::GetCoinsCacheSizeState()\n {\n     AssertLockHeld(::cs_main);\n     return this-&gt;GetCoinsCacheSizeState(\n         m_coinstip_cache_size_bytes,\n         m_mempool ? m_mempool-&gt;m_opts.max_size_bytes : 0);\n }\n \n CoinsCacheSizeState Chainstate::GetCoinsCacheSizeState(\n     size_t max_coins_cache_size_bytes,\n     size_t max_mempool_size_bytes)\n {\n     AssertLockHeld(::cs_main);\n     const int64_t nMempoolUsage = m_mempool ? m_mempool-&gt;DynamicMemoryUsage() : 0;\n     int64_t cacheSize = CoinsTip().DynamicMemoryUsage();\n     int64_t nTotalSpace =\n         max_coins_cache_size_bytes + std::max(int64_t(max_mempool_size_bytes) - nMempoolUsage, 0);\n \n     if (cacheSize &gt; nTotalSpace) {\n         LogInfo(\"Cache size (%s) exceeds total space (%s)\\n\", cacheSize, nTotalSpace);\n         return CoinsCacheSizeState::CRITICAL;\n     } else if (cacheSize &gt; LargeCoinsCacheThreshold(nTotalSpace)) {\n         return CoinsCacheSizeState::LARGE;\n     }\n     return CoinsCacheSizeState::OK;\n }\n \n bool Chainstate::FlushStateToDisk(\n     BlockValidationState &amp;state,\n     FlushStateMode mode,\n     int nManualPruneHeight)\n {\n     LOCK(cs_main);\n     assert(this-&gt;CanFlushToDisk());\n     std::set setFilesToPrune;\n     bool full_flush_completed = false;\n \n     [[maybe_unused]] const size_t coins_count{CoinsTip().GetCacheSize()};\n     [[maybe_unused]] const size_t coins_mem_usage{CoinsTip().DynamicMemoryUsage()};\n \n     try {\n     {\n         bool fFlushForPrune = false;\n \n         CoinsCacheSizeState cache_state = GetCoinsCacheSizeState();\n         LOCK(m_blockman.cs_LastBlockFile);\n         if (m_blockman.IsPruneMode() &amp;&amp; (m_blockman.m_check_for_pruning || nManualPruneHeight &gt; 0) &amp;&amp; m_chainman.m_blockman.m_blockfiles_indexed) {\n             // make sure we don't prune above any of the prune locks bestblocks\n             // pruning is height-based\n             int last_prune{m_chain.Height()}; // last height we can prune\n             std::optional limiting_lock; // prune lock that actually was the limiting factor, only used for logging\n \n             for (const auto&amp; prune_lock : m_blockman.m_prune_locks) {\n                 if (prune_lock.second.height_first == std::numeric_limits::max()) continue;\n                 // Remove the buffer and one additional block here to get actual height that is outside of the buffer\n                 const int lock_height{prune_lock.second.height_first - PRUNE_LOCK_BUFFER - 1};\n                 last_prune = std::max(1, std::min(last_prune, lock_height));\n                 if (last_prune == lock_height) {\n                     limiting_lock = prune_lock.first;\n                 }\n             }\n \n             if (limiting_lock) {\n                 LogDebug(BCLog::PRUNE, \"%s limited pruning to height %d\\n\", limiting_lock.value(), last_prune);\n             }\n \n             if (nManualPruneHeight &gt; 0) {\n                 LOG_TIME_MILLIS_WITH_CATEGORY(\"find files to prune (manual)\", BCLog::BENCH);\n \n                 m_blockman.FindFilesToPruneManual(\n                     setFilesToPrune,\n                     std::min(last_prune, nManualPruneHeight),\n                     *this);\n             } else {\n                 LOG_TIME_MILLIS_WITH_CATEGORY(\"find files to prune\", BCLog::BENCH);\n \n                 m_blockman.FindFilesToPrune(setFilesToPrune, last_prune, *this, m_chainman);\n                 m_blockman.m_check_for_pruning = false;\n             }\n             if (!setFilesToPrune.empty()) {\n                 fFlushForPrune = true;\n                 if (!m_blockman.m_have_pruned) {\n                     m_blockman.m_block_tree_db-&gt;WriteFlag(\"prunedblockfiles\", true);\n                     m_blockman.m_have_pruned = true;\n                 }\n             }\n         }\n         const auto nNow{NodeClock::now()};\n         // The cache is large and we're within 10% and 10 MiB of the limit, but we have time now (not in the middle of a block processing).\n         bool fCacheLarge = mode == FlushStateMode::PERIODIC &amp;&amp; cache_state &gt;= CoinsCacheSizeState::LARGE;\n         // The cache is over the limit, we have to write now.\n         bool fCacheCritical = mode == FlushStateMode::IF_NEEDED &amp;&amp; cache_state &gt;= CoinsCacheSizeState::CRITICAL;\n         // It's been a while since we wrote the block index and chain state to disk. Do this frequently, so we don't need to redownload or reindex after a crash.\n         bool fPeriodicWrite = mode == FlushStateMode::PERIODIC &amp;&amp; nNow &gt;= m_next_write;\n         const auto empty_cache{(mode == FlushStateMode::FORCE_FLUSH) || fCacheLarge || fCacheCritical};\n         // Combine all conditions that result in a write to disk.\n         bool should_write = (mode == FlushStateMode::FORCE_SYNC) || empty_cache || fPeriodicWrite || fFlushForPrune;\n         // Write blocks, block index and best chain related state to disk.\n         if (should_write) {\n-            LogDebug(BCLog::COINDB, \"Writing chainstate to disk: flush mode=%s, prune=%d, large=%d, critical=%d, periodic=%d\",\n-                     FlushStateModeNames[size_t(mode)], fFlushForPrune, fCacheLarge, fCacheCritical, fPeriodicWrite);\n+            LogDebug(BCLog::COINDB, \"Writing new chainstate to disk: flush mode=%s, prune=%d, large=%d, critical=%d, periodic=%d, empty=%d\",\n+                     FlushStateModeNames[size_t(mode)], fFlushForPrune, fCacheLarge, fCacheCritical, fPeriodicWrite, empty_cache);\n \n             // Ensure we can write block index\n             if (!CheckDiskSpace(m_blockman.m_opts.blocks_dir)) {\n                 return FatalError(m_chainman.GetNotifications(), state, _(\"Disk space is too low!\"));\n             }\n             {\n                 LOG_TIME_MILLIS_WITH_CATEGORY(\"write block and undo data to disk\", BCLog::BENCH);\n \n                 // First make sure all block and undo data is flushed to disk.\n                 // TODO: Handle return error, or add detailed comment why it is\n                 // safe to not return an error upon failure.\n                 if (!m_blockman.FlushChainstateBlockFile(m_chain.Height())) {\n                     LogWarning(\"%s: Failed to flush block file.\\n\", __func__);\n                 }\n             }\n \n             // Then update all block file information (which may refer to block and undo files).\n             {\n                 LOG_TIME_MILLIS_WITH_CATEGORY(\"write block index to disk\", BCLog::BENCH);\n \n                 m_blockman.WriteBlockIndexDB();\n             }\n             // Finally remove any pruned files\n             if (fFlushForPrune) {\n                 LOG_TIME_MILLIS_WITH_CATEGORY(\"unlink pruned files\", BCLog::BENCH);\n \n                 m_blockman.UnlinkPrunedFiles(setFilesToPrune);\n             }\n \n             if (!CoinsTip().GetBestBlock().IsNull()) {\n                 // Typical Coin structures on disk are around 48 bytes in size.\n                 // Pushing a new one to the database can cause it to be written\n                 // twice (once in the log, and once in the tables). This is already\n                 // an overestimation, as most will delete an existing entry or\n                 // overwrite one. Still, use a conservative safety factor of 2.\n                 if (!CheckDiskSpace(m_chainman.m_options.datadir, 48 * 2 * 2 * CoinsTip().GetDirtyCount())) {\n                     return FatalError(m_chainman.GetNotifications(), state, _(\"Disk space is too low!\"));\n                 }\n                 // Flush the chainstate (which may refer to block index entries).\n                 empty_cache ? CoinsTip().Flush() : CoinsTip().Sync();\n                 full_flush_completed = true;\n                 TRACEPOINT(utxocache, flush,\n                     int64_t{Ticks(NodeClock::now() - nNow)},\n                     (uint32_t)mode,\n                     (uint64_t)coins_count,\n                     (uint64_t)coins_mem_usage,\n                     (bool)fFlushForPrune);\n             }\n         }\n \n         if (should_write || m_next_write == NodeClock::time_point::max()) {\n             constexpr auto range{DATABASE_WRITE_INTERVAL_MAX - DATABASE_WRITE_INTERVAL_MIN};\n             m_next_write = FastRandomContext().rand_uniform_delay(NodeClock::now() + DATABASE_WRITE_INTERVAL_MIN, range);\n         }\n     }\n     if (full_flush_completed &amp;&amp; m_chainman.m_options.signals) {\n         // Update best block in wallet (so we can detect restored wallets).\n         m_chainman.m_options.signals-&gt;ChainStateFlushed(this-&gt;GetRole(), GetLocator(m_chain.Tip()));\n     }\n     } catch (const std::runtime_error&amp; e) {\n         return FatalError(m_chainman.GetNotifications(), state, strprintf(_(\"System error while flushing: %s\"), e.what()));\n     }\n     return true;\n }\n \n void Chainstate::ForceFlushStateToDisk(bool wipe_cache)\n {\n     BlockValidationState state;\n     if (!this-&gt;FlushStateToDisk(state, wipe_cache ? FlushStateMode::FORCE_FLUSH : FlushStateMode::FORCE_SYNC)) {\n         LogWarning(\"Failed to force flush state (%s)\", state.ToString());\n     }\n }\n \n void Chainstate::PruneAndFlush()\n {\n     BlockValidationState state;\n     m_blockman.m_check_for_pruning = true;\n     if (!this-&gt;FlushStateToDisk(state, FlushStateMode::NONE)) {\n         LogWarning(\"Failed to flush state (%s)\", state.ToString());\n     }\n }\n \n static void UpdateTipLog(\n     const ChainstateManager&amp; chainman,\n     const CCoinsViewCache&amp; coins_tip,\n     const CBlockIndex* tip,\n     const std::string&amp; func_name,\n     const std::string&amp; prefix,\n     const std::string&amp; warning_messages,\n     const bool background_validation) EXCLUSIVE_LOCKS_REQUIRED(::cs_main)\n {\n \n     AssertLockHeld(::cs_main);\n \n     // Disable rate limiting in LogPrintLevel_ so this source location may log during IBD.\n     LogPrintLevel_(BCLog::LogFlags::ALL, util::log::Level::Info, /*should_ratelimit=*/false, \"%s%s: new best=%s height=%d version=0x%08x log2_work=%f tx=%lu date='%s' progress=%f cache=%.1fMiB(%utxo)%s\\n\",\n                    prefix, func_name,\n                    tip-&gt;GetBlockHash().ToString(), tip-&gt;nHeight, tip-&gt;nVersion,\n                    log(tip-&gt;nChainWork.getdouble()) / log(2.0), tip-&gt;m_chain_tx_count,\n                    FormatISO8601DateTime(tip-&gt;GetBlockTime()),\n                    background_validation ? chainman.GetBackgroundVerificationProgress(*tip) : chainman.GuessVerificationProgress(tip),\n                    coins_tip.DynamicMemoryUsage() / double(1_MiB),\n                    coins_tip.GetCacheSize(),\n                    !warning_messages.empty() ? strprintf(\" warning='%s'\", warning_messages) : \"\");\n }\n \n void Chainstate::UpdateTip(const CBlockIndex* pindexNew)\n {\n     AssertLockHeld(::cs_main);\n     const auto&amp; coins_tip = this-&gt;CoinsTip();\n \n     // The remainder of the function isn't relevant if we are not acting on\n     // the active chainstate, so return if need be.\n     if (this != &amp;m_chainman.ActiveChainstate()) {\n         // Only log every so often so that we don't bury log messages at the tip.\n         constexpr int BACKGROUND_LOG_INTERVAL = 2000;\n         if (pindexNew-&gt;nHeight % BACKGROUND_LOG_INTERVAL == 0) {\n             UpdateTipLog(m_chainman, coins_tip, pindexNew, __func__, \"[background validation] \", \"\", /*background_validation=*/true);\n         }\n         return;\n     }\n \n     // New best block\n     if (m_mempool) {\n         m_mempool-&gt;AddTransactionsUpdated(1);\n     }\n \n     std::vector warning_messages;\n     if (!m_chainman.IsInitialBlockDownload()) {\n         auto bits = m_chainman.m_versionbitscache.CheckUnknownActivations(pindexNew, m_chainman.GetParams());\n         for (auto [bit, active] : bits) {\n             const bilingual_str warning = strprintf(_(\"Unknown new rules activated (versionbit %i)\"), bit);\n             if (active) {\n                 m_chainman.GetNotifications().warningSet(kernel::Warning::UNKNOWN_NEW_RULES_ACTIVATED, warning);\n             } else {\n                 warning_messages.push_back(warning);\n             }\n         }\n     }\n     UpdateTipLog(m_chainman, coins_tip, pindexNew, __func__, \"\",\n                  util::Join(warning_messages, Untranslated(\", \")).original, /*background_validation=*/false);\n }\n \n /** Disconnect m_chain's tip.\n   * After calling, the mempool will be in an inconsistent state, with\n   * transactions from disconnected blocks being added to disconnectpool.  You\n   * should make the mempool consistent again by calling MaybeUpdateMempoolForReorg.\n   * with cs_main held.\n   *\n   * If disconnectpool is nullptr, then no disconnected transactions are added to\n   * disconnectpool (note that the caller is responsible for mempool consistency\n   * in any case).\n   */\n bool Chainstate::DisconnectTip(BlockValidationState&amp; state, DisconnectedBlockTransactions* disconnectpool)\n {\n     AssertLockHeld(cs_main);\n     if (m_mempool) AssertLockHeld(m_mempool-&gt;cs);\n \n     CBlockIndex *pindexDelete = m_chain.Tip();\n     assert(pindexDelete);\n     assert(pindexDelete-&gt;pprev);\n     // Read block from disk.\n     std::shared_ptr pblock = std::make_shared();\n     CBlock&amp; block = *pblock;\n     if (!m_blockman.ReadBlock(block, *pindexDelete)) {\n         LogError(\"DisconnectTip(): Failed to read block\\n\");\n         return false;\n     }\n     // Apply the block atomically to the chain state.\n     const auto time_start{SteadyClock::now()};\n     {\n         CCoinsViewCache view(&amp;CoinsTip());\n         assert(view.GetBestBlock() == pindexDelete-&gt;GetBlockHash());\n         if (DisconnectBlock(block, pindexDelete, view) != DISCONNECT_OK) {\n             LogError(\"DisconnectTip(): DisconnectBlock %s failed\\n\", pindexDelete-&gt;GetBlockHash().ToString());\n             return false;\n         }\n         view.Flush(/*reallocate_cache=*/false); // local CCoinsViewCache goes out of scope\n     }\n     LogDebug(BCLog::BENCH, \"- Disconnect block: %.2fms\\n\",\n              Ticks(SteadyClock::now() - time_start));\n \n     {\n         // Prune locks that began at or after the tip should be moved backward so they get a chance to reorg\n         const int max_height_first{pindexDelete-&gt;nHeight - 1};\n         for (auto&amp; prune_lock : m_blockman.m_prune_locks) {\n             if (prune_lock.second.height_first &lt;= max_height_first) continue;\n \n             prune_lock.second.height_first = max_height_first;\n             LogDebug(BCLog::PRUNE, \"%s prune lock moved back to %d\\n\", prune_lock.first, max_height_first);\n         }\n     }\n \n     // Write the chain state to disk, if necessary.\n     if (!FlushStateToDisk(state, FlushStateMode::IF_NEEDED)) {\n         return false;\n     }\n \n     if (disconnectpool &amp;&amp; m_mempool) {\n         // Save transactions to re-add to mempool at end of reorg. If any entries are evicted for\n         // exceeding memory limits, remove them and their descendants from the mempool.\n         for (auto&amp;&amp; evicted_tx : disconnectpool-&gt;AddTransactionsFromBlock(block.vtx)) {\n             m_mempool-&gt;removeRecursive(*evicted_tx, MemPoolRemovalReason::REORG);\n         }\n     }\n \n     m_chain.SetTip(*pindexDelete-&gt;pprev);\n     m_chainman.UpdateIBDStatus();\n \n     UpdateTip(pindexDelete-&gt;pprev);\n     // Let wallets know transactions went from 1-confirmed to\n     // 0-confirmed or conflicted:\n     if (m_chainman.m_options.signals) {\n         m_chainman.m_options.signals-&gt;BlockDisconnected(std::move(pblock), pindexDelete);\n     }\n     return true;\n }\n \n struct ConnectedBlock {\n     const CBlockIndex* pindex;\n     std::shared_ptr pblock;\n };\n \n /**\n  * Connect a new block to m_chain. block_to_connect is either nullptr or a pointer to a CBlock\n  * corresponding to pindexNew, to bypass loading it again from disk.\n  *\n  * The block is added to connected_blocks if connection succeeds.\n  */\n bool Chainstate::ConnectTip(\n     BlockValidationState&amp; state,\n     CBlockIndex* pindexNew,\n     std::shared_ptr block_to_connect,\n     std::vector&amp; connected_blocks,\n     DisconnectedBlockTransactions&amp; disconnectpool)\n {\n     AssertLockHeld(cs_main);\n     if (m_mempool) AssertLockHeld(m_mempool-&gt;cs);\n \n     assert(pindexNew-&gt;pprev == m_chain.Tip());\n     // Read block from disk.\n     const auto time_1{SteadyClock::now()};\n     if (!block_to_connect) {\n         std::shared_ptr pblockNew = std::make_shared();\n         if (!m_blockman.ReadBlock(*pblockNew, *pindexNew)) {\n             return FatalError(m_chainman.GetNotifications(), state, _(\"Failed to read block.\"));\n         }\n         block_to_connect = std::move(pblockNew);\n     } else {\n         LogDebug(BCLog::BENCH, \"  - Using cached block\\n\");\n     }\n     // Apply the block atomically to the chain state.\n     const auto time_2{SteadyClock::now()};\n     SteadyClock::time_point time_3;\n     // When adding aggregate statistics in the future, keep in mind that\n     // num_blocks_total may be zero until the ConnectBlock() call below.\n     LogDebug(BCLog::BENCH, \"  - Load block from disk: %.2fms\\n\",\n              Ticks(time_2 - time_1));\n     {\n         CCoinsViewCache&amp; view{*m_coins_views-&gt;m_connect_block_view};\n         const auto reset_guard{view.CreateResetGuard()};\n         bool rv = ConnectBlock(*block_to_connect, state, pindexNew, view);\n         if (m_chainman.m_options.signals) {\n             m_chainman.m_options.signals-&gt;BlockChecked(block_to_connect, state);\n         }\n         if (!rv) {\n             if (state.IsInvalid())\n                 InvalidBlockFound(pindexNew, state);\n             LogError(\"%s: ConnectBlock %s failed, %s\\n\", __func__, pindexNew-&gt;GetBlockHash().ToString(), state.ToString());\n             return false;\n         }\n         time_3 = SteadyClock::now();\n         m_chainman.time_connect_total += time_3 - time_2;\n         assert(m_chainman.num_blocks_total &gt; 0);\n         LogDebug(BCLog::BENCH, \"  - Connect total: %.2fms [%.2fs (%.2fms/blk)]\\n\",\n                  Ticks(time_3 - time_2),\n                  Ticks(m_chainman.time_connect_total),\n                  Ticks(m_chainman.time_connect_total) / m_chainman.num_blocks_total);\n         view.Flush(/*reallocate_cache=*/false); // No need to reallocate since it only has capacity for 1 block\n     }\n     const auto time_4{SteadyClock::now()};\n     m_chainman.time_flush += time_4 - time_3;\n     LogDebug(BCLog::BENCH, \"  - Flush: %.2fms [%.2fs (%.2fms/blk)]\\n\",\n              Ticks(time_4 - time_3),\n              Ticks(m_chainman.time_flush),\n              Ticks(m_chainman.time_flush) / m_chainman.num_blocks_total);\n     // Write the chain state to disk, if necessary.\n     if (!FlushStateToDisk(state, FlushStateMode::IF_NEEDED)) {\n         return false;\n     }\n     const auto time_5{SteadyClock::now()};\n     m_chainman.time_chainstate += time_5 - time_4;\n     LogDebug(BCLog::BENCH, \"  - Writing chainstate: %.2fms [%.2fs (%.2fms/blk)]\\n\",\n              Ticks(time_5 - time_4),\n              Ticks(m_chainman.time_chainstate),\n              Ticks(m_chainman.time_chainstate) / m_chainman.num_blocks_total);\n     // Remove conflicting transactions from the mempool.;\n     if (m_mempool) {\n         m_mempool-&gt;removeForBlock(block_to_connect-&gt;vtx, pindexNew-&gt;nHeight);\n         disconnectpool.removeForBlock(block_to_connect-&gt;vtx);\n     }\n     // Update m_chain &amp; related variables.\n     m_chain.SetTip(*pindexNew);\n     m_chainman.UpdateIBDStatus();\n     UpdateTip(pindexNew);\n \n     const auto time_6{SteadyClock::now()};\n     m_chainman.time_post_connect += time_6 - time_5;\n     m_chainman.time_total += time_6 - time_1;\n     LogDebug(BCLog::BENCH, \"  - Connect postprocess: %.2fms [%.2fs (%.2fms/blk)]\\n\",\n              Ticks(time_6 - time_5),\n              Ticks(m_chainman.time_post_connect),\n              Ticks(m_chainman.time_post_connect) / m_chainman.num_blocks_total);\n     LogDebug(BCLog::BENCH, \"- Connect block: %.2fms [%.2fs (%.2fms/blk)]\\n\",\n              Ticks(time_6 - time_1),\n              Ticks(m_chainman.time_total),\n              Ticks(m_chainman.time_total) / m_chainman.num_blocks_total);\n \n     // See if this chainstate has reached a target block and can be used to\n     // validate an assumeutxo snapshot. If it can, hashing the UTXO database\n     // will be slow, and cs_main could remain locked here for several minutes.\n     // If the snapshot is validated, the UTXO hash will be saved to\n     // this-&gt;m_target_utxohash, causing HistoricalChainstate() to return null\n     // and this chainstate to no longer be used. ActivateBestChain() will also\n     // stop connecting blocks to this chainstate because this-&gt;ReachedTarget()\n     // will be true and this-&gt;setBlockIndexCandidates will not have additional\n     // blocks.\n     Chainstate&amp; current_cs{m_chainman.CurrentChainstate()};\n     m_chainman.MaybeValidateSnapshot(*this, current_cs);\n \n     connected_blocks.emplace_back(pindexNew, std::move(block_to_connect));\n     return true;\n }\n \n /**\n  * Return the tip of the chain with the most work in it, that isn't\n  * known to be invalid (it's however far from certain to be valid).\n  */\n CBlockIndex* Chainstate::FindMostWorkChain()\n {\n     AssertLockHeld(::cs_main);\n     do {\n         CBlockIndex *pindexNew = nullptr;\n \n         // Find the best candidate header.\n         {\n             std::set::reverse_iterator it = setBlockIndexCandidates.rbegin();\n             if (it == setBlockIndexCandidates.rend())\n                 return nullptr;\n             pindexNew = *it;\n         }\n \n         // Check whether all blocks on the path between the currently active chain and the candidate are valid.\n         // Just going until the active chain is an optimization, as we know all blocks in it are valid already.\n         bool fInvalidAncestor = false;\n         for (CBlockIndex *pindexTest = pindexNew; pindexTest &amp;&amp; !m_chain.Contains(*pindexTest); pindexTest = pindexTest-&gt;pprev) {\n             assert(pindexTest-&gt;HaveNumChainTxs() || pindexTest-&gt;nHeight == 0);\n \n             // Pruned nodes may have entries in setBlockIndexCandidates for\n             // which block files have been deleted.  Remove those as candidates\n             // for the most work chain if we come across them; we can't switch\n             // to a chain unless we have all the non-active-chain parent blocks.\n             bool fFailedChain = pindexTest-&gt;nStatus &amp; BLOCK_FAILED_VALID;\n             bool fMissingData = !(pindexTest-&gt;nStatus &amp; BLOCK_HAVE_DATA);\n             if (fFailedChain || fMissingData) {\n                 // Candidate chain is not usable (either invalid or missing data)\n                 if (fFailedChain &amp;&amp; (m_chainman.m_best_invalid == nullptr || pindexNew-&gt;nChainWork &gt; m_chainman.m_best_invalid-&gt;nChainWork)) {\n                     m_chainman.m_best_invalid = pindexNew;\n                 }\n                 // Remove the entire chain from the set.\n                 for (CBlockIndex *pindexFailed = pindexNew; pindexFailed != pindexTest; pindexFailed = pindexFailed-&gt;pprev) {\n                     if (fMissingData &amp;&amp; !fFailedChain) {\n                         // If we're missing data and not a descendant of an invalid block,\n                         // then add back to m_blocks_unlinked, so that if the block arrives in the future\n                         // we can try adding to setBlockIndexCandidates again.\n                         m_blockman.m_blocks_unlinked.insert(\n                             std::make_pair(pindexFailed-&gt;pprev, pindexFailed));\n                     }\n                     setBlockIndexCandidates.erase(pindexFailed);\n                 }\n                 setBlockIndexCandidates.erase(pindexTest);\n                 fInvalidAncestor = true;\n                 break;\n             }\n         }\n         if (!fInvalidAncestor)\n             return pindexNew;\n     } while(true);\n }\n \n /** Delete all entries in setBlockIndexCandidates that are worse than the current tip. */\n void Chainstate::PruneBlockIndexCandidates() {\n     // Note that we can't delete the current block itself, as we may need to return to it later in case a\n     // reorganization to a better block fails.\n     std::set::iterator it = setBlockIndexCandidates.begin();\n     while (it != setBlockIndexCandidates.end() &amp;&amp; setBlockIndexCandidates.value_comp()(*it, m_chain.Tip())) {\n         setBlockIndexCandidates.erase(it++);\n     }\n     // Either the current tip or a successor of it we're working towards is left in setBlockIndexCandidates.\n     assert(!setBlockIndexCandidates.empty());\n }\n \n /**\n  * Try to make some progress towards making index_most_work the active block.\n  * pblock is either nullptr or a pointer to a CBlock corresponding to index_most_work.\n  *\n  * @returns true unless a system error occurred\n  */\n bool Chainstate::ActivateBestChainStep(BlockValidationState&amp; state, CBlockIndex&amp; index_most_work, const std::shared_ptr&amp; pblock, bool&amp; fInvalidFound, std::vector&amp; connected_blocks)\n {\n     AssertLockHeld(cs_main);\n     if (m_mempool) AssertLockHeld(m_mempool-&gt;cs);\n \n     const CBlockIndex* pindexOldTip = m_chain.Tip();\n     const CBlockIndex* pindexFork = m_chain.FindFork(index_most_work);\n \n     // Disconnect active blocks which are no longer in the best chain.\n     bool fBlocksDisconnected = false;\n     DisconnectedBlockTransactions disconnectpool{MAX_DISCONNECTED_TX_POOL_BYTES};\n     while (m_chain.Tip() &amp;&amp; m_chain.Tip() != pindexFork) {\n         if (!DisconnectTip(state, &amp;disconnectpool)) {\n             // This is likely a fatal error, but keep the mempool consistent,\n             // just in case. Only remove from the mempool in this case.\n             MaybeUpdateMempoolForReorg(disconnectpool, false);\n \n             // If we're unable to disconnect a block during normal operation,\n             // then that is a failure of our local system -- we should abort\n             // rather than stay on a less work chain.\n             FatalError(m_chainman.GetNotifications(), state, _(\"Failed to disconnect block.\"));\n             return false;\n         }\n         fBlocksDisconnected = true;\n     }\n \n     // Build list of new blocks to connect (in descending height order).\n     std::vector vpindexToConnect;\n     bool fContinue = true;\n     int nHeight = pindexFork ? pindexFork-&gt;nHeight : -1;\n     while (fContinue &amp;&amp; nHeight != index_most_work.nHeight) {\n         // Don't iterate the entire list of potential improvements toward the best tip, as we likely only need\n         // a few blocks along the way.\n         int nTargetHeight = std::min(nHeight + 32, index_most_work.nHeight);\n         vpindexToConnect.clear();\n         vpindexToConnect.reserve(nTargetHeight - nHeight);\n         CBlockIndex* pindexIter = index_most_work.GetAncestor(nTargetHeight);\n         while (pindexIter &amp;&amp; pindexIter-&gt;nHeight != nHeight) {\n             vpindexToConnect.push_back(pindexIter);\n             pindexIter = pindexIter-&gt;pprev;\n         }\n         nHeight = nTargetHeight;\n \n         // Connect new blocks.\n         for (CBlockIndex* pindexConnect : vpindexToConnect | std::views::reverse) {\n             if (!ConnectTip(state, pindexConnect, pindexConnect == &amp;index_most_work ? pblock : std::shared_ptr(), connected_blocks, disconnectpool)) {\n                 if (state.IsInvalid()) {\n                     // The block violates a consensus rule.\n                     if (state.GetResult() != BlockValidationResult::BLOCK_MUTATED) {\n                         InvalidChainFound(vpindexToConnect.front());\n                     }\n                     state = BlockValidationState();\n                     fInvalidFound = true;\n                     fContinue = false;\n                     break;\n                 } else {\n                     // A system error occurred (disk space, database error, ...).\n                     // Make the mempool consistent with the current tip, just in case\n                     // any observers try to use it before shutdown.\n                     MaybeUpdateMempoolForReorg(disconnectpool, false);\n                     return false;\n                 }\n             } else {\n                 PruneBlockIndexCandidates();\n                 if (!pindexOldTip || m_chain.Tip()-&gt;nChainWork &gt; pindexOldTip-&gt;nChainWork) {\n                     // We're in a better position than we were. Return temporarily to release the lock.\n                     fContinue = false;\n                     break;\n                 }\n             }\n         }\n     }\n \n     if (fBlocksDisconnected) {\n         // If any blocks were disconnected, disconnectpool may be non empty.  Add\n         // any disconnected transactions back to the mempool.\n         MaybeUpdateMempoolForReorg(disconnectpool, true);\n     }\n     if (m_mempool) m_mempool-&gt;check(this-&gt;CoinsTip(), this-&gt;m_chain.Height() + 1);\n \n     CheckForkWarningConditions();\n \n     return true;\n }\n \n static SynchronizationState GetSynchronizationState(bool init, bool blockfiles_indexed)\n {\n     if (!init) return SynchronizationState::POST_INIT;\n     if (!blockfiles_indexed) return SynchronizationState::INIT_REINDEX;\n     return SynchronizationState::INIT_DOWNLOAD;\n }\n \n void ChainstateManager::UpdateIBDStatus()\n {\n     AssertLockHeld(cs_main);\n     if (!m_cached_is_ibd.load(std::memory_order_relaxed)) return;\n     if (m_blockman.LoadingBlocks()) return;\n     if (!CurrentChainstate().m_chain.IsTipRecent(MinimumChainWork(), m_options.max_tip_age)) return;\n     LogInfo(\"Leaving InitialBlockDownload (latching to false)\");\n     m_cached_is_ibd.store(false, std::memory_order_relaxed);\n }\n \n bool ChainstateManager::NotifyHeaderTip()\n {\n     bool fNotify = false;\n     bool fInitialBlockDownload = false;\n     CBlockIndex* pindexHeader = nullptr;\n     {\n         LOCK(GetMutex());\n         pindexHeader = m_best_header;\n \n         if (pindexHeader != m_last_notified_header) {\n             fNotify = true;\n             fInitialBlockDownload = IsInitialBlockDownload();\n             m_last_notified_header = pindexHeader;\n         }\n     }\n     // Send block tip changed notifications without the lock held\n     if (fNotify) {\n         GetNotifications().headerTip(GetSynchronizationState(fInitialBlockDownload, m_blockman.m_blockfiles_indexed), pindexHeader-&gt;nHeight, pindexHeader-&gt;nTime, false);\n     }\n     return fNotify;\n }\n \n static void LimitValidationInterfaceQueue(ValidationSignals&amp; signals) LOCKS_EXCLUDED(cs_main) {\n     AssertLockNotHeld(cs_main);\n \n     if (signals.CallbacksPending() &gt; 10) {\n         signals.SyncWithValidationInterfaceQueue();\n     }\n }\n \n bool Chainstate::ActivateBestChain(BlockValidationState&amp; state, std::shared_ptr pblock)\n {\n     AssertLockNotHeld(m_chainstate_mutex);\n \n     // Note that while we're often called here from ProcessNewBlock, this is\n     // far from a guarantee. Things in the P2P/RPC will often end up calling\n     // us in the middle of ProcessNewBlock - do not assume pblock is set\n     // sanely for performance or correctness!\n     AssertLockNotHeld(::cs_main);\n \n     // ABC maintains a fair degree of expensive-to-calculate internal state\n     // because this function periodically releases cs_main so that it does not lock up other threads for too long\n     // during large connects - and to allow for e.g. the callback queue to drain\n     // we use m_chainstate_mutex to enforce mutual exclusion so that only one caller may execute this function at a time\n     LOCK(m_chainstate_mutex);\n \n     // Belt-and-suspenders check that we aren't attempting to advance the\n     // chainstate past the target block.\n     if (WITH_LOCK(::cs_main, return m_target_utxohash)) {\n         LogError(\"%s\", STR_INTERNAL_BUG(\"m_target_utxohash is set - this chainstate should not be in operation.\"));\n         return Assume(false);\n     }\n \n     CBlockIndex *pindexMostWork = nullptr;\n     CBlockIndex *pindexNewTip = nullptr;\n     bool exited_ibd{false};\n     do {\n         // Block until the validation queue drains. This should largely\n         // never happen in normal operation, however may happen during\n         // reindex, causing memory blowup if we run too far ahead.\n         // Note that if a validationinterface callback ends up calling\n         // ActivateBestChain this may lead to a deadlock! We should\n         // probably have a DEBUG_LOCKORDER test for this in the future.\n         if (m_chainman.m_options.signals) LimitValidationInterfaceQueue(*m_chainman.m_options.signals);\n \n         {\n             LOCK(cs_main);\n             {\n             // Lock transaction pool for at least as long as it takes for connected_blocks to be consumed\n             LOCK(MempoolMutex());\n             const bool was_in_ibd = m_chainman.IsInitialBlockDownload();\n             CBlockIndex* starting_tip = m_chain.Tip();\n             bool blocks_connected = false;\n             do {\n                 // We absolutely may not unlock cs_main until we've made forward progress\n                 // (with the exception of shutdown due to hardware issues, low disk space, etc).\n                 std::vector connected_blocks; // Destructed before cs_main is unlocked\n \n                 if (pindexMostWork == nullptr) {\n                     pindexMostWork = FindMostWorkChain();\n                 }\n \n                 // Whether we have anything to do at all.\n                 if (pindexMostWork == nullptr || pindexMostWork == m_chain.Tip()) {\n                     break;\n                 }\n \n                 bool fInvalidFound = false;\n                 std::shared_ptr nullBlockPtr;\n                 // BlockConnected signals must be sent for the original role;\n                 // in case snapshot validation is completed during ActivateBestChainStep, the\n                 // result of GetRole() changes from BACKGROUND to NORMAL.\n                const ChainstateRole chainstate_role{this-&gt;GetRole()};\n                 if (!ActivateBestChainStep(state, *pindexMostWork, pblock &amp;&amp; pblock-&gt;GetHash() == pindexMostWork-&gt;GetBlockHash() ? pblock : nullBlockPtr, fInvalidFound, connected_blocks)) {\n                     // A system error occurred\n                     return false;\n                 }\n                 blocks_connected = true;\n \n                 if (fInvalidFound) {\n                     // Wipe cache, we may need another branch now.\n                     pindexMostWork = nullptr;\n                 }\n                 pindexNewTip = m_chain.Tip();\n \n                 for (auto&amp; [index, block] : std::move(connected_blocks)) {\n                     if (m_chainman.m_options.signals) {\n                         m_chainman.m_options.signals-&gt;BlockConnected(chainstate_role, std::move(Assert(block)), Assert(index));\n                     }\n                 }\n \n                 // Break this do-while to ensure we don't advance past the target block.\n                 if (ReachedTarget()) {\n                     break;\n                 }\n             } while (!m_chain.Tip() || (starting_tip &amp;&amp; CBlockIndexWorkComparator()(m_chain.Tip(), starting_tip)));\n             if (!blocks_connected) return true;\n \n             const CBlockIndex* pindexFork = starting_tip ? m_chain.FindFork(*starting_tip) : nullptr;\n             bool still_in_ibd = m_chainman.IsInitialBlockDownload();\n \n             if (was_in_ibd &amp;&amp; !still_in_ibd) {\n                 // Active chainstate has exited IBD.\n                 exited_ibd = true;\n             }\n \n             // Notify external listeners about the new tip.\n             // Enqueue while holding cs_main to ensure that UpdatedBlockTip is called in the order in which blocks are connected\n             if (this == &amp;m_chainman.ActiveChainstate() &amp;&amp; pindexFork != pindexNewTip) {\n                 // Notify ValidationInterface subscribers\n                 if (m_chainman.m_options.signals) {\n                     m_chainman.m_options.signals-&gt;UpdatedBlockTip(pindexNewTip, pindexFork, still_in_ibd);\n                 }\n \n                 if (kernel::IsInterrupted(m_chainman.GetNotifications().blockTip(\n                         /*state=*/GetSynchronizationState(still_in_ibd, m_chainman.m_blockman.m_blockfiles_indexed),\n                         /*index=*/*pindexNewTip,\n                         /*verification_progress=*/m_chainman.GuessVerificationProgress(pindexNewTip))))\n                 {\n                     // Just breaking and returning success for now. This could\n                     // be changed to bubble up the kernel::Interrupted value to\n                     // the caller so the caller could distinguish between\n                     // completed and interrupted operations.\n                     break;\n                 }\n             }\n             } // release MempoolMutex\n             // Notify external listeners about the new tip, even if pindexFork == pindexNewTip.\n             if (m_chainman.m_options.signals &amp;&amp; this == &amp;m_chainman.ActiveChainstate()) {\n                 m_chainman.m_options.signals-&gt;ActiveTipChange(*Assert(pindexNewTip), m_chainman.IsInitialBlockDownload());\n             }\n         } // release cs_main\n         // When we reach this point, we switched to a new tip (stored in pindexNewTip).\n \n         bool reached_target;\n         {\n             LOCK(m_chainman.GetMutex());\n             if (exited_ibd) {\n                 // If a background chainstate is in use, we may need to rebalance our\n                 // allocation of caches once a chainstate exits initial block download.\n                 m_chainman.MaybeRebalanceCaches();\n             }\n \n             // Write changes periodically to disk, after relay.\n             if (!FlushStateToDisk(state, FlushStateMode::PERIODIC)) {\n                 return false;\n             }\n \n             reached_target = ReachedTarget();\n         }\n \n         if (reached_target) {\n             // Chainstate has reached the target block, so exit.\n             //\n             // Restart indexes so indexes can resync and index new blocks after\n             // the target block.\n             //\n             // This cannot be done while holding cs_main (within\n             // MaybeValidateSnapshot) or a cs_main deadlock will occur.\n             if (m_chainman.snapshot_download_completed) {\n                 m_chainman.snapshot_download_completed();\n             }\n             break;\n         }\n \n         // We check interrupt only after giving ActivateBestChainStep a chance to run once so that we\n         // never interrupt before connecting the genesis block during LoadChainTip(). Previously this\n         // caused an assert() failure during interrupt in such cases as the UTXO DB flushing checks\n         // that the best block hash is non-null.\n         if (m_chainman.m_interrupt) break;\n     } while (pindexNewTip != pindexMostWork);\n \n     m_chainman.CheckBlockIndex();\n \n     return true;\n }\n \n bool Chainstate::PreciousBlock(BlockValidationState&amp; state, CBlockIndex* pindex)\n {\n     AssertLockNotHeld(m_chainstate_mutex);\n     AssertLockNotHeld(::cs_main);\n     {\n         LOCK(cs_main);\n         if (pindex-&gt;nChainWork &lt; m_chain.Tip()-&gt;nChainWork) {\n             // Nothing to do, this block is not at the tip.\n             return true;\n         }\n         if (m_chain.Tip()-&gt;nChainWork &gt; m_chainman.nLastPreciousChainwork) {\n             // The chain has been extended since the last call, reset the counter.\n             m_chainman.nBlockReverseSequenceId = -1;\n         }\n         m_chainman.nLastPreciousChainwork = m_chain.Tip()-&gt;nChainWork;\n         setBlockIndexCandidates.erase(pindex);\n         pindex-&gt;nSequenceId = m_chainman.nBlockReverseSequenceId;\n         if (m_chainman.nBlockReverseSequenceId &gt; std::numeric_limits::min()) {\n             // We can't keep reducing the counter if somebody really wants to\n             // call preciousblock 2**31-1 times on the same set of tips...\n             m_chainman.nBlockReverseSequenceId--;\n         }\n         if (pindex-&gt;IsValid(BLOCK_VALID_TRANSACTIONS) &amp;&amp; pindex-&gt;HaveNumChainTxs()) {\n             setBlockIndexCandidates.insert(pindex);\n             PruneBlockIndexCandidates();\n         }\n     }\n \n     return ActivateBestChain(state, std::shared_ptr());\n }\n \n bool Chainstate::InvalidateBlock(BlockValidationState&amp; state, CBlockIndex* const pindex)\n {\n     AssertLockNotHeld(m_chainstate_mutex);\n     AssertLockNotHeld(::cs_main);\n \n     // Genesis block can't be invalidated\n     assert(pindex);\n     if (pindex-&gt;nHeight == 0) return false;\n \n     // We do not allow ActivateBestChain() to run while InvalidateBlock() is\n     // running, as that could cause the tip to change while we disconnect\n     // blocks.\n     LOCK(m_chainstate_mutex);\n \n     // We'll be acquiring and releasing cs_main below, to allow the validation\n     // callbacks to run. However, we should keep the block index in a\n     // consistent state as we disconnect blocks -- in particular we need to\n     // add equal-work blocks to setBlockIndexCandidates as we disconnect.\n     // To avoid walking the block index repeatedly in search of candidates,\n     // build a map once so that we can look up candidate blocks by chain\n     // work as we go.\n     std::multimap highpow_outofchain_headers;\n \n     {\n         LOCK(cs_main);\n         for (auto&amp; entry : m_blockman.m_block_index) {\n             CBlockIndex&amp; candidate = entry.second;\n             // We don't need to put anything in our active chain into the\n             // multimap, because those candidates will be found and considered\n             // as we disconnect.\n             // Instead, consider only non-active-chain blocks that score\n             // at least as good with CBlockIndexWorkComparator as the new tip.\n             if (!m_chain.Contains(candidate) &amp;&amp;\n                 !CBlockIndexWorkComparator()(&amp;candidate, pindex-&gt;pprev) &amp;&amp;\n                 !(candidate.nStatus &amp; BLOCK_FAILED_VALID)) {\n                 highpow_outofchain_headers.insert({candidate.nChainWork, &amp;candidate});\n             }\n         }\n     }\n \n     CBlockIndex* to_mark_failed = pindex;\n     bool pindex_was_in_chain = false;\n     int disconnected = 0;\n \n     // Disconnect (descendants of) pindex, and mark them invalid.\n     while (true) {\n         if (m_chainman.m_interrupt) break;\n \n         // Make sure the queue of validation callbacks doesn't grow unboundedly.\n         if (m_chainman.m_options.signals) LimitValidationInterfaceQueue(*m_chainman.m_options.signals);\n \n         LOCK(cs_main);\n         // Lock for as long as disconnectpool is in scope to make sure MaybeUpdateMempoolForReorg is\n         // called after DisconnectTip without unlocking in between\n         LOCK(MempoolMutex());\n         if (!m_chain.Contains(*pindex)) break;\n         pindex_was_in_chain = true;\n         CBlockIndex* const disconnected_tip{m_chain.Tip()};\n \n         // ActivateBestChain considers blocks already in m_chain\n         // unconditionally valid already, so force disconnect away from it.\n         DisconnectedBlockTransactions disconnectpool{MAX_DISCONNECTED_TX_POOL_BYTES};\n         bool ret = DisconnectTip(state, &amp;disconnectpool);\n         // DisconnectTip will add transactions to disconnectpool.\n         // Adjust the mempool to be consistent with the new tip, adding\n         // transactions back to the mempool if disconnecting was successful,\n         // and we're not doing a very deep invalidation (in which case\n         // keeping the mempool up to date is probably futile anyway).\n         MaybeUpdateMempoolForReorg(disconnectpool, /* fAddToMempool = */ (++disconnected &lt;= 10) &amp;&amp; ret);\n         if (!ret) return false;\n         CBlockIndex* new_tip{m_chain.Tip()};\n         assert(disconnected_tip-&gt;pprev == new_tip);\n \n         // We immediately mark the disconnected blocks as invalid.\n         // This prevents a case where pruned nodes may fail to invalidateblock\n         // and be left unable to start as they have no tip candidates (as there\n         // are no blocks that meet the \"have data and are not invalid per\n         // nStatus\" criteria for inclusion in setBlockIndexCandidates).\n         disconnected_tip-&gt;nStatus |= BLOCK_FAILED_VALID;\n         m_blockman.m_dirty_blockindex.insert(disconnected_tip);\n         setBlockIndexCandidates.erase(disconnected_tip);\n         setBlockIndexCandidates.insert(new_tip);\n \n         // Mark out-of-chain descendants of the invalidated block as invalid\n         // Add any equal or more work headers that are not invalidated to setBlockIndexCandidates\n         // Recalculate m_best_header if it became invalid.\n         auto candidate_it = highpow_outofchain_headers.lower_bound(new_tip-&gt;nChainWork);\n \n         const bool best_header_needs_update{m_chainman.m_best_header-&gt;GetAncestor(disconnected_tip-&gt;nHeight) == disconnected_tip};\n         if (best_header_needs_update) {\n             // new_tip is definitely still valid at this point, but there may be better ones\n             m_chainman.m_best_header = new_tip;\n         }\n \n         while (candidate_it != highpow_outofchain_headers.end()) {\n             CBlockIndex* candidate{candidate_it-&gt;second};\n             if (candidate-&gt;GetAncestor(disconnected_tip-&gt;nHeight) == disconnected_tip) {\n                 // Children of failed blocks are marked as BLOCK_FAILED_VALID.\n                 candidate-&gt;nStatus |= BLOCK_FAILED_VALID;\n                 m_blockman.m_dirty_blockindex.insert(candidate);\n                 // If invalidated, the block is irrelevant for setBlockIndexCandidates\n                 // and for m_best_header and can be removed from the cache.\n                 candidate_it = highpow_outofchain_headers.erase(candidate_it);\n                 continue;\n             }\n             if (!CBlockIndexWorkComparator()(candidate, new_tip) &amp;&amp;\n                 candidate-&gt;IsValid(BLOCK_VALID_TRANSACTIONS) &amp;&amp;\n                 candidate-&gt;HaveNumChainTxs()) {\n                 setBlockIndexCandidates.insert(candidate);\n                 // Do not remove candidate from the highpow_outofchain_headers cache, because it might be a descendant of the block being invalidated\n                 // which needs to be marked failed later.\n             }\n             if (best_header_needs_update &amp;&amp;\n                 m_chainman.m_best_header-&gt;nChainWork &lt; candidate-&gt;nChainWork) {\n                 m_chainman.m_best_header = candidate;\n             }\n             ++candidate_it;\n         }\n \n         // Track the last disconnected block to call InvalidChainFound on it.\n         to_mark_failed = disconnected_tip;\n     }\n \n     m_chainman.CheckBlockIndex();\n \n     {\n         LOCK(cs_main);\n         if (m_chain.Contains(*to_mark_failed)) {\n             // If the to-be-marked invalid block is in the active chain, something is interfering and we can't proceed.\n             return false;\n         }\n \n         // Mark pindex as invalid if it never was in the main chain\n         if (!pindex_was_in_chain &amp;&amp; !(pindex-&gt;nStatus &amp; BLOCK_FAILED_VALID)) {\n             pindex-&gt;nStatus |= BLOCK_FAILED_VALID;\n             m_blockman.m_dirty_blockindex.insert(pindex);\n             setBlockIndexCandidates.erase(pindex);\n         }\n \n         // If any new blocks somehow arrived while we were disconnecting\n         // (above), then the pre-calculation of what should go into\n         // setBlockIndexCandidates may have missed entries. This would\n         // technically be an inconsistency in the block index, but if we clean\n         // it up here, this should be an essentially unobservable error.\n         // Loop back over all block index entries and add any missing entries\n         // to setBlockIndexCandidates.\n         for (auto&amp; [_, block_index] : m_blockman.m_block_index) {\n             if (block_index.IsValid(BLOCK_VALID_TRANSACTIONS) &amp;&amp; block_index.HaveNumChainTxs() &amp;&amp; !setBlockIndexCandidates.value_comp()(&amp;block_index, m_chain.Tip())) {\n                 setBlockIndexCandidates.insert(&amp;block_index);\n             }\n         }\n \n         InvalidChainFound(to_mark_failed);\n     }\n \n     // Only notify about a new block tip if the active chain was modified.\n     if (pindex_was_in_chain) {\n         // Ignoring return value for now, this could be changed to bubble up\n         // kernel::Interrupted value to the caller so the caller could\n         // distinguish between completed and interrupted operations. It might\n         // also make sense for the blockTip notification to have an enum\n         // parameter indicating the source of the tip change so hooks can\n         // distinguish user-initiated invalidateblock changes from other\n         // changes.\n         (void)m_chainman.GetNotifications().blockTip(\n             /*state=*/GetSynchronizationState(m_chainman.IsInitialBlockDownload(), m_chainman.m_blockman.m_blockfiles_indexed),\n             /*index=*/*to_mark_failed-&gt;pprev,\n             /*verification_progress=*/WITH_LOCK(m_chainman.GetMutex(), return m_chainman.GuessVerificationProgress(to_mark_failed-&gt;pprev)));\n \n         // Fire ActiveTipChange now for the current chain tip to make sure clients are notified.\n         // ActivateBestChain may call this as well, but not necessarily.\n         if (m_chainman.m_options.signals) {\n             m_chainman.m_options.signals-&gt;ActiveTipChange(*Assert(m_chain.Tip()), m_chainman.IsInitialBlockDownload());\n         }\n     }\n     return true;\n }\n \n void Chainstate::SetBlockFailureFlags(CBlockIndex* invalid_block)\n {\n     AssertLockHeld(cs_main);\n \n     for (auto&amp; [_, block_index] : m_blockman.m_block_index) {\n         if (invalid_block != &amp;block_index &amp;&amp; block_index.GetAncestor(invalid_block-&gt;nHeight) == invalid_block) {\n             block_index.nStatus |= BLOCK_FAILED_VALID;\n             m_blockman.m_dirty_blockindex.insert(&amp;block_index);\n         }\n     }\n }\n \n void Chainstate::ResetBlockFailureFlags(CBlockIndex *pindex) {\n     AssertLockHeld(cs_main);\n \n     int nHeight = pindex-&gt;nHeight;\n \n     // Remove the invalidity flag from this block and all its descendants and ancestors.\n     for (auto&amp; [_, block_index] : m_blockman.m_block_index) {\n         if ((block_index.nStatus &amp; BLOCK_FAILED_VALID) &amp;&amp; (block_index.GetAncestor(nHeight) == pindex || pindex-&gt;GetAncestor(block_index.nHeight) == &amp;block_index)) {\n             block_index.nStatus &amp;= ~BLOCK_FAILED_VALID;\n             m_blockman.m_dirty_blockindex.insert(&amp;block_index);\n             if (block_index.IsValid(BLOCK_VALID_TRANSACTIONS) &amp;&amp; block_index.HaveNumChainTxs() &amp;&amp; setBlockIndexCandidates.value_comp()(m_chain.Tip(), &amp;block_index)) {\n                 setBlockIndexCandidates.insert(&amp;block_index);\n             }\n             if (&amp;block_index == m_chainman.m_best_invalid) {\n                 // Reset invalid block marker if it was pointing to one of those.\n                 m_chainman.m_best_invalid = nullptr;\n             }\n         }\n     }\n }\n \n void Chainstate::TryAddBlockIndexCandidate(CBlockIndex* pindex)\n {\n     AssertLockHeld(cs_main);\n \n     // Do not continue building a chainstate that is based on an invalid\n     // snapshot. This is a belt-and-suspenders type of check because if an\n     // invalid snapshot is loaded, the node will shut down to force a manual\n     // intervention. But it is good to handle this case correctly regardless.\n     if (m_assumeutxo == Assumeutxo::INVALID) {\n         return;\n     }\n \n     // The block only is a candidate for the most-work-chain if it has the same\n     // or more work than our current tip.\n     if (m_chain.Tip() != nullptr &amp;&amp; setBlockIndexCandidates.value_comp()(pindex, m_chain.Tip())) {\n         return;\n     }\n \n     const CBlockIndex* target_block{TargetBlock()};\n     if (!target_block) {\n         // If no specific target block, add all entries that have more\n         // work than the tip.\n         setBlockIndexCandidates.insert(pindex);\n     } else {\n         // If there is a target block, only consider connecting blocks\n         // towards the target block.\n         if (target_block-&gt;GetAncestor(pindex-&gt;nHeight) == pindex) {\n             setBlockIndexCandidates.insert(pindex);\n         }\n     }\n }\n \n /** Mark a block as having its data received and checked (up to BLOCK_VALID_TRANSACTIONS). */\n void ChainstateManager::ReceivedBlockTransactions(const CBlock&amp; block, CBlockIndex* pindexNew, const FlatFilePos&amp; pos)\n {\n     AssertLockHeld(cs_main);\n     pindexNew-&gt;nTx = block.vtx.size();\n     // Typically m_chain_tx_count will be 0 at this point, but it can be nonzero if this\n     // is a pruned block which is being downloaded again, or if this is an\n     // assumeutxo snapshot block which has a hardcoded m_chain_tx_count value from the\n     // snapshot metadata. If the pindex is not the snapshot block and the\n     // m_chain_tx_count value is not zero, assert that value is actually correct.\n     auto prev_tx_sum = [](CBlockIndex&amp; block) { return block.nTx + (block.pprev ? block.pprev-&gt;m_chain_tx_count : 0); };\n     if (!Assume(pindexNew-&gt;m_chain_tx_count == 0 || pindexNew-&gt;m_chain_tx_count == prev_tx_sum(*pindexNew) ||\n                 std::ranges::any_of(m_chainstates, [&amp;](const auto&amp; cs) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { return cs-&gt;SnapshotBase() == pindexNew; }))) {\n         LogWarning(\"Internal bug detected: block %d has unexpected m_chain_tx_count %i that should be %i (%s %s). Please report this issue here: %s\\n\",\n             pindexNew-&gt;nHeight, pindexNew-&gt;m_chain_tx_count, prev_tx_sum(*pindexNew), CLIENT_NAME, FormatFullVersion(), CLIENT_BUGREPORT);\n         pindexNew-&gt;m_chain_tx_count = 0;\n     }\n     pindexNew-&gt;nFile = pos.nFile;\n", "creation_timestamp": "2026-05-06T07:21:34.000000Z"}]}