26#include <boost/test/unit_test.hpp>
38 FastRandomContext& m_rng;
39 uint256 hashBestBlock_;
40 std::map<COutPoint, Coin> map_;
43 explicit CCoinsViewTest(FastRandomContext&
rng) : m_rng{
rng} {}
45 std::optional<Coin> GetCoin(
const COutPoint& outpoint)
const override
47 if (
auto it{map_.find(outpoint)}; it != map_.end() && !it->second.IsSpent())
return it->second;
51 uint256 GetBestBlock()
const override {
return hashBestBlock_; }
53 void BatchWrite(CoinsViewCacheCursor& cursor,
const uint256& block_hash)
override
56 if (it->second.IsDirty()) {
58 map_[it->first] = it->second.coin;
59 if (it->second.coin.IsSpent() && m_rng.randrange(3) == 0) {
61 map_.erase(it->first);
66 hashBestBlock_ = block_hash;
73 explicit CCoinsViewCacheTest(CCoinsView* _base) : CCoinsViewCache(_base) {}
75 void SelfTest(
bool sanity_check =
true)
const
80 for (
const auto& entry : cacheCoins) {
81 ret += entry.second.coin.DynamicMemoryUsage();
91 CCoinsMap& map()
const {
return cacheCoins; }
93 size_t& usage()
const {
return cachedCoinsUsage; }
94 size_t& dirty()
const {
return m_dirty_count; }
119 bool removed_all_caches =
false;
120 bool reached_4_caches =
false;
121 bool added_an_entry =
false;
122 bool added_an_unspendable_entry =
false;
123 bool removed_an_entry =
false;
124 bool updated_an_entry =
false;
125 bool found_an_entry =
false;
126 bool missed_an_entry =
false;
127 bool uncached_an_entry =
false;
128 bool flushed_without_erase =
false;
131 std::map<COutPoint, Coin> result;
134 std::vector<std::unique_ptr<CCoinsViewCacheTest>> stack;
135 stack.push_back(std::make_unique<CCoinsViewCacheTest>(base));
138 std::vector<Txid> txids;
140 for (
unsigned int i = 0; i < txids.size(); i++) {
156 bool result_havecoin = test_havecoin_before ? stack.back()->HaveCoin(
COutPoint(txid, 0)) :
false;
164 if (test_havecoin_before) {
168 if (test_havecoin_after) {
182 added_an_unspendable_entry =
true;
186 (coin.
IsSpent() ? added_an_entry : updated_an_entry) =
true;
190 stack.back()->EmplaceCoinInternalDANGER(op, std::move(newcoin));
196 removed_an_entry =
true;
206 stack[cacheid]->Uncache(out);
207 uncached_an_entry |= !stack[cacheid]->HaveCoinInCache(out);
212 for (
const auto& entry : result) {
213 bool have = stack.back()->HaveCoin(entry.first);
214 const Coin& coin = stack.back()->AccessCoin(entry.first);
218 missed_an_entry =
true;
220 BOOST_CHECK(stack.back()->HaveCoinInCache(entry.first));
221 found_an_entry =
true;
224 for (
const auto& test : stack) {
233 if (fake_best_block) stack[flushIndex]->SetBestBlock(
m_rng.
rand256());
235 should_erase ? stack[flushIndex]->Flush() : stack[flushIndex]->Sync();
236 flushed_without_erase |= !should_erase;
243 if (fake_best_block) stack.back()->SetBestBlock(
m_rng.
rand256());
245 should_erase ? stack.back()->Flush() : stack.back()->Sync();
246 flushed_without_erase |= !should_erase;
249 if (stack.size() == 0 || (stack.size() < 4 &&
m_rng.
randbool())) {
252 if (stack.size() > 0) {
253 tip = stack.back().get();
255 removed_all_caches =
true;
257 stack.push_back(std::make_unique<CCoinsViewCacheTest>(tip));
258 if (stack.size() == 4) {
259 reached_4_caches =
true;
284 CCoinsViewTest base{m_rng};
285 SimulationTest(&base,
false);
294 CCoinsViewDB db_base{{.path =
"test", .cache_bytes = 8_MiB, .memory_only =
true}, {}};
295 SimulationTest(&db_base,
true);
304typedef std::map<COutPoint, std::tuple<CTransaction,CTxUndo,Coin>>
UtxoData;
310 if (utxoSetIt == utxoSet.end()) {
311 utxoSetIt = utxoSet.begin();
313 auto utxoDataIt =
utxoData.find(*utxoSetIt);
329 bool spent_a_duplicate_coinbase =
false;
331 std::map<COutPoint, Coin> result;
334 CCoinsViewTest base{m_rng};
335 std::vector<std::unique_ptr<CCoinsViewCacheTest>> stack;
336 stack.push_back(std::make_unique<CCoinsViewCacheTest>(&base));
339 std::set<COutPoint> coinbase_coins;
340 std::set<COutPoint> disconnected_coins;
341 std::set<COutPoint> duplicate_coins;
342 std::set<COutPoint> utxoset;
345 uint32_t randiter = m_rng.
rand32();
348 if (randiter % 20 < 19) {
352 tx.
vout[0].nValue = i;
353 tx.
vout[0].scriptPubKey.assign(m_rng.
rand32() & 0x3F, 0);
354 const int height{int(m_rng.
rand32() >> 1)};
358 if (randiter % 20 < 2 || coinbase_coins.size() < 10) {
360 if (m_rng.
randrange(10) == 0 && coinbase_coins.size()) {
361 auto utxod = FindRandomFrom(coinbase_coins);
365 disconnected_coins.erase(utxod->first);
367 duplicate_coins.insert(utxod->first);
380 if (randiter % 20 == 2 && disconnected_coins.size()) {
381 auto utxod = FindRandomFrom(disconnected_coins);
383 prevout = tx.
vin[0].prevout;
384 if (!
CTransaction(tx).IsCoinBase() && !utxoset.contains(prevout)) {
385 disconnected_coins.erase(utxod->first);
390 if (utxoset.contains(utxod->first)) {
392 assert(duplicate_coins.contains(utxod->first));
394 disconnected_coins.erase(utxod->first);
399 auto utxod = FindRandomFrom(utxoset);
400 prevout = utxod->first;
403 tx.
vin[0].prevout = prevout;
407 old_coin = result[prevout];
409 result[prevout].
Clear();
411 utxoset.erase(prevout);
415 if (duplicate_coins.contains(prevout)) {
416 spent_a_duplicate_coinbase =
true;
430 utxoset.insert(outpoint);
433 utxoData.emplace(outpoint, std::make_tuple(tx,undo,old_coin));
434 }
else if (utxoset.size()) {
436 auto utxod = FindRandomFrom(utxoset);
439 CTxUndo &undo = std::get<1>(utxod->second);
440 Coin &orig_coin = std::get<2>(utxod->second);
444 result[utxod->first].Clear();
447 result[tx.
vin[0].prevout] = orig_coin;
453 BOOST_CHECK(stack.back()->SpendCoin(utxod->first));
461 disconnected_coins.insert(utxod->first);
464 utxoset.erase(utxod->first);
466 utxoset.insert(tx.
vin[0].prevout);
471 for (
const auto& entry : result) {
472 bool have = stack.back()->HaveCoin(entry.first);
473 const Coin& coin = stack.back()->AccessCoin(entry.first);
480 if (utxoset.size() > 1 && m_rng.
randrange(30) == 0) {
481 stack[m_rng.
rand32() % stack.size()]->Uncache(FindRandomFrom(utxoset)->first);
483 if (disconnected_coins.size() > 1 && m_rng.
randrange(30) == 0) {
484 stack[m_rng.
rand32() % stack.size()]->Uncache(FindRandomFrom(disconnected_coins)->first);
486 if (duplicate_coins.size() > 1 && m_rng.
randrange(30) == 0) {
487 stack[m_rng.
rand32() % stack.size()]->Uncache(FindRandomFrom(duplicate_coins)->first);
492 if (stack.size() > 1 && m_rng.
randbool() == 0) {
493 unsigned int flushIndex = m_rng.
randrange(stack.size() - 1);
494 stack[flushIndex]->Flush();
499 if (stack.size() > 0 && m_rng.
randbool() == 0) {
500 stack.back()->Flush();
503 if (stack.size() == 0 || (stack.size() < 4 && m_rng.
randbool())) {
505 if (stack.size() > 0) {
506 tip = stack.back().get();
508 stack.push_back(std::make_unique<CCoinsViewCacheTest>(tip));
521 SpanReader{
"97f23c835800816115944e077fe7c803cfa57f29b36bf87c1d35"_hex} >> cc1;
529 SpanReader{
"8ddf77bbd123008c988f1a4a4de2161e0f50aac7f17e7f9555caa4"_hex} >> cc2;
548 uint64_t x = 3000000000ULL;
562 enum class State { CLEAN, DIRTY, FRESH, DIRTY_FRESH };
576 static constexpr State ToState(
const bool is_dirty,
const bool is_fresh) {
577 if (is_dirty && is_fresh)
return State::DIRTY_FRESH;
578 if (is_dirty)
return State::DIRTY;
579 if (is_fresh)
return State::FRESH;
603constexpr auto EX_OVERWRITE_UNSPENT{
"Attempted to overwrite an unspent coin (when possible_overwrite is false)"};
611 if (value !=
SPENT) {
622 auto [iter, inserted] = map.emplace(
OUTPOINT, std::move(entry));
626 return iter->second.coin.DynamicMemoryUsage();
631 if (
auto it{map.find(outp)}; it != map.end()) {
633 it->second.coin.IsSpent() ?
SPENT : it->second.coin.out.nValue,
642 sentinel.second.SelfRef(sentinel);
644 CCoinsMap map{0, CCoinsMap::hasher{}, CCoinsMap::key_equal{}, &resource};
646 size_t dirty_count{cache_coin && cache_coin->IsDirty()};
661 cache.dirty() += cache_coin->IsDirty();
674 test.
cache.SelfTest(
false);
704 test.
cache.SelfTest();
733 bool possible_overwrite{coinbase};
735 if (
auto* expected_coin{std::get_if<MaybeCoin>(&expected)}) {
737 test.
cache.SelfTest();
780 if (
auto* expected_coin{std::get_if<MaybeCoin>(&expected)}) {
782 test.
cache.SelfTest(
false);
884 CCoinsViewCacheTest* view,
886 std::vector<std::unique_ptr<CCoinsViewCacheTest>>& all_caches,
887 bool do_erasing_flush)
892 auto flush_all = [
this, &all_caches](
bool erase) {
894 for (
auto i = all_caches.rbegin(); i != all_caches.rend(); ++i) {
896 cache->SanityCheck();
900 erase ? cache->Flush() : cache->Sync();
913 view->AddCoin(outp,
Coin(coin),
false);
915 cache_usage = view->DynamicMemoryUsage();
916 cache_size = view->map().size();
940 if (do_erasing_flush) {
946 BOOST_TEST(view->DynamicMemoryUsage() <= cache_usage);
948 BOOST_TEST(view->map().size() < cache_size);
953 view->AccessCoin(outp);
960 view->AddCoin(outp,
Coin(coin),
false),
991 all_caches[0]->AddCoin(outp, std::move(coin),
false);
992 all_caches[0]->Sync();
995 BOOST_CHECK(!all_caches[1]->HaveCoinInCache(outp));
1016 all_caches[0]->AddCoin(outp, std::move(coin),
false);
1024 all_caches[0]->Sync();
1028 BOOST_CHECK(!all_caches[0]->HaveCoinInCache(outp));
1036 CCoinsViewDB base{{.path =
"test", .cache_bytes = 8_MiB, .memory_only =
true}, {}};
1037 std::vector<std::unique_ptr<CCoinsViewCacheTest>>
caches;
1038 caches.push_back(std::make_unique<CCoinsViewCacheTest>(&base));
1039 caches.push_back(std::make_unique<CCoinsViewCacheTest>(
caches.back().get()));
1041 for (
const auto& view :
caches) {
1042 TestFlushBehavior(view.get(), base,
caches,
false);
1043 TestFlushBehavior(view.get(), base,
caches,
true);
1053 const Coin coin{MakeCoin()};
1056 CCoinsViewDB base{{.path = m_args.GetDataDirBase() /
"coins_db_leveldb_layout", .cache_bytes = 1_MiB, .wipe_data =
true}, {}};
1077 CCoinsMap map{0, CCoinsMap::hasher{}, CCoinsMap::key_equal{}, &resource};
1078 BOOST_TEST(memusage::DynamicUsage(map) >= resource.ChunkSizeBytes());
1083 const auto usage_before = memusage::DynamicUsage(map);
1086 for (
size_t i = 0; i < 1000; ++i) {
1090 BOOST_TEST(usage_before == memusage::DynamicUsage(map));
1103 cache.AddCoin(outpoint,
Coin{coin1},
false);
1120 cache.EmplaceCoinInternalDANGER(outpoint,
Coin{coin1});
1124 cache.EmplaceCoinInternalDANGER(outpoint,
Coin{coin2});
1132 CCoinsViewTest root{m_rng};
1183 CCoinsViewTest base{m_rng};
1195 CCoinsViewCacheTest main_cache{&base};
1196 const auto fetched{main_cache.PeekCoin(outpoint)};
1199 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).
BOOST_FIXTURE_TEST_CASE(util_CheckValue, CheckValueTest)
#define Assert(val)
Identity function.
CCoinsView that adds a memory cache for transactions to another CCoinsView.
void Sync()
Push the modifications applied to this cache to its base while retaining the contents of this cache (...
size_t GetDirtyCount() const noexcept
Number of dirty cache entries (transaction outputs).
ResetGuard CreateResetGuard() noexcept
Create a scoped guard that will call Reset() on this cache when it goes out of scope.
void EmplaceCoinInternalDANGER(const COutPoint &outpoint, Coin &&coin)
Emplace a coin into cacheCoins without performing any checks, marking the emplaced coin as dirty.
void AddCoin(const COutPoint &outpoint, Coin &&coin, bool possible_overwrite)
Add a coin.
virtual void Flush(bool reallocate_cache=true)
Push the modifications applied to this cache to its base and wipe local state.
void SetBestBlock(const uint256 &block_hash)
unsigned int GetCacheSize() const
Size of the cache (in number of transaction outputs).
uint256 GetBestBlock() const override
Retrieve the block hash whose state this CCoinsView currently represents.
bool HaveCoinInCache(const COutPoint &outpoint) const
Check if we have the given utxo already loaded in this cache.
const Coin & AccessCoin(const COutPoint &output) const
Return a reference to Coin in the cache, or coinEmpty if not found.
CCoinsView backed by the coin database (chainstate/).
std::shared_future< void > CompactFullAsync() EXCLUSIVE_LOCKS_REQUIRED(cs_main
Perform a full compaction of the underlying LevelDB on a one-shot background thread.
bool HaveCoin(const COutPoint &outpoint) const override
uint256 GetBestBlock() const override
Retrieve the block hash whose state this CCoinsView currently represents.
std::optional< Coin > GetCoin(const COutPoint &outpoint) const override
Pure abstract view on the open txout dataset.
virtual void BatchWrite(CoinsViewCacheCursor &cursor, const uint256 &block_hash)=0
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.
CTransaction(const CMutableTransaction &tx)
Convert a CMutableTransaction into a CTransaction.
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
static CoinsViewEmpty & Get()
Double ended buffer combining vector and stream-like interfaces.
BOOST_CHECK_EXCEPTION predicates to check the specific validation error.
HasReason(std::string_view reason)
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)
constexpr uint160()=default
const Coin & AccessByTxid(const CCoinsViewCache &view, const Txid &txid)
std::unordered_map< COutPoint, CCoinsCacheEntry, SaltedCoinsCacheHasher, 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::pair< const COutPoint, CCoinsCacheEntry > CoinsCachePair
CCoinsMap::allocator_type::ResourceType CCoinsMapMemoryResource
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)
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)
std::vector< std::unique_ptr< CCoinsViewCache > > caches
Real CCoinsViewCache objects.
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_EXCEPTION(req.LoadBody(reader), std::runtime_error, HasReason{"Cannot parse Content-Length value"})
BOOST_AUTO_TEST_CASE(ipc_tests)
void SanityCheck(const DepGraph< SetType > &depgraph)
Perform a sanity/consistency check on a DepGraph.
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...
#define BOOST_CHECK_THROW(stmt, excMatch)
#define BOOST_CHECK_EQUAL(v1, v2)
#define BOOST_CHECK(expr)
std::optional< T > ToIntegral(std::string_view str, size_t base=10)
Convert string to integral type T.
BasicTestingSetup(ChainType chainType=ChainType::MAIN, TestOpts={})
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)
CoinsCachePair * NextAndMaybeErase(CoinsCachePair ¤t) noexcept
Return the next entry after current, possibly erasing current.
CoinsCachePair * Begin() const noexcept
CoinsViewCacheCursor(size_t &dirty_count LIFETIMEBOUND, CoinsCachePair &sentinel LIFETIMEBOUND, CCoinsMap &map LIFETIMEBOUND, bool will_erase) noexcept
CoinsCachePair * End() const noexcept
void TestFlushBehavior(CCoinsViewCacheTest *view, CCoinsViewDB &base, std::vector< std::unique_ptr< CCoinsViewCacheTest > > &all_caches, bool do_erasing_flush)
std::map< COutPoint, std::tuple< CTransaction, CTxUndo, Coin > > UtxoData
UtxoData::iterator FindRandomFrom(const std::set< COutPoint > &utxoSet)
#define WITH_LOCK(cs, code)
@ ZEROS
Seed with a compile time constant of zeros.
CAmount RandMoney(Rng &&rng)
transaction_identifier< false > Txid
Txid commits to all transaction fields except the witness.