// Copyright (c) 2011-2015 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include BOOST_FIXTURE_TEST_SUITE(pegin_spent_tests, TestingSetup) class CCoinsViewTester : public CCoinsView { public: bool IsPeginSpentCalled{false}; bool IsPeginSpent(const std::pair &outpoint) const override { const_cast(IsPeginSpentCalled) = true; return CCoinsView::IsPeginSpent(outpoint); } CoinsCachePair m_written_sentinel{}; CCoinsMapMemoryResource resource; CCoinsMap mapCoinsWritten{0, CCoinsMap::hasher{}, CCoinsMap::key_equal{}, &resource}; bool BatchWrite(CoinsViewCacheCursor& cursor, const uint256 &hashBlock) override { mapCoinsWritten.clear(); m_written_sentinel.second.SelfRef(m_written_sentinel); for (auto it{cursor.Begin()}; it != cursor.End(); it = cursor.NextAndMaybeErase(*it)) { auto [dest_it, inserted] = mapCoinsWritten.try_emplace(it->first); dest_it->second.coin = it->second.coin; dest_it->second.peginSpent = it->second.peginSpent; dest_it->second.AddFlags(it->second.GetFlags(), *dest_it, m_written_sentinel); } return true; } CCoinsViewTester() { m_written_sentinel.second.SelfRef(m_written_sentinel); } }; BOOST_AUTO_TEST_CASE(PeginSpent_validity) { CCoinsViewTester coins; CCoinsViewCache coinsCache(&coins); Coin ret; //Basic insert of blank outpoint pair, blank COutPoint allows for checking coinsCache std::pair outpoint = std::make_pair(GetRandHash(), COutPoint(Txid::FromUint256(GetRandHash()), 42)); BOOST_CHECK(!coinsCache.GetCoin(outpoint.second)); //Checking for pegin spentness should not create an entry BOOST_CHECK(!coinsCache.IsPeginSpent(outpoint)); BOOST_CHECK(coins.IsPeginSpentCalled); coins.IsPeginSpentCalled = false; BOOST_CHECK(!coinsCache.IsPeginSpent(outpoint)); BOOST_CHECK(!coins.IsPeginSpentCalled); coinsCache.SetPeginSpent(outpoint, true); BOOST_CHECK(coinsCache.IsPeginSpent(outpoint)); coinsCache.SetPeginSpent(outpoint, false); BOOST_CHECK(!coinsCache.IsPeginSpent(outpoint)); coinsCache.SetPeginSpent(outpoint, true); BOOST_CHECK(coinsCache.IsPeginSpent(outpoint)); //Check for slightly similar non-existent entries std::pair outpoint2(outpoint); outpoint2.second.n = 0; BOOST_CHECK(!coinsCache.IsPeginSpent(outpoint2)); CCoinsMapMemoryResource resource; CCoinsMap mapCoins{0, CCoinsMap::hasher{}, CCoinsMap::key_equal{}, &resource}; CoinsCachePair sentinel{}; sentinel.second.SelfRef(sentinel); size_t usage{0}; std::pair outpoint3(std::make_pair(GetRandHash(), COutPoint(Txid::FromUint256(GetRandHash()), 42))); //Attempt batch write of non-dirty pegin, no effect { auto [it, inserted] = mapCoins.try_emplace(outpoint3); it->second.peginSpent = true; it->second.AddFlags(CCoinsCacheEntry::PEGIN, *it, sentinel); } { auto cursor{CoinsViewCacheCursor(usage, sentinel, mapCoins, /*will_erase=*/true)}; coinsCache.BatchWrite(cursor, uint256()); } //Check for effect coins.IsPeginSpentCalled = false; BOOST_CHECK(!coinsCache.IsPeginSpent(outpoint3)); BOOST_CHECK(coins.IsPeginSpentCalled); mapCoins.clear(); sentinel.second.SelfRef(sentinel); //Write again with pegin, dirty && fresh flags, but unspent. No effect. { auto [it, inserted] = mapCoins.try_emplace(outpoint3); it->second.peginSpent = false; it->second.AddFlags(CCoinsCacheEntry::PEGIN | CCoinsCacheEntry::DIRTY | CCoinsCacheEntry::FRESH, *it, sentinel); } { auto cursor{CoinsViewCacheCursor(usage, sentinel, mapCoins, /*will_erase=*/true)}; coinsCache.BatchWrite(cursor, uint256()); } //Check for effect coins.IsPeginSpentCalled = false; BOOST_CHECK(!coinsCache.IsPeginSpent(outpoint3)); BOOST_CHECK(coins.IsPeginSpentCalled); mapCoins.clear(); sentinel.second.SelfRef(sentinel); //Re-mark as spent. It's super effective. { auto [it, inserted] = mapCoins.try_emplace(outpoint3); it->second.peginSpent = true; it->second.AddFlags(CCoinsCacheEntry::PEGIN | CCoinsCacheEntry::DIRTY | CCoinsCacheEntry::FRESH, *it, sentinel); } { auto cursor{CoinsViewCacheCursor(usage, sentinel, mapCoins, /*will_erase=*/true)}; coinsCache.BatchWrite(cursor, uint256()); } //Check for effect coins.IsPeginSpentCalled = false; BOOST_CHECK(coinsCache.IsPeginSpent(outpoint3)); BOOST_CHECK(!coins.IsPeginSpentCalled); mapCoins.clear(); sentinel.second.SelfRef(sentinel); //Add an entry we never IsPeginSpent'd first (ie added to cache via SetPeginSpent) std::pair outpoint4(std::make_pair(GetRandHash(), COutPoint(Txid::FromUint256(GetRandHash()), 42))); coinsCache.SetPeginSpent(outpoint4, true); // Check the final state of coinsCache.mapCoins is sane. BOOST_CHECK_EQUAL(coins.mapCoinsWritten.size(), 0U); coinsCache.Flush(); BOOST_CHECK_EQUAL(coins.mapCoinsWritten.size(), 4U); BOOST_CHECK_EQUAL(coins.mapCoinsWritten[outpoint].GetFlags(), CCoinsCacheEntry::DIRTY | CCoinsCacheEntry::FRESH | CCoinsCacheEntry::PEGIN); BOOST_CHECK_EQUAL(coins.mapCoinsWritten[outpoint].peginSpent, true); BOOST_CHECK_EQUAL(coins.mapCoinsWritten[outpoint2].GetFlags(), CCoinsCacheEntry::FRESH | CCoinsCacheEntry::PEGIN); BOOST_CHECK_EQUAL(coins.mapCoinsWritten[outpoint2].peginSpent, false); BOOST_CHECK_EQUAL(coins.mapCoinsWritten[outpoint3].GetFlags(), CCoinsCacheEntry::DIRTY | CCoinsCacheEntry::FRESH | CCoinsCacheEntry::PEGIN); BOOST_CHECK_EQUAL(coins.mapCoinsWritten[outpoint3].peginSpent, true); BOOST_CHECK_EQUAL(coins.mapCoinsWritten[outpoint4].GetFlags(), CCoinsCacheEntry::DIRTY | CCoinsCacheEntry::FRESH | CCoinsCacheEntry::PEGIN); BOOST_CHECK_EQUAL(coins.mapCoinsWritten[outpoint3].peginSpent, true); // CCoinsViewCache should lose outpoint2 in BatchWrite logic CCoinsViewTester coins2; CCoinsViewCache coinsCache2(&coins2); { size_t written_usage{0}; for (auto& [key, entry] : coins.mapCoinsWritten) { written_usage += entry.coin.DynamicMemoryUsage(); } auto cursor{CoinsViewCacheCursor(written_usage, coins.m_written_sentinel, coins.mapCoinsWritten, /*will_erase=*/true)}; BOOST_CHECK(coinsCache2.BatchWrite(cursor, uint256())); } coinsCache2.Flush(); BOOST_CHECK_EQUAL(coins2.mapCoinsWritten.size(), 3U); BOOST_CHECK_EQUAL(coins2.mapCoinsWritten[outpoint].GetFlags(), CCoinsCacheEntry::DIRTY | CCoinsCacheEntry::FRESH | CCoinsCacheEntry::PEGIN); BOOST_CHECK_EQUAL(coins2.mapCoinsWritten[outpoint].peginSpent, true); BOOST_CHECK_EQUAL(coins2.mapCoinsWritten[outpoint3].GetFlags(), CCoinsCacheEntry::DIRTY | CCoinsCacheEntry::FRESH | CCoinsCacheEntry::PEGIN); BOOST_CHECK_EQUAL(coins2.mapCoinsWritten[outpoint3].peginSpent, true); BOOST_CHECK_EQUAL(coins2.mapCoinsWritten[outpoint4].GetFlags(), CCoinsCacheEntry::DIRTY | CCoinsCacheEntry::FRESH | CCoinsCacheEntry::PEGIN); BOOST_CHECK_EQUAL(coins2.mapCoinsWritten[outpoint4].peginSpent, true); } BOOST_AUTO_TEST_SUITE_END()