20#include <boost/test/unit_test.hpp>
39 std::map<COutPoint, Coin> map_;
42 [[nodiscard]]
bool GetCoin(
const COutPoint& outpoint,
Coin& coin)
const override
44 std::map<COutPoint, Coin>::const_iterator it = map_.find(outpoint);
45 if (it == map_.end()) {
56 uint256 GetBestBlock()
const override {
return hashBestBlock_; }
61 if (it->second.IsDirty()) {
63 map_[it->first] = it->second.coin;
66 map_.erase(it->first);
71 hashBestBlock_ = hashBlock;
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; }
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++) {
162 bool result_havecoin = test_havecoin_before ? stack.back()->HaveCoin(
COutPoint(txid, 0)) :
false;
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()->AddCoin(
COutPoint(txid, 0), std::move(newcoin), is_overwrite);
199 removed_an_entry =
true;
209 stack[cacheid]->Uncache(out);
210 uncached_an_entry |= !stack[cacheid]->HaveCoinInCache(out);
215 for (
const auto& entry : result) {
216 bool have = stack.back()->HaveCoin(entry.first);
217 const Coin& coin = stack.back()->AccessCoin(entry.first);
221 missed_an_entry =
true;
223 BOOST_CHECK(stack.back()->HaveCoinInCache(entry.first));
224 found_an_entry =
true;
227 for (
const auto& test : stack) {
236 if (fake_best_block) stack[flushIndex]->SetBestBlock(
InsecureRand256());
238 BOOST_CHECK(should_erase ? stack[flushIndex]->Flush() : stack[flushIndex]->Sync());
239 flushed_without_erase |= !should_erase;
248 BOOST_CHECK(should_erase ? stack.back()->Flush() : stack.back()->Sync());
249 flushed_without_erase |= !should_erase;
255 if (stack.size() > 0) {
256 tip = stack.back().get();
258 removed_all_caches =
true;
260 stack.push_back(std::make_unique<CCoinsViewCacheTest>(tip));
261 if (stack.size() == 4) {
262 reached_4_caches =
true;
287 CCoinsViewDB db_base{{.path =
"test", .cache_bytes = 1 << 23, .memory_only =
true}, {}};
292typedef std::map<COutPoint, std::tuple<CTransaction,CTxUndo,Coin>>
UtxoData;
298 if (utxoSetIt == utxoSet.end()) {
299 utxoSetIt = utxoSet.begin();
301 auto utxoDataIt =
utxoData.find(*utxoSetIt);
316 bool spent_a_duplicate_coinbase =
false;
318 std::map<COutPoint, Coin> result;
322 std::vector<std::unique_ptr<CCoinsViewCacheTest>> stack;
323 stack.push_back(std::make_unique<CCoinsViewCacheTest>(&base));
326 std::set<COutPoint> coinbase_coins;
327 std::set<COutPoint> disconnected_coins;
328 std::set<COutPoint> duplicate_coins;
329 std::set<COutPoint> utxoset;
335 if (randiter % 20 < 19) {
339 tx.
vout[0].nValue = i;
345 if (randiter % 20 < 2 || coinbase_coins.size() < 10) {
352 disconnected_coins.erase(utxod->first);
354 duplicate_coins.insert(utxod->first);
367 if (randiter % 20 == 2 && disconnected_coins.size()) {
370 prevout = tx.
vin[0].prevout;
371 if (!
CTransaction(tx).IsCoinBase() && !utxoset.count(prevout)) {
372 disconnected_coins.erase(utxod->first);
377 if (utxoset.count(utxod->first)) {
379 assert(duplicate_coins.count(utxod->first));
381 disconnected_coins.erase(utxod->first);
387 prevout = utxod->first;
390 tx.
vin[0].prevout = prevout;
394 old_coin = result[prevout];
396 result[prevout].
Clear();
398 utxoset.erase(prevout);
402 if (duplicate_coins.count(prevout)) {
403 spent_a_duplicate_coinbase =
true;
417 utxoset.insert(outpoint);
420 utxoData.emplace(outpoint, std::make_tuple(tx,undo,old_coin));
421 }
else if (utxoset.size()) {
426 CTxUndo &undo = std::get<1>(utxod->second);
427 Coin &orig_coin = std::get<2>(utxod->second);
431 result[utxod->first].Clear();
434 result[tx.
vin[0].prevout] = orig_coin;
440 BOOST_CHECK(stack.back()->SpendCoin(utxod->first));
448 disconnected_coins.insert(utxod->first);
451 utxoset.erase(utxod->first);
453 utxoset.insert(tx.
vin[0].prevout);
458 for (
const auto& entry : result) {
459 bool have = stack.back()->HaveCoin(entry.first);
460 const Coin& coin = stack.back()->AccessCoin(entry.first);
492 if (stack.size() > 0) {
493 tip = stack.back().get();
495 stack.push_back(std::make_unique<CCoinsViewCacheTest>(tip));
538 BOOST_CHECK_MESSAGE(
false,
"We should have thrown");
539 }
catch (
const std::ios_base::failure&) {
544 uint64_t x = 3000000000ULL;
551 BOOST_CHECK_MESSAGE(
false,
"We should have thrown");
552 }
catch (
const std::ios_base::failure&) {
576 if (value !=
SPENT) {
592 auto inserted = map.emplace(
OUTPOINT, std::move(entry));
594 inserted.first->second.AddFlags(
flags, *inserted.first, sentinel);
595 return inserted.first->second.coin.DynamicMemoryUsage();
600 auto it = map.find(outp);
601 if (it == map.end()) {
605 if (it->second.coin.IsSpent()) {
608 value = it->second.coin.out.nValue;
610 flags = it->second.GetFlags();
618 sentinel.second.SelfRef(sentinel);
620 CCoinsMap map{0, CCoinsMap::hasher{}, CCoinsMap::key_equal{}, &resource};
644 test.
cache.SelfTest(
false);
695 test.
cache.SelfTest();
750 output.
nValue = modify_value;
752 test.
cache.SelfTest();
754 }
catch (std::logic_error&) {
768template <
typename... Args>
813 test.
cache.SelfTest(
false);
815 }
catch (std::logic_error&) {
887 CheckWriteCoins(parent_value, child_value, parent_value, parent_flags, child_flags, parent_flags);
912 CCoinsViewCacheTest* view,
914 std::vector<std::unique_ptr<CCoinsViewCacheTest>>& all_caches,
915 bool do_erasing_flush)
922 auto flush_all = [&all_caches](
bool erase) {
924 for (
auto i = all_caches.rbegin(); i != all_caches.rend(); ++i) {
926 cache->SanityCheck();
930 erase ? cache->Flush() : cache->Sync();
943 view->AddCoin(outp,
Coin(coin),
false);
945 cache_usage = view->DynamicMemoryUsage();
946 cache_size = view->map().size();
974 if (do_erasing_flush) {
980 BOOST_TEST(view->DynamicMemoryUsage() <= cache_usage);
982 BOOST_TEST(view->map().size() < cache_size);
990 view->AccessCoin(outp);
999 view->AddCoin(outp,
Coin(coin),
false),
1032 all_caches[0]->AddCoin(outp, std::move(coin),
false);
1033 all_caches[0]->Sync();
1036 BOOST_CHECK(!all_caches[1]->HaveCoinInCache(outp));
1057 all_caches[0]->AddCoin(outp, std::move(coin),
false);
1068 all_caches[0]->Sync();
1074 BOOST_CHECK(!all_caches[0]->HaveCoinInCache(outp));
1081 CCoinsViewDB base{{.path =
"test", .cache_bytes = 1 << 23, .memory_only =
true}, {}};
1082 std::vector<std::unique_ptr<CCoinsViewCacheTest>> caches;
1083 caches.push_back(std::make_unique<CCoinsViewCacheTest>(&base));
1084 caches.push_back(std::make_unique<CCoinsViewCacheTest>(caches.back().get()));
1086 for (
const auto& view : caches) {
1098 CCoinsMap map{0, CCoinsMap::hasher{}, CCoinsMap::key_equal{}, &resource};
1107 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.
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 bool BatchWrite(CoinsViewCacheCursor &cursor, const uint256 &hashBlock)
Do a bulk modification (multiple Coin changes + BestBlock change).
An outpoint - a combination of a transaction hash and an index n into its vout.
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.
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:
CCoinsViewCacheTest cache
SingleEntryCacheTest(CAmount base_value, CAmount cache_value, char cache_flags)
constexpr bool IsNull() const
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
static void CheckAddCoin(Args &&... args)
static const COutPoint OUTPOINT
static const CAmount ABSENT
void WriteCoinsViewEntry(CCoinsView &view, CAmount value, char flags)
static size_t InsertCoinsMapEntry(CCoinsMap &map, CoinsCachePair &sentinel, CAmount value, char flags)
static const CAmount VALUE2
static const CAmount SPENT
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
std::map< COutPoint, std::tuple< CTransaction, CTxUndo, Coin > > UtxoData
static void CheckAddCoinBase(CAmount base_value, CAmount cache_value, CAmount modify_value, CAmount expected_value, char cache_flags, char expected_flags, bool coinbase)
void UpdateCoins(const CTransaction &tx, CCoinsViewCache &inputs, CTxUndo &txundo, int nHeight)
static void CheckAccessCoin(CAmount base_value, CAmount cache_value, CAmount expected_value, char cache_flags, char expected_flags)
static void SetCoinsValue(CAmount value, Coin &coin)
void CheckWriteCoins(CAmount parent_value, CAmount child_value, CAmount expected_value, char parent_flags, char child_flags, char expected_flags)
static const CAmount VALUE1
static const char NO_ENTRY
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...
BOOST_AUTO_TEST_CASE(coins_cache_simulation_test)
static const auto ABSENT_FLAGS
void GetCoinsMapEntry(const CCoinsMap &map, CAmount &value, char &flags, const COutPoint &outp=OUTPOINT)
static const CAmount VALUE3
static void CheckSpendCoins(CAmount base_value, CAmount cache_value, CAmount expected_value, char cache_flags, char expected_flags)
void SimulationTest(CCoinsView *base, bool fake_best_block)
UtxoData::iterator FindRandomFrom(const std::set< COutPoint > &utxoSet)
static const auto CLEAN_FLAGS
static const CAmount FAIL
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.
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::vector< Byte > ParseHex(std::string_view hex_str)
Like TryParseHex, but returns an empty vector on invalid input.
A Coin in one level of the coins database caching hierarchy.
@ FRESH
FRESH means the parent cache does not have this coin or that it is a spent coin in the parent cache.
@ DIRTY
DIRTY means the CCoinsCacheEntry is potentially different from the version in the parent cache.
A mutable version of CTransaction.
std::vector< CTxOut > vout
Txid GetHash() const
Compute the hash of this CMutableTransaction.
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 SeedRandomForTest(SeedRand seedtype)
Seed the RNG for testing.
@ ZEROS
Seed with a compile time constant of zeros.
static CAmount InsecureRandMoneyAmount()
static uint64_t InsecureRandRange(uint64_t range)
static uint256 InsecureRand256()
static uint64_t InsecureRandBits(int bits)
static uint32_t InsecureRand32()
static bool InsecureRandBool()