Merge bitcoin/bitcoin#30906: refactor: prohibit direct flags access in CCoinsCacheEntry and remove invalid tests
Some checks are pending
CI / ASan + LSan + UBSan + integer, no depends, USDT (push) Waiting to run
CI / Win64 native, VS 2022 (push) Waiting to run
CI / Win64 native fuzz, VS 2022 (push) Waiting to run
CI / test each commit (push) Waiting to run
CI / macOS 14 native, arm64, no depends, sqlite only, gui (push) Waiting to run
CI / macOS 14 native, arm64, fuzz (push) Waiting to run

50cce20013 test, refactor: Compact ccoins_access and ccoins_spend (Lőrinc)
0a159f0914 test, refactor: Remove remaining unbounded flags from coins_tests (Lőrinc)
c0b4b2c1ee test: Validate error messages on fail (Lőrinc)
d5f8d607ab test: Group values and states in tests into CoinEntry wrappers (Lőrinc)
ca74aa7490 test, refactor: Migrate GetCoinsMapEntry to return MaybeCoin (Lőrinc)
15aaa81c38 coins, refactor: Remove direct GetFlags access (Lőrinc)
6b733699cf coins, refactor: Assume state after SetClean in AddFlags to prevent dangling pointers (Lőrinc)
fc8c282022 coins, refactor: Make AddFlags, SetDirty, SetFresh static (Lőrinc)
cd0498eabc coins, refactor: Split up AddFlags to remove invalid states (Lőrinc)

Pull request description:

  Similarly to https://github.com/bitcoin/bitcoin/pull/30849, this cleanup is intended to de-risk https://github.com/bitcoin/bitcoin/pull/30673#discussion_r1739909068 by simplifying the coin cache public interface.

  `CCoinsCacheEntry` provided general access to its internal flags state, even though, in reality, it could only be `clean`, `fresh`, `dirty`, or `fresh|dirty` (in the follow-up, we will remove `fresh` without `dirty`).

  Once it was marked as `dirty`, we couldn’t set the state back to clean with `AddFlags(0)`—tests explicitly checked against that.

  This PR refines the public interface to make this distinction clearer and to make invalid behavior impossible, rather than just checked by tests. We don't need extensive access to the internals of `CCoinsCacheEntry`, as many tests were simply validating invalid combinations in this way.

  The last few commits contain significant test refactorings to make `coins_tests` easier to change in follow-ups.

ACKs for top commit:
  andrewtoth:
    Code Review ACK 50cce20013
  laanwj:
    Code review ACK 50cce20013
  ryanofsky:
    Code review ACK 50cce20013. Looks good! Thanks for the followups.

Tree-SHA512: c0d65f1c7680b4bb9cd368422b218f2473c2ec75a32c7350a6e11e8a1601c81d3c0ae651b9f1dae08400fb4e5d43431d9e4ccca305a718183f9a936fe47c1a6c
This commit is contained in:
Ryan Ofsky 2024-12-04 13:50:21 -05:00
commit 17372d788e
No known key found for this signature in database
GPG key ID: 46800E30FC748A66
5 changed files with 296 additions and 362 deletions

View file

@ -53,7 +53,7 @@ CCoinsMap::iterator CCoinsViewCache::FetchCoin(const COutPoint &outpoint) const
cachedCoinsUsage += ret->second.coin.DynamicMemoryUsage(); cachedCoinsUsage += ret->second.coin.DynamicMemoryUsage();
if (ret->second.coin.IsSpent()) { // TODO GetCoin cannot return spent coins if (ret->second.coin.IsSpent()) { // TODO GetCoin cannot return spent coins
// The parent only has an empty entry for this outpoint; we can consider our version as fresh. // The parent only has an empty entry for this outpoint; we can consider our version as fresh.
ret->second.AddFlags(CCoinsCacheEntry::FRESH, *ret, m_sentinel); CCoinsCacheEntry::SetFresh(*ret, m_sentinel);
} }
} else { } else {
cacheCoins.erase(ret); cacheCoins.erase(ret);
@ -99,7 +99,8 @@ void CCoinsViewCache::AddCoin(const COutPoint &outpoint, Coin&& coin, bool possi
fresh = !it->second.IsDirty(); fresh = !it->second.IsDirty();
} }
it->second.coin = std::move(coin); it->second.coin = std::move(coin);
it->second.AddFlags(CCoinsCacheEntry::DIRTY | (fresh ? CCoinsCacheEntry::FRESH : 0), *it, m_sentinel); CCoinsCacheEntry::SetDirty(*it, m_sentinel);
if (fresh) CCoinsCacheEntry::SetFresh(*it, m_sentinel);
cachedCoinsUsage += it->second.coin.DynamicMemoryUsage(); cachedCoinsUsage += it->second.coin.DynamicMemoryUsage();
TRACEPOINT(utxocache, add, TRACEPOINT(utxocache, add,
outpoint.hash.data(), outpoint.hash.data(),
@ -111,13 +112,8 @@ void CCoinsViewCache::AddCoin(const COutPoint &outpoint, Coin&& coin, bool possi
void CCoinsViewCache::EmplaceCoinInternalDANGER(COutPoint&& outpoint, Coin&& coin) { void CCoinsViewCache::EmplaceCoinInternalDANGER(COutPoint&& outpoint, Coin&& coin) {
cachedCoinsUsage += coin.DynamicMemoryUsage(); cachedCoinsUsage += coin.DynamicMemoryUsage();
auto [it, inserted] = cacheCoins.emplace( auto [it, inserted] = cacheCoins.try_emplace(std::move(outpoint), std::move(coin));
std::piecewise_construct, if (inserted) CCoinsCacheEntry::SetDirty(*it, m_sentinel);
std::forward_as_tuple(std::move(outpoint)),
std::forward_as_tuple(std::move(coin)));
if (inserted) {
it->second.AddFlags(CCoinsCacheEntry::DIRTY, *it, m_sentinel);
}
} }
void AddCoins(CCoinsViewCache& cache, const CTransaction &tx, int nHeight, bool check_for_overwrite) { void AddCoins(CCoinsViewCache& cache, const CTransaction &tx, int nHeight, bool check_for_overwrite) {
@ -147,7 +143,7 @@ bool CCoinsViewCache::SpendCoin(const COutPoint &outpoint, Coin* moveout) {
if (it->second.IsFresh()) { if (it->second.IsFresh()) {
cacheCoins.erase(it); cacheCoins.erase(it);
} else { } else {
it->second.AddFlags(CCoinsCacheEntry::DIRTY, *it, m_sentinel); CCoinsCacheEntry::SetDirty(*it, m_sentinel);
it->second.coin.Clear(); it->second.coin.Clear();
} }
return true; return true;
@ -207,13 +203,11 @@ bool CCoinsViewCache::BatchWrite(CoinsViewCacheCursor& cursor, const uint256 &ha
entry.coin = it->second.coin; entry.coin = it->second.coin;
} }
cachedCoinsUsage += entry.coin.DynamicMemoryUsage(); cachedCoinsUsage += entry.coin.DynamicMemoryUsage();
entry.AddFlags(CCoinsCacheEntry::DIRTY, *itUs, m_sentinel); CCoinsCacheEntry::SetDirty(*itUs, m_sentinel);
// We can mark it FRESH in the parent if it was FRESH in the child // We can mark it FRESH in the parent if it was FRESH in the child
// Otherwise it might have just been flushed from the parent's cache // Otherwise it might have just been flushed from the parent's cache
// and already exist in the grandparent // and already exist in the grandparent
if (it->second.IsFresh()) { if (it->second.IsFresh()) CCoinsCacheEntry::SetFresh(*itUs, m_sentinel);
entry.AddFlags(CCoinsCacheEntry::FRESH, *itUs, m_sentinel);
}
} }
} else { } else {
// Found the entry in the parent cache // Found the entry in the parent cache
@ -241,7 +235,7 @@ bool CCoinsViewCache::BatchWrite(CoinsViewCacheCursor& cursor, const uint256 &ha
itUs->second.coin = it->second.coin; itUs->second.coin = it->second.coin;
} }
cachedCoinsUsage += itUs->second.coin.DynamicMemoryUsage(); cachedCoinsUsage += itUs->second.coin.DynamicMemoryUsage();
itUs->second.AddFlags(CCoinsCacheEntry::DIRTY, *itUs, m_sentinel); CCoinsCacheEntry::SetDirty(*itUs, m_sentinel);
// NOTE: It isn't safe to mark the coin as FRESH in the parent // NOTE: It isn't safe to mark the coin as FRESH in the parent
// cache. If it already existed and was spent in the parent // cache. If it already existed and was spent in the parent
// cache then marking it FRESH would prevent that spentness // cache then marking it FRESH would prevent that spentness

View file

@ -110,7 +110,7 @@ struct CCoinsCacheEntry
private: private:
/** /**
* These are used to create a doubly linked list of flagged entries. * These are used to create a doubly linked list of flagged entries.
* They are set in AddFlags and unset in ClearFlags. * They are set in SetDirty, SetFresh, and unset in SetClean.
* A flagged entry is any entry that is either DIRTY, FRESH, or both. * A flagged entry is any entry that is either DIRTY, FRESH, or both.
* *
* DIRTY entries are tracked so that only modified entries can be passed to * DIRTY entries are tracked so that only modified entries can be passed to
@ -128,6 +128,21 @@ private:
CoinsCachePair* m_next{nullptr}; CoinsCachePair* m_next{nullptr};
uint8_t m_flags{0}; uint8_t m_flags{0};
//! Adding a flag requires a reference to the sentinel of the flagged pair linked list.
static void AddFlags(uint8_t flags, CoinsCachePair& pair, CoinsCachePair& sentinel) noexcept
{
Assume(flags & (DIRTY | FRESH));
if (!pair.second.m_flags) {
Assume(!pair.second.m_prev && !pair.second.m_next);
pair.second.m_prev = sentinel.second.m_prev;
pair.second.m_next = &sentinel;
sentinel.second.m_prev = &pair;
pair.second.m_prev->second.m_next = &pair;
}
Assume(pair.second.m_prev && pair.second.m_next);
pair.second.m_flags |= flags;
}
public: public:
Coin coin; // The actual cached data. Coin coin; // The actual cached data.
@ -156,52 +171,43 @@ public:
explicit CCoinsCacheEntry(Coin&& coin_) noexcept : coin(std::move(coin_)) {} explicit CCoinsCacheEntry(Coin&& coin_) noexcept : coin(std::move(coin_)) {}
~CCoinsCacheEntry() ~CCoinsCacheEntry()
{ {
ClearFlags(); SetClean();
} }
//! Adding a flag also requires a self reference to the pair that contains static void SetDirty(CoinsCachePair& pair, CoinsCachePair& sentinel) noexcept { AddFlags(DIRTY, pair, sentinel); }
//! this entry in the CCoinsCache map and a reference to the sentinel of the static void SetFresh(CoinsCachePair& pair, CoinsCachePair& sentinel) noexcept { AddFlags(FRESH, pair, sentinel); }
//! flagged pair linked list.
inline void AddFlags(uint8_t flags, CoinsCachePair& self, CoinsCachePair& sentinel) noexcept void SetClean() noexcept
{
Assume(&self.second == this);
if (!m_flags && flags) {
m_prev = sentinel.second.m_prev;
m_next = &sentinel;
sentinel.second.m_prev = &self;
m_prev->second.m_next = &self;
}
m_flags |= flags;
}
inline void ClearFlags() noexcept
{ {
if (!m_flags) return; if (!m_flags) return;
m_next->second.m_prev = m_prev; m_next->second.m_prev = m_prev;
m_prev->second.m_next = m_next; m_prev->second.m_next = m_next;
m_flags = 0; m_flags = 0;
m_prev = m_next = nullptr;
} }
inline uint8_t GetFlags() const noexcept { return m_flags; } bool IsDirty() const noexcept { return m_flags & DIRTY; }
inline bool IsDirty() const noexcept { return m_flags & DIRTY; } bool IsFresh() const noexcept { return m_flags & FRESH; }
inline bool IsFresh() const noexcept { return m_flags & FRESH; }
//! Only call Next when this entry is DIRTY, FRESH, or both //! Only call Next when this entry is DIRTY, FRESH, or both
inline CoinsCachePair* Next() const noexcept { CoinsCachePair* Next() const noexcept
{
Assume(m_flags); Assume(m_flags);
return m_next; return m_next;
} }
//! Only call Prev when this entry is DIRTY, FRESH, or both //! Only call Prev when this entry is DIRTY, FRESH, or both
inline CoinsCachePair* Prev() const noexcept { CoinsCachePair* Prev() const noexcept
{
Assume(m_flags); Assume(m_flags);
return m_prev; return m_prev;
} }
//! Only use this for initializing the linked list sentinel //! Only use this for initializing the linked list sentinel
inline void SelfRef(CoinsCachePair& self) noexcept void SelfRef(CoinsCachePair& pair) noexcept
{ {
Assume(&self.second == this); Assume(&pair.second == this);
m_prev = &self; m_prev = &pair;
m_next = &self; m_next = &pair;
// Set sentinel to DIRTY so we can call Next on it // Set sentinel to DIRTY so we can call Next on it
m_flags = DIRTY; m_flags = DIRTY;
} }
@ -279,13 +285,13 @@ struct CoinsViewCacheCursor
{ {
const auto next_entry{current.second.Next()}; const auto next_entry{current.second.Next()};
// If we are not going to erase the cache, we must still erase spent entries. // If we are not going to erase the cache, we must still erase spent entries.
// Otherwise clear the flags on the entry. // Otherwise, clear the state of the entry.
if (!m_will_erase) { if (!m_will_erase) {
if (current.second.coin.IsSpent()) { if (current.second.coin.IsSpent()) {
m_usage -= current.second.coin.DynamicMemoryUsage(); m_usage -= current.second.coin.DynamicMemoryUsage();
m_map.erase(current.first); m_map.erase(current.first);
} else { } else {
current.second.ClearFlags(); current.second.SetClean();
} }
} }
return next_entry; return next_entry;

View file

@ -15,6 +15,8 @@
#include <util/strencodings.h> #include <util/strencodings.h>
#include <map> #include <map>
#include <string>
#include <variant>
#include <vector> #include <vector>
#include <boost/test/unit_test.hpp> #include <boost/test/unit_test.hpp>
@ -559,21 +561,58 @@ BOOST_AUTO_TEST_CASE(ccoins_serialization)
} }
const static COutPoint OUTPOINT; const static COutPoint OUTPOINT;
const static CAmount SPENT = -1; constexpr CAmount SPENT {-1};
const static CAmount ABSENT = -2; constexpr CAmount ABSENT{-2};
const static CAmount FAIL = -3; constexpr CAmount VALUE1{100};
const static CAmount VALUE1 = 100; constexpr CAmount VALUE2{200};
const static CAmount VALUE2 = 200; constexpr CAmount VALUE3{300};
const static CAmount VALUE3 = 300;
const static char DIRTY = CCoinsCacheEntry::DIRTY;
const static char FRESH = CCoinsCacheEntry::FRESH;
const static char NO_ENTRY = -1;
const static auto FLAGS = {char(0), FRESH, DIRTY, char(DIRTY | FRESH)}; struct CoinEntry {
const static auto CLEAN_FLAGS = {char(0), FRESH}; enum class State { CLEAN, DIRTY, FRESH, DIRTY_FRESH };
const static auto ABSENT_FLAGS = {NO_ENTRY};
static void SetCoinsValue(CAmount value, Coin& coin) const CAmount value;
const State state;
constexpr CoinEntry(const CAmount v, const State s) : value{v}, state{s} {}
bool operator==(const CoinEntry& o) const = default;
friend std::ostream& operator<<(std::ostream& os, const CoinEntry& e) { return os << e.value << ", " << e.state; }
constexpr bool IsDirtyFresh() const { return state == State::DIRTY_FRESH; }
constexpr bool IsDirty() const { return state == State::DIRTY || IsDirtyFresh(); }
constexpr bool IsFresh() const { return state == State::FRESH || IsDirtyFresh(); }
static constexpr State ToState(const bool is_dirty, const bool is_fresh) {
if (is_dirty && is_fresh) return State::DIRTY_FRESH;
if (is_dirty) return State::DIRTY;
if (is_fresh) return State::FRESH;
return State::CLEAN;
}
};
using MaybeCoin = std::optional<CoinEntry>;
using CoinOrError = std::variant<MaybeCoin, std::string>;
constexpr MaybeCoin MISSING {std::nullopt};
constexpr MaybeCoin SPENT_DIRTY {{SPENT, CoinEntry::State::DIRTY}};
constexpr MaybeCoin SPENT_DIRTY_FRESH {{SPENT, CoinEntry::State::DIRTY_FRESH}};
constexpr MaybeCoin SPENT_FRESH {{SPENT, CoinEntry::State::FRESH}};
constexpr MaybeCoin SPENT_CLEAN {{SPENT, CoinEntry::State::CLEAN}};
constexpr MaybeCoin VALUE1_DIRTY {{VALUE1, CoinEntry::State::DIRTY}};
constexpr MaybeCoin VALUE1_DIRTY_FRESH{{VALUE1, CoinEntry::State::DIRTY_FRESH}};
constexpr MaybeCoin VALUE1_FRESH {{VALUE1, CoinEntry::State::FRESH}};
constexpr MaybeCoin VALUE1_CLEAN {{VALUE1, CoinEntry::State::CLEAN}};
constexpr MaybeCoin VALUE2_DIRTY {{VALUE2, CoinEntry::State::DIRTY}};
constexpr MaybeCoin VALUE2_DIRTY_FRESH{{VALUE2, CoinEntry::State::DIRTY_FRESH}};
constexpr MaybeCoin VALUE2_FRESH {{VALUE2, CoinEntry::State::FRESH}};
constexpr MaybeCoin VALUE2_CLEAN {{VALUE2, CoinEntry::State::CLEAN}};
constexpr MaybeCoin VALUE3_DIRTY {{VALUE3, CoinEntry::State::DIRTY}};
constexpr MaybeCoin VALUE3_DIRTY_FRESH{{VALUE3, CoinEntry::State::DIRTY_FRESH}};
constexpr auto EX_OVERWRITE_UNSPENT{"Attempted to overwrite an unspent coin (when possible_overwrite is false)"};
constexpr auto EX_FRESH_MISAPPLIED {"FRESH flag misapplied to coin that exists in parent cache"};
static void SetCoinsValue(const CAmount value, Coin& coin)
{ {
assert(value != ABSENT); assert(value != ABSENT);
coin.Clear(); coin.Clear();
@ -585,45 +624,34 @@ static void SetCoinsValue(CAmount value, Coin& coin)
} }
} }
static size_t InsertCoinsMapEntry(CCoinsMap& map, CoinsCachePair& sentinel, CAmount value, char flags) static size_t InsertCoinsMapEntry(CCoinsMap& map, CoinsCachePair& sentinel, const CoinEntry& cache_coin)
{ {
if (value == ABSENT) {
assert(flags == NO_ENTRY);
return 0;
}
assert(flags != NO_ENTRY);
CCoinsCacheEntry entry; CCoinsCacheEntry entry;
SetCoinsValue(value, entry.coin); SetCoinsValue(cache_coin.value, entry.coin);
auto inserted = map.emplace(OUTPOINT, std::move(entry)); auto [iter, inserted] = map.emplace(OUTPOINT, std::move(entry));
assert(inserted.second); assert(inserted);
inserted.first->second.AddFlags(flags, *inserted.first, sentinel); if (cache_coin.IsDirty()) CCoinsCacheEntry::SetDirty(*iter, sentinel);
return inserted.first->second.coin.DynamicMemoryUsage(); if (cache_coin.IsFresh()) CCoinsCacheEntry::SetFresh(*iter, sentinel);
return iter->second.coin.DynamicMemoryUsage();
} }
void GetCoinsMapEntry(const CCoinsMap& map, CAmount& value, char& flags, const COutPoint& outp = OUTPOINT) static MaybeCoin GetCoinsMapEntry(const CCoinsMap& map, const COutPoint& outp = OUTPOINT)
{ {
auto it = map.find(outp); if (auto it{map.find(outp)}; it != map.end()) {
if (it == map.end()) { return CoinEntry{
value = ABSENT; it->second.coin.IsSpent() ? SPENT : it->second.coin.out.nValue,
flags = NO_ENTRY; CoinEntry::ToState(it->second.IsDirty(), it->second.IsFresh())};
} else {
if (it->second.coin.IsSpent()) {
value = SPENT;
} else {
value = it->second.coin.out.nValue;
}
flags = it->second.GetFlags();
assert(flags != NO_ENTRY);
} }
return MISSING;
} }
void WriteCoinsViewEntry(CCoinsView& view, CAmount value, char flags) static void WriteCoinsViewEntry(CCoinsView& view, const MaybeCoin& cache_coin)
{ {
CoinsCachePair sentinel{}; CoinsCachePair sentinel{};
sentinel.second.SelfRef(sentinel); sentinel.second.SelfRef(sentinel);
CCoinsMapMemoryResource resource; CCoinsMapMemoryResource resource;
CCoinsMap map{0, CCoinsMap::hasher{}, CCoinsMap::key_equal{}, &resource}; CCoinsMap map{0, CCoinsMap::hasher{}, CCoinsMap::key_equal{}, &resource};
auto usage{InsertCoinsMapEntry(map, sentinel, value, flags)}; auto usage{cache_coin ? InsertCoinsMapEntry(map, sentinel, *cache_coin) : 0};
auto cursor{CoinsViewCacheCursor(usage, sentinel, map, /*will_erase=*/true)}; auto cursor{CoinsViewCacheCursor(usage, sentinel, map, /*will_erase=*/true)};
BOOST_CHECK(view.BatchWrite(cursor, {})); BOOST_CHECK(view.BatchWrite(cursor, {}));
} }
@ -631,10 +659,11 @@ void WriteCoinsViewEntry(CCoinsView& view, CAmount value, char flags)
class SingleEntryCacheTest class SingleEntryCacheTest
{ {
public: public:
SingleEntryCacheTest(CAmount base_value, CAmount cache_value, char cache_flags) SingleEntryCacheTest(const CAmount base_value, const MaybeCoin& cache_coin)
{ {
WriteCoinsViewEntry(base, base_value, base_value == ABSENT ? NO_ENTRY : DIRTY); auto base_cache_coin{base_value == ABSENT ? MISSING : CoinEntry{base_value, CoinEntry::State::DIRTY}};
cache.usage() += InsertCoinsMapEntry(cache.map(), cache.sentinel(), cache_value, cache_flags); WriteCoinsViewEntry(base, base_cache_coin);
if (cache_coin) cache.usage() += InsertCoinsMapEntry(cache.map(), cache.sentinel(), *cache_coin);
} }
CCoinsView root; CCoinsView root;
@ -642,17 +671,13 @@ public:
CCoinsViewCacheTest cache{&base}; CCoinsViewCacheTest cache{&base};
}; };
static void CheckAccessCoin(CAmount base_value, CAmount cache_value, CAmount expected_value, char cache_flags, char expected_flags) static void CheckAccessCoin(const CAmount base_value, const MaybeCoin& cache_coin, const MaybeCoin& expected)
{ {
SingleEntryCacheTest test(base_value, cache_value, cache_flags); SingleEntryCacheTest test{base_value, cache_coin};
test.cache.AccessCoin(OUTPOINT); auto& coin = test.cache.AccessCoin(OUTPOINT);
BOOST_CHECK_EQUAL(coin.IsSpent(), !test.cache.GetCoin(OUTPOINT));
test.cache.SelfTest(/*sanity_check=*/false); test.cache.SelfTest(/*sanity_check=*/false);
BOOST_CHECK_EQUAL(GetCoinsMapEntry(test.cache.map()), expected);
CAmount result_value;
char result_flags;
GetCoinsMapEntry(test.cache.map(), result_value, result_flags);
BOOST_CHECK_EQUAL(result_value, expected_value);
BOOST_CHECK_EQUAL(result_flags, expected_flags);
} }
BOOST_AUTO_TEST_CASE(ccoins_access) BOOST_AUTO_TEST_CASE(ccoins_access)
@ -660,121 +685,65 @@ BOOST_AUTO_TEST_CASE(ccoins_access)
/* Check AccessCoin behavior, requesting a coin from a cache view layered on /* Check AccessCoin behavior, requesting a coin from a cache view layered on
* top of a base view, and checking the resulting entry in the cache after * top of a base view, and checking the resulting entry in the cache after
* the access. * the access.
* * Base Cache Expected
* Base Cache Result Cache Result
* Value Value Value Flags Flags
*/ */
CheckAccessCoin(ABSENT, ABSENT, ABSENT, NO_ENTRY , NO_ENTRY ); for (auto base_value : {ABSENT, SPENT, VALUE1}) {
CheckAccessCoin(ABSENT, SPENT , SPENT , 0 , 0 ); CheckAccessCoin(base_value, MISSING, base_value == VALUE1 ? VALUE1_CLEAN : MISSING);
CheckAccessCoin(ABSENT, SPENT , SPENT , FRESH , FRESH );
CheckAccessCoin(ABSENT, SPENT , SPENT , DIRTY , DIRTY ); CheckAccessCoin(base_value, SPENT_CLEAN, SPENT_CLEAN );
CheckAccessCoin(ABSENT, SPENT , SPENT , DIRTY|FRESH, DIRTY|FRESH); CheckAccessCoin(base_value, SPENT_FRESH, SPENT_FRESH );
CheckAccessCoin(ABSENT, VALUE2, VALUE2, 0 , 0 ); CheckAccessCoin(base_value, SPENT_DIRTY, SPENT_DIRTY );
CheckAccessCoin(ABSENT, VALUE2, VALUE2, FRESH , FRESH ); CheckAccessCoin(base_value, SPENT_DIRTY_FRESH, SPENT_DIRTY_FRESH );
CheckAccessCoin(ABSENT, VALUE2, VALUE2, DIRTY , DIRTY );
CheckAccessCoin(ABSENT, VALUE2, VALUE2, DIRTY|FRESH, DIRTY|FRESH); CheckAccessCoin(base_value, VALUE2_CLEAN, VALUE2_CLEAN );
CheckAccessCoin(SPENT , ABSENT, ABSENT, NO_ENTRY , NO_ENTRY ); CheckAccessCoin(base_value, VALUE2_FRESH, VALUE2_FRESH );
CheckAccessCoin(SPENT , SPENT , SPENT , 0 , 0 ); CheckAccessCoin(base_value, VALUE2_DIRTY, VALUE2_DIRTY );
CheckAccessCoin(SPENT , SPENT , SPENT , FRESH , FRESH ); CheckAccessCoin(base_value, VALUE2_DIRTY_FRESH, VALUE2_DIRTY_FRESH);
CheckAccessCoin(SPENT , SPENT , SPENT , DIRTY , DIRTY ); }
CheckAccessCoin(SPENT , SPENT , SPENT , DIRTY|FRESH, DIRTY|FRESH);
CheckAccessCoin(SPENT , VALUE2, VALUE2, 0 , 0 );
CheckAccessCoin(SPENT , VALUE2, VALUE2, FRESH , FRESH );
CheckAccessCoin(SPENT , VALUE2, VALUE2, DIRTY , DIRTY );
CheckAccessCoin(SPENT , VALUE2, VALUE2, DIRTY|FRESH, DIRTY|FRESH);
CheckAccessCoin(VALUE1, ABSENT, VALUE1, NO_ENTRY , 0 );
CheckAccessCoin(VALUE1, SPENT , SPENT , 0 , 0 );
CheckAccessCoin(VALUE1, SPENT , SPENT , FRESH , FRESH );
CheckAccessCoin(VALUE1, SPENT , SPENT , DIRTY , DIRTY );
CheckAccessCoin(VALUE1, SPENT , SPENT , DIRTY|FRESH, DIRTY|FRESH);
CheckAccessCoin(VALUE1, VALUE2, VALUE2, 0 , 0 );
CheckAccessCoin(VALUE1, VALUE2, VALUE2, FRESH , FRESH );
CheckAccessCoin(VALUE1, VALUE2, VALUE2, DIRTY , DIRTY );
CheckAccessCoin(VALUE1, VALUE2, VALUE2, DIRTY|FRESH, DIRTY|FRESH);
} }
static void CheckSpendCoins(CAmount base_value, CAmount cache_value, CAmount expected_value, char cache_flags, char expected_flags) static void CheckSpendCoins(const CAmount base_value, const MaybeCoin& cache_coin, const MaybeCoin& expected)
{ {
SingleEntryCacheTest test(base_value, cache_value, cache_flags); SingleEntryCacheTest test{base_value, cache_coin};
test.cache.SpendCoin(OUTPOINT); test.cache.SpendCoin(OUTPOINT);
test.cache.SelfTest(); test.cache.SelfTest();
BOOST_CHECK_EQUAL(GetCoinsMapEntry(test.cache.map()), expected);
CAmount result_value; }
char result_flags;
GetCoinsMapEntry(test.cache.map(), result_value, result_flags);
BOOST_CHECK_EQUAL(result_value, expected_value);
BOOST_CHECK_EQUAL(result_flags, expected_flags);
};
BOOST_AUTO_TEST_CASE(ccoins_spend) BOOST_AUTO_TEST_CASE(ccoins_spend)
{ {
/* Check SpendCoin behavior, requesting a coin from a cache view layered on /* Check SpendCoin behavior, requesting a coin from a cache view layered on
* top of a base view, spending, and then checking * top of a base view, spending, and then checking
* the resulting entry in the cache after the modification. * the resulting entry in the cache after the modification.
* * Base Cache Expected
* Base Cache Result Cache Result
* Value Value Value Flags Flags
*/ */
CheckSpendCoins(ABSENT, ABSENT, ABSENT, NO_ENTRY , NO_ENTRY ); for (auto base_value : {ABSENT, SPENT, VALUE1}) {
CheckSpendCoins(ABSENT, SPENT , SPENT , 0 , DIRTY ); CheckSpendCoins(base_value, MISSING, base_value == VALUE1 ? SPENT_DIRTY : MISSING);
CheckSpendCoins(ABSENT, SPENT , ABSENT, FRESH , NO_ENTRY );
CheckSpendCoins(ABSENT, SPENT , SPENT , DIRTY , DIRTY );
CheckSpendCoins(ABSENT, SPENT , ABSENT, DIRTY|FRESH, NO_ENTRY );
CheckSpendCoins(ABSENT, VALUE2, SPENT , 0 , DIRTY );
CheckSpendCoins(ABSENT, VALUE2, ABSENT, FRESH , NO_ENTRY );
CheckSpendCoins(ABSENT, VALUE2, SPENT , DIRTY , DIRTY );
CheckSpendCoins(ABSENT, VALUE2, ABSENT, DIRTY|FRESH, NO_ENTRY );
CheckSpendCoins(SPENT , ABSENT, ABSENT, NO_ENTRY , NO_ENTRY );
CheckSpendCoins(SPENT , SPENT , SPENT , 0 , DIRTY );
CheckSpendCoins(SPENT , SPENT , ABSENT, FRESH , NO_ENTRY );
CheckSpendCoins(SPENT , SPENT , SPENT , DIRTY , DIRTY );
CheckSpendCoins(SPENT , SPENT , ABSENT, DIRTY|FRESH, NO_ENTRY );
CheckSpendCoins(SPENT , VALUE2, SPENT , 0 , DIRTY );
CheckSpendCoins(SPENT , VALUE2, ABSENT, FRESH , NO_ENTRY );
CheckSpendCoins(SPENT , VALUE2, SPENT , DIRTY , DIRTY );
CheckSpendCoins(SPENT , VALUE2, ABSENT, DIRTY|FRESH, NO_ENTRY );
CheckSpendCoins(VALUE1, ABSENT, SPENT , NO_ENTRY , DIRTY );
CheckSpendCoins(VALUE1, SPENT , SPENT , 0 , DIRTY );
CheckSpendCoins(VALUE1, SPENT , ABSENT, FRESH , NO_ENTRY );
CheckSpendCoins(VALUE1, SPENT , SPENT , DIRTY , DIRTY );
CheckSpendCoins(VALUE1, SPENT , ABSENT, DIRTY|FRESH, NO_ENTRY );
CheckSpendCoins(VALUE1, VALUE2, SPENT , 0 , DIRTY );
CheckSpendCoins(VALUE1, VALUE2, ABSENT, FRESH , NO_ENTRY );
CheckSpendCoins(VALUE1, VALUE2, SPENT , DIRTY , DIRTY );
CheckSpendCoins(VALUE1, VALUE2, ABSENT, DIRTY|FRESH, NO_ENTRY );
}
static void CheckAddCoinBase(CAmount base_value, CAmount cache_value, CAmount modify_value, CAmount expected_value, char cache_flags, char expected_flags, bool coinbase) CheckSpendCoins(base_value, SPENT_CLEAN, SPENT_DIRTY);
{ CheckSpendCoins(base_value, SPENT_FRESH, MISSING );
SingleEntryCacheTest test(base_value, cache_value, cache_flags); CheckSpendCoins(base_value, SPENT_DIRTY, SPENT_DIRTY);
CheckSpendCoins(base_value, SPENT_DIRTY_FRESH, MISSING );
CAmount result_value; CheckSpendCoins(base_value, VALUE2_CLEAN, SPENT_DIRTY);
char result_flags; CheckSpendCoins(base_value, VALUE2_FRESH, MISSING );
try { CheckSpendCoins(base_value, VALUE2_DIRTY, SPENT_DIRTY);
CTxOut output; CheckSpendCoins(base_value, VALUE2_DIRTY_FRESH, MISSING );
output.nValue = modify_value;
test.cache.AddCoin(OUTPOINT, Coin(std::move(output), 1, coinbase), coinbase);
test.cache.SelfTest();
GetCoinsMapEntry(test.cache.map(), result_value, result_flags);
} catch (std::logic_error&) {
result_value = FAIL;
result_flags = NO_ENTRY;
} }
BOOST_CHECK_EQUAL(result_value, expected_value);
BOOST_CHECK_EQUAL(result_flags, expected_flags);
} }
// Simple wrapper for CheckAddCoinBase function above that loops through static void CheckAddCoin(const CAmount base_value, const MaybeCoin& cache_coin, const CAmount modify_value, const CoinOrError& expected, const bool coinbase)
// different possible base_values, making sure each one gives the same results.
// This wrapper lets the coins_add test below be shorter and less repetitive,
// while still verifying that the CoinsViewCache::AddCoin implementation
// ignores base values.
template <typename... Args>
static void CheckAddCoin(Args&&... args)
{ {
for (const CAmount base_value : {ABSENT, SPENT, VALUE1}) SingleEntryCacheTest test{base_value, cache_coin};
CheckAddCoinBase(base_value, std::forward<Args>(args)...); bool possible_overwrite{coinbase};
auto add_coin{[&] { test.cache.AddCoin(OUTPOINT, Coin{CTxOut{modify_value, CScript{}}, 1, coinbase}, possible_overwrite); }};
if (auto* expected_coin{std::get_if<MaybeCoin>(&expected)}) {
add_coin();
test.cache.SelfTest();
BOOST_CHECK_EQUAL(GetCoinsMapEntry(test.cache.map()), *expected_coin);
} else {
BOOST_CHECK_EXCEPTION(add_coin(), std::logic_error, HasReason(std::get<std::string>(expected)));
}
} }
BOOST_AUTO_TEST_CASE(ccoins_add) BOOST_AUTO_TEST_CASE(ccoins_add)
@ -782,48 +751,44 @@ BOOST_AUTO_TEST_CASE(ccoins_add)
/* Check AddCoin behavior, requesting a new coin from a cache view, /* Check AddCoin behavior, requesting a new coin from a cache view,
* writing a modification to the coin, and then checking the resulting * writing a modification to the coin, and then checking the resulting
* entry in the cache after the modification. Verify behavior with the * entry in the cache after the modification. Verify behavior with the
* AddCoin possible_overwrite argument set to false, and to true. * AddCoin coinbase argument set to false, and to true.
* * Base Cache Write Expected Coinbase
* Cache Write Result Cache Result possible_overwrite
* Value Value Value Flags Flags
*/ */
CheckAddCoin(ABSENT, VALUE3, VALUE3, NO_ENTRY , DIRTY|FRESH, false); for (auto base_value : {ABSENT, SPENT, VALUE1}) {
CheckAddCoin(ABSENT, VALUE3, VALUE3, NO_ENTRY , DIRTY , true ); CheckAddCoin(base_value, MISSING, VALUE3, VALUE3_DIRTY_FRESH, false);
CheckAddCoin(SPENT , VALUE3, VALUE3, 0 , DIRTY|FRESH, false); CheckAddCoin(base_value, MISSING, VALUE3, VALUE3_DIRTY, true );
CheckAddCoin(SPENT , VALUE3, VALUE3, 0 , DIRTY , true );
CheckAddCoin(SPENT , VALUE3, VALUE3, FRESH , DIRTY|FRESH, false); CheckAddCoin(base_value, SPENT_CLEAN, VALUE3, VALUE3_DIRTY_FRESH, false);
CheckAddCoin(SPENT , VALUE3, VALUE3, FRESH , DIRTY|FRESH, true ); CheckAddCoin(base_value, SPENT_CLEAN, VALUE3, VALUE3_DIRTY, true );
CheckAddCoin(SPENT , VALUE3, VALUE3, DIRTY , DIRTY , false); CheckAddCoin(base_value, SPENT_FRESH, VALUE3, VALUE3_DIRTY_FRESH, false);
CheckAddCoin(SPENT , VALUE3, VALUE3, DIRTY , DIRTY , true ); CheckAddCoin(base_value, SPENT_FRESH, VALUE3, VALUE3_DIRTY_FRESH, true );
CheckAddCoin(SPENT , VALUE3, VALUE3, DIRTY|FRESH, DIRTY|FRESH, false); CheckAddCoin(base_value, SPENT_DIRTY, VALUE3, VALUE3_DIRTY, false);
CheckAddCoin(SPENT , VALUE3, VALUE3, DIRTY|FRESH, DIRTY|FRESH, true ); CheckAddCoin(base_value, SPENT_DIRTY, VALUE3, VALUE3_DIRTY, true );
CheckAddCoin(VALUE2, VALUE3, FAIL , 0 , NO_ENTRY , false); CheckAddCoin(base_value, SPENT_DIRTY_FRESH, VALUE3, VALUE3_DIRTY_FRESH, false);
CheckAddCoin(VALUE2, VALUE3, VALUE3, 0 , DIRTY , true ); CheckAddCoin(base_value, SPENT_DIRTY_FRESH, VALUE3, VALUE3_DIRTY_FRESH, true );
CheckAddCoin(VALUE2, VALUE3, FAIL , FRESH , NO_ENTRY , false);
CheckAddCoin(VALUE2, VALUE3, VALUE3, FRESH , DIRTY|FRESH, true ); CheckAddCoin(base_value, VALUE2_CLEAN, VALUE3, EX_OVERWRITE_UNSPENT, false);
CheckAddCoin(VALUE2, VALUE3, FAIL , DIRTY , NO_ENTRY , false); CheckAddCoin(base_value, VALUE2_CLEAN, VALUE3, VALUE3_DIRTY, true );
CheckAddCoin(VALUE2, VALUE3, VALUE3, DIRTY , DIRTY , true ); CheckAddCoin(base_value, VALUE2_FRESH, VALUE3, EX_OVERWRITE_UNSPENT, false);
CheckAddCoin(VALUE2, VALUE3, FAIL , DIRTY|FRESH, NO_ENTRY , false); CheckAddCoin(base_value, VALUE2_FRESH, VALUE3, VALUE3_DIRTY_FRESH, true );
CheckAddCoin(VALUE2, VALUE3, VALUE3, DIRTY|FRESH, DIRTY|FRESH, true ); CheckAddCoin(base_value, VALUE2_DIRTY, VALUE3, EX_OVERWRITE_UNSPENT, false);
CheckAddCoin(base_value, VALUE2_DIRTY, VALUE3, VALUE3_DIRTY, true );
CheckAddCoin(base_value, VALUE2_DIRTY_FRESH, VALUE3, EX_OVERWRITE_UNSPENT, false);
CheckAddCoin(base_value, VALUE2_DIRTY_FRESH, VALUE3, VALUE3_DIRTY_FRESH, true );
}
} }
void CheckWriteCoins(CAmount parent_value, CAmount child_value, CAmount expected_value, char parent_flags, char child_flags, char expected_flags) static void CheckWriteCoins(const MaybeCoin& parent, const MaybeCoin& child, const CoinOrError& expected)
{ {
SingleEntryCacheTest test(ABSENT, parent_value, parent_flags); SingleEntryCacheTest test{ABSENT, parent};
auto write_coins{[&] { WriteCoinsViewEntry(test.cache, child); }};
CAmount result_value; if (auto* expected_coin{std::get_if<MaybeCoin>(&expected)}) {
char result_flags; write_coins();
try {
WriteCoinsViewEntry(test.cache, child_value, child_flags);
test.cache.SelfTest(/*sanity_check=*/false); test.cache.SelfTest(/*sanity_check=*/false);
GetCoinsMapEntry(test.cache.map(), result_value, result_flags); BOOST_CHECK_EQUAL(GetCoinsMapEntry(test.cache.map()), *expected_coin);
} catch (std::logic_error&) { } else {
result_value = FAIL; BOOST_CHECK_EXCEPTION(write_coins(), std::logic_error, HasReason(std::get<std::string>(expected)));
result_flags = NO_ENTRY;
} }
BOOST_CHECK_EQUAL(result_value, expected_value);
BOOST_CHECK_EQUAL(result_flags, expected_flags);
} }
BOOST_AUTO_TEST_CASE(ccoins_write) BOOST_AUTO_TEST_CASE(ccoins_write)
@ -831,68 +796,74 @@ BOOST_AUTO_TEST_CASE(ccoins_write)
/* Check BatchWrite behavior, flushing one entry from a child cache to a /* Check BatchWrite behavior, flushing one entry from a child cache to a
* parent cache, and checking the resulting entry in the parent cache * parent cache, and checking the resulting entry in the parent cache
* after the write. * after the write.
* * Parent Child Expected
* Parent Child Result Parent Child Result
* Value Value Value Flags Flags Flags
*/ */
CheckWriteCoins(ABSENT, ABSENT, ABSENT, NO_ENTRY , NO_ENTRY , NO_ENTRY ); CheckWriteCoins(MISSING, MISSING, MISSING );
CheckWriteCoins(ABSENT, SPENT , SPENT , NO_ENTRY , DIRTY , DIRTY ); CheckWriteCoins(MISSING, SPENT_DIRTY, SPENT_DIRTY );
CheckWriteCoins(ABSENT, SPENT , ABSENT, NO_ENTRY , DIRTY|FRESH, NO_ENTRY ); CheckWriteCoins(MISSING, SPENT_DIRTY_FRESH, MISSING );
CheckWriteCoins(ABSENT, VALUE2, VALUE2, NO_ENTRY , DIRTY , DIRTY ); CheckWriteCoins(MISSING, VALUE2_DIRTY, VALUE2_DIRTY );
CheckWriteCoins(ABSENT, VALUE2, VALUE2, NO_ENTRY , DIRTY|FRESH, DIRTY|FRESH); CheckWriteCoins(MISSING, VALUE2_DIRTY_FRESH, VALUE2_DIRTY_FRESH );
CheckWriteCoins(SPENT , ABSENT, SPENT , 0 , NO_ENTRY , 0 ); CheckWriteCoins(SPENT_CLEAN, MISSING, SPENT_CLEAN );
CheckWriteCoins(SPENT , ABSENT, SPENT , FRESH , NO_ENTRY , FRESH ); CheckWriteCoins(SPENT_FRESH, MISSING, SPENT_FRESH );
CheckWriteCoins(SPENT , ABSENT, SPENT , DIRTY , NO_ENTRY , DIRTY ); CheckWriteCoins(SPENT_DIRTY, MISSING, SPENT_DIRTY );
CheckWriteCoins(SPENT , ABSENT, SPENT , DIRTY|FRESH, NO_ENTRY , DIRTY|FRESH); CheckWriteCoins(SPENT_DIRTY_FRESH, MISSING, SPENT_DIRTY_FRESH );
CheckWriteCoins(SPENT , SPENT , SPENT , 0 , DIRTY , DIRTY );
CheckWriteCoins(SPENT , SPENT , SPENT , 0 , DIRTY|FRESH, DIRTY );
CheckWriteCoins(SPENT , SPENT , ABSENT, FRESH , DIRTY , NO_ENTRY );
CheckWriteCoins(SPENT , SPENT , ABSENT, FRESH , DIRTY|FRESH, NO_ENTRY );
CheckWriteCoins(SPENT , SPENT , SPENT , DIRTY , DIRTY , DIRTY );
CheckWriteCoins(SPENT , SPENT , SPENT , DIRTY , DIRTY|FRESH, DIRTY );
CheckWriteCoins(SPENT , SPENT , ABSENT, DIRTY|FRESH, DIRTY , NO_ENTRY );
CheckWriteCoins(SPENT , SPENT , ABSENT, DIRTY|FRESH, DIRTY|FRESH, NO_ENTRY );
CheckWriteCoins(SPENT , VALUE2, VALUE2, 0 , DIRTY , DIRTY );
CheckWriteCoins(SPENT , VALUE2, VALUE2, 0 , DIRTY|FRESH, DIRTY );
CheckWriteCoins(SPENT , VALUE2, VALUE2, FRESH , DIRTY , DIRTY|FRESH);
CheckWriteCoins(SPENT , VALUE2, VALUE2, FRESH , DIRTY|FRESH, DIRTY|FRESH);
CheckWriteCoins(SPENT , VALUE2, VALUE2, DIRTY , DIRTY , DIRTY );
CheckWriteCoins(SPENT , VALUE2, VALUE2, DIRTY , DIRTY|FRESH, DIRTY );
CheckWriteCoins(SPENT , VALUE2, VALUE2, DIRTY|FRESH, DIRTY , DIRTY|FRESH);
CheckWriteCoins(SPENT , VALUE2, VALUE2, DIRTY|FRESH, DIRTY|FRESH, DIRTY|FRESH);
CheckWriteCoins(VALUE1, ABSENT, VALUE1, 0 , NO_ENTRY , 0 );
CheckWriteCoins(VALUE1, ABSENT, VALUE1, FRESH , NO_ENTRY , FRESH );
CheckWriteCoins(VALUE1, ABSENT, VALUE1, DIRTY , NO_ENTRY , DIRTY );
CheckWriteCoins(VALUE1, ABSENT, VALUE1, DIRTY|FRESH, NO_ENTRY , DIRTY|FRESH);
CheckWriteCoins(VALUE1, SPENT , SPENT , 0 , DIRTY , DIRTY );
CheckWriteCoins(VALUE1, SPENT , FAIL , 0 , DIRTY|FRESH, NO_ENTRY );
CheckWriteCoins(VALUE1, SPENT , ABSENT, FRESH , DIRTY , NO_ENTRY );
CheckWriteCoins(VALUE1, SPENT , FAIL , FRESH , DIRTY|FRESH, NO_ENTRY );
CheckWriteCoins(VALUE1, SPENT , SPENT , DIRTY , DIRTY , DIRTY );
CheckWriteCoins(VALUE1, SPENT , FAIL , DIRTY , DIRTY|FRESH, NO_ENTRY );
CheckWriteCoins(VALUE1, SPENT , ABSENT, DIRTY|FRESH, DIRTY , NO_ENTRY );
CheckWriteCoins(VALUE1, SPENT , FAIL , DIRTY|FRESH, DIRTY|FRESH, NO_ENTRY );
CheckWriteCoins(VALUE1, VALUE2, VALUE2, 0 , DIRTY , DIRTY );
CheckWriteCoins(VALUE1, VALUE2, FAIL , 0 , DIRTY|FRESH, NO_ENTRY );
CheckWriteCoins(VALUE1, VALUE2, VALUE2, FRESH , DIRTY , DIRTY|FRESH);
CheckWriteCoins(VALUE1, VALUE2, FAIL , FRESH , DIRTY|FRESH, NO_ENTRY );
CheckWriteCoins(VALUE1, VALUE2, VALUE2, DIRTY , DIRTY , DIRTY );
CheckWriteCoins(VALUE1, VALUE2, FAIL , DIRTY , DIRTY|FRESH, NO_ENTRY );
CheckWriteCoins(VALUE1, VALUE2, VALUE2, DIRTY|FRESH, DIRTY , DIRTY|FRESH);
CheckWriteCoins(VALUE1, VALUE2, FAIL , DIRTY|FRESH, DIRTY|FRESH, NO_ENTRY );
// The checks above omit cases where the child flags are not DIRTY, since CheckWriteCoins(SPENT_CLEAN, SPENT_DIRTY, SPENT_DIRTY );
CheckWriteCoins(SPENT_CLEAN, SPENT_DIRTY_FRESH, SPENT_DIRTY );
CheckWriteCoins(SPENT_FRESH, SPENT_DIRTY, MISSING );
CheckWriteCoins(SPENT_FRESH, SPENT_DIRTY_FRESH, MISSING );
CheckWriteCoins(SPENT_DIRTY, SPENT_DIRTY, SPENT_DIRTY );
CheckWriteCoins(SPENT_DIRTY, SPENT_DIRTY_FRESH, SPENT_DIRTY );
CheckWriteCoins(SPENT_DIRTY_FRESH, SPENT_DIRTY, MISSING );
CheckWriteCoins(SPENT_DIRTY_FRESH, SPENT_DIRTY_FRESH, MISSING );
CheckWriteCoins(SPENT_CLEAN, VALUE2_DIRTY, VALUE2_DIRTY );
CheckWriteCoins(SPENT_CLEAN, VALUE2_DIRTY_FRESH, VALUE2_DIRTY );
CheckWriteCoins(SPENT_FRESH, VALUE2_DIRTY, VALUE2_DIRTY_FRESH );
CheckWriteCoins(SPENT_FRESH, VALUE2_DIRTY_FRESH, VALUE2_DIRTY_FRESH );
CheckWriteCoins(SPENT_DIRTY, VALUE2_DIRTY, VALUE2_DIRTY );
CheckWriteCoins(SPENT_DIRTY, VALUE2_DIRTY_FRESH, VALUE2_DIRTY );
CheckWriteCoins(SPENT_DIRTY_FRESH, VALUE2_DIRTY, VALUE2_DIRTY_FRESH );
CheckWriteCoins(SPENT_DIRTY_FRESH, VALUE2_DIRTY_FRESH, VALUE2_DIRTY_FRESH );
CheckWriteCoins(VALUE1_CLEAN, MISSING, VALUE1_CLEAN );
CheckWriteCoins(VALUE1_FRESH, MISSING, VALUE1_FRESH );
CheckWriteCoins(VALUE1_DIRTY, MISSING, VALUE1_DIRTY );
CheckWriteCoins(VALUE1_DIRTY_FRESH, MISSING, VALUE1_DIRTY_FRESH );
CheckWriteCoins(VALUE1_CLEAN, SPENT_DIRTY, SPENT_DIRTY );
CheckWriteCoins(VALUE1_CLEAN, SPENT_DIRTY_FRESH, EX_FRESH_MISAPPLIED);
CheckWriteCoins(VALUE1_FRESH, SPENT_DIRTY, MISSING );
CheckWriteCoins(VALUE1_FRESH, SPENT_DIRTY_FRESH, EX_FRESH_MISAPPLIED);
CheckWriteCoins(VALUE1_DIRTY, SPENT_DIRTY, SPENT_DIRTY );
CheckWriteCoins(VALUE1_DIRTY, SPENT_DIRTY_FRESH, EX_FRESH_MISAPPLIED);
CheckWriteCoins(VALUE1_DIRTY_FRESH, SPENT_DIRTY, MISSING );
CheckWriteCoins(VALUE1_DIRTY_FRESH, SPENT_DIRTY_FRESH, EX_FRESH_MISAPPLIED);
CheckWriteCoins(VALUE1_CLEAN, VALUE2_DIRTY, VALUE2_DIRTY );
CheckWriteCoins(VALUE1_CLEAN, VALUE2_DIRTY_FRESH, EX_FRESH_MISAPPLIED);
CheckWriteCoins(VALUE1_FRESH, VALUE2_DIRTY, VALUE2_DIRTY_FRESH );
CheckWriteCoins(VALUE1_FRESH, VALUE2_DIRTY_FRESH, EX_FRESH_MISAPPLIED);
CheckWriteCoins(VALUE1_DIRTY, VALUE2_DIRTY, VALUE2_DIRTY );
CheckWriteCoins(VALUE1_DIRTY, VALUE2_DIRTY_FRESH, EX_FRESH_MISAPPLIED);
CheckWriteCoins(VALUE1_DIRTY_FRESH, VALUE2_DIRTY, VALUE2_DIRTY_FRESH );
CheckWriteCoins(VALUE1_DIRTY_FRESH, VALUE2_DIRTY_FRESH, EX_FRESH_MISAPPLIED);
// The checks above omit cases where the child state is not DIRTY, since
// they would be too repetitive (the parent cache is never updated in these // they would be too repetitive (the parent cache is never updated in these
// cases). The loop below covers these cases and makes sure the parent cache // cases). The loop below covers these cases and makes sure the parent cache
// is always left unchanged. // is always left unchanged.
for (const CAmount parent_value : {ABSENT, SPENT, VALUE1}) for (const MaybeCoin& parent : {MISSING,
for (const CAmount child_value : {ABSENT, SPENT, VALUE2}) SPENT_CLEAN, SPENT_DIRTY, SPENT_FRESH, SPENT_DIRTY_FRESH,
for (const char parent_flags : parent_value == ABSENT ? ABSENT_FLAGS : FLAGS) VALUE1_CLEAN, VALUE1_DIRTY, VALUE1_FRESH, VALUE1_DIRTY_FRESH}) {
for (const char child_flags : child_value == ABSENT ? ABSENT_FLAGS : CLEAN_FLAGS) for (const MaybeCoin& child : {MISSING,
CheckWriteCoins(parent_value, child_value, parent_value, parent_flags, child_flags, parent_flags); SPENT_CLEAN, SPENT_FRESH,
VALUE2_CLEAN, VALUE2_FRESH}) {
auto expected{CoinOrError{parent}}; // TODO test failure cases as well
CheckWriteCoins(parent, child, expected);
}
}
} }
struct FlushTest : BasicTestingSetup { struct FlushTest : BasicTestingSetup {
Coin MakeCoin() Coin MakeCoin()
{ {
@ -920,8 +891,6 @@ void TestFlushBehavior(
std::vector<std::unique_ptr<CCoinsViewCacheTest>>& all_caches, std::vector<std::unique_ptr<CCoinsViewCacheTest>>& all_caches,
bool do_erasing_flush) bool do_erasing_flush)
{ {
CAmount value;
char flags;
size_t cache_usage; size_t cache_usage;
size_t cache_size; size_t cache_size;
@ -955,9 +924,7 @@ void TestFlushBehavior(
BOOST_CHECK(!base.HaveCoin(outp)); BOOST_CHECK(!base.HaveCoin(outp));
BOOST_CHECK(view->HaveCoin(outp)); BOOST_CHECK(view->HaveCoin(outp));
GetCoinsMapEntry(view->map(), value, flags, outp); BOOST_CHECK_EQUAL(GetCoinsMapEntry(view->map(), outp), CoinEntry(coin.out.nValue, CoinEntry::State::DIRTY_FRESH));
BOOST_CHECK_EQUAL(value, coin.out.nValue);
BOOST_CHECK_EQUAL(flags, DIRTY|FRESH);
// --- 2. Flushing all caches (without erasing) // --- 2. Flushing all caches (without erasing)
// //
@ -969,9 +936,7 @@ void TestFlushBehavior(
// --- 3. Ensuring the entry still exists in the cache and has been written to parent // --- 3. Ensuring the entry still exists in the cache and has been written to parent
// //
GetCoinsMapEntry(view->map(), value, flags, outp); BOOST_CHECK_EQUAL(GetCoinsMapEntry(view->map(), outp), CoinEntry(coin.out.nValue, CoinEntry::State::CLEAN)); // State should have been wiped.
BOOST_CHECK_EQUAL(value, coin.out.nValue);
BOOST_CHECK_EQUAL(flags, 0); // Flags should have been wiped.
// Both views should now have the coin. // Both views should now have the coin.
BOOST_CHECK(base.HaveCoin(outp)); BOOST_CHECK(base.HaveCoin(outp));
@ -989,14 +954,9 @@ void TestFlushBehavior(
// --- 5. Ensuring the entry is no longer in the cache // --- 5. Ensuring the entry is no longer in the cache
// //
GetCoinsMapEntry(view->map(), value, flags, outp); BOOST_CHECK(!GetCoinsMapEntry(view->map(), outp));
BOOST_CHECK_EQUAL(value, ABSENT);
BOOST_CHECK_EQUAL(flags, NO_ENTRY);
view->AccessCoin(outp); view->AccessCoin(outp);
GetCoinsMapEntry(view->map(), value, flags, outp); BOOST_CHECK_EQUAL(GetCoinsMapEntry(view->map(), outp), CoinEntry(coin.out.nValue, CoinEntry::State::CLEAN));
BOOST_CHECK_EQUAL(value, coin.out.nValue);
BOOST_CHECK_EQUAL(flags, 0);
} }
// Can't overwrite an entry without specifying that an overwrite is // Can't overwrite an entry without specifying that an overwrite is
@ -1010,9 +970,7 @@ void TestFlushBehavior(
BOOST_CHECK(view->SpendCoin(outp)); BOOST_CHECK(view->SpendCoin(outp));
// The coin should be in the cache, but spent and marked dirty. // The coin should be in the cache, but spent and marked dirty.
GetCoinsMapEntry(view->map(), value, flags, outp); BOOST_CHECK_EQUAL(GetCoinsMapEntry(view->map(), outp), SPENT_DIRTY);
BOOST_CHECK_EQUAL(value, SPENT);
BOOST_CHECK_EQUAL(flags, DIRTY);
BOOST_CHECK(!view->HaveCoin(outp)); // Coin should be considered spent in `view`. BOOST_CHECK(!view->HaveCoin(outp)); // Coin should be considered spent in `view`.
BOOST_CHECK(base.HaveCoin(outp)); // But coin should still be unspent in `base`. BOOST_CHECK(base.HaveCoin(outp)); // But coin should still be unspent in `base`.
@ -1063,10 +1021,7 @@ void TestFlushBehavior(
all_caches[0]->AddCoin(outp, std::move(coin), false); all_caches[0]->AddCoin(outp, std::move(coin), false);
// Coin should be FRESH in the cache. // Coin should be FRESH in the cache.
GetCoinsMapEntry(all_caches[0]->map(), value, flags, outp); BOOST_CHECK_EQUAL(GetCoinsMapEntry(all_caches[0]->map(), outp), CoinEntry(coin_val, CoinEntry::State::DIRTY_FRESH));
BOOST_CHECK_EQUAL(value, coin_val);
BOOST_CHECK_EQUAL(flags, DIRTY|FRESH);
// Base shouldn't have seen coin. // Base shouldn't have seen coin.
BOOST_CHECK(!base.HaveCoin(outp)); BOOST_CHECK(!base.HaveCoin(outp));
@ -1074,9 +1029,7 @@ void TestFlushBehavior(
all_caches[0]->Sync(); all_caches[0]->Sync();
// Ensure there is no sign of the coin after spend/flush. // Ensure there is no sign of the coin after spend/flush.
GetCoinsMapEntry(all_caches[0]->map(), value, flags, outp); BOOST_CHECK(!GetCoinsMapEntry(all_caches[0]->map(), outp));
BOOST_CHECK_EQUAL(value, ABSENT);
BOOST_CHECK_EQUAL(flags, NO_ENTRY);
BOOST_CHECK(!all_caches[0]->HaveCoinInCache(outp)); BOOST_CHECK(!all_caches[0]->HaveCoinInCache(outp));
BOOST_CHECK(!base.HaveCoin(outp)); BOOST_CHECK(!base.HaveCoin(outp));
} }

View file

@ -19,9 +19,9 @@ std::list<CoinsCachePair> CreatePairs(CoinsCachePair& sentinel)
nodes.emplace_back(); nodes.emplace_back();
auto node{std::prev(nodes.end())}; auto node{std::prev(nodes.end())};
node->second.AddFlags(CCoinsCacheEntry::DIRTY, *node, sentinel); CCoinsCacheEntry::SetDirty(*node, sentinel);
BOOST_CHECK_EQUAL(node->second.GetFlags(), CCoinsCacheEntry::DIRTY); BOOST_CHECK(node->second.IsDirty() && !node->second.IsFresh());
BOOST_CHECK_EQUAL(node->second.Next(), &sentinel); BOOST_CHECK_EQUAL(node->second.Next(), &sentinel);
BOOST_CHECK_EQUAL(sentinel.second.Prev(), &(*node)); BOOST_CHECK_EQUAL(sentinel.second.Prev(), &(*node));
@ -48,12 +48,12 @@ BOOST_AUTO_TEST_CASE(linked_list_iteration)
BOOST_CHECK_EQUAL(node, &sentinel); BOOST_CHECK_EQUAL(node, &sentinel);
// Check iterating through pairs is identical to iterating through a list // Check iterating through pairs is identical to iterating through a list
// Clear the flags during iteration // Clear the state during iteration
node = sentinel.second.Next(); node = sentinel.second.Next();
for (const auto& expected : nodes) { for (const auto& expected : nodes) {
BOOST_CHECK_EQUAL(&expected, node); BOOST_CHECK_EQUAL(&expected, node);
auto next = node->second.Next(); auto next = node->second.Next();
node->second.ClearFlags(); node->second.SetClean();
node = next; node = next;
} }
BOOST_CHECK_EQUAL(node, &sentinel); BOOST_CHECK_EQUAL(node, &sentinel);
@ -63,7 +63,7 @@ BOOST_AUTO_TEST_CASE(linked_list_iteration)
// Delete the nodes from the list to make sure there are no dangling pointers // Delete the nodes from the list to make sure there are no dangling pointers
for (auto it{nodes.begin()}; it != nodes.end(); it = nodes.erase(it)) { for (auto it{nodes.begin()}; it != nodes.end(); it = nodes.erase(it)) {
BOOST_CHECK_EQUAL(it->second.GetFlags(), 0); BOOST_CHECK(!it->second.IsDirty() && !it->second.IsFresh());
} }
} }
@ -74,8 +74,8 @@ BOOST_AUTO_TEST_CASE(linked_list_iterate_erase)
auto nodes{CreatePairs(sentinel)}; auto nodes{CreatePairs(sentinel)};
// Check iterating through pairs is identical to iterating through a list // Check iterating through pairs is identical to iterating through a list
// Erase the nodes as we iterate through, but don't clear flags // Erase the nodes as we iterate through, but don't clear state
// The flags will be cleared by the CCoinsCacheEntry's destructor // The state will be cleared by the CCoinsCacheEntry's destructor
auto node{sentinel.second.Next()}; auto node{sentinel.second.Next()};
for (auto expected{nodes.begin()}; expected != nodes.end(); expected = nodes.erase(expected)) { for (auto expected{nodes.begin()}; expected != nodes.end(); expected = nodes.erase(expected)) {
BOOST_CHECK_EQUAL(&(*expected), node); BOOST_CHECK_EQUAL(&(*expected), node);
@ -104,10 +104,10 @@ BOOST_AUTO_TEST_CASE(linked_list_random_deletion)
// sentinel->n1->n3->n4->sentinel // sentinel->n1->n3->n4->sentinel
nodes.erase(n2); nodes.erase(n2);
// Check that n1 now points to n3, and n3 still points to n4 // Check that n1 now points to n3, and n3 still points to n4
// Also check that flags were not altered // Also check that state was not altered
BOOST_CHECK_EQUAL(n1->second.GetFlags(), CCoinsCacheEntry::DIRTY); BOOST_CHECK(n1->second.IsDirty() && !n1->second.IsFresh());
BOOST_CHECK_EQUAL(n1->second.Next(), &(*n3)); BOOST_CHECK_EQUAL(n1->second.Next(), &(*n3));
BOOST_CHECK_EQUAL(n3->second.GetFlags(), CCoinsCacheEntry::DIRTY); BOOST_CHECK(n3->second.IsDirty() && !n3->second.IsFresh());
BOOST_CHECK_EQUAL(n3->second.Next(), &(*n4)); BOOST_CHECK_EQUAL(n3->second.Next(), &(*n4));
BOOST_CHECK_EQUAL(n3->second.Prev(), &(*n1)); BOOST_CHECK_EQUAL(n3->second.Prev(), &(*n1));
@ -115,8 +115,8 @@ BOOST_AUTO_TEST_CASE(linked_list_random_deletion)
// sentinel->n3->n4->sentinel // sentinel->n3->n4->sentinel
nodes.erase(n1); nodes.erase(n1);
// Check that sentinel now points to n3, and n3 still points to n4 // Check that sentinel now points to n3, and n3 still points to n4
// Also check that flags were not altered // Also check that state was not altered
BOOST_CHECK_EQUAL(n3->second.GetFlags(), CCoinsCacheEntry::DIRTY); BOOST_CHECK(n3->second.IsDirty() && !n3->second.IsFresh());
BOOST_CHECK_EQUAL(sentinel.second.Next(), &(*n3)); BOOST_CHECK_EQUAL(sentinel.second.Next(), &(*n3));
BOOST_CHECK_EQUAL(n3->second.Next(), &(*n4)); BOOST_CHECK_EQUAL(n3->second.Next(), &(*n4));
BOOST_CHECK_EQUAL(n3->second.Prev(), &sentinel); BOOST_CHECK_EQUAL(n3->second.Prev(), &sentinel);
@ -125,8 +125,8 @@ BOOST_AUTO_TEST_CASE(linked_list_random_deletion)
// sentinel->n3->sentinel // sentinel->n3->sentinel
nodes.erase(n4); nodes.erase(n4);
// Check that sentinel still points to n3, and n3 points to sentinel // Check that sentinel still points to n3, and n3 points to sentinel
// Also check that flags were not altered // Also check that state was not altered
BOOST_CHECK_EQUAL(n3->second.GetFlags(), CCoinsCacheEntry::DIRTY); BOOST_CHECK(n3->second.IsDirty() && !n3->second.IsFresh());
BOOST_CHECK_EQUAL(sentinel.second.Next(), &(*n3)); BOOST_CHECK_EQUAL(sentinel.second.Next(), &(*n3));
BOOST_CHECK_EQUAL(n3->second.Next(), &sentinel); BOOST_CHECK_EQUAL(n3->second.Next(), &sentinel);
BOOST_CHECK_EQUAL(sentinel.second.Prev(), &(*n3)); BOOST_CHECK_EQUAL(sentinel.second.Prev(), &(*n3));
@ -139,77 +139,56 @@ BOOST_AUTO_TEST_CASE(linked_list_random_deletion)
BOOST_CHECK_EQUAL(sentinel.second.Prev(), &sentinel); BOOST_CHECK_EQUAL(sentinel.second.Prev(), &sentinel);
} }
BOOST_AUTO_TEST_CASE(linked_list_add_flags) BOOST_AUTO_TEST_CASE(linked_list_set_state)
{ {
CoinsCachePair sentinel; CoinsCachePair sentinel;
sentinel.second.SelfRef(sentinel); sentinel.second.SelfRef(sentinel);
CoinsCachePair n1; CoinsCachePair n1;
CoinsCachePair n2; CoinsCachePair n2;
// Check that adding 0 flag has no effect // Check that setting DIRTY inserts it into linked list and sets state
n1.second.AddFlags(0, n1, sentinel); CCoinsCacheEntry::SetDirty(n1, sentinel);
BOOST_CHECK_EQUAL(n1.second.GetFlags(), 0); BOOST_CHECK(n1.second.IsDirty() && !n1.second.IsFresh());
BOOST_CHECK_EQUAL(sentinel.second.Next(), &sentinel);
BOOST_CHECK_EQUAL(sentinel.second.Prev(), &sentinel);
// Check that adding DIRTY flag inserts it into linked list and sets flags
n1.second.AddFlags(CCoinsCacheEntry::DIRTY, n1, sentinel);
BOOST_CHECK_EQUAL(n1.second.GetFlags(), CCoinsCacheEntry::DIRTY);
BOOST_CHECK_EQUAL(n1.second.Next(), &sentinel); BOOST_CHECK_EQUAL(n1.second.Next(), &sentinel);
BOOST_CHECK_EQUAL(n1.second.Prev(), &sentinel); BOOST_CHECK_EQUAL(n1.second.Prev(), &sentinel);
BOOST_CHECK_EQUAL(sentinel.second.Next(), &n1); BOOST_CHECK_EQUAL(sentinel.second.Next(), &n1);
BOOST_CHECK_EQUAL(sentinel.second.Prev(), &n1); BOOST_CHECK_EQUAL(sentinel.second.Prev(), &n1);
// Check that adding FRESH flag on new node inserts it after n1 // Check that setting FRESH on new node inserts it after n1
n2.second.AddFlags(CCoinsCacheEntry::FRESH, n2, sentinel); CCoinsCacheEntry::SetFresh(n2, sentinel);
BOOST_CHECK_EQUAL(n2.second.GetFlags(), CCoinsCacheEntry::FRESH); BOOST_CHECK(n2.second.IsFresh() && !n2.second.IsDirty());
BOOST_CHECK_EQUAL(n2.second.Next(), &sentinel); BOOST_CHECK_EQUAL(n2.second.Next(), &sentinel);
BOOST_CHECK_EQUAL(n2.second.Prev(), &n1); BOOST_CHECK_EQUAL(n2.second.Prev(), &n1);
BOOST_CHECK_EQUAL(n1.second.Next(), &n2); BOOST_CHECK_EQUAL(n1.second.Next(), &n2);
BOOST_CHECK_EQUAL(sentinel.second.Prev(), &n2); BOOST_CHECK_EQUAL(sentinel.second.Prev(), &n2);
// Check that adding 0 flag has no effect, and doesn't change position // Check that we can set extra state, but they don't change our position
n1.second.AddFlags(0, n1, sentinel); CCoinsCacheEntry::SetFresh(n1, sentinel);
BOOST_CHECK_EQUAL(n1.second.GetFlags(), CCoinsCacheEntry::DIRTY); BOOST_CHECK(n1.second.IsDirty() && n1.second.IsFresh());
BOOST_CHECK_EQUAL(n1.second.Next(), &n2); BOOST_CHECK_EQUAL(n1.second.Next(), &n2);
BOOST_CHECK_EQUAL(n1.second.Prev(), &sentinel); BOOST_CHECK_EQUAL(n1.second.Prev(), &sentinel);
BOOST_CHECK_EQUAL(sentinel.second.Next(), &n1); BOOST_CHECK_EQUAL(sentinel.second.Next(), &n1);
BOOST_CHECK_EQUAL(n2.second.Prev(), &n1); BOOST_CHECK_EQUAL(n2.second.Prev(), &n1);
// Check that we can add extra flags, but they don't change our position // Check that we can clear state then re-set it
n1.second.AddFlags(CCoinsCacheEntry::FRESH, n1, sentinel); n1.second.SetClean();
BOOST_CHECK_EQUAL(n1.second.GetFlags(), CCoinsCacheEntry::DIRTY | CCoinsCacheEntry::FRESH); BOOST_CHECK(!n1.second.IsDirty() && !n1.second.IsFresh());
BOOST_CHECK_EQUAL(n1.second.Next(), &n2);
BOOST_CHECK_EQUAL(n1.second.Prev(), &sentinel);
BOOST_CHECK_EQUAL(sentinel.second.Next(), &n1);
BOOST_CHECK_EQUAL(n2.second.Prev(), &n1);
// Check that we can clear flags then re-add them
n1.second.ClearFlags();
BOOST_CHECK_EQUAL(n1.second.GetFlags(), 0);
BOOST_CHECK_EQUAL(sentinel.second.Next(), &n2); BOOST_CHECK_EQUAL(sentinel.second.Next(), &n2);
BOOST_CHECK_EQUAL(sentinel.second.Prev(), &n2); BOOST_CHECK_EQUAL(sentinel.second.Prev(), &n2);
BOOST_CHECK_EQUAL(n2.second.Next(), &sentinel); BOOST_CHECK_EQUAL(n2.second.Next(), &sentinel);
BOOST_CHECK_EQUAL(n2.second.Prev(), &sentinel); BOOST_CHECK_EQUAL(n2.second.Prev(), &sentinel);
// Check that calling `ClearFlags` with 0 flags has no effect // Calling `SetClean` a second time has no effect
n1.second.ClearFlags(); n1.second.SetClean();
BOOST_CHECK_EQUAL(n1.second.GetFlags(), 0); BOOST_CHECK(!n1.second.IsDirty() && !n1.second.IsFresh());
BOOST_CHECK_EQUAL(sentinel.second.Next(), &n2); BOOST_CHECK_EQUAL(sentinel.second.Next(), &n2);
BOOST_CHECK_EQUAL(sentinel.second.Prev(), &n2); BOOST_CHECK_EQUAL(sentinel.second.Prev(), &n2);
BOOST_CHECK_EQUAL(n2.second.Next(), &sentinel); BOOST_CHECK_EQUAL(n2.second.Next(), &sentinel);
BOOST_CHECK_EQUAL(n2.second.Prev(), &sentinel); BOOST_CHECK_EQUAL(n2.second.Prev(), &sentinel);
// Adding 0 still has no effect // Adding DIRTY re-inserts it after n2
n1.second.AddFlags(0, n1, sentinel); CCoinsCacheEntry::SetDirty(n1, sentinel);
BOOST_CHECK_EQUAL(sentinel.second.Next(), &n2); BOOST_CHECK(n1.second.IsDirty() && !n1.second.IsFresh());
BOOST_CHECK_EQUAL(sentinel.second.Prev(), &n2);
BOOST_CHECK_EQUAL(n2.second.Next(), &sentinel);
BOOST_CHECK_EQUAL(n2.second.Prev(), &sentinel);
// But adding DIRTY re-inserts it after n2
n1.second.AddFlags(CCoinsCacheEntry::DIRTY, n1, sentinel);
BOOST_CHECK_EQUAL(n1.second.GetFlags(), CCoinsCacheEntry::DIRTY);
BOOST_CHECK_EQUAL(n2.second.Next(), &n1); BOOST_CHECK_EQUAL(n2.second.Next(), &n1);
BOOST_CHECK_EQUAL(n1.second.Prev(), &n2); BOOST_CHECK_EQUAL(n1.second.Prev(), &n2);
BOOST_CHECK_EQUAL(n1.second.Next(), &sentinel); BOOST_CHECK_EQUAL(n1.second.Next(), &sentinel);

View file

@ -128,7 +128,8 @@ FUZZ_TARGET(coins_view, .init = initialize_coins_view)
LIMITED_WHILE(good_data && fuzzed_data_provider.ConsumeBool(), 10'000) LIMITED_WHILE(good_data && fuzzed_data_provider.ConsumeBool(), 10'000)
{ {
CCoinsCacheEntry coins_cache_entry; CCoinsCacheEntry coins_cache_entry;
const auto flags{fuzzed_data_provider.ConsumeIntegral<uint8_t>()}; const auto dirty{fuzzed_data_provider.ConsumeBool()};
const auto fresh{fuzzed_data_provider.ConsumeBool()};
if (fuzzed_data_provider.ConsumeBool()) { if (fuzzed_data_provider.ConsumeBool()) {
coins_cache_entry.coin = random_coin; coins_cache_entry.coin = random_coin;
} else { } else {
@ -140,7 +141,8 @@ FUZZ_TARGET(coins_view, .init = initialize_coins_view)
coins_cache_entry.coin = *opt_coin; coins_cache_entry.coin = *opt_coin;
} }
auto it{coins_map.emplace(random_out_point, std::move(coins_cache_entry)).first}; auto it{coins_map.emplace(random_out_point, std::move(coins_cache_entry)).first};
it->second.AddFlags(flags, *it, sentinel); if (dirty) CCoinsCacheEntry::SetDirty(*it, sentinel);
if (fresh) CCoinsCacheEntry::SetFresh(*it, sentinel);
usage += it->second.coin.DynamicMemoryUsage(); usage += it->second.coin.DynamicMemoryUsage();
} }
bool expected_code_path = false; bool expected_code_path = false;