22 #include <boost/test/unit_test.hpp> 44 std::map<COutPoint, Coin> map_;
49 std::optional<Coin> GetCoin(
const COutPoint& outpoint)
const override 51 if (
auto it{map_.find(outpoint)}; it != map_.end()) {
52 if (!it->second.IsSpent() || m_rng.
randbool()) {
59 uint256 GetBestBlock()
const override {
return hashBestBlock_; }
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();
100 CCoinsMap& map()
const {
return cacheCoins; }
102 size_t& usage()
const {
return cachedCoinsUsage; }
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) {
183 if (m_rng.
randrange(5) == 0 || coin.IsSpent()) {
189 if (m_rng.
randrange(16) == 0 && coin.IsSpent()) {
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);
298 typedef std::map<COutPoint, std::tuple<CTransaction,CTxUndo,Coin>>
UtxoData;
304 if (utxoSetIt == utxoSet.end()) {
305 utxoSetIt = utxoSet.begin();
307 auto utxoDataIt = utxoData.find(*utxoSetIt);
308 assert(utxoDataIt != utxoData.end());
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;
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];
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();
447 BOOST_CHECK(stack.back()->SpendCoin(utxod->first));
451 Coin coin = undo.vprevout[0];
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 };
578 bool operator==(
const CoinEntry& o)
const =
default;
579 friend std::ostream&
operator<<(std::ostream& os,
const CoinEntry& e) {
return os << e.value <<
", " << e.state; }
581 constexpr
bool IsDirtyFresh()
const {
return state == State::DIRTY_FRESH; }
582 constexpr
bool IsDirty()
const {
return state == State::DIRTY || IsDirtyFresh(); }
583 constexpr
bool IsFresh()
const {
return state == State::FRESH || IsDirtyFresh(); }
585 static constexpr
State ToState(
const bool is_dirty,
const bool is_fresh) {
586 if (is_dirty && is_fresh)
return State::DIRTY_FRESH;
587 if (is_dirty)
return State::DIRTY;
588 if (is_fresh)
return State::FRESH;
612 constexpr
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};
664 auto base_cache_coin{base_value ==
ABSENT ?
MISSING : CoinEntry{base_value, CoinEntry::State::DIRTY}};
666 if (cache_coin) cache.usage() +=
InsertCoinsMapEntry(cache.map(), cache.sentinel(), *cache_coin);
670 CCoinsViewCacheTest base{&root};
671 CCoinsViewCacheTest cache{&base};
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();
904 cache->SetBestBlock(m_rng.
rand256());
905 erase ? cache->Flush() : cache->Sync();
911 Coin coin = MakeCoin();
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) {
CoinsCachePair * NextAndMaybeErase(CoinsCachePair ¤t) noexcept
Return the next entry after current, possibly erasing current.
static const unsigned int NUM_SIMULATION_ITERATIONS
std::optional< CoinEntry > MaybeCoin
bool IsSpent() const
Either this coin never existed (see e.g.
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.
virtual bool BatchWrite(CoinsViewCacheCursor &cursor, const uint256 &hashBlock)
Do a bulk modification (multiple Coin changes + BestBlock change).
BOOST_FIXTURE_TEST_CASE(coins_cache_simulation_test, CacheTest)
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...
int64_t CAmount
Amount in satoshis (Can be negative)
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)
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.
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
std::string HexStr(const Span< const uint8_t > s)
Convert a span of bytes to a lower-case hexadecimal string.
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
static size_t InsertCoinsMapEntry(CCoinsMap &map, CoinsCachePair &sentinel, const CoinEntry &cache_coin)
CCoinsViewCacheTest cache
#define BOOST_CHECK(expr)