Line data Source code
1 : // Copyright (c) 2014-2019 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 <attributes.h>
6 : #include <clientversion.h>
7 : #include <coins.h>
8 : #include <script/standard.h>
9 : #include <streams.h>
10 : #include <test/util/setup_common.h>
11 : #include <txdb.h>
12 : #include <uint256.h>
13 : #include <undo.h>
14 : #include <util/strencodings.h>
15 :
16 : #include <map>
17 : #include <vector>
18 :
19 : #include <boost/test/unit_test.hpp>
20 :
21 : int ApplyTxInUndo(Coin&& undo, CCoinsViewCache& view, const COutPoint& out);
22 : void UpdateCoins(const CTransaction& tx, CCoinsViewCache& inputs, CTxUndo &txundo, int nHeight);
23 :
24 : namespace
25 : {
26 : //! equality test
27 1217096 : bool operator==(const Coin &a, const Coin &b) {
28 : // Empty Coin objects are always equal.
29 1217096 : if (a.IsSpent() && b.IsSpent()) return true;
30 717814 : return a.fCoinBase == b.fCoinBase &&
31 358907 : a.nHeight == b.nHeight &&
32 358907 : a.out == b.out;
33 1217096 : }
34 :
35 8 : class CCoinsViewTest : public CCoinsView
36 : {
37 : uint256 hashBestBlock_;
38 : std::map<COutPoint, Coin> map_;
39 :
40 : public:
41 6443888 : NODISCARD bool GetCoin(const COutPoint& outpoint, Coin& coin) const override
42 : {
43 6443888 : std::map<COutPoint, Coin>::const_iterator it = map_.find(outpoint);
44 6443888 : if (it == map_.end()) {
45 5984394 : return false;
46 : }
47 459494 : coin = it->second;
48 459494 : if (coin.IsSpent() && InsecureRandBool() == 0) {
49 : // Randomly return false in case of an empty entry.
50 164435 : return false;
51 : }
52 295059 : return true;
53 6443888 : }
54 :
55 0 : uint256 GetBestBlock() const override { return hashBestBlock_; }
56 :
57 262 : bool BatchWrite(CCoinsMap& mapCoins, const uint256& hashBlock) override
58 : {
59 338009 : for (CCoinsMap::iterator it = mapCoins.begin(); it != mapCoins.end(); ) {
60 337747 : if (it->second.flags & CCoinsCacheEntry::DIRTY) {
61 : // Same optimization used in CCoinsViewDB is to only write dirty entries.
62 79201 : map_[it->first] = it->second.coin;
63 79201 : if (it->second.coin.IsSpent() && InsecureRandRange(3) == 0) {
64 : // Randomly delete empty entries on write.
65 11679 : map_.erase(it->first);
66 11679 : }
67 : }
68 337747 : mapCoins.erase(it++);
69 : }
70 262 : if (!hashBlock.IsNull())
71 0 : hashBestBlock_ = hashBlock;
72 262 : return true;
73 : }
74 : };
75 :
76 2616 : class CCoinsViewCacheTest : public CCoinsViewCache
77 : {
78 : public:
79 2008 : explicit CCoinsViewCacheTest(CCoinsView* _base) : CCoinsViewCache(_base) {}
80 :
81 400 : void SelfTest() const
82 : {
83 : // Manually recompute the dynamic usage of the whole data, and compare it.
84 400 : size_t ret = memusage::DynamicUsage(cacheCoins);
85 400 : size_t count = 0;
86 572451 : for (const auto& entry : cacheCoins) {
87 572051 : ret += entry.second.coin.DynamicMemoryUsage();
88 572051 : ++count;
89 : }
90 400 : BOOST_CHECK_EQUAL(GetCacheSize(), count);
91 400 : BOOST_CHECK_EQUAL(DynamicMemoryUsage(), ret);
92 400 : }
93 :
94 376 : CCoinsMap& map() const { return cacheCoins; }
95 198 : size_t& usage() const { return cachedCoinsUsage; }
96 : };
97 :
98 : } // namespace
99 :
100 89 : BOOST_FIXTURE_TEST_SUITE(coins_tests, BasicTestingSetup)
101 :
102 : static const unsigned int NUM_SIMULATION_ITERATIONS = 40000;
103 :
104 : // This is a large randomized insert/remove simulation test on a variable-size
105 : // stack of caches on top of CCoinsViewTest.
106 : //
107 : // It will randomly create/update/delete Coin entries to a tip of caches, with
108 : // txids picked from a limited list of random 256-bit hashes. Occasionally, a
109 : // new tip is added to the stack of caches, or the tip is flushed and removed.
110 : //
111 : // During the process, booleans are kept to make sure that the randomized
112 : // operation hits all branches.
113 : //
114 : // If fake_best_block is true, assign a random uint256 to mock the recording
115 : // of best block on flush. This is necessary when using CCoinsViewDB as the base,
116 : // otherwise we'll hit an assertion in BatchWrite.
117 : //
118 2 : void SimulationTest(CCoinsView* base, bool fake_best_block)
119 : {
120 : // Various coverage trackers.
121 80002 : bool removed_all_caches = false;
122 : bool reached_4_caches = false;
123 2 : bool added_an_entry = false;
124 : bool added_an_unspendable_entry = false;
125 80002 : bool removed_an_entry = false;
126 2 : bool updated_an_entry = false;
127 574004 : bool found_an_entry = false;
128 160002 : bool missed_an_entry = false;
129 : bool uncached_an_entry = false;
130 :
131 : // A simple map to track what we expect the cache stack to represent.
132 2 : std::map<COutPoint, Coin> result;
133 :
134 : // The cache stack.
135 2 : std::vector<CCoinsViewCacheTest*> stack; // A stack of CCoinsViewCaches on top.
136 2 : stack.push_back(new CCoinsViewCacheTest(base)); // Start with one cache.
137 :
138 : // Use a limited set of random transaction ids, so we do test overwriting entries.
139 2 : std::vector<uint256> txids;
140 2 : txids.resize(NUM_SIMULATION_ITERATIONS / 8);
141 10002 : for (unsigned int i = 0; i < txids.size(); i++) {
142 10000 : txids[i] = InsecureRand256();
143 : }
144 :
145 80002 : for (unsigned int i = 0; i < NUM_SIMULATION_ITERATIONS; i++) {
146 : // Do a random modification.
147 : {
148 80000 : uint256 txid = txids[InsecureRandRange(txids.size())]; // txid we're going to modify in this iteration.
149 80000 : Coin& coin = result[COutPoint(txid, 0)];
150 :
151 : // Determine whether to test HaveCoin before or after Access* (or both). As these functions
152 : // can influence each other's behaviour by pulling things into the cache, all combinations
153 : // are tested.
154 80000 : bool test_havecoin_before = InsecureRandBits(2) == 0;
155 80000 : bool test_havecoin_after = InsecureRandBits(2) == 0;
156 :
157 80000 : bool result_havecoin = test_havecoin_before ? stack.back()->HaveCoin(COutPoint(txid, 0)) : false;
158 80000 : const Coin& entry = (InsecureRandRange(500) == 0) ? AccessByTxid(*stack.back(), txid) : stack.back()->AccessCoin(COutPoint(txid, 0));
159 80000 : BOOST_CHECK(coin == entry);
160 80000 : BOOST_CHECK(!test_havecoin_before || result_havecoin == !entry.IsSpent());
161 :
162 80000 : if (test_havecoin_after) {
163 19927 : bool ret = stack.back()->HaveCoin(COutPoint(txid, 0));
164 19927 : BOOST_CHECK(ret == !entry.IsSpent());
165 19927 : }
166 :
167 80000 : if (InsecureRandRange(5) == 0 || coin.IsSpent()) {
168 48097 : Coin newcoin;
169 48097 : newcoin.out.nValue = InsecureRand32();
170 48097 : newcoin.nHeight = 1;
171 48097 : if (InsecureRandRange(16) == 0 && coin.IsSpent()) {
172 2579 : newcoin.out.scriptPubKey.assign(1 + InsecureRandBits(6), OP_RETURN);
173 2579 : BOOST_CHECK(newcoin.out.scriptPubKey.IsUnspendable());
174 : added_an_unspendable_entry = true;
175 2579 : } else {
176 45518 : newcoin.out.scriptPubKey.assign(InsecureRandBits(6), 0); // Random sizes so we can test memory usage accounting
177 45518 : (coin.IsSpent() ? added_an_entry : updated_an_entry) = true;
178 45518 : coin = newcoin;
179 : }
180 48097 : stack.back()->AddCoin(COutPoint(txid, 0), std::move(newcoin), !coin.IsSpent() || InsecureRand32() & 1);
181 48097 : } else {
182 : removed_an_entry = true;
183 31903 : coin.Clear();
184 31903 : BOOST_CHECK(stack.back()->SpendCoin(COutPoint(txid, 0)));
185 : }
186 80000 : }
187 :
188 : // One every 10 iterations, remove a random entry from the cache
189 80000 : if (InsecureRandRange(10) == 0) {
190 8024 : COutPoint out(txids[InsecureRand32() % txids.size()], 0);
191 8024 : int cacheid = InsecureRand32() % stack.size();
192 8024 : stack[cacheid]->Uncache(out);
193 8024 : uncached_an_entry |= !stack[cacheid]->HaveCoinInCache(out);
194 8024 : }
195 :
196 : // Once every 1000 iterations and at the end, verify the full cache.
197 80000 : if (InsecureRandRange(1000) == 1 || i == NUM_SIMULATION_ITERATIONS - 1) {
198 414002 : for (const auto& entry : result) {
199 413907 : bool have = stack.back()->HaveCoin(entry.first);
200 413907 : const Coin& coin = stack.back()->AccessCoin(entry.first);
201 413907 : BOOST_CHECK(have == !coin.IsSpent());
202 413907 : BOOST_CHECK(coin == entry.second);
203 413907 : if (coin.IsSpent()) {
204 : missed_an_entry = true;
205 176325 : } else {
206 237582 : BOOST_CHECK(stack.back()->HaveCoinInCache(entry.first));
207 : found_an_entry = true;
208 : }
209 0 : }
210 317 : for (const CCoinsViewCacheTest *test : stack) {
211 222 : test->SelfTest();
212 : }
213 95 : }
214 :
215 80000 : if (InsecureRandRange(100) == 0) {
216 : // Every 100 iterations, flush an intermediate cache
217 843 : if (stack.size() > 1 && InsecureRandBool() == 0) {
218 306 : unsigned int flushIndex = InsecureRandRange(stack.size() - 1);
219 306 : if (fake_best_block) stack[flushIndex]->SetBestBlock(InsecureRand256());
220 306 : BOOST_CHECK(stack[flushIndex]->Flush());
221 306 : }
222 : }
223 80000 : if (InsecureRandRange(100) == 0) {
224 : // Every 100 iterations, change the cache stack.
225 823 : if (stack.size() > 0 && InsecureRandBool() == 0) {
226 : //Remove the top cache
227 420 : if (fake_best_block) stack.back()->SetBestBlock(InsecureRand256());
228 420 : BOOST_CHECK(stack.back()->Flush());
229 420 : delete stack.back();
230 420 : stack.pop_back();
231 420 : }
232 823 : if (stack.size() == 0 || (stack.size() < 4 && InsecureRandBool())) {
233 : //Add a new cache
234 : CCoinsView* tip = base;
235 420 : if (stack.size() > 0) {
236 294 : tip = stack.back();
237 294 : } else {
238 : removed_all_caches = true;
239 : }
240 420 : stack.push_back(new CCoinsViewCacheTest(tip));
241 420 : if (stack.size() == 4) {
242 : reached_4_caches = true;
243 103 : }
244 420 : }
245 : }
246 : }
247 :
248 : // Clean up the stack.
249 4 : while (stack.size() > 0) {
250 2 : delete stack.back();
251 2 : stack.pop_back();
252 : }
253 :
254 : // Verify coverage.
255 2 : BOOST_CHECK(removed_all_caches);
256 2 : BOOST_CHECK(reached_4_caches);
257 2 : BOOST_CHECK(added_an_entry);
258 2 : BOOST_CHECK(added_an_unspendable_entry);
259 2 : BOOST_CHECK(removed_an_entry);
260 2 : BOOST_CHECK(updated_an_entry);
261 2 : BOOST_CHECK(found_an_entry);
262 2 : BOOST_CHECK(missed_an_entry);
263 2 : BOOST_CHECK(uncached_an_entry);
264 2 : }
265 :
266 : // Run the above simulation for multiple base types.
267 95 : BOOST_AUTO_TEST_CASE(coins_cache_simulation_test)
268 : {
269 1 : CCoinsViewTest base;
270 1 : SimulationTest(&base, false);
271 :
272 1 : CCoinsViewDB db_base{"test", /*nCacheSize*/ 1 << 23, /*fMemory*/ true, /*fWipe*/ false};
273 1 : SimulationTest(&db_base, true);
274 1 : }
275 :
276 : // Store of all necessary tx and undo data for next test
277 : typedef std::map<COutPoint, std::tuple<CTransaction,CTxUndo,Coin>> UtxoData;
278 89 : UtxoData utxoData;
279 :
280 40222 : UtxoData::iterator FindRandomFrom(const std::set<COutPoint> &utxoSet) {
281 40222 : assert(utxoSet.size());
282 40222 : auto utxoSetIt = utxoSet.lower_bound(COutPoint(InsecureRand256(), 0));
283 40222 : if (utxoSetIt == utxoSet.end()) {
284 583 : utxoSetIt = utxoSet.begin();
285 583 : }
286 40222 : auto utxoDataIt = utxoData.find(*utxoSetIt);
287 40222 : assert(utxoDataIt != utxoData.end());
288 : return utxoDataIt;
289 40222 : }
290 :
291 :
292 : // This test is similar to the previous test
293 : // except the emphasis is on testing the functionality of UpdateCoins
294 : // random txs are created and UpdateCoins is used to update the cache stack
295 : // In particular it is tested that spending a duplicate coinbase tx
296 : // has the expected effect (the other duplicate is overwritten at all cache levels)
297 95 : BOOST_AUTO_TEST_CASE(updatecoins_simulation_test)
298 : {
299 1 : SeedInsecureRand(SeedRand::ZEROS);
300 1 : g_mock_deterministic_tests = true;
301 :
302 79663 : bool spent_a_duplicate_coinbase = false;
303 : // A simple map to track what we expect the cache stack to represent.
304 1 : std::map<COutPoint, Coin> result;
305 :
306 : // The cache stack.
307 1 : CCoinsViewTest base; // A CCoinsViewTest at the bottom.
308 1 : std::vector<CCoinsViewCacheTest*> stack; // A stack of CCoinsViewCaches on top.
309 1 : stack.push_back(new CCoinsViewCacheTest(&base)); // Start with one cache.
310 :
311 : // Track the txids we've used in various sets
312 1 : std::set<COutPoint> coinbase_coins;
313 1 : std::set<COutPoint> disconnected_coins;
314 1 : std::set<COutPoint> duplicate_coins;
315 1 : std::set<COutPoint> utxoset;
316 :
317 40001 : for (unsigned int i = 0; i < NUM_SIMULATION_ITERATIONS; i++) {
318 40000 : uint32_t randiter = InsecureRand32();
319 :
320 : // 19/20 txs add a new transaction
321 40000 : if (randiter % 20 < 19) {
322 38023 : CMutableTransaction tx;
323 38023 : tx.vin.resize(1);
324 38023 : tx.vout.resize(1);
325 38023 : tx.vout[0].nValue = i; //Keep txs unique unless intended to duplicate
326 38023 : tx.vout[0].scriptPubKey.assign(InsecureRand32() & 0x3F, 0); // Random sizes so we can test memory usage accounting
327 38023 : unsigned int height = InsecureRand32();
328 38023 : Coin old_coin;
329 :
330 : // 2/20 times create a new coinbase
331 38023 : if (randiter % 20 < 2 || coinbase_coins.size() < 10) {
332 : // 1/10 of those times create a duplicate coinbase
333 4045 : if (InsecureRandRange(10) == 0 && coinbase_coins.size()) {
334 391 : auto utxod = FindRandomFrom(coinbase_coins);
335 : // Reuse the exact same coinbase
336 391 : tx = CMutableTransaction{std::get<0>(utxod->second)};
337 : // shouldn't be available for reconnection if it's been duplicated
338 391 : disconnected_coins.erase(utxod->first);
339 :
340 391 : duplicate_coins.insert(utxod->first);
341 391 : }
342 : else {
343 3654 : coinbase_coins.insert(COutPoint(tx.GetHash(), 0));
344 : }
345 4045 : assert(CTransaction(tx).IsCoinBase());
346 4045 : }
347 :
348 : // 17/20 times reconnect previous or add a regular tx
349 : else {
350 :
351 33978 : COutPoint prevout;
352 : // 1/20 times reconnect a previously disconnected tx
353 33978 : if (randiter % 20 == 2 && disconnected_coins.size()) {
354 1959 : auto utxod = FindRandomFrom(disconnected_coins);
355 1959 : tx = CMutableTransaction{std::get<0>(utxod->second)};
356 1959 : prevout = tx.vin[0].prevout;
357 1959 : if (!CTransaction(tx).IsCoinBase() && !utxoset.count(prevout)) {
358 338 : disconnected_coins.erase(utxod->first);
359 338 : continue;
360 : }
361 :
362 : // If this tx is already IN the UTXO, then it must be a coinbase, and it must be a duplicate
363 1621 : if (utxoset.count(utxod->first)) {
364 0 : assert(CTransaction(tx).IsCoinBase());
365 0 : assert(duplicate_coins.count(utxod->first));
366 : }
367 1621 : disconnected_coins.erase(utxod->first);
368 1959 : }
369 :
370 : // 16/20 times create a regular tx
371 : else {
372 32019 : auto utxod = FindRandomFrom(utxoset);
373 32019 : prevout = utxod->first;
374 :
375 : // Construct the tx to spend the coins of prevouthash
376 32019 : tx.vin[0].prevout = prevout;
377 32019 : assert(!CTransaction(tx).IsCoinBase());
378 32019 : }
379 : // In this simple test coins only have two states, spent or unspent, save the unspent state to restore
380 33640 : old_coin = result[prevout];
381 : // Update the expected result of prevouthash to know these coins are spent
382 33640 : result[prevout].Clear();
383 :
384 33640 : utxoset.erase(prevout);
385 :
386 : // The test is designed to ensure spending a duplicate coinbase will work properly
387 : // if that ever happens and not resurrect the previously overwritten coinbase
388 33640 : if (duplicate_coins.count(prevout)) {
389 : spent_a_duplicate_coinbase = true;
390 348 : }
391 :
392 33978 : }
393 : // Update the expected result to know about the new output coins
394 37685 : assert(tx.vout.size() == 1);
395 37685 : const COutPoint outpoint(tx.GetHash(), 0);
396 37685 : result[outpoint] = Coin(tx.vout[0], height, CTransaction(tx).IsCoinBase());
397 :
398 : // Call UpdateCoins on the top cache
399 37685 : CTxUndo undo;
400 37685 : UpdateCoins(CTransaction(tx), *(stack.back()), undo, height);
401 :
402 : // Update the utxo set for future spends
403 37685 : utxoset.insert(outpoint);
404 :
405 : // Track this tx and undo info to use later
406 37685 : utxoData.emplace(outpoint, std::make_tuple(tx,undo,old_coin));
407 40000 : } else if (utxoset.size()) {
408 : //1/20 times undo a previous transaction
409 1977 : auto utxod = FindRandomFrom(utxoset);
410 :
411 1977 : CTransaction &tx = std::get<0>(utxod->second);
412 1977 : CTxUndo &undo = std::get<1>(utxod->second);
413 1977 : Coin &orig_coin = std::get<2>(utxod->second);
414 :
415 : // Update the expected result
416 : // Remove new outputs
417 1977 : result[utxod->first].Clear();
418 : // If not coinbase restore prevout
419 1977 : if (!tx.IsCoinBase()) {
420 1750 : result[tx.vin[0].prevout] = orig_coin;
421 : }
422 :
423 : // Disconnect the tx from the current UTXO
424 : // See code in DisconnectBlock
425 : // remove outputs
426 1977 : BOOST_CHECK(stack.back()->SpendCoin(utxod->first));
427 : // restore inputs
428 1977 : if (!tx.IsCoinBase()) {
429 1750 : const COutPoint &out = tx.vin[0].prevout;
430 1750 : Coin coin = undo.vprevout[0];
431 1750 : ApplyTxInUndo(std::move(coin), *(stack.back()), out);
432 1750 : }
433 : // Store as a candidate for reconnection
434 1977 : disconnected_coins.insert(utxod->first);
435 :
436 : // Update the utxoset
437 1977 : utxoset.erase(utxod->first);
438 1977 : if (!tx.IsCoinBase())
439 1750 : utxoset.insert(tx.vin[0].prevout);
440 1977 : }
441 :
442 : // Once every 1000 iterations and at the end, verify the full cache.
443 39662 : if (InsecureRandRange(1000) == 1 || i == NUM_SIMULATION_ITERATIONS - 1) {
444 723227 : for (const auto& entry : result) {
445 723189 : bool have = stack.back()->HaveCoin(entry.first);
446 723189 : const Coin& coin = stack.back()->AccessCoin(entry.first);
447 723189 : BOOST_CHECK(have == !coin.IsSpent());
448 723189 : BOOST_CHECK(coin == entry.second);
449 0 : }
450 38 : }
451 :
452 : // One every 10 iterations, remove a random entry from the cache
453 39662 : if (utxoset.size() > 1 && InsecureRandRange(30) == 0) {
454 1344 : stack[InsecureRand32() % stack.size()]->Uncache(FindRandomFrom(utxoset)->first);
455 1344 : }
456 39662 : if (disconnected_coins.size() > 1 && InsecureRandRange(30) == 0) {
457 1199 : stack[InsecureRand32() % stack.size()]->Uncache(FindRandomFrom(disconnected_coins)->first);
458 1199 : }
459 39662 : if (duplicate_coins.size() > 1 && InsecureRandRange(30) == 0) {
460 1333 : stack[InsecureRand32() % stack.size()]->Uncache(FindRandomFrom(duplicate_coins)->first);
461 1333 : }
462 :
463 39662 : if (InsecureRandRange(100) == 0) {
464 : // Every 100 iterations, flush an intermediate cache
465 425 : if (stack.size() > 1 && InsecureRandBool() == 0) {
466 187 : unsigned int flushIndex = InsecureRandRange(stack.size() - 1);
467 187 : BOOST_CHECK(stack[flushIndex]->Flush());
468 187 : }
469 : }
470 39662 : if (InsecureRandRange(100) == 0) {
471 : // Every 100 iterations, change the cache stack.
472 399 : if (stack.size() > 0 && InsecureRandBool() == 0) {
473 182 : BOOST_CHECK(stack.back()->Flush());
474 182 : delete stack.back();
475 182 : stack.pop_back();
476 182 : }
477 399 : if (stack.size() == 0 || (stack.size() < 4 && InsecureRandBool())) {
478 : CCoinsView* tip = &base;
479 185 : if (stack.size() > 0) {
480 159 : tip = stack.back();
481 159 : }
482 185 : stack.push_back(new CCoinsViewCacheTest(tip));
483 185 : }
484 : }
485 39662 : }
486 :
487 : // Clean up the stack.
488 5 : while (stack.size() > 0) {
489 4 : delete stack.back();
490 4 : stack.pop_back();
491 : }
492 :
493 : // Verify coverage.
494 1 : BOOST_CHECK(spent_a_duplicate_coinbase);
495 :
496 1 : g_mock_deterministic_tests = false;
497 1 : }
498 :
499 95 : BOOST_AUTO_TEST_CASE(ccoins_serialization)
500 : {
501 : // Good example
502 1 : CDataStream ss1(ParseHex("97f23c835800816115944e077fe7c803cfa57f29b36bf87c1d35"), SER_DISK, CLIENT_VERSION);
503 1 : Coin cc1;
504 1 : ss1 >> cc1;
505 1 : BOOST_CHECK_EQUAL(cc1.fCoinBase, false);
506 1 : BOOST_CHECK_EQUAL(cc1.nHeight, 203998U);
507 1 : BOOST_CHECK_EQUAL(cc1.out.nValue, CAmount{60000000000});
508 1 : BOOST_CHECK_EQUAL(HexStr(cc1.out.scriptPubKey), HexStr(GetScriptForDestination(PKHash(uint160(ParseHex("816115944e077fe7c803cfa57f29b36bf87c1d35"))))));
509 :
510 : // Good example
511 1 : CDataStream ss2(ParseHex("8ddf77bbd123008c988f1a4a4de2161e0f50aac7f17e7f9555caa4"), SER_DISK, CLIENT_VERSION);
512 1 : Coin cc2;
513 1 : ss2 >> cc2;
514 1 : BOOST_CHECK_EQUAL(cc2.fCoinBase, true);
515 1 : BOOST_CHECK_EQUAL(cc2.nHeight, 120891U);
516 1 : BOOST_CHECK_EQUAL(cc2.out.nValue, 110397);
517 1 : BOOST_CHECK_EQUAL(HexStr(cc2.out.scriptPubKey), HexStr(GetScriptForDestination(PKHash(uint160(ParseHex("8c988f1a4a4de2161e0f50aac7f17e7f9555caa4"))))));
518 :
519 : // Smallest possible example
520 1 : CDataStream ss3(ParseHex("000006"), SER_DISK, CLIENT_VERSION);
521 1 : Coin cc3;
522 1 : ss3 >> cc3;
523 1 : BOOST_CHECK_EQUAL(cc3.fCoinBase, false);
524 1 : BOOST_CHECK_EQUAL(cc3.nHeight, 0U);
525 1 : BOOST_CHECK_EQUAL(cc3.out.nValue, 0);
526 1 : BOOST_CHECK_EQUAL(cc3.out.scriptPubKey.size(), 0U);
527 :
528 : // scriptPubKey that ends beyond the end of the stream
529 1 : CDataStream ss4(ParseHex("000007"), SER_DISK, CLIENT_VERSION);
530 : try {
531 1 : Coin cc4;
532 1 : ss4 >> cc4;
533 0 : BOOST_CHECK_MESSAGE(false, "We should have thrown");
534 1 : } catch (const std::ios_base::failure&) {
535 1 : }
536 :
537 : // Very large scriptPubKey (3*10^9 bytes) past the end of the stream
538 1 : CDataStream tmp(SER_DISK, CLIENT_VERSION);
539 1 : uint64_t x = 3000000000ULL;
540 1 : tmp << VARINT(x);
541 1 : BOOST_CHECK_EQUAL(HexStr(tmp), "8a95c0bb00");
542 1 : CDataStream ss5(ParseHex("00008a95c0bb00"), SER_DISK, CLIENT_VERSION);
543 : try {
544 1 : Coin cc5;
545 1 : ss5 >> cc5;
546 0 : BOOST_CHECK_MESSAGE(false, "We should have thrown");
547 1 : } catch (const std::ios_base::failure&) {
548 1 : }
549 3 : }
550 :
551 89 : const static COutPoint OUTPOINT;
552 : const static CAmount SPENT = -1;
553 : const static CAmount ABSENT = -2;
554 : const static CAmount FAIL = -3;
555 : const static CAmount VALUE1 = 100;
556 : const static CAmount VALUE2 = 200;
557 : const static CAmount VALUE3 = 300;
558 : const static char DIRTY = CCoinsCacheEntry::DIRTY;
559 : const static char FRESH = CCoinsCacheEntry::FRESH;
560 : const static char NO_ENTRY = -1;
561 :
562 : const static auto FLAGS = {char(0), FRESH, DIRTY, char(DIRTY | FRESH)};
563 : const static auto CLEAN_FLAGS = {char(0), FRESH};
564 : const static auto ABSENT_FLAGS = {NO_ENTRY};
565 :
566 320 : static void SetCoinsValue(CAmount value, Coin& coin)
567 : {
568 320 : assert(value != ABSENT);
569 320 : coin.Clear();
570 320 : assert(coin.IsSpent());
571 320 : if (value != SPENT) {
572 160 : coin.out.nValue = value;
573 160 : coin.nHeight = 1;
574 160 : assert(!coin.IsSpent());
575 : }
576 320 : }
577 :
578 486 : static size_t InsertCoinsMapEntry(CCoinsMap& map, CAmount value, char flags)
579 : {
580 486 : if (value == ABSENT) {
581 166 : assert(flags == NO_ENTRY);
582 166 : return 0;
583 : }
584 320 : assert(flags != NO_ENTRY);
585 320 : CCoinsCacheEntry entry;
586 320 : entry.flags = flags;
587 320 : SetCoinsValue(value, entry.coin);
588 320 : auto inserted = map.emplace(OUTPOINT, std::move(entry));
589 320 : assert(inserted.second);
590 320 : return inserted.first->second.coin.DynamicMemoryUsage();
591 486 : }
592 :
593 178 : void GetCoinsMapEntry(const CCoinsMap& map, CAmount& value, char& flags)
594 : {
595 178 : auto it = map.find(OUTPOINT);
596 178 : if (it == map.end()) {
597 29 : value = ABSENT;
598 29 : flags = NO_ENTRY;
599 29 : } else {
600 149 : if (it->second.coin.IsSpent()) {
601 56 : value = SPENT;
602 56 : } else {
603 93 : value = it->second.coin.out.nValue;
604 : }
605 149 : flags = it->second.flags;
606 149 : assert(flags != NO_ENTRY);
607 : }
608 178 : }
609 :
610 288 : void WriteCoinsViewEntry(CCoinsView& view, CAmount value, char flags)
611 : {
612 288 : CCoinsMap map;
613 288 : InsertCoinsMapEntry(map, value, flags);
614 296 : BOOST_CHECK(view.BatchWrite(map, {}));
615 288 : }
616 :
617 396 : class SingleEntryCacheTest
618 : {
619 : public:
620 396 : SingleEntryCacheTest(CAmount base_value, CAmount cache_value, char cache_flags)
621 198 : {
622 198 : WriteCoinsViewEntry(base, base_value, base_value == ABSENT ? NO_ENTRY : DIRTY);
623 198 : cache.usage() += InsertCoinsMapEntry(cache.map(), cache_value, cache_flags);
624 396 : }
625 :
626 : CCoinsView root;
627 198 : CCoinsViewCacheTest base{&root};
628 198 : CCoinsViewCacheTest cache{&base};
629 : };
630 :
631 27 : static void CheckAccessCoin(CAmount base_value, CAmount cache_value, CAmount expected_value, char cache_flags, char expected_flags)
632 : {
633 27 : SingleEntryCacheTest test(base_value, cache_value, cache_flags);
634 27 : test.cache.AccessCoin(OUTPOINT);
635 27 : test.cache.SelfTest();
636 :
637 27 : CAmount result_value;
638 27 : char result_flags;
639 27 : GetCoinsMapEntry(test.cache.map(), result_value, result_flags);
640 27 : BOOST_CHECK_EQUAL(result_value, expected_value);
641 27 : BOOST_CHECK_EQUAL(result_flags, expected_flags);
642 27 : }
643 :
644 95 : BOOST_AUTO_TEST_CASE(ccoins_access)
645 : {
646 : /* Check AccessCoin behavior, requesting a coin from a cache view layered on
647 : * top of a base view, and checking the resulting entry in the cache after
648 : * the access.
649 : *
650 : * Base Cache Result Cache Result
651 : * Value Value Value Flags Flags
652 : */
653 1 : CheckAccessCoin(ABSENT, ABSENT, ABSENT, NO_ENTRY , NO_ENTRY );
654 1 : CheckAccessCoin(ABSENT, SPENT , SPENT , 0 , 0 );
655 1 : CheckAccessCoin(ABSENT, SPENT , SPENT , FRESH , FRESH );
656 1 : CheckAccessCoin(ABSENT, SPENT , SPENT , DIRTY , DIRTY );
657 1 : CheckAccessCoin(ABSENT, SPENT , SPENT , DIRTY|FRESH, DIRTY|FRESH);
658 1 : CheckAccessCoin(ABSENT, VALUE2, VALUE2, 0 , 0 );
659 1 : CheckAccessCoin(ABSENT, VALUE2, VALUE2, FRESH , FRESH );
660 1 : CheckAccessCoin(ABSENT, VALUE2, VALUE2, DIRTY , DIRTY );
661 1 : CheckAccessCoin(ABSENT, VALUE2, VALUE2, DIRTY|FRESH, DIRTY|FRESH);
662 1 : CheckAccessCoin(SPENT , ABSENT, ABSENT, NO_ENTRY , NO_ENTRY );
663 1 : CheckAccessCoin(SPENT , SPENT , SPENT , 0 , 0 );
664 1 : CheckAccessCoin(SPENT , SPENT , SPENT , FRESH , FRESH );
665 1 : CheckAccessCoin(SPENT , SPENT , SPENT , DIRTY , DIRTY );
666 1 : CheckAccessCoin(SPENT , SPENT , SPENT , DIRTY|FRESH, DIRTY|FRESH);
667 1 : CheckAccessCoin(SPENT , VALUE2, VALUE2, 0 , 0 );
668 1 : CheckAccessCoin(SPENT , VALUE2, VALUE2, FRESH , FRESH );
669 1 : CheckAccessCoin(SPENT , VALUE2, VALUE2, DIRTY , DIRTY );
670 1 : CheckAccessCoin(SPENT , VALUE2, VALUE2, DIRTY|FRESH, DIRTY|FRESH);
671 1 : CheckAccessCoin(VALUE1, ABSENT, VALUE1, NO_ENTRY , 0 );
672 1 : CheckAccessCoin(VALUE1, SPENT , SPENT , 0 , 0 );
673 1 : CheckAccessCoin(VALUE1, SPENT , SPENT , FRESH , FRESH );
674 1 : CheckAccessCoin(VALUE1, SPENT , SPENT , DIRTY , DIRTY );
675 1 : CheckAccessCoin(VALUE1, SPENT , SPENT , DIRTY|FRESH, DIRTY|FRESH);
676 1 : CheckAccessCoin(VALUE1, VALUE2, VALUE2, 0 , 0 );
677 1 : CheckAccessCoin(VALUE1, VALUE2, VALUE2, FRESH , FRESH );
678 1 : CheckAccessCoin(VALUE1, VALUE2, VALUE2, DIRTY , DIRTY );
679 1 : CheckAccessCoin(VALUE1, VALUE2, VALUE2, DIRTY|FRESH, DIRTY|FRESH);
680 1 : }
681 :
682 27 : static void CheckSpendCoins(CAmount base_value, CAmount cache_value, CAmount expected_value, char cache_flags, char expected_flags)
683 : {
684 27 : SingleEntryCacheTest test(base_value, cache_value, cache_flags);
685 27 : test.cache.SpendCoin(OUTPOINT);
686 27 : test.cache.SelfTest();
687 :
688 27 : CAmount result_value;
689 27 : char result_flags;
690 27 : GetCoinsMapEntry(test.cache.map(), result_value, result_flags);
691 27 : BOOST_CHECK_EQUAL(result_value, expected_value);
692 27 : BOOST_CHECK_EQUAL(result_flags, expected_flags);
693 27 : };
694 :
695 95 : BOOST_AUTO_TEST_CASE(ccoins_spend)
696 : {
697 : /* Check SpendCoin behavior, requesting a coin from a cache view layered on
698 : * top of a base view, spending, and then checking
699 : * the resulting entry in the cache after the modification.
700 : *
701 : * Base Cache Result Cache Result
702 : * Value Value Value Flags Flags
703 : */
704 1 : CheckSpendCoins(ABSENT, ABSENT, ABSENT, NO_ENTRY , NO_ENTRY );
705 1 : CheckSpendCoins(ABSENT, SPENT , SPENT , 0 , DIRTY );
706 1 : CheckSpendCoins(ABSENT, SPENT , ABSENT, FRESH , NO_ENTRY );
707 1 : CheckSpendCoins(ABSENT, SPENT , SPENT , DIRTY , DIRTY );
708 1 : CheckSpendCoins(ABSENT, SPENT , ABSENT, DIRTY|FRESH, NO_ENTRY );
709 1 : CheckSpendCoins(ABSENT, VALUE2, SPENT , 0 , DIRTY );
710 1 : CheckSpendCoins(ABSENT, VALUE2, ABSENT, FRESH , NO_ENTRY );
711 1 : CheckSpendCoins(ABSENT, VALUE2, SPENT , DIRTY , DIRTY );
712 1 : CheckSpendCoins(ABSENT, VALUE2, ABSENT, DIRTY|FRESH, NO_ENTRY );
713 1 : CheckSpendCoins(SPENT , ABSENT, ABSENT, NO_ENTRY , NO_ENTRY );
714 1 : CheckSpendCoins(SPENT , SPENT , SPENT , 0 , DIRTY );
715 1 : CheckSpendCoins(SPENT , SPENT , ABSENT, FRESH , NO_ENTRY );
716 1 : CheckSpendCoins(SPENT , SPENT , SPENT , DIRTY , DIRTY );
717 1 : CheckSpendCoins(SPENT , SPENT , ABSENT, DIRTY|FRESH, NO_ENTRY );
718 1 : CheckSpendCoins(SPENT , VALUE2, SPENT , 0 , DIRTY );
719 1 : CheckSpendCoins(SPENT , VALUE2, ABSENT, FRESH , NO_ENTRY );
720 1 : CheckSpendCoins(SPENT , VALUE2, SPENT , DIRTY , DIRTY );
721 1 : CheckSpendCoins(SPENT , VALUE2, ABSENT, DIRTY|FRESH, NO_ENTRY );
722 1 : CheckSpendCoins(VALUE1, ABSENT, SPENT , NO_ENTRY , DIRTY );
723 1 : CheckSpendCoins(VALUE1, SPENT , SPENT , 0 , DIRTY );
724 1 : CheckSpendCoins(VALUE1, SPENT , ABSENT, FRESH , NO_ENTRY );
725 1 : CheckSpendCoins(VALUE1, SPENT , SPENT , DIRTY , DIRTY );
726 1 : CheckSpendCoins(VALUE1, SPENT , ABSENT, DIRTY|FRESH, NO_ENTRY );
727 1 : CheckSpendCoins(VALUE1, VALUE2, SPENT , 0 , DIRTY );
728 1 : CheckSpendCoins(VALUE1, VALUE2, ABSENT, FRESH , NO_ENTRY );
729 1 : CheckSpendCoins(VALUE1, VALUE2, SPENT , DIRTY , DIRTY );
730 1 : CheckSpendCoins(VALUE1, VALUE2, ABSENT, DIRTY|FRESH, NO_ENTRY );
731 1 : }
732 :
733 54 : static void CheckAddCoinBase(CAmount base_value, CAmount cache_value, CAmount modify_value, CAmount expected_value, char cache_flags, char expected_flags, bool coinbase)
734 : {
735 54 : SingleEntryCacheTest test(base_value, cache_value, cache_flags);
736 :
737 54 : CAmount result_value;
738 54 : char result_flags;
739 : try {
740 54 : CTxOut output;
741 54 : output.nValue = modify_value;
742 54 : test.cache.AddCoin(OUTPOINT, Coin(std::move(output), 1, coinbase), coinbase);
743 42 : test.cache.SelfTest();
744 42 : GetCoinsMapEntry(test.cache.map(), result_value, result_flags);
745 54 : } catch (std::logic_error&) {
746 12 : result_value = FAIL;
747 12 : result_flags = NO_ENTRY;
748 12 : }
749 :
750 54 : BOOST_CHECK_EQUAL(result_value, expected_value);
751 54 : BOOST_CHECK_EQUAL(result_flags, expected_flags);
752 66 : }
753 :
754 : // Simple wrapper for CheckAddCoinBase function above that loops through
755 : // different possible base_values, making sure each one gives the same results.
756 : // This wrapper lets the coins_add test below be shorter and less repetitive,
757 : // while still verifying that the CoinsViewCache::AddCoin implementation
758 : // ignores base values.
759 : template <typename... Args>
760 18 : static void CheckAddCoin(Args&&... args)
761 : {
762 72 : for (const CAmount base_value : {ABSENT, SPENT, VALUE1})
763 54 : CheckAddCoinBase(base_value, std::forward<Args>(args)...);
764 18 : }
765 :
766 95 : BOOST_AUTO_TEST_CASE(ccoins_add)
767 : {
768 : /* Check AddCoin behavior, requesting a new coin from a cache view,
769 : * writing a modification to the coin, and then checking the resulting
770 : * entry in the cache after the modification. Verify behavior with the
771 : * AddCoin possible_overwrite argument set to false, and to true.
772 : *
773 : * Cache Write Result Cache Result possible_overwrite
774 : * Value Value Value Flags Flags
775 : */
776 1 : CheckAddCoin(ABSENT, VALUE3, VALUE3, NO_ENTRY , DIRTY|FRESH, false);
777 1 : CheckAddCoin(ABSENT, VALUE3, VALUE3, NO_ENTRY , DIRTY , true );
778 1 : CheckAddCoin(SPENT , VALUE3, VALUE3, 0 , DIRTY|FRESH, false);
779 1 : CheckAddCoin(SPENT , VALUE3, VALUE3, 0 , DIRTY , true );
780 1 : CheckAddCoin(SPENT , VALUE3, VALUE3, FRESH , DIRTY|FRESH, false);
781 1 : CheckAddCoin(SPENT , VALUE3, VALUE3, FRESH , DIRTY|FRESH, true );
782 1 : CheckAddCoin(SPENT , VALUE3, VALUE3, DIRTY , DIRTY , false);
783 1 : CheckAddCoin(SPENT , VALUE3, VALUE3, DIRTY , DIRTY , true );
784 1 : CheckAddCoin(SPENT , VALUE3, VALUE3, DIRTY|FRESH, DIRTY|FRESH, false);
785 1 : CheckAddCoin(SPENT , VALUE3, VALUE3, DIRTY|FRESH, DIRTY|FRESH, true );
786 1 : CheckAddCoin(VALUE2, VALUE3, FAIL , 0 , NO_ENTRY , false);
787 1 : CheckAddCoin(VALUE2, VALUE3, VALUE3, 0 , DIRTY , true );
788 1 : CheckAddCoin(VALUE2, VALUE3, FAIL , FRESH , NO_ENTRY , false);
789 1 : CheckAddCoin(VALUE2, VALUE3, VALUE3, FRESH , DIRTY|FRESH, true );
790 1 : CheckAddCoin(VALUE2, VALUE3, FAIL , DIRTY , NO_ENTRY , false);
791 1 : CheckAddCoin(VALUE2, VALUE3, VALUE3, DIRTY , DIRTY , true );
792 1 : CheckAddCoin(VALUE2, VALUE3, FAIL , DIRTY|FRESH, NO_ENTRY , false);
793 1 : CheckAddCoin(VALUE2, VALUE3, VALUE3, DIRTY|FRESH, DIRTY|FRESH, true );
794 1 : }
795 :
796 90 : void CheckWriteCoins(CAmount parent_value, CAmount child_value, CAmount expected_value, char parent_flags, char child_flags, char expected_flags)
797 : {
798 90 : SingleEntryCacheTest test(ABSENT, parent_value, parent_flags);
799 :
800 90 : CAmount result_value;
801 90 : char result_flags;
802 : try {
803 90 : WriteCoinsViewEntry(test.cache, child_value, child_flags);
804 82 : test.cache.SelfTest();
805 82 : GetCoinsMapEntry(test.cache.map(), result_value, result_flags);
806 8 : } catch (std::logic_error&) {
807 8 : result_value = FAIL;
808 8 : result_flags = NO_ENTRY;
809 8 : }
810 :
811 90 : BOOST_CHECK_EQUAL(result_value, expected_value);
812 90 : BOOST_CHECK_EQUAL(result_flags, expected_flags);
813 98 : }
814 :
815 95 : BOOST_AUTO_TEST_CASE(ccoins_write)
816 : {
817 : /* Check BatchWrite behavior, flushing one entry from a child cache to a
818 : * parent cache, and checking the resulting entry in the parent cache
819 : * after the write.
820 : *
821 : * Parent Child Result Parent Child Result
822 : * Value Value Value Flags Flags Flags
823 : */
824 1 : CheckWriteCoins(ABSENT, ABSENT, ABSENT, NO_ENTRY , NO_ENTRY , NO_ENTRY );
825 1 : CheckWriteCoins(ABSENT, SPENT , SPENT , NO_ENTRY , DIRTY , DIRTY );
826 1 : CheckWriteCoins(ABSENT, SPENT , ABSENT, NO_ENTRY , DIRTY|FRESH, NO_ENTRY );
827 1 : CheckWriteCoins(ABSENT, VALUE2, VALUE2, NO_ENTRY , DIRTY , DIRTY );
828 1 : CheckWriteCoins(ABSENT, VALUE2, VALUE2, NO_ENTRY , DIRTY|FRESH, DIRTY|FRESH);
829 1 : CheckWriteCoins(SPENT , ABSENT, SPENT , 0 , NO_ENTRY , 0 );
830 1 : CheckWriteCoins(SPENT , ABSENT, SPENT , FRESH , NO_ENTRY , FRESH );
831 1 : CheckWriteCoins(SPENT , ABSENT, SPENT , DIRTY , NO_ENTRY , DIRTY );
832 1 : CheckWriteCoins(SPENT , ABSENT, SPENT , DIRTY|FRESH, NO_ENTRY , DIRTY|FRESH);
833 1 : CheckWriteCoins(SPENT , SPENT , SPENT , 0 , DIRTY , DIRTY );
834 1 : CheckWriteCoins(SPENT , SPENT , SPENT , 0 , DIRTY|FRESH, DIRTY );
835 1 : CheckWriteCoins(SPENT , SPENT , ABSENT, FRESH , DIRTY , NO_ENTRY );
836 1 : CheckWriteCoins(SPENT , SPENT , ABSENT, FRESH , DIRTY|FRESH, NO_ENTRY );
837 1 : CheckWriteCoins(SPENT , SPENT , SPENT , DIRTY , DIRTY , DIRTY );
838 1 : CheckWriteCoins(SPENT , SPENT , SPENT , DIRTY , DIRTY|FRESH, DIRTY );
839 1 : CheckWriteCoins(SPENT , SPENT , ABSENT, DIRTY|FRESH, DIRTY , NO_ENTRY );
840 1 : CheckWriteCoins(SPENT , SPENT , ABSENT, DIRTY|FRESH, DIRTY|FRESH, NO_ENTRY );
841 1 : CheckWriteCoins(SPENT , VALUE2, VALUE2, 0 , DIRTY , DIRTY );
842 1 : CheckWriteCoins(SPENT , VALUE2, VALUE2, 0 , DIRTY|FRESH, DIRTY );
843 1 : CheckWriteCoins(SPENT , VALUE2, VALUE2, FRESH , DIRTY , DIRTY|FRESH);
844 1 : CheckWriteCoins(SPENT , VALUE2, VALUE2, FRESH , DIRTY|FRESH, DIRTY|FRESH);
845 1 : CheckWriteCoins(SPENT , VALUE2, VALUE2, DIRTY , DIRTY , DIRTY );
846 1 : CheckWriteCoins(SPENT , VALUE2, VALUE2, DIRTY , DIRTY|FRESH, DIRTY );
847 1 : CheckWriteCoins(SPENT , VALUE2, VALUE2, DIRTY|FRESH, DIRTY , DIRTY|FRESH);
848 1 : CheckWriteCoins(SPENT , VALUE2, VALUE2, DIRTY|FRESH, DIRTY|FRESH, DIRTY|FRESH);
849 1 : CheckWriteCoins(VALUE1, ABSENT, VALUE1, 0 , NO_ENTRY , 0 );
850 1 : CheckWriteCoins(VALUE1, ABSENT, VALUE1, FRESH , NO_ENTRY , FRESH );
851 1 : CheckWriteCoins(VALUE1, ABSENT, VALUE1, DIRTY , NO_ENTRY , DIRTY );
852 1 : CheckWriteCoins(VALUE1, ABSENT, VALUE1, DIRTY|FRESH, NO_ENTRY , DIRTY|FRESH);
853 1 : CheckWriteCoins(VALUE1, SPENT , SPENT , 0 , DIRTY , DIRTY );
854 1 : CheckWriteCoins(VALUE1, SPENT , FAIL , 0 , DIRTY|FRESH, NO_ENTRY );
855 1 : CheckWriteCoins(VALUE1, SPENT , ABSENT, FRESH , DIRTY , NO_ENTRY );
856 1 : CheckWriteCoins(VALUE1, SPENT , FAIL , FRESH , DIRTY|FRESH, NO_ENTRY );
857 1 : CheckWriteCoins(VALUE1, SPENT , SPENT , DIRTY , DIRTY , DIRTY );
858 1 : CheckWriteCoins(VALUE1, SPENT , FAIL , DIRTY , DIRTY|FRESH, NO_ENTRY );
859 1 : CheckWriteCoins(VALUE1, SPENT , ABSENT, DIRTY|FRESH, DIRTY , NO_ENTRY );
860 1 : CheckWriteCoins(VALUE1, SPENT , FAIL , DIRTY|FRESH, DIRTY|FRESH, NO_ENTRY );
861 1 : CheckWriteCoins(VALUE1, VALUE2, VALUE2, 0 , DIRTY , DIRTY );
862 1 : CheckWriteCoins(VALUE1, VALUE2, FAIL , 0 , DIRTY|FRESH, NO_ENTRY );
863 1 : CheckWriteCoins(VALUE1, VALUE2, VALUE2, FRESH , DIRTY , DIRTY|FRESH);
864 1 : CheckWriteCoins(VALUE1, VALUE2, FAIL , FRESH , DIRTY|FRESH, NO_ENTRY );
865 1 : CheckWriteCoins(VALUE1, VALUE2, VALUE2, DIRTY , DIRTY , DIRTY );
866 1 : CheckWriteCoins(VALUE1, VALUE2, FAIL , DIRTY , DIRTY|FRESH, NO_ENTRY );
867 1 : CheckWriteCoins(VALUE1, VALUE2, VALUE2, DIRTY|FRESH, DIRTY , DIRTY|FRESH);
868 1 : CheckWriteCoins(VALUE1, VALUE2, FAIL , DIRTY|FRESH, DIRTY|FRESH, NO_ENTRY );
869 :
870 : // The checks above omit cases where the child flags are not DIRTY, since
871 : // they would be too repetitive (the parent cache is never updated in these
872 : // cases). The loop below covers these cases and makes sure the parent cache
873 : // is always left unchanged.
874 4 : for (const CAmount parent_value : {ABSENT, SPENT, VALUE1})
875 12 : for (const CAmount child_value : {ABSENT, SPENT, VALUE2})
876 36 : for (const char parent_flags : parent_value == ABSENT ? ABSENT_FLAGS : FLAGS)
877 72 : for (const char child_flags : child_value == ABSENT ? ABSENT_FLAGS : CLEAN_FLAGS)
878 45 : CheckWriteCoins(parent_value, child_value, parent_value, parent_flags, child_flags, parent_flags);
879 1 : }
880 :
881 89 : BOOST_AUTO_TEST_SUITE_END()
|