25#include <boost/test/unit_test.hpp>
47 std::map<COutPoint, Coin> map_;
54 if (
auto it{map_.find(outpoint)}; it != map_.end() && !it->second.IsSpent())
return it->second;
63 if (it->second.IsDirty()) {
65 map_[it->first] = it->second.coin;
66 if (it->second.coin.IsSpent() && m_rng.
randrange(3) == 0) {
68 map_.erase(it->first);
73 hashBestBlock_ = block_hash;
87 for (
const auto& entry : cacheCoins) {
88 ret += entry.second.coin.DynamicMemoryUsage();
126 bool removed_all_caches =
false;
127 bool reached_4_caches =
false;
128 bool added_an_entry =
false;
129 bool added_an_unspendable_entry =
false;
130 bool removed_an_entry =
false;
131 bool updated_an_entry =
false;
132 bool found_an_entry =
false;
133 bool missed_an_entry =
false;
134 bool uncached_an_entry =
false;
135 bool flushed_without_erase =
false;
138 std::map<COutPoint, Coin> result;
141 std::vector<std::unique_ptr<CCoinsViewCacheTest>> stack;
142 stack.push_back(std::make_unique<CCoinsViewCacheTest>(base));
145 std::vector<Txid> txids;
147 for (
unsigned int i = 0; i < txids.size(); i++) {
163 bool result_havecoin = test_havecoin_before ? stack.back()->HaveCoin(
COutPoint(txid, 0)) :
false;
171 if (test_havecoin_before) {
175 if (test_havecoin_after) {
189 added_an_unspendable_entry =
true;
193 (coin.
IsSpent() ? added_an_entry : updated_an_entry) =
true;
197 stack.back()->EmplaceCoinInternalDANGER(std::move(op), std::move(newcoin));
203 removed_an_entry =
true;
213 stack[cacheid]->Uncache(
out);
214 uncached_an_entry |= !stack[cacheid]->HaveCoinInCache(
out);
219 for (
const auto& entry : result) {
220 bool have = stack.back()->HaveCoin(entry.first);
221 const Coin& coin = stack.back()->AccessCoin(entry.first);
225 missed_an_entry =
true;
227 BOOST_CHECK(stack.back()->HaveCoinInCache(entry.first));
228 found_an_entry =
true;
231 for (
const auto& test : stack) {
240 if (fake_best_block) stack[flushIndex]->SetBestBlock(
m_rng.
rand256());
242 should_erase ? stack[flushIndex]->Flush() : stack[flushIndex]->Sync();
243 flushed_without_erase |= !should_erase;
250 if (fake_best_block) stack.back()->SetBestBlock(
m_rng.
rand256());
252 should_erase ? stack.back()->Flush() : stack.back()->Sync();
253 flushed_without_erase |= !should_erase;
256 if (stack.size() == 0 || (stack.size() < 4 &&
m_rng.
randbool())) {
259 if (stack.size() > 0) {
260 tip = stack.back().get();
262 removed_all_caches =
true;
264 stack.push_back(std::make_unique<CCoinsViewCacheTest>(tip));
265 if (stack.size() == 4) {
266 reached_4_caches =
true;
291 CCoinsViewTest base{m_rng};
292 SimulationTest(&base,
false);
301 CCoinsViewDB db_base{{.path =
"test", .cache_bytes = 8_MiB, .memory_only =
true}, {}};
302 SimulationTest(&db_base,
true);
311typedef std::map<COutPoint, std::tuple<CTransaction,CTxUndo,Coin>>
UtxoData;
317 if (utxoSetIt == utxoSet.end()) {
318 utxoSetIt = utxoSet.begin();
320 auto utxoDataIt = utxoData.find(*utxoSetIt);
321 assert(utxoDataIt != utxoData.end());
336 bool spent_a_duplicate_coinbase =
false;
338 std::map<COutPoint, Coin> result;
341 CCoinsViewTest base{m_rng};
342 std::vector<std::unique_ptr<CCoinsViewCacheTest>> stack;
343 stack.push_back(std::make_unique<CCoinsViewCacheTest>(&base));
346 std::set<COutPoint> coinbase_coins;
347 std::set<COutPoint> disconnected_coins;
348 std::set<COutPoint> duplicate_coins;
349 std::set<COutPoint> utxoset;
352 uint32_t randiter = m_rng.
rand32();
355 if (randiter % 20 < 19) {
359 tx.
vout[0].nValue = i;
360 tx.
vout[0].scriptPubKey.assign(m_rng.
rand32() & 0x3F, 0);
361 const int height{int(m_rng.
rand32() >> 1)};
365 if (randiter % 20 < 2 || coinbase_coins.size() < 10) {
367 if (m_rng.
randrange(10) == 0 && coinbase_coins.size()) {
368 auto utxod = FindRandomFrom(coinbase_coins);
372 disconnected_coins.erase(utxod->first);
374 duplicate_coins.insert(utxod->first);
387 if (randiter % 20 == 2 && disconnected_coins.size()) {
388 auto utxod = FindRandomFrom(disconnected_coins);
390 prevout = tx.
vin[0].prevout;
391 if (!
CTransaction(tx).IsCoinBase() && !utxoset.contains(prevout)) {
392 disconnected_coins.erase(utxod->first);
397 if (utxoset.contains(utxod->first)) {
399 assert(duplicate_coins.contains(utxod->first));
401 disconnected_coins.erase(utxod->first);
406 auto utxod = FindRandomFrom(utxoset);
407 prevout = utxod->first;
410 tx.
vin[0].prevout = prevout;
414 old_coin = result[prevout];
416 result[prevout].
Clear();
418 utxoset.erase(prevout);
422 if (duplicate_coins.contains(prevout)) {
423 spent_a_duplicate_coinbase =
true;
437 utxoset.insert(outpoint);
440 utxoData.emplace(outpoint, std::make_tuple(tx,undo,old_coin));
441 }
else if (utxoset.size()) {
443 auto utxod = FindRandomFrom(utxoset);
446 CTxUndo &undo = std::get<1>(utxod->second);
447 Coin &orig_coin = std::get<2>(utxod->second);
451 result[utxod->first].Clear();
454 result[tx.
vin[0].prevout] = orig_coin;
460 BOOST_CHECK(stack.back()->SpendCoin(utxod->first));
468 disconnected_coins.insert(utxod->first);
471 utxoset.erase(utxod->first);
473 utxoset.insert(tx.
vin[0].prevout);
478 for (
const auto& entry : result) {
479 bool have = stack.back()->HaveCoin(entry.first);
480 const Coin& coin = stack.back()->AccessCoin(entry.first);
487 if (utxoset.size() > 1 && m_rng.
randrange(30) == 0) {
488 stack[m_rng.
rand32() % stack.size()]->Uncache(FindRandomFrom(utxoset)->first);
490 if (disconnected_coins.size() > 1 && m_rng.
randrange(30) == 0) {
491 stack[m_rng.
rand32() % stack.size()]->Uncache(FindRandomFrom(disconnected_coins)->first);
493 if (duplicate_coins.size() > 1 && m_rng.
randrange(30) == 0) {
494 stack[m_rng.
rand32() % stack.size()]->Uncache(FindRandomFrom(duplicate_coins)->first);
499 if (stack.size() > 1 && m_rng.
randbool() == 0) {
500 unsigned int flushIndex = m_rng.
randrange(stack.size() - 1);
501 stack[flushIndex]->Flush();
506 if (stack.size() > 0 && m_rng.
randbool() == 0) {
507 stack.back()->Flush();
510 if (stack.size() == 0 || (stack.size() < 4 && m_rng.
randbool())) {
512 if (stack.size() > 0) {
513 tip = stack.back().get();
515 stack.push_back(std::make_unique<CCoinsViewCacheTest>(tip));
528 SpanReader{
"97f23c835800816115944e077fe7c803cfa57f29b36bf87c1d35"_hex} >> cc1;
536 SpanReader{
"8ddf77bbd123008c988f1a4a4de2161e0f50aac7f17e7f9555caa4"_hex} >> cc2;
554 BOOST_CHECK_MESSAGE(
false,
"We should have thrown");
555 }
catch (
const std::ios_base::failure&) {
560 uint64_t x = 3000000000ULL;
566 BOOST_CHECK_MESSAGE(
false,
"We should have thrown");
567 }
catch (
const std::ios_base::failure&) {
579 enum class State { CLEAN, DIRTY, FRESH, DIRTY_FRESH };
587 friend std::ostream&
operator<<(std::ostream& os,
const CoinEntry& e) {
return os << e.value <<
", " << e.state; }
593 static constexpr State ToState(
const bool is_dirty,
const bool is_fresh) {
620constexpr auto EX_OVERWRITE_UNSPENT{
"Attempted to overwrite an unspent coin (when possible_overwrite is false)"};
628 if (value !=
SPENT) {
639 auto [iter, inserted] = map.emplace(
OUTPOINT, std::move(entry));
643 return iter->second.coin.DynamicMemoryUsage();
648 if (
auto it{map.find(outp)}; it != map.end()) {
650 it->second.coin.IsSpent() ?
SPENT : it->second.coin.out.nValue,
659 sentinel.second.SelfRef(sentinel);
661 CCoinsMap map{0, CCoinsMap::hasher{}, CCoinsMap::key_equal{}, &resource};
663 size_t dirty_count{cache_coin && cache_coin->IsDirty()};
678 cache.dirty() += cache_coin->IsDirty();
691 test.cache.SelfTest(
false);
721 test.cache.SelfTest();
750 bool possible_overwrite{coinbase};
752 if (
auto* expected_coin{std::get_if<MaybeCoin>(&expected)}) {
754 test.cache.SelfTest();
797 if (
auto* expected_coin{std::get_if<MaybeCoin>(&expected)}) {
799 test.cache.SelfTest(
false);
901 CCoinsViewCacheTest* view,
903 std::vector<std::unique_ptr<CCoinsViewCacheTest>>& all_caches,
904 bool do_erasing_flush)
909 auto flush_all = [
this, &all_caches](
bool erase) {
911 for (
auto i = all_caches.rbegin(); i != all_caches.rend(); ++i) {
913 cache->SanityCheck();
917 erase ? cache->Flush() : cache->Sync();
930 view->AddCoin(outp,
Coin(coin),
false);
932 cache_usage = view->DynamicMemoryUsage();
933 cache_size = view->map().size();
957 if (do_erasing_flush) {
963 BOOST_TEST(view->DynamicMemoryUsage() <= cache_usage);
965 BOOST_TEST(view->map().size() < cache_size);
970 view->AccessCoin(outp);
977 view->AddCoin(outp,
Coin(coin),
false),
1008 all_caches[0]->AddCoin(outp, std::move(coin),
false);
1009 all_caches[0]->Sync();
1012 BOOST_CHECK(!all_caches[1]->HaveCoinInCache(outp));
1033 all_caches[0]->AddCoin(outp, std::move(coin),
false);
1041 all_caches[0]->Sync();
1045 BOOST_CHECK(!all_caches[0]->HaveCoinInCache(outp));
1053 CCoinsViewDB base{{.path =
"test", .cache_bytes = 8_MiB, .memory_only =
true}, {}};
1054 std::vector<std::unique_ptr<CCoinsViewCacheTest>> caches;
1055 caches.push_back(std::make_unique<CCoinsViewCacheTest>(&base));
1056 caches.push_back(std::make_unique<CCoinsViewCacheTest>(caches.back().get()));
1058 for (
const auto& view : caches) {
1059 TestFlushBehavior(view.get(), base, caches,
false);
1060 TestFlushBehavior(view.get(), base, caches,
true);
1067 return *
Assert(ToIntegral<int>(*
Assert(base.GetDBProperty(
"leveldb.num-files-at-level2"))));
1073 CCoinsViewDB base{{.path = m_args.GetDataDirBase() /
"coins_db_leveldb_layout", .cache_bytes = 1_MiB, .wipe_data =
true}, {}};
1077 cache.SetBestBlock(block_hash);
1094 CCoinsMap map{0, CCoinsMap::hasher{}, CCoinsMap::key_equal{}, &resource};
1103 for (
size_t i = 0; i < 1000; ++i) {
1120 cache.AddCoin(outpoint,
Coin{coin1},
false);
1137 cache.EmplaceCoinInternalDANGER(
COutPoint{outpoint},
Coin{coin1});
1141 cache.EmplaceCoinInternalDANGER(
COutPoint{outpoint},
Coin{coin2});
1149 CCoinsViewTest root{m_rng};
1152 root_cache.SetBestBlock(base_best_block);
1160 cache.EmplaceCoinInternalDANGER(
COutPoint{outpoint},
Coin{coin});
1164 cache.SetBestBlock(cache_best_block);
1167 const auto reset_guard{cache.CreateResetGuard()};
1169 BOOST_CHECK(!cache.AccessCoin(outpoint).IsSpent());
1173 BOOST_CHECK(!root_cache.HaveCoinInCache(outpoint));
1176 BOOST_CHECK(cache.AccessCoin(outpoint).IsSpent());
1180 BOOST_CHECK(!root_cache.HaveCoinInCache(outpoint));
1184 const auto reset_guard{cache.CreateResetGuard()};
1187 BOOST_CHECK(cache.AccessCoin(outpoint).IsSpent());
1191 BOOST_CHECK(!root_cache.HaveCoinInCache(outpoint));
1200 CCoinsViewTest base{m_rng};
1212 CCoinsViewCacheTest main_cache{&base};
1213 const auto fetched{main_cache.PeekCoin(outpoint)};
1216 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)
#define Assert(val)
Identity function.
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 EmplaceCoinInternalDANGER(COutPoint &&outpoint, Coin &&coin)
Emplace a coin into cacheCoins without performing any checks, marking the emplaced coin as dirty.
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.
bool fCoinBase
whether containing transaction was a coinbase
uint32_t nHeight
at which height this containing transaction was included in the active block chain
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)
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)
RecursiveMutex cs_main
Mutex to guard access to validation specific variables, such as reading or changing the chainstate.
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.
BOOST_CHECK_EQUAL(headers.FindFirst("key"), "value")
BOOST_CHECK_EXCEPTION(HTTPHeaders{}.Read(reader), std::runtime_error, HasReason{"Empty HTTP header name"})
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...
static void add_coin(const CAmount &nValue, uint32_t nInput, std::vector< OutputGroup > &set)
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 ...
bool operator==(const CNetAddr &a, const CNetAddr &b)
#define BOOST_CHECK_THROW(stmt, excMatch)
#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)
#define WITH_LOCK(cs, code)
Run code while locking a mutex.
@ ZEROS
Seed with a compile time constant of zeros.
CAmount RandMoney(Rng &&rng)