LCOV - code coverage report
Current view: top level - src/test - coins_tests.cpp (source / functions) Hit Total Coverage
Test: total_coverage.info Lines: 735 743 98.9 %
Date: 2025-02-23 09:33:43 Functions: 57 59 96.6 %

          Line data    Source code
       1             : // Copyright (c) 2014 The Bitcoin Core developers
       2             : // Copyright (c) 2019-2021 The PIVX Core developers
       3             : // Distributed under the MIT software license, see the accompanying
       4             : // file COPYING or http://www.opensource.org/licenses/mit-license.php.
       5             : 
       6             : #include "test/test_pivx.h"
       7             : 
       8             : #include "coins.h"
       9             : #include "script/standard.h"
      10             : #include "uint256.h"
      11             : #include "undo.h"
      12             : #include "utilstrencodings.h"
      13             : #include "random.h"
      14             : 
      15             : #include "sapling/incrementalmerkletree.h"
      16             : 
      17             : #include <vector>
      18             : #include <map>
      19             : 
      20             : #include <boost/test/unit_test.hpp>
      21             : 
      22             : int ApplyTxInUndo(Coin&& undo, CCoinsViewCache& view, const COutPoint& out);
      23             : void UpdateCoins(const CTransaction& tx, CCoinsViewCache& inputs, CTxUndo &txundo, int nHeight, bool fSkipInvalid = false);
      24             : 
      25             : namespace
      26             : {
      27             : //! equality test
      28      912331 : bool operator==(const Coin &a, const Coin &b) {
      29             :     // Empty Coin objects are always equal.
      30      912331 :     if (a.IsSpent() && b.IsSpent()) return true;
      31      218624 :     return a.fCoinBase == b.fCoinBase &&
      32      218624 :            a.fCoinStake == b.fCoinStake &&
      33      437248 :            a.nHeight == b.nHeight &&
      34      218624 :            a.out == b.out;
      35             : }
      36             : 
      37             : class CCoinsViewTest : public CCoinsView
      38             : {
      39             :     uint256 hashBestBlock_;
      40             :     std::map<COutPoint, Coin> map_;
      41             : 
      42             :     // Sapling
      43             :     uint256 hashBestSaplingAnchor_;
      44             :     std::map<uint256, SaplingMerkleTree> mapSaplingAnchors_;
      45             :     std::map<uint256, bool> mapSaplingNullifiers_;
      46             : 
      47             : public:
      48          45 :     CCoinsViewTest() {
      49          15 :         hashBestSaplingAnchor_ = SaplingMerkleTree::empty_root();
      50          15 :     }
      51             : 
      52     8997620 :     bool GetCoin(const COutPoint& outpoint, Coin& coin) const
      53             :     {
      54     8997620 :         std::map<COutPoint, Coin>::const_iterator it = map_.find(outpoint);
      55     8997620 :         if (it == map_.end()) {
      56             :             return false;
      57             :         }
      58      324948 :         coin = it->second;
      59      537026 :         if (coin.IsSpent() && InsecureRandBool() == 0) {
      60             :             // Randomly return false in case of an empty entry.
      61      105801 :             return false;
      62             :         }
      63             :         return true;
      64             :     }
      65             : 
      66           0 :     bool HaveCoin(const COutPoint& outpoint) const
      67             :     {
      68           0 :         Coin coin;
      69           0 :         return GetCoin(outpoint, coin);
      70             :     }
      71             : 
      72           0 :     uint256 GetBestBlock() const { return hashBestBlock_; }
      73             : 
      74             :     // Sapling
      75             : 
      76          12 :     bool GetSaplingAnchorAt(const uint256& rt, SaplingMerkleTree &tree) const {
      77          12 :         if (rt == SaplingMerkleTree::empty_root()) {
      78           4 :             SaplingMerkleTree new_tree;
      79           2 :             tree = new_tree;
      80           2 :             return true;
      81             :         }
      82             : 
      83          10 :         std::map<uint256, SaplingMerkleTree>::const_iterator it = mapSaplingAnchors_.find(rt);
      84          10 :         if (it == mapSaplingAnchors_.end()) {
      85             :             return false;
      86             :         } else {
      87           9 :             tree = it->second;
      88           9 :             return true;
      89             :         }
      90             :     }
      91             : 
      92           5 :     bool GetNullifier(const uint256 &nf) const
      93             :     {
      94           5 :         const std::map<uint256, bool>* mapToUse = &mapSaplingNullifiers_;
      95           5 :         std::map<uint256, bool>::const_iterator it = mapToUse->find(nf);
      96           5 :         if (it == mapToUse->end()) {
      97             :             return false;
      98             :         } else {
      99             :             // The map shouldn't contain any false entries.
     100           2 :             assert(it->second);
     101             :             return true;
     102             :         }
     103             :     }
     104             : 
     105           9 :     uint256 GetBestAnchor() const {
     106           9 :         return hashBestSaplingAnchor_;
     107             :     }
     108             : 
     109         196 :     void BatchWriteNullifiers(CNullifiersMap& mapNullifiers, std::map<uint256, bool>& cacheNullifiers)
     110             :     {
     111         203 :         for (CNullifiersMap::iterator it = mapNullifiers.begin(); it != mapNullifiers.end(); ) {
     112           7 :             if (it->second.flags & CNullifiersCacheEntry::DIRTY) {
     113             :                 // Same optimization used in CCoinsViewDB is to only write dirty entries.
     114           7 :                 if (it->second.entered) {
     115           5 :                     cacheNullifiers[it->first] = true;
     116             :                 } else {
     117           2 :                     cacheNullifiers.erase(it->first);
     118             :                 }
     119             :             }
     120           7 :             mapNullifiers.erase(it++);
     121             :         }
     122         196 :     }
     123             : 
     124             :     template<typename Tree, typename Map, typename MapEntry>
     125         196 :     void BatchWriteAnchors(Map& mapAnchors, std::map<uint256, Tree>& cacheAnchors)
     126             :     {
     127         208 :         for (auto it = mapAnchors.begin(); it != mapAnchors.end(); ) {
     128          12 :             if (it->second.flags & MapEntry::DIRTY) {
     129             :                 // Same optimization used in CCoinsViewDB is to only write dirty entries.
     130          11 :                 if (it->second.entered) {
     131           9 :                     if (it->first != Tree::empty_root()) {
     132          27 :                         auto ret = cacheAnchors.insert(std::make_pair(it->first, Tree())).first;
     133           9 :                         ret->second = it->second.tree;
     134             :                     }
     135             :                 } else {
     136          12 :                     cacheAnchors.erase(it->first);
     137             :                 }
     138             :             }
     139          12 :             mapAnchors.erase(it++);
     140             :         }
     141         196 :     }
     142             : 
     143         196 :     bool BatchWrite(CCoinsMap& mapCoins,
     144             :                     const uint256& hashBlock,
     145             :                     const uint256& hashSaplingAnchor,
     146             :                     CAnchorsSaplingMap& mapSaplingAnchors,
     147             :                     CNullifiersMap& mapSaplingNullifiers)
     148             :     {
     149      241836 :         for (CCoinsMap::iterator it = mapCoins.begin(); it != mapCoins.end(); ) {
     150      241640 :             if (it->second.flags & CCoinsCacheEntry::DIRTY) {
     151             :                 // Same optimization used in CCoinsViewDB is to only write dirty entries.
     152       65939 :                 map_[it->first] = it->second.coin;
     153       93952 :                 if (it->second.coin.IsSpent() && InsecureRandRange(3) == 0) {
     154             :                     // Randomly delete empty entries on write.
     155        9445 :                     map_.erase(it->first);
     156             :                 }
     157             :             }
     158      241640 :             mapCoins.erase(it++);
     159             :         }
     160             : 
     161         196 :         BatchWriteAnchors<SaplingMerkleTree, CAnchorsSaplingMap, CAnchorsSaplingCacheEntry>(mapSaplingAnchors, mapSaplingAnchors_);
     162         196 :         BatchWriteNullifiers(mapSaplingNullifiers, mapSaplingNullifiers_);
     163             : 
     164         392 :         if (!hashBlock.IsNull())
     165           0 :             hashBestBlock_ = hashBlock;
     166         392 :         if (!hashSaplingAnchor.IsNull())
     167          11 :             hashBestSaplingAnchor_ = hashSaplingAnchor;
     168         196 :         return true;
     169             :     }
     170             : };
     171             : 
     172           4 : class TxWithNullifiers
     173             : {
     174             : public:
     175             :     CTransactionRef tx;
     176             :     uint256 saplingNullifier;
     177             : 
     178           5 :     TxWithNullifiers()
     179           5 :     {
     180           5 :         CMutableTransaction mutableTx;
     181             : 
     182           5 :         saplingNullifier = GetRandHash();
     183           5 :         SpendDescription sd;
     184           5 :         sd.nullifier = saplingNullifier;
     185           5 :         mutableTx.sapData->vShieldedSpend.push_back(sd);
     186          10 :         tx = MakeTransactionRef(CTransaction(mutableTx));
     187           5 :     }
     188             : };
     189             : 
     190             : 
     191         342 : class CCoinsViewCacheTest : public CCoinsViewCache
     192             : {
     193             : public:
     194         171 :     explicit CCoinsViewCacheTest(CCoinsView* base) : CCoinsViewCache(base) {}
     195             : 
     196         286 :     void SelfTest() const
     197             :     {
     198             :         // Manually recompute the dynamic usage of the whole data, and compare it.
     199         286 :         size_t ret = memusage::DynamicUsage(cacheCoins) +
     200         286 :                      memusage::DynamicUsage(cacheSaplingAnchors) +
     201         286 :                      memusage::DynamicUsage(cacheSaplingNullifiers);
     202         286 :         size_t count = 0;
     203      426486 :         for (const auto& entry : cacheCoins) {
     204      426200 :             ret += memusage::DynamicUsage(entry.second.coin);
     205      426200 :             ++count;
     206             :         }
     207         286 :         BOOST_CHECK_EQUAL(GetCacheSize(), count);
     208         286 :         BOOST_CHECK_EQUAL(memusage::DynamicUsage(*this), ret);
     209         286 :     }
     210             : 
     211         322 :     CCoinsMap& map() { return cacheCoins; }
     212         171 :     size_t& usage() { return cachedCoinsUsage; }
     213             : };
     214             : 
     215             : }
     216             : 
     217             : template<typename Tree> bool GetAnchorAt(const CCoinsViewCache &cache, const uint256 &rt, Tree &tree);
     218          17 : template<> bool GetAnchorAt(const CCoinsViewCache &cache, const uint256 &rt, SaplingMerkleTree &tree) { return cache.GetSaplingAnchorAt(rt, tree); }
     219             : 
     220             : BOOST_FIXTURE_TEST_SUITE(coins_tests, BasicTestingSetup)
     221             : 
     222          13 : void checkNullifierCache(const CCoinsViewCache &cache, const TxWithNullifiers &txWithNullifiers, bool shouldBeInCache) {
     223             :     // Check if the nullifiers either are or are not in the cache
     224          13 :     bool containsSaplingNullifier = cache.GetNullifier(txWithNullifiers.saplingNullifier);
     225          26 :     BOOST_CHECK(containsSaplingNullifier == shouldBeInCache);
     226          13 : }
     227             : 
     228           2 : BOOST_AUTO_TEST_CASE(nullifier_regression_test)
     229             : {
     230             :     // Correct behavior:
     231           1 :     {
     232           2 :     CCoinsViewTest base;
     233           1 :     CCoinsViewCache cache1(&base);
     234             : 
     235           2 :     TxWithNullifiers txWithNullifiers;
     236             : 
     237             :     // Insert a nullifier into the base.
     238           1 :     cache1.SetNullifiers(*txWithNullifiers.tx, true);
     239           1 :     checkNullifierCache(cache1, txWithNullifiers, true);
     240           1 :     cache1.Flush(); // Flush to base.
     241             : 
     242             :     // Remove the nullifier from cache
     243           1 :     cache1.SetNullifiers(*txWithNullifiers.tx, false);
     244             : 
     245             :     // The nullifier now should be `false`.
     246           1 :     checkNullifierCache(cache1, txWithNullifiers, false);
     247             :     }
     248             : 
     249             :     // Also correct behavior:
     250           1 :     {
     251           2 :         CCoinsViewTest base;
     252           1 :         CCoinsViewCache cache1(&base);
     253             : 
     254           2 :         TxWithNullifiers txWithNullifiers;
     255             : 
     256             :         // Insert a nullifier into the base.
     257           1 :         cache1.SetNullifiers(*txWithNullifiers.tx, true);
     258           1 :         checkNullifierCache(cache1, txWithNullifiers, true);
     259           1 :         cache1.Flush(); // Flush to base.
     260             : 
     261             :         // Remove the nullifier from cache
     262           1 :         cache1.SetNullifiers(*txWithNullifiers.tx, false);
     263           1 :         cache1.Flush(); // Flush to base.
     264             : 
     265             :         // The nullifier now should be `false`.
     266           1 :         checkNullifierCache(cache1, txWithNullifiers, false);
     267             :     }
     268             : 
     269             :     // Works because we bring it from the parent cache:
     270           1 :     {
     271           2 :         CCoinsViewTest base;
     272           1 :         CCoinsViewCache cache1(&base);
     273             : 
     274             :         // Insert a nullifier into the base.
     275           2 :         TxWithNullifiers txWithNullifiers;
     276           1 :         cache1.SetNullifiers(*txWithNullifiers.tx, true);
     277           1 :         checkNullifierCache(cache1, txWithNullifiers, true);
     278           1 :         cache1.Flush(); // Empties cache.
     279             : 
     280             :         // Create cache on top.
     281           1 :         {
     282             :             // Remove the nullifier.
     283           2 :             CCoinsViewCache cache2(&cache1);
     284           1 :             checkNullifierCache(cache2, txWithNullifiers, true);
     285           1 :             cache1.SetNullifiers(*txWithNullifiers.tx, false);
     286           1 :             cache2.Flush(); // Empties cache, flushes to cache1.
     287             :         }
     288             : 
     289             :         // The nullifier now should be `false`.
     290           1 :         checkNullifierCache(cache1, txWithNullifiers, false);
     291             :     }
     292             : 
     293             :     // Was broken:
     294           1 :     {
     295           2 :         CCoinsViewTest base;
     296           1 :         CCoinsViewCache cache1(&base);
     297             : 
     298             :         // Insert a nullifier into the base.
     299           2 :         TxWithNullifiers txWithNullifiers;
     300           1 :         cache1.SetNullifiers(*txWithNullifiers.tx, true);
     301           1 :         cache1.Flush(); // Empties cache.
     302             : 
     303             :         // Create cache on top.
     304           1 :         {
     305             :             // Remove the nullifier.
     306           2 :             CCoinsViewCache cache2(&cache1);
     307           1 :             cache2.SetNullifiers(*txWithNullifiers.tx, false);
     308           1 :             cache2.Flush(); // Empties cache, flushes to cache1.
     309             :         }
     310             : 
     311             :         // The nullifier now should be `false`.
     312           1 :         checkNullifierCache(cache1, txWithNullifiers, false);
     313             :     }
     314           1 : }
     315             : 
     316           1 : template<typename Tree> void anchorPopRegressionTestImpl()
     317             : {
     318             :     // Correct behavior:
     319             :     {
     320           2 :         CCoinsViewTest base;
     321           1 :         CCoinsViewCache cache1(&base);
     322             : 
     323             :         // Create dummy anchor/commitment
     324           1 :         Tree tree;
     325           1 :         tree.append(GetRandHash());
     326             : 
     327             :         // Add the anchor
     328           1 :         cache1.PushAnchor(tree);
     329           1 :         cache1.Flush();
     330             : 
     331             :         // Remove the anchor
     332           1 :         cache1.PopAnchor(Tree::empty_root());
     333           1 :         cache1.Flush();
     334             : 
     335             :         // Add the anchor back
     336           1 :         cache1.PushAnchor(tree);
     337           1 :         cache1.Flush();
     338             : 
     339             :         // The base contains the anchor, of course!
     340             :         {
     341           1 :             Tree checkTree;
     342           2 :             BOOST_CHECK(GetAnchorAt(cache1, tree.root(), checkTree));
     343           2 :             BOOST_CHECK(checkTree.root() == tree.root());
     344             :         }
     345             :     }
     346             : 
     347             :     // Previously incorrect behavior
     348             :     {
     349           2 :         CCoinsViewTest base;
     350           1 :         CCoinsViewCache cache1(&base);
     351             : 
     352             :         // Create dummy anchor/commitment
     353           1 :         Tree tree;
     354           1 :         tree.append(GetRandHash());
     355             : 
     356             :         // Add the anchor and flush to disk
     357           1 :         cache1.PushAnchor(tree);
     358           1 :         cache1.Flush();
     359             : 
     360             :         // Remove the anchor, but don't flush yet!
     361           1 :         cache1.PopAnchor(Tree::empty_root());
     362             : 
     363             :         {
     364           2 :             CCoinsViewCache cache2(&cache1); // Build cache on top
     365           1 :             cache2.PushAnchor(tree); // Put the same anchor back!
     366           1 :             cache2.Flush(); // Flush to cache1
     367             :         }
     368             : 
     369             :         // cache2's flush kinda worked, i.e. cache1 thinks the
     370             :         // tree is there, but it didn't bring down the correct
     371             :         // treestate...
     372             :         {
     373           0 :             Tree checktree;
     374           2 :             BOOST_CHECK(GetAnchorAt(cache1, tree.root(), checktree));
     375           2 :             BOOST_CHECK(checktree.root() == tree.root()); // Oh, shucks.
     376             :         }
     377             : 
     378             :         // Flushing cache won't help either, just makes the inconsistency
     379             :         // permanent.
     380           1 :         cache1.Flush();
     381             :         {
     382           1 :             Tree checktree;
     383           2 :             BOOST_CHECK(GetAnchorAt(cache1, tree.root(), checktree));
     384           2 :             BOOST_CHECK(checktree.root() == tree.root()); // Oh, shucks.
     385             :         }
     386             :     }
     387           1 : }
     388             : 
     389           2 : BOOST_AUTO_TEST_CASE(anchor_pop_regression_test)
     390             : {
     391           1 :     anchorPopRegressionTestImpl<SaplingMerkleTree>();
     392           1 : }
     393             : 
     394           1 : template<typename Tree> void anchorRegressionTestImpl()
     395             : {
     396             :     // Correct behavior:
     397             :     {
     398           2 :         CCoinsViewTest base;
     399           1 :         CCoinsViewCache cache1(&base);
     400             : 
     401             :         // Insert anchor into base.
     402           1 :         Tree tree;
     403           1 :         tree.append(GetRandHash());
     404             : 
     405           1 :         cache1.PushAnchor(tree);
     406           1 :         cache1.Flush();
     407             : 
     408           1 :         cache1.PopAnchor(Tree::empty_root());
     409           3 :         BOOST_CHECK(cache1.GetBestAnchor() == Tree::empty_root());
     410           2 :         BOOST_CHECK(!GetAnchorAt(cache1, tree.root(), tree));
     411             :     }
     412             : 
     413             :     // Also correct behavior:
     414             :     {
     415           2 :         CCoinsViewTest base;
     416           1 :         CCoinsViewCache cache1(&base);
     417             : 
     418             :         // Insert anchor into base.
     419           1 :         Tree tree;
     420           1 :         tree.append(GetRandHash());
     421           1 :         cache1.PushAnchor(tree);
     422           1 :         cache1.Flush();
     423             : 
     424           1 :         cache1.PopAnchor(Tree::empty_root());
     425           1 :         cache1.Flush();
     426           3 :         BOOST_CHECK(cache1.GetBestAnchor() == Tree::empty_root());
     427           2 :         BOOST_CHECK(!GetAnchorAt(cache1, tree.root(), tree));
     428             :     }
     429             : 
     430             :     // Works because we bring the anchor in from parent cache.
     431             :     {
     432           2 :         CCoinsViewTest base;
     433           1 :         CCoinsViewCache cache1(&base);
     434             : 
     435             :         // Insert anchor into base.
     436           1 :         Tree tree;
     437           1 :         tree.append(GetRandHash());
     438           1 :         cache1.PushAnchor(tree);
     439           1 :         cache1.Flush();
     440             : 
     441             :         {
     442             :             // Pop anchor.
     443           2 :             CCoinsViewCache cache2(&cache1);
     444           2 :             BOOST_CHECK(GetAnchorAt(cache2, tree.root(), tree));
     445           1 :             cache2.PopAnchor(Tree::empty_root());
     446           1 :             cache2.Flush();
     447             :         }
     448             : 
     449           3 :         BOOST_CHECK(cache1.GetBestAnchor() == Tree::empty_root());
     450           2 :         BOOST_CHECK(!GetAnchorAt(cache1, tree.root(), tree));
     451             :     }
     452             : 
     453             :     // Was broken:
     454             :     {
     455           2 :         CCoinsViewTest base;
     456           1 :         CCoinsViewCache cache1(&base);
     457             : 
     458             :         // Insert anchor into base.
     459           1 :         Tree tree;
     460           1 :         tree.append(GetRandHash());
     461           1 :         cache1.PushAnchor(tree);
     462           1 :         cache1.Flush();
     463             : 
     464             :         {
     465             :             // Pop anchor.
     466           2 :             CCoinsViewCache cache2(&cache1);
     467           1 :             cache2.PopAnchor(Tree::empty_root());
     468           1 :             cache2.Flush();
     469             :         }
     470             : 
     471           3 :         BOOST_CHECK(cache1.GetBestAnchor() == Tree::empty_root());
     472           2 :         BOOST_CHECK(!GetAnchorAt(cache1, tree.root(), tree));
     473             :     }
     474           1 : }
     475             : 
     476           2 : BOOST_AUTO_TEST_CASE(anchor_regression_test)
     477             : {
     478           1 :     anchorRegressionTestImpl<SaplingMerkleTree>();
     479           1 : }
     480             : 
     481           2 : BOOST_AUTO_TEST_CASE(nullifiers_test)
     482             : {
     483           2 :     CCoinsViewTest base;
     484           1 :     CCoinsViewCache cache(&base);
     485             : 
     486           2 :     TxWithNullifiers txWithNullifiers;
     487           1 :     checkNullifierCache(cache, txWithNullifiers, false);
     488           1 :     cache.SetNullifiers(*txWithNullifiers.tx, true);
     489           1 :     checkNullifierCache(cache, txWithNullifiers, true);
     490           1 :     cache.Flush();
     491             : 
     492           2 :     CCoinsViewCache cache2(&base);
     493             : 
     494           1 :     checkNullifierCache(cache2, txWithNullifiers, true);
     495           1 :     cache2.SetNullifiers(*txWithNullifiers.tx, false);
     496           1 :     checkNullifierCache(cache2, txWithNullifiers, false);
     497           1 :     cache2.Flush();
     498             : 
     499           2 :     CCoinsViewCache cache3(&base);
     500             : 
     501           1 :     checkNullifierCache(cache3, txWithNullifiers, false);
     502           1 : }
     503             : 
     504           1 : template<typename Tree> void anchorsFlushImpl()
     505             : {
     506           2 :     CCoinsViewTest base;
     507           1 :     uint256 newrt;
     508             :     {
     509           1 :         CCoinsViewCache cache(&base);
     510           1 :         Tree tree;
     511           2 :         BOOST_CHECK(GetAnchorAt(cache, cache.GetBestAnchor(), tree));
     512           1 :         tree.append(GetRandHash());
     513             : 
     514           1 :         newrt = tree.root();
     515             : 
     516           1 :         cache.PushAnchor(tree);
     517           1 :         cache.Flush();
     518             :     }
     519             : 
     520             :     {
     521           1 :         CCoinsViewCache cache(&base);
     522           1 :         Tree tree;
     523           2 :         BOOST_CHECK(GetAnchorAt(cache, cache.GetBestAnchor(), tree));
     524             : 
     525             :         // Get the cached entry.
     526           2 :         BOOST_CHECK(GetAnchorAt(cache, cache.GetBestAnchor(), tree));
     527             : 
     528           1 :         uint256 check_rt = tree.root();
     529             : 
     530           2 :         BOOST_CHECK(check_rt == newrt);
     531             :     }
     532           1 : }
     533             : 
     534           2 : BOOST_AUTO_TEST_CASE(anchors_flush_test)
     535             : {
     536           1 :     anchorsFlushImpl<SaplingMerkleTree>();
     537           1 : }
     538             : 
     539           1 : template<typename Tree> void anchorsTestImpl()
     540             : {
     541             :     // TODO: These tests should be more methodical.
     542             :     //       Or, integrate with Bitcoin's tests later.
     543             : 
     544           2 :     CCoinsViewTest base;
     545           1 :     CCoinsViewCache cache(&base);
     546             : 
     547           3 :     BOOST_CHECK(cache.GetBestAnchor() == Tree::empty_root());
     548             : 
     549             :     {
     550           1 :         Tree tree;
     551             : 
     552           2 :         BOOST_CHECK(GetAnchorAt(cache, cache.GetBestAnchor(), tree));
     553           2 :         BOOST_CHECK(cache.GetBestAnchor() == tree.root());
     554           1 :         tree.append(GetRandHash());
     555           1 :         tree.append(GetRandHash());
     556           1 :         tree.append(GetRandHash());
     557           1 :         tree.append(GetRandHash());
     558           1 :         tree.append(GetRandHash());
     559           1 :         tree.append(GetRandHash());
     560           1 :         tree.append(GetRandHash());
     561             : 
     562           1 :         Tree save_tree_for_later;
     563           1 :         save_tree_for_later = tree;
     564             : 
     565           1 :         uint256 newrt = tree.root();
     566           1 :         uint256 newrt2;
     567             : 
     568           1 :         cache.PushAnchor(tree);
     569           2 :         BOOST_CHECK(cache.GetBestAnchor() == newrt);
     570             : 
     571             :         {
     572           0 :             Tree confirm_same;
     573           2 :             BOOST_CHECK(GetAnchorAt(cache, cache.GetBestAnchor(), confirm_same));
     574             : 
     575           2 :             BOOST_CHECK(confirm_same.root() == newrt);
     576             :         }
     577             : 
     578           1 :         tree.append(GetRandHash());
     579           1 :         tree.append(GetRandHash());
     580             : 
     581           1 :         newrt2 = tree.root();
     582             : 
     583           1 :         cache.PushAnchor(tree);
     584           2 :         BOOST_CHECK(cache.GetBestAnchor() == newrt2);
     585             : 
     586           1 :         Tree test_tree;
     587           2 :         BOOST_CHECK(GetAnchorAt(cache, cache.GetBestAnchor(), test_tree));
     588             : 
     589           2 :         BOOST_CHECK(tree.root() == test_tree.root());
     590             : 
     591             :         {
     592           0 :             Tree test_tree2;
     593           1 :             GetAnchorAt(cache, newrt, test_tree2);
     594             : 
     595           2 :             BOOST_CHECK(test_tree2.root() == newrt);
     596             :         }
     597             : 
     598             :         {
     599           1 :             cache.PopAnchor(newrt);
     600           1 :             Tree obtain_tree;
     601           1 :             assert(!GetAnchorAt(cache, newrt2, obtain_tree)); // should have been popped off
     602           1 :             assert(GetAnchorAt(cache, newrt, obtain_tree));
     603             : 
     604           1 :             assert(obtain_tree.root() == newrt);
     605             :         }
     606             :     }
     607           1 : }
     608             : 
     609           2 : BOOST_AUTO_TEST_CASE(anchors_test)
     610             : {
     611           1 :     anchorsTestImpl<SaplingMerkleTree>();
     612           1 : }
     613             : 
     614             : static const unsigned int NUM_SIMULATION_ITERATIONS = 40000;
     615             : 
     616             : // This is a large randomized insert/remove simulation test on a variable-size
     617             : // stack of caches on top of CCoinsViewTest.
     618             : //
     619             : // It will randomly create/update/delete Coin entries to a tip of caches, with
     620             : // txids picked from a limited list of random 256-bit hashes. Occasionally, a
     621             : // new tip is added to the stack of caches, or the tip is flushed and removed.
     622             : //
     623             : // During the process, booleans are kept to make sure that the randomized
     624             : // operation hits all branches.
     625           2 : BOOST_AUTO_TEST_CASE(coins_cache_simulation_test)
     626             : {
     627             :     // Various coverage trackers.
     628           1 :     bool removed_all_caches = false;
     629           1 :     bool reached_4_caches = false;
     630           1 :     bool added_an_entry = false;
     631           1 :     bool added_an_unspendable_entry = false;
     632           1 :     bool removed_an_entry = false;
     633           1 :     bool updated_an_entry = false;
     634           1 :     bool found_an_entry = false;
     635           1 :     bool missed_an_entry = false;
     636           1 :     bool uncached_an_entry = false;
     637             : 
     638             :     // A simple map to track what we expect the cache stack to represent.
     639           1 :     std::map<COutPoint, Coin> result;
     640             : 
     641             :     // The cache stack.
     642           2 :     CCoinsViewTest base; // A CCoinsViewTest at the bottom.
     643           2 :     std::vector<CCoinsViewCacheTest*> stack; // A stack of CCoinsViewCaches on top.
     644           1 :     stack.push_back(new CCoinsViewCacheTest(&base)); // Start with one cache.
     645             : 
     646             :     // Use a limited set of random transaction ids, so we do test overwriting entries.
     647           2 :     std::vector<uint256> txids;
     648           1 :     txids.resize(NUM_SIMULATION_ITERATIONS / 8);
     649        5001 :     for (unsigned int i = 0; i < txids.size(); i++) {
     650        5000 :         txids[i] = InsecureRand256();
     651             :     }
     652             : 
     653       40001 :     for (unsigned int i = 0; i < NUM_SIMULATION_ITERATIONS; i++) {
     654             :         // Do a random modification.
     655       40000 :         {
     656       40000 :             uint256 txid = txids[InsecureRandRange(txids.size())]; // txid we're going to modify in this iteration.
     657       40000 :             Coin& coin = result[COutPoint(txid, 0)];
     658       80000 :             const Coin& entry = (InsecureRandRange(500) == 0) ?
     659          86 :                                     AccessByTxid(*stack.back(), txid) : stack.back()->AccessCoin(COutPoint(txid, 0)
     660       39914 :                                 );
     661       80000 :             BOOST_CHECK(coin == entry);
     662       80000 :             if (InsecureRandRange(5) == 0 || coin.IsSpent()) {
     663       47862 :                 Coin newcoin;
     664       23931 :                 newcoin.out.nValue = InsecureRand32();
     665       23931 :                 newcoin.nHeight = 1;
     666       47862 :                 if (InsecureRandRange(16) == 0 && coin.IsSpent()) {
     667        1230 :                     newcoin.out.scriptPubKey.assign(1 + (InsecureRand32() & 0x3F), OP_RETURN);
     668        3690 :                     BOOST_CHECK(newcoin.out.scriptPubKey.IsUnspendable());
     669        1230 :                     added_an_unspendable_entry = true;
     670             :                 } else {
     671       22701 :                     newcoin.out.scriptPubKey.assign(InsecureRand32() & 0x3F, 0); // Random sizes so we can test memory usage accounting
     672       22701 :                     (coin.IsSpent() ? added_an_entry : updated_an_entry) = true;
     673       22701 :                     coin = newcoin;
     674             :                 }
     675       25761 :                 stack.back()->AddCoin(COutPoint(txid, 0), std::move(newcoin), !coin.IsSpent() || InsecureRandBool());
     676             :             } else {
     677       16069 :                 removed_an_entry = true;
     678       16069 :                 coin.Clear();
     679       16069 :                 stack.back()->SpendCoin(COutPoint(txid, 0));
     680             :             }
     681             :         }
     682             : 
     683             :         // One every 10 iterations, remove a random entry from the cache
     684       80000 :         if (InsecureRandRange(10) == 0) {
     685        4083 :             COutPoint out(txids[InsecureRandRange(txids.size())], 0);
     686        4083 :             int cacheid = InsecureRandRange(stack.size());
     687        4083 :             stack[cacheid]->Uncache(out);
     688        4083 :             uncached_an_entry |= !stack[cacheid]->HaveCoinInCache(out);
     689             :         }
     690             : 
     691             :         // Once every 1000 iterations and at the end, verify the full cache.
     692       80000 :         if (InsecureRandRange(1000) == 1 || i == NUM_SIMULATION_ITERATIONS - 1) {
     693      224696 :             for (const auto& entry : result) {
     694      224645 :                 bool have = stack.back()->HaveCoin(entry.first);
     695      224645 :                 const Coin& coin = stack.back()->AccessCoin(entry.first);
     696      449290 :                 BOOST_CHECK(have == !coin.IsSpent());
     697      449290 :                 BOOST_CHECK(coin == entry.second);
     698      224645 :                 if (coin.IsSpent()) {
     699             :                     missed_an_entry = true;
     700             :                 } else {
     701      258632 :                     BOOST_CHECK(stack.back()->HaveCoinInCache(entry.first));
     702      129316 :                     found_an_entry = true;
     703             :                 }
     704             :             }
     705         186 :             for (const CCoinsViewCacheTest *test : stack) {
     706         135 :                 test->SelfTest();
     707             :             }
     708             :         }
     709             : 
     710       80000 :         if (InsecureRandRange(100) == 0) {
     711             :             // Every 100 iterations, flush an intermediate cache
     712         742 :             if (stack.size() > 1 && InsecureRandBool() == 0) {
     713         178 :                 unsigned int flushIndex = InsecureRandRange(stack.size() - 1) == 0;
     714         178 :                 stack[flushIndex]->Flush();
     715             :             }
     716             :         }
     717             : 
     718       91310 :         if (InsecureRandRange(100) == 0) {
     719             :             // Every 100 iterations, change the cache stack.
     720         782 :             if (stack.size() > 0 && InsecureRandBool() == 0) {
     721             :                 //Remove the top cache
     722         196 :                 stack.back()->Flush();
     723         196 :                 delete stack.back();
     724         196 :                 stack.pop_back();
     725             :             }
     726         696 :             if (stack.size() == 0 || (stack.size() < 4 && InsecureRandBool())) {
     727             :                 //Add a new cache
     728         197 :                 CCoinsView* tip = &base;
     729         197 :                 if (stack.size() > 0) {
     730         164 :                     tip = stack.back();
     731             :                 } else {
     732             :                     removed_all_caches = true;
     733             :                 }
     734         197 :                 stack.push_back(new CCoinsViewCacheTest(tip));
     735         197 :                 if (stack.size() == 4) {
     736          65 :                     reached_4_caches = true;
     737             :                 }
     738             :             }
     739             :         }
     740             :     }
     741             : 
     742             :     // Clean up the stack.
     743           3 :     while (stack.size() > 0) {
     744           2 :         delete stack.back();
     745           3 :         stack.pop_back();
     746             :     }
     747             : 
     748             :     // Verify coverage.
     749           2 :     BOOST_CHECK(removed_all_caches);
     750           2 :     BOOST_CHECK(reached_4_caches);
     751           2 :     BOOST_CHECK(added_an_entry);
     752           2 :     BOOST_CHECK(added_an_unspendable_entry);
     753           2 :     BOOST_CHECK(removed_an_entry);
     754           2 :     BOOST_CHECK(updated_an_entry);
     755           2 :     BOOST_CHECK(found_an_entry);
     756           2 :     BOOST_CHECK(missed_an_entry);
     757           2 :     BOOST_CHECK(uncached_an_entry);
     758           1 : }
     759             : 
     760             : // Store of all necessary tx and undo data for next test
     761             : typedef std::map<COutPoint, std::tuple<CTransaction,CTxUndo,Coin>> UtxoData;
     762             : UtxoData utxoData;
     763             : 
     764       39708 : UtxoData::iterator FindRandomFrom(const std::set<COutPoint>& utxoSet) {
     765       39708 :     assert(utxoSet.size());
     766       39708 :     auto utxoSetIt = utxoSet.lower_bound(COutPoint(GetRandHash(), 0));
     767       39708 :     if (utxoSetIt == utxoSet.end()) {
     768          98 :         utxoSetIt = utxoSet.begin();
     769             :     }
     770       39708 :     auto utxoDataIt = utxoData.find(*utxoSetIt);
     771       39708 :     assert(utxoDataIt != utxoData.end());
     772       39708 :     return utxoDataIt;
     773             : }
     774             : 
     775             : // This test is similar to the previous test
     776             : // except the emphasis is on testing the functionality of UpdateCoins
     777             : // random txs are created and UpdateCoins is used to update the cache stack
     778           2 : BOOST_AUTO_TEST_CASE(updatecoins_simulation_test)
     779             : {
     780             :     // A simple map to track what we expect the cache stack to represent.
     781           1 :     std::map<COutPoint, Coin> result;
     782             : 
     783             :     // The cache stack.
     784           2 :     CCoinsViewTest base; // A CCoinsViewTest at the bottom.
     785           2 :     std::vector<CCoinsViewCacheTest*> stack; // A stack of CCoinsViewCaches on top.
     786           1 :     stack.push_back(new CCoinsViewCacheTest(&base)); // Start with one cache.
     787             : 
     788             :     // Track the txids we've used in various sets
     789           2 :     std::set<COutPoint> coinbase_coins;
     790           1 :     std::set<COutPoint> disconnected_coins;
     791           1 :     std::set<COutPoint> utxoset;
     792             : 
     793       40001 :     for (unsigned int i = 0; i < NUM_SIMULATION_ITERATIONS; i++) {
     794       40000 :         uint32_t randiter = InsecureRand32();
     795             : 
     796             :         // 19/20 txs add a new transaction
     797       40000 :         if (randiter % 20 < 19) {
     798       37986 :             CMutableTransaction tx;
     799       37986 :             tx.vin.resize(1);
     800       37986 :             tx.vout.resize(1);
     801       37986 :             tx.vout[0].nValue = i; //Keep txs unique unless intended to duplicate
     802       37986 :             unsigned int height = InsecureRand32();
     803       73929 :             Coin old_coin;
     804             : 
     805             :             // 2/20 times create a new coinbase
     806       37986 :             if (randiter % 20 < 2 || coinbase_coins.size() < 10) {
     807             :                 // PIVX: don't test for duplicate coinbases as those are not possible due to
     808             :                 // BIP34 enforced since the beginning.
     809        4060 :                 assert(CTransaction(tx).IsCoinBase());
     810        4060 :                 coinbase_coins.insert(COutPoint(tx.GetHash(), 0));
     811             :             }
     812             : 
     813             :             // 17/20 times reconnect previous or add a regular tx
     814             :             else {
     815       33926 :                 COutPoint prevout;
     816             :                 // 1/20 times reconnect a previously disconnected tx
     817       33926 :                 if (randiter % 20 == 2 && disconnected_coins.size()) {
     818        2043 :                     auto utxod = FindRandomFrom(coinbase_coins);
     819        2043 :                     tx = std::get<0>(utxod->second);
     820        2043 :                     prevout = tx.vin[0].prevout;
     821             :                     // PIVX: no duplicates
     822        6129 :                     BOOST_CHECK(!utxoset.count(prevout));
     823        2043 :                     disconnected_coins.erase(utxod->first);
     824        2043 :                     continue;
     825             :                 }
     826             : 
     827             :                 // 16/20 times create a regular tx
     828             :                 else {
     829       31883 :                     auto utxod = FindRandomFrom(utxoset);
     830       31883 :                     prevout = utxod->first;
     831             : 
     832             :                     // Construct the tx to spend the coins of prevouthash
     833       31883 :                     tx.vin[0].prevout = prevout;
     834       31883 :                     assert(!CTransaction(tx).IsCoinBase());
     835             :                 }
     836             :                 // In this simple test coins only have two states, spent or unspent, save the unspent state to restore
     837       31883 :                 old_coin = result[prevout];
     838             : 
     839             :                 // Update the expected result of prevouthash to know these coins are spent
     840       31883 :                 result[prevout].Clear();
     841             : 
     842       31883 :                 utxoset.erase(prevout);
     843             : 
     844             :             }
     845             :             // Update the expected result to know about the new output coins
     846       35943 :             assert(tx.vout.size() == 1);
     847       35943 :             const COutPoint outpoint(tx.GetHash(), 0);
     848       35943 :             result[outpoint] = Coin(tx.vout[0], height, CTransaction(tx).IsCoinBase(), CTransaction(tx).IsCoinStake());
     849             : 
     850             :             // Call UpdateCoins on the top cache
     851       71886 :             CTxUndo undo;
     852       35943 :             UpdateCoins(tx, *(stack.back()), undo, height);
     853             : 
     854             :             // Update the utxo set for future spends
     855       35943 :             utxoset.insert(outpoint);
     856             : 
     857             :             // Track this tx and undo info to use later
     858      107829 :             utxoData.emplace(outpoint, std::make_tuple(tx,undo,old_coin));
     859             : 
     860        2014 :         } else if (utxoset.size()) {
     861             :             //1/20 times undo a previous transaction
     862        2014 :             auto utxod = FindRandomFrom(utxoset);
     863             : 
     864        2014 :             CTransaction& tx = std::get<0>(utxod->second);
     865        2014 :             CTxUndo& undo = std::get<1>(utxod->second);
     866        2014 :             Coin& orig_coin = std::get<2>(utxod->second);
     867             : 
     868             :             // Update the expected result
     869             :             // Remove new outputs
     870        2014 :             result[utxod->first].Clear();
     871             :             // If not coinbase restore prevout
     872        2014 :             if (!tx.IsCoinBase()) {
     873        1786 :                 result[tx.vin[0].prevout] = orig_coin;
     874             :             }
     875             : 
     876             :             // Disconnect the tx from the current UTXO
     877             :             // See code in DisconnectBlock
     878             :             // remove outputs
     879        2014 :             stack.back()->SpendCoin(utxod->first);
     880             : 
     881             :             // restore inputs
     882        2014 :             if (!tx.IsCoinBase()) {
     883        1786 :                 const COutPoint &out = tx.vin[0].prevout;
     884        3572 :                 Coin coin = undo.vprevout[0];
     885        1786 :                 ApplyTxInUndo(std::move(coin), *(stack.back()), out);
     886             :             }
     887             :             // Store as a candidate for reconnection
     888        2014 :             disconnected_coins.insert(utxod->first);
     889             : 
     890             :             // Update the utxoset
     891        2014 :             utxoset.erase(utxod->first);
     892        2014 :             if (!tx.IsCoinBase())
     893        3800 :                 utxoset.insert(tx.vin[0].prevout);
     894             :         }
     895             : 
     896             :         // Once every 1000 iterations and at the end, verify the full cache.
     897       76798 :         if (InsecureRandRange(1000) == 1 || i == NUM_SIMULATION_ITERATIONS - 1) {
     898      647723 :             for (const auto& entry : result) {
     899      647686 :                 bool have = stack.back()->HaveCoin(entry.first);
     900      647686 :                 const Coin& coin = stack.back()->AccessCoin(entry.first);
     901     1295370 :                 BOOST_CHECK(have == !coin.IsSpent());
     902     1295370 :                 BOOST_CHECK(coin == entry.second);
     903             :             }
     904             :         }
     905             : 
     906             :         // One every 10 iterations, remove a random entry from the cache
     907       75912 :         if (utxoset.size() > 1 && InsecureRandRange(20) == 0) {
     908        1885 :             stack[InsecureRandRange(stack.size())]->Uncache(FindRandomFrom(utxoset)->first);
     909             :         }
     910       75909 :         if (disconnected_coins.size() > 1 && InsecureRandRange(20) == 0) {
     911        1883 :             stack[InsecureRandRange(stack.size())]->Uncache(FindRandomFrom(disconnected_coins)->first);
     912             :         }
     913             : 
     914       86777 :         if (InsecureRandRange(100) == 0) {
     915             :             // Every 100 iterations, change the cache stack.
     916         752 :             if (stack.size() > 0 && InsecureRandBool() == 0) {
     917         202 :                 stack.back()->Flush();
     918         202 :                 delete stack.back();
     919         202 :                 stack.pop_back();
     920             :             }
     921         662 :             if (stack.size() == 0 || (stack.size() < 4 && InsecureRandBool())) {
     922         204 :                 CCoinsView* tip = &base;
     923         204 :                 if (stack.size() > 0) {
     924         136 :                     tip = stack.back();
     925             :                 }
     926         204 :                 stack.push_back(new CCoinsViewCacheTest(tip));
     927             :             }
     928             :         }
     929             :     }
     930             : 
     931             :     // Clean up the stack.
     932           4 :     while (stack.size() > 0) {
     933           3 :         delete stack.back();
     934           4 :         stack.pop_back();
     935             :     }
     936           1 : }
     937             : 
     938           2 : BOOST_AUTO_TEST_CASE(ccoins_serialization)
     939             : {
     940             :     // Good example
     941           2 :     CDataStream ss1(ParseHex("00835800816115944e077fe7c803cfa57f29b36bf87c1d35"), SER_DISK, CLIENT_VERSION);
     942           2 :     Coin cc1;
     943           1 :     ss1 >> cc1;
     944           1 :     BOOST_CHECK_EQUAL(cc1.out.nValue, 60000000000ULL);
     945           7 :     BOOST_CHECK_EQUAL(HexStr(cc1.out.scriptPubKey), HexStr(GetScriptForDestination(CKeyID(uint160(ParseHex("816115944e077fe7c803cfa57f29b36bf87c1d35"))))));
     946             : 
     947             :     // Good example
     948           3 :     CDataStream ss2(ParseHex("8dcb7ebbd123008c988f1a4a4de2161e0f50aac7f17e7f9555caa4"), SER_DISK, CLIENT_VERSION);
     949           2 :     Coin cc2;
     950           1 :     ss2 >> cc2;
     951           1 :     BOOST_CHECK_EQUAL(cc2.fCoinBase, true);
     952           1 :     BOOST_CHECK_EQUAL(cc2.nHeight, 59807);
     953           1 :     BOOST_CHECK_EQUAL(cc2.out.nValue, 110397);
     954           7 :     BOOST_CHECK_EQUAL(HexStr(cc2.out.scriptPubKey), HexStr(GetScriptForDestination(CKeyID(uint160(ParseHex("8c988f1a4a4de2161e0f50aac7f17e7f9555caa4"))))));
     955             : 
     956             :     // PIVX: Example with fCoinStake
     957           3 :     CDataStream ss2b(ParseHex("97b401808b63008c988f1a4a4de2161e0f50aac7f17e7f9555caa4"), SER_DISK, CLIENT_VERSION);
     958           2 :     Coin cc2b;
     959           1 :     ss2b >> cc2b;
     960           1 :     BOOST_CHECK_EQUAL(cc2b.fCoinBase, false);
     961           1 :     BOOST_CHECK_EQUAL(cc2b.fCoinStake, true);
     962           1 :     BOOST_CHECK_EQUAL(cc2b.nHeight, 100000);
     963           1 :     BOOST_CHECK_EQUAL(cc2b.out.nValue, 2002 * COIN);
     964           7 :     BOOST_CHECK_EQUAL(HexStr(cc2b.out.scriptPubKey), HexStr(GetScriptForDestination(CKeyID(uint160(ParseHex("8c988f1a4a4de2161e0f50aac7f17e7f9555caa4"))))));
     965             : 
     966             :     // Smallest possible example
     967           3 :     CDataStream ss3(ParseHex("000006"), SER_DISK, CLIENT_VERSION);
     968           2 :     Coin cc3;
     969           1 :     ss3 >> cc3;
     970           1 :     BOOST_CHECK_EQUAL(cc3.fCoinBase, false);
     971           1 :     BOOST_CHECK_EQUAL(cc3.nHeight, 0);
     972           1 :     BOOST_CHECK_EQUAL(cc3.out.nValue, 0);
     973           1 :     BOOST_CHECK_EQUAL(cc3.out.scriptPubKey.size(), 0);
     974             : 
     975             :     // scriptPubKey that ends beyond the end of the stream
     976           3 :     CDataStream ss4(ParseHex("000007"), SER_DISK, CLIENT_VERSION);
     977           1 :     try {
     978           2 :         Coin cc4;
     979           1 :         ss4 >> cc4;
     980           1 :         BOOST_CHECK_MESSAGE(false, "We should have thrown");
     981           1 :     } catch (const std::ios_base::failure& e) {
     982             :     }
     983             : 
     984             :     // Very large scriptPubKey (3*10^9 bytes) past the end of the stream
     985           2 :     CDataStream tmp(SER_DISK, CLIENT_VERSION);
     986           1 :     uint64_t x = 3000000000ULL;
     987           1 :     tmp << VARINT(x);
     988           1 :     BOOST_CHECK_EQUAL(HexStr(tmp), "8a95c0bb00");
     989           3 :     CDataStream ss5(ParseHex("00008a95c0bb00"), SER_DISK, CLIENT_VERSION);
     990           1 :     try {
     991           2 :         Coin cc5;
     992           1 :         ss5 >> cc5;
     993           1 :         BOOST_CHECK_MESSAGE(false, "We should have thrown");
     994           1 :     } catch (const std::ios_base::failure& e) {
     995             :     }
     996           1 : }
     997             : 
     998             : const static COutPoint OUTPOINT;
     999             : const static CAmount PRUNED = -1;
    1000             : const static CAmount ABSENT = -2;
    1001             : const static CAmount FAIL = -3;
    1002             : const static CAmount VALUE1 = 100;
    1003             : const static CAmount VALUE2 = 200;
    1004             : const static CAmount VALUE3 = 300;
    1005             : const static char DIRTY = CCoinsCacheEntry::DIRTY;
    1006             : const static char FRESH = CCoinsCacheEntry::FRESH;
    1007             : const static char NO_ENTRY = -1;
    1008             : 
    1009             : const static auto FLAGS = {char(0), FRESH, DIRTY, char(DIRTY | FRESH)};
    1010             : const static auto CLEAN_FLAGS = {char(0), FRESH};
    1011             : const static auto ABSENT_FLAGS = {NO_ENTRY};
    1012             : 
    1013         278 : void SetCoinsValue(CAmount value, Coin& coin)
    1014             : {
    1015         278 :     assert(value != ABSENT);
    1016         278 :     coin.Clear();
    1017         278 :     assert(coin.IsSpent());
    1018         278 :     if (value != PRUNED) {
    1019         139 :         coin.out.nValue = value;
    1020         139 :         coin.nHeight = 1;
    1021         139 :         assert(!coin.IsSpent());
    1022             :     }
    1023         278 : }
    1024             : 
    1025         432 : size_t InsertCoinsMapEntry(CCoinsMap& map, CAmount value, char flags)
    1026             : {
    1027         432 :     if (value == ABSENT) {
    1028         154 :         assert(flags == NO_ENTRY);
    1029             :         return 0;
    1030             :     }
    1031         278 :     assert(flags != NO_ENTRY);
    1032         710 :     CCoinsCacheEntry entry;
    1033         278 :     entry.flags = flags;
    1034         278 :     SetCoinsValue(value, entry.coin);
    1035         278 :     auto inserted = map.emplace(OUTPOINT, std::move(entry));
    1036         278 :     assert(inserted.second);
    1037         278 :     return inserted.first->second.coin.DynamicMemoryUsage();
    1038             : }
    1039             : 
    1040         151 : void GetCoinsMapEntry(const CCoinsMap& map, CAmount& value, char& flags)
    1041             : {
    1042         151 :     auto it = map.find(OUTPOINT);
    1043         151 :     if (it == map.end()) {
    1044          28 :         value = ABSENT;
    1045          28 :         flags = NO_ENTRY;
    1046             :     } else {
    1047         123 :         if (it->second.coin.IsSpent()) {
    1048          57 :             value = PRUNED;
    1049             :         } else {
    1050          66 :             value = it->second.coin.out.nValue;
    1051             :         }
    1052         123 :         flags = it->second.flags;
    1053         123 :         assert(flags != NO_ENTRY);
    1054             :     }
    1055         151 : }
    1056             : 
    1057         261 : void WriteCoinsViewEntry(CCoinsView& view, CAmount value, char flags)
    1058             : {
    1059         261 :     CCoinsMap map;
    1060         261 :     InsertCoinsMapEntry(map, value, flags);
    1061         522 :     CAnchorsSaplingMap mapSaplingAnchors;
    1062         522 :     CNullifiersMap mapSaplingNullifiers;
    1063         783 :     view.BatchWrite(map, {}, {}, mapSaplingAnchors, mapSaplingNullifiers);
    1064         253 : }
    1065             : 
    1066         171 : class SingleEntryCacheTest
    1067             : {
    1068             : public:
    1069         171 :     SingleEntryCacheTest(CAmount base_value, CAmount cache_value, char cache_flags)
    1070         171 :     {
    1071         225 :         WriteCoinsViewEntry(base, base_value, base_value == ABSENT ? NO_ENTRY : DIRTY);
    1072         171 :         cache.usage() += InsertCoinsMapEntry(cache.map(), cache_value, cache_flags);
    1073         171 :     }
    1074             : 
    1075             :     CCoinsView root;
    1076             :     CCoinsViewCacheTest base{&root};
    1077             :     CCoinsViewCacheTest cache{&base};
    1078             : };
    1079             : 
    1080          27 : void CheckAccessCoin(CAmount base_value, CAmount cache_value, CAmount expected_value, char cache_flags, char expected_flags)
    1081             : {
    1082          27 :     SingleEntryCacheTest test(base_value, cache_value, cache_flags);
    1083          27 :     test.cache.AccessCoin(OUTPOINT);
    1084          27 :     test.cache.SelfTest();
    1085             : 
    1086          27 :     CAmount result_value;
    1087          27 :     char result_flags;
    1088          27 :     GetCoinsMapEntry(test.cache.map(), result_value, result_flags);
    1089          27 :     BOOST_CHECK_EQUAL(result_value, expected_value);
    1090          27 :     BOOST_CHECK_EQUAL(result_flags, expected_flags);
    1091          27 : }
    1092             : 
    1093           2 : BOOST_AUTO_TEST_CASE(ccoins_access)
    1094             : {
    1095             :     /* Check AccessCoin behavior, requesting a coin from a cache view layered on
    1096             :      * top of a base view, and checking the resulting entry in the cache after
    1097             :      * the access.
    1098             :      *
    1099             :      *               Base    Cache   Result  Cache        Result
    1100             :      *               Value   Value   Value   Flags        Flags
    1101             :      */
    1102           1 :     CheckAccessCoin(ABSENT, ABSENT, ABSENT, NO_ENTRY   , NO_ENTRY   );
    1103           1 :     CheckAccessCoin(ABSENT, PRUNED, PRUNED, 0          , 0          );
    1104           1 :     CheckAccessCoin(ABSENT, PRUNED, PRUNED, FRESH      , FRESH      );
    1105           1 :     CheckAccessCoin(ABSENT, PRUNED, PRUNED, DIRTY      , DIRTY      );
    1106           1 :     CheckAccessCoin(ABSENT, PRUNED, PRUNED, DIRTY|FRESH, DIRTY|FRESH);
    1107           1 :     CheckAccessCoin(ABSENT, VALUE2, VALUE2, 0          , 0          );
    1108           1 :     CheckAccessCoin(ABSENT, VALUE2, VALUE2, FRESH      , FRESH      );
    1109           1 :     CheckAccessCoin(ABSENT, VALUE2, VALUE2, DIRTY      , DIRTY      );
    1110           1 :     CheckAccessCoin(ABSENT, VALUE2, VALUE2, DIRTY|FRESH, DIRTY|FRESH);
    1111           1 :     CheckAccessCoin(PRUNED, ABSENT, PRUNED, NO_ENTRY   , FRESH      );
    1112           1 :     CheckAccessCoin(PRUNED, PRUNED, PRUNED, 0          , 0          );
    1113           1 :     CheckAccessCoin(PRUNED, PRUNED, PRUNED, FRESH      , FRESH      );
    1114           1 :     CheckAccessCoin(PRUNED, PRUNED, PRUNED, DIRTY      , DIRTY      );
    1115           1 :     CheckAccessCoin(PRUNED, PRUNED, PRUNED, DIRTY|FRESH, DIRTY|FRESH);
    1116           1 :     CheckAccessCoin(PRUNED, VALUE2, VALUE2, 0          , 0          );
    1117           1 :     CheckAccessCoin(PRUNED, VALUE2, VALUE2, FRESH      , FRESH      );
    1118           1 :     CheckAccessCoin(PRUNED, VALUE2, VALUE2, DIRTY      , DIRTY      );
    1119           1 :     CheckAccessCoin(PRUNED, VALUE2, VALUE2, DIRTY|FRESH, DIRTY|FRESH);
    1120           1 :     CheckAccessCoin(VALUE1, ABSENT, VALUE1, NO_ENTRY   , 0          );
    1121           1 :     CheckAccessCoin(VALUE1, PRUNED, PRUNED, 0          , 0          );
    1122           1 :     CheckAccessCoin(VALUE1, PRUNED, PRUNED, FRESH      , FRESH      );
    1123           1 :     CheckAccessCoin(VALUE1, PRUNED, PRUNED, DIRTY      , DIRTY      );
    1124           1 :     CheckAccessCoin(VALUE1, PRUNED, PRUNED, DIRTY|FRESH, DIRTY|FRESH);
    1125           1 :     CheckAccessCoin(VALUE1, VALUE2, VALUE2, 0          , 0          );
    1126           1 :     CheckAccessCoin(VALUE1, VALUE2, VALUE2, FRESH      , FRESH      );
    1127           1 :     CheckAccessCoin(VALUE1, VALUE2, VALUE2, DIRTY      , DIRTY      );
    1128           1 :     CheckAccessCoin(VALUE1, VALUE2, VALUE2, DIRTY|FRESH, DIRTY|FRESH);
    1129           1 : }
    1130             : 
    1131          27 : void CheckSpendCoins(CAmount base_value, CAmount cache_value, CAmount expected_value, char cache_flags, char expected_flags)
    1132             : {
    1133          27 :     SingleEntryCacheTest test(base_value, cache_value, cache_flags);
    1134          27 :     test.cache.SpendCoin(OUTPOINT);
    1135          27 :     test.cache.SelfTest();
    1136             : 
    1137          27 :     CAmount result_value;
    1138          27 :     char result_flags;
    1139          27 :     GetCoinsMapEntry(test.cache.map(), result_value, result_flags);
    1140          27 :     BOOST_CHECK_EQUAL(result_value, expected_value);
    1141          27 :     BOOST_CHECK_EQUAL(result_flags, expected_flags);
    1142          27 : };
    1143             : 
    1144           2 : BOOST_AUTO_TEST_CASE(ccoins_spend)
    1145             : {
    1146             :     /* Check SpendCoin behavior, requesting a coin from a cache view layered on
    1147             :      * top of a base view, spending, and then checking
    1148             :      * the resulting entry in the cache after the modification.
    1149             :      *
    1150             :      *               Base    Cache   Result  Cache        Result
    1151             :      *               Value   Value   Value   Flags        Flags
    1152             :      */
    1153           1 :     CheckSpendCoins(ABSENT, ABSENT, ABSENT, NO_ENTRY   , NO_ENTRY   );
    1154           1 :     CheckSpendCoins(ABSENT, PRUNED, PRUNED, 0          , DIRTY      );
    1155           1 :     CheckSpendCoins(ABSENT, PRUNED, ABSENT, FRESH      , NO_ENTRY   );
    1156           1 :     CheckSpendCoins(ABSENT, PRUNED, PRUNED, DIRTY      , DIRTY      );
    1157           1 :     CheckSpendCoins(ABSENT, PRUNED, ABSENT, DIRTY|FRESH, NO_ENTRY   );
    1158           1 :     CheckSpendCoins(ABSENT, VALUE2, PRUNED, 0          , DIRTY      );
    1159           1 :     CheckSpendCoins(ABSENT, VALUE2, ABSENT, FRESH      , NO_ENTRY   );
    1160           1 :     CheckSpendCoins(ABSENT, VALUE2, PRUNED, DIRTY      , DIRTY      );
    1161           1 :     CheckSpendCoins(ABSENT, VALUE2, ABSENT, DIRTY|FRESH, NO_ENTRY   );
    1162           1 :     CheckSpendCoins(PRUNED, ABSENT, ABSENT, NO_ENTRY   , NO_ENTRY   );
    1163           1 :     CheckSpendCoins(PRUNED, PRUNED, PRUNED, 0          , DIRTY      );
    1164           1 :     CheckSpendCoins(PRUNED, PRUNED, ABSENT, FRESH      , NO_ENTRY   );
    1165           1 :     CheckSpendCoins(PRUNED, PRUNED, PRUNED, DIRTY      , DIRTY      );
    1166           1 :     CheckSpendCoins(PRUNED, PRUNED, ABSENT, DIRTY|FRESH, NO_ENTRY   );
    1167           1 :     CheckSpendCoins(PRUNED, VALUE2, PRUNED, 0          , DIRTY      );
    1168           1 :     CheckSpendCoins(PRUNED, VALUE2, ABSENT, FRESH      , NO_ENTRY   );
    1169           1 :     CheckSpendCoins(PRUNED, VALUE2, PRUNED, DIRTY      , DIRTY      );
    1170           1 :     CheckSpendCoins(PRUNED, VALUE2, ABSENT, DIRTY|FRESH, NO_ENTRY   );
    1171           1 :     CheckSpendCoins(VALUE1, ABSENT, PRUNED, NO_ENTRY   , DIRTY      );
    1172           1 :     CheckSpendCoins(VALUE1, PRUNED, PRUNED, 0          , DIRTY      );
    1173           1 :     CheckSpendCoins(VALUE1, PRUNED, ABSENT, FRESH      , NO_ENTRY   );
    1174           1 :     CheckSpendCoins(VALUE1, PRUNED, PRUNED, DIRTY      , DIRTY      );
    1175           1 :     CheckSpendCoins(VALUE1, PRUNED, ABSENT, DIRTY|FRESH, NO_ENTRY   );
    1176           1 :     CheckSpendCoins(VALUE1, VALUE2, PRUNED, 0          , DIRTY      );
    1177           1 :     CheckSpendCoins(VALUE1, VALUE2, ABSENT, FRESH      , NO_ENTRY   );
    1178           1 :     CheckSpendCoins(VALUE1, VALUE2, PRUNED, DIRTY      , DIRTY      );
    1179           1 :     CheckSpendCoins(VALUE1, VALUE2, ABSENT, DIRTY|FRESH, NO_ENTRY   );
    1180           1 : }
    1181             : 
    1182          27 : void CheckAddCoinBase(CAmount base_value, CAmount cache_value, CAmount modify_value, CAmount expected_value, char cache_flags, char expected_flags)
    1183             : {
    1184          27 :     SingleEntryCacheTest test(base_value, cache_value, cache_flags);
    1185             : 
    1186          27 :     CAmount result_value;
    1187          27 :     char result_flags;
    1188          27 :     try {
    1189          54 :         CTxOut output;
    1190          27 :         output.nValue = modify_value;
    1191          39 :         test.cache.AddCoin(OUTPOINT, Coin(std::move(output), 1, false, false), false);
    1192          15 :         test.cache.SelfTest();
    1193          15 :         GetCoinsMapEntry(test.cache.map(), result_value, result_flags);
    1194          12 :     } catch (std::logic_error& e) {
    1195          12 :         result_value = FAIL;
    1196          12 :         result_flags = NO_ENTRY;
    1197             :     }
    1198             : 
    1199          27 :     BOOST_CHECK_EQUAL(result_value, expected_value);
    1200          27 :     BOOST_CHECK_EQUAL(result_flags, expected_flags);
    1201          27 : }
    1202             : 
    1203             : // Simple wrapper for CheckAddCoinBase function above that loops through
    1204             : // different possible base_values, making sure each one gives the same results.
    1205             : // This wrapper lets the coins_add test below be shorter and less repetitive,
    1206             : // while still verifying that the CoinsViewCache::ModifyNewCoins implementation
    1207             : // ignores base values.
    1208             : template <typename... Args>
    1209           9 : void CheckAddCoin(Args&&... args)
    1210             : {
    1211          36 :     for (CAmount base_value : {ABSENT, PRUNED, VALUE1})
    1212          27 :         CheckAddCoinBase(base_value, std::forward<Args>(args)...);
    1213           9 : }
    1214             : 
    1215           2 : BOOST_AUTO_TEST_CASE(ccoins_add)
    1216             : {
    1217             :     /* Check ModifyNewCoin behavior, requesting a new coin from a cache view,
    1218             :      * writing a modification to the coin, and then checking the resulting
    1219             :      * entry in the cache after the modification. Verify behavior with the
    1220             :      * with the ModifyNewCoin coinbase argument set to false, and to true.
    1221             :      *
    1222             :      * PIVX: Remove Coinbase argument (ref: https://github.com/PIVX-Project/PIVX/pull/1775)
    1223             :      *
    1224             :      *           Cache   Write   Result  Cache        Result
    1225             :      *           Value   Value   Value   Flags        Flags
    1226             :      */
    1227           1 :     CheckAddCoin(ABSENT, VALUE3, VALUE3, NO_ENTRY   , DIRTY|FRESH );
    1228           1 :     CheckAddCoin(PRUNED, VALUE3, VALUE3, 0          , DIRTY|FRESH );
    1229           1 :     CheckAddCoin(PRUNED, VALUE3, VALUE3, FRESH      , DIRTY|FRESH );
    1230           1 :     CheckAddCoin(PRUNED, VALUE3, VALUE3, DIRTY      , DIRTY       );
    1231           1 :     CheckAddCoin(PRUNED, VALUE3, VALUE3, DIRTY|FRESH, DIRTY|FRESH );
    1232           1 :     CheckAddCoin(VALUE2, VALUE3, FAIL,   0          , NO_ENTRY    );
    1233           1 :     CheckAddCoin(VALUE2, VALUE3, FAIL,   FRESH      , NO_ENTRY    );
    1234           1 :     CheckAddCoin(VALUE2, VALUE3, FAIL,   DIRTY      , NO_ENTRY    );
    1235           1 :     CheckAddCoin(VALUE2, VALUE3, FAIL,   DIRTY|FRESH, NO_ENTRY    );
    1236           1 : }
    1237             : 
    1238          90 : void CheckWriteCoins(CAmount parent_value, CAmount child_value, CAmount expected_value, char parent_flags, char child_flags, char expected_flags)
    1239             : {
    1240          90 :     SingleEntryCacheTest test(ABSENT, parent_value, parent_flags);
    1241             : 
    1242          90 :     CAmount result_value;
    1243          90 :     char result_flags;
    1244          90 :     try {
    1245          90 :         WriteCoinsViewEntry(test.cache, child_value, child_flags);
    1246          82 :         test.cache.SelfTest();
    1247          82 :         GetCoinsMapEntry(test.cache.map(), result_value, result_flags);
    1248           8 :     } catch (std::logic_error& e) {
    1249           8 :         result_value = FAIL;
    1250           8 :         result_flags = NO_ENTRY;
    1251             :     }
    1252             : 
    1253          90 :     BOOST_CHECK_EQUAL(result_value, expected_value);
    1254          90 :     BOOST_CHECK_EQUAL(result_flags, expected_flags);
    1255          90 : }
    1256             : 
    1257           2 : BOOST_AUTO_TEST_CASE(ccoins_write)
    1258             : {
    1259             :     /* Check BatchWrite behavior, flushing one entry from a child cache to a
    1260             :      * parent cache, and checking the resulting entry in the parent cache
    1261             :      * after the write.
    1262             :      *
    1263             :      *              Parent  Child   Result  Parent       Child        Result
    1264             :      *              Value   Value   Value   Flags        Flags        Flags
    1265             :      */
    1266           1 :     CheckWriteCoins(ABSENT, ABSENT, ABSENT, NO_ENTRY   , NO_ENTRY   , NO_ENTRY   );
    1267           1 :     CheckWriteCoins(ABSENT, PRUNED, PRUNED, NO_ENTRY   , DIRTY      , DIRTY      );
    1268           1 :     CheckWriteCoins(ABSENT, PRUNED, ABSENT, NO_ENTRY   , DIRTY|FRESH, NO_ENTRY   );
    1269           1 :     CheckWriteCoins(ABSENT, VALUE2, VALUE2, NO_ENTRY   , DIRTY      , DIRTY      );
    1270           1 :     CheckWriteCoins(ABSENT, VALUE2, VALUE2, NO_ENTRY   , DIRTY|FRESH, DIRTY|FRESH);
    1271           1 :     CheckWriteCoins(PRUNED, ABSENT, PRUNED, 0          , NO_ENTRY   , 0          );
    1272           1 :     CheckWriteCoins(PRUNED, ABSENT, PRUNED, FRESH      , NO_ENTRY   , FRESH      );
    1273           1 :     CheckWriteCoins(PRUNED, ABSENT, PRUNED, DIRTY      , NO_ENTRY   , DIRTY      );
    1274           1 :     CheckWriteCoins(PRUNED, ABSENT, PRUNED, DIRTY|FRESH, NO_ENTRY   , DIRTY|FRESH);
    1275           1 :     CheckWriteCoins(PRUNED, PRUNED, PRUNED, 0          , DIRTY      , DIRTY      );
    1276           1 :     CheckWriteCoins(PRUNED, PRUNED, PRUNED, 0          , DIRTY|FRESH, DIRTY      );
    1277           1 :     CheckWriteCoins(PRUNED, PRUNED, ABSENT, FRESH      , DIRTY      , NO_ENTRY   );
    1278           1 :     CheckWriteCoins(PRUNED, PRUNED, ABSENT, FRESH      , DIRTY|FRESH, NO_ENTRY   );
    1279           1 :     CheckWriteCoins(PRUNED, PRUNED, PRUNED, DIRTY      , DIRTY      , DIRTY      );
    1280           1 :     CheckWriteCoins(PRUNED, PRUNED, PRUNED, DIRTY      , DIRTY|FRESH, DIRTY      );
    1281           1 :     CheckWriteCoins(PRUNED, PRUNED, ABSENT, DIRTY|FRESH, DIRTY      , NO_ENTRY   );
    1282           1 :     CheckWriteCoins(PRUNED, PRUNED, ABSENT, DIRTY|FRESH, DIRTY|FRESH, NO_ENTRY   );
    1283           1 :     CheckWriteCoins(PRUNED, VALUE2, VALUE2, 0          , DIRTY      , DIRTY      );
    1284           1 :     CheckWriteCoins(PRUNED, VALUE2, VALUE2, 0          , DIRTY|FRESH, DIRTY      );
    1285           1 :     CheckWriteCoins(PRUNED, VALUE2, VALUE2, FRESH      , DIRTY      , DIRTY|FRESH);
    1286           1 :     CheckWriteCoins(PRUNED, VALUE2, VALUE2, FRESH      , DIRTY|FRESH, DIRTY|FRESH);
    1287           1 :     CheckWriteCoins(PRUNED, VALUE2, VALUE2, DIRTY      , DIRTY      , DIRTY      );
    1288           1 :     CheckWriteCoins(PRUNED, VALUE2, VALUE2, DIRTY      , DIRTY|FRESH, DIRTY      );
    1289           1 :     CheckWriteCoins(PRUNED, VALUE2, VALUE2, DIRTY|FRESH, DIRTY      , DIRTY|FRESH);
    1290           1 :     CheckWriteCoins(PRUNED, VALUE2, VALUE2, DIRTY|FRESH, DIRTY|FRESH, DIRTY|FRESH);
    1291           1 :     CheckWriteCoins(VALUE1, ABSENT, VALUE1, 0          , NO_ENTRY   , 0          );
    1292           1 :     CheckWriteCoins(VALUE1, ABSENT, VALUE1, FRESH      , NO_ENTRY   , FRESH      );
    1293           1 :     CheckWriteCoins(VALUE1, ABSENT, VALUE1, DIRTY      , NO_ENTRY   , DIRTY      );
    1294           1 :     CheckWriteCoins(VALUE1, ABSENT, VALUE1, DIRTY|FRESH, NO_ENTRY   , DIRTY|FRESH);
    1295           1 :     CheckWriteCoins(VALUE1, PRUNED, PRUNED, 0          , DIRTY      , DIRTY      );
    1296           1 :     CheckWriteCoins(VALUE1, PRUNED, FAIL  , 0          , DIRTY|FRESH, NO_ENTRY   );
    1297           1 :     CheckWriteCoins(VALUE1, PRUNED, ABSENT, FRESH      , DIRTY      , NO_ENTRY   );
    1298           1 :     CheckWriteCoins(VALUE1, PRUNED, FAIL  , FRESH      , DIRTY|FRESH, NO_ENTRY   );
    1299           1 :     CheckWriteCoins(VALUE1, PRUNED, PRUNED, DIRTY      , DIRTY      , DIRTY      );
    1300           1 :     CheckWriteCoins(VALUE1, PRUNED, FAIL  , DIRTY      , DIRTY|FRESH, NO_ENTRY   );
    1301           1 :     CheckWriteCoins(VALUE1, PRUNED, ABSENT, DIRTY|FRESH, DIRTY      , NO_ENTRY   );
    1302           1 :     CheckWriteCoins(VALUE1, PRUNED, FAIL  , DIRTY|FRESH, DIRTY|FRESH, NO_ENTRY   );
    1303           1 :     CheckWriteCoins(VALUE1, VALUE2, VALUE2, 0          , DIRTY      , DIRTY      );
    1304           1 :     CheckWriteCoins(VALUE1, VALUE2, FAIL  , 0          , DIRTY|FRESH, NO_ENTRY   );
    1305           1 :     CheckWriteCoins(VALUE1, VALUE2, VALUE2, FRESH      , DIRTY      , DIRTY|FRESH);
    1306           1 :     CheckWriteCoins(VALUE1, VALUE2, FAIL  , FRESH      , DIRTY|FRESH, NO_ENTRY   );
    1307           1 :     CheckWriteCoins(VALUE1, VALUE2, VALUE2, DIRTY      , DIRTY      , DIRTY      );
    1308           1 :     CheckWriteCoins(VALUE1, VALUE2, FAIL  , DIRTY      , DIRTY|FRESH, NO_ENTRY   );
    1309           1 :     CheckWriteCoins(VALUE1, VALUE2, VALUE2, DIRTY|FRESH, DIRTY      , DIRTY|FRESH);
    1310           1 :     CheckWriteCoins(VALUE1, VALUE2, FAIL  , DIRTY|FRESH, DIRTY|FRESH, NO_ENTRY   );
    1311             : 
    1312             :     // The checks above omit cases where the child flags are not DIRTY, since
    1313             :     // they would be too repetitive (the parent cache is never updated in these
    1314             :     // cases). The loop below covers these cases and makes sure the parent cache
    1315             :     // is always left unchanged.
    1316           4 :     for (CAmount parent_value : {ABSENT, PRUNED, VALUE1})
    1317          12 :         for (CAmount child_value : {ABSENT, PRUNED, VALUE2})
    1318          42 :             for (char parent_flags : parent_value == ABSENT ? ABSENT_FLAGS : FLAGS)
    1319          90 :                 for (char child_flags : child_value == ABSENT ? ABSENT_FLAGS : CLEAN_FLAGS)
    1320          45 :                     CheckWriteCoins(parent_value, child_value, parent_value, parent_flags, child_flags, parent_flags);
    1321           1 : }
    1322             : 
    1323             : BOOST_AUTO_TEST_SUITE_END()

Generated by: LCOV version 1.14