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.
8 #include "test/test_bitcoin.h"
13 #include <boost/test/unit_test.hpp>
14 #include "zcash/IncrementalMerkleTree.hpp"
18 class CCoinsViewTest : public CCoinsView
20 uint256 hashBestBlock_;
21 uint256 hashBestAnchor_;
22 std::map<uint256, CCoins> map_;
23 std::map<uint256, ZCIncrementalMerkleTree> mapAnchors_;
24 std::map<uint256, bool> mapSerials_;
27 bool GetAnchorAt(const uint256& rt, ZCIncrementalMerkleTree &tree) const {
29 ZCIncrementalMerkleTree new_tree;
34 std::map<uint256, ZCIncrementalMerkleTree>::const_iterator it = mapAnchors_.find(rt);
35 if (it == mapAnchors_.end()) {
43 bool GetSerial(const uint256 &serial) const
45 std::map<uint256, bool>::const_iterator it = mapSerials_.find(serial);
47 if (it == mapSerials_.end()) {
50 // The map shouldn't contain any false entries.
56 uint256 GetBestAnchor() const { return hashBestAnchor_; }
58 bool GetCoins(const uint256& txid, CCoins& coins) const
60 std::map<uint256, CCoins>::const_iterator it = map_.find(txid);
61 if (it == map_.end()) {
65 if (coins.IsPruned() && insecure_rand() % 2 == 0) {
66 // Randomly return false in case of an empty entry.
72 bool HaveCoins(const uint256& txid) const
75 return GetCoins(txid, coins);
78 uint256 GetBestBlock() const { return hashBestBlock_; }
80 bool BatchWrite(CCoinsMap& mapCoins,
81 const uint256& hashBlock,
82 const uint256& hashAnchor,
83 CAnchorsMap& mapAnchors,
84 CSerialsMap& mapSerials)
86 for (CCoinsMap::iterator it = mapCoins.begin(); it != mapCoins.end(); ) {
87 map_[it->first] = it->second.coins;
88 if (it->second.coins.IsPruned() && insecure_rand() % 3 == 0) {
89 // Randomly delete empty entries on write.
90 map_.erase(it->first);
94 for (CAnchorsMap::iterator it = mapAnchors.begin(); it != mapAnchors.end(); ) {
95 if (it->second.entered) {
96 std::map<uint256, ZCIncrementalMerkleTree>::iterator ret =
97 mapAnchors_.insert(std::make_pair(it->first, ZCIncrementalMerkleTree())).first;
99 ret->second = it->second.tree;
101 mapAnchors_.erase(it->first);
103 mapAnchors.erase(it++);
105 for (CSerialsMap::iterator it = mapSerials.begin(); it != mapSerials.end(); ) {
106 if (it->second.entered) {
107 mapSerials_[it->first] = true;
109 mapSerials_.erase(it->first);
111 mapSerials.erase(it++);
116 hashBestBlock_ = hashBlock;
117 hashBestAnchor_ = hashAnchor;
121 bool GetStats(CCoinsStats& stats) const { return false; }
124 class CCoinsViewCacheTest : public CCoinsViewCache
127 CCoinsViewCacheTest(CCoinsView* base) : CCoinsViewCache(base) {}
129 void SelfTest() const
131 // Manually recompute the dynamic usage of the whole data, and compare it.
132 size_t ret = memusage::DynamicUsage(cacheCoins);
133 for (CCoinsMap::iterator it = cacheCoins.begin(); it != cacheCoins.end(); it++) {
134 ret += memusage::DynamicUsage(it->second.coins);
136 BOOST_CHECK_EQUAL(memusage::DynamicUsage(*this), ret);
143 BOOST_AUTO_TEST_CASE(serials_test)
146 CCoinsViewCacheTest cache(&base);
148 uint256 myserial = GetRandHash();
150 BOOST_CHECK(!cache.GetSerial(myserial));
151 cache.SetSerial(myserial, true);
152 BOOST_CHECK(cache.GetSerial(myserial));
155 CCoinsViewCacheTest cache2(&base);
157 BOOST_CHECK(cache2.GetSerial(myserial));
158 cache2.SetSerial(myserial, false);
159 BOOST_CHECK(!cache2.GetSerial(myserial));
162 CCoinsViewCacheTest cache3(&base);
164 BOOST_CHECK(!cache3.GetSerial(myserial));
167 void appendRandomCommitment(ZCIncrementalMerkleTree &tree)
169 Address addr = Address::CreateNewRandomAddress();
170 Coin coin(addr.getPublicAddress(), 100);
172 tree.append(uint256(coin.getCoinCommitment().getCommitmentValue()));
175 BOOST_AUTO_TEST_CASE(anchors_flush_test)
180 CCoinsViewCacheTest cache(&base);
181 ZCIncrementalMerkleTree tree;
182 BOOST_CHECK(cache.GetAnchorAt(cache.GetBestAnchor(), tree));
183 appendRandomCommitment(tree);
187 cache.PushAnchor(tree);
192 CCoinsViewCacheTest cache(&base);
193 ZCIncrementalMerkleTree tree;
194 BOOST_CHECK(cache.GetAnchorAt(cache.GetBestAnchor(), tree));
196 // Get the cached entry.
197 BOOST_CHECK(cache.GetAnchorAt(cache.GetBestAnchor(), tree));
199 uint256 check_rt = tree.root();
201 BOOST_CHECK(check_rt == newrt);
205 BOOST_AUTO_TEST_CASE(anchors_test)
207 // TODO: These tests should be more methodical.
208 // Or, integrate with Bitcoin's tests later.
211 CCoinsViewCacheTest cache(&base);
213 BOOST_CHECK(cache.GetBestAnchor() == uint256());
216 ZCIncrementalMerkleTree tree;
218 BOOST_CHECK(cache.GetAnchorAt(cache.GetBestAnchor(), tree));
219 appendRandomCommitment(tree);
220 appendRandomCommitment(tree);
221 appendRandomCommitment(tree);
222 appendRandomCommitment(tree);
223 appendRandomCommitment(tree);
224 appendRandomCommitment(tree);
225 appendRandomCommitment(tree);
227 ZCIncrementalMerkleTree save_tree_for_later;
228 save_tree_for_later = tree;
230 uint256 newrt = tree.root();
233 cache.PushAnchor(tree);
234 BOOST_CHECK(cache.GetBestAnchor() == newrt);
237 ZCIncrementalMerkleTree confirm_same;
238 BOOST_CHECK(cache.GetAnchorAt(cache.GetBestAnchor(), confirm_same));
240 BOOST_CHECK(confirm_same.root() == newrt);
243 appendRandomCommitment(tree);
244 appendRandomCommitment(tree);
246 newrt2 = tree.root();
248 cache.PushAnchor(tree);
249 BOOST_CHECK(cache.GetBestAnchor() == newrt2);
251 ZCIncrementalMerkleTree test_tree;
252 BOOST_CHECK(cache.GetAnchorAt(cache.GetBestAnchor(), test_tree));
254 BOOST_CHECK(tree.root() == test_tree.root());
257 ZCIncrementalMerkleTree test_tree2;
258 cache.GetAnchorAt(newrt, test_tree2);
260 BOOST_CHECK(test_tree2.root() == newrt);
264 cache.PopAnchor(newrt);
265 ZCIncrementalMerkleTree obtain_tree;
266 assert(!cache.GetAnchorAt(newrt2, obtain_tree)); // should have been popped off
267 assert(cache.GetAnchorAt(newrt, obtain_tree));
269 assert(obtain_tree.root() == newrt);
274 BOOST_FIXTURE_TEST_SUITE(coins_tests, BasicTestingSetup)
276 static const unsigned int NUM_SIMULATION_ITERATIONS = 40000;
278 // This is a large randomized insert/remove simulation test on a variable-size
279 // stack of caches on top of CCoinsViewTest.
281 // It will randomly create/update/delete CCoins entries to a tip of caches, with
282 // txids picked from a limited list of random 256-bit hashes. Occasionally, a
283 // new tip is added to the stack of caches, or the tip is flushed and removed.
285 // During the process, booleans are kept to make sure that the randomized
286 // operation hits all branches.
287 BOOST_AUTO_TEST_CASE(coins_cache_simulation_test)
289 // Various coverage trackers.
290 bool removed_all_caches = false;
291 bool reached_4_caches = false;
292 bool added_an_entry = false;
293 bool removed_an_entry = false;
294 bool updated_an_entry = false;
295 bool found_an_entry = false;
296 bool missed_an_entry = false;
298 // A simple map to track what we expect the cache stack to represent.
299 std::map<uint256, CCoins> result;
302 CCoinsViewTest base; // A CCoinsViewTest at the bottom.
303 std::vector<CCoinsViewCacheTest*> stack; // A stack of CCoinsViewCaches on top.
304 stack.push_back(new CCoinsViewCacheTest(&base)); // Start with one cache.
306 // Use a limited set of random transaction ids, so we do test overwriting entries.
307 std::vector<uint256> txids;
308 txids.resize(NUM_SIMULATION_ITERATIONS / 8);
309 for (unsigned int i = 0; i < txids.size(); i++) {
310 txids[i] = GetRandHash();
313 for (unsigned int i = 0; i < NUM_SIMULATION_ITERATIONS; i++) {
314 // Do a random modification.
316 uint256 txid = txids[insecure_rand() % txids.size()]; // txid we're going to modify in this iteration.
317 CCoins& coins = result[txid];
318 CCoinsModifier entry = stack.back()->ModifyCoins(txid);
319 BOOST_CHECK(coins == *entry);
320 if (insecure_rand() % 5 == 0 || coins.IsPruned()) {
321 if (coins.IsPruned()) {
322 added_an_entry = true;
324 updated_an_entry = true;
326 coins.nVersion = insecure_rand();
327 coins.vout.resize(1);
328 coins.vout[0].nValue = insecure_rand();
333 removed_an_entry = true;
337 // Once every 1000 iterations and at the end, verify the full cache.
338 if (insecure_rand() % 1000 == 1 || i == NUM_SIMULATION_ITERATIONS - 1) {
339 for (std::map<uint256, CCoins>::iterator it = result.begin(); it != result.end(); it++) {
340 const CCoins* coins = stack.back()->AccessCoins(it->first);
342 BOOST_CHECK(*coins == it->second);
343 found_an_entry = true;
345 BOOST_CHECK(it->second.IsPruned());
346 missed_an_entry = true;
349 BOOST_FOREACH(const CCoinsViewCacheTest *test, stack) {
354 if (insecure_rand() % 100 == 0) {
355 // Every 100 iterations, change the cache stack.
356 if (stack.size() > 0 && insecure_rand() % 2 == 0) {
357 stack.back()->Flush();
361 if (stack.size() == 0 || (stack.size() < 4 && insecure_rand() % 2)) {
362 CCoinsView* tip = &base;
363 if (stack.size() > 0) {
366 removed_all_caches = true;
368 stack.push_back(new CCoinsViewCacheTest(tip));
369 if (stack.size() == 4) {
370 reached_4_caches = true;
376 // Clean up the stack.
377 while (stack.size() > 0) {
383 BOOST_CHECK(removed_all_caches);
384 BOOST_CHECK(reached_4_caches);
385 BOOST_CHECK(added_an_entry);
386 BOOST_CHECK(removed_an_entry);
387 BOOST_CHECK(updated_an_entry);
388 BOOST_CHECK(found_an_entry);
389 BOOST_CHECK(missed_an_entry);
392 BOOST_AUTO_TEST_SUITE_END()