text: // Copyright (c) 2014 The Bitcoin Core developers
  // Copyright (c) 2019 The PIVX developers
  // Distributed under the MIT software license, see the accompanying
  // file COPYING or http://www.opensource.org/licenses/mit-license.php.
  
  #include "coins.h"
  #include "script/standard.h"
  #include "uint256.h"
  #include "utilstrencodings.h"
  #include "test/test_deviant.h"
  
  #include <vector>
  #include <map>
  
  #include <boost/test/unit_test.hpp>
  
  namespace
  {
  class CCoinsViewTest : public CCoinsView
  {
      uint256 hashBestBlock_;
      std::map<uint256, CCoins> map_;
  
  public:
      bool GetCoins(const uint256& txid, CCoins& coins) const
      {
          std::map<uint256, CCoins>::const_iterator it = map_.find(txid);
          if (it == map_.end()) {
              return false;
          }
          coins = it->second;
          if (coins.IsPruned() && InsecureRandBool() == 0) {
              // Randomly return false in case of an empty entry.
              return false;
          }
          return true;
      }
  
      bool HaveCoins(const uint256& txid) const
      {
          CCoins coins;
          return GetCoins(txid, coins);
      }
  
      uint256 GetBestBlock() const { return hashBestBlock_; }
  
      bool BatchWrite(CCoinsMap& mapCoins, const uint256& hashBlock)
      {
          for (CCoinsMap::iterator it = mapCoins.begin(); it != mapCoins.end(); ) {
              map_[it->first] = it->second.coins;
              if (it->second.coins.IsPruned() && InsecureRandRange(3) == 0) {
                  // Randomly delete empty entries on write.
                  map_.erase(it->first);
              }
              mapCoins.erase(it++);
          }
          mapCoins.clear();
          hashBestBlock_ = hashBlock;
          return true;
      }
  
      bool GetStats(CCoinsStats& stats) const { return false; }
  };
  }
  
  BOOST_FIXTURE_TEST_SUITE(coins_tests, BasicTestingSetup)
  
  static const unsigned int NUM_SIMULATION_ITERATIONS = 40000;
  
  // This is a large randomized insert/remove simulation test on a variable-size
  // stack of caches on top of CCoinsViewTest.
  //
  // It will randomly create/update/delete CCoins entries to a tip of caches, with
  // txids picked from a limited list of random 256-bit hashes. Occasionally, a
  // new tip is added to the stack of caches, or the tip is flushed and removed.
  //
  // During the process, booleans are kept to make sure that the randomized
  // operation hits all branches.
  BOOST_AUTO_TEST_CASE(coins_cache_simulation_test)
  {
      // Various coverage trackers.
      bool removed_all_caches = false;
      bool reached_4_caches = false;
      bool added_an_entry = false;
      bool removed_an_entry = false;
      bool updated_an_entry = false;
      bool found_an_entry = false;
      bool missed_an_entry = false;
  
      // A simple map to track what we expect the cache stack to represent.
      std::map<uint256, CCoins> result;
  
      // The cache stack.
      CCoinsViewTest base; // A CCoinsViewTest at the bottom.
      std::vector<CCoinsViewCache*> stack; // A stack of CCoinsViewCaches on top.
      stack.push_back(new CCoinsViewCache(&base)); // Start with one cache.
  
      // Use a limited set of random transaction ids, so we do test overwriting entries.
      std::vector<uint256> txids;
      txids.resize(NUM_SIMULATION_ITERATIONS / 8);
      for (unsigned int i = 0; i < txids.size(); i++) {
          txids[i] = InsecureRand256();
      }
  
      for (unsigned int i = 0; i < NUM_SIMULATION_ITERATIONS; i++) {
          // Do a random modification.
          {
              uint256 txid = txids[InsecureRandRange(txids.size())]; // txid we're going to modify in this iteration.
              CCoins& coins = result[txid];
              CCoinsModifier entry = stack.back()->ModifyCoins(txid);
              BOOST_CHECK(coins == *entry);
              if (InsecureRandRange(5) == 0 || coins.IsPruned()) {
                  if (coins.IsPruned()) {
                      added_an_entry = true;
                  } else {
                      updated_an_entry = true;
                  }
                  coins.nVersion = InsecureRand32();
                  coins.vout.resize(1);
                  coins.vout[0].nValue = InsecureRand32();
                  *entry = coins;
              } else {
                  coins.Clear();
                  entry->Clear();
                  removed_an_entry = true;
              }
          }
  
          // Once every 1000 iterations and at the end, verify the full cache.
          if (InsecureRandRange(1000) == 1 || i == NUM_SIMULATION_ITERATIONS - 1) {
              for (std::map<uint256, CCoins>::iterator it = result.begin(); it != result.end(); it++) {
                  const CCoins* coins = stack.back()->AccessCoins(it->first);
                  if (coins) {
                      BOOST_CHECK(*coins == it->second);
                      found_an_entry = true;
                  } else {
                      BOOST_CHECK(it->second.IsPruned());
                      missed_an_entry = true;
                  }
              }
          }
  
          if (InsecureRandRange(100) == 0) {
              // Every 100 iterations, change the cache stack.
              if (stack.size() > 0 && InsecureRandBool() == 0) {
                  stack.back()->Flush();
                  delete stack.back();
                  stack.pop_back();
              }
              if (stack.size() == 0 || (stack.size() < 4 && InsecureRandBool())) {
                  CCoinsView* tip = &base;
                  if (stack.size() > 0) {
                      tip = stack.back();
                  } else {
                      removed_all_caches = true;
                  }
                  stack.push_back(new CCoinsViewCache(tip));
                  if (stack.size() == 4) {
                      reached_4_caches = true;
                  }
              }
          }
      }
  
      // Clean up the stack.
      while (stack.size() > 0) {
          delete stack.back();
          stack.pop_back();
      }
  
      // Verify coverage.
      BOOST_CHECK(removed_all_caches);
      BOOST_CHECK(reached_4_caches);
      BOOST_CHECK(added_an_entry);
      BOOST_CHECK(removed_an_entry);
      BOOST_CHECK(updated_an_entry);
      BOOST_CHECK(found_an_entry);
      BOOST_CHECK(missed_an_entry);
  }
  
  BOOST_AUTO_TEST_CASE(ccoins_serialization)
  {
      // Good example
      CDataStream ss1(ParseHex("0108835800816115944e077fe7c803cfa57f29b36bf87c1d35b4934b"), SER_DISK, CLIENT_VERSION);
      CCoins cc1;
      ss1 >> cc1;
      BOOST_CHECK_EQUAL(cc1.nVersion, 1);
      BOOST_CHECK_EQUAL(cc1.fCoinBase, false);
      BOOST_CHECK_EQUAL(cc1.nHeight, 870987);
      BOOST_CHECK_EQUAL(cc1.vout.size(), 2);
      BOOST_CHECK_EQUAL(cc1.IsAvailable(0), false);
      BOOST_CHECK_EQUAL(cc1.IsAvailable(1), true);
      BOOST_CHECK_EQUAL(cc1.vout[1].nValue, 60000000000ULL);
      BOOST_CHECK_EQUAL(HexStr(cc1.vout[1].scriptPubKey), HexStr(GetScriptForDestination(CKeyID(uint160(ParseHex("816115944e077fe7c803cfa57f29b36bf87c1d35"))))));
  
      // Good example
      CDataStream ss2(ParseHex("0111044086ef97d5790061b01caab50f1b8e9c50a5057eb43c2d9563a4eebbd123008c988f1a4a4de2161e0f50aac7f17e7f9555caa482d21f"), SER_DISK, CLIENT_VERSION);
      CCoins cc2;
      ss2 >> cc2;
      BOOST_CHECK_EQUAL(cc2.nVersion, 1);
      BOOST_CHECK_EQUAL(cc2.fCoinBase, true);
      BOOST_CHECK_EQUAL(cc2.nHeight, 59807);
      BOOST_CHECK_EQUAL(cc2.vout.size(), 17);
      for (int i = 0; i < 17; i++) {
          BOOST_CHECK_EQUAL(cc2.IsAvailable(i), i == 4 || i == 16);
      }
      BOOST_CHECK_EQUAL(cc2.vout[4].nValue, 234925952);
      BOOST_CHECK_EQUAL(HexStr(cc2.vout[4].scriptPubKey), HexStr(GetScriptForDestination(CKeyID(uint160(ParseHex("61b01caab50f1b8e9c50a5057eb43c2d9563a4ee"))))));
      BOOST_CHECK_EQUAL(cc2.vout[16].nValue, 110397);
      BOOST_CHECK_EQUAL(HexStr(cc2.vout[16].scriptPubKey), HexStr(GetScriptForDestination(CKeyID(uint160(ParseHex("8c988f1a4a4de2161e0f50aac7f17e7f9555caa4"))))));
  
      // Smallest possible example
      CDataStream ssx(SER_DISK, CLIENT_VERSION);
      BOOST_CHECK_EQUAL(HexStr(ssx.begin(), ssx.end()), "");
  
      CDataStream ss3(ParseHex("0004000600"), SER_DISK, CLIENT_VERSION);
      CCoins cc3;
      ss3 >> cc3;
      BOOST_CHECK_EQUAL(cc3.nVersion, 0);
      BOOST_CHECK_EQUAL(cc3.fCoinBase, false);
      BOOST_CHECK_EQUAL(cc3.nHeight, 0);
      BOOST_CHECK_EQUAL(cc3.vout.size(), 1);
      BOOST_CHECK_EQUAL(cc3.IsAvailable(0), true);
      BOOST_CHECK_EQUAL(cc3.vout[0].nValue, 0);
      BOOST_CHECK_EQUAL(cc3.vout[0].scriptPubKey.size(), 0);
  
      // scriptPubKey that ends beyond the end of the stream
      CDataStream ss4(ParseHex("0004000800"), SER_DISK, CLIENT_VERSION);
      try {
          CCoins cc4;
          ss4 >> cc4;
          BOOST_CHECK_MESSAGE(false, "We should have thrown");
      } catch (const std::ios_base::failure& e) {
      }
  
      // Very large scriptPubKey (3*10^9 bytes) past the end of the stream
      CDataStream tmp(SER_DISK, CLIENT_VERSION);
      uint64_t x = 3000000000ULL;
      tmp << VARINT(x);
      BOOST_CHECK_EQUAL(HexStr(tmp.begin(), tmp.end()), "8a95c0bb00");
      CDataStream ss5(ParseHex("0002008a95c0bb0000"), SER_DISK, CLIENT_VERSION);
      try {
          CCoins cc5;
          ss5 >> cc5;
          BOOST_CHECK_MESSAGE(false, "We should have thrown");
      } catch (const std::ios_base::failure& e) {
      }
  }
  
  BOOST_AUTO_TEST_SUITE_END()
  