Line data Source code
1 : // Copyright (c) 2014 The Bitcoin Core developers
2 : // Copyright (c) 2019-2021 The PIVX Core developers
3 : // Distributed under the MIT software license, see the accompanying
4 : // file COPYING or http://www.opensource.org/licenses/mit-license.php.
5 :
6 : #include "test/test_pivx.h"
7 :
8 : #include "coins.h"
9 : #include "script/standard.h"
10 : #include "uint256.h"
11 : #include "undo.h"
12 : #include "utilstrencodings.h"
13 : #include "random.h"
14 :
15 : #include "sapling/incrementalmerkletree.h"
16 :
17 : #include <vector>
18 : #include <map>
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, bool fSkipInvalid = false);
24 :
25 : namespace
26 : {
27 : //! equality test
28 912331 : bool operator==(const Coin &a, const Coin &b) {
29 : // Empty Coin objects are always equal.
30 912331 : if (a.IsSpent() && b.IsSpent()) return true;
31 218624 : return a.fCoinBase == b.fCoinBase &&
32 218624 : a.fCoinStake == b.fCoinStake &&
33 437248 : a.nHeight == b.nHeight &&
34 218624 : a.out == b.out;
35 : }
36 :
37 : class CCoinsViewTest : public CCoinsView
38 : {
39 : uint256 hashBestBlock_;
40 : std::map<COutPoint, Coin> map_;
41 :
42 : // Sapling
43 : uint256 hashBestSaplingAnchor_;
44 : std::map<uint256, SaplingMerkleTree> mapSaplingAnchors_;
45 : std::map<uint256, bool> mapSaplingNullifiers_;
46 :
47 : public:
48 45 : CCoinsViewTest() {
49 15 : hashBestSaplingAnchor_ = SaplingMerkleTree::empty_root();
50 15 : }
51 :
52 8997620 : bool GetCoin(const COutPoint& outpoint, Coin& coin) const
53 : {
54 8997620 : std::map<COutPoint, Coin>::const_iterator it = map_.find(outpoint);
55 8997620 : if (it == map_.end()) {
56 : return false;
57 : }
58 324948 : coin = it->second;
59 537026 : if (coin.IsSpent() && InsecureRandBool() == 0) {
60 : // Randomly return false in case of an empty entry.
61 105801 : return false;
62 : }
63 : return true;
64 : }
65 :
66 0 : bool HaveCoin(const COutPoint& outpoint) const
67 : {
68 0 : Coin coin;
69 0 : return GetCoin(outpoint, coin);
70 : }
71 :
72 0 : uint256 GetBestBlock() const { return hashBestBlock_; }
73 :
74 : // Sapling
75 :
76 12 : bool GetSaplingAnchorAt(const uint256& rt, SaplingMerkleTree &tree) const {
77 12 : if (rt == SaplingMerkleTree::empty_root()) {
78 4 : SaplingMerkleTree new_tree;
79 2 : tree = new_tree;
80 2 : return true;
81 : }
82 :
83 10 : std::map<uint256, SaplingMerkleTree>::const_iterator it = mapSaplingAnchors_.find(rt);
84 10 : if (it == mapSaplingAnchors_.end()) {
85 : return false;
86 : } else {
87 9 : tree = it->second;
88 9 : return true;
89 : }
90 : }
91 :
92 5 : bool GetNullifier(const uint256 &nf) const
93 : {
94 5 : const std::map<uint256, bool>* mapToUse = &mapSaplingNullifiers_;
95 5 : std::map<uint256, bool>::const_iterator it = mapToUse->find(nf);
96 5 : if (it == mapToUse->end()) {
97 : return false;
98 : } else {
99 : // The map shouldn't contain any false entries.
100 2 : assert(it->second);
101 : return true;
102 : }
103 : }
104 :
105 9 : uint256 GetBestAnchor() const {
106 9 : return hashBestSaplingAnchor_;
107 : }
108 :
109 196 : void BatchWriteNullifiers(CNullifiersMap& mapNullifiers, std::map<uint256, bool>& cacheNullifiers)
110 : {
111 203 : for (CNullifiersMap::iterator it = mapNullifiers.begin(); it != mapNullifiers.end(); ) {
112 7 : if (it->second.flags & CNullifiersCacheEntry::DIRTY) {
113 : // Same optimization used in CCoinsViewDB is to only write dirty entries.
114 7 : if (it->second.entered) {
115 5 : cacheNullifiers[it->first] = true;
116 : } else {
117 2 : cacheNullifiers.erase(it->first);
118 : }
119 : }
120 7 : mapNullifiers.erase(it++);
121 : }
122 196 : }
123 :
124 : template<typename Tree, typename Map, typename MapEntry>
125 196 : void BatchWriteAnchors(Map& mapAnchors, std::map<uint256, Tree>& cacheAnchors)
126 : {
127 208 : for (auto it = mapAnchors.begin(); it != mapAnchors.end(); ) {
128 12 : if (it->second.flags & MapEntry::DIRTY) {
129 : // Same optimization used in CCoinsViewDB is to only write dirty entries.
130 11 : if (it->second.entered) {
131 9 : if (it->first != Tree::empty_root()) {
132 27 : auto ret = cacheAnchors.insert(std::make_pair(it->first, Tree())).first;
133 9 : ret->second = it->second.tree;
134 : }
135 : } else {
136 12 : cacheAnchors.erase(it->first);
137 : }
138 : }
139 12 : mapAnchors.erase(it++);
140 : }
141 196 : }
142 :
143 196 : bool BatchWrite(CCoinsMap& mapCoins,
144 : const uint256& hashBlock,
145 : const uint256& hashSaplingAnchor,
146 : CAnchorsSaplingMap& mapSaplingAnchors,
147 : CNullifiersMap& mapSaplingNullifiers)
148 : {
149 241836 : for (CCoinsMap::iterator it = mapCoins.begin(); it != mapCoins.end(); ) {
150 241640 : if (it->second.flags & CCoinsCacheEntry::DIRTY) {
151 : // Same optimization used in CCoinsViewDB is to only write dirty entries.
152 65939 : map_[it->first] = it->second.coin;
153 93952 : if (it->second.coin.IsSpent() && InsecureRandRange(3) == 0) {
154 : // Randomly delete empty entries on write.
155 9445 : map_.erase(it->first);
156 : }
157 : }
158 241640 : mapCoins.erase(it++);
159 : }
160 :
161 196 : BatchWriteAnchors<SaplingMerkleTree, CAnchorsSaplingMap, CAnchorsSaplingCacheEntry>(mapSaplingAnchors, mapSaplingAnchors_);
162 196 : BatchWriteNullifiers(mapSaplingNullifiers, mapSaplingNullifiers_);
163 :
164 392 : if (!hashBlock.IsNull())
165 0 : hashBestBlock_ = hashBlock;
166 392 : if (!hashSaplingAnchor.IsNull())
167 11 : hashBestSaplingAnchor_ = hashSaplingAnchor;
168 196 : return true;
169 : }
170 : };
171 :
172 4 : class TxWithNullifiers
173 : {
174 : public:
175 : CTransactionRef tx;
176 : uint256 saplingNullifier;
177 :
178 5 : TxWithNullifiers()
179 5 : {
180 5 : CMutableTransaction mutableTx;
181 :
182 5 : saplingNullifier = GetRandHash();
183 5 : SpendDescription sd;
184 5 : sd.nullifier = saplingNullifier;
185 5 : mutableTx.sapData->vShieldedSpend.push_back(sd);
186 10 : tx = MakeTransactionRef(CTransaction(mutableTx));
187 5 : }
188 : };
189 :
190 :
191 342 : class CCoinsViewCacheTest : public CCoinsViewCache
192 : {
193 : public:
194 171 : explicit CCoinsViewCacheTest(CCoinsView* base) : CCoinsViewCache(base) {}
195 :
196 286 : void SelfTest() const
197 : {
198 : // Manually recompute the dynamic usage of the whole data, and compare it.
199 286 : size_t ret = memusage::DynamicUsage(cacheCoins) +
200 286 : memusage::DynamicUsage(cacheSaplingAnchors) +
201 286 : memusage::DynamicUsage(cacheSaplingNullifiers);
202 286 : size_t count = 0;
203 426486 : for (const auto& entry : cacheCoins) {
204 426200 : ret += memusage::DynamicUsage(entry.second.coin);
205 426200 : ++count;
206 : }
207 286 : BOOST_CHECK_EQUAL(GetCacheSize(), count);
208 286 : BOOST_CHECK_EQUAL(memusage::DynamicUsage(*this), ret);
209 286 : }
210 :
211 322 : CCoinsMap& map() { return cacheCoins; }
212 171 : size_t& usage() { return cachedCoinsUsage; }
213 : };
214 :
215 : }
216 :
217 : template<typename Tree> bool GetAnchorAt(const CCoinsViewCache &cache, const uint256 &rt, Tree &tree);
218 17 : template<> bool GetAnchorAt(const CCoinsViewCache &cache, const uint256 &rt, SaplingMerkleTree &tree) { return cache.GetSaplingAnchorAt(rt, tree); }
219 :
220 : BOOST_FIXTURE_TEST_SUITE(coins_tests, BasicTestingSetup)
221 :
222 13 : void checkNullifierCache(const CCoinsViewCache &cache, const TxWithNullifiers &txWithNullifiers, bool shouldBeInCache) {
223 : // Check if the nullifiers either are or are not in the cache
224 13 : bool containsSaplingNullifier = cache.GetNullifier(txWithNullifiers.saplingNullifier);
225 26 : BOOST_CHECK(containsSaplingNullifier == shouldBeInCache);
226 13 : }
227 :
228 2 : BOOST_AUTO_TEST_CASE(nullifier_regression_test)
229 : {
230 : // Correct behavior:
231 1 : {
232 2 : CCoinsViewTest base;
233 1 : CCoinsViewCache cache1(&base);
234 :
235 2 : TxWithNullifiers txWithNullifiers;
236 :
237 : // Insert a nullifier into the base.
238 1 : cache1.SetNullifiers(*txWithNullifiers.tx, true);
239 1 : checkNullifierCache(cache1, txWithNullifiers, true);
240 1 : cache1.Flush(); // Flush to base.
241 :
242 : // Remove the nullifier from cache
243 1 : cache1.SetNullifiers(*txWithNullifiers.tx, false);
244 :
245 : // The nullifier now should be `false`.
246 1 : checkNullifierCache(cache1, txWithNullifiers, false);
247 : }
248 :
249 : // Also correct behavior:
250 1 : {
251 2 : CCoinsViewTest base;
252 1 : CCoinsViewCache cache1(&base);
253 :
254 2 : TxWithNullifiers txWithNullifiers;
255 :
256 : // Insert a nullifier into the base.
257 1 : cache1.SetNullifiers(*txWithNullifiers.tx, true);
258 1 : checkNullifierCache(cache1, txWithNullifiers, true);
259 1 : cache1.Flush(); // Flush to base.
260 :
261 : // Remove the nullifier from cache
262 1 : cache1.SetNullifiers(*txWithNullifiers.tx, false);
263 1 : cache1.Flush(); // Flush to base.
264 :
265 : // The nullifier now should be `false`.
266 1 : checkNullifierCache(cache1, txWithNullifiers, false);
267 : }
268 :
269 : // Works because we bring it from the parent cache:
270 1 : {
271 2 : CCoinsViewTest base;
272 1 : CCoinsViewCache cache1(&base);
273 :
274 : // Insert a nullifier into the base.
275 2 : TxWithNullifiers txWithNullifiers;
276 1 : cache1.SetNullifiers(*txWithNullifiers.tx, true);
277 1 : checkNullifierCache(cache1, txWithNullifiers, true);
278 1 : cache1.Flush(); // Empties cache.
279 :
280 : // Create cache on top.
281 1 : {
282 : // Remove the nullifier.
283 2 : CCoinsViewCache cache2(&cache1);
284 1 : checkNullifierCache(cache2, txWithNullifiers, true);
285 1 : cache1.SetNullifiers(*txWithNullifiers.tx, false);
286 1 : cache2.Flush(); // Empties cache, flushes to cache1.
287 : }
288 :
289 : // The nullifier now should be `false`.
290 1 : checkNullifierCache(cache1, txWithNullifiers, false);
291 : }
292 :
293 : // Was broken:
294 1 : {
295 2 : CCoinsViewTest base;
296 1 : CCoinsViewCache cache1(&base);
297 :
298 : // Insert a nullifier into the base.
299 2 : TxWithNullifiers txWithNullifiers;
300 1 : cache1.SetNullifiers(*txWithNullifiers.tx, true);
301 1 : cache1.Flush(); // Empties cache.
302 :
303 : // Create cache on top.
304 1 : {
305 : // Remove the nullifier.
306 2 : CCoinsViewCache cache2(&cache1);
307 1 : cache2.SetNullifiers(*txWithNullifiers.tx, false);
308 1 : cache2.Flush(); // Empties cache, flushes to cache1.
309 : }
310 :
311 : // The nullifier now should be `false`.
312 1 : checkNullifierCache(cache1, txWithNullifiers, false);
313 : }
314 1 : }
315 :
316 1 : template<typename Tree> void anchorPopRegressionTestImpl()
317 : {
318 : // Correct behavior:
319 : {
320 2 : CCoinsViewTest base;
321 1 : CCoinsViewCache cache1(&base);
322 :
323 : // Create dummy anchor/commitment
324 1 : Tree tree;
325 1 : tree.append(GetRandHash());
326 :
327 : // Add the anchor
328 1 : cache1.PushAnchor(tree);
329 1 : cache1.Flush();
330 :
331 : // Remove the anchor
332 1 : cache1.PopAnchor(Tree::empty_root());
333 1 : cache1.Flush();
334 :
335 : // Add the anchor back
336 1 : cache1.PushAnchor(tree);
337 1 : cache1.Flush();
338 :
339 : // The base contains the anchor, of course!
340 : {
341 1 : Tree checkTree;
342 2 : BOOST_CHECK(GetAnchorAt(cache1, tree.root(), checkTree));
343 2 : BOOST_CHECK(checkTree.root() == tree.root());
344 : }
345 : }
346 :
347 : // Previously incorrect behavior
348 : {
349 2 : CCoinsViewTest base;
350 1 : CCoinsViewCache cache1(&base);
351 :
352 : // Create dummy anchor/commitment
353 1 : Tree tree;
354 1 : tree.append(GetRandHash());
355 :
356 : // Add the anchor and flush to disk
357 1 : cache1.PushAnchor(tree);
358 1 : cache1.Flush();
359 :
360 : // Remove the anchor, but don't flush yet!
361 1 : cache1.PopAnchor(Tree::empty_root());
362 :
363 : {
364 2 : CCoinsViewCache cache2(&cache1); // Build cache on top
365 1 : cache2.PushAnchor(tree); // Put the same anchor back!
366 1 : cache2.Flush(); // Flush to cache1
367 : }
368 :
369 : // cache2's flush kinda worked, i.e. cache1 thinks the
370 : // tree is there, but it didn't bring down the correct
371 : // treestate...
372 : {
373 0 : Tree checktree;
374 2 : BOOST_CHECK(GetAnchorAt(cache1, tree.root(), checktree));
375 2 : BOOST_CHECK(checktree.root() == tree.root()); // Oh, shucks.
376 : }
377 :
378 : // Flushing cache won't help either, just makes the inconsistency
379 : // permanent.
380 1 : cache1.Flush();
381 : {
382 1 : Tree checktree;
383 2 : BOOST_CHECK(GetAnchorAt(cache1, tree.root(), checktree));
384 2 : BOOST_CHECK(checktree.root() == tree.root()); // Oh, shucks.
385 : }
386 : }
387 1 : }
388 :
389 2 : BOOST_AUTO_TEST_CASE(anchor_pop_regression_test)
390 : {
391 1 : anchorPopRegressionTestImpl<SaplingMerkleTree>();
392 1 : }
393 :
394 1 : template<typename Tree> void anchorRegressionTestImpl()
395 : {
396 : // Correct behavior:
397 : {
398 2 : CCoinsViewTest base;
399 1 : CCoinsViewCache cache1(&base);
400 :
401 : // Insert anchor into base.
402 1 : Tree tree;
403 1 : tree.append(GetRandHash());
404 :
405 1 : cache1.PushAnchor(tree);
406 1 : cache1.Flush();
407 :
408 1 : cache1.PopAnchor(Tree::empty_root());
409 3 : BOOST_CHECK(cache1.GetBestAnchor() == Tree::empty_root());
410 2 : BOOST_CHECK(!GetAnchorAt(cache1, tree.root(), tree));
411 : }
412 :
413 : // Also correct behavior:
414 : {
415 2 : CCoinsViewTest base;
416 1 : CCoinsViewCache cache1(&base);
417 :
418 : // Insert anchor into base.
419 1 : Tree tree;
420 1 : tree.append(GetRandHash());
421 1 : cache1.PushAnchor(tree);
422 1 : cache1.Flush();
423 :
424 1 : cache1.PopAnchor(Tree::empty_root());
425 1 : cache1.Flush();
426 3 : BOOST_CHECK(cache1.GetBestAnchor() == Tree::empty_root());
427 2 : BOOST_CHECK(!GetAnchorAt(cache1, tree.root(), tree));
428 : }
429 :
430 : // Works because we bring the anchor in from parent cache.
431 : {
432 2 : CCoinsViewTest base;
433 1 : CCoinsViewCache cache1(&base);
434 :
435 : // Insert anchor into base.
436 1 : Tree tree;
437 1 : tree.append(GetRandHash());
438 1 : cache1.PushAnchor(tree);
439 1 : cache1.Flush();
440 :
441 : {
442 : // Pop anchor.
443 2 : CCoinsViewCache cache2(&cache1);
444 2 : BOOST_CHECK(GetAnchorAt(cache2, tree.root(), tree));
445 1 : cache2.PopAnchor(Tree::empty_root());
446 1 : cache2.Flush();
447 : }
448 :
449 3 : BOOST_CHECK(cache1.GetBestAnchor() == Tree::empty_root());
450 2 : BOOST_CHECK(!GetAnchorAt(cache1, tree.root(), tree));
451 : }
452 :
453 : // Was broken:
454 : {
455 2 : CCoinsViewTest base;
456 1 : CCoinsViewCache cache1(&base);
457 :
458 : // Insert anchor into base.
459 1 : Tree tree;
460 1 : tree.append(GetRandHash());
461 1 : cache1.PushAnchor(tree);
462 1 : cache1.Flush();
463 :
464 : {
465 : // Pop anchor.
466 2 : CCoinsViewCache cache2(&cache1);
467 1 : cache2.PopAnchor(Tree::empty_root());
468 1 : cache2.Flush();
469 : }
470 :
471 3 : BOOST_CHECK(cache1.GetBestAnchor() == Tree::empty_root());
472 2 : BOOST_CHECK(!GetAnchorAt(cache1, tree.root(), tree));
473 : }
474 1 : }
475 :
476 2 : BOOST_AUTO_TEST_CASE(anchor_regression_test)
477 : {
478 1 : anchorRegressionTestImpl<SaplingMerkleTree>();
479 1 : }
480 :
481 2 : BOOST_AUTO_TEST_CASE(nullifiers_test)
482 : {
483 2 : CCoinsViewTest base;
484 1 : CCoinsViewCache cache(&base);
485 :
486 2 : TxWithNullifiers txWithNullifiers;
487 1 : checkNullifierCache(cache, txWithNullifiers, false);
488 1 : cache.SetNullifiers(*txWithNullifiers.tx, true);
489 1 : checkNullifierCache(cache, txWithNullifiers, true);
490 1 : cache.Flush();
491 :
492 2 : CCoinsViewCache cache2(&base);
493 :
494 1 : checkNullifierCache(cache2, txWithNullifiers, true);
495 1 : cache2.SetNullifiers(*txWithNullifiers.tx, false);
496 1 : checkNullifierCache(cache2, txWithNullifiers, false);
497 1 : cache2.Flush();
498 :
499 2 : CCoinsViewCache cache3(&base);
500 :
501 1 : checkNullifierCache(cache3, txWithNullifiers, false);
502 1 : }
503 :
504 1 : template<typename Tree> void anchorsFlushImpl()
505 : {
506 2 : CCoinsViewTest base;
507 1 : uint256 newrt;
508 : {
509 1 : CCoinsViewCache cache(&base);
510 1 : Tree tree;
511 2 : BOOST_CHECK(GetAnchorAt(cache, cache.GetBestAnchor(), tree));
512 1 : tree.append(GetRandHash());
513 :
514 1 : newrt = tree.root();
515 :
516 1 : cache.PushAnchor(tree);
517 1 : cache.Flush();
518 : }
519 :
520 : {
521 1 : CCoinsViewCache cache(&base);
522 1 : Tree tree;
523 2 : BOOST_CHECK(GetAnchorAt(cache, cache.GetBestAnchor(), tree));
524 :
525 : // Get the cached entry.
526 2 : BOOST_CHECK(GetAnchorAt(cache, cache.GetBestAnchor(), tree));
527 :
528 1 : uint256 check_rt = tree.root();
529 :
530 2 : BOOST_CHECK(check_rt == newrt);
531 : }
532 1 : }
533 :
534 2 : BOOST_AUTO_TEST_CASE(anchors_flush_test)
535 : {
536 1 : anchorsFlushImpl<SaplingMerkleTree>();
537 1 : }
538 :
539 1 : template<typename Tree> void anchorsTestImpl()
540 : {
541 : // TODO: These tests should be more methodical.
542 : // Or, integrate with Bitcoin's tests later.
543 :
544 2 : CCoinsViewTest base;
545 1 : CCoinsViewCache cache(&base);
546 :
547 3 : BOOST_CHECK(cache.GetBestAnchor() == Tree::empty_root());
548 :
549 : {
550 1 : Tree tree;
551 :
552 2 : BOOST_CHECK(GetAnchorAt(cache, cache.GetBestAnchor(), tree));
553 2 : BOOST_CHECK(cache.GetBestAnchor() == tree.root());
554 1 : tree.append(GetRandHash());
555 1 : tree.append(GetRandHash());
556 1 : tree.append(GetRandHash());
557 1 : tree.append(GetRandHash());
558 1 : tree.append(GetRandHash());
559 1 : tree.append(GetRandHash());
560 1 : tree.append(GetRandHash());
561 :
562 1 : Tree save_tree_for_later;
563 1 : save_tree_for_later = tree;
564 :
565 1 : uint256 newrt = tree.root();
566 1 : uint256 newrt2;
567 :
568 1 : cache.PushAnchor(tree);
569 2 : BOOST_CHECK(cache.GetBestAnchor() == newrt);
570 :
571 : {
572 0 : Tree confirm_same;
573 2 : BOOST_CHECK(GetAnchorAt(cache, cache.GetBestAnchor(), confirm_same));
574 :
575 2 : BOOST_CHECK(confirm_same.root() == newrt);
576 : }
577 :
578 1 : tree.append(GetRandHash());
579 1 : tree.append(GetRandHash());
580 :
581 1 : newrt2 = tree.root();
582 :
583 1 : cache.PushAnchor(tree);
584 2 : BOOST_CHECK(cache.GetBestAnchor() == newrt2);
585 :
586 1 : Tree test_tree;
587 2 : BOOST_CHECK(GetAnchorAt(cache, cache.GetBestAnchor(), test_tree));
588 :
589 2 : BOOST_CHECK(tree.root() == test_tree.root());
590 :
591 : {
592 0 : Tree test_tree2;
593 1 : GetAnchorAt(cache, newrt, test_tree2);
594 :
595 2 : BOOST_CHECK(test_tree2.root() == newrt);
596 : }
597 :
598 : {
599 1 : cache.PopAnchor(newrt);
600 1 : Tree obtain_tree;
601 1 : assert(!GetAnchorAt(cache, newrt2, obtain_tree)); // should have been popped off
602 1 : assert(GetAnchorAt(cache, newrt, obtain_tree));
603 :
604 1 : assert(obtain_tree.root() == newrt);
605 : }
606 : }
607 1 : }
608 :
609 2 : BOOST_AUTO_TEST_CASE(anchors_test)
610 : {
611 1 : anchorsTestImpl<SaplingMerkleTree>();
612 1 : }
613 :
614 : static const unsigned int NUM_SIMULATION_ITERATIONS = 40000;
615 :
616 : // This is a large randomized insert/remove simulation test on a variable-size
617 : // stack of caches on top of CCoinsViewTest.
618 : //
619 : // It will randomly create/update/delete Coin entries to a tip of caches, with
620 : // txids picked from a limited list of random 256-bit hashes. Occasionally, a
621 : // new tip is added to the stack of caches, or the tip is flushed and removed.
622 : //
623 : // During the process, booleans are kept to make sure that the randomized
624 : // operation hits all branches.
625 2 : BOOST_AUTO_TEST_CASE(coins_cache_simulation_test)
626 : {
627 : // Various coverage trackers.
628 1 : bool removed_all_caches = false;
629 1 : bool reached_4_caches = false;
630 1 : bool added_an_entry = false;
631 1 : bool added_an_unspendable_entry = false;
632 1 : bool removed_an_entry = false;
633 1 : bool updated_an_entry = false;
634 1 : bool found_an_entry = false;
635 1 : bool missed_an_entry = false;
636 1 : bool uncached_an_entry = false;
637 :
638 : // A simple map to track what we expect the cache stack to represent.
639 1 : std::map<COutPoint, Coin> result;
640 :
641 : // The cache stack.
642 2 : CCoinsViewTest base; // A CCoinsViewTest at the bottom.
643 2 : std::vector<CCoinsViewCacheTest*> stack; // A stack of CCoinsViewCaches on top.
644 1 : stack.push_back(new CCoinsViewCacheTest(&base)); // Start with one cache.
645 :
646 : // Use a limited set of random transaction ids, so we do test overwriting entries.
647 2 : std::vector<uint256> txids;
648 1 : txids.resize(NUM_SIMULATION_ITERATIONS / 8);
649 5001 : for (unsigned int i = 0; i < txids.size(); i++) {
650 5000 : txids[i] = InsecureRand256();
651 : }
652 :
653 40001 : for (unsigned int i = 0; i < NUM_SIMULATION_ITERATIONS; i++) {
654 : // Do a random modification.
655 40000 : {
656 40000 : uint256 txid = txids[InsecureRandRange(txids.size())]; // txid we're going to modify in this iteration.
657 40000 : Coin& coin = result[COutPoint(txid, 0)];
658 80000 : const Coin& entry = (InsecureRandRange(500) == 0) ?
659 86 : AccessByTxid(*stack.back(), txid) : stack.back()->AccessCoin(COutPoint(txid, 0)
660 39914 : );
661 80000 : BOOST_CHECK(coin == entry);
662 80000 : if (InsecureRandRange(5) == 0 || coin.IsSpent()) {
663 47862 : Coin newcoin;
664 23931 : newcoin.out.nValue = InsecureRand32();
665 23931 : newcoin.nHeight = 1;
666 47862 : if (InsecureRandRange(16) == 0 && coin.IsSpent()) {
667 1230 : newcoin.out.scriptPubKey.assign(1 + (InsecureRand32() & 0x3F), OP_RETURN);
668 3690 : BOOST_CHECK(newcoin.out.scriptPubKey.IsUnspendable());
669 1230 : added_an_unspendable_entry = true;
670 : } else {
671 22701 : newcoin.out.scriptPubKey.assign(InsecureRand32() & 0x3F, 0); // Random sizes so we can test memory usage accounting
672 22701 : (coin.IsSpent() ? added_an_entry : updated_an_entry) = true;
673 22701 : coin = newcoin;
674 : }
675 25761 : stack.back()->AddCoin(COutPoint(txid, 0), std::move(newcoin), !coin.IsSpent() || InsecureRandBool());
676 : } else {
677 16069 : removed_an_entry = true;
678 16069 : coin.Clear();
679 16069 : stack.back()->SpendCoin(COutPoint(txid, 0));
680 : }
681 : }
682 :
683 : // One every 10 iterations, remove a random entry from the cache
684 80000 : if (InsecureRandRange(10) == 0) {
685 4083 : COutPoint out(txids[InsecureRandRange(txids.size())], 0);
686 4083 : int cacheid = InsecureRandRange(stack.size());
687 4083 : stack[cacheid]->Uncache(out);
688 4083 : uncached_an_entry |= !stack[cacheid]->HaveCoinInCache(out);
689 : }
690 :
691 : // Once every 1000 iterations and at the end, verify the full cache.
692 80000 : if (InsecureRandRange(1000) == 1 || i == NUM_SIMULATION_ITERATIONS - 1) {
693 224696 : for (const auto& entry : result) {
694 224645 : bool have = stack.back()->HaveCoin(entry.first);
695 224645 : const Coin& coin = stack.back()->AccessCoin(entry.first);
696 449290 : BOOST_CHECK(have == !coin.IsSpent());
697 449290 : BOOST_CHECK(coin == entry.second);
698 224645 : if (coin.IsSpent()) {
699 : missed_an_entry = true;
700 : } else {
701 258632 : BOOST_CHECK(stack.back()->HaveCoinInCache(entry.first));
702 129316 : found_an_entry = true;
703 : }
704 : }
705 186 : for (const CCoinsViewCacheTest *test : stack) {
706 135 : test->SelfTest();
707 : }
708 : }
709 :
710 80000 : if (InsecureRandRange(100) == 0) {
711 : // Every 100 iterations, flush an intermediate cache
712 742 : if (stack.size() > 1 && InsecureRandBool() == 0) {
713 178 : unsigned int flushIndex = InsecureRandRange(stack.size() - 1) == 0;
714 178 : stack[flushIndex]->Flush();
715 : }
716 : }
717 :
718 91310 : if (InsecureRandRange(100) == 0) {
719 : // Every 100 iterations, change the cache stack.
720 782 : if (stack.size() > 0 && InsecureRandBool() == 0) {
721 : //Remove the top cache
722 196 : stack.back()->Flush();
723 196 : delete stack.back();
724 196 : stack.pop_back();
725 : }
726 696 : if (stack.size() == 0 || (stack.size() < 4 && InsecureRandBool())) {
727 : //Add a new cache
728 197 : CCoinsView* tip = &base;
729 197 : if (stack.size() > 0) {
730 164 : tip = stack.back();
731 : } else {
732 : removed_all_caches = true;
733 : }
734 197 : stack.push_back(new CCoinsViewCacheTest(tip));
735 197 : if (stack.size() == 4) {
736 65 : reached_4_caches = true;
737 : }
738 : }
739 : }
740 : }
741 :
742 : // Clean up the stack.
743 3 : while (stack.size() > 0) {
744 2 : delete stack.back();
745 3 : stack.pop_back();
746 : }
747 :
748 : // Verify coverage.
749 2 : BOOST_CHECK(removed_all_caches);
750 2 : BOOST_CHECK(reached_4_caches);
751 2 : BOOST_CHECK(added_an_entry);
752 2 : BOOST_CHECK(added_an_unspendable_entry);
753 2 : BOOST_CHECK(removed_an_entry);
754 2 : BOOST_CHECK(updated_an_entry);
755 2 : BOOST_CHECK(found_an_entry);
756 2 : BOOST_CHECK(missed_an_entry);
757 2 : BOOST_CHECK(uncached_an_entry);
758 1 : }
759 :
760 : // Store of all necessary tx and undo data for next test
761 : typedef std::map<COutPoint, std::tuple<CTransaction,CTxUndo,Coin>> UtxoData;
762 : UtxoData utxoData;
763 :
764 39708 : UtxoData::iterator FindRandomFrom(const std::set<COutPoint>& utxoSet) {
765 39708 : assert(utxoSet.size());
766 39708 : auto utxoSetIt = utxoSet.lower_bound(COutPoint(GetRandHash(), 0));
767 39708 : if (utxoSetIt == utxoSet.end()) {
768 98 : utxoSetIt = utxoSet.begin();
769 : }
770 39708 : auto utxoDataIt = utxoData.find(*utxoSetIt);
771 39708 : assert(utxoDataIt != utxoData.end());
772 39708 : return utxoDataIt;
773 : }
774 :
775 : // This test is similar to the previous test
776 : // except the emphasis is on testing the functionality of UpdateCoins
777 : // random txs are created and UpdateCoins is used to update the cache stack
778 2 : BOOST_AUTO_TEST_CASE(updatecoins_simulation_test)
779 : {
780 : // A simple map to track what we expect the cache stack to represent.
781 1 : std::map<COutPoint, Coin> result;
782 :
783 : // The cache stack.
784 2 : CCoinsViewTest base; // A CCoinsViewTest at the bottom.
785 2 : std::vector<CCoinsViewCacheTest*> stack; // A stack of CCoinsViewCaches on top.
786 1 : stack.push_back(new CCoinsViewCacheTest(&base)); // Start with one cache.
787 :
788 : // Track the txids we've used in various sets
789 2 : std::set<COutPoint> coinbase_coins;
790 1 : std::set<COutPoint> disconnected_coins;
791 1 : std::set<COutPoint> utxoset;
792 :
793 40001 : for (unsigned int i = 0; i < NUM_SIMULATION_ITERATIONS; i++) {
794 40000 : uint32_t randiter = InsecureRand32();
795 :
796 : // 19/20 txs add a new transaction
797 40000 : if (randiter % 20 < 19) {
798 37986 : CMutableTransaction tx;
799 37986 : tx.vin.resize(1);
800 37986 : tx.vout.resize(1);
801 37986 : tx.vout[0].nValue = i; //Keep txs unique unless intended to duplicate
802 37986 : unsigned int height = InsecureRand32();
803 73929 : Coin old_coin;
804 :
805 : // 2/20 times create a new coinbase
806 37986 : if (randiter % 20 < 2 || coinbase_coins.size() < 10) {
807 : // PIVX: don't test for duplicate coinbases as those are not possible due to
808 : // BIP34 enforced since the beginning.
809 4060 : assert(CTransaction(tx).IsCoinBase());
810 4060 : coinbase_coins.insert(COutPoint(tx.GetHash(), 0));
811 : }
812 :
813 : // 17/20 times reconnect previous or add a regular tx
814 : else {
815 33926 : COutPoint prevout;
816 : // 1/20 times reconnect a previously disconnected tx
817 33926 : if (randiter % 20 == 2 && disconnected_coins.size()) {
818 2043 : auto utxod = FindRandomFrom(coinbase_coins);
819 2043 : tx = std::get<0>(utxod->second);
820 2043 : prevout = tx.vin[0].prevout;
821 : // PIVX: no duplicates
822 6129 : BOOST_CHECK(!utxoset.count(prevout));
823 2043 : disconnected_coins.erase(utxod->first);
824 2043 : continue;
825 : }
826 :
827 : // 16/20 times create a regular tx
828 : else {
829 31883 : auto utxod = FindRandomFrom(utxoset);
830 31883 : prevout = utxod->first;
831 :
832 : // Construct the tx to spend the coins of prevouthash
833 31883 : tx.vin[0].prevout = prevout;
834 31883 : assert(!CTransaction(tx).IsCoinBase());
835 : }
836 : // In this simple test coins only have two states, spent or unspent, save the unspent state to restore
837 31883 : old_coin = result[prevout];
838 :
839 : // Update the expected result of prevouthash to know these coins are spent
840 31883 : result[prevout].Clear();
841 :
842 31883 : utxoset.erase(prevout);
843 :
844 : }
845 : // Update the expected result to know about the new output coins
846 35943 : assert(tx.vout.size() == 1);
847 35943 : const COutPoint outpoint(tx.GetHash(), 0);
848 35943 : result[outpoint] = Coin(tx.vout[0], height, CTransaction(tx).IsCoinBase(), CTransaction(tx).IsCoinStake());
849 :
850 : // Call UpdateCoins on the top cache
851 71886 : CTxUndo undo;
852 35943 : UpdateCoins(tx, *(stack.back()), undo, height);
853 :
854 : // Update the utxo set for future spends
855 35943 : utxoset.insert(outpoint);
856 :
857 : // Track this tx and undo info to use later
858 107829 : utxoData.emplace(outpoint, std::make_tuple(tx,undo,old_coin));
859 :
860 2014 : } else if (utxoset.size()) {
861 : //1/20 times undo a previous transaction
862 2014 : auto utxod = FindRandomFrom(utxoset);
863 :
864 2014 : CTransaction& tx = std::get<0>(utxod->second);
865 2014 : CTxUndo& undo = std::get<1>(utxod->second);
866 2014 : Coin& orig_coin = std::get<2>(utxod->second);
867 :
868 : // Update the expected result
869 : // Remove new outputs
870 2014 : result[utxod->first].Clear();
871 : // If not coinbase restore prevout
872 2014 : if (!tx.IsCoinBase()) {
873 1786 : result[tx.vin[0].prevout] = orig_coin;
874 : }
875 :
876 : // Disconnect the tx from the current UTXO
877 : // See code in DisconnectBlock
878 : // remove outputs
879 2014 : stack.back()->SpendCoin(utxod->first);
880 :
881 : // restore inputs
882 2014 : if (!tx.IsCoinBase()) {
883 1786 : const COutPoint &out = tx.vin[0].prevout;
884 3572 : Coin coin = undo.vprevout[0];
885 1786 : ApplyTxInUndo(std::move(coin), *(stack.back()), out);
886 : }
887 : // Store as a candidate for reconnection
888 2014 : disconnected_coins.insert(utxod->first);
889 :
890 : // Update the utxoset
891 2014 : utxoset.erase(utxod->first);
892 2014 : if (!tx.IsCoinBase())
893 3800 : utxoset.insert(tx.vin[0].prevout);
894 : }
895 :
896 : // Once every 1000 iterations and at the end, verify the full cache.
897 76798 : if (InsecureRandRange(1000) == 1 || i == NUM_SIMULATION_ITERATIONS - 1) {
898 647723 : for (const auto& entry : result) {
899 647686 : bool have = stack.back()->HaveCoin(entry.first);
900 647686 : const Coin& coin = stack.back()->AccessCoin(entry.first);
901 1295370 : BOOST_CHECK(have == !coin.IsSpent());
902 1295370 : BOOST_CHECK(coin == entry.second);
903 : }
904 : }
905 :
906 : // One every 10 iterations, remove a random entry from the cache
907 75912 : if (utxoset.size() > 1 && InsecureRandRange(20) == 0) {
908 1885 : stack[InsecureRandRange(stack.size())]->Uncache(FindRandomFrom(utxoset)->first);
909 : }
910 75909 : if (disconnected_coins.size() > 1 && InsecureRandRange(20) == 0) {
911 1883 : stack[InsecureRandRange(stack.size())]->Uncache(FindRandomFrom(disconnected_coins)->first);
912 : }
913 :
914 86777 : if (InsecureRandRange(100) == 0) {
915 : // Every 100 iterations, change the cache stack.
916 752 : if (stack.size() > 0 && InsecureRandBool() == 0) {
917 202 : stack.back()->Flush();
918 202 : delete stack.back();
919 202 : stack.pop_back();
920 : }
921 662 : if (stack.size() == 0 || (stack.size() < 4 && InsecureRandBool())) {
922 204 : CCoinsView* tip = &base;
923 204 : if (stack.size() > 0) {
924 136 : tip = stack.back();
925 : }
926 204 : stack.push_back(new CCoinsViewCacheTest(tip));
927 : }
928 : }
929 : }
930 :
931 : // Clean up the stack.
932 4 : while (stack.size() > 0) {
933 3 : delete stack.back();
934 4 : stack.pop_back();
935 : }
936 1 : }
937 :
938 2 : BOOST_AUTO_TEST_CASE(ccoins_serialization)
939 : {
940 : // Good example
941 2 : CDataStream ss1(ParseHex("00835800816115944e077fe7c803cfa57f29b36bf87c1d35"), SER_DISK, CLIENT_VERSION);
942 2 : Coin cc1;
943 1 : ss1 >> cc1;
944 1 : BOOST_CHECK_EQUAL(cc1.out.nValue, 60000000000ULL);
945 7 : BOOST_CHECK_EQUAL(HexStr(cc1.out.scriptPubKey), HexStr(GetScriptForDestination(CKeyID(uint160(ParseHex("816115944e077fe7c803cfa57f29b36bf87c1d35"))))));
946 :
947 : // Good example
948 3 : CDataStream ss2(ParseHex("8dcb7ebbd123008c988f1a4a4de2161e0f50aac7f17e7f9555caa4"), SER_DISK, CLIENT_VERSION);
949 2 : Coin cc2;
950 1 : ss2 >> cc2;
951 1 : BOOST_CHECK_EQUAL(cc2.fCoinBase, true);
952 1 : BOOST_CHECK_EQUAL(cc2.nHeight, 59807);
953 1 : BOOST_CHECK_EQUAL(cc2.out.nValue, 110397);
954 7 : BOOST_CHECK_EQUAL(HexStr(cc2.out.scriptPubKey), HexStr(GetScriptForDestination(CKeyID(uint160(ParseHex("8c988f1a4a4de2161e0f50aac7f17e7f9555caa4"))))));
955 :
956 : // PIVX: Example with fCoinStake
957 3 : CDataStream ss2b(ParseHex("97b401808b63008c988f1a4a4de2161e0f50aac7f17e7f9555caa4"), SER_DISK, CLIENT_VERSION);
958 2 : Coin cc2b;
959 1 : ss2b >> cc2b;
960 1 : BOOST_CHECK_EQUAL(cc2b.fCoinBase, false);
961 1 : BOOST_CHECK_EQUAL(cc2b.fCoinStake, true);
962 1 : BOOST_CHECK_EQUAL(cc2b.nHeight, 100000);
963 1 : BOOST_CHECK_EQUAL(cc2b.out.nValue, 2002 * COIN);
964 7 : BOOST_CHECK_EQUAL(HexStr(cc2b.out.scriptPubKey), HexStr(GetScriptForDestination(CKeyID(uint160(ParseHex("8c988f1a4a4de2161e0f50aac7f17e7f9555caa4"))))));
965 :
966 : // Smallest possible example
967 3 : CDataStream ss3(ParseHex("000006"), SER_DISK, CLIENT_VERSION);
968 2 : Coin cc3;
969 1 : ss3 >> cc3;
970 1 : BOOST_CHECK_EQUAL(cc3.fCoinBase, false);
971 1 : BOOST_CHECK_EQUAL(cc3.nHeight, 0);
972 1 : BOOST_CHECK_EQUAL(cc3.out.nValue, 0);
973 1 : BOOST_CHECK_EQUAL(cc3.out.scriptPubKey.size(), 0);
974 :
975 : // scriptPubKey that ends beyond the end of the stream
976 3 : CDataStream ss4(ParseHex("000007"), SER_DISK, CLIENT_VERSION);
977 1 : try {
978 2 : Coin cc4;
979 1 : ss4 >> cc4;
980 1 : BOOST_CHECK_MESSAGE(false, "We should have thrown");
981 1 : } catch (const std::ios_base::failure& e) {
982 : }
983 :
984 : // Very large scriptPubKey (3*10^9 bytes) past the end of the stream
985 2 : CDataStream tmp(SER_DISK, CLIENT_VERSION);
986 1 : uint64_t x = 3000000000ULL;
987 1 : tmp << VARINT(x);
988 1 : BOOST_CHECK_EQUAL(HexStr(tmp), "8a95c0bb00");
989 3 : CDataStream ss5(ParseHex("00008a95c0bb00"), SER_DISK, CLIENT_VERSION);
990 1 : try {
991 2 : Coin cc5;
992 1 : ss5 >> cc5;
993 1 : BOOST_CHECK_MESSAGE(false, "We should have thrown");
994 1 : } catch (const std::ios_base::failure& e) {
995 : }
996 1 : }
997 :
998 : const static COutPoint OUTPOINT;
999 : const static CAmount PRUNED = -1;
1000 : const static CAmount ABSENT = -2;
1001 : const static CAmount FAIL = -3;
1002 : const static CAmount VALUE1 = 100;
1003 : const static CAmount VALUE2 = 200;
1004 : const static CAmount VALUE3 = 300;
1005 : const static char DIRTY = CCoinsCacheEntry::DIRTY;
1006 : const static char FRESH = CCoinsCacheEntry::FRESH;
1007 : const static char NO_ENTRY = -1;
1008 :
1009 : const static auto FLAGS = {char(0), FRESH, DIRTY, char(DIRTY | FRESH)};
1010 : const static auto CLEAN_FLAGS = {char(0), FRESH};
1011 : const static auto ABSENT_FLAGS = {NO_ENTRY};
1012 :
1013 278 : void SetCoinsValue(CAmount value, Coin& coin)
1014 : {
1015 278 : assert(value != ABSENT);
1016 278 : coin.Clear();
1017 278 : assert(coin.IsSpent());
1018 278 : if (value != PRUNED) {
1019 139 : coin.out.nValue = value;
1020 139 : coin.nHeight = 1;
1021 139 : assert(!coin.IsSpent());
1022 : }
1023 278 : }
1024 :
1025 432 : size_t InsertCoinsMapEntry(CCoinsMap& map, CAmount value, char flags)
1026 : {
1027 432 : if (value == ABSENT) {
1028 154 : assert(flags == NO_ENTRY);
1029 : return 0;
1030 : }
1031 278 : assert(flags != NO_ENTRY);
1032 710 : CCoinsCacheEntry entry;
1033 278 : entry.flags = flags;
1034 278 : SetCoinsValue(value, entry.coin);
1035 278 : auto inserted = map.emplace(OUTPOINT, std::move(entry));
1036 278 : assert(inserted.second);
1037 278 : return inserted.first->second.coin.DynamicMemoryUsage();
1038 : }
1039 :
1040 151 : void GetCoinsMapEntry(const CCoinsMap& map, CAmount& value, char& flags)
1041 : {
1042 151 : auto it = map.find(OUTPOINT);
1043 151 : if (it == map.end()) {
1044 28 : value = ABSENT;
1045 28 : flags = NO_ENTRY;
1046 : } else {
1047 123 : if (it->second.coin.IsSpent()) {
1048 57 : value = PRUNED;
1049 : } else {
1050 66 : value = it->second.coin.out.nValue;
1051 : }
1052 123 : flags = it->second.flags;
1053 123 : assert(flags != NO_ENTRY);
1054 : }
1055 151 : }
1056 :
1057 261 : void WriteCoinsViewEntry(CCoinsView& view, CAmount value, char flags)
1058 : {
1059 261 : CCoinsMap map;
1060 261 : InsertCoinsMapEntry(map, value, flags);
1061 522 : CAnchorsSaplingMap mapSaplingAnchors;
1062 522 : CNullifiersMap mapSaplingNullifiers;
1063 783 : view.BatchWrite(map, {}, {}, mapSaplingAnchors, mapSaplingNullifiers);
1064 253 : }
1065 :
1066 171 : class SingleEntryCacheTest
1067 : {
1068 : public:
1069 171 : SingleEntryCacheTest(CAmount base_value, CAmount cache_value, char cache_flags)
1070 171 : {
1071 225 : WriteCoinsViewEntry(base, base_value, base_value == ABSENT ? NO_ENTRY : DIRTY);
1072 171 : cache.usage() += InsertCoinsMapEntry(cache.map(), cache_value, cache_flags);
1073 171 : }
1074 :
1075 : CCoinsView root;
1076 : CCoinsViewCacheTest base{&root};
1077 : CCoinsViewCacheTest cache{&base};
1078 : };
1079 :
1080 27 : void CheckAccessCoin(CAmount base_value, CAmount cache_value, CAmount expected_value, char cache_flags, char expected_flags)
1081 : {
1082 27 : SingleEntryCacheTest test(base_value, cache_value, cache_flags);
1083 27 : test.cache.AccessCoin(OUTPOINT);
1084 27 : test.cache.SelfTest();
1085 :
1086 27 : CAmount result_value;
1087 27 : char result_flags;
1088 27 : GetCoinsMapEntry(test.cache.map(), result_value, result_flags);
1089 27 : BOOST_CHECK_EQUAL(result_value, expected_value);
1090 27 : BOOST_CHECK_EQUAL(result_flags, expected_flags);
1091 27 : }
1092 :
1093 2 : BOOST_AUTO_TEST_CASE(ccoins_access)
1094 : {
1095 : /* Check AccessCoin behavior, requesting a coin from a cache view layered on
1096 : * top of a base view, and checking the resulting entry in the cache after
1097 : * the access.
1098 : *
1099 : * Base Cache Result Cache Result
1100 : * Value Value Value Flags Flags
1101 : */
1102 1 : CheckAccessCoin(ABSENT, ABSENT, ABSENT, NO_ENTRY , NO_ENTRY );
1103 1 : CheckAccessCoin(ABSENT, PRUNED, PRUNED, 0 , 0 );
1104 1 : CheckAccessCoin(ABSENT, PRUNED, PRUNED, FRESH , FRESH );
1105 1 : CheckAccessCoin(ABSENT, PRUNED, PRUNED, DIRTY , DIRTY );
1106 1 : CheckAccessCoin(ABSENT, PRUNED, PRUNED, DIRTY|FRESH, DIRTY|FRESH);
1107 1 : CheckAccessCoin(ABSENT, VALUE2, VALUE2, 0 , 0 );
1108 1 : CheckAccessCoin(ABSENT, VALUE2, VALUE2, FRESH , FRESH );
1109 1 : CheckAccessCoin(ABSENT, VALUE2, VALUE2, DIRTY , DIRTY );
1110 1 : CheckAccessCoin(ABSENT, VALUE2, VALUE2, DIRTY|FRESH, DIRTY|FRESH);
1111 1 : CheckAccessCoin(PRUNED, ABSENT, PRUNED, NO_ENTRY , FRESH );
1112 1 : CheckAccessCoin(PRUNED, PRUNED, PRUNED, 0 , 0 );
1113 1 : CheckAccessCoin(PRUNED, PRUNED, PRUNED, FRESH , FRESH );
1114 1 : CheckAccessCoin(PRUNED, PRUNED, PRUNED, DIRTY , DIRTY );
1115 1 : CheckAccessCoin(PRUNED, PRUNED, PRUNED, DIRTY|FRESH, DIRTY|FRESH);
1116 1 : CheckAccessCoin(PRUNED, VALUE2, VALUE2, 0 , 0 );
1117 1 : CheckAccessCoin(PRUNED, VALUE2, VALUE2, FRESH , FRESH );
1118 1 : CheckAccessCoin(PRUNED, VALUE2, VALUE2, DIRTY , DIRTY );
1119 1 : CheckAccessCoin(PRUNED, VALUE2, VALUE2, DIRTY|FRESH, DIRTY|FRESH);
1120 1 : CheckAccessCoin(VALUE1, ABSENT, VALUE1, NO_ENTRY , 0 );
1121 1 : CheckAccessCoin(VALUE1, PRUNED, PRUNED, 0 , 0 );
1122 1 : CheckAccessCoin(VALUE1, PRUNED, PRUNED, FRESH , FRESH );
1123 1 : CheckAccessCoin(VALUE1, PRUNED, PRUNED, DIRTY , DIRTY );
1124 1 : CheckAccessCoin(VALUE1, PRUNED, PRUNED, DIRTY|FRESH, DIRTY|FRESH);
1125 1 : CheckAccessCoin(VALUE1, VALUE2, VALUE2, 0 , 0 );
1126 1 : CheckAccessCoin(VALUE1, VALUE2, VALUE2, FRESH , FRESH );
1127 1 : CheckAccessCoin(VALUE1, VALUE2, VALUE2, DIRTY , DIRTY );
1128 1 : CheckAccessCoin(VALUE1, VALUE2, VALUE2, DIRTY|FRESH, DIRTY|FRESH);
1129 1 : }
1130 :
1131 27 : void CheckSpendCoins(CAmount base_value, CAmount cache_value, CAmount expected_value, char cache_flags, char expected_flags)
1132 : {
1133 27 : SingleEntryCacheTest test(base_value, cache_value, cache_flags);
1134 27 : test.cache.SpendCoin(OUTPOINT);
1135 27 : test.cache.SelfTest();
1136 :
1137 27 : CAmount result_value;
1138 27 : char result_flags;
1139 27 : GetCoinsMapEntry(test.cache.map(), result_value, result_flags);
1140 27 : BOOST_CHECK_EQUAL(result_value, expected_value);
1141 27 : BOOST_CHECK_EQUAL(result_flags, expected_flags);
1142 27 : };
1143 :
1144 2 : BOOST_AUTO_TEST_CASE(ccoins_spend)
1145 : {
1146 : /* Check SpendCoin behavior, requesting a coin from a cache view layered on
1147 : * top of a base view, spending, and then checking
1148 : * the resulting entry in the cache after the modification.
1149 : *
1150 : * Base Cache Result Cache Result
1151 : * Value Value Value Flags Flags
1152 : */
1153 1 : CheckSpendCoins(ABSENT, ABSENT, ABSENT, NO_ENTRY , NO_ENTRY );
1154 1 : CheckSpendCoins(ABSENT, PRUNED, PRUNED, 0 , DIRTY );
1155 1 : CheckSpendCoins(ABSENT, PRUNED, ABSENT, FRESH , NO_ENTRY );
1156 1 : CheckSpendCoins(ABSENT, PRUNED, PRUNED, DIRTY , DIRTY );
1157 1 : CheckSpendCoins(ABSENT, PRUNED, ABSENT, DIRTY|FRESH, NO_ENTRY );
1158 1 : CheckSpendCoins(ABSENT, VALUE2, PRUNED, 0 , DIRTY );
1159 1 : CheckSpendCoins(ABSENT, VALUE2, ABSENT, FRESH , NO_ENTRY );
1160 1 : CheckSpendCoins(ABSENT, VALUE2, PRUNED, DIRTY , DIRTY );
1161 1 : CheckSpendCoins(ABSENT, VALUE2, ABSENT, DIRTY|FRESH, NO_ENTRY );
1162 1 : CheckSpendCoins(PRUNED, ABSENT, ABSENT, NO_ENTRY , NO_ENTRY );
1163 1 : CheckSpendCoins(PRUNED, PRUNED, PRUNED, 0 , DIRTY );
1164 1 : CheckSpendCoins(PRUNED, PRUNED, ABSENT, FRESH , NO_ENTRY );
1165 1 : CheckSpendCoins(PRUNED, PRUNED, PRUNED, DIRTY , DIRTY );
1166 1 : CheckSpendCoins(PRUNED, PRUNED, ABSENT, DIRTY|FRESH, NO_ENTRY );
1167 1 : CheckSpendCoins(PRUNED, VALUE2, PRUNED, 0 , DIRTY );
1168 1 : CheckSpendCoins(PRUNED, VALUE2, ABSENT, FRESH , NO_ENTRY );
1169 1 : CheckSpendCoins(PRUNED, VALUE2, PRUNED, DIRTY , DIRTY );
1170 1 : CheckSpendCoins(PRUNED, VALUE2, ABSENT, DIRTY|FRESH, NO_ENTRY );
1171 1 : CheckSpendCoins(VALUE1, ABSENT, PRUNED, NO_ENTRY , DIRTY );
1172 1 : CheckSpendCoins(VALUE1, PRUNED, PRUNED, 0 , DIRTY );
1173 1 : CheckSpendCoins(VALUE1, PRUNED, ABSENT, FRESH , NO_ENTRY );
1174 1 : CheckSpendCoins(VALUE1, PRUNED, PRUNED, DIRTY , DIRTY );
1175 1 : CheckSpendCoins(VALUE1, PRUNED, ABSENT, DIRTY|FRESH, NO_ENTRY );
1176 1 : CheckSpendCoins(VALUE1, VALUE2, PRUNED, 0 , DIRTY );
1177 1 : CheckSpendCoins(VALUE1, VALUE2, ABSENT, FRESH , NO_ENTRY );
1178 1 : CheckSpendCoins(VALUE1, VALUE2, PRUNED, DIRTY , DIRTY );
1179 1 : CheckSpendCoins(VALUE1, VALUE2, ABSENT, DIRTY|FRESH, NO_ENTRY );
1180 1 : }
1181 :
1182 27 : void CheckAddCoinBase(CAmount base_value, CAmount cache_value, CAmount modify_value, CAmount expected_value, char cache_flags, char expected_flags)
1183 : {
1184 27 : SingleEntryCacheTest test(base_value, cache_value, cache_flags);
1185 :
1186 27 : CAmount result_value;
1187 27 : char result_flags;
1188 27 : try {
1189 54 : CTxOut output;
1190 27 : output.nValue = modify_value;
1191 39 : test.cache.AddCoin(OUTPOINT, Coin(std::move(output), 1, false, false), false);
1192 15 : test.cache.SelfTest();
1193 15 : GetCoinsMapEntry(test.cache.map(), result_value, result_flags);
1194 12 : } catch (std::logic_error& e) {
1195 12 : result_value = FAIL;
1196 12 : result_flags = NO_ENTRY;
1197 : }
1198 :
1199 27 : BOOST_CHECK_EQUAL(result_value, expected_value);
1200 27 : BOOST_CHECK_EQUAL(result_flags, expected_flags);
1201 27 : }
1202 :
1203 : // Simple wrapper for CheckAddCoinBase function above that loops through
1204 : // different possible base_values, making sure each one gives the same results.
1205 : // This wrapper lets the coins_add test below be shorter and less repetitive,
1206 : // while still verifying that the CoinsViewCache::ModifyNewCoins implementation
1207 : // ignores base values.
1208 : template <typename... Args>
1209 9 : void CheckAddCoin(Args&&... args)
1210 : {
1211 36 : for (CAmount base_value : {ABSENT, PRUNED, VALUE1})
1212 27 : CheckAddCoinBase(base_value, std::forward<Args>(args)...);
1213 9 : }
1214 :
1215 2 : BOOST_AUTO_TEST_CASE(ccoins_add)
1216 : {
1217 : /* Check ModifyNewCoin behavior, requesting a new coin from a cache view,
1218 : * writing a modification to the coin, and then checking the resulting
1219 : * entry in the cache after the modification. Verify behavior with the
1220 : * with the ModifyNewCoin coinbase argument set to false, and to true.
1221 : *
1222 : * PIVX: Remove Coinbase argument (ref: https://github.com/PIVX-Project/PIVX/pull/1775)
1223 : *
1224 : * Cache Write Result Cache Result
1225 : * Value Value Value Flags Flags
1226 : */
1227 1 : CheckAddCoin(ABSENT, VALUE3, VALUE3, NO_ENTRY , DIRTY|FRESH );
1228 1 : CheckAddCoin(PRUNED, VALUE3, VALUE3, 0 , DIRTY|FRESH );
1229 1 : CheckAddCoin(PRUNED, VALUE3, VALUE3, FRESH , DIRTY|FRESH );
1230 1 : CheckAddCoin(PRUNED, VALUE3, VALUE3, DIRTY , DIRTY );
1231 1 : CheckAddCoin(PRUNED, VALUE3, VALUE3, DIRTY|FRESH, DIRTY|FRESH );
1232 1 : CheckAddCoin(VALUE2, VALUE3, FAIL, 0 , NO_ENTRY );
1233 1 : CheckAddCoin(VALUE2, VALUE3, FAIL, FRESH , NO_ENTRY );
1234 1 : CheckAddCoin(VALUE2, VALUE3, FAIL, DIRTY , NO_ENTRY );
1235 1 : CheckAddCoin(VALUE2, VALUE3, FAIL, DIRTY|FRESH, NO_ENTRY );
1236 1 : }
1237 :
1238 90 : void CheckWriteCoins(CAmount parent_value, CAmount child_value, CAmount expected_value, char parent_flags, char child_flags, char expected_flags)
1239 : {
1240 90 : SingleEntryCacheTest test(ABSENT, parent_value, parent_flags);
1241 :
1242 90 : CAmount result_value;
1243 90 : char result_flags;
1244 90 : try {
1245 90 : WriteCoinsViewEntry(test.cache, child_value, child_flags);
1246 82 : test.cache.SelfTest();
1247 82 : GetCoinsMapEntry(test.cache.map(), result_value, result_flags);
1248 8 : } catch (std::logic_error& e) {
1249 8 : result_value = FAIL;
1250 8 : result_flags = NO_ENTRY;
1251 : }
1252 :
1253 90 : BOOST_CHECK_EQUAL(result_value, expected_value);
1254 90 : BOOST_CHECK_EQUAL(result_flags, expected_flags);
1255 90 : }
1256 :
1257 2 : BOOST_AUTO_TEST_CASE(ccoins_write)
1258 : {
1259 : /* Check BatchWrite behavior, flushing one entry from a child cache to a
1260 : * parent cache, and checking the resulting entry in the parent cache
1261 : * after the write.
1262 : *
1263 : * Parent Child Result Parent Child Result
1264 : * Value Value Value Flags Flags Flags
1265 : */
1266 1 : CheckWriteCoins(ABSENT, ABSENT, ABSENT, NO_ENTRY , NO_ENTRY , NO_ENTRY );
1267 1 : CheckWriteCoins(ABSENT, PRUNED, PRUNED, NO_ENTRY , DIRTY , DIRTY );
1268 1 : CheckWriteCoins(ABSENT, PRUNED, ABSENT, NO_ENTRY , DIRTY|FRESH, NO_ENTRY );
1269 1 : CheckWriteCoins(ABSENT, VALUE2, VALUE2, NO_ENTRY , DIRTY , DIRTY );
1270 1 : CheckWriteCoins(ABSENT, VALUE2, VALUE2, NO_ENTRY , DIRTY|FRESH, DIRTY|FRESH);
1271 1 : CheckWriteCoins(PRUNED, ABSENT, PRUNED, 0 , NO_ENTRY , 0 );
1272 1 : CheckWriteCoins(PRUNED, ABSENT, PRUNED, FRESH , NO_ENTRY , FRESH );
1273 1 : CheckWriteCoins(PRUNED, ABSENT, PRUNED, DIRTY , NO_ENTRY , DIRTY );
1274 1 : CheckWriteCoins(PRUNED, ABSENT, PRUNED, DIRTY|FRESH, NO_ENTRY , DIRTY|FRESH);
1275 1 : CheckWriteCoins(PRUNED, PRUNED, PRUNED, 0 , DIRTY , DIRTY );
1276 1 : CheckWriteCoins(PRUNED, PRUNED, PRUNED, 0 , DIRTY|FRESH, DIRTY );
1277 1 : CheckWriteCoins(PRUNED, PRUNED, ABSENT, FRESH , DIRTY , NO_ENTRY );
1278 1 : CheckWriteCoins(PRUNED, PRUNED, ABSENT, FRESH , DIRTY|FRESH, NO_ENTRY );
1279 1 : CheckWriteCoins(PRUNED, PRUNED, PRUNED, DIRTY , DIRTY , DIRTY );
1280 1 : CheckWriteCoins(PRUNED, PRUNED, PRUNED, DIRTY , DIRTY|FRESH, DIRTY );
1281 1 : CheckWriteCoins(PRUNED, PRUNED, ABSENT, DIRTY|FRESH, DIRTY , NO_ENTRY );
1282 1 : CheckWriteCoins(PRUNED, PRUNED, ABSENT, DIRTY|FRESH, DIRTY|FRESH, NO_ENTRY );
1283 1 : CheckWriteCoins(PRUNED, VALUE2, VALUE2, 0 , DIRTY , DIRTY );
1284 1 : CheckWriteCoins(PRUNED, VALUE2, VALUE2, 0 , DIRTY|FRESH, DIRTY );
1285 1 : CheckWriteCoins(PRUNED, VALUE2, VALUE2, FRESH , DIRTY , DIRTY|FRESH);
1286 1 : CheckWriteCoins(PRUNED, VALUE2, VALUE2, FRESH , DIRTY|FRESH, DIRTY|FRESH);
1287 1 : CheckWriteCoins(PRUNED, VALUE2, VALUE2, DIRTY , DIRTY , DIRTY );
1288 1 : CheckWriteCoins(PRUNED, VALUE2, VALUE2, DIRTY , DIRTY|FRESH, DIRTY );
1289 1 : CheckWriteCoins(PRUNED, VALUE2, VALUE2, DIRTY|FRESH, DIRTY , DIRTY|FRESH);
1290 1 : CheckWriteCoins(PRUNED, VALUE2, VALUE2, DIRTY|FRESH, DIRTY|FRESH, DIRTY|FRESH);
1291 1 : CheckWriteCoins(VALUE1, ABSENT, VALUE1, 0 , NO_ENTRY , 0 );
1292 1 : CheckWriteCoins(VALUE1, ABSENT, VALUE1, FRESH , NO_ENTRY , FRESH );
1293 1 : CheckWriteCoins(VALUE1, ABSENT, VALUE1, DIRTY , NO_ENTRY , DIRTY );
1294 1 : CheckWriteCoins(VALUE1, ABSENT, VALUE1, DIRTY|FRESH, NO_ENTRY , DIRTY|FRESH);
1295 1 : CheckWriteCoins(VALUE1, PRUNED, PRUNED, 0 , DIRTY , DIRTY );
1296 1 : CheckWriteCoins(VALUE1, PRUNED, FAIL , 0 , DIRTY|FRESH, NO_ENTRY );
1297 1 : CheckWriteCoins(VALUE1, PRUNED, ABSENT, FRESH , DIRTY , NO_ENTRY );
1298 1 : CheckWriteCoins(VALUE1, PRUNED, FAIL , FRESH , DIRTY|FRESH, NO_ENTRY );
1299 1 : CheckWriteCoins(VALUE1, PRUNED, PRUNED, DIRTY , DIRTY , DIRTY );
1300 1 : CheckWriteCoins(VALUE1, PRUNED, FAIL , DIRTY , DIRTY|FRESH, NO_ENTRY );
1301 1 : CheckWriteCoins(VALUE1, PRUNED, ABSENT, DIRTY|FRESH, DIRTY , NO_ENTRY );
1302 1 : CheckWriteCoins(VALUE1, PRUNED, FAIL , DIRTY|FRESH, DIRTY|FRESH, NO_ENTRY );
1303 1 : CheckWriteCoins(VALUE1, VALUE2, VALUE2, 0 , DIRTY , DIRTY );
1304 1 : CheckWriteCoins(VALUE1, VALUE2, FAIL , 0 , DIRTY|FRESH, NO_ENTRY );
1305 1 : CheckWriteCoins(VALUE1, VALUE2, VALUE2, FRESH , DIRTY , DIRTY|FRESH);
1306 1 : CheckWriteCoins(VALUE1, VALUE2, FAIL , FRESH , DIRTY|FRESH, NO_ENTRY );
1307 1 : CheckWriteCoins(VALUE1, VALUE2, VALUE2, DIRTY , DIRTY , DIRTY );
1308 1 : CheckWriteCoins(VALUE1, VALUE2, FAIL , DIRTY , DIRTY|FRESH, NO_ENTRY );
1309 1 : CheckWriteCoins(VALUE1, VALUE2, VALUE2, DIRTY|FRESH, DIRTY , DIRTY|FRESH);
1310 1 : CheckWriteCoins(VALUE1, VALUE2, FAIL , DIRTY|FRESH, DIRTY|FRESH, NO_ENTRY );
1311 :
1312 : // The checks above omit cases where the child flags are not DIRTY, since
1313 : // they would be too repetitive (the parent cache is never updated in these
1314 : // cases). The loop below covers these cases and makes sure the parent cache
1315 : // is always left unchanged.
1316 4 : for (CAmount parent_value : {ABSENT, PRUNED, VALUE1})
1317 12 : for (CAmount child_value : {ABSENT, PRUNED, VALUE2})
1318 42 : for (char parent_flags : parent_value == ABSENT ? ABSENT_FLAGS : FLAGS)
1319 90 : for (char child_flags : child_value == ABSENT ? ABSENT_FLAGS : CLEAN_FLAGS)
1320 45 : CheckWriteCoins(parent_value, child_value, parent_value, parent_flags, child_flags, parent_flags);
1321 1 : }
1322 :
1323 : BOOST_AUTO_TEST_SUITE_END()
|