@@ -606,9 +606,9 @@ static size_t InsertCoinsMapEntry(CCoinsMap& map, CAmount value, char flags)
606
606
return inserted.first ->second .coin .DynamicMemoryUsage ();
607
607
}
608
608
609
- void GetCoinsMapEntry (const CCoinsMap& map, CAmount& value, char & flags)
609
+ void GetCoinsMapEntry (const CCoinsMap& map, CAmount& value, char & flags, const COutPoint& outp = OUTPOINT )
610
610
{
611
- auto it = map.find (OUTPOINT );
611
+ auto it = map.find (outp );
612
612
if (it == map.end ()) {
613
613
value = ABSENT;
614
614
flags = NO_ENTRY;
@@ -894,4 +894,205 @@ BOOST_AUTO_TEST_CASE(ccoins_write)
894
894
CheckWriteCoins (parent_value, child_value, parent_value, parent_flags, child_flags, parent_flags);
895
895
}
896
896
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
+
897
1098
BOOST_AUTO_TEST_SUITE_END ()
0 commit comments