|
6 | 6 | #include "random.h"
|
7 | 7 | #include "uint256.h"
|
8 | 8 | #include "test/test_bitcoin.h"
|
| 9 | +#include "main.h" |
| 10 | +#include "consensus/validation.h" |
9 | 11 |
|
10 | 12 | #include <vector>
|
11 | 13 | #include <map>
|
@@ -45,15 +47,18 @@ class CCoinsViewTest : public CCoinsView
|
45 | 47 | bool BatchWrite(CCoinsMap& mapCoins, const uint256& hashBlock)
|
46 | 48 | {
|
47 | 49 | for (CCoinsMap::iterator it = mapCoins.begin(); it != mapCoins.end(); ) {
|
48 |
| - map_[it->first] = it->second.coins; |
49 |
| - if (it->second.coins.IsPruned() && insecure_rand() % 3 == 0) { |
50 |
| - // Randomly delete empty entries on write. |
51 |
| - map_.erase(it->first); |
| 50 | + if (it->second.flags & CCoinsCacheEntry::DIRTY) { |
| 51 | + // Same optimization used in CCoinsViewDB is to only write dirty entries. |
| 52 | + map_[it->first] = it->second.coins; |
| 53 | + if (it->second.coins.IsPruned() && insecure_rand() % 3 == 0) { |
| 54 | + // Randomly delete empty entries on write. |
| 55 | + map_.erase(it->first); |
| 56 | + } |
52 | 57 | }
|
53 | 58 | mapCoins.erase(it++);
|
54 | 59 | }
|
55 |
| - mapCoins.clear(); |
56 |
| - hashBestBlock_ = hashBlock; |
| 60 | + if (!hashBlock.IsNull()) |
| 61 | + hashBestBlock_ = hashBlock; |
57 | 62 | return true;
|
58 | 63 | }
|
59 | 64 |
|
@@ -197,4 +202,133 @@ BOOST_AUTO_TEST_CASE(coins_cache_simulation_test)
|
197 | 202 | BOOST_CHECK(missed_an_entry);
|
198 | 203 | }
|
199 | 204 |
|
| 205 | +// This test is similar to the previous test |
| 206 | +// except the emphasis is on testing the functionality of UpdateCoins |
| 207 | +// random txs are created and UpdateCoins is used to update the cache stack |
| 208 | +// In particular it is tested that spending a duplicate coinbase tx |
| 209 | +// has the expected effect (the other duplicate is overwitten at all cache levels) |
| 210 | +BOOST_AUTO_TEST_CASE(updatecoins_simulation_test) |
| 211 | +{ |
| 212 | + bool spent_a_duplicate_coinbase = false; |
| 213 | + // A simple map to track what we expect the cache stack to represent. |
| 214 | + std::map<uint256, CCoins> result; |
| 215 | + |
| 216 | + // The cache stack. |
| 217 | + CCoinsViewTest base; // A CCoinsViewTest at the bottom. |
| 218 | + std::vector<CCoinsViewCacheTest*> stack; // A stack of CCoinsViewCaches on top. |
| 219 | + stack.push_back(new CCoinsViewCacheTest(&base)); // Start with one cache. |
| 220 | + |
| 221 | + // Track the txids we've used and whether they have been spent or not |
| 222 | + std::map<uint256, CAmount> coinbaseids; |
| 223 | + std::set<uint256> alltxids; |
| 224 | + std::set<uint256> duplicateids; |
| 225 | + |
| 226 | + for (unsigned int i = 0; i < NUM_SIMULATION_ITERATIONS; i++) { |
| 227 | + { |
| 228 | + CMutableTransaction tx; |
| 229 | + tx.vin.resize(1); |
| 230 | + tx.vout.resize(1); |
| 231 | + tx.vout[0].nValue = i; //Keep txs unique unless intended to duplicate |
| 232 | + unsigned int height = insecure_rand(); |
| 233 | + |
| 234 | + // 1/10 times create a coinbase |
| 235 | + if (insecure_rand() % 10 == 0 || coinbaseids.size() < 10) { |
| 236 | + // 1/100 times create a duplicate coinbase |
| 237 | + if (insecure_rand() % 10 == 0 && coinbaseids.size()) { |
| 238 | + std::map<uint256, CAmount>::iterator coinbaseIt = coinbaseids.lower_bound(GetRandHash()); |
| 239 | + if (coinbaseIt == coinbaseids.end()) { |
| 240 | + coinbaseIt = coinbaseids.begin(); |
| 241 | + } |
| 242 | + //Use same random value to have same hash and be a true duplicate |
| 243 | + tx.vout[0].nValue = coinbaseIt->second; |
| 244 | + assert(tx.GetHash() == coinbaseIt->first); |
| 245 | + duplicateids.insert(coinbaseIt->first); |
| 246 | + } |
| 247 | + else { |
| 248 | + coinbaseids[tx.GetHash()] = tx.vout[0].nValue; |
| 249 | + } |
| 250 | + assert(CTransaction(tx).IsCoinBase()); |
| 251 | + } |
| 252 | + // 9/10 times create a regular tx |
| 253 | + else { |
| 254 | + uint256 prevouthash; |
| 255 | + // equally likely to spend coinbase or non coinbase |
| 256 | + std::set<uint256>::iterator txIt = alltxids.lower_bound(GetRandHash()); |
| 257 | + if (txIt == alltxids.end()) { |
| 258 | + txIt = alltxids.begin(); |
| 259 | + } |
| 260 | + prevouthash = *txIt; |
| 261 | + |
| 262 | + // Construct the tx to spend the coins of prevouthash |
| 263 | + tx.vin[0].prevout.hash = prevouthash; |
| 264 | + tx.vin[0].prevout.n = 0; |
| 265 | + |
| 266 | + // Update the expected result of prevouthash to know these coins are spent |
| 267 | + CCoins& oldcoins = result[prevouthash]; |
| 268 | + oldcoins.Clear(); |
| 269 | + |
| 270 | + // It is of particular importance here that once we spend a coinbase tx hash |
| 271 | + // it is no longer available to be duplicated (or spent again) |
| 272 | + // BIP 34 in conjunction with enforcing BIP 30 (at least until BIP 34 was active) |
| 273 | + // results in the fact that no coinbases were duplicated after they were already spent |
| 274 | + alltxids.erase(prevouthash); |
| 275 | + coinbaseids.erase(prevouthash); |
| 276 | + |
| 277 | + // The test is designed to ensure spending a duplicate coinbase will work properly |
| 278 | + // if that ever happens and not resurrect the previously overwritten coinbase |
| 279 | + if (duplicateids.count(prevouthash)) |
| 280 | + spent_a_duplicate_coinbase = true; |
| 281 | + |
| 282 | + assert(!CTransaction(tx).IsCoinBase()); |
| 283 | + } |
| 284 | + // Track this tx to possibly spend later |
| 285 | + alltxids.insert(tx.GetHash()); |
| 286 | + |
| 287 | + // Update the expected result to know about the new output coins |
| 288 | + CCoins &coins = result[tx.GetHash()]; |
| 289 | + coins.FromTx(tx, height); |
| 290 | + |
| 291 | + CValidationState dummy; |
| 292 | + UpdateCoins(tx, dummy, *(stack.back()), height); |
| 293 | + } |
| 294 | + |
| 295 | + // Once every 1000 iterations and at the end, verify the full cache. |
| 296 | + if (insecure_rand() % 1000 == 1 || i == NUM_SIMULATION_ITERATIONS - 1) { |
| 297 | + for (std::map<uint256, CCoins>::iterator it = result.begin(); it != result.end(); it++) { |
| 298 | + const CCoins* coins = stack.back()->AccessCoins(it->first); |
| 299 | + if (coins) { |
| 300 | + BOOST_CHECK(*coins == it->second); |
| 301 | + } else { |
| 302 | + BOOST_CHECK(it->second.IsPruned()); |
| 303 | + } |
| 304 | + } |
| 305 | + } |
| 306 | + |
| 307 | + if (insecure_rand() % 100 == 0) { |
| 308 | + // Every 100 iterations, change the cache stack. |
| 309 | + if (stack.size() > 0 && insecure_rand() % 2 == 0) { |
| 310 | + stack.back()->Flush(); |
| 311 | + delete stack.back(); |
| 312 | + stack.pop_back(); |
| 313 | + } |
| 314 | + if (stack.size() == 0 || (stack.size() < 4 && insecure_rand() % 2)) { |
| 315 | + CCoinsView* tip = &base; |
| 316 | + if (stack.size() > 0) { |
| 317 | + tip = stack.back(); |
| 318 | + } |
| 319 | + stack.push_back(new CCoinsViewCacheTest(tip)); |
| 320 | + } |
| 321 | + } |
| 322 | + } |
| 323 | + |
| 324 | + // Clean up the stack. |
| 325 | + while (stack.size() > 0) { |
| 326 | + delete stack.back(); |
| 327 | + stack.pop_back(); |
| 328 | + } |
| 329 | + |
| 330 | + // Verify coverage. |
| 331 | + BOOST_CHECK(spent_a_duplicate_coinbase); |
| 332 | +} |
| 333 | + |
200 | 334 | BOOST_AUTO_TEST_SUITE_END()
|
0 commit comments