mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-01-26 11:13:23 -03:00
test: add test for coins view flush behavior using Sync()
Thanks to Marco Falke for help with move semantics.
This commit is contained in:
parent
2c3cbd6c00
commit
1d7935b45a
1 changed files with 203 additions and 2 deletions
|
@ -606,9 +606,9 @@ static size_t InsertCoinsMapEntry(CCoinsMap& map, CAmount value, char flags)
|
||||||
return inserted.first->second.coin.DynamicMemoryUsage();
|
return inserted.first->second.coin.DynamicMemoryUsage();
|
||||||
}
|
}
|
||||||
|
|
||||||
void GetCoinsMapEntry(const CCoinsMap& map, CAmount& value, char& flags)
|
void GetCoinsMapEntry(const CCoinsMap& map, CAmount& value, char& flags, const COutPoint& outp = OUTPOINT)
|
||||||
{
|
{
|
||||||
auto it = map.find(OUTPOINT);
|
auto it = map.find(outp);
|
||||||
if (it == map.end()) {
|
if (it == map.end()) {
|
||||||
value = ABSENT;
|
value = ABSENT;
|
||||||
flags = NO_ENTRY;
|
flags = NO_ENTRY;
|
||||||
|
@ -894,4 +894,205 @@ BOOST_AUTO_TEST_CASE(ccoins_write)
|
||||||
CheckWriteCoins(parent_value, child_value, parent_value, parent_flags, child_flags, parent_flags);
|
CheckWriteCoins(parent_value, child_value, parent_value, parent_flags, child_flags, parent_flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Coin MakeCoin()
|
||||||
|
{
|
||||||
|
Coin coin;
|
||||||
|
coin.out.nValue = InsecureRand32();
|
||||||
|
coin.nHeight = InsecureRandRange(4096);
|
||||||
|
coin.fCoinBase = 0;
|
||||||
|
return coin;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//! For CCoinsViewCache instances backed by either another cache instance or
|
||||||
|
//! leveldb, test cache behavior and flag state (DIRTY/FRESH) by
|
||||||
|
//!
|
||||||
|
//! 1. Adding a random coin to the child-most cache,
|
||||||
|
//! 2. Flushing all caches (without erasing),
|
||||||
|
//! 3. Ensure the entry still exists in the cache and has been written to parent,
|
||||||
|
//! 4. (if `do_erasing_flush`) Flushing the caches again (with erasing),
|
||||||
|
//! 5. (if `do_erasing_flush`) Ensure the entry has been written to the parent and is no longer in the cache,
|
||||||
|
//! 6. Spend the coin, ensure it no longer exists in the parent.
|
||||||
|
//!
|
||||||
|
void TestFlushBehavior(
|
||||||
|
CCoinsViewCacheTest* view,
|
||||||
|
CCoinsViewDB& base,
|
||||||
|
std::vector<CCoinsViewCacheTest*>& all_caches,
|
||||||
|
bool do_erasing_flush)
|
||||||
|
{
|
||||||
|
CAmount value;
|
||||||
|
char flags;
|
||||||
|
size_t cache_usage;
|
||||||
|
|
||||||
|
auto flush_all = [&all_caches](bool erase) {
|
||||||
|
// Flush in reverse order to ensure that flushes happen from children up.
|
||||||
|
for (auto i = all_caches.rbegin(); i != all_caches.rend(); ++i) {
|
||||||
|
auto cache = *i;
|
||||||
|
// hashBlock must be filled before flushing to disk; value is
|
||||||
|
// unimportant here. This is normally done during connect/disconnect block.
|
||||||
|
cache->SetBestBlock(InsecureRand256());
|
||||||
|
erase ? cache->Flush() : cache->Sync();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
uint256 txid = InsecureRand256();
|
||||||
|
COutPoint outp = COutPoint(txid, 0);
|
||||||
|
Coin coin = MakeCoin();
|
||||||
|
// Ensure the coins views haven't seen this coin before.
|
||||||
|
BOOST_CHECK(!base.HaveCoin(outp));
|
||||||
|
BOOST_CHECK(!view->HaveCoin(outp));
|
||||||
|
|
||||||
|
// --- 1. Adding a random coin to the child cache
|
||||||
|
//
|
||||||
|
view->AddCoin(outp, Coin(coin), false);
|
||||||
|
|
||||||
|
cache_usage = view->DynamicMemoryUsage();
|
||||||
|
// `base` shouldn't have coin (no flush yet) but `view` should have cached it.
|
||||||
|
BOOST_CHECK(!base.HaveCoin(outp));
|
||||||
|
BOOST_CHECK(view->HaveCoin(outp));
|
||||||
|
|
||||||
|
GetCoinsMapEntry(view->map(), value, flags, outp);
|
||||||
|
BOOST_CHECK_EQUAL(value, coin.out.nValue);
|
||||||
|
BOOST_CHECK_EQUAL(flags, DIRTY|FRESH);
|
||||||
|
|
||||||
|
// --- 2. Flushing all caches (without erasing)
|
||||||
|
//
|
||||||
|
flush_all(/*erase=*/ false);
|
||||||
|
|
||||||
|
// CoinsMap usage should be unchanged since we didn't erase anything.
|
||||||
|
BOOST_CHECK_EQUAL(cache_usage, view->DynamicMemoryUsage());
|
||||||
|
|
||||||
|
// --- 3. Ensuring the entry still exists in the cache and has been written to parent
|
||||||
|
//
|
||||||
|
GetCoinsMapEntry(view->map(), value, flags, outp);
|
||||||
|
BOOST_CHECK_EQUAL(value, coin.out.nValue);
|
||||||
|
BOOST_CHECK_EQUAL(flags, 0); // Flags should have been wiped.
|
||||||
|
|
||||||
|
// Both views should now have the coin.
|
||||||
|
BOOST_CHECK(base.HaveCoin(outp));
|
||||||
|
BOOST_CHECK(view->HaveCoin(outp));
|
||||||
|
|
||||||
|
if (do_erasing_flush) {
|
||||||
|
// --- 4. Flushing the caches again (with erasing)
|
||||||
|
//
|
||||||
|
flush_all(/*erase=*/ true);
|
||||||
|
|
||||||
|
// Memory usage should have gone down.
|
||||||
|
BOOST_CHECK(view->DynamicMemoryUsage() < cache_usage);
|
||||||
|
|
||||||
|
// --- 5. Ensuring the entry is no longer in the cache
|
||||||
|
//
|
||||||
|
GetCoinsMapEntry(view->map(), value, flags, outp);
|
||||||
|
BOOST_CHECK_EQUAL(value, ABSENT);
|
||||||
|
BOOST_CHECK_EQUAL(flags, NO_ENTRY);
|
||||||
|
|
||||||
|
view->AccessCoin(outp);
|
||||||
|
GetCoinsMapEntry(view->map(), value, flags, outp);
|
||||||
|
BOOST_CHECK_EQUAL(value, coin.out.nValue);
|
||||||
|
BOOST_CHECK_EQUAL(flags, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Can't overwrite an entry without specifying that an overwrite is
|
||||||
|
// expected.
|
||||||
|
BOOST_CHECK_THROW(
|
||||||
|
view->AddCoin(outp, Coin(coin), /*possible_overwrite=*/ false),
|
||||||
|
std::logic_error);
|
||||||
|
|
||||||
|
// --- 6. Spend the coin.
|
||||||
|
//
|
||||||
|
BOOST_CHECK(view->SpendCoin(outp));
|
||||||
|
|
||||||
|
// The coin should be in the cache, but spent and marked dirty.
|
||||||
|
GetCoinsMapEntry(view->map(), value, flags, outp);
|
||||||
|
BOOST_CHECK_EQUAL(value, SPENT);
|
||||||
|
BOOST_CHECK_EQUAL(flags, DIRTY);
|
||||||
|
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`.
|
||||||
|
|
||||||
|
flush_all(/*erase=*/ false);
|
||||||
|
|
||||||
|
// Coin should be considered spent in both views.
|
||||||
|
BOOST_CHECK(!view->HaveCoin(outp));
|
||||||
|
BOOST_CHECK(!base.HaveCoin(outp));
|
||||||
|
|
||||||
|
// Spent coin should not be spendable.
|
||||||
|
BOOST_CHECK(!view->SpendCoin(outp));
|
||||||
|
|
||||||
|
// --- Bonus check: ensure that a coin added to the base view via one cache
|
||||||
|
// can be spent by another cache which has never seen it.
|
||||||
|
//
|
||||||
|
txid = InsecureRand256();
|
||||||
|
outp = COutPoint(txid, 0);
|
||||||
|
coin = MakeCoin();
|
||||||
|
BOOST_CHECK(!base.HaveCoin(outp));
|
||||||
|
BOOST_CHECK(!all_caches[0]->HaveCoin(outp));
|
||||||
|
BOOST_CHECK(!all_caches[1]->HaveCoin(outp));
|
||||||
|
|
||||||
|
all_caches[0]->AddCoin(outp, std::move(coin), false);
|
||||||
|
all_caches[0]->Sync();
|
||||||
|
BOOST_CHECK(base.HaveCoin(outp));
|
||||||
|
BOOST_CHECK(all_caches[0]->HaveCoin(outp));
|
||||||
|
BOOST_CHECK(!all_caches[1]->HaveCoinInCache(outp));
|
||||||
|
|
||||||
|
BOOST_CHECK(all_caches[1]->SpendCoin(outp));
|
||||||
|
flush_all(/*erase=*/ false);
|
||||||
|
BOOST_CHECK(!base.HaveCoin(outp));
|
||||||
|
BOOST_CHECK(!all_caches[0]->HaveCoin(outp));
|
||||||
|
BOOST_CHECK(!all_caches[1]->HaveCoin(outp));
|
||||||
|
|
||||||
|
flush_all(/*erase=*/ true); // Erase all cache content.
|
||||||
|
|
||||||
|
// --- Bonus check 2: ensure that a FRESH, spent coin is deleted by Sync()
|
||||||
|
//
|
||||||
|
txid = InsecureRand256();
|
||||||
|
outp = COutPoint(txid, 0);
|
||||||
|
coin = MakeCoin();
|
||||||
|
CAmount coin_val = coin.out.nValue;
|
||||||
|
BOOST_CHECK(!base.HaveCoin(outp));
|
||||||
|
BOOST_CHECK(!all_caches[0]->HaveCoin(outp));
|
||||||
|
BOOST_CHECK(!all_caches[1]->HaveCoin(outp));
|
||||||
|
|
||||||
|
// Add and spend from same cache without flushing.
|
||||||
|
all_caches[0]->AddCoin(outp, std::move(coin), false);
|
||||||
|
|
||||||
|
// Coin should be FRESH in the cache.
|
||||||
|
GetCoinsMapEntry(all_caches[0]->map(), value, flags, outp);
|
||||||
|
BOOST_CHECK_EQUAL(value, coin_val);
|
||||||
|
BOOST_CHECK_EQUAL(flags, DIRTY|FRESH);
|
||||||
|
|
||||||
|
// Base shouldn't have seen coin.
|
||||||
|
BOOST_CHECK(!base.HaveCoin(outp));
|
||||||
|
|
||||||
|
BOOST_CHECK(all_caches[0]->SpendCoin(outp));
|
||||||
|
all_caches[0]->Sync();
|
||||||
|
|
||||||
|
// Ensure there is no sign of the coin after spend/flush.
|
||||||
|
GetCoinsMapEntry(all_caches[0]->map(), value, flags, outp);
|
||||||
|
BOOST_CHECK_EQUAL(value, ABSENT);
|
||||||
|
BOOST_CHECK_EQUAL(flags, NO_ENTRY);
|
||||||
|
BOOST_CHECK(!all_caches[0]->HaveCoinInCache(outp));
|
||||||
|
BOOST_CHECK(!base.HaveCoin(outp));
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_CASE(ccoins_flush_behavior)
|
||||||
|
{
|
||||||
|
// Create two in-memory caches atop a leveldb view.
|
||||||
|
CCoinsViewDB base{"test", /*nCacheSize=*/ 1 << 23, /*fMemory=*/ true, /*fWipe=*/ false};
|
||||||
|
std::vector<CCoinsViewCacheTest*> caches;
|
||||||
|
caches.push_back(new CCoinsViewCacheTest(&base));
|
||||||
|
caches.push_back(new CCoinsViewCacheTest(caches.back()));
|
||||||
|
|
||||||
|
for (CCoinsViewCacheTest* view : caches) {
|
||||||
|
TestFlushBehavior(view, base, caches, /*do_erasing_flush=*/ false);
|
||||||
|
TestFlushBehavior(view, base, caches, /*do_erasing_flush=*/ true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up the caches.
|
||||||
|
while (caches.size() > 0) {
|
||||||
|
delete caches.back();
|
||||||
|
caches.pop_back();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
BOOST_AUTO_TEST_SUITE_END()
|
BOOST_AUTO_TEST_SUITE_END()
|
||||||
|
|
Loading…
Add table
Reference in a new issue