Branch data Line data Source code
1 : : // Copyright (c) 2014-2022 The Bitcoin Core developers
2 : : // Distributed under the MIT software license, see the accompanying
3 : : // file COPYING or http://www.opensource.org/licenses/mit-license.php.
4 : :
5 : : #include <addresstype.h>
6 : : #include <clientversion.h>
7 : : #include <coins.h>
8 : : #include <streams.h>
9 : : #include <test/util/poolresourcetester.h>
10 : : #include <test/util/random.h>
11 : : #include <test/util/setup_common.h>
12 : : #include <txdb.h>
13 : : #include <uint256.h>
14 : : #include <undo.h>
15 : : #include <util/strencodings.h>
16 : :
17 : : #include <map>
18 : : #include <vector>
19 : :
20 : : #include <boost/test/unit_test.hpp>
21 : :
22 : : int ApplyTxInUndo(Coin&& undo, CCoinsViewCache& view, const COutPoint& out);
23 : : void UpdateCoins(const CTransaction& tx, CCoinsViewCache& inputs, CTxUndo &txundo, int nHeight);
24 : :
25 : : namespace
26 : : {
27 : : //! equality test
28 : 1096128 : bool operator==(const Coin &a, const Coin &b) {
29 : : // Empty Coin objects are always equal.
30 [ + + - + ]: 1096128 : if (a.IsSpent() && b.IsSpent()) return true;
31 : 630336 : return a.fCoinBase == b.fCoinBase &&
32 [ + - + - : 630336 : a.nHeight == b.nHeight &&
- + ]
33 : 315168 : a.out == b.out;
34 : : }
35 : :
36 : 2 : class CCoinsViewTest : public CCoinsView
37 : : {
38 : : uint256 hashBestBlock_;
39 : : std::map<COutPoint, Coin> map_;
40 : :
41 : : public:
42 : 6062986 : [[nodiscard]] bool GetCoin(const COutPoint& outpoint, Coin& coin) const override
43 : : {
44 : 6062986 : std::map<COutPoint, Coin>::const_iterator it = map_.find(outpoint);
45 [ + + ]: 6062986 : if (it == map_.end()) {
46 : : return false;
47 : : }
48 : 441054 : coin = it->second;
49 [ + + + + ]: 774273 : if (coin.IsSpent() && InsecureRandBool() == 0) {
50 : : // Randomly return false in case of an empty entry.
51 : 167083 : return false;
52 : : }
53 : : return true;
54 : : }
55 : :
56 : 0 : uint256 GetBestBlock() const override { return hashBestBlock_; }
57 : :
58 : 274 : bool BatchWrite(CoinsViewCacheCursor& cursor, const uint256& hashBlock) override
59 : : {
60 [ + + ]: 236294 : for (auto it{cursor.Begin()}; it != cursor.End(); it = cursor.NextAndMaybeErase(*it)){
61 [ + + ]: 236020 : if (it->second.IsDirty()) {
62 : : // Same optimization used in CCoinsViewDB is to only write dirty entries.
63 : 83339 : map_[it->first] = it->second.coin;
64 [ + + + + ]: 120393 : if (it->second.coin.IsSpent() && InsecureRandRange(3) == 0) {
65 : : // Randomly delete empty entries on write.
66 : 12463 : map_.erase(it->first);
67 : : }
68 : : }
69 : : }
70 [ - + ]: 274 : if (!hashBlock.IsNull())
71 : 0 : hashBestBlock_ = hashBlock;
72 : 274 : return true;
73 : : }
74 : : };
75 : :
76 : 0 : class CCoinsViewCacheTest : public CCoinsViewCache
77 : : {
78 : : public:
79 [ + - + - : 797 : explicit CCoinsViewCacheTest(CCoinsView* _base) : CCoinsViewCache(_base) {}
+ - + - +
- ]
80 : :
81 : 397 : void SelfTest(bool sanity_check = true) const
82 : : {
83 : : // Manually recompute the dynamic usage of the whole data, and compare it.
84 : 397 : size_t ret = memusage::DynamicUsage(cacheCoins);
85 : 397 : size_t count = 0;
86 [ + + ]: 550778 : for (const auto& entry : cacheCoins) {
87 [ + + ]: 550381 : ret += entry.second.coin.DynamicMemoryUsage();
88 : 550381 : ++count;
89 : : }
90 [ + - ]: 397 : BOOST_CHECK_EQUAL(GetCacheSize(), count);
91 [ + - ]: 397 : BOOST_CHECK_EQUAL(DynamicMemoryUsage(), ret);
92 [ + + ]: 397 : if (sanity_check) {
93 : 288 : SanityCheck();
94 : : }
95 : 397 : }
96 : :
97 [ + - ]: 10 : CCoinsMap& map() const { return cacheCoins; }
98 : 198 : CoinsCachePair& sentinel() const { return m_sentinel; }
99 : 198 : size_t& usage() const { return cachedCoinsUsage; }
100 : : };
101 : :
102 : : } // namespace
103 : :
104 : : BOOST_FIXTURE_TEST_SUITE(coins_tests, BasicTestingSetup)
105 : :
106 : : static const unsigned int NUM_SIMULATION_ITERATIONS = 40000;
107 : :
108 : : // This is a large randomized insert/remove simulation test on a variable-size
109 : : // stack of caches on top of CCoinsViewTest.
110 : : //
111 : : // It will randomly create/update/delete Coin entries to a tip of caches, with
112 : : // txids picked from a limited list of random 256-bit hashes. Occasionally, a
113 : : // new tip is added to the stack of caches, or the tip is flushed and removed.
114 : : //
115 : : // During the process, booleans are kept to make sure that the randomized
116 : : // operation hits all branches.
117 : : //
118 : : // If fake_best_block is true, assign a random uint256 to mock the recording
119 : : // of best block on flush. This is necessary when using CCoinsViewDB as the base,
120 : : // otherwise we'll hit an assertion in BatchWrite.
121 : : //
122 : 2 : void SimulationTest(CCoinsView* base, bool fake_best_block)
123 : : {
124 : : // Various coverage trackers.
125 : 2 : bool removed_all_caches = false;
126 : 2 : bool reached_4_caches = false;
127 : 2 : bool added_an_entry = false;
128 : 2 : bool added_an_unspendable_entry = false;
129 : 2 : bool removed_an_entry = false;
130 : 2 : bool updated_an_entry = false;
131 : 2 : bool found_an_entry = false;
132 : 2 : bool missed_an_entry = false;
133 : 2 : bool uncached_an_entry = false;
134 : 2 : bool flushed_without_erase = false;
135 : :
136 : : // A simple map to track what we expect the cache stack to represent.
137 [ + - ]: 2 : std::map<COutPoint, Coin> result;
138 : :
139 : : // The cache stack.
140 : 2 : std::vector<std::unique_ptr<CCoinsViewCacheTest>> stack; // A stack of CCoinsViewCaches on top.
141 [ + - ]: 4 : stack.push_back(std::make_unique<CCoinsViewCacheTest>(base)); // Start with one cache.
142 : :
143 : : // Use a limited set of random transaction ids, so we do test overwriting entries.
144 : 2 : std::vector<Txid> txids;
145 [ + - ]: 2 : txids.resize(NUM_SIMULATION_ITERATIONS / 8);
146 [ + + ]: 10002 : for (unsigned int i = 0; i < txids.size(); i++) {
147 : 10000 : txids[i] = Txid::FromUint256(InsecureRand256());
148 : : }
149 : :
150 [ + + ]: 80002 : for (unsigned int i = 0; i < NUM_SIMULATION_ITERATIONS; i++) {
151 : : // Do a random modification.
152 : 80000 : {
153 [ + - ]: 80000 : auto txid = txids[InsecureRandRange(txids.size())]; // txid we're going to modify in this iteration.
154 [ + - ]: 80000 : Coin& coin = result[COutPoint(txid, 0)];
155 : :
156 : : // Determine whether to test HaveCoin before or after Access* (or both). As these functions
157 : : // can influence each other's behaviour by pulling things into the cache, all combinations
158 : : // are tested.
159 : 80000 : bool test_havecoin_before = InsecureRandBits(2) == 0;
160 : 80000 : bool test_havecoin_after = InsecureRandBits(2) == 0;
161 : :
162 [ + + + - ]: 80000 : bool result_havecoin = test_havecoin_before ? stack.back()->HaveCoin(COutPoint(txid, 0)) : false;
163 : :
164 : : // Infrequently, test usage of AccessByTxid instead of AccessCoin - the
165 : : // former just delegates to the latter and returns the first unspent in a txn.
166 [ + + ]: 80000 : const Coin& entry = (InsecureRandRange(500) == 0) ?
167 [ + - + - ]: 80000 : AccessByTxid(*stack.back(), txid) : stack.back()->AccessCoin(COutPoint(txid, 0));
168 [ + - + - : 160000 : BOOST_CHECK(coin == entry);
+ + ]
169 : :
170 [ + + ]: 80000 : if (test_havecoin_before) {
171 [ + - + - ]: 39994 : BOOST_CHECK(result_havecoin == !entry.IsSpent());
172 : : }
173 : :
174 [ + + ]: 80000 : if (test_havecoin_after) {
175 [ + - ]: 19800 : bool ret = stack.back()->HaveCoin(COutPoint(txid, 0));
176 [ + - + - ]: 39600 : BOOST_CHECK(ret == !entry.IsSpent());
177 : : }
178 : :
179 [ + + + + ]: 80000 : if (InsecureRandRange(5) == 0 || coin.IsSpent()) {
180 : 47983 : Coin newcoin;
181 : 47983 : newcoin.out.nValue = InsecureRandMoneyAmount();
182 : 47983 : newcoin.nHeight = 1;
183 : :
184 : : // Infrequently test adding unspendable coins.
185 [ + + + + ]: 47983 : if (InsecureRandRange(16) == 0 && coin.IsSpent()) {
186 : 2470 : newcoin.out.scriptPubKey.assign(1 + InsecureRandBits(6), OP_RETURN);
187 [ + - + - ]: 4940 : BOOST_CHECK(newcoin.out.scriptPubKey.IsUnspendable());
188 : 2470 : added_an_unspendable_entry = true;
189 : : } else {
190 : : // Random sizes so we can test memory usage accounting
191 : 45513 : newcoin.out.scriptPubKey.assign(InsecureRandBits(6), 0);
192 [ + + ]: 45513 : (coin.IsSpent() ? added_an_entry : updated_an_entry) = true;
193 : 45513 : coin = newcoin;
194 : : }
195 [ + + + + ]: 50453 : bool is_overwrite = !coin.IsSpent() || InsecureRand32() & 1;
196 [ + - ]: 47983 : stack.back()->AddCoin(COutPoint(txid, 0), std::move(newcoin), is_overwrite);
197 : 47983 : } else {
198 : : // Spend the coin.
199 : 32017 : removed_an_entry = true;
200 : 32017 : coin.Clear();
201 [ + - + - : 64034 : BOOST_CHECK(stack.back()->SpendCoin(COutPoint(txid, 0)));
+ - ]
202 : : }
203 : : }
204 : :
205 : : // Once every 10 iterations, remove a random entry from the cache
206 [ + + ]: 80000 : if (InsecureRandRange(10) == 0) {
207 : 7969 : COutPoint out(txids[InsecureRand32() % txids.size()], 0);
208 [ + - ]: 7969 : int cacheid = InsecureRand32() % stack.size();
209 [ + - ]: 7969 : stack[cacheid]->Uncache(out);
210 [ + - ]: 7969 : uncached_an_entry |= !stack[cacheid]->HaveCoinInCache(out);
211 : : }
212 : :
213 : : // Once every 1000 iterations and at the end, verify the full cache.
214 [ + + + + ]: 80000 : if (InsecureRandRange(1000) == 1 || i == NUM_SIMULATION_ITERATIONS - 1) {
215 [ + + ]: 353602 : for (const auto& entry : result) {
216 [ + - ]: 353523 : bool have = stack.back()->HaveCoin(entry.first);
217 [ + - ]: 353523 : const Coin& coin = stack.back()->AccessCoin(entry.first);
218 [ + - + - : 707046 : BOOST_CHECK(have == !coin.IsSpent());
+ - ]
219 [ + - + - : 707046 : BOOST_CHECK(coin == entry.second);
+ + ]
220 [ + + ]: 353523 : if (coin.IsSpent()) {
221 : : missed_an_entry = true;
222 : : } else {
223 [ + - + - : 402332 : BOOST_CHECK(stack.back()->HaveCoinInCache(entry.first));
+ - ]
224 : 201166 : found_an_entry = true;
225 : : }
226 : : }
227 [ + + ]: 298 : for (const auto& test : stack) {
228 [ + - ]: 219 : test->SelfTest();
229 : : }
230 : : }
231 : :
232 [ + + ]: 80000 : if (InsecureRandRange(100) == 0) {
233 : : // Every 100 iterations, flush an intermediate cache
234 [ + + + + ]: 1515 : if (stack.size() > 1 && InsecureRandBool() == 0) {
235 : 332 : unsigned int flushIndex = InsecureRandRange(stack.size() - 1);
236 [ + + + - ]: 332 : if (fake_best_block) stack[flushIndex]->SetBestBlock(InsecureRand256());
237 : 332 : bool should_erase = InsecureRandRange(4) < 3;
238 [ + - + + : 664 : BOOST_CHECK(should_erase ? stack[flushIndex]->Flush() : stack[flushIndex]->Sync());
+ - + - +
- ]
239 : 332 : flushed_without_erase |= !should_erase;
240 : : }
241 : : }
242 [ + + ]: 80000 : if (InsecureRandRange(100) == 0) {
243 : : // Every 100 iterations, change the cache stack.
244 [ + - + + ]: 1626 : if (stack.size() > 0 && InsecureRandBool() == 0) {
245 : : //Remove the top cache
246 [ + + + - ]: 395 : if (fake_best_block) stack.back()->SetBestBlock(InsecureRand256());
247 : 395 : bool should_erase = InsecureRandRange(4) < 3;
248 [ + - + + : 790 : BOOST_CHECK(should_erase ? stack.back()->Flush() : stack.back()->Sync());
+ - + - +
- ]
249 : 395 : flushed_without_erase |= !should_erase;
250 : 395 : stack.pop_back();
251 : : }
252 [ + + + + : 1427 : if (stack.size() == 0 || (stack.size() < 4 && InsecureRandBool())) {
+ + ]
253 : : //Add a new cache
254 : 398 : CCoinsView* tip = base;
255 [ + + ]: 398 : if (stack.size() > 0) {
256 : 329 : tip = stack.back().get();
257 : : } else {
258 : : removed_all_caches = true;
259 : : }
260 [ + - ]: 796 : stack.push_back(std::make_unique<CCoinsViewCacheTest>(tip));
261 [ + + ]: 398 : if (stack.size() == 4) {
262 : 115 : reached_4_caches = true;
263 : : }
264 : : }
265 : : }
266 : : }
267 : :
268 : : // Verify coverage.
269 [ + - + - : 4 : BOOST_CHECK(removed_all_caches);
+ - ]
270 [ + - + - : 4 : BOOST_CHECK(reached_4_caches);
+ - ]
271 [ + - + - : 4 : BOOST_CHECK(added_an_entry);
+ - ]
272 [ + - + - : 4 : BOOST_CHECK(added_an_unspendable_entry);
+ - ]
273 [ + - + - : 4 : BOOST_CHECK(removed_an_entry);
+ - ]
274 [ + - + - : 4 : BOOST_CHECK(updated_an_entry);
+ - ]
275 [ + - + - : 4 : BOOST_CHECK(found_an_entry);
+ - ]
276 [ + - + - : 4 : BOOST_CHECK(missed_an_entry);
+ - ]
277 [ + - + - : 4 : BOOST_CHECK(uncached_an_entry);
+ - ]
278 [ + - + - ]: 4 : BOOST_CHECK(flushed_without_erase);
279 : 2 : }
280 : :
281 : : // Run the above simulation for multiple base types.
282 [ + - + - : 7 : BOOST_AUTO_TEST_CASE(coins_cache_simulation_test)
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- ]
283 : : {
284 [ + - ]: 1 : CCoinsViewTest base;
285 [ + - ]: 1 : SimulationTest(&base, false);
286 : :
287 : 0 : CCoinsViewDB db_base{{.path = "test", .cache_bytes = 1 << 23, .memory_only = true}, {}};
288 [ + - ]: 1 : SimulationTest(&db_base, true);
289 [ + - + - ]: 2 : }
290 : :
291 : : // Store of all necessary tx and undo data for next test
292 : : typedef std::map<COutPoint, std::tuple<CTransaction,CTxUndo,Coin>> UtxoData;
293 : : UtxoData utxoData;
294 : :
295 : 40257 : UtxoData::iterator FindRandomFrom(const std::set<COutPoint> &utxoSet) {
296 [ - + ]: 40257 : assert(utxoSet.size());
297 : 40257 : auto utxoSetIt = utxoSet.lower_bound(COutPoint(Txid::FromUint256(InsecureRand256()), 0));
298 [ + + ]: 40257 : if (utxoSetIt == utxoSet.end()) {
299 : 382 : utxoSetIt = utxoSet.begin();
300 : : }
301 : 40257 : auto utxoDataIt = utxoData.find(*utxoSetIt);
302 [ - + ]: 40257 : assert(utxoDataIt != utxoData.end());
303 : 40257 : return utxoDataIt;
304 : : }
305 : :
306 : :
307 : : // This test is similar to the previous test
308 : : // except the emphasis is on testing the functionality of UpdateCoins
309 : : // random txs are created and UpdateCoins is used to update the cache stack
310 : : // In particular it is tested that spending a duplicate coinbase tx
311 : : // has the expected effect (the other duplicate is overwritten at all cache levels)
312 [ + - + - : 7 : BOOST_AUTO_TEST_CASE(updatecoins_simulation_test)
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- ]
313 : : {
314 : 1 : SeedRandomForTest(SeedRand::ZEROS);
315 : :
316 : 1 : bool spent_a_duplicate_coinbase = false;
317 : : // A simple map to track what we expect the cache stack to represent.
318 [ + - ]: 1 : std::map<COutPoint, Coin> result;
319 : :
320 : : // The cache stack.
321 [ + - ]: 1 : CCoinsViewTest base; // A CCoinsViewTest at the bottom.
322 : 1 : std::vector<std::unique_ptr<CCoinsViewCacheTest>> stack; // A stack of CCoinsViewCaches on top.
323 [ + - ]: 2 : stack.push_back(std::make_unique<CCoinsViewCacheTest>(&base)); // Start with one cache.
324 : :
325 : : // Track the txids we've used in various sets
326 : 1 : std::set<COutPoint> coinbase_coins;
327 : 1 : std::set<COutPoint> disconnected_coins;
328 : 1 : std::set<COutPoint> duplicate_coins;
329 : 1 : std::set<COutPoint> utxoset;
330 : :
331 [ + + ]: 40001 : for (unsigned int i = 0; i < NUM_SIMULATION_ITERATIONS; i++) {
332 : 40000 : uint32_t randiter = InsecureRand32();
333 : :
334 : : // 19/20 txs add a new transaction
335 [ + + ]: 40000 : if (randiter % 20 < 19) {
336 [ + - ]: 37952 : CMutableTransaction tx;
337 [ + - ]: 37952 : tx.vin.resize(1);
338 [ + - ]: 37952 : tx.vout.resize(1);
339 : 37952 : tx.vout[0].nValue = i; //Keep txs unique unless intended to duplicate
340 : 37952 : tx.vout[0].scriptPubKey.assign(InsecureRand32() & 0x3F, 0); // Random sizes so we can test memory usage accounting
341 : 37952 : const int height{int(InsecureRand32() >> 1)};
342 : 37952 : Coin old_coin;
343 : :
344 : : // 2/20 times create a new coinbase
345 [ + + + + ]: 37952 : if (randiter % 20 < 2 || coinbase_coins.size() < 10) {
346 : : // 1/10 of those times create a duplicate coinbase
347 [ + + - + ]: 4049 : if (InsecureRandRange(10) == 0 && coinbase_coins.size()) {
348 : 380 : auto utxod = FindRandomFrom(coinbase_coins);
349 : : // Reuse the exact same coinbase
350 [ + - ]: 380 : tx = CMutableTransaction{std::get<0>(utxod->second)};
351 : : // shouldn't be available for reconnection if it's been duplicated
352 : 380 : disconnected_coins.erase(utxod->first);
353 : :
354 [ + - ]: 380 : duplicate_coins.insert(utxod->first);
355 : : }
356 : : else {
357 [ + - + - ]: 3669 : coinbase_coins.insert(COutPoint(tx.GetHash(), 0));
358 : : }
359 [ + - - + ]: 8098 : assert(CTransaction(tx).IsCoinBase());
360 : : }
361 : :
362 : : // 17/20 times reconnect previous or add a regular tx
363 : : else {
364 : :
365 [ + + ]: 33903 : COutPoint prevout;
366 : : // 1/20 times reconnect a previously disconnected tx
367 [ + + + + ]: 33903 : if (randiter % 20 == 2 && disconnected_coins.size()) {
368 : 2018 : auto utxod = FindRandomFrom(disconnected_coins);
369 [ + - ]: 2018 : tx = CMutableTransaction{std::get<0>(utxod->second)};
370 [ + - ]: 2018 : prevout = tx.vin[0].prevout;
371 [ + - + + : 4036 : if (!CTransaction(tx).IsCoinBase() && !utxoset.count(prevout)) {
+ + + + ]
372 : 581 : disconnected_coins.erase(utxod->first);
373 : 581 : continue;
374 : : }
375 : :
376 : : // If this tx is already IN the UTXO, then it must be a coinbase, and it must be a duplicate
377 [ - + ]: 1437 : if (utxoset.count(utxod->first)) {
378 [ # # # # ]: 0 : assert(CTransaction(tx).IsCoinBase());
379 [ # # ]: 0 : assert(duplicate_coins.count(utxod->first));
380 : : }
381 : 1437 : disconnected_coins.erase(utxod->first);
382 : : }
383 : :
384 : : // 16/20 times create a regular tx
385 : : else {
386 : 31885 : auto utxod = FindRandomFrom(utxoset);
387 [ + - ]: 31885 : prevout = utxod->first;
388 : :
389 : : // Construct the tx to spend the coins of prevouthash
390 [ + - ]: 31885 : tx.vin[0].prevout = prevout;
391 [ + - - + ]: 63770 : assert(!CTransaction(tx).IsCoinBase());
392 : : }
393 : : // In this simple test coins only have two states, spent or unspent, save the unspent state to restore
394 [ + - ]: 33322 : old_coin = result[prevout];
395 : : // Update the expected result of prevouthash to know these coins are spent
396 [ + - ]: 33322 : result[prevout].Clear();
397 : :
398 : 33322 : utxoset.erase(prevout);
399 : :
400 : : // The test is designed to ensure spending a duplicate coinbase will work properly
401 : : // if that ever happens and not resurrect the previously overwritten coinbase
402 [ + + ]: 33322 : if (duplicate_coins.count(prevout)) {
403 : 346 : spent_a_duplicate_coinbase = true;
404 : : }
405 : :
406 : : }
407 : : // Update the expected result to know about the new output coins
408 [ - + ]: 37371 : assert(tx.vout.size() == 1);
409 [ + - ]: 37371 : const COutPoint outpoint(tx.GetHash(), 0);
410 [ + - + - ]: 37371 : result[outpoint] = Coin{tx.vout[0], height, CTransaction{tx}.IsCoinBase()};
411 : :
412 : : // Call UpdateCoins on the top cache
413 : 37371 : CTxUndo undo;
414 [ + - + - ]: 37371 : UpdateCoins(CTransaction{tx}, *(stack.back()), undo, height);
415 : :
416 : : // Update the utxo set for future spends
417 [ + - ]: 37371 : utxoset.insert(outpoint);
418 : :
419 : : // Track this tx and undo info to use later
420 [ + - + - ]: 112113 : utxoData.emplace(outpoint, std::make_tuple(tx,undo,old_coin));
421 [ + - ]: 77952 : } else if (utxoset.size()) {
422 : : //1/20 times undo a previous transaction
423 : 2048 : auto utxod = FindRandomFrom(utxoset);
424 : :
425 [ + - ]: 2048 : CTransaction &tx = std::get<0>(utxod->second);
426 : 2048 : CTxUndo &undo = std::get<1>(utxod->second);
427 [ + - ]: 2048 : Coin &orig_coin = std::get<2>(utxod->second);
428 : :
429 : : // Update the expected result
430 : : // Remove new outputs
431 [ + - ]: 2048 : result[utxod->first].Clear();
432 : : // If not coinbase restore prevout
433 [ + + ]: 2048 : if (!tx.IsCoinBase()) {
434 [ + - ]: 1810 : result[tx.vin[0].prevout] = orig_coin;
435 : : }
436 : :
437 : : // Disconnect the tx from the current UTXO
438 : : // See code in DisconnectBlock
439 : : // remove outputs
440 [ + - + - : 4096 : BOOST_CHECK(stack.back()->SpendCoin(utxod->first));
+ - + + ]
441 : : // restore inputs
442 [ + + ]: 2048 : if (!tx.IsCoinBase()) {
443 : 1810 : const COutPoint &out = tx.vin[0].prevout;
444 : 1810 : Coin coin = undo.vprevout[0];
445 [ + - ]: 1810 : ApplyTxInUndo(std::move(coin), *(stack.back()), out);
446 : 1810 : }
447 : : // Store as a candidate for reconnection
448 [ + - ]: 2048 : disconnected_coins.insert(utxod->first);
449 : :
450 : : // Update the utxoset
451 : 2048 : utxoset.erase(utxod->first);
452 [ + + ]: 2048 : if (!tx.IsCoinBase())
453 [ + - ]: 1810 : utxoset.insert(tx.vin[0].prevout);
454 : : }
455 : :
456 : : // Once every 1000 iterations and at the end, verify the full cache.
457 [ + + + + ]: 39419 : if (InsecureRandRange(1000) == 1 || i == NUM_SIMULATION_ITERATIONS - 1) {
458 [ + + ]: 662643 : for (const auto& entry : result) {
459 [ + - ]: 662605 : bool have = stack.back()->HaveCoin(entry.first);
460 [ + - ]: 662605 : const Coin& coin = stack.back()->AccessCoin(entry.first);
461 [ + - + - : 1325210 : BOOST_CHECK(have == !coin.IsSpent());
+ - ]
462 [ + - + - ]: 1325210 : BOOST_CHECK(coin == entry.second);
463 : : }
464 : : }
465 : :
466 : : // One every 10 iterations, remove a random entry from the cache
467 [ + + + + ]: 78837 : if (utxoset.size() > 1 && InsecureRandRange(30) == 0) {
468 [ + - ]: 1351 : stack[InsecureRand32() % stack.size()]->Uncache(FindRandomFrom(utxoset)->first);
469 : : }
470 [ + + + + ]: 77575 : if (disconnected_coins.size() > 1 && InsecureRandRange(30) == 0) {
471 [ + - ]: 1231 : stack[InsecureRand32() % stack.size()]->Uncache(FindRandomFrom(disconnected_coins)->first);
472 : : }
473 [ + + + + ]: 78678 : if (duplicate_coins.size() > 1 && InsecureRandRange(30) == 0) {
474 [ + - ]: 1344 : stack[InsecureRand32() % stack.size()]->Uncache(FindRandomFrom(duplicate_coins)->first);
475 : : }
476 : :
477 [ + + ]: 39419 : if (InsecureRandRange(100) == 0) {
478 : : // Every 100 iterations, flush an intermediate cache
479 [ + + + + ]: 694 : if (stack.size() > 1 && InsecureRandBool() == 0) {
480 : 173 : unsigned int flushIndex = InsecureRandRange(stack.size() - 1);
481 [ + - + - : 346 : BOOST_CHECK(stack[flushIndex]->Flush());
+ - ]
482 : : }
483 : : }
484 [ + + ]: 39419 : if (InsecureRandRange(100) == 0) {
485 : : // Every 100 iterations, change the cache stack.
486 [ + - + + ]: 796 : if (stack.size() > 0 && InsecureRandBool() == 0) {
487 [ + - + - : 390 : BOOST_CHECK(stack.back()->Flush());
+ - ]
488 : 195 : stack.pop_back();
489 : : }
490 [ + + + + : 728 : if (stack.size() == 0 || (stack.size() < 4 && InsecureRandBool())) {
+ + ]
491 : 196 : CCoinsView* tip = &base;
492 [ + + ]: 196 : if (stack.size() > 0) {
493 : 165 : tip = stack.back().get();
494 : : }
495 [ + - ]: 392 : stack.push_back(std::make_unique<CCoinsViewCacheTest>(tip));
496 : : }
497 : : }
498 : : }
499 : :
500 : : // Verify coverage.
501 [ + - + - ]: 2 : BOOST_CHECK(spent_a_duplicate_coinbase);
502 : 1 : }
503 : :
504 [ + - + - : 7 : BOOST_AUTO_TEST_CASE(ccoins_serialization)
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- ]
505 : : {
506 : : // Good example
507 [ + - ]: 1 : DataStream ss1{ParseHex("97f23c835800816115944e077fe7c803cfa57f29b36bf87c1d35")};
508 : 1 : Coin cc1;
509 [ + - ]: 1 : ss1 >> cc1;
510 [ + - + - ]: 1 : BOOST_CHECK_EQUAL(cc1.fCoinBase, false);
511 [ + - + - ]: 1 : BOOST_CHECK_EQUAL(cc1.nHeight, 203998U);
512 [ + - + - ]: 1 : BOOST_CHECK_EQUAL(cc1.out.nValue, CAmount{60000000000});
513 [ + - + - : 3 : BOOST_CHECK_EQUAL(HexStr(cc1.out.scriptPubKey), HexStr(GetScriptForDestination(PKHash(uint160(ParseHex("816115944e077fe7c803cfa57f29b36bf87c1d35"))))));
+ - + - +
- + - ]
514 : :
515 : : // Good example
516 [ + - + - ]: 1 : DataStream ss2{ParseHex("8ddf77bbd123008c988f1a4a4de2161e0f50aac7f17e7f9555caa4")};
517 : 1 : Coin cc2;
518 [ + - ]: 1 : ss2 >> cc2;
519 [ + - + - ]: 1 : BOOST_CHECK_EQUAL(cc2.fCoinBase, true);
520 [ + - + - ]: 1 : BOOST_CHECK_EQUAL(cc2.nHeight, 120891U);
521 [ + - + - ]: 1 : BOOST_CHECK_EQUAL(cc2.out.nValue, 110397);
522 [ + - + - : 3 : BOOST_CHECK_EQUAL(HexStr(cc2.out.scriptPubKey), HexStr(GetScriptForDestination(PKHash(uint160(ParseHex("8c988f1a4a4de2161e0f50aac7f17e7f9555caa4"))))));
+ - + - +
- + - ]
523 : :
524 : : // Smallest possible example
525 [ + - + - ]: 1 : DataStream ss3{ParseHex("000006")};
526 : 1 : Coin cc3;
527 [ + - ]: 1 : ss3 >> cc3;
528 [ + - + - ]: 1 : BOOST_CHECK_EQUAL(cc3.fCoinBase, false);
529 [ + - + - ]: 1 : BOOST_CHECK_EQUAL(cc3.nHeight, 0U);
530 [ + - + - ]: 1 : BOOST_CHECK_EQUAL(cc3.out.nValue, 0);
531 [ + - - + : 1 : BOOST_CHECK_EQUAL(cc3.out.scriptPubKey.size(), 0U);
+ - ]
532 : :
533 : : // scriptPubKey that ends beyond the end of the stream
534 [ + - + - ]: 1 : DataStream ss4{ParseHex("000007")};
535 : 1 : try {
536 : 1 : Coin cc4;
537 [ - + ]: 1 : ss4 >> cc4;
538 [ # # # # ]: 0 : BOOST_CHECK_MESSAGE(false, "We should have thrown");
539 [ - + ]: 1 : } catch (const std::ios_base::failure&) {
540 : 1 : }
541 : :
542 : : // Very large scriptPubKey (3*10^9 bytes) past the end of the stream
543 : 1 : DataStream tmp{};
544 : 1 : uint64_t x = 3000000000ULL;
545 [ + - ]: 1 : tmp << VARINT(x);
546 [ + - + - : 1 : BOOST_CHECK_EQUAL(HexStr(tmp), "8a95c0bb00");
+ - ]
547 [ + - + - ]: 1 : DataStream ss5{ParseHex("00008a95c0bb00")};
548 : 1 : try {
549 : 1 : Coin cc5;
550 [ - + ]: 1 : ss5 >> cc5;
551 [ # # # # ]: 0 : BOOST_CHECK_MESSAGE(false, "We should have thrown");
552 [ - + ]: 1 : } catch (const std::ios_base::failure&) {
553 : 1 : }
554 : 1 : }
555 : :
556 : : const static COutPoint OUTPOINT;
557 : : const static CAmount SPENT = -1;
558 : : const static CAmount ABSENT = -2;
559 : : const static CAmount FAIL = -3;
560 : : const static CAmount VALUE1 = 100;
561 : : const static CAmount VALUE2 = 200;
562 : : const static CAmount VALUE3 = 300;
563 : : const static char DIRTY = CCoinsCacheEntry::DIRTY;
564 : : const static char FRESH = CCoinsCacheEntry::FRESH;
565 : : const static char NO_ENTRY = -1;
566 : :
567 : : const static auto FLAGS = {char(0), FRESH, DIRTY, char(DIRTY | FRESH)};
568 : : const static auto CLEAN_FLAGS = {char(0), FRESH};
569 : : const static auto ABSENT_FLAGS = {NO_ENTRY};
570 : :
571 : 320 : static void SetCoinsValue(CAmount value, Coin& coin)
572 : : {
573 [ - + ]: 320 : assert(value != ABSENT);
574 : 320 : coin.Clear();
575 [ - + ]: 320 : assert(coin.IsSpent());
576 [ + + ]: 320 : if (value != SPENT) {
577 : 160 : coin.out.nValue = value;
578 : 160 : coin.nHeight = 1;
579 : 160 : assert(!coin.IsSpent());
580 : : }
581 : 320 : }
582 : :
583 : 486 : static size_t InsertCoinsMapEntry(CCoinsMap& map, CoinsCachePair& sentinel, CAmount value, char flags)
584 : : {
585 [ + + ]: 486 : if (value == ABSENT) {
586 [ - + ]: 166 : assert(flags == NO_ENTRY);
587 : : return 0;
588 : : }
589 [ - + ]: 320 : assert(flags != NO_ENTRY);
590 : 320 : CCoinsCacheEntry entry;
591 : 320 : SetCoinsValue(value, entry.coin);
592 [ + - ]: 320 : auto inserted = map.emplace(OUTPOINT, std::move(entry));
593 [ - + ]: 320 : assert(inserted.second);
594 : 320 : inserted.first->second.AddFlags(flags, *inserted.first, sentinel);
595 [ - + ]: 320 : return inserted.first->second.coin.DynamicMemoryUsage();
596 : 320 : }
597 : :
598 : 202 : void GetCoinsMapEntry(const CCoinsMap& map, CAmount& value, char& flags, const COutPoint& outp = OUTPOINT)
599 : : {
600 : 202 : auto it = map.find(outp);
601 [ + + ]: 202 : if (it == map.end()) {
602 : 35 : value = ABSENT;
603 : 35 : flags = NO_ENTRY;
604 : : } else {
605 [ + + ]: 167 : if (it->second.coin.IsSpent()) {
606 : 60 : value = SPENT;
607 : : } else {
608 : 107 : value = it->second.coin.out.nValue;
609 : : }
610 [ - + ]: 167 : flags = it->second.GetFlags();
611 [ - + ]: 167 : assert(flags != NO_ENTRY);
612 : : }
613 : 202 : }
614 : :
615 : 288 : void WriteCoinsViewEntry(CCoinsView& view, CAmount value, char flags)
616 : : {
617 : 288 : CoinsCachePair sentinel{};
618 [ + - ]: 288 : sentinel.second.SelfRef(sentinel);
619 [ + - ]: 288 : CCoinsMapMemoryResource resource;
620 [ + - + - ]: 288 : CCoinsMap map{0, CCoinsMap::hasher{}, CCoinsMap::key_equal{}, &resource};
621 [ + - ]: 288 : auto usage{InsertCoinsMapEntry(map, sentinel, value, flags)};
622 [ + - ]: 288 : auto cursor{CoinsViewCacheCursor(usage, sentinel, map, /*will_erase=*/true)};
623 [ + - + + : 568 : BOOST_CHECK(view.BatchWrite(cursor, {}));
+ - ]
624 : 288 : }
625 : :
626 : : class SingleEntryCacheTest
627 : : {
628 : : public:
629 : 198 : SingleEntryCacheTest(CAmount base_value, CAmount cache_value, char cache_flags)
630 [ + - + - ]: 198 : {
631 [ + + + - ]: 270 : WriteCoinsViewEntry(base, base_value, base_value == ABSENT ? NO_ENTRY : DIRTY);
632 [ + - ]: 198 : cache.usage() += InsertCoinsMapEntry(cache.map(), cache.sentinel(), cache_value, cache_flags);
633 : 198 : }
634 : :
635 : : CCoinsView root;
636 : : CCoinsViewCacheTest base{&root};
637 : : CCoinsViewCacheTest cache{&base};
638 : : };
639 : :
640 : 27 : static void CheckAccessCoin(CAmount base_value, CAmount cache_value, CAmount expected_value, char cache_flags, char expected_flags)
641 : : {
642 : 27 : SingleEntryCacheTest test(base_value, cache_value, cache_flags);
643 [ + - ]: 27 : test.cache.AccessCoin(OUTPOINT);
644 [ + - ]: 27 : test.cache.SelfTest(/*sanity_check=*/false);
645 : :
646 : 27 : CAmount result_value;
647 : 27 : char result_flags;
648 : 27 : GetCoinsMapEntry(test.cache.map(), result_value, result_flags);
649 [ + - + - ]: 27 : BOOST_CHECK_EQUAL(result_value, expected_value);
650 [ + - + - ]: 27 : BOOST_CHECK_EQUAL(result_flags, expected_flags);
651 : 27 : }
652 : :
653 [ + - + - : 7 : BOOST_AUTO_TEST_CASE(ccoins_access)
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- ]
654 : : {
655 : : /* Check AccessCoin behavior, requesting a coin from a cache view layered on
656 : : * top of a base view, and checking the resulting entry in the cache after
657 : : * the access.
658 : : *
659 : : * Base Cache Result Cache Result
660 : : * Value Value Value Flags Flags
661 : : */
662 : 1 : CheckAccessCoin(ABSENT, ABSENT, ABSENT, NO_ENTRY , NO_ENTRY );
663 : 1 : CheckAccessCoin(ABSENT, SPENT , SPENT , 0 , 0 );
664 : 1 : CheckAccessCoin(ABSENT, SPENT , SPENT , FRESH , FRESH );
665 : 1 : CheckAccessCoin(ABSENT, SPENT , SPENT , DIRTY , DIRTY );
666 : 1 : CheckAccessCoin(ABSENT, SPENT , SPENT , DIRTY|FRESH, DIRTY|FRESH);
667 : 1 : CheckAccessCoin(ABSENT, VALUE2, VALUE2, 0 , 0 );
668 : 1 : CheckAccessCoin(ABSENT, VALUE2, VALUE2, FRESH , FRESH );
669 : 1 : CheckAccessCoin(ABSENT, VALUE2, VALUE2, DIRTY , DIRTY );
670 : 1 : CheckAccessCoin(ABSENT, VALUE2, VALUE2, DIRTY|FRESH, DIRTY|FRESH);
671 : 1 : CheckAccessCoin(SPENT , ABSENT, ABSENT, NO_ENTRY , NO_ENTRY );
672 : 1 : CheckAccessCoin(SPENT , SPENT , SPENT , 0 , 0 );
673 : 1 : CheckAccessCoin(SPENT , SPENT , SPENT , FRESH , FRESH );
674 : 1 : CheckAccessCoin(SPENT , SPENT , SPENT , DIRTY , DIRTY );
675 : 1 : CheckAccessCoin(SPENT , SPENT , SPENT , DIRTY|FRESH, DIRTY|FRESH);
676 : 1 : CheckAccessCoin(SPENT , VALUE2, VALUE2, 0 , 0 );
677 : 1 : CheckAccessCoin(SPENT , VALUE2, VALUE2, FRESH , FRESH );
678 : 1 : CheckAccessCoin(SPENT , VALUE2, VALUE2, DIRTY , DIRTY );
679 : 1 : CheckAccessCoin(SPENT , VALUE2, VALUE2, DIRTY|FRESH, DIRTY|FRESH);
680 : 1 : CheckAccessCoin(VALUE1, ABSENT, VALUE1, NO_ENTRY , 0 );
681 : 1 : CheckAccessCoin(VALUE1, SPENT , SPENT , 0 , 0 );
682 : 1 : CheckAccessCoin(VALUE1, SPENT , SPENT , FRESH , FRESH );
683 : 1 : CheckAccessCoin(VALUE1, SPENT , SPENT , DIRTY , DIRTY );
684 : 1 : CheckAccessCoin(VALUE1, SPENT , SPENT , DIRTY|FRESH, DIRTY|FRESH);
685 : 1 : CheckAccessCoin(VALUE1, VALUE2, VALUE2, 0 , 0 );
686 : 1 : CheckAccessCoin(VALUE1, VALUE2, VALUE2, FRESH , FRESH );
687 : 1 : CheckAccessCoin(VALUE1, VALUE2, VALUE2, DIRTY , DIRTY );
688 : 1 : CheckAccessCoin(VALUE1, VALUE2, VALUE2, DIRTY|FRESH, DIRTY|FRESH);
689 : 1 : }
690 : :
691 : 27 : static void CheckSpendCoins(CAmount base_value, CAmount cache_value, CAmount expected_value, char cache_flags, char expected_flags)
692 : : {
693 : 27 : SingleEntryCacheTest test(base_value, cache_value, cache_flags);
694 [ + - ]: 27 : test.cache.SpendCoin(OUTPOINT);
695 [ + - ]: 27 : test.cache.SelfTest();
696 : :
697 : 27 : CAmount result_value;
698 : 27 : char result_flags;
699 : 27 : GetCoinsMapEntry(test.cache.map(), result_value, result_flags);
700 [ + - + - ]: 27 : BOOST_CHECK_EQUAL(result_value, expected_value);
701 [ + - + - ]: 27 : BOOST_CHECK_EQUAL(result_flags, expected_flags);
702 : 27 : };
703 : :
704 [ + - + - : 7 : BOOST_AUTO_TEST_CASE(ccoins_spend)
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- ]
705 : : {
706 : : /* Check SpendCoin behavior, requesting a coin from a cache view layered on
707 : : * top of a base view, spending, and then checking
708 : : * the resulting entry in the cache after the modification.
709 : : *
710 : : * Base Cache Result Cache Result
711 : : * Value Value Value Flags Flags
712 : : */
713 : 1 : CheckSpendCoins(ABSENT, ABSENT, ABSENT, NO_ENTRY , NO_ENTRY );
714 : 1 : CheckSpendCoins(ABSENT, SPENT , SPENT , 0 , DIRTY );
715 : 1 : CheckSpendCoins(ABSENT, SPENT , ABSENT, FRESH , NO_ENTRY );
716 : 1 : CheckSpendCoins(ABSENT, SPENT , SPENT , DIRTY , DIRTY );
717 : 1 : CheckSpendCoins(ABSENT, SPENT , ABSENT, DIRTY|FRESH, NO_ENTRY );
718 : 1 : CheckSpendCoins(ABSENT, VALUE2, SPENT , 0 , DIRTY );
719 : 1 : CheckSpendCoins(ABSENT, VALUE2, ABSENT, FRESH , NO_ENTRY );
720 : 1 : CheckSpendCoins(ABSENT, VALUE2, SPENT , DIRTY , DIRTY );
721 : 1 : CheckSpendCoins(ABSENT, VALUE2, ABSENT, DIRTY|FRESH, NO_ENTRY );
722 : 1 : CheckSpendCoins(SPENT , ABSENT, ABSENT, NO_ENTRY , NO_ENTRY );
723 : 1 : CheckSpendCoins(SPENT , SPENT , SPENT , 0 , DIRTY );
724 : 1 : CheckSpendCoins(SPENT , SPENT , ABSENT, FRESH , NO_ENTRY );
725 : 1 : CheckSpendCoins(SPENT , SPENT , SPENT , DIRTY , DIRTY );
726 : 1 : CheckSpendCoins(SPENT , SPENT , ABSENT, DIRTY|FRESH, NO_ENTRY );
727 : 1 : CheckSpendCoins(SPENT , VALUE2, SPENT , 0 , DIRTY );
728 : 1 : CheckSpendCoins(SPENT , VALUE2, ABSENT, FRESH , NO_ENTRY );
729 : 1 : CheckSpendCoins(SPENT , VALUE2, SPENT , DIRTY , DIRTY );
730 : 1 : CheckSpendCoins(SPENT , VALUE2, ABSENT, DIRTY|FRESH, NO_ENTRY );
731 : 1 : CheckSpendCoins(VALUE1, ABSENT, SPENT , NO_ENTRY , DIRTY );
732 : 1 : CheckSpendCoins(VALUE1, SPENT , SPENT , 0 , DIRTY );
733 : 1 : CheckSpendCoins(VALUE1, SPENT , ABSENT, FRESH , NO_ENTRY );
734 : 1 : CheckSpendCoins(VALUE1, SPENT , SPENT , DIRTY , DIRTY );
735 : 1 : CheckSpendCoins(VALUE1, SPENT , ABSENT, DIRTY|FRESH, NO_ENTRY );
736 : 1 : CheckSpendCoins(VALUE1, VALUE2, SPENT , 0 , DIRTY );
737 : 1 : CheckSpendCoins(VALUE1, VALUE2, ABSENT, FRESH , NO_ENTRY );
738 : 1 : CheckSpendCoins(VALUE1, VALUE2, SPENT , DIRTY , DIRTY );
739 : 1 : CheckSpendCoins(VALUE1, VALUE2, ABSENT, DIRTY|FRESH, NO_ENTRY );
740 : 1 : }
741 : :
742 : 54 : static void CheckAddCoinBase(CAmount base_value, CAmount cache_value, CAmount modify_value, CAmount expected_value, char cache_flags, char expected_flags, bool coinbase)
743 : : {
744 : 54 : SingleEntryCacheTest test(base_value, cache_value, cache_flags);
745 : :
746 : 54 : CAmount result_value;
747 : 54 : char result_flags;
748 : 54 : try {
749 : 54 : CTxOut output;
750 : 54 : output.nValue = modify_value;
751 [ + + ]: 66 : test.cache.AddCoin(OUTPOINT, Coin(std::move(output), 1, coinbase), coinbase);
752 [ + - ]: 42 : test.cache.SelfTest();
753 : 42 : GetCoinsMapEntry(test.cache.map(), result_value, result_flags);
754 [ - + ]: 54 : } catch (std::logic_error&) {
755 : 12 : result_value = FAIL;
756 : 12 : result_flags = NO_ENTRY;
757 : 12 : }
758 : :
759 [ + - + - ]: 54 : BOOST_CHECK_EQUAL(result_value, expected_value);
760 [ + - + - ]: 54 : BOOST_CHECK_EQUAL(result_flags, expected_flags);
761 : 54 : }
762 : :
763 : : // Simple wrapper for CheckAddCoinBase function above that loops through
764 : : // different possible base_values, making sure each one gives the same results.
765 : : // This wrapper lets the coins_add test below be shorter and less repetitive,
766 : : // while still verifying that the CoinsViewCache::AddCoin implementation
767 : : // ignores base values.
768 : : template <typename... Args>
769 : 18 : static void CheckAddCoin(Args&&... args)
770 : : {
771 [ + + ]: 72 : for (const CAmount base_value : {ABSENT, SPENT, VALUE1})
772 : 54 : CheckAddCoinBase(base_value, std::forward<Args>(args)...);
773 : 18 : }
774 : :
775 [ + - + - : 7 : BOOST_AUTO_TEST_CASE(ccoins_add)
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- ]
776 : : {
777 : : /* Check AddCoin behavior, requesting a new coin from a cache view,
778 : : * writing a modification to the coin, and then checking the resulting
779 : : * entry in the cache after the modification. Verify behavior with the
780 : : * AddCoin possible_overwrite argument set to false, and to true.
781 : : *
782 : : * Cache Write Result Cache Result possible_overwrite
783 : : * Value Value Value Flags Flags
784 : : */
785 : 1 : CheckAddCoin(ABSENT, VALUE3, VALUE3, NO_ENTRY , DIRTY|FRESH, false);
786 : 1 : CheckAddCoin(ABSENT, VALUE3, VALUE3, NO_ENTRY , DIRTY , true );
787 : 1 : CheckAddCoin(SPENT , VALUE3, VALUE3, 0 , DIRTY|FRESH, false);
788 : 1 : CheckAddCoin(SPENT , VALUE3, VALUE3, 0 , DIRTY , true );
789 : 1 : CheckAddCoin(SPENT , VALUE3, VALUE3, FRESH , DIRTY|FRESH, false);
790 : 1 : CheckAddCoin(SPENT , VALUE3, VALUE3, FRESH , DIRTY|FRESH, true );
791 : 1 : CheckAddCoin(SPENT , VALUE3, VALUE3, DIRTY , DIRTY , false);
792 : 1 : CheckAddCoin(SPENT , VALUE3, VALUE3, DIRTY , DIRTY , true );
793 : 1 : CheckAddCoin(SPENT , VALUE3, VALUE3, DIRTY|FRESH, DIRTY|FRESH, false);
794 : 1 : CheckAddCoin(SPENT , VALUE3, VALUE3, DIRTY|FRESH, DIRTY|FRESH, true );
795 : 1 : CheckAddCoin(VALUE2, VALUE3, FAIL , 0 , NO_ENTRY , false);
796 : 1 : CheckAddCoin(VALUE2, VALUE3, VALUE3, 0 , DIRTY , true );
797 : 1 : CheckAddCoin(VALUE2, VALUE3, FAIL , FRESH , NO_ENTRY , false);
798 : 1 : CheckAddCoin(VALUE2, VALUE3, VALUE3, FRESH , DIRTY|FRESH, true );
799 : 1 : CheckAddCoin(VALUE2, VALUE3, FAIL , DIRTY , NO_ENTRY , false);
800 : 1 : CheckAddCoin(VALUE2, VALUE3, VALUE3, DIRTY , DIRTY , true );
801 : 1 : CheckAddCoin(VALUE2, VALUE3, FAIL , DIRTY|FRESH, NO_ENTRY , false);
802 : 1 : CheckAddCoin(VALUE2, VALUE3, VALUE3, DIRTY|FRESH, DIRTY|FRESH, true );
803 : 1 : }
804 : :
805 : 90 : void CheckWriteCoins(CAmount parent_value, CAmount child_value, CAmount expected_value, char parent_flags, char child_flags, char expected_flags)
806 : : {
807 : 90 : SingleEntryCacheTest test(ABSENT, parent_value, parent_flags);
808 : :
809 : 90 : CAmount result_value;
810 : 90 : char result_flags;
811 : 90 : try {
812 [ + + ]: 90 : WriteCoinsViewEntry(test.cache, child_value, child_flags);
813 [ + - ]: 82 : test.cache.SelfTest(/*sanity_check=*/false);
814 : 82 : GetCoinsMapEntry(test.cache.map(), result_value, result_flags);
815 [ - + ]: 8 : } catch (std::logic_error&) {
816 : 8 : result_value = FAIL;
817 : 8 : result_flags = NO_ENTRY;
818 : 8 : }
819 : :
820 [ + - + - ]: 90 : BOOST_CHECK_EQUAL(result_value, expected_value);
821 [ + - + - ]: 90 : BOOST_CHECK_EQUAL(result_flags, expected_flags);
822 : 90 : }
823 : :
824 [ + - + - : 7 : BOOST_AUTO_TEST_CASE(ccoins_write)
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- ]
825 : : {
826 : : /* Check BatchWrite behavior, flushing one entry from a child cache to a
827 : : * parent cache, and checking the resulting entry in the parent cache
828 : : * after the write.
829 : : *
830 : : * Parent Child Result Parent Child Result
831 : : * Value Value Value Flags Flags Flags
832 : : */
833 : 1 : CheckWriteCoins(ABSENT, ABSENT, ABSENT, NO_ENTRY , NO_ENTRY , NO_ENTRY );
834 : 1 : CheckWriteCoins(ABSENT, SPENT , SPENT , NO_ENTRY , DIRTY , DIRTY );
835 : 1 : CheckWriteCoins(ABSENT, SPENT , ABSENT, NO_ENTRY , DIRTY|FRESH, NO_ENTRY );
836 : 1 : CheckWriteCoins(ABSENT, VALUE2, VALUE2, NO_ENTRY , DIRTY , DIRTY );
837 : 1 : CheckWriteCoins(ABSENT, VALUE2, VALUE2, NO_ENTRY , DIRTY|FRESH, DIRTY|FRESH);
838 : 1 : CheckWriteCoins(SPENT , ABSENT, SPENT , 0 , NO_ENTRY , 0 );
839 : 1 : CheckWriteCoins(SPENT , ABSENT, SPENT , FRESH , NO_ENTRY , FRESH );
840 : 1 : CheckWriteCoins(SPENT , ABSENT, SPENT , DIRTY , NO_ENTRY , DIRTY );
841 : 1 : CheckWriteCoins(SPENT , ABSENT, SPENT , DIRTY|FRESH, NO_ENTRY , DIRTY|FRESH);
842 : 1 : CheckWriteCoins(SPENT , SPENT , SPENT , 0 , DIRTY , DIRTY );
843 : 1 : CheckWriteCoins(SPENT , SPENT , SPENT , 0 , DIRTY|FRESH, DIRTY );
844 : 1 : CheckWriteCoins(SPENT , SPENT , ABSENT, FRESH , DIRTY , NO_ENTRY );
845 : 1 : CheckWriteCoins(SPENT , SPENT , ABSENT, FRESH , DIRTY|FRESH, NO_ENTRY );
846 : 1 : CheckWriteCoins(SPENT , SPENT , SPENT , DIRTY , DIRTY , DIRTY );
847 : 1 : CheckWriteCoins(SPENT , SPENT , SPENT , DIRTY , DIRTY|FRESH, DIRTY );
848 : 1 : CheckWriteCoins(SPENT , SPENT , ABSENT, DIRTY|FRESH, DIRTY , NO_ENTRY );
849 : 1 : CheckWriteCoins(SPENT , SPENT , ABSENT, DIRTY|FRESH, DIRTY|FRESH, NO_ENTRY );
850 : 1 : CheckWriteCoins(SPENT , VALUE2, VALUE2, 0 , DIRTY , DIRTY );
851 : 1 : CheckWriteCoins(SPENT , VALUE2, VALUE2, 0 , DIRTY|FRESH, DIRTY );
852 : 1 : CheckWriteCoins(SPENT , VALUE2, VALUE2, FRESH , DIRTY , DIRTY|FRESH);
853 : 1 : CheckWriteCoins(SPENT , VALUE2, VALUE2, FRESH , DIRTY|FRESH, DIRTY|FRESH);
854 : 1 : CheckWriteCoins(SPENT , VALUE2, VALUE2, DIRTY , DIRTY , DIRTY );
855 : 1 : CheckWriteCoins(SPENT , VALUE2, VALUE2, DIRTY , DIRTY|FRESH, DIRTY );
856 : 1 : CheckWriteCoins(SPENT , VALUE2, VALUE2, DIRTY|FRESH, DIRTY , DIRTY|FRESH);
857 : 1 : CheckWriteCoins(SPENT , VALUE2, VALUE2, DIRTY|FRESH, DIRTY|FRESH, DIRTY|FRESH);
858 : 1 : CheckWriteCoins(VALUE1, ABSENT, VALUE1, 0 , NO_ENTRY , 0 );
859 : 1 : CheckWriteCoins(VALUE1, ABSENT, VALUE1, FRESH , NO_ENTRY , FRESH );
860 : 1 : CheckWriteCoins(VALUE1, ABSENT, VALUE1, DIRTY , NO_ENTRY , DIRTY );
861 : 1 : CheckWriteCoins(VALUE1, ABSENT, VALUE1, DIRTY|FRESH, NO_ENTRY , DIRTY|FRESH);
862 : 1 : CheckWriteCoins(VALUE1, SPENT , SPENT , 0 , DIRTY , DIRTY );
863 : 1 : CheckWriteCoins(VALUE1, SPENT , FAIL , 0 , DIRTY|FRESH, NO_ENTRY );
864 : 1 : CheckWriteCoins(VALUE1, SPENT , ABSENT, FRESH , DIRTY , NO_ENTRY );
865 : 1 : CheckWriteCoins(VALUE1, SPENT , FAIL , FRESH , DIRTY|FRESH, NO_ENTRY );
866 : 1 : CheckWriteCoins(VALUE1, SPENT , SPENT , DIRTY , DIRTY , DIRTY );
867 : 1 : CheckWriteCoins(VALUE1, SPENT , FAIL , DIRTY , DIRTY|FRESH, NO_ENTRY );
868 : 1 : CheckWriteCoins(VALUE1, SPENT , ABSENT, DIRTY|FRESH, DIRTY , NO_ENTRY );
869 : 1 : CheckWriteCoins(VALUE1, SPENT , FAIL , DIRTY|FRESH, DIRTY|FRESH, NO_ENTRY );
870 : 1 : CheckWriteCoins(VALUE1, VALUE2, VALUE2, 0 , DIRTY , DIRTY );
871 : 1 : CheckWriteCoins(VALUE1, VALUE2, FAIL , 0 , DIRTY|FRESH, NO_ENTRY );
872 : 1 : CheckWriteCoins(VALUE1, VALUE2, VALUE2, FRESH , DIRTY , DIRTY|FRESH);
873 : 1 : CheckWriteCoins(VALUE1, VALUE2, FAIL , FRESH , DIRTY|FRESH, NO_ENTRY );
874 : 1 : CheckWriteCoins(VALUE1, VALUE2, VALUE2, DIRTY , DIRTY , DIRTY );
875 : 1 : CheckWriteCoins(VALUE1, VALUE2, FAIL , DIRTY , DIRTY|FRESH, NO_ENTRY );
876 : 1 : CheckWriteCoins(VALUE1, VALUE2, VALUE2, DIRTY|FRESH, DIRTY , DIRTY|FRESH);
877 : 1 : CheckWriteCoins(VALUE1, VALUE2, FAIL , DIRTY|FRESH, DIRTY|FRESH, NO_ENTRY );
878 : :
879 : : // The checks above omit cases where the child flags are not DIRTY, since
880 : : // they would be too repetitive (the parent cache is never updated in these
881 : : // cases). The loop below covers these cases and makes sure the parent cache
882 : : // is always left unchanged.
883 [ + + ]: 4 : for (const CAmount parent_value : {ABSENT, SPENT, VALUE1})
884 [ + + ]: 12 : for (const CAmount child_value : {ABSENT, SPENT, VALUE2})
885 [ + + + + ]: 42 : for (const char parent_flags : parent_value == ABSENT ? ABSENT_FLAGS : FLAGS)
886 [ + + + + ]: 90 : for (const char child_flags : child_value == ABSENT ? ABSENT_FLAGS : CLEAN_FLAGS)
887 : 45 : CheckWriteCoins(parent_value, child_value, parent_value, parent_flags, child_flags, parent_flags);
888 : 1 : }
889 : :
890 : :
891 : 12 : Coin MakeCoin()
892 : : {
893 : 12 : Coin coin;
894 : 12 : coin.out.nValue = InsecureRand32();
895 : 12 : coin.nHeight = InsecureRandRange(4096);
896 : 12 : coin.fCoinBase = 0;
897 : 12 : return coin;
898 : : }
899 : :
900 : :
901 : : //! For CCoinsViewCache instances backed by either another cache instance or
902 : : //! leveldb, test cache behavior and flag state (DIRTY/FRESH) by
903 : : //!
904 : : //! 1. Adding a random coin to the child-most cache,
905 : : //! 2. Flushing all caches (without erasing),
906 : : //! 3. Ensure the entry still exists in the cache and has been written to parent,
907 : : //! 4. (if `do_erasing_flush`) Flushing the caches again (with erasing),
908 : : //! 5. (if `do_erasing_flush`) Ensure the entry has been written to the parent and is no longer in the cache,
909 : : //! 6. Spend the coin, ensure it no longer exists in the parent.
910 : : //!
911 : 4 : void TestFlushBehavior(
912 : : CCoinsViewCacheTest* view,
913 : : CCoinsViewDB& base,
914 : : std::vector<std::unique_ptr<CCoinsViewCacheTest>>& all_caches,
915 : : bool do_erasing_flush)
916 : : {
917 : 4 : CAmount value;
918 : 4 : char flags;
919 : 4 : size_t cache_usage;
920 : 4 : size_t cache_size;
921 : :
922 : 22 : auto flush_all = [&all_caches](bool erase) {
923 : : // Flush in reverse order to ensure that flushes happen from children up.
924 [ + + ]: 54 : for (auto i = all_caches.rbegin(); i != all_caches.rend(); ++i) {
925 : 36 : auto& cache = *i;
926 : 36 : cache->SanityCheck();
927 : : // hashBlock must be filled before flushing to disk; value is
928 : : // unimportant here. This is normally done during connect/disconnect block.
929 : 36 : cache->SetBestBlock(InsecureRand256());
930 [ + + ]: 36 : erase ? cache->Flush() : cache->Sync();
931 : : }
932 : 22 : };
933 : :
934 : 4 : Txid txid = Txid::FromUint256(InsecureRand256());
935 : 4 : COutPoint outp = COutPoint(txid, 0);
936 : 4 : Coin coin = MakeCoin();
937 : : // Ensure the coins views haven't seen this coin before.
938 [ + - + - : 8 : BOOST_CHECK(!base.HaveCoin(outp));
+ - + - ]
939 [ + - + - : 8 : BOOST_CHECK(!view->HaveCoin(outp));
+ - ]
940 : :
941 : : // --- 1. Adding a random coin to the child cache
942 : : //
943 [ + - ]: 4 : view->AddCoin(outp, Coin(coin), false);
944 : :
945 [ + - ]: 4 : cache_usage = view->DynamicMemoryUsage();
946 [ + - ]: 4 : cache_size = view->map().size();
947 : :
948 : : // `base` shouldn't have coin (no flush yet) but `view` should have cached it.
949 [ + - + - : 8 : BOOST_CHECK(!base.HaveCoin(outp));
+ - + - ]
950 [ + - + - : 8 : BOOST_CHECK(view->HaveCoin(outp));
+ - ]
951 : :
952 : 4 : GetCoinsMapEntry(view->map(), value, flags, outp);
953 [ + - + - ]: 4 : BOOST_CHECK_EQUAL(value, coin.out.nValue);
954 [ + - + - ]: 4 : BOOST_CHECK_EQUAL(flags, DIRTY|FRESH);
955 : :
956 : : // --- 2. Flushing all caches (without erasing)
957 : : //
958 [ + - ]: 4 : flush_all(/*erase=*/ false);
959 : :
960 : : // CoinsMap usage should be unchanged since we didn't erase anything.
961 [ + - + - : 4 : BOOST_CHECK_EQUAL(cache_usage, view->DynamicMemoryUsage());
+ - ]
962 [ + - + - ]: 4 : BOOST_CHECK_EQUAL(cache_size, view->map().size());
963 : :
964 : : // --- 3. Ensuring the entry still exists in the cache and has been written to parent
965 : : //
966 : 4 : GetCoinsMapEntry(view->map(), value, flags, outp);
967 [ + - + - ]: 4 : BOOST_CHECK_EQUAL(value, coin.out.nValue);
968 [ + - + - ]: 4 : BOOST_CHECK_EQUAL(flags, 0); // Flags should have been wiped.
969 : :
970 : : // Both views should now have the coin.
971 [ + - + - : 8 : BOOST_CHECK(base.HaveCoin(outp));
+ - + - ]
972 [ + - + - : 8 : BOOST_CHECK(view->HaveCoin(outp));
+ - + + ]
973 : :
974 [ + + ]: 4 : if (do_erasing_flush) {
975 : : // --- 4. Flushing the caches again (with erasing)
976 : : //
977 [ + - ]: 2 : flush_all(/*erase=*/ true);
978 : :
979 : : // Memory does not necessarily go down due to the map using a memory pool
980 [ + - + - : 4 : BOOST_TEST(view->DynamicMemoryUsage() <= cache_usage);
+ - + - +
- ]
981 : : // Size of the cache must go down though
982 [ + - + - : 4 : BOOST_TEST(view->map().size() < cache_size);
+ - ]
983 : :
984 : : // --- 5. Ensuring the entry is no longer in the cache
985 : : //
986 : 2 : GetCoinsMapEntry(view->map(), value, flags, outp);
987 [ + - + - ]: 2 : BOOST_CHECK_EQUAL(value, ABSENT);
988 [ + - + - ]: 2 : BOOST_CHECK_EQUAL(flags, NO_ENTRY);
989 : :
990 [ + - ]: 2 : view->AccessCoin(outp);
991 : 2 : GetCoinsMapEntry(view->map(), value, flags, outp);
992 [ + - + - ]: 2 : BOOST_CHECK_EQUAL(value, coin.out.nValue);
993 [ + - + - ]: 2 : BOOST_CHECK_EQUAL(flags, 0);
994 : : }
995 : :
996 : : // Can't overwrite an entry without specifying that an overwrite is
997 : : // expected.
998 [ + - - + : 12 : BOOST_CHECK_THROW(
- - - - -
+ + - +
- ]
999 : : view->AddCoin(outp, Coin(coin), /*possible_overwrite=*/ false),
1000 : : std::logic_error);
1001 : :
1002 : : // --- 6. Spend the coin.
1003 : : //
1004 [ + - + - : 8 : BOOST_CHECK(view->SpendCoin(outp));
+ - ]
1005 : :
1006 : : // The coin should be in the cache, but spent and marked dirty.
1007 : 4 : GetCoinsMapEntry(view->map(), value, flags, outp);
1008 [ + - + - ]: 4 : BOOST_CHECK_EQUAL(value, SPENT);
1009 [ + - + - ]: 4 : BOOST_CHECK_EQUAL(flags, DIRTY);
1010 [ + - + - : 8 : BOOST_CHECK(!view->HaveCoin(outp)); // Coin should be considered spent in `view`.
+ - + - ]
1011 [ + - + - : 8 : BOOST_CHECK(base.HaveCoin(outp)); // But coin should still be unspent in `base`.
+ - + - ]
1012 : :
1013 [ + - ]: 4 : flush_all(/*erase=*/ false);
1014 : :
1015 : : // Coin should be considered spent in both views.
1016 [ + - + - : 8 : BOOST_CHECK(!view->HaveCoin(outp));
+ - + - ]
1017 [ + - + - : 8 : BOOST_CHECK(!base.HaveCoin(outp));
+ - + - ]
1018 : :
1019 : : // Spent coin should not be spendable.
1020 [ + - + - : 8 : 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 : 4 : txid = Txid::FromUint256(InsecureRand256());
1026 : 4 : outp = COutPoint(txid, 0);
1027 : 4 : coin = MakeCoin();
1028 [ + - + - : 8 : BOOST_CHECK(!base.HaveCoin(outp));
+ - + - ]
1029 [ + - + - : 8 : BOOST_CHECK(!all_caches[0]->HaveCoin(outp));
+ - + - ]
1030 [ + - + - : 8 : BOOST_CHECK(!all_caches[1]->HaveCoin(outp));
+ - + - ]
1031 : :
1032 [ + - ]: 4 : all_caches[0]->AddCoin(outp, std::move(coin), false);
1033 [ + - ]: 4 : all_caches[0]->Sync();
1034 [ + - + - : 8 : BOOST_CHECK(base.HaveCoin(outp));
+ - + - ]
1035 [ + - + - : 8 : BOOST_CHECK(all_caches[0]->HaveCoin(outp));
+ - + - ]
1036 [ + - + - : 8 : BOOST_CHECK(!all_caches[1]->HaveCoinInCache(outp));
+ - + - ]
1037 : :
1038 [ + - + - : 8 : BOOST_CHECK(all_caches[1]->SpendCoin(outp));
+ - + - ]
1039 [ + - ]: 4 : flush_all(/*erase=*/ false);
1040 [ + - + - : 8 : BOOST_CHECK(!base.HaveCoin(outp));
+ - + - ]
1041 [ + - + - : 8 : BOOST_CHECK(!all_caches[0]->HaveCoin(outp));
+ - + - ]
1042 [ + - + - : 8 : BOOST_CHECK(!all_caches[1]->HaveCoin(outp));
+ - + - ]
1043 : :
1044 [ + - ]: 4 : 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 : 4 : txid = Txid::FromUint256(InsecureRand256());
1049 : 4 : outp = COutPoint(txid, 0);
1050 : 4 : coin = MakeCoin();
1051 : 4 : CAmount coin_val = coin.out.nValue;
1052 [ + - + - : 8 : BOOST_CHECK(!base.HaveCoin(outp));
+ - + - ]
1053 [ + - + - : 8 : BOOST_CHECK(!all_caches[0]->HaveCoin(outp));
+ - + - ]
1054 [ + - + - : 8 : BOOST_CHECK(!all_caches[1]->HaveCoin(outp));
+ - + - ]
1055 : :
1056 : : // Add and spend from same cache without flushing.
1057 [ + - ]: 4 : all_caches[0]->AddCoin(outp, std::move(coin), false);
1058 : :
1059 : : // Coin should be FRESH in the cache.
1060 : 4 : GetCoinsMapEntry(all_caches[0]->map(), value, flags, outp);
1061 [ + - + - ]: 4 : BOOST_CHECK_EQUAL(value, coin_val);
1062 [ + - + - ]: 4 : BOOST_CHECK_EQUAL(flags, DIRTY|FRESH);
1063 : :
1064 : : // Base shouldn't have seen coin.
1065 [ + - + - : 8 : BOOST_CHECK(!base.HaveCoin(outp));
+ - + - ]
1066 : :
1067 [ + - + - : 8 : BOOST_CHECK(all_caches[0]->SpendCoin(outp));
+ - + - ]
1068 [ + - ]: 4 : all_caches[0]->Sync();
1069 : :
1070 : : // Ensure there is no sign of the coin after spend/flush.
1071 : 4 : GetCoinsMapEntry(all_caches[0]->map(), value, flags, outp);
1072 [ + - + - ]: 4 : BOOST_CHECK_EQUAL(value, ABSENT);
1073 [ + - + - ]: 4 : BOOST_CHECK_EQUAL(flags, NO_ENTRY);
1074 [ + - + - : 8 : BOOST_CHECK(!all_caches[0]->HaveCoinInCache(outp));
+ - + - ]
1075 [ + - + - : 8 : BOOST_CHECK(!base.HaveCoin(outp));
+ - ]
1076 : 4 : }
1077 : :
1078 [ + - + - : 7 : BOOST_AUTO_TEST_CASE(ccoins_flush_behavior)
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- ]
1079 : : {
1080 : : // Create two in-memory caches atop a leveldb view.
1081 : 0 : CCoinsViewDB base{{.path = "test", .cache_bytes = 1 << 23, .memory_only = true}, {}};
1082 : 1 : std::vector<std::unique_ptr<CCoinsViewCacheTest>> caches;
1083 [ + - ]: 2 : caches.push_back(std::make_unique<CCoinsViewCacheTest>(&base));
1084 [ + - ]: 2 : caches.push_back(std::make_unique<CCoinsViewCacheTest>(caches.back().get()));
1085 : :
1086 [ + + ]: 3 : for (const auto& view : caches) {
1087 [ + - ]: 2 : TestFlushBehavior(view.get(), base, caches, /*do_erasing_flush=*/false);
1088 [ + - ]: 2 : TestFlushBehavior(view.get(), base, caches, /*do_erasing_flush=*/true);
1089 : : }
1090 [ + - ]: 2 : }
1091 : :
1092 [ + - + - : 7 : BOOST_AUTO_TEST_CASE(coins_resource_is_used)
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- ]
1093 : : {
1094 : 1 : CCoinsMapMemoryResource resource;
1095 [ + - ]: 1 : PoolResourceTester::CheckAllDataAccountedFor(resource);
1096 : :
1097 : 1 : {
1098 [ + - + - ]: 1 : CCoinsMap map{0, CCoinsMap::hasher{}, CCoinsMap::key_equal{}, &resource};
1099 [ + - + - : 2 : BOOST_TEST(memusage::DynamicUsage(map) >= resource.ChunkSizeBytes());
+ - + - ]
1100 : :
1101 [ + - ]: 1 : map.reserve(1000);
1102 : :
1103 : : // The resource has preallocated a chunk, so we should have space for at several nodes without the need to allocate anything else.
1104 : 1 : const auto usage_before = memusage::DynamicUsage(map);
1105 : :
1106 : 1 : COutPoint out_point{};
1107 [ + + ]: 1001 : for (size_t i = 0; i < 1000; ++i) {
1108 : 1000 : out_point.n = i;
1109 [ + - ]: 1000 : map[out_point];
1110 : : }
1111 [ + - + - : 2 : BOOST_TEST(usage_before == memusage::DynamicUsage(map));
+ - ]
1112 : 0 : }
1113 : :
1114 [ + - ]: 1 : PoolResourceTester::CheckAllDataAccountedFor(resource);
1115 : 1 : }
1116 : :
1117 : : BOOST_AUTO_TEST_SUITE_END()
|