23 #include <boost/test/unit_test.hpp> 45 std::map<COutPoint, Coin> map_;
50 std::optional<Coin> GetCoin(
const COutPoint& outpoint)
const override 52 if (
auto it{map_.find(outpoint)}; it != map_.end() && !it->second.IsSpent())
return it->second;
56 uint256 GetBestBlock()
const override {
return hashBestBlock_; }
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_ = hashBlock;
85 for (
const auto& entry : cacheCoins) {
86 ret += entry.second.coin.DynamicMemoryUsage();
96 CCoinsMap& map()
const {
return cacheCoins; }
98 size_t& usage()
const {
return cachedCoinsUsage; }
99 size_t& dirty()
const {
return m_dirty_count; }
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++) {
152 auto txid = txids[m_rng.
randrange(txids.size())];
158 bool test_havecoin_before = m_rng.
randbits(2) == 0;
159 bool test_havecoin_after = m_rng.
randbits(2) == 0;
161 bool result_havecoin = test_havecoin_before ? stack.back()->HaveCoin(
COutPoint(txid, 0)) :
false;
169 if (test_havecoin_before) {
173 if (test_havecoin_after) {
178 if (m_rng.
randrange(5) == 0 || coin.IsSpent()) {
184 if (m_rng.
randrange(16) == 0 && coin.IsSpent()) {
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));
197 stack.back()->AddCoin(op, std::move(newcoin), !coin.IsSpent() || m_rng.
randbool());
201 removed_an_entry =
true;
210 int cacheid = m_rng.
rand32() % stack.size();
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) {
236 if (stack.size() > 1 && m_rng.
randbool() == 0) {
237 unsigned int flushIndex = m_rng.
randrange(stack.size() - 1);
238 if (fake_best_block) stack[flushIndex]->SetBestBlock(m_rng.
rand256());
239 bool should_erase = m_rng.
randrange(4) < 3;
240 should_erase ? stack[flushIndex]->Flush() : stack[flushIndex]->Sync();
241 flushed_without_erase |= !should_erase;
246 if (stack.size() > 0 && m_rng.
randbool() == 0) {
248 if (fake_best_block) stack.back()->SetBestBlock(m_rng.
rand256());
249 bool should_erase = m_rng.
randrange(4) < 3;
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);
309 typedef 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;
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];
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();
458 BOOST_CHECK(stack.back()->SpendCoin(utxod->first));
462 Coin coin = undo.vprevout[0];
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 };
584 bool operator==(
const CoinEntry& o)
const =
default;
585 friend std::ostream&
operator<<(std::ostream& os,
const CoinEntry& e) {
return os << e.value <<
", " << e.state; }
587 constexpr
bool IsDirtyFresh()
const {
return state == State::DIRTY_FRESH; }
588 constexpr
bool IsDirty()
const {
return state == State::DIRTY || IsDirtyFresh(); }
589 constexpr
bool IsFresh()
const {
return state == State::FRESH || IsDirtyFresh(); }
591 static constexpr
State ToState(
const bool is_dirty,
const bool is_fresh) {
592 if (is_dirty && is_fresh)
return State::DIRTY_FRESH;
593 if (is_dirty)
return State::DIRTY;
594 if (is_fresh)
return State::FRESH;
618 constexpr
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()};
672 auto base_cache_coin{base_value ==
ABSENT ?
MISSING : CoinEntry{base_value, CoinEntry::State::DIRTY}};
676 cache.dirty() += cache_coin->IsDirty();
681 CCoinsViewCacheTest base{&root};
682 CCoinsViewCacheTest cache{&base};
690 test.cache.SelfTest(
false);
720 test.cache.SelfTest();
749 bool possible_overwrite{coinbase};
751 if (
auto* expected_coin{std::get_if<MaybeCoin>(&expected)}) {
753 test.cache.SelfTest();
756 BOOST_CHECK_EXCEPTION(
add_coin(), std::logic_error,
HasReason(std::get<std::string>(expected)));
796 if (
auto* expected_coin{std::get_if<MaybeCoin>(&expected)}) {
798 test.cache.SelfTest(
false);
801 BOOST_CHECK_EXCEPTION(write_coins(), std::logic_error,
HasReason(std::get<std::string>(expected)));
900 CCoinsViewCacheTest* view,
902 std::vector<std::unique_ptr<CCoinsViewCacheTest>>& all_caches,
903 bool do_erasing_flush)
908 auto flush_all = [
this, &all_caches](
bool erase) {
910 for (
auto i = all_caches.rbegin(); i != all_caches.rend(); ++i) {
912 cache->SanityCheck();
915 cache->SetBestBlock(m_rng.
rand256());
916 erase ? cache->Flush() : cache->Sync();
929 view->AddCoin(outp,
Coin(coin),
false);
931 cache_usage = view->DynamicMemoryUsage();
932 cache_size = view->map().size();
956 if (do_erasing_flush) {
962 BOOST_TEST(view->DynamicMemoryUsage() <= cache_usage);
964 BOOST_TEST(view->map().size() < cache_size);
969 view->AccessCoin(outp);
976 view->AddCoin(outp,
Coin(coin),
false),
1007 all_caches[0]->AddCoin(outp, std::move(coin),
false);
1008 all_caches[0]->Sync();
1011 BOOST_CHECK(!all_caches[1]->HaveCoinInCache(outp));
1032 all_caches[0]->AddCoin(outp, std::move(coin),
false);
1040 all_caches[0]->Sync();
1044 BOOST_CHECK(!all_caches[0]->HaveCoinInCache(outp));
1052 CCoinsViewDB base{{.path =
"test", .cache_bytes = 1 << 23, .memory_only =
true}, {}};
1053 std::vector<std::unique_ptr<CCoinsViewCacheTest>> caches;
1054 caches.push_back(std::make_unique<CCoinsViewCacheTest>(&base));
1055 caches.push_back(std::make_unique<CCoinsViewCacheTest>(caches.back().get()));
1057 for (
const auto& view : caches) {
1058 TestFlushBehavior(view.get(), base, caches,
false);
1059 TestFlushBehavior(view.get(), base, caches,
true);
1069 CCoinsMap map{0, CCoinsMap::hasher{}, CCoinsMap::key_equal{}, &resource};
1078 for (
size_t i = 0; i < 1000; ++i) {
1091 CCoinsViewCacheTest cache{&root};
1096 cache.AddCoin(outpoint,
Coin{coin1},
false);
1109 CCoinsViewCacheTest cache{&root};
1114 cache.EmplaceCoinInternalDANGER(
COutPoint{outpoint},
Coin{coin1});
1118 cache.EmplaceCoinInternalDANGER(
COutPoint{outpoint},
Coin{coin2});
1126 CCoinsViewTest root{m_rng};
1129 root_cache.SetBestBlock(base_best_block);
1137 cache.EmplaceCoinInternalDANGER(
COutPoint{outpoint},
Coin{coin});
1141 cache.SetBestBlock(cache_best_block);
1144 const auto reset_guard{cache.CreateResetGuard()};
1146 BOOST_CHECK(!cache.AccessCoin(outpoint).IsSpent());
1150 BOOST_CHECK(!root_cache.HaveCoinInCache(outpoint));
1153 BOOST_CHECK(cache.AccessCoin(outpoint).IsSpent());
1157 BOOST_CHECK(!root_cache.HaveCoinInCache(outpoint));
1161 const auto reset_guard{cache.CreateResetGuard()};
1164 BOOST_CHECK(cache.AccessCoin(outpoint).IsSpent());
1168 BOOST_CHECK(!root_cache.HaveCoinInCache(outpoint));
1177 CCoinsViewTest base{m_rng};
1189 CCoinsViewCacheTest main_cache{&base};
1190 const auto fetched{main_cache.PeekCoin(outpoint)};
1193 BOOST_CHECK(!main_cache.HaveCoinInCache(outpoint));
CoinsCachePair * NextAndMaybeErase(CoinsCachePair ¤t) noexcept
Return the next entry after current, possibly erasing current.
std::vector< B > randbytes(size_t len) noexcept
Generate random bytes.
static const unsigned int NUM_SIMULATION_ITERATIONS
std::optional< CoinEntry > MaybeCoin
bool IsSpent() const
Either this coin never existed (see e.g.
static OutputGroup MakeCoin(const CAmount &amount, bool is_eff_value=true, CoinSelectionParams cs_params=default_cs_params, int custom_spending_vsize=P2WPKH_INPUT_VSIZE)
Make one OutputGroup with a single UTXO that either has a given effective value (default) or a given ...
int ApplyTxInUndo(Coin &&undo, CCoinsViewCache &view, const COutPoint &out)
Restore the UTXO in a Coin at a given COutPoint.
void assign(size_type n, const T &val)
bool randbool() noexcept
Generate a random boolean.
A Coin in one level of the coins database caching hierarchy.
#define BOOST_CHECK_THROW(stmt, excMatch)
bool operator==(const CNetAddr &a, const CNetAddr &b)
static void CheckAccessCoin(const CAmount base_value, const MaybeCoin &cache_coin, const MaybeCoin &expected)
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...
std::map< COutPoint, std::tuple< CTransaction, CTxUndo, Coin > > UtxoData
CoinsCachePair * End() const noexcept
constexpr MaybeCoin VALUE3_DIRTY_FRESH
static size_t DynamicUsage(const int8_t &v)
Dynamic memory usage for built-in types is zero.
static void CheckWriteCoins(const MaybeCoin &parent, const MaybeCoin &child, const CoinOrError &expected)
constexpr bool IsDirtyFresh() const
constexpr MaybeCoin VALUE1_DIRTY
CTxOut out
unspent transaction output
unsigned int fCoinBase
whether containing transaction was a coinbase
friend std::ostream & operator<<(std::ostream &os, const CoinEntry &e)
constexpr MaybeCoin MISSING
static void SetCoinsValue(const CAmount value, Coin &coin)
constexpr auto EX_OVERWRITE_UNSPENT
std::variant< MaybeCoin, std::string > CoinOrError
static MaybeCoin GetCoinsMapEntry(const CCoinsMap &map, const COutPoint &outp=OUTPOINT)
CCoinsMap::allocator_type::ResourceType CCoinsMapMemoryResource
constexpr MaybeCoin SPENT_DIRTY
const std::vector< CTxIn > vin
CAmount RandMoney(Rng &&rng)
constexpr MaybeCoin VALUE1_DIRTY_FRESH
const Coin & AccessByTxid(const CCoinsViewCache &view, const Txid &txid)
Utility function to find any unspent output with a given txid.
bool IsUnspendable() const
Returns whether the script is guaranteed to fail at execution, regardless of the initial stack...
Minimal stream for reading from an existing byte array by std::span.
int64_t CAmount
Amount in satoshis (Can be negative)
static constexpr unsigned int STATIC_SIZE
uint32_t nHeight
at which height this containing transaction was included in the active block chain ...
std::pair< const COutPoint, CCoinsCacheEntry > CoinsCachePair
constexpr MaybeCoin VALUE2_CLEAN
SingleEntryCacheTest(const CAmount base_value, const MaybeCoin &cache_coin)
Abstract view on the open txout dataset.
BOOST_FIXTURE_TEST_SUITE(cuckoocache_tests, BasicTestingSetup)
Test Suite for CuckooCache.
Double ended buffer combining vector and stream-like interfaces.
constexpr MaybeCoin VALUE3_DIRTY
Cursor for iterating over the linked list of flagged entries in CCoinsViewCache.
BOOST_AUTO_TEST_CASE(ccoins_serialization)
void SimulationTest(CCoinsView *base, bool fake_best_block)
BOOST_AUTO_TEST_SUITE_END()
constexpr MaybeCoin VALUE2_FRESH
static void CheckSpendCoins(const CAmount base_value, const MaybeCoin &cache_coin, const MaybeCoin &expected)
constexpr MaybeCoin SPENT_DIRTY_FRESH
BOOST_CHECK_EXCEPTION predicates to check the specific validation error.
uint32_t rand32() noexcept
Generate a random 32-bit integer.
An output of a transaction.
constexpr MaybeCoin VALUE1_CLEAN
""_hex is a compile-time user-defined literal returning a std::array<std::byte>, equivalent to ParseH...
UtxoData::iterator FindRandomFrom(const std::set< COutPoint > &utxoSet)
virtual void BatchWrite(CoinsViewCacheCursor &cursor, const uint256 &hashBlock)
Do a bulk modification (multiple Coin changes + BestBlock change).
static void WriteCoinsViewEntry(CCoinsView &view, const MaybeCoin &cache_coin)
Txid GetHash() const
Compute the hash of this CMutableTransaction.
An outpoint - a combination of a transaction hash and an index n into its vout.
std::vector< CTxOut > vout
static void add_coin(const CAmount &nValue, int nInput, std::vector< OutputGroup > &set)
constexpr bool IsNull() const
bool HaveCoin(const COutPoint &outpoint) const override
Just check whether a given outpoint is unspent.
void AddCoin(const COutPoint &outpoint, Coin &&coin, bool possible_overwrite)
Add a coin.
static void CheckAddCoin(const CAmount base_value, const MaybeCoin &cache_coin, const CAmount modify_value, const CoinOrError &expected, const bool coinbase)
void UpdateCoins(const CTransaction &tx, CCoinsViewCache &inputs, CTxUndo &txundo, int nHeight)
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...
CScript GetScriptForDestination(const CTxDestination &dest)
Generate a Bitcoin scriptPubKey for the given CTxDestination.
CoinsCachePair * Begin() const noexcept
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: ...
static bool sanity_check(const std::vector< CTransactionRef > &transactions, const std::map< COutPoint, CAmount > &bumpfees)
static const COutPoint OUTPOINT
constexpr CoinEntry(const CAmount v, const State s)
static void SetDirty(CoinsCachePair &pair, CoinsCachePair &sentinel) noexcept
#define BOOST_CHECK_EQUAL(v1, v2)
Serialized script, used inside transaction inputs and outputs.
static transaction_identifier FromUint256(const uint256 &id)
Undo information for a CTransaction.
CCoinsView backed by the coin database (chainstate/)
uint64_t randbits(int bits) noexcept
Generate a random (bits)-bit integer.
uint256 rand256() noexcept
generate a random uint256.
constexpr MaybeCoin SPENT_FRESH
I randrange(I range) noexcept
Generate a random integer in the range [0..range), with range > 0.
static constexpr State ToState(const bool is_dirty, const bool is_fresh)
constexpr MaybeCoin SPENT_CLEAN
constexpr bool IsFresh() const
A mutable version of CTransaction.
constexpr MaybeCoin VALUE1_FRESH
constexpr MaybeCoin VALUE2_DIRTY_FRESH
The basic transaction that is broadcasted on the network and contained in blocks. ...
constexpr bool IsDirty() const
CCoinsView that adds a memory cache for transactions to another CCoinsView.
constexpr MaybeCoin VALUE2_DIRTY
static void SetFresh(CoinsCachePair &pair, CoinsCachePair &sentinel) noexcept
Seed with a compile time constant of zeros.
constexpr auto EX_FRESH_MISAPPLIED
BOOST_FIXTURE_TEST_CASE(coins_cache_base_simulation_test, CacheTest)
static size_t InsertCoinsMapEntry(CCoinsMap &map, CoinsCachePair &sentinel, const CoinEntry &cache_coin)
std::string HexStr(const std::span< const uint8_t > s)
Convert a span of bytes to a lower-case hexadecimal string.
CCoinsViewCacheTest cache
#define BOOST_CHECK(expr)