]>
Commit | Line | Data |
---|---|---|
ed27e53c PW |
1 | // Copyright (c) 2014 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 "coins.h" | |
6 | #include "random.h" | |
7 | #include "uint256.h" | |
92fd887f | 8 | #include "test/test_bitcoin.h" |
1d38795f SB |
9 | #include "consensus/validation.h" |
10 | #include "main.h" | |
11 | #include "undo.h" | |
ed27e53c PW |
12 | |
13 | #include <vector> | |
14 | #include <map> | |
15 | ||
16 | #include <boost/test/unit_test.hpp> | |
434f3284 | 17 | #include "zcash/IncrementalMerkleTree.hpp" |
ed27e53c PW |
18 | |
19 | namespace | |
20 | { | |
21 | class CCoinsViewTest : public CCoinsView | |
22 | { | |
23 | uint256 hashBestBlock_; | |
9f25631d | 24 | uint256 hashBestAnchor_; |
ed27e53c | 25 | std::map<uint256, CCoins> map_; |
434f3284 | 26 | std::map<uint256, ZCIncrementalMerkleTree> mapAnchors_; |
45d6bee9 | 27 | std::map<uint256, bool> mapSerials_; |
ed27e53c PW |
28 | |
29 | public: | |
8048f4c0 SB |
30 | CCoinsViewTest() { |
31 | hashBestAnchor_ = ZCIncrementalMerkleTree::empty_root(); | |
32 | } | |
33 | ||
434f3284 | 34 | bool GetAnchorAt(const uint256& rt, ZCIncrementalMerkleTree &tree) const { |
8048f4c0 | 35 | if (rt == ZCIncrementalMerkleTree::empty_root()) { |
434f3284 SB |
36 | ZCIncrementalMerkleTree new_tree; |
37 | tree = new_tree; | |
9f25631d SB |
38 | return true; |
39 | } | |
40 | ||
434f3284 | 41 | std::map<uint256, ZCIncrementalMerkleTree>::const_iterator it = mapAnchors_.find(rt); |
9f25631d SB |
42 | if (it == mapAnchors_.end()) { |
43 | return false; | |
44 | } else { | |
434f3284 | 45 | tree = it->second; |
9f25631d SB |
46 | return true; |
47 | } | |
48 | } | |
49 | ||
45d6bee9 SB |
50 | bool GetSerial(const uint256 &serial) const |
51 | { | |
52 | std::map<uint256, bool>::const_iterator it = mapSerials_.find(serial); | |
53 | ||
54 | if (it == mapSerials_.end()) { | |
55 | return false; | |
56 | } else { | |
57 | // The map shouldn't contain any false entries. | |
58 | assert(it->second); | |
59 | return true; | |
60 | } | |
61 | } | |
62 | ||
9f25631d SB |
63 | uint256 GetBestAnchor() const { return hashBestAnchor_; } |
64 | ||
ed27e53c PW |
65 | bool GetCoins(const uint256& txid, CCoins& coins) const |
66 | { | |
67 | std::map<uint256, CCoins>::const_iterator it = map_.find(txid); | |
68 | if (it == map_.end()) { | |
69 | return false; | |
70 | } | |
71 | coins = it->second; | |
72 | if (coins.IsPruned() && insecure_rand() % 2 == 0) { | |
73 | // Randomly return false in case of an empty entry. | |
74 | return false; | |
75 | } | |
76 | return true; | |
77 | } | |
78 | ||
79 | bool HaveCoins(const uint256& txid) const | |
80 | { | |
81 | CCoins coins; | |
82 | return GetCoins(txid, coins); | |
83 | } | |
84 | ||
85 | uint256 GetBestBlock() const { return hashBestBlock_; } | |
86 | ||
9f25631d SB |
87 | bool BatchWrite(CCoinsMap& mapCoins, |
88 | const uint256& hashBlock, | |
89 | const uint256& hashAnchor, | |
45d6bee9 SB |
90 | CAnchorsMap& mapAnchors, |
91 | CSerialsMap& mapSerials) | |
ed27e53c PW |
92 | { |
93 | for (CCoinsMap::iterator it = mapCoins.begin(); it != mapCoins.end(); ) { | |
94 | map_[it->first] = it->second.coins; | |
95 | if (it->second.coins.IsPruned() && insecure_rand() % 3 == 0) { | |
96 | // Randomly delete empty entries on write. | |
97 | map_.erase(it->first); | |
98 | } | |
99 | mapCoins.erase(it++); | |
100 | } | |
9f25631d SB |
101 | for (CAnchorsMap::iterator it = mapAnchors.begin(); it != mapAnchors.end(); ) { |
102 | if (it->second.entered) { | |
434f3284 SB |
103 | std::map<uint256, ZCIncrementalMerkleTree>::iterator ret = |
104 | mapAnchors_.insert(std::make_pair(it->first, ZCIncrementalMerkleTree())).first; | |
cf471983 | 105 | |
434f3284 | 106 | ret->second = it->second.tree; |
9f25631d SB |
107 | } else { |
108 | mapAnchors_.erase(it->first); | |
109 | } | |
110 | mapAnchors.erase(it++); | |
111 | } | |
45d6bee9 SB |
112 | for (CSerialsMap::iterator it = mapSerials.begin(); it != mapSerials.end(); ) { |
113 | if (it->second.entered) { | |
114 | mapSerials_[it->first] = true; | |
115 | } else { | |
116 | mapSerials_.erase(it->first); | |
117 | } | |
118 | mapSerials.erase(it++); | |
119 | } | |
ed27e53c | 120 | mapCoins.clear(); |
9f25631d | 121 | mapAnchors.clear(); |
45d6bee9 | 122 | mapSerials.clear(); |
ed27e53c | 123 | hashBestBlock_ = hashBlock; |
9f25631d | 124 | hashBestAnchor_ = hashAnchor; |
ed27e53c PW |
125 | return true; |
126 | } | |
127 | ||
128 | bool GetStats(CCoinsStats& stats) const { return false; } | |
129 | }; | |
046392dc PW |
130 | |
131 | class CCoinsViewCacheTest : public CCoinsViewCache | |
132 | { | |
133 | public: | |
134 | CCoinsViewCacheTest(CCoinsView* base) : CCoinsViewCache(base) {} | |
135 | ||
136 | void SelfTest() const | |
137 | { | |
138 | // Manually recompute the dynamic usage of the whole data, and compare it. | |
139 | size_t ret = memusage::DynamicUsage(cacheCoins); | |
140 | for (CCoinsMap::iterator it = cacheCoins.begin(); it != cacheCoins.end(); it++) { | |
141 | ret += memusage::DynamicUsage(it->second.coins); | |
142 | } | |
143 | BOOST_CHECK_EQUAL(memusage::DynamicUsage(*this), ret); | |
144 | } | |
145 | ||
146 | }; | |
147 | ||
ed27e53c PW |
148 | } |
149 | ||
10c33f0f | 150 | uint256 appendRandomCommitment(ZCIncrementalMerkleTree &tree) |
14b12fde SB |
151 | { |
152 | libzcash::SpendingKey k = libzcash::SpendingKey::random(); | |
153 | libzcash::PaymentAddress addr = k.address(); | |
154 | ||
155 | libzcash::Note note(addr.a_pk, 0, uint256(), uint256()); | |
156 | ||
ecd8ca5d SB |
157 | auto cm = note.cm(); |
158 | tree.append(cm); | |
159 | return cm; | |
14b12fde SB |
160 | } |
161 | ||
162 | BOOST_FIXTURE_TEST_SUITE(coins_tests, BasicTestingSetup) | |
163 | ||
45d6bee9 SB |
164 | BOOST_AUTO_TEST_CASE(serials_test) |
165 | { | |
166 | CCoinsViewTest base; | |
167 | CCoinsViewCacheTest cache(&base); | |
168 | ||
169 | uint256 myserial = GetRandHash(); | |
170 | ||
171 | BOOST_CHECK(!cache.GetSerial(myserial)); | |
172 | cache.SetSerial(myserial, true); | |
173 | BOOST_CHECK(cache.GetSerial(myserial)); | |
174 | cache.Flush(); | |
175 | ||
176 | CCoinsViewCacheTest cache2(&base); | |
177 | ||
178 | BOOST_CHECK(cache2.GetSerial(myserial)); | |
179 | cache2.SetSerial(myserial, false); | |
180 | BOOST_CHECK(!cache2.GetSerial(myserial)); | |
181 | cache2.Flush(); | |
182 | ||
183 | CCoinsViewCacheTest cache3(&base); | |
184 | ||
185 | BOOST_CHECK(!cache3.GetSerial(myserial)); | |
186 | } | |
187 | ||
cf471983 SB |
188 | BOOST_AUTO_TEST_CASE(anchors_flush_test) |
189 | { | |
190 | CCoinsViewTest base; | |
191 | uint256 newrt; | |
192 | { | |
193 | CCoinsViewCacheTest cache(&base); | |
434f3284 | 194 | ZCIncrementalMerkleTree tree; |
cf471983 SB |
195 | BOOST_CHECK(cache.GetAnchorAt(cache.GetBestAnchor(), tree)); |
196 | appendRandomCommitment(tree); | |
197 | ||
434f3284 | 198 | newrt = tree.root(); |
cf471983 SB |
199 | |
200 | cache.PushAnchor(tree); | |
201 | cache.Flush(); | |
202 | } | |
203 | ||
204 | { | |
205 | CCoinsViewCacheTest cache(&base); | |
434f3284 | 206 | ZCIncrementalMerkleTree tree; |
cf471983 SB |
207 | BOOST_CHECK(cache.GetAnchorAt(cache.GetBestAnchor(), tree)); |
208 | ||
209 | // Get the cached entry. | |
210 | BOOST_CHECK(cache.GetAnchorAt(cache.GetBestAnchor(), tree)); | |
211 | ||
434f3284 | 212 | uint256 check_rt = tree.root(); |
cf471983 SB |
213 | |
214 | BOOST_CHECK(check_rt == newrt); | |
215 | } | |
216 | } | |
217 | ||
10c33f0f SB |
218 | BOOST_AUTO_TEST_CASE(chained_pours) |
219 | { | |
220 | CCoinsViewTest base; | |
221 | CCoinsViewCacheTest cache(&base); | |
222 | ||
223 | ZCIncrementalMerkleTree tree; | |
224 | ||
225 | CPourTx ptx1; | |
226 | ptx1.anchor = tree.root(); | |
227 | ptx1.commitments[0] = appendRandomCommitment(tree); | |
228 | ptx1.commitments[1] = appendRandomCommitment(tree); | |
229 | ||
230 | // Although it's not possible given our assumptions, if | |
231 | // two pours create the same treestate twice, we should | |
232 | // still be able to anchor to it. | |
233 | CPourTx ptx1b; | |
234 | ptx1b.anchor = tree.root(); | |
235 | ptx1b.commitments[0] = ptx1.commitments[0]; | |
236 | ptx1b.commitments[1] = ptx1.commitments[1]; | |
237 | ||
238 | CPourTx ptx2; | |
239 | CPourTx ptx3; | |
240 | ||
241 | ptx2.anchor = tree.root(); | |
242 | ptx3.anchor = tree.root(); | |
243 | ||
244 | ptx2.commitments[0] = appendRandomCommitment(tree); | |
245 | ptx2.commitments[1] = appendRandomCommitment(tree); | |
246 | ||
247 | ptx3.commitments[0] = appendRandomCommitment(tree); | |
248 | ptx3.commitments[1] = appendRandomCommitment(tree); | |
249 | ||
250 | { | |
251 | CMutableTransaction mtx; | |
252 | mtx.vpour.push_back(ptx2); | |
253 | ||
254 | BOOST_CHECK(!cache.HavePourRequirements(mtx)); | |
255 | } | |
256 | ||
49ab032b | 257 | { |
ecd8ca5d SB |
258 | // ptx2 is trying to anchor to ptx1 but ptx1 |
259 | // appears afterwards -- not a permitted ordering | |
49ab032b SB |
260 | CMutableTransaction mtx; |
261 | mtx.vpour.push_back(ptx2); | |
262 | mtx.vpour.push_back(ptx1); | |
263 | ||
264 | BOOST_CHECK(!cache.HavePourRequirements(mtx)); | |
265 | } | |
266 | ||
267 | { | |
268 | CMutableTransaction mtx; | |
269 | mtx.vpour.push_back(ptx1); | |
270 | mtx.vpour.push_back(ptx2); | |
271 | ||
272 | BOOST_CHECK(cache.HavePourRequirements(mtx)); | |
273 | } | |
274 | ||
10c33f0f SB |
275 | { |
276 | CMutableTransaction mtx; | |
277 | mtx.vpour.push_back(ptx1); | |
278 | mtx.vpour.push_back(ptx2); | |
279 | mtx.vpour.push_back(ptx3); | |
280 | ||
281 | BOOST_CHECK(cache.HavePourRequirements(mtx)); | |
282 | } | |
283 | ||
284 | { | |
285 | CMutableTransaction mtx; | |
286 | mtx.vpour.push_back(ptx1); | |
287 | mtx.vpour.push_back(ptx1b); | |
288 | mtx.vpour.push_back(ptx2); | |
289 | mtx.vpour.push_back(ptx3); | |
290 | ||
291 | BOOST_CHECK(cache.HavePourRequirements(mtx)); | |
292 | } | |
293 | } | |
294 | ||
9f25631d SB |
295 | BOOST_AUTO_TEST_CASE(anchors_test) |
296 | { | |
297 | // TODO: These tests should be more methodical. | |
298 | // Or, integrate with Bitcoin's tests later. | |
299 | ||
300 | CCoinsViewTest base; | |
301 | CCoinsViewCacheTest cache(&base); | |
302 | ||
8048f4c0 | 303 | BOOST_CHECK(cache.GetBestAnchor() == ZCIncrementalMerkleTree::empty_root()); |
9f25631d SB |
304 | |
305 | { | |
434f3284 | 306 | ZCIncrementalMerkleTree tree; |
9f25631d SB |
307 | |
308 | BOOST_CHECK(cache.GetAnchorAt(cache.GetBestAnchor(), tree)); | |
8048f4c0 | 309 | BOOST_CHECK(cache.GetBestAnchor() == tree.root()); |
9f25631d SB |
310 | appendRandomCommitment(tree); |
311 | appendRandomCommitment(tree); | |
312 | appendRandomCommitment(tree); | |
313 | appendRandomCommitment(tree); | |
314 | appendRandomCommitment(tree); | |
315 | appendRandomCommitment(tree); | |
316 | appendRandomCommitment(tree); | |
9f25631d | 317 | |
434f3284 SB |
318 | ZCIncrementalMerkleTree save_tree_for_later; |
319 | save_tree_for_later = tree; | |
9f25631d | 320 | |
434f3284 | 321 | uint256 newrt = tree.root(); |
9f25631d | 322 | uint256 newrt2; |
9f25631d SB |
323 | |
324 | cache.PushAnchor(tree); | |
325 | BOOST_CHECK(cache.GetBestAnchor() == newrt); | |
326 | ||
327 | { | |
434f3284 | 328 | ZCIncrementalMerkleTree confirm_same; |
9f25631d SB |
329 | BOOST_CHECK(cache.GetAnchorAt(cache.GetBestAnchor(), confirm_same)); |
330 | ||
434f3284 | 331 | BOOST_CHECK(confirm_same.root() == newrt); |
9f25631d SB |
332 | } |
333 | ||
334 | appendRandomCommitment(tree); | |
335 | appendRandomCommitment(tree); | |
9f25631d | 336 | |
434f3284 | 337 | newrt2 = tree.root(); |
9f25631d SB |
338 | |
339 | cache.PushAnchor(tree); | |
340 | BOOST_CHECK(cache.GetBestAnchor() == newrt2); | |
341 | ||
434f3284 | 342 | ZCIncrementalMerkleTree test_tree; |
9f25631d SB |
343 | BOOST_CHECK(cache.GetAnchorAt(cache.GetBestAnchor(), test_tree)); |
344 | ||
434f3284 | 345 | BOOST_CHECK(tree.root() == test_tree.root()); |
9f25631d SB |
346 | |
347 | { | |
434f3284 | 348 | ZCIncrementalMerkleTree test_tree2; |
9f25631d SB |
349 | cache.GetAnchorAt(newrt, test_tree2); |
350 | ||
434f3284 | 351 | BOOST_CHECK(test_tree2.root() == newrt); |
9f25631d SB |
352 | } |
353 | ||
354 | { | |
355 | cache.PopAnchor(newrt); | |
434f3284 | 356 | ZCIncrementalMerkleTree obtain_tree; |
9f25631d SB |
357 | assert(!cache.GetAnchorAt(newrt2, obtain_tree)); // should have been popped off |
358 | assert(cache.GetAnchorAt(newrt, obtain_tree)); | |
359 | ||
434f3284 | 360 | assert(obtain_tree.root() == newrt); |
9f25631d SB |
361 | } |
362 | } | |
363 | } | |
364 | ||
ed27e53c PW |
365 | static const unsigned int NUM_SIMULATION_ITERATIONS = 40000; |
366 | ||
367 | // This is a large randomized insert/remove simulation test on a variable-size | |
368 | // stack of caches on top of CCoinsViewTest. | |
369 | // | |
370 | // It will randomly create/update/delete CCoins entries to a tip of caches, with | |
371 | // txids picked from a limited list of random 256-bit hashes. Occasionally, a | |
372 | // new tip is added to the stack of caches, or the tip is flushed and removed. | |
373 | // | |
374 | // During the process, booleans are kept to make sure that the randomized | |
375 | // operation hits all branches. | |
376 | BOOST_AUTO_TEST_CASE(coins_cache_simulation_test) | |
377 | { | |
378 | // Various coverage trackers. | |
379 | bool removed_all_caches = false; | |
380 | bool reached_4_caches = false; | |
381 | bool added_an_entry = false; | |
382 | bool removed_an_entry = false; | |
383 | bool updated_an_entry = false; | |
384 | bool found_an_entry = false; | |
385 | bool missed_an_entry = false; | |
386 | ||
387 | // A simple map to track what we expect the cache stack to represent. | |
388 | std::map<uint256, CCoins> result; | |
389 | ||
390 | // The cache stack. | |
391 | CCoinsViewTest base; // A CCoinsViewTest at the bottom. | |
046392dc PW |
392 | std::vector<CCoinsViewCacheTest*> stack; // A stack of CCoinsViewCaches on top. |
393 | stack.push_back(new CCoinsViewCacheTest(&base)); // Start with one cache. | |
ed27e53c PW |
394 | |
395 | // Use a limited set of random transaction ids, so we do test overwriting entries. | |
396 | std::vector<uint256> txids; | |
397 | txids.resize(NUM_SIMULATION_ITERATIONS / 8); | |
398 | for (unsigned int i = 0; i < txids.size(); i++) { | |
399 | txids[i] = GetRandHash(); | |
400 | } | |
401 | ||
402 | for (unsigned int i = 0; i < NUM_SIMULATION_ITERATIONS; i++) { | |
403 | // Do a random modification. | |
404 | { | |
405 | uint256 txid = txids[insecure_rand() % txids.size()]; // txid we're going to modify in this iteration. | |
406 | CCoins& coins = result[txid]; | |
407 | CCoinsModifier entry = stack.back()->ModifyCoins(txid); | |
408 | BOOST_CHECK(coins == *entry); | |
409 | if (insecure_rand() % 5 == 0 || coins.IsPruned()) { | |
410 | if (coins.IsPruned()) { | |
411 | added_an_entry = true; | |
412 | } else { | |
413 | updated_an_entry = true; | |
414 | } | |
415 | coins.nVersion = insecure_rand(); | |
416 | coins.vout.resize(1); | |
417 | coins.vout[0].nValue = insecure_rand(); | |
418 | *entry = coins; | |
419 | } else { | |
420 | coins.Clear(); | |
421 | entry->Clear(); | |
422 | removed_an_entry = true; | |
423 | } | |
424 | } | |
425 | ||
426 | // Once every 1000 iterations and at the end, verify the full cache. | |
427 | if (insecure_rand() % 1000 == 1 || i == NUM_SIMULATION_ITERATIONS - 1) { | |
428 | for (std::map<uint256, CCoins>::iterator it = result.begin(); it != result.end(); it++) { | |
429 | const CCoins* coins = stack.back()->AccessCoins(it->first); | |
430 | if (coins) { | |
431 | BOOST_CHECK(*coins == it->second); | |
432 | found_an_entry = true; | |
433 | } else { | |
434 | BOOST_CHECK(it->second.IsPruned()); | |
435 | missed_an_entry = true; | |
436 | } | |
437 | } | |
046392dc PW |
438 | BOOST_FOREACH(const CCoinsViewCacheTest *test, stack) { |
439 | test->SelfTest(); | |
440 | } | |
ed27e53c PW |
441 | } |
442 | ||
443 | if (insecure_rand() % 100 == 0) { | |
444 | // Every 100 iterations, change the cache stack. | |
445 | if (stack.size() > 0 && insecure_rand() % 2 == 0) { | |
446 | stack.back()->Flush(); | |
447 | delete stack.back(); | |
448 | stack.pop_back(); | |
449 | } | |
450 | if (stack.size() == 0 || (stack.size() < 4 && insecure_rand() % 2)) { | |
451 | CCoinsView* tip = &base; | |
452 | if (stack.size() > 0) { | |
453 | tip = stack.back(); | |
454 | } else { | |
455 | removed_all_caches = true; | |
456 | } | |
046392dc | 457 | stack.push_back(new CCoinsViewCacheTest(tip)); |
ed27e53c PW |
458 | if (stack.size() == 4) { |
459 | reached_4_caches = true; | |
460 | } | |
461 | } | |
462 | } | |
463 | } | |
464 | ||
465 | // Clean up the stack. | |
466 | while (stack.size() > 0) { | |
467 | delete stack.back(); | |
468 | stack.pop_back(); | |
469 | } | |
470 | ||
471 | // Verify coverage. | |
472 | BOOST_CHECK(removed_all_caches); | |
473 | BOOST_CHECK(reached_4_caches); | |
474 | BOOST_CHECK(added_an_entry); | |
475 | BOOST_CHECK(removed_an_entry); | |
476 | BOOST_CHECK(updated_an_entry); | |
477 | BOOST_CHECK(found_an_entry); | |
478 | BOOST_CHECK(missed_an_entry); | |
479 | } | |
480 | ||
1d38795f SB |
481 | BOOST_AUTO_TEST_CASE(coins_coinbase_spends) |
482 | { | |
483 | CCoinsViewTest base; | |
484 | CCoinsViewCacheTest cache(&base); | |
485 | ||
486 | // Create coinbase transaction | |
487 | CMutableTransaction mtx; | |
488 | mtx.vin.resize(1); | |
489 | mtx.vin[0].scriptSig = CScript() << OP_1; | |
490 | mtx.vin[0].nSequence = 0; | |
491 | mtx.vout.resize(1); | |
492 | mtx.vout[0].nValue = 500; | |
493 | mtx.vout[0].scriptPubKey = CScript() << OP_1; | |
494 | ||
495 | CTransaction tx(mtx); | |
496 | ||
497 | BOOST_CHECK(tx.IsCoinBase()); | |
498 | ||
499 | CValidationState state; | |
500 | UpdateCoins(tx, state, cache, 100); | |
501 | ||
502 | // Create coinbase spend | |
503 | CMutableTransaction mtx2; | |
504 | mtx2.vin.resize(1); | |
505 | mtx2.vin[0].prevout = COutPoint(tx.GetHash(), 0); | |
506 | mtx2.vin[0].scriptSig = CScript() << OP_1; | |
507 | mtx2.vin[0].nSequence = 0; | |
508 | ||
509 | { | |
510 | CTransaction tx2(mtx2); | |
c0dde76d | 511 | BOOST_CHECK(NonContextualCheckInputs(tx2, state, cache, false, SCRIPT_VERIFY_NONE, false, Params().GetConsensus())); |
1d38795f SB |
512 | } |
513 | ||
514 | mtx2.vout.resize(1); | |
515 | mtx2.vout[0].nValue = 500; | |
516 | mtx2.vout[0].scriptPubKey = CScript() << OP_1; | |
517 | ||
518 | { | |
519 | CTransaction tx2(mtx2); | |
c0dde76d | 520 | BOOST_CHECK(!NonContextualCheckInputs(tx2, state, cache, false, SCRIPT_VERIFY_NONE, false, Params().GetConsensus())); |
1d38795f SB |
521 | BOOST_CHECK(state.GetRejectReason() == "bad-txns-coinbase-spend-has-transparent-outputs"); |
522 | } | |
523 | } | |
524 | ||
ed27e53c | 525 | BOOST_AUTO_TEST_SUITE_END() |