22#include <boost/test/unit_test.hpp>
44 std::map<COutPoint, Coin> map_;
51 if (
auto it{map_.find(outpoint)}; it != map_.end()) {
52 if (!it->second.IsSpent() || m_rng.
randbool()) {
64 if (it->second.IsDirty()) {
66 map_[it->first] = it->second.coin;
67 if (it->second.coin.IsSpent() && m_rng.
randrange(3) == 0) {
69 map_.erase(it->first);
74 hashBestBlock_ = hashBlock;
89 for (
const auto& entry : cacheCoins) {
90 ret += entry.second.coin.DynamicMemoryUsage();
129 bool removed_all_caches =
false;
130 bool reached_4_caches =
false;
131 bool added_an_entry =
false;
132 bool added_an_unspendable_entry =
false;
133 bool removed_an_entry =
false;
134 bool updated_an_entry =
false;
135 bool found_an_entry =
false;
136 bool missed_an_entry =
false;
137 bool uncached_an_entry =
false;
138 bool flushed_without_erase =
false;
141 std::map<COutPoint, Coin> result;
144 std::vector<std::unique_ptr<CCoinsViewCacheTest>> stack;
145 stack.push_back(std::make_unique<CCoinsViewCacheTest>(base));
148 std::vector<Txid> txids;
150 for (
unsigned int i = 0; i < txids.size(); i++) {
157 auto txid = txids[m_rng.
randrange(txids.size())];
163 bool test_havecoin_before = m_rng.
randbits(2) == 0;
164 bool test_havecoin_after = m_rng.
randbits(2) == 0;
166 bool result_havecoin = test_havecoin_before ? stack.back()->HaveCoin(
COutPoint(txid, 0)) :
false;
174 if (test_havecoin_before) {
178 if (test_havecoin_after) {
192 added_an_unspendable_entry =
true;
196 (coin.
IsSpent() ? added_an_entry : updated_an_entry) =
true;
200 stack.back()->AddCoin(
COutPoint(txid, 0), std::move(newcoin), is_overwrite);
203 removed_an_entry =
true;
212 int cacheid = m_rng.
rand32() % stack.size();
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) {
238 if (stack.size() > 1 && m_rng.
randbool() == 0) {
239 unsigned int flushIndex = m_rng.
randrange(stack.size() - 1);
240 if (fake_best_block) stack[flushIndex]->SetBestBlock(m_rng.
rand256());
241 bool should_erase = m_rng.
randrange(4) < 3;
242 BOOST_CHECK(should_erase ? stack[flushIndex]->Flush() : stack[flushIndex]->Sync());
243 flushed_without_erase |= !should_erase;
248 if (stack.size() > 0 && m_rng.
randbool() == 0) {
250 if (fake_best_block) stack.back()->SetBestBlock(m_rng.
rand256());
251 bool should_erase = m_rng.
randrange(4) < 3;
252 BOOST_CHECK(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;
289 CCoinsViewTest base{m_rng};
290 SimulationTest(&base,
false);
292 CCoinsViewDB db_base{{.path =
"test", .cache_bytes = 1 << 23, .memory_only =
true}, {}};
293 SimulationTest(&db_base,
true);
298typedef std::map<COutPoint, std::tuple<CTransaction,CTxUndo,Coin>>
UtxoData;
304 if (utxoSetIt == utxoSet.end()) {
305 utxoSetIt = utxoSet.begin();
307 auto utxoDataIt =
utxoData.find(*utxoSetIt);
323 bool spent_a_duplicate_coinbase =
false;
325 std::map<COutPoint, Coin> result;
328 CCoinsViewTest base{m_rng};
329 std::vector<std::unique_ptr<CCoinsViewCacheTest>> stack;
330 stack.push_back(std::make_unique<CCoinsViewCacheTest>(&base));
333 std::set<COutPoint> coinbase_coins;
334 std::set<COutPoint> disconnected_coins;
335 std::set<COutPoint> duplicate_coins;
336 std::set<COutPoint> utxoset;
339 uint32_t randiter = m_rng.
rand32();
342 if (randiter % 20 < 19) {
346 tx.
vout[0].nValue = i;
347 tx.
vout[0].scriptPubKey.assign(m_rng.
rand32() & 0x3F, 0);
348 const int height{int(m_rng.
rand32() >> 1)};
352 if (randiter % 20 < 2 || coinbase_coins.size() < 10) {
354 if (m_rng.
randrange(10) == 0 && coinbase_coins.size()) {
355 auto utxod = FindRandomFrom(coinbase_coins);
359 disconnected_coins.erase(utxod->first);
361 duplicate_coins.insert(utxod->first);
374 if (randiter % 20 == 2 && disconnected_coins.size()) {
375 auto utxod = FindRandomFrom(disconnected_coins);
377 prevout = tx.
vin[0].prevout;
378 if (!
CTransaction(tx).IsCoinBase() && !utxoset.count(prevout)) {
379 disconnected_coins.erase(utxod->first);
384 if (utxoset.count(utxod->first)) {
386 assert(duplicate_coins.count(utxod->first));
388 disconnected_coins.erase(utxod->first);
393 auto utxod = FindRandomFrom(utxoset);
394 prevout = utxod->first;
397 tx.
vin[0].prevout = prevout;
401 old_coin = result[prevout];
403 result[prevout].
Clear();
405 utxoset.erase(prevout);
409 if (duplicate_coins.count(prevout)) {
410 spent_a_duplicate_coinbase =
true;
424 utxoset.insert(outpoint);
427 utxoData.emplace(outpoint, std::make_tuple(tx,undo,old_coin));
428 }
else if (utxoset.size()) {
430 auto utxod = FindRandomFrom(utxoset);
433 CTxUndo &undo = std::get<1>(utxod->second);
434 Coin &orig_coin = std::get<2>(utxod->second);
438 result[utxod->first].Clear();
441 result[tx.
vin[0].prevout] = orig_coin;
447 BOOST_CHECK(stack.back()->SpendCoin(utxod->first));
455 disconnected_coins.insert(utxod->first);
458 utxoset.erase(utxod->first);
460 utxoset.insert(tx.
vin[0].prevout);
465 for (
const auto& entry : result) {
466 bool have = stack.back()->HaveCoin(entry.first);
467 const Coin& coin = stack.back()->AccessCoin(entry.first);
474 if (utxoset.size() > 1 && m_rng.
randrange(30) == 0) {
475 stack[m_rng.
rand32() % stack.size()]->Uncache(FindRandomFrom(utxoset)->first);
477 if (disconnected_coins.size() > 1 && m_rng.
randrange(30) == 0) {
478 stack[m_rng.
rand32() % stack.size()]->Uncache(FindRandomFrom(disconnected_coins)->first);
480 if (duplicate_coins.size() > 1 && m_rng.
randrange(30) == 0) {
481 stack[m_rng.
rand32() % stack.size()]->Uncache(FindRandomFrom(duplicate_coins)->first);
486 if (stack.size() > 1 && m_rng.
randbool() == 0) {
487 unsigned int flushIndex = m_rng.
randrange(stack.size() - 1);
493 if (stack.size() > 0 && m_rng.
randbool() == 0) {
497 if (stack.size() == 0 || (stack.size() < 4 && m_rng.
randbool())) {
499 if (stack.size() > 0) {
500 tip = stack.back().get();
502 stack.push_back(std::make_unique<CCoinsViewCacheTest>(tip));
514 DataStream ss1{
"97f23c835800816115944e077fe7c803cfa57f29b36bf87c1d35"_hex};
523 DataStream ss2{
"8ddf77bbd123008c988f1a4a4de2161e0f50aac7f17e7f9555caa4"_hex};
545 BOOST_CHECK_MESSAGE(
false,
"We should have thrown");
546 }
catch (
const std::ios_base::failure&) {
551 uint64_t x = 3000000000ULL;
558 BOOST_CHECK_MESSAGE(
false,
"We should have thrown");
559 }
catch (
const std::ios_base::failure&) {
571 enum class State { CLEAN, DIRTY, FRESH, DIRTY_FRESH };
579 friend std::ostream&
operator<<(std::ostream& os,
const CoinEntry& e) {
return os << e.value <<
", " << e.state; }
585 static constexpr State ToState(
const bool is_dirty,
const bool is_fresh) {
612constexpr auto EX_OVERWRITE_UNSPENT{
"Attempted to overwrite an unspent coin (when possible_overwrite is false)"};
620 if (value !=
SPENT) {
631 auto [iter, inserted] = map.emplace(
OUTPOINT, std::move(entry));
635 return iter->second.coin.DynamicMemoryUsage();
640 if (
auto it{map.find(outp)}; it != map.end()) {
642 it->second.coin.IsSpent() ?
SPENT : it->second.coin.out.nValue,
651 sentinel.second.SelfRef(sentinel);
653 CCoinsMap map{0, CCoinsMap::hasher{}, CCoinsMap::key_equal{}, &resource};
679 test.cache.SelfTest(
false);
709 test.cache.SelfTest();
738 bool possible_overwrite{coinbase};
740 if (
auto* expected_coin{std::get_if<MaybeCoin>(&expected)}) {
742 test.cache.SelfTest();
745 BOOST_CHECK_EXCEPTION(
add_coin(), std::logic_error,
HasReason(std::get<std::string>(expected)));
785 if (
auto* expected_coin{std::get_if<MaybeCoin>(&expected)}) {
787 test.cache.SelfTest(
false);
790 BOOST_CHECK_EXCEPTION(write_coins(), std::logic_error,
HasReason(std::get<std::string>(expected)));
889 CCoinsViewCacheTest* view,
891 std::vector<std::unique_ptr<CCoinsViewCacheTest>>& all_caches,
892 bool do_erasing_flush)
897 auto flush_all = [
this, &all_caches](
bool erase) {
899 for (
auto i = all_caches.rbegin(); i != all_caches.rend(); ++i) {
901 cache->SanityCheck();
905 erase ? cache->Flush() : cache->Sync();
918 view->AddCoin(outp,
Coin(coin),
false);
920 cache_usage = view->DynamicMemoryUsage();
921 cache_size = view->map().size();
945 if (do_erasing_flush) {
951 BOOST_TEST(view->DynamicMemoryUsage() <= cache_usage);
953 BOOST_TEST(view->map().size() < cache_size);
958 view->AccessCoin(outp);
965 view->AddCoin(outp,
Coin(coin),
false),
996 all_caches[0]->AddCoin(outp, std::move(coin),
false);
997 all_caches[0]->Sync();
1000 BOOST_CHECK(!all_caches[1]->HaveCoinInCache(outp));
1021 all_caches[0]->AddCoin(outp, std::move(coin),
false);
1029 all_caches[0]->Sync();
1033 BOOST_CHECK(!all_caches[0]->HaveCoinInCache(outp));
1041 CCoinsViewDB base{{.path =
"test", .cache_bytes = 1 << 23, .memory_only =
true}, {}};
1042 std::vector<std::unique_ptr<CCoinsViewCacheTest>> caches;
1043 caches.push_back(std::make_unique<CCoinsViewCacheTest>(&base));
1044 caches.push_back(std::make_unique<CCoinsViewCacheTest>(caches.back().get()));
1046 for (
const auto& view : caches) {
1047 TestFlushBehavior(view.get(), base, caches,
false);
1048 TestFlushBehavior(view.get(), base, caches,
true);
1058 CCoinsMap map{0, CCoinsMap::hasher{}, CCoinsMap::key_equal{}, &resource};
1067 for (
size_t i = 0; i < 1000; ++i) {
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.
unsigned int GetCacheSize() const
Calculate the 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.
Abstract view on the open txout dataset.
virtual std::optional< Coin > GetCoin(const COutPoint &outpoint) const
Retrieve the Coin (unspent transaction output) for a given outpoint.
virtual bool BatchWrite(CoinsViewCacheCursor &cursor, const uint256 &hashBlock)
Do a bulk modification (multiple Coin changes + BestBlock change).
virtual uint256 GetBestBlock() const
Retrieve the block hash whose state this CCoinsView currently represents.
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.
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)
constexpr bool IsNull() const
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)
constexpr MaybeCoin SPENT_FRESH
BOOST_FIXTURE_TEST_CASE(coins_cache_simulation_test, CacheTest)
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 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)