// Copyright (c) 2014 The Bitcoin Core 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 "random.h" #include "uint256.h" #include #include #include namespace { class CCoinsViewTest : public CCoinsView { uint256 hashBestBlock_; std::map map_; public: bool GetCoins(const uint256& txid, CCoins& coins) const { std::map::const_iterator it = map_.find(txid); if (it == map_.end()) { return false; } coins = it->second; if (coins.IsPruned() && insecure_rand() % 2 == 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() && insecure_rand() % 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_AUTO_TEST_SUITE(coins_tests) 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 result; // The cache stack. CCoinsViewTest base; // A CCoinsViewTest at the bottom. std::vector stack; // A stack of CCoinsViewCaches on top. stack.push_back(new CCoinsViewCache(base, false)); // Start with one cache. // Use a limited set of random transaction ids, so we do test overwriting entries. std::vector txids; txids.resize(NUM_SIMULATION_ITERATIONS / 8); for (unsigned int i = 0; i < txids.size(); i++) { txids[i] = GetRandHash(); } for (unsigned int i = 0; i < NUM_SIMULATION_ITERATIONS; i++) { // Do a random modification. { uint256 txid = txids[insecure_rand() % 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 (insecure_rand() % 5 == 0 || coins.IsPruned()) { if (coins.IsPruned()) { added_an_entry = true; } else { updated_an_entry = true; } coins.nVersion = insecure_rand(); coins.vout.resize(1); coins.vout[0].nValue = insecure_rand(); *entry = coins; } else { coins.Clear(); entry->Clear(); removed_an_entry = true; } } // Once every 1000 iterations and at the end, verify the full cache. if (insecure_rand() % 1000 == 1 || i == NUM_SIMULATION_ITERATIONS - 1) { for (std::map::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 (insecure_rand() % 100 == 0) { // Every 100 iterations, change the cache stack. if (stack.size() > 0 && insecure_rand() % 2 == 0) { stack.back()->Flush(); delete stack.back(); stack.pop_back(); } if (stack.size() == 0 || (stack.size() < 4 && insecure_rand() % 2)) { CCoinsView* tip = &base; if (stack.size() > 0) { tip = stack.back(); } else { removed_all_caches = true; } stack.push_back(new CCoinsViewCache(*tip, false)); 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_SUITE_END()