23#include <boost/test/unit_test.hpp>
45 std::map<COutPoint, Coin> map_;
52 if (
auto it{map_.find(outpoint)}; it != map_.end() && !it->second.IsSpent())
return it->second;
61 if (it->second.IsDirty()) {
63 map_[it->first] = it->second.coin;
64 if (it->second.coin.IsSpent() && m_rng.
randrange(3) == 0) {
66 map_.erase(it->first);
71 hashBestBlock_ = block_hash;
85 for (
const auto& entry : cacheCoins) {
86 ret += entry.second.coin.DynamicMemoryUsage();
124 bool removed_all_caches =
false;
125 bool reached_4_caches =
false;
126 bool added_an_entry =
false;
127 bool added_an_unspendable_entry =
false;
128 bool removed_an_entry =
false;
129 bool updated_an_entry =
false;
130 bool found_an_entry =
false;
131 bool missed_an_entry =
false;
132 bool uncached_an_entry =
false;
133 bool flushed_without_erase =
false;
136 std::map<COutPoint, Coin> result;
139 std::vector<std::unique_ptr<CCoinsViewCacheTest>> stack;
140 stack.push_back(std::make_unique<CCoinsViewCacheTest>(base));
143 std::vector<Txid> txids;
145 for (
unsigned int i = 0; i < txids.size(); i++) {
161 bool result_havecoin = test_havecoin_before ? stack.back()->HaveCoin(
COutPoint(txid, 0)) :
false;
169 if (test_havecoin_before) {
173 if (test_havecoin_after) {
187 added_an_unspendable_entry =
true;
191 (coin.
IsSpent() ? added_an_entry : updated_an_entry) =
true;
195 stack.back()->EmplaceCoinInternalDANGER(std::move(op), std::move(newcoin));
201 removed_an_entry =
true;
211 stack[cacheid]->Uncache(
out);
212 uncached_an_entry |= !stack[cacheid]->HaveCoinInCache(
out);
217 for (
const auto& entry : result) {
218 bool have = stack.back()->HaveCoin(entry.first);
219 const Coin& coin = stack.back()->AccessCoin(entry.first);
223 missed_an_entry =
true;
225 BOOST_CHECK(stack.back()->HaveCoinInCache(entry.first));
226 found_an_entry =
true;
229 for (
const auto& test : stack) {
238 if (fake_best_block) stack[flushIndex]->SetBestBlock(
m_rng.
rand256());
240 should_erase ? stack[flushIndex]->Flush() : stack[flushIndex]->Sync();
241 flushed_without_erase |= !should_erase;
248 if (fake_best_block) stack.back()->SetBestBlock(
m_rng.
rand256());
250 should_erase ? stack.back()->Flush() : stack.back()->Sync();
251 flushed_without_erase |= !should_erase;
254 if (stack.size() == 0 || (stack.size() < 4 &&
m_rng.
randbool())) {
257 if (stack.size() > 0) {
258 tip = stack.back().get();
260 removed_all_caches =
true;
262 stack.push_back(std::make_unique<CCoinsViewCacheTest>(tip));
263 if (stack.size() == 4) {
264 reached_4_caches =
true;
289 CCoinsViewTest base{m_rng};
290 SimulationTest(&base,
false);
299 CCoinsViewDB db_base{{.path =
"test", .cache_bytes = 1 << 23, .memory_only =
true}, {}};
300 SimulationTest(&db_base,
true);
309typedef std::map<COutPoint, std::tuple<CTransaction,CTxUndo,Coin>>
UtxoData;
315 if (utxoSetIt == utxoSet.end()) {
316 utxoSetIt = utxoSet.begin();
318 auto utxoDataIt = utxoData.find(*utxoSetIt);
319 assert(utxoDataIt != utxoData.end());
334 bool spent_a_duplicate_coinbase =
false;
336 std::map<COutPoint, Coin> result;
339 CCoinsViewTest base{m_rng};
340 std::vector<std::unique_ptr<CCoinsViewCacheTest>> stack;
341 stack.push_back(std::make_unique<CCoinsViewCacheTest>(&base));
344 std::set<COutPoint> coinbase_coins;
345 std::set<COutPoint> disconnected_coins;
346 std::set<COutPoint> duplicate_coins;
347 std::set<COutPoint> utxoset;
350 uint32_t randiter = m_rng.
rand32();
353 if (randiter % 20 < 19) {
357 tx.
vout[0].nValue = i;
358 tx.
vout[0].scriptPubKey.assign(m_rng.
rand32() & 0x3F, 0);
359 const int height{int(m_rng.
rand32() >> 1)};
363 if (randiter % 20 < 2 || coinbase_coins.size() < 10) {
365 if (m_rng.
randrange(10) == 0 && coinbase_coins.size()) {
366 auto utxod = FindRandomFrom(coinbase_coins);
370 disconnected_coins.erase(utxod->first);
372 duplicate_coins.insert(utxod->first);
385 if (randiter % 20 == 2 && disconnected_coins.size()) {
386 auto utxod = FindRandomFrom(disconnected_coins);
388 prevout = tx.
vin[0].prevout;
389 if (!
CTransaction(tx).IsCoinBase() && !utxoset.contains(prevout)) {
390 disconnected_coins.erase(utxod->first);
395 if (utxoset.contains(utxod->first)) {
397 assert(duplicate_coins.contains(utxod->first));
399 disconnected_coins.erase(utxod->first);
404 auto utxod = FindRandomFrom(utxoset);
405 prevout = utxod->first;
408 tx.
vin[0].prevout = prevout;
412 old_coin = result[prevout];
414 result[prevout].
Clear();
416 utxoset.erase(prevout);
420 if (duplicate_coins.contains(prevout)) {
421 spent_a_duplicate_coinbase =
true;
435 utxoset.insert(outpoint);
438 utxoData.emplace(outpoint, std::make_tuple(tx,undo,old_coin));
439 }
else if (utxoset.size()) {
441 auto utxod = FindRandomFrom(utxoset);
444 CTxUndo &undo = std::get<1>(utxod->second);
445 Coin &orig_coin = std::get<2>(utxod->second);
449 result[utxod->first].Clear();
452 result[tx.
vin[0].prevout] = orig_coin;
458 BOOST_CHECK(stack.back()->SpendCoin(utxod->first));
466 disconnected_coins.insert(utxod->first);
469 utxoset.erase(utxod->first);
471 utxoset.insert(tx.
vin[0].prevout);
476 for (
const auto& entry : result) {
477 bool have = stack.back()->HaveCoin(entry.first);
478 const Coin& coin = stack.back()->AccessCoin(entry.first);
485 if (utxoset.size() > 1 && m_rng.
randrange(30) == 0) {
486 stack[m_rng.
rand32() % stack.size()]->Uncache(FindRandomFrom(utxoset)->first);
488 if (disconnected_coins.size() > 1 && m_rng.
randrange(30) == 0) {
489 stack[m_rng.
rand32() % stack.size()]->Uncache(FindRandomFrom(disconnected_coins)->first);
491 if (duplicate_coins.size() > 1 && m_rng.
randrange(30) == 0) {
492 stack[m_rng.
rand32() % stack.size()]->Uncache(FindRandomFrom(duplicate_coins)->first);
497 if (stack.size() > 1 && m_rng.
randbool() == 0) {
498 unsigned int flushIndex = m_rng.
randrange(stack.size() - 1);
499 stack[flushIndex]->Flush();
504 if (stack.size() > 0 && m_rng.
randbool() == 0) {
505 stack.back()->Flush();
508 if (stack.size() == 0 || (stack.size() < 4 && m_rng.
randbool())) {
510 if (stack.size() > 0) {
511 tip = stack.back().get();
513 stack.push_back(std::make_unique<CCoinsViewCacheTest>(tip));
526 SpanReader{
"97f23c835800816115944e077fe7c803cfa57f29b36bf87c1d35"_hex} >> cc1;
534 SpanReader{
"8ddf77bbd123008c988f1a4a4de2161e0f50aac7f17e7f9555caa4"_hex} >> cc2;
552 BOOST_CHECK_MESSAGE(
false,
"We should have thrown");
553 }
catch (
const std::ios_base::failure&) {
558 uint64_t x = 3000000000ULL;
564 BOOST_CHECK_MESSAGE(
false,
"We should have thrown");
565 }
catch (
const std::ios_base::failure&) {
577 enum class State { CLEAN, DIRTY, FRESH, DIRTY_FRESH };
585 friend std::ostream&
operator<<(std::ostream& os,
const CoinEntry& e) {
return os << e.value <<
", " << e.state; }
591 static constexpr State ToState(
const bool is_dirty,
const bool is_fresh) {
618constexpr auto EX_OVERWRITE_UNSPENT{
"Attempted to overwrite an unspent coin (when possible_overwrite is false)"};
626 if (value !=
SPENT) {
637 auto [iter, inserted] = map.emplace(
OUTPOINT, std::move(entry));
641 return iter->second.coin.DynamicMemoryUsage();
646 if (
auto it{map.find(outp)}; it != map.end()) {
648 it->second.coin.IsSpent() ?
SPENT : it->second.coin.out.nValue,
657 sentinel.second.SelfRef(sentinel);
659 CCoinsMap map{0, CCoinsMap::hasher{}, CCoinsMap::key_equal{}, &resource};
661 size_t dirty_count{cache_coin && cache_coin->IsDirty()};
676 cache.dirty() += cache_coin->IsDirty();
689 test.cache.SelfTest(
false);
719 test.cache.SelfTest();
748 bool possible_overwrite{coinbase};
750 if (
auto* expected_coin{std::get_if<MaybeCoin>(&expected)}) {
752 test.cache.SelfTest();
755 BOOST_CHECK_EXCEPTION(
add_coin(), std::logic_error,
HasReason(std::get<std::string>(expected)));
795 if (
auto* expected_coin{std::get_if<MaybeCoin>(&expected)}) {
797 test.cache.SelfTest(
false);
800 BOOST_CHECK_EXCEPTION(write_coins(), std::logic_error,
HasReason(std::get<std::string>(expected)));
899 CCoinsViewCacheTest* view,
901 std::vector<std::unique_ptr<CCoinsViewCacheTest>>& all_caches,
902 bool do_erasing_flush)
907 auto flush_all = [
this, &all_caches](
bool erase) {
909 for (
auto i = all_caches.rbegin(); i != all_caches.rend(); ++i) {
911 cache->SanityCheck();
915 erase ? cache->Flush() : cache->Sync();
928 view->AddCoin(outp,
Coin(coin),
false);
930 cache_usage = view->DynamicMemoryUsage();
931 cache_size = view->map().size();
955 if (do_erasing_flush) {
961 BOOST_TEST(view->DynamicMemoryUsage() <= cache_usage);
963 BOOST_TEST(view->map().size() < cache_size);
968 view->AccessCoin(outp);
975 view->AddCoin(outp,
Coin(coin),
false),
1006 all_caches[0]->AddCoin(outp, std::move(coin),
false);
1007 all_caches[0]->Sync();
1010 BOOST_CHECK(!all_caches[1]->HaveCoinInCache(outp));
1031 all_caches[0]->AddCoin(outp, std::move(coin),
false);
1039 all_caches[0]->Sync();
1043 BOOST_CHECK(!all_caches[0]->HaveCoinInCache(outp));
1051 CCoinsViewDB base{{.path =
"test", .cache_bytes = 1 << 23, .memory_only =
true}, {}};
1052 std::vector<std::unique_ptr<CCoinsViewCacheTest>> caches;
1053 caches.push_back(std::make_unique<CCoinsViewCacheTest>(&base));
1054 caches.push_back(std::make_unique<CCoinsViewCacheTest>(caches.back().get()));
1056 for (
const auto& view : caches) {
1057 TestFlushBehavior(view.get(), base, caches,
false);
1058 TestFlushBehavior(view.get(), base, caches,
true);
1068 CCoinsMap map{0, CCoinsMap::hasher{}, CCoinsMap::key_equal{}, &resource};
1077 for (
size_t i = 0; i < 1000; ++i) {
1094 cache.AddCoin(outpoint,
Coin{coin1},
false);
1111 cache.EmplaceCoinInternalDANGER(
COutPoint{outpoint},
Coin{coin1});
1115 cache.EmplaceCoinInternalDANGER(
COutPoint{outpoint},
Coin{coin2});
1123 CCoinsViewTest root{m_rng};
1126 root_cache.SetBestBlock(base_best_block);
1134 cache.EmplaceCoinInternalDANGER(
COutPoint{outpoint},
Coin{coin});
1138 cache.SetBestBlock(cache_best_block);
1141 const auto reset_guard{cache.CreateResetGuard()};
1143 BOOST_CHECK(!cache.AccessCoin(outpoint).IsSpent());
1147 BOOST_CHECK(!root_cache.HaveCoinInCache(outpoint));
1150 BOOST_CHECK(cache.AccessCoin(outpoint).IsSpent());
1154 BOOST_CHECK(!root_cache.HaveCoinInCache(outpoint));
1158 const auto reset_guard{cache.CreateResetGuard()};
1161 BOOST_CHECK(cache.AccessCoin(outpoint).IsSpent());
1165 BOOST_CHECK(!root_cache.HaveCoinInCache(outpoint));
1174 CCoinsViewTest base{m_rng};
1186 CCoinsViewCacheTest main_cache{&base};
1187 const auto fetched{main_cache.PeekCoin(outpoint)};
1190 BOOST_CHECK(!main_cache.HaveCoinInCache(outpoint));
CScript GetScriptForDestination(const CTxDestination &dest)
Generate a Bitcoin scriptPubKey for the given CTxDestination.
int64_t CAmount
Amount in satoshis (Can be negative)
CCoinsView that adds a memory cache for transactions to another CCoinsView.
void AddCoin(const COutPoint &outpoint, Coin &&coin, bool possible_overwrite)
Add a coin.
unsigned int GetCacheSize() const
Size of the cache (in number of transaction outputs)
CoinsCachePair m_sentinel
size_t DynamicMemoryUsage() const
Calculate the size of the cache (in bytes)
void SanityCheck() const
Run an internal sanity check on the cache data structure. */.
CCoinsView backed by the coin database (chainstate/)
bool HaveCoin(const COutPoint &outpoint) const override
Just check whether a given outpoint is unspent.
Pure abstract view on the open txout dataset.
virtual void BatchWrite(CoinsViewCacheCursor &cursor, const uint256 &block_hash)=0
Do a bulk modification (multiple Coin changes + BestBlock change).
An outpoint - a combination of a transaction hash and an index n into its vout.
Serialized script, used inside transaction inputs and outputs.
bool IsUnspendable() const
Returns whether the script is guaranteed to fail at execution, regardless of the initial stack.
The basic transaction that is broadcasted on the network and contained in blocks.
const std::vector< CTxIn > vin
An output of a transaction.
Undo information for a CTransaction.
std::vector< Coin > vprevout
CTxOut out
unspent transaction output
bool IsSpent() const
Either this coin never existed (see e.g.
uint32_t nHeight
at which height this containing transaction was included in the active block chain
unsigned int fCoinBase
whether containing transaction was a coinbase
void BatchWrite(CoinsViewCacheCursor &cursor, const uint256 &) override
Do a bulk modification (multiple Coin changes + BestBlock change).
uint256 GetBestBlock() const override
Retrieve the block hash whose state this CCoinsView currently represents.
std::optional< Coin > GetCoin(const COutPoint &) const override
Retrieve the Coin (unspent transaction output) for a given outpoint.
static CoinsViewEmpty & Get()
Double ended buffer combining vector and stream-like interfaces.
BOOST_CHECK_EXCEPTION predicates to check the specific validation error.
static void CheckAllDataAccountedFor(const PoolResource< MAX_BLOCK_SIZE_BYTES, ALIGN_BYTES > &resource)
Once all blocks are given back to the resource, tests that the freelists are consistent:
I randrange(I range) noexcept
Generate a random integer in the range [0..range), with range > 0.
uint256 rand256() noexcept
generate a random uint256.
bool randbool() noexcept
Generate a random boolean.
std::vector< B > randbytes(size_t len) noexcept
Generate random bytes.
uint32_t rand32() noexcept
Generate a random 32-bit integer.
uint64_t randbits(int bits) noexcept
Generate a random (bits)-bit integer.
CCoinsViewCacheTest cache
SingleEntryCacheTest(const CAmount base_value, const MaybeCoin &cache_coin)
Minimal stream for reading from an existing byte array by std::span.
constexpr bool IsNull() const
static constexpr unsigned int STATIC_SIZE
void assign(size_type n, const T &val)
static transaction_identifier FromUint256(const uint256 &id)
static void add_coin(const CAmount &nValue, int nInput, std::vector< OutputGroup > &set)
const Coin & AccessByTxid(const CCoinsViewCache &view, const Txid &txid)
Utility function to find any unspent output with a given txid.
std::pair< const COutPoint, CCoinsCacheEntry > CoinsCachePair
std::unordered_map< COutPoint, CCoinsCacheEntry, SaltedOutpointHasher, std::equal_to< COutPoint >, PoolAllocator< CoinsCachePair, sizeof(CoinsCachePair)+sizeof(void *) *4 > > CCoinsMap
PoolAllocator's MAX_BLOCK_SIZE_BYTES parameter here uses sizeof the data, and adds the size of 4 poin...
CCoinsMap::allocator_type::ResourceType CCoinsMapMemoryResource
BOOST_AUTO_TEST_CASE(ccoins_serialization)
constexpr MaybeCoin VALUE2_DIRTY
std::optional< CoinEntry > MaybeCoin
constexpr MaybeCoin VALUE2_CLEAN
constexpr MaybeCoin MISSING
static MaybeCoin GetCoinsMapEntry(const CCoinsMap &map, const COutPoint &outp=OUTPOINT)
static const COutPoint OUTPOINT
constexpr MaybeCoin VALUE2_DIRTY_FRESH
static void WriteCoinsViewEntry(CCoinsView &view, const MaybeCoin &cache_coin)
static void CheckWriteCoins(const MaybeCoin &parent, const MaybeCoin &child, const CoinOrError &expected)
int ApplyTxInUndo(Coin &&undo, CCoinsViewCache &view, const COutPoint &out)
Restore the UTXO in a Coin at a given COutPoint.
static const unsigned int NUM_SIMULATION_ITERATIONS
constexpr MaybeCoin VALUE1_CLEAN
constexpr MaybeCoin SPENT_DIRTY_FRESH
constexpr MaybeCoin SPENT_CLEAN
constexpr auto EX_OVERWRITE_UNSPENT
constexpr MaybeCoin VALUE1_DIRTY
constexpr MaybeCoin VALUE1_FRESH
static size_t InsertCoinsMapEntry(CCoinsMap &map, CoinsCachePair &sentinel, const CoinEntry &cache_coin)
static void CheckSpendCoins(const CAmount base_value, const MaybeCoin &cache_coin, const MaybeCoin &expected)
void UpdateCoins(const CTransaction &tx, CCoinsViewCache &inputs, CTxUndo &txundo, int nHeight)
constexpr MaybeCoin VALUE2_FRESH
constexpr MaybeCoin VALUE3_DIRTY_FRESH
static void CheckAccessCoin(const CAmount base_value, const MaybeCoin &cache_coin, const MaybeCoin &expected)
constexpr MaybeCoin VALUE3_DIRTY
constexpr MaybeCoin VALUE1_DIRTY_FRESH
constexpr MaybeCoin SPENT_DIRTY
constexpr auto EX_FRESH_MISAPPLIED
std::variant< MaybeCoin, std::string > CoinOrError
static void SetCoinsValue(const CAmount value, Coin &coin)
BOOST_FIXTURE_TEST_CASE(coins_cache_base_simulation_test, CacheTest)
constexpr MaybeCoin SPENT_FRESH
static void CheckAddCoin(const CAmount base_value, const MaybeCoin &cache_coin, const CAmount modify_value, const CoinOrError &expected, const bool coinbase)
BOOST_FIXTURE_TEST_SUITE(cuckoocache_tests, BasicTestingSetup)
Test Suite for CuckooCache.
BOOST_AUTO_TEST_SUITE_END()
std::string HexStr(const std::span< const uint8_t > s)
Convert a span of bytes to a lower-case hexadecimal string.
static bool sanity_check(const std::vector< CTransactionRef > &transactions, const std::map< COutPoint, CAmount > &bumpfees)
static size_t DynamicUsage(const int8_t &v)
Dynamic memory usage for built-in types is zero.
""_hex is a compile-time user-defined literal returning a std::array<std::byte>, equivalent to ParseH...
bool operator==(const CNetAddr &a, const CNetAddr &b)
#define BOOST_CHECK_THROW(stmt, excMatch)
#define BOOST_CHECK_EQUAL(v1, v2)
#define BOOST_CHECK(expr)
A Coin in one level of the coins database caching hierarchy.
static void SetFresh(CoinsCachePair &pair, CoinsCachePair &sentinel) noexcept
static void SetDirty(CoinsCachePair &pair, CoinsCachePair &sentinel) noexcept
A mutable version of CTransaction.
std::vector< CTxOut > vout
Txid GetHash() const
Compute the hash of this CMutableTransaction.
void SimulationTest(CCoinsView *base, bool fake_best_block)
constexpr bool IsDirty() const
friend std::ostream & operator<<(std::ostream &os, const CoinEntry &e)
bool operator==(const CoinEntry &o) const =default
constexpr bool IsDirtyFresh() const
static constexpr State ToState(const bool is_dirty, const bool is_fresh)
constexpr bool IsFresh() const
constexpr CoinEntry(const CAmount v, const State s)
Cursor for iterating over the linked list of flagged entries in CCoinsViewCache.
CoinsCachePair * NextAndMaybeErase(CoinsCachePair ¤t) noexcept
Return the next entry after current, possibly erasing current.
CoinsCachePair * Begin() const noexcept
CoinsCachePair * End() const noexcept
void TestFlushBehavior(CCoinsViewCacheTest *view, CCoinsViewDB &base, std::vector< std::unique_ptr< CCoinsViewCacheTest > > &all_caches, bool do_erasing_flush)
For CCoinsViewCache instances backed by either another cache instance or leveldb, test cache behavior...
std::map< COutPoint, std::tuple< CTransaction, CTxUndo, Coin > > UtxoData
UtxoData::iterator FindRandomFrom(const std::set< COutPoint > &utxoSet)
@ ZEROS
Seed with a compile time constant of zeros.
CAmount RandMoney(Rng &&rng)