aboutsummaryrefslogtreecommitdiff
path: root/src/test/coins_tests.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/test/coins_tests.cpp')
-rw-r--r--src/test/coins_tests.cpp146
1 files changed, 140 insertions, 6 deletions
diff --git a/src/test/coins_tests.cpp b/src/test/coins_tests.cpp
index 13d848311a..946f904dfa 100644
--- a/src/test/coins_tests.cpp
+++ b/src/test/coins_tests.cpp
@@ -6,6 +6,8 @@
#include "random.h"
#include "uint256.h"
#include "test/test_bitcoin.h"
+#include "main.h"
+#include "consensus/validation.h"
#include <vector>
#include <map>
@@ -45,15 +47,18 @@ public:
bool BatchWrite(CCoinsMap& mapCoins, const uint256& hashBlock)
{
for (CCoinsMap::iterator it = mapCoins.begin(); it != mapCoins.end(); ) {
- map_[it->first] = it->second.coins;
- if (it->second.coins.IsPruned() && insecure_rand() % 3 == 0) {
- // Randomly delete empty entries on write.
- map_.erase(it->first);
+ if (it->second.flags & CCoinsCacheEntry::DIRTY) {
+ // Same optimization used in CCoinsViewDB is to only write dirty entries.
+ map_[it->first] = it->second.coins;
+ if (it->second.coins.IsPruned() && insecure_rand() % 3 == 0) {
+ // Randomly delete empty entries on write.
+ map_.erase(it->first);
+ }
}
mapCoins.erase(it++);
}
- mapCoins.clear();
- hashBestBlock_ = hashBlock;
+ if (!hashBlock.IsNull())
+ hashBestBlock_ = hashBlock;
return true;
}
@@ -197,4 +202,133 @@ BOOST_AUTO_TEST_CASE(coins_cache_simulation_test)
BOOST_CHECK(missed_an_entry);
}
+// This test is similar to the previous test
+// except the emphasis is on testing the functionality of UpdateCoins
+// random txs are created and UpdateCoins is used to update the cache stack
+// In particular it is tested that spending a duplicate coinbase tx
+// has the expected effect (the other duplicate is overwitten at all cache levels)
+BOOST_AUTO_TEST_CASE(updatecoins_simulation_test)
+{
+ bool spent_a_duplicate_coinbase = false;
+ // A simple map to track what we expect the cache stack to represent.
+ std::map<uint256, CCoins> result;
+
+ // The cache stack.
+ CCoinsViewTest base; // A CCoinsViewTest at the bottom.
+ std::vector<CCoinsViewCacheTest*> stack; // A stack of CCoinsViewCaches on top.
+ stack.push_back(new CCoinsViewCacheTest(&base)); // Start with one cache.
+
+ // Track the txids we've used and whether they have been spent or not
+ std::map<uint256, CAmount> coinbaseids;
+ std::set<uint256> alltxids;
+ std::set<uint256> duplicateids;
+
+ for (unsigned int i = 0; i < NUM_SIMULATION_ITERATIONS; i++) {
+ {
+ CMutableTransaction tx;
+ tx.vin.resize(1);
+ tx.vout.resize(1);
+ tx.vout[0].nValue = i; //Keep txs unique unless intended to duplicate
+ unsigned int height = insecure_rand();
+
+ // 1/10 times create a coinbase
+ if (insecure_rand() % 10 == 0 || coinbaseids.size() < 10) {
+ // 1/100 times create a duplicate coinbase
+ if (insecure_rand() % 10 == 0 && coinbaseids.size()) {
+ std::map<uint256, CAmount>::iterator coinbaseIt = coinbaseids.lower_bound(GetRandHash());
+ if (coinbaseIt == coinbaseids.end()) {
+ coinbaseIt = coinbaseids.begin();
+ }
+ //Use same random value to have same hash and be a true duplicate
+ tx.vout[0].nValue = coinbaseIt->second;
+ assert(tx.GetHash() == coinbaseIt->first);
+ duplicateids.insert(coinbaseIt->first);
+ }
+ else {
+ coinbaseids[tx.GetHash()] = tx.vout[0].nValue;
+ }
+ assert(CTransaction(tx).IsCoinBase());
+ }
+ // 9/10 times create a regular tx
+ else {
+ uint256 prevouthash;
+ // equally likely to spend coinbase or non coinbase
+ std::set<uint256>::iterator txIt = alltxids.lower_bound(GetRandHash());
+ if (txIt == alltxids.end()) {
+ txIt = alltxids.begin();
+ }
+ prevouthash = *txIt;
+
+ // Construct the tx to spend the coins of prevouthash
+ tx.vin[0].prevout.hash = prevouthash;
+ tx.vin[0].prevout.n = 0;
+
+ // Update the expected result of prevouthash to know these coins are spent
+ CCoins& oldcoins = result[prevouthash];
+ oldcoins.Clear();
+
+ // It is of particular importance here that once we spend a coinbase tx hash
+ // it is no longer available to be duplicated (or spent again)
+ // BIP 34 in conjunction with enforcing BIP 30 (at least until BIP 34 was active)
+ // results in the fact that no coinbases were duplicated after they were already spent
+ alltxids.erase(prevouthash);
+ coinbaseids.erase(prevouthash);
+
+ // The test is designed to ensure spending a duplicate coinbase will work properly
+ // if that ever happens and not resurrect the previously overwritten coinbase
+ if (duplicateids.count(prevouthash))
+ spent_a_duplicate_coinbase = true;
+
+ assert(!CTransaction(tx).IsCoinBase());
+ }
+ // Track this tx to possibly spend later
+ alltxids.insert(tx.GetHash());
+
+ // Update the expected result to know about the new output coins
+ CCoins &coins = result[tx.GetHash()];
+ coins.FromTx(tx, height);
+
+ CValidationState dummy;
+ UpdateCoins(tx, dummy, *(stack.back()), height);
+ }
+
+ // Once every 1000 iterations and at the end, verify the full cache.
+ if (insecure_rand() % 1000 == 1 || i == NUM_SIMULATION_ITERATIONS - 1) {
+ for (std::map<uint256, CCoins>::iterator it = result.begin(); it != result.end(); it++) {
+ const CCoins* coins = stack.back()->AccessCoins(it->first);
+ if (coins) {
+ BOOST_CHECK(*coins == it->second);
+ } else {
+ BOOST_CHECK(it->second.IsPruned());
+ }
+ }
+ }
+
+ if (insecure_rand() % 100 == 0) {
+ // Every 100 iterations, change the cache stack.
+ if (stack.size() > 0 && insecure_rand() % 2 == 0) {
+ stack.back()->Flush();
+ delete stack.back();
+ stack.pop_back();
+ }
+ if (stack.size() == 0 || (stack.size() < 4 && insecure_rand() % 2)) {
+ CCoinsView* tip = &base;
+ if (stack.size() > 0) {
+ tip = stack.back();
+ }
+ stack.push_back(new CCoinsViewCacheTest(tip));
+ }
+ }
+ }
+
+ // Clean up the stack.
+ while (stack.size() > 0) {
+ delete stack.back();
+ stack.pop_back();
+ }
+
+ // Verify coverage.
+ BOOST_CHECK(spent_a_duplicate_coinbase);
+}
+
BOOST_AUTO_TEST_SUITE_END()