Skip to content

Commit 1d7935b

Browse files
committed
test: add test for coins view flush behavior using Sync()
Thanks to Marco Falke for help with move semantics.
1 parent 2c3cbd6 commit 1d7935b

File tree

1 file changed

+203
-2
lines changed

1 file changed

+203
-2
lines changed

src/test/coins_tests.cpp

Lines changed: 203 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -606,9 +606,9 @@ static size_t InsertCoinsMapEntry(CCoinsMap& map, CAmount value, char flags)
606606
return inserted.first->second.coin.DynamicMemoryUsage();
607607
}
608608

609-
void GetCoinsMapEntry(const CCoinsMap& map, CAmount& value, char& flags)
609+
void GetCoinsMapEntry(const CCoinsMap& map, CAmount& value, char& flags, const COutPoint& outp = OUTPOINT)
610610
{
611-
auto it = map.find(OUTPOINT);
611+
auto it = map.find(outp);
612612
if (it == map.end()) {
613613
value = ABSENT;
614614
flags = NO_ENTRY;
@@ -894,4 +894,205 @@ BOOST_AUTO_TEST_CASE(ccoins_write)
894894
CheckWriteCoins(parent_value, child_value, parent_value, parent_flags, child_flags, parent_flags);
895895
}
896896

897+
898+
Coin MakeCoin()
899+
{
900+
Coin coin;
901+
coin.out.nValue = InsecureRand32();
902+
coin.nHeight = InsecureRandRange(4096);
903+
coin.fCoinBase = 0;
904+
return coin;
905+
}
906+
907+
908+
//! For CCoinsViewCache instances backed by either another cache instance or
909+
//! leveldb, test cache behavior and flag state (DIRTY/FRESH) by
910+
//!
911+
//! 1. Adding a random coin to the child-most cache,
912+
//! 2. Flushing all caches (without erasing),
913+
//! 3. Ensure the entry still exists in the cache and has been written to parent,
914+
//! 4. (if `do_erasing_flush`) Flushing the caches again (with erasing),
915+
//! 5. (if `do_erasing_flush`) Ensure the entry has been written to the parent and is no longer in the cache,
916+
//! 6. Spend the coin, ensure it no longer exists in the parent.
917+
//!
918+
void TestFlushBehavior(
919+
CCoinsViewCacheTest* view,
920+
CCoinsViewDB& base,
921+
std::vector<CCoinsViewCacheTest*>& all_caches,
922+
bool do_erasing_flush)
923+
{
924+
CAmount value;
925+
char flags;
926+
size_t cache_usage;
927+
928+
auto flush_all = [&all_caches](bool erase) {
929+
// Flush in reverse order to ensure that flushes happen from children up.
930+
for (auto i = all_caches.rbegin(); i != all_caches.rend(); ++i) {
931+
auto cache = *i;
932+
// hashBlock must be filled before flushing to disk; value is
933+
// unimportant here. This is normally done during connect/disconnect block.
934+
cache->SetBestBlock(InsecureRand256());
935+
erase ? cache->Flush() : cache->Sync();
936+
}
937+
};
938+
939+
uint256 txid = InsecureRand256();
940+
COutPoint outp = COutPoint(txid, 0);
941+
Coin coin = MakeCoin();
942+
// Ensure the coins views haven't seen this coin before.
943+
BOOST_CHECK(!base.HaveCoin(outp));
944+
BOOST_CHECK(!view->HaveCoin(outp));
945+
946+
// --- 1. Adding a random coin to the child cache
947+
//
948+
view->AddCoin(outp, Coin(coin), false);
949+
950+
cache_usage = view->DynamicMemoryUsage();
951+
// `base` shouldn't have coin (no flush yet) but `view` should have cached it.
952+
BOOST_CHECK(!base.HaveCoin(outp));
953+
BOOST_CHECK(view->HaveCoin(outp));
954+
955+
GetCoinsMapEntry(view->map(), value, flags, outp);
956+
BOOST_CHECK_EQUAL(value, coin.out.nValue);
957+
BOOST_CHECK_EQUAL(flags, DIRTY|FRESH);
958+
959+
// --- 2. Flushing all caches (without erasing)
960+
//
961+
flush_all(/*erase=*/ false);
962+
963+
// CoinsMap usage should be unchanged since we didn't erase anything.
964+
BOOST_CHECK_EQUAL(cache_usage, view->DynamicMemoryUsage());
965+
966+
// --- 3. Ensuring the entry still exists in the cache and has been written to parent
967+
//
968+
GetCoinsMapEntry(view->map(), value, flags, outp);
969+
BOOST_CHECK_EQUAL(value, coin.out.nValue);
970+
BOOST_CHECK_EQUAL(flags, 0); // Flags should have been wiped.
971+
972+
// Both views should now have the coin.
973+
BOOST_CHECK(base.HaveCoin(outp));
974+
BOOST_CHECK(view->HaveCoin(outp));
975+
976+
if (do_erasing_flush) {
977+
// --- 4. Flushing the caches again (with erasing)
978+
//
979+
flush_all(/*erase=*/ true);
980+
981+
// Memory usage should have gone down.
982+
BOOST_CHECK(view->DynamicMemoryUsage() < cache_usage);
983+
984+
// --- 5. Ensuring the entry is no longer in the cache
985+
//
986+
GetCoinsMapEntry(view->map(), value, flags, outp);
987+
BOOST_CHECK_EQUAL(value, ABSENT);
988+
BOOST_CHECK_EQUAL(flags, NO_ENTRY);
989+
990+
view->AccessCoin(outp);
991+
GetCoinsMapEntry(view->map(), value, flags, outp);
992+
BOOST_CHECK_EQUAL(value, coin.out.nValue);
993+
BOOST_CHECK_EQUAL(flags, 0);
994+
}
995+
996+
// Can't overwrite an entry without specifying that an overwrite is
997+
// expected.
998+
BOOST_CHECK_THROW(
999+
view->AddCoin(outp, Coin(coin), /*possible_overwrite=*/ false),
1000+
std::logic_error);
1001+
1002+
// --- 6. Spend the coin.
1003+
//
1004+
BOOST_CHECK(view->SpendCoin(outp));
1005+
1006+
// The coin should be in the cache, but spent and marked dirty.
1007+
GetCoinsMapEntry(view->map(), value, flags, outp);
1008+
BOOST_CHECK_EQUAL(value, SPENT);
1009+
BOOST_CHECK_EQUAL(flags, DIRTY);
1010+
BOOST_CHECK(!view->HaveCoin(outp)); // Coin should be considered spent in `view`.
1011+
BOOST_CHECK(base.HaveCoin(outp)); // But coin should still be unspent in `base`.
1012+
1013+
flush_all(/*erase=*/ false);
1014+
1015+
// Coin should be considered spent in both views.
1016+
BOOST_CHECK(!view->HaveCoin(outp));
1017+
BOOST_CHECK(!base.HaveCoin(outp));
1018+
1019+
// Spent coin should not be spendable.
1020+
BOOST_CHECK(!view->SpendCoin(outp));
1021+
1022+
// --- Bonus check: ensure that a coin added to the base view via one cache
1023+
// can be spent by another cache which has never seen it.
1024+
//
1025+
txid = InsecureRand256();
1026+
outp = COutPoint(txid, 0);
1027+
coin = MakeCoin();
1028+
BOOST_CHECK(!base.HaveCoin(outp));
1029+
BOOST_CHECK(!all_caches[0]->HaveCoin(outp));
1030+
BOOST_CHECK(!all_caches[1]->HaveCoin(outp));
1031+
1032+
all_caches[0]->AddCoin(outp, std::move(coin), false);
1033+
all_caches[0]->Sync();
1034+
BOOST_CHECK(base.HaveCoin(outp));
1035+
BOOST_CHECK(all_caches[0]->HaveCoin(outp));
1036+
BOOST_CHECK(!all_caches[1]->HaveCoinInCache(outp));
1037+
1038+
BOOST_CHECK(all_caches[1]->SpendCoin(outp));
1039+
flush_all(/*erase=*/ false);
1040+
BOOST_CHECK(!base.HaveCoin(outp));
1041+
BOOST_CHECK(!all_caches[0]->HaveCoin(outp));
1042+
BOOST_CHECK(!all_caches[1]->HaveCoin(outp));
1043+
1044+
flush_all(/*erase=*/ true); // Erase all cache content.
1045+
1046+
// --- Bonus check 2: ensure that a FRESH, spent coin is deleted by Sync()
1047+
//
1048+
txid = InsecureRand256();
1049+
outp = COutPoint(txid, 0);
1050+
coin = MakeCoin();
1051+
CAmount coin_val = coin.out.nValue;
1052+
BOOST_CHECK(!base.HaveCoin(outp));
1053+
BOOST_CHECK(!all_caches[0]->HaveCoin(outp));
1054+
BOOST_CHECK(!all_caches[1]->HaveCoin(outp));
1055+
1056+
// Add and spend from same cache without flushing.
1057+
all_caches[0]->AddCoin(outp, std::move(coin), false);
1058+
1059+
// Coin should be FRESH in the cache.
1060+
GetCoinsMapEntry(all_caches[0]->map(), value, flags, outp);
1061+
BOOST_CHECK_EQUAL(value, coin_val);
1062+
BOOST_CHECK_EQUAL(flags, DIRTY|FRESH);
1063+
1064+
// Base shouldn't have seen coin.
1065+
BOOST_CHECK(!base.HaveCoin(outp));
1066+
1067+
BOOST_CHECK(all_caches[0]->SpendCoin(outp));
1068+
all_caches[0]->Sync();
1069+
1070+
// Ensure there is no sign of the coin after spend/flush.
1071+
GetCoinsMapEntry(all_caches[0]->map(), value, flags, outp);
1072+
BOOST_CHECK_EQUAL(value, ABSENT);
1073+
BOOST_CHECK_EQUAL(flags, NO_ENTRY);
1074+
BOOST_CHECK(!all_caches[0]->HaveCoinInCache(outp));
1075+
BOOST_CHECK(!base.HaveCoin(outp));
1076+
}
1077+
1078+
BOOST_AUTO_TEST_CASE(ccoins_flush_behavior)
1079+
{
1080+
// Create two in-memory caches atop a leveldb view.
1081+
CCoinsViewDB base{"test", /*nCacheSize=*/ 1 << 23, /*fMemory=*/ true, /*fWipe=*/ false};
1082+
std::vector<CCoinsViewCacheTest*> caches;
1083+
caches.push_back(new CCoinsViewCacheTest(&base));
1084+
caches.push_back(new CCoinsViewCacheTest(caches.back()));
1085+
1086+
for (CCoinsViewCacheTest* view : caches) {
1087+
TestFlushBehavior(view, base, caches, /*do_erasing_flush=*/ false);
1088+
TestFlushBehavior(view, base, caches, /*do_erasing_flush=*/ true);
1089+
}
1090+
1091+
// Clean up the caches.
1092+
while (caches.size() > 0) {
1093+
delete caches.back();
1094+
caches.pop_back();
1095+
}
1096+
}
1097+
8971098
BOOST_AUTO_TEST_SUITE_END()

0 commit comments

Comments
 (0)