diff --git a/src/test/coins_tests.cpp b/src/test/coins_tests.cpp index 79dea00e46..f0e74a55d2 100644 --- a/src/test/coins_tests.cpp +++ b/src/test/coins_tests.cpp @@ -80,6 +80,8 @@ public: BOOST_CHECK_EQUAL(DynamicMemoryUsage(), ret); } + CCoinsMap& map() { return cacheCoins; } + size_t& usage() { return cachedCoinsUsage; } }; } @@ -415,4 +417,366 @@ BOOST_AUTO_TEST_CASE(ccoins_serialization) } } +const static uint256 TXID; +const static CAmount PRUNED = -1; +const static CAmount ABSENT = -2; +const static CAmount VALUE1 = 100; +const static CAmount VALUE2 = 200; +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)}; +const static auto CLEAN_FLAGS = {char(0), FRESH}; +const static auto DIRTY_FLAGS = {DIRTY, char(DIRTY | FRESH)}; +const static auto ABSENT_FLAGS = {NO_ENTRY}; + +void SetCoinsValue(CAmount value, CCoins& coins) +{ + assert(value != ABSENT); + coins.Clear(); + assert(coins.IsPruned()); + if (value != PRUNED) { + coins.vout.emplace_back(); + coins.vout.back().nValue = value; + assert(!coins.IsPruned()); + } +} + +size_t InsertCoinsMapEntry(CCoinsMap& map, CAmount value, char flags) +{ + if (value == ABSENT) { + assert(flags == NO_ENTRY); + return 0; + } + assert(flags != NO_ENTRY); + CCoinsCacheEntry entry; + entry.flags = flags; + SetCoinsValue(value, entry.coins); + auto inserted = map.emplace(TXID, std::move(entry)); + assert(inserted.second); + return inserted.first->second.coins.DynamicMemoryUsage(); +} + +void GetCoinsMapEntry(const CCoinsMap& map, CAmount& value, char& flags) +{ + auto it = map.find(TXID); + if (it == map.end()) { + value = ABSENT; + flags = NO_ENTRY; + } else { + if (it->second.coins.IsPruned()) { + assert(it->second.coins.vout.size() == 0); + value = PRUNED; + } else { + assert(it->second.coins.vout.size() == 1); + value = it->second.coins.vout[0].nValue; + } + flags = it->second.flags; + assert(flags != NO_ENTRY); + } +} + +void WriteCoinsViewEntry(CCoinsView& view, CAmount value, char flags) +{ + CCoinsMap map; + InsertCoinsMapEntry(map, value, flags); + view.BatchWrite(map, {}); +} + +class SingleEntryCacheTest +{ +public: + SingleEntryCacheTest(CAmount base_value, CAmount cache_value, char cache_flags) + { + WriteCoinsViewEntry(base, base_value, base_value == ABSENT ? NO_ENTRY : DIRTY); + cache.usage() += InsertCoinsMapEntry(cache.map(), cache_value, cache_flags); + } + + CCoinsView root; + CCoinsViewCacheTest base{&root}; + CCoinsViewCacheTest cache{&base}; +}; + +void CheckAccessCoins(CAmount base_value, CAmount cache_value, CAmount expected_value, char cache_flags, char expected_flags) +{ + SingleEntryCacheTest test(base_value, cache_value, cache_flags); + test.cache.AccessCoins(TXID); + test.cache.SelfTest(); + + 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) +{ + /* 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 + * the access. + * + * Base Cache Result Cache Result + * Value Value Value Flags Flags + */ + CheckAccessCoins(ABSENT, ABSENT, ABSENT, NO_ENTRY , NO_ENTRY ); + CheckAccessCoins(ABSENT, PRUNED, PRUNED, 0 , 0 ); + CheckAccessCoins(ABSENT, PRUNED, PRUNED, FRESH , FRESH ); + CheckAccessCoins(ABSENT, PRUNED, PRUNED, DIRTY , DIRTY ); + CheckAccessCoins(ABSENT, PRUNED, PRUNED, DIRTY|FRESH, DIRTY|FRESH); + CheckAccessCoins(ABSENT, VALUE2, VALUE2, 0 , 0 ); + CheckAccessCoins(ABSENT, VALUE2, VALUE2, FRESH , FRESH ); + CheckAccessCoins(ABSENT, VALUE2, VALUE2, DIRTY , DIRTY ); + CheckAccessCoins(ABSENT, VALUE2, VALUE2, DIRTY|FRESH, DIRTY|FRESH); + CheckAccessCoins(PRUNED, ABSENT, PRUNED, NO_ENTRY , FRESH ); + CheckAccessCoins(PRUNED, PRUNED, PRUNED, 0 , 0 ); + CheckAccessCoins(PRUNED, PRUNED, PRUNED, FRESH , FRESH ); + CheckAccessCoins(PRUNED, PRUNED, PRUNED, DIRTY , DIRTY ); + CheckAccessCoins(PRUNED, PRUNED, PRUNED, DIRTY|FRESH, DIRTY|FRESH); + CheckAccessCoins(PRUNED, VALUE2, VALUE2, 0 , 0 ); + CheckAccessCoins(PRUNED, VALUE2, VALUE2, FRESH , FRESH ); + CheckAccessCoins(PRUNED, VALUE2, VALUE2, DIRTY , DIRTY ); + CheckAccessCoins(PRUNED, VALUE2, VALUE2, DIRTY|FRESH, DIRTY|FRESH); + CheckAccessCoins(VALUE1, ABSENT, VALUE1, NO_ENTRY , 0 ); + CheckAccessCoins(VALUE1, PRUNED, PRUNED, 0 , 0 ); + CheckAccessCoins(VALUE1, PRUNED, PRUNED, FRESH , FRESH ); + CheckAccessCoins(VALUE1, PRUNED, PRUNED, DIRTY , DIRTY ); + CheckAccessCoins(VALUE1, PRUNED, PRUNED, DIRTY|FRESH, DIRTY|FRESH); + CheckAccessCoins(VALUE1, VALUE2, VALUE2, 0 , 0 ); + CheckAccessCoins(VALUE1, VALUE2, VALUE2, FRESH , FRESH ); + CheckAccessCoins(VALUE1, VALUE2, VALUE2, DIRTY , DIRTY ); + CheckAccessCoins(VALUE1, VALUE2, VALUE2, DIRTY|FRESH, DIRTY|FRESH); +} + +void CheckModifyCoins(CAmount base_value, CAmount cache_value, CAmount modify_value, CAmount expected_value, char cache_flags, char expected_flags) +{ + SingleEntryCacheTest test(base_value, cache_value, cache_flags); + SetCoinsValue(modify_value, *test.cache.ModifyCoins(TXID)); + test.cache.SelfTest(); + + 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_modify) +{ + /* Check ModifyCoin behavior, requesting a coin from a cache view layered on + * top of a base view, writing a modification to the coin, and then checking + * the resulting entry in the cache after the modification. + * + * Base Cache Write Result Cache Result + * Value Value Value Value Flags Flags + */ + CheckModifyCoins(ABSENT, ABSENT, PRUNED, ABSENT, NO_ENTRY , NO_ENTRY ); + CheckModifyCoins(ABSENT, ABSENT, VALUE3, VALUE3, NO_ENTRY , DIRTY|FRESH); + CheckModifyCoins(ABSENT, PRUNED, PRUNED, PRUNED, 0 , DIRTY ); + CheckModifyCoins(ABSENT, PRUNED, PRUNED, ABSENT, FRESH , NO_ENTRY ); + CheckModifyCoins(ABSENT, PRUNED, PRUNED, PRUNED, DIRTY , DIRTY ); + CheckModifyCoins(ABSENT, PRUNED, PRUNED, ABSENT, DIRTY|FRESH, NO_ENTRY ); + CheckModifyCoins(ABSENT, PRUNED, VALUE3, VALUE3, 0 , DIRTY ); + CheckModifyCoins(ABSENT, PRUNED, VALUE3, VALUE3, FRESH , DIRTY|FRESH); + CheckModifyCoins(ABSENT, PRUNED, VALUE3, VALUE3, DIRTY , DIRTY ); + CheckModifyCoins(ABSENT, PRUNED, VALUE3, VALUE3, DIRTY|FRESH, DIRTY|FRESH); + CheckModifyCoins(ABSENT, VALUE2, PRUNED, PRUNED, 0 , DIRTY ); + CheckModifyCoins(ABSENT, VALUE2, PRUNED, ABSENT, FRESH , NO_ENTRY ); + CheckModifyCoins(ABSENT, VALUE2, PRUNED, PRUNED, DIRTY , DIRTY ); + CheckModifyCoins(ABSENT, VALUE2, PRUNED, ABSENT, DIRTY|FRESH, NO_ENTRY ); + CheckModifyCoins(ABSENT, VALUE2, VALUE3, VALUE3, 0 , DIRTY ); + CheckModifyCoins(ABSENT, VALUE2, VALUE3, VALUE3, FRESH , DIRTY|FRESH); + CheckModifyCoins(ABSENT, VALUE2, VALUE3, VALUE3, DIRTY , DIRTY ); + CheckModifyCoins(ABSENT, VALUE2, VALUE3, VALUE3, DIRTY|FRESH, DIRTY|FRESH); + CheckModifyCoins(PRUNED, ABSENT, PRUNED, ABSENT, NO_ENTRY , NO_ENTRY ); + CheckModifyCoins(PRUNED, ABSENT, VALUE3, VALUE3, NO_ENTRY , DIRTY|FRESH); + CheckModifyCoins(PRUNED, PRUNED, PRUNED, PRUNED, 0 , DIRTY ); + CheckModifyCoins(PRUNED, PRUNED, PRUNED, ABSENT, FRESH , NO_ENTRY ); + CheckModifyCoins(PRUNED, PRUNED, PRUNED, PRUNED, DIRTY , DIRTY ); + CheckModifyCoins(PRUNED, PRUNED, PRUNED, ABSENT, DIRTY|FRESH, NO_ENTRY ); + CheckModifyCoins(PRUNED, PRUNED, VALUE3, VALUE3, 0 , DIRTY ); + CheckModifyCoins(PRUNED, PRUNED, VALUE3, VALUE3, FRESH , DIRTY|FRESH); + CheckModifyCoins(PRUNED, PRUNED, VALUE3, VALUE3, DIRTY , DIRTY ); + CheckModifyCoins(PRUNED, PRUNED, VALUE3, VALUE3, DIRTY|FRESH, DIRTY|FRESH); + CheckModifyCoins(PRUNED, VALUE2, PRUNED, PRUNED, 0 , DIRTY ); + CheckModifyCoins(PRUNED, VALUE2, PRUNED, ABSENT, FRESH , NO_ENTRY ); + CheckModifyCoins(PRUNED, VALUE2, PRUNED, PRUNED, DIRTY , DIRTY ); + CheckModifyCoins(PRUNED, VALUE2, PRUNED, ABSENT, DIRTY|FRESH, NO_ENTRY ); + CheckModifyCoins(PRUNED, VALUE2, VALUE3, VALUE3, 0 , DIRTY ); + CheckModifyCoins(PRUNED, VALUE2, VALUE3, VALUE3, FRESH , DIRTY|FRESH); + CheckModifyCoins(PRUNED, VALUE2, VALUE3, VALUE3, DIRTY , DIRTY ); + CheckModifyCoins(PRUNED, VALUE2, VALUE3, VALUE3, DIRTY|FRESH, DIRTY|FRESH); + CheckModifyCoins(VALUE1, ABSENT, PRUNED, PRUNED, NO_ENTRY , DIRTY ); + CheckModifyCoins(VALUE1, ABSENT, VALUE3, VALUE3, NO_ENTRY , DIRTY ); + CheckModifyCoins(VALUE1, PRUNED, PRUNED, PRUNED, 0 , DIRTY ); + CheckModifyCoins(VALUE1, PRUNED, PRUNED, ABSENT, FRESH , NO_ENTRY ); + CheckModifyCoins(VALUE1, PRUNED, PRUNED, PRUNED, DIRTY , DIRTY ); + CheckModifyCoins(VALUE1, PRUNED, PRUNED, ABSENT, DIRTY|FRESH, NO_ENTRY ); + CheckModifyCoins(VALUE1, PRUNED, VALUE3, VALUE3, 0 , DIRTY ); + CheckModifyCoins(VALUE1, PRUNED, VALUE3, VALUE3, FRESH , DIRTY|FRESH); + CheckModifyCoins(VALUE1, PRUNED, VALUE3, VALUE3, DIRTY , DIRTY ); + CheckModifyCoins(VALUE1, PRUNED, VALUE3, VALUE3, DIRTY|FRESH, DIRTY|FRESH); + CheckModifyCoins(VALUE1, VALUE2, PRUNED, PRUNED, 0 , DIRTY ); + CheckModifyCoins(VALUE1, VALUE2, PRUNED, ABSENT, FRESH , NO_ENTRY ); + CheckModifyCoins(VALUE1, VALUE2, PRUNED, PRUNED, DIRTY , DIRTY ); + CheckModifyCoins(VALUE1, VALUE2, PRUNED, ABSENT, DIRTY|FRESH, NO_ENTRY ); + CheckModifyCoins(VALUE1, VALUE2, VALUE3, VALUE3, 0 , DIRTY ); + CheckModifyCoins(VALUE1, VALUE2, VALUE3, VALUE3, FRESH , DIRTY|FRESH); + CheckModifyCoins(VALUE1, VALUE2, VALUE3, VALUE3, DIRTY , DIRTY ); + CheckModifyCoins(VALUE1, VALUE2, VALUE3, VALUE3, DIRTY|FRESH, DIRTY|FRESH); +} + +void CheckModifyNewCoinsBase(CAmount base_value, CAmount cache_value, CAmount modify_value, CAmount expected_value, char cache_flags, char expected_flags, bool coinbase) +{ + SingleEntryCacheTest test(base_value, cache_value, cache_flags); + SetCoinsValue(modify_value, *test.cache.ModifyNewCoins(TXID, coinbase)); + + 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); +} + +// Simple wrapper for CheckModifyNewCoinsBase function above that loops through +// different possible base_values, making sure each one gives the same results. +// This wrapper lets the modify_new test below be shorter and less repetitive, +// while still verifying that the CoinsViewCache::ModifyNewCoins implementation +// ignores base values. +template +void CheckModifyNewCoins(Args&&... args) +{ + for (CAmount base_value : {ABSENT, PRUNED, VALUE1}) + CheckModifyNewCoinsBase(base_value, std::forward(args)...); +} + +BOOST_AUTO_TEST_CASE(ccoins_modify_new) +{ + /* Check ModifyNewCoin behavior, requesting a new coin from a cache view, + * writing a modification to the coin, and then checking the resulting + * entry in the cache after the modification. Verify behavior with the + * with the ModifyNewCoin coinbase argument set to false, and to true. + * + * Cache Write Result Cache Result Coinbase + * Value Value Value Flags Flags + */ + CheckModifyNewCoins(ABSENT, PRUNED, ABSENT, NO_ENTRY , NO_ENTRY , false); + CheckModifyNewCoins(ABSENT, PRUNED, PRUNED, NO_ENTRY , DIRTY , true ); + CheckModifyNewCoins(ABSENT, VALUE3, VALUE3, NO_ENTRY , DIRTY|FRESH, false); + CheckModifyNewCoins(ABSENT, VALUE3, VALUE3, NO_ENTRY , DIRTY , true ); + CheckModifyNewCoins(PRUNED, PRUNED, ABSENT, 0 , NO_ENTRY , false); + CheckModifyNewCoins(PRUNED, PRUNED, PRUNED, 0 , DIRTY , true ); + CheckModifyNewCoins(PRUNED, PRUNED, ABSENT, FRESH , NO_ENTRY , false); + CheckModifyNewCoins(PRUNED, PRUNED, ABSENT, FRESH , NO_ENTRY , true ); + CheckModifyNewCoins(PRUNED, PRUNED, ABSENT, DIRTY , NO_ENTRY , false); + CheckModifyNewCoins(PRUNED, PRUNED, PRUNED, DIRTY , DIRTY , true ); + CheckModifyNewCoins(PRUNED, PRUNED, ABSENT, DIRTY|FRESH, NO_ENTRY , false); + CheckModifyNewCoins(PRUNED, PRUNED, ABSENT, DIRTY|FRESH, NO_ENTRY , true ); + CheckModifyNewCoins(PRUNED, VALUE3, VALUE3, 0 , DIRTY|FRESH, false); + CheckModifyNewCoins(PRUNED, VALUE3, VALUE3, 0 , DIRTY , true ); + CheckModifyNewCoins(PRUNED, VALUE3, VALUE3, FRESH , DIRTY|FRESH, false); + CheckModifyNewCoins(PRUNED, VALUE3, VALUE3, FRESH , DIRTY|FRESH, true ); + CheckModifyNewCoins(PRUNED, VALUE3, VALUE3, DIRTY , DIRTY|FRESH, false); + CheckModifyNewCoins(PRUNED, VALUE3, VALUE3, DIRTY , DIRTY , true ); + CheckModifyNewCoins(PRUNED, VALUE3, VALUE3, DIRTY|FRESH, DIRTY|FRESH, false); + CheckModifyNewCoins(PRUNED, VALUE3, VALUE3, DIRTY|FRESH, DIRTY|FRESH, true ); + CheckModifyNewCoins(VALUE2, PRUNED, ABSENT, 0 , NO_ENTRY , false); + CheckModifyNewCoins(VALUE2, PRUNED, PRUNED, 0 , DIRTY , true ); + CheckModifyNewCoins(VALUE2, PRUNED, ABSENT, FRESH , NO_ENTRY , false); + CheckModifyNewCoins(VALUE2, PRUNED, ABSENT, FRESH , NO_ENTRY , true ); + CheckModifyNewCoins(VALUE2, PRUNED, ABSENT, DIRTY , NO_ENTRY , false); + CheckModifyNewCoins(VALUE2, PRUNED, PRUNED, DIRTY , DIRTY , true ); + CheckModifyNewCoins(VALUE2, PRUNED, ABSENT, DIRTY|FRESH, NO_ENTRY , false); + CheckModifyNewCoins(VALUE2, PRUNED, ABSENT, DIRTY|FRESH, NO_ENTRY , true ); + CheckModifyNewCoins(VALUE2, VALUE3, VALUE3, 0 , DIRTY|FRESH, false); + CheckModifyNewCoins(VALUE2, VALUE3, VALUE3, 0 , DIRTY , true ); + CheckModifyNewCoins(VALUE2, VALUE3, VALUE3, FRESH , DIRTY|FRESH, false); + CheckModifyNewCoins(VALUE2, VALUE3, VALUE3, FRESH , DIRTY|FRESH, true ); + CheckModifyNewCoins(VALUE2, VALUE3, VALUE3, DIRTY , DIRTY|FRESH, false); + CheckModifyNewCoins(VALUE2, VALUE3, VALUE3, DIRTY , DIRTY , true ); + CheckModifyNewCoins(VALUE2, VALUE3, VALUE3, DIRTY|FRESH, DIRTY|FRESH, false); + CheckModifyNewCoins(VALUE2, VALUE3, VALUE3, DIRTY|FRESH, DIRTY|FRESH, true ); +} + +void CheckWriteCoins(CAmount parent_value, CAmount child_value, CAmount expected_value, char parent_flags, char child_flags, char expected_flags) +{ + SingleEntryCacheTest test(ABSENT, parent_value, parent_flags); + WriteCoinsViewEntry(test.cache, child_value, child_flags); + test.cache.SelfTest(); + + 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_write) +{ + /* Check BatchWrite behavior, flushing one entry from a child cache to a + * parent cache, and checking the resulting entry in the parent cache + * after the write. + * + * Parent Child Result Parent Child Result + * Value Value Value Flags Flags Flags + */ + CheckWriteCoins(ABSENT, ABSENT, ABSENT, NO_ENTRY , NO_ENTRY , NO_ENTRY ); + CheckWriteCoins(ABSENT, PRUNED, PRUNED, NO_ENTRY , DIRTY , DIRTY ); + CheckWriteCoins(ABSENT, PRUNED, ABSENT, NO_ENTRY , DIRTY|FRESH, NO_ENTRY ); + CheckWriteCoins(ABSENT, VALUE2, VALUE2, NO_ENTRY , DIRTY , DIRTY ); + CheckWriteCoins(ABSENT, VALUE2, VALUE2, NO_ENTRY , DIRTY|FRESH, DIRTY|FRESH); + CheckWriteCoins(PRUNED, ABSENT, PRUNED, 0 , NO_ENTRY , 0 ); + CheckWriteCoins(PRUNED, ABSENT, PRUNED, FRESH , NO_ENTRY , FRESH ); + CheckWriteCoins(PRUNED, ABSENT, PRUNED, DIRTY , NO_ENTRY , DIRTY ); + CheckWriteCoins(PRUNED, ABSENT, PRUNED, DIRTY|FRESH, NO_ENTRY , DIRTY|FRESH); + CheckWriteCoins(PRUNED, PRUNED, PRUNED, 0 , DIRTY , DIRTY ); + CheckWriteCoins(PRUNED, PRUNED, PRUNED, 0 , DIRTY|FRESH, DIRTY ); + CheckWriteCoins(PRUNED, PRUNED, ABSENT, FRESH , DIRTY , NO_ENTRY ); + CheckWriteCoins(PRUNED, PRUNED, ABSENT, FRESH , DIRTY|FRESH, NO_ENTRY ); + CheckWriteCoins(PRUNED, PRUNED, PRUNED, DIRTY , DIRTY , DIRTY ); + CheckWriteCoins(PRUNED, PRUNED, PRUNED, DIRTY , DIRTY|FRESH, DIRTY ); + CheckWriteCoins(PRUNED, PRUNED, ABSENT, DIRTY|FRESH, DIRTY , NO_ENTRY ); + CheckWriteCoins(PRUNED, PRUNED, ABSENT, DIRTY|FRESH, DIRTY|FRESH, NO_ENTRY ); + CheckWriteCoins(PRUNED, VALUE2, VALUE2, 0 , DIRTY , DIRTY ); + CheckWriteCoins(PRUNED, VALUE2, VALUE2, 0 , DIRTY|FRESH, DIRTY ); + CheckWriteCoins(PRUNED, VALUE2, VALUE2, FRESH , DIRTY , DIRTY|FRESH); + CheckWriteCoins(PRUNED, VALUE2, VALUE2, FRESH , DIRTY|FRESH, DIRTY|FRESH); + CheckWriteCoins(PRUNED, VALUE2, VALUE2, DIRTY , DIRTY , DIRTY ); + CheckWriteCoins(PRUNED, VALUE2, VALUE2, DIRTY , DIRTY|FRESH, DIRTY ); + CheckWriteCoins(PRUNED, VALUE2, VALUE2, DIRTY|FRESH, DIRTY , DIRTY|FRESH); + CheckWriteCoins(PRUNED, 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, PRUNED, PRUNED, 0 , DIRTY , DIRTY ); + CheckWriteCoins(VALUE1, PRUNED, PRUNED, 0 , DIRTY|FRESH, DIRTY ); + CheckWriteCoins(VALUE1, PRUNED, ABSENT, FRESH , DIRTY , NO_ENTRY ); + CheckWriteCoins(VALUE1, PRUNED, ABSENT, FRESH , DIRTY|FRESH, NO_ENTRY ); + CheckWriteCoins(VALUE1, PRUNED, PRUNED, DIRTY , DIRTY , DIRTY ); + CheckWriteCoins(VALUE1, PRUNED, PRUNED, DIRTY , DIRTY|FRESH, DIRTY ); + CheckWriteCoins(VALUE1, PRUNED, ABSENT, DIRTY|FRESH, DIRTY , NO_ENTRY ); + CheckWriteCoins(VALUE1, PRUNED, ABSENT, DIRTY|FRESH, DIRTY|FRESH, NO_ENTRY ); + CheckWriteCoins(VALUE1, VALUE2, VALUE2, 0 , DIRTY , DIRTY ); + CheckWriteCoins(VALUE1, VALUE2, VALUE2, 0 , DIRTY|FRESH, DIRTY ); + CheckWriteCoins(VALUE1, VALUE2, VALUE2, FRESH , DIRTY , DIRTY|FRESH); + CheckWriteCoins(VALUE1, VALUE2, VALUE2, FRESH , DIRTY|FRESH, DIRTY|FRESH); + CheckWriteCoins(VALUE1, VALUE2, VALUE2, DIRTY , DIRTY , DIRTY ); + CheckWriteCoins(VALUE1, VALUE2, VALUE2, DIRTY , DIRTY|FRESH, DIRTY ); + CheckWriteCoins(VALUE1, VALUE2, VALUE2, DIRTY|FRESH, DIRTY , DIRTY|FRESH); + CheckWriteCoins(VALUE1, VALUE2, VALUE2, DIRTY|FRESH, DIRTY|FRESH, DIRTY|FRESH); + + // The checks above omit cases where the child flags are not DIRTY, since + // 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 + // is always left unchanged. + for (CAmount parent_value : {ABSENT, PRUNED, VALUE1}) + for (CAmount child_value : {ABSENT, PRUNED, VALUE2}) + for (char parent_flags : parent_value == ABSENT ? ABSENT_FLAGS : FLAGS) + for (char child_flags : child_value == ABSENT ? ABSENT_FLAGS : CLEAN_FLAGS) + CheckWriteCoins(parent_value, child_value, parent_value, parent_flags, child_flags, parent_flags); +} + BOOST_AUTO_TEST_SUITE_END()