24#include <boost/test/unit_test.hpp>
44 FastRandomContext& m_rng;
45 uint256 hashBestBlock_;
46 std::map<COutPoint, Coin> map_;
49 CCoinsViewTest(FastRandomContext& rng) : m_rng{rng} {}
51 std::optional<Coin> GetCoin(
const COutPoint& outpoint)
const override
53 if (
auto it{map_.find(outpoint)}; it != map_.end() && !it->second.IsSpent())
return it->second;
57 uint256 GetBestBlock()
const override {
return hashBestBlock_; }
59 void BatchWrite(CoinsViewCacheCursor& cursor,
const uint256& hashBlock)
override
62 if (it->second.IsDirty()) {
64 map_[it->first] = it->second.coin;
65 if (it->second.coin.IsSpent() && m_rng.randrange(3) == 0) {
67 map_.erase(it->first);
72 hashBestBlock_ = hashBlock;
79 explicit CCoinsViewCacheTest(CCoinsView* _base) : CCoinsViewCache(_base) {}
86 for (
const auto& entry : cacheCoins) {
87 ret += entry.second.coin.DynamicMemoryUsage();
97 CCoinsMap& map()
const {
return cacheCoins; }
99 size_t& usage()
const {
return cachedCoinsUsage; }
100 size_t& dirty()
const {
return m_dirty_count; }
125 bool removed_all_caches =
false;
126 bool reached_4_caches =
false;
127 bool added_an_entry =
false;
128 bool added_an_unspendable_entry =
false;
129 bool removed_an_entry =
false;
130 bool updated_an_entry =
false;
131 bool found_an_entry =
false;
132 bool missed_an_entry =
false;
133 bool uncached_an_entry =
false;
134 bool flushed_without_erase =
false;
137 std::map<COutPoint, Coin> result;
140 std::vector<std::unique_ptr<CCoinsViewCacheTest>> stack;
141 stack.push_back(std::make_unique<CCoinsViewCacheTest>(base));
144 std::vector<Txid> txids;
146 for (
unsigned int i = 0; i < txids.size(); i++) {
153 auto txid = txids[
m_rng.randrange(txids.size())];
159 bool test_havecoin_before =
m_rng.randbits(2) == 0;
160 bool test_havecoin_after =
m_rng.randbits(2) == 0;
162 bool result_havecoin = test_havecoin_before ? stack.back()->HaveCoin(
COutPoint(txid, 0)) :
false;
166 const Coin& entry = (
m_rng.randrange(500) == 0) ?
170 if (test_havecoin_before) {
174 if (test_havecoin_after) {
188 added_an_unspendable_entry =
true;
192 (coin.
IsSpent() ? added_an_entry : updated_an_entry) =
true;
196 stack.back()->EmplaceCoinInternalDANGER(std::move(op), std::move(newcoin));
198 stack.back()->AddCoin(op, std::move(newcoin), !coin.
IsSpent() ||
m_rng.randbool());
202 removed_an_entry =
true;
209 if (
m_rng.randrange(10) == 0) {
211 int cacheid =
m_rng.rand32() % stack.size();
212 stack[cacheid]->Uncache(out);
213 uncached_an_entry |= !stack[cacheid]->HaveCoinInCache(out);
218 for (
const auto& entry : result) {
219 bool have = stack.back()->HaveCoin(entry.first);
220 const Coin& coin = stack.back()->AccessCoin(entry.first);
224 missed_an_entry =
true;
226 BOOST_CHECK(stack.back()->HaveCoinInCache(entry.first));
227 found_an_entry =
true;
230 for (
const auto& test : stack) {
235 if (
m_rng.randrange(100) == 0) {
237 if (stack.size() > 1 &&
m_rng.randbool() == 0) {
238 unsigned int flushIndex =
m_rng.randrange(stack.size() - 1);
239 if (fake_best_block) stack[flushIndex]->SetBestBlock(
m_rng.rand256());
240 bool should_erase =
m_rng.randrange(4) < 3;
241 should_erase ? stack[flushIndex]->Flush() : stack[flushIndex]->Sync();
242 flushed_without_erase |= !should_erase;
245 if (
m_rng.randrange(100) == 0) {
247 if (stack.size() > 0 &&
m_rng.randbool() == 0) {
249 if (fake_best_block) stack.back()->SetBestBlock(
m_rng.rand256());
250 bool should_erase =
m_rng.randrange(4) < 3;
251 should_erase ? stack.back()->Flush() : stack.back()->Sync();
252 flushed_without_erase |= !should_erase;
255 if (stack.size() == 0 || (stack.size() < 4 &&
m_rng.randbool())) {
258 if (stack.size() > 0) {
259 tip = stack.back().get();
261 removed_all_caches =
true;
263 stack.push_back(std::make_unique<CCoinsViewCacheTest>(tip));
264 if (stack.size() == 4) {
265 reached_4_caches =
true;
290 CCoinsViewTest base{m_rng};
291 SimulationTest(&base,
false);
300 CCoinsViewDB db_base{{.path =
"test", .cache_bytes = 1 << 23, .memory_only =
true}, {}};
301 SimulationTest(&db_base,
true);
310typedef std::map<COutPoint, std::tuple<CTransaction,CTxUndo,Coin>>
UtxoData;
316 if (utxoSetIt == utxoSet.end()) {
317 utxoSetIt = utxoSet.begin();
319 auto utxoDataIt =
utxoData.find(*utxoSetIt);
335 bool spent_a_duplicate_coinbase =
false;
337 std::map<COutPoint, Coin> result;
340 CCoinsViewTest base{m_rng};
341 std::vector<std::unique_ptr<CCoinsViewCacheTest>> stack;
342 stack.push_back(std::make_unique<CCoinsViewCacheTest>(&base));
345 std::set<COutPoint> coinbase_coins;
346 std::set<COutPoint> disconnected_coins;
347 std::set<COutPoint> duplicate_coins;
348 std::set<COutPoint> utxoset;
351 uint32_t randiter = m_rng.
rand32();
354 if (randiter % 20 < 19) {
358 tx.
vout[0].nValue = i;
359 tx.
vout[0].scriptPubKey.assign(m_rng.
rand32() & 0x3F, 0);
360 const int height{int(m_rng.
rand32() >> 1)};
364 if (randiter % 20 < 2 || coinbase_coins.size() < 10) {
366 if (m_rng.
randrange(10) == 0 && coinbase_coins.size()) {
367 auto utxod = FindRandomFrom(coinbase_coins);
371 disconnected_coins.erase(utxod->first);
373 duplicate_coins.insert(utxod->first);
386 if (randiter % 20 == 2 && disconnected_coins.size()) {
387 auto utxod = FindRandomFrom(disconnected_coins);
389 prevout = tx.
vin[0].prevout;
390 if (!
CTransaction(tx).IsCoinBase() && !utxoset.contains(prevout)) {
391 disconnected_coins.erase(utxod->first);
396 if (utxoset.contains(utxod->first)) {
398 assert(duplicate_coins.contains(utxod->first));
400 disconnected_coins.erase(utxod->first);
405 auto utxod = FindRandomFrom(utxoset);
406 prevout = utxod->first;
409 tx.
vin[0].prevout = prevout;
413 old_coin = result[prevout];
415 result[prevout].
Clear();
417 utxoset.erase(prevout);
421 if (duplicate_coins.contains(prevout)) {
422 spent_a_duplicate_coinbase =
true;
436 utxoset.insert(outpoint);
439 utxoData.emplace(outpoint, std::make_tuple(tx,undo,old_coin));
440 }
else if (utxoset.size()) {
442 auto utxod = FindRandomFrom(utxoset);
445 CTxUndo &undo = std::get<1>(utxod->second);
446 Coin &orig_coin = std::get<2>(utxod->second);
450 result[utxod->first].Clear();
453 result[tx.
vin[0].prevout] = orig_coin;
459 BOOST_CHECK(stack.back()->SpendCoin(utxod->first));
467 disconnected_coins.insert(utxod->first);
470 utxoset.erase(utxod->first);
472 utxoset.insert(tx.
vin[0].prevout);
477 for (
const auto& entry : result) {
478 bool have = stack.back()->HaveCoin(entry.first);
479 const Coin& coin = stack.back()->AccessCoin(entry.first);
486 if (utxoset.size() > 1 && m_rng.
randrange(30) == 0) {
487 stack[m_rng.
rand32() % stack.size()]->Uncache(FindRandomFrom(utxoset)->first);
489 if (disconnected_coins.size() > 1 && m_rng.
randrange(30) == 0) {
490 stack[m_rng.
rand32() % stack.size()]->Uncache(FindRandomFrom(disconnected_coins)->first);
492 if (duplicate_coins.size() > 1 && m_rng.
randrange(30) == 0) {
493 stack[m_rng.
rand32() % stack.size()]->Uncache(FindRandomFrom(duplicate_coins)->first);
498 if (stack.size() > 1 && m_rng.
randbool() == 0) {
499 unsigned int flushIndex = m_rng.
randrange(stack.size() - 1);
500 stack[flushIndex]->Flush();
505 if (stack.size() > 0 && m_rng.
randbool() == 0) {
506 stack.back()->Flush();
509 if (stack.size() == 0 || (stack.size() < 4 && m_rng.
randbool())) {
511 if (stack.size() > 0) {
512 tip = stack.back().get();
514 stack.push_back(std::make_unique<CCoinsViewCacheTest>(tip));
527 SpanReader{
"97f23c835800816115944e077fe7c803cfa57f29b36bf87c1d35"_hex} >> cc1;
535 SpanReader{
"8ddf77bbd123008c988f1a4a4de2161e0f50aac7f17e7f9555caa4"_hex} >> cc2;
553 BOOST_CHECK_MESSAGE(
false,
"We should have thrown");
554 }
catch (
const std::ios_base::failure&) {
559 uint64_t x = 3000000000ULL;
565 BOOST_CHECK_MESSAGE(
false,
"We should have thrown");
566 }
catch (
const std::ios_base::failure&) {
592 static constexpr State ToState(
const bool is_dirty,
const bool is_fresh) {
593 if (is_dirty && is_fresh)
return State::DIRTY_FRESH;
594 if (is_dirty)
return State::DIRTY;
595 if (is_fresh)
return State::FRESH;
619constexpr auto EX_OVERWRITE_UNSPENT{
"Attempted to overwrite an unspent coin (when possible_overwrite is false)"};
627 if (value !=
SPENT) {
638 auto [iter, inserted] = map.emplace(
OUTPOINT, std::move(entry));
642 return iter->second.coin.DynamicMemoryUsage();
647 if (
auto it{map.find(outp)}; it != map.end()) {
649 it->second.coin.IsSpent() ?
SPENT : it->second.coin.out.nValue,
658 sentinel.second.SelfRef(sentinel);
660 CCoinsMap map{0, CCoinsMap::hasher{}, CCoinsMap::key_equal{}, &resource};
662 size_t dirty_count{cache_coin && cache_coin->IsDirty()};
677 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();
757 BOOST_CHECK_EXCEPTION(
add_coin(), std::logic_error,
HasReason(std::get<std::string>(expected)));
797 if (
auto* expected_coin{std::get_if<MaybeCoin>(&expected)}) {
799 test.
cache.SelfTest(
false);
802 BOOST_CHECK_EXCEPTION(write_coins(), std::logic_error,
HasReason(std::get<std::string>(expected)));
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();
916 cache->SetBestBlock(
m_rng.rand256());
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 = 1 << 23, .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);
1070 const Coin coin{MakeCoin()};
1073 CCoinsViewDB base{{.path = m_args.GetDataDirBase() /
"coins_db_leveldb_layout", .cache_bytes = 1_MiB, .wipe_data =
true}, {}};
1094 CCoinsMap map{0, CCoinsMap::hasher{}, CCoinsMap::key_equal{}, &resource};
1103 for (
size_t i = 0; i < 1000; ++i) {
1116 CCoinsViewCacheTest cache{&root};
1121 cache.AddCoin(outpoint,
Coin{coin1},
false);
1134 CCoinsViewCacheTest cache{&root};
1139 cache.EmplaceCoinInternalDANGER(
COutPoint{outpoint},
Coin{coin1});
1143 cache.EmplaceCoinInternalDANGER(
COutPoint{outpoint},
Coin{coin2});
1151 CCoinsViewTest root{m_rng};
1202 CCoinsViewTest base{m_rng};
1214 CCoinsViewCacheTest main_cache{&base};
1215 const auto fetched{main_cache.PeekCoin(outpoint)};
1218 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 AddCoin(const COutPoint &outpoint, Coin &&coin, bool possible_overwrite)
Add a coin.
void Flush(bool reallocate_cache=true)
Push the modifications applied to this cache to its base and wipe local state.
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.
void SetBestBlock(const uint256 &hashBlock)
bool HaveCoinInCache(const COutPoint &outpoint) const
Check if we have the given utxo already loaded in this cache.
void EmplaceCoinInternalDANGER(COutPoint &&outpoint, Coin &&coin)
Emplace a coin into cacheCoins without performing any checks, marking the emplaced coin as dirty.
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/).
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
std::shared_future< void > CompactFull() EXCLUSIVE_LOCKS_REQUIRED(cs_main
Perform a full compaction of the underlying LevelDB on a one-shot background thread.
Abstract view on the open txout dataset.
virtual void BatchWrite(CoinsViewCacheCursor &cursor, const uint256 &hashBlock)
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
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.
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)
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::pair< const COutPoint, CCoinsCacheEntry > CoinsCachePair
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.
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)
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)
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)
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.