Changeset View
Changeset View
Standalone View
Standalone View
src/test/coins_tests.cpp
Show All 24 Lines | bool operator==(const Coin &a, const Coin &b) { | ||||
if (a.IsSpent() && b.IsSpent()) { | if (a.IsSpent() && b.IsSpent()) { | ||||
return true; | return true; | ||||
} | } | ||||
return a.IsCoinBase() == b.IsCoinBase() && a.GetHeight() == b.GetHeight() && | return a.IsCoinBase() == b.IsCoinBase() && a.GetHeight() == b.GetHeight() && | ||||
a.GetTxOut() == b.GetTxOut(); | a.GetTxOut() == b.GetTxOut(); | ||||
} | } | ||||
//! equality test | |||||
bool operator==(const Coin &c, const CCoins &coins) { | |||||
if (coins.vout.empty()) { | |||||
return c.IsSpent(); | |||||
} | |||||
return c == Coin(coins.vout[0], coins.nHeight, coins.fCoinBase); | |||||
} | |||||
class CCoinsViewTest : public CCoinsView { | class CCoinsViewTest : public CCoinsView { | ||||
uint256 hashBestBlock_; | uint256 hashBestBlock_; | ||||
std::map<uint256, CCoins> map_; | std::map<uint256, CCoins> map_; | ||||
public: | public: | ||||
bool GetCoins(const uint256 &txid, CCoins &coins) const { | bool GetCoins(const uint256 &txid, CCoins &coins) const { | ||||
std::map<uint256, CCoins>::const_iterator it = map_.find(txid); | std::map<uint256, CCoins>::const_iterator it = map_.find(txid); | ||||
if (it == map_.end()) { | if (it == map_.end()) { | ||||
▲ Show 20 Lines • Show All 65 Lines • ▼ Show 20 Lines | |||||
// | // | ||||
// During the process, booleans are kept to make sure that the randomized | // During the process, booleans are kept to make sure that the randomized | ||||
// operation hits all branches. | // operation hits all branches. | ||||
BOOST_AUTO_TEST_CASE(coins_cache_simulation_test) { | BOOST_AUTO_TEST_CASE(coins_cache_simulation_test) { | ||||
// Various coverage trackers. | // Various coverage trackers. | ||||
bool removed_all_caches = false; | bool removed_all_caches = false; | ||||
bool reached_4_caches = false; | bool reached_4_caches = false; | ||||
bool added_an_entry = false; | bool added_an_entry = false; | ||||
bool added_an_unspendable_entry = false; | |||||
bool removed_an_entry = false; | bool removed_an_entry = false; | ||||
bool updated_an_entry = false; | bool updated_an_entry = false; | ||||
bool found_an_entry = false; | bool found_an_entry = false; | ||||
bool missed_an_entry = false; | bool missed_an_entry = false; | ||||
bool uncached_an_entry = false; | bool uncached_an_entry = false; | ||||
// A simple map to track what we expect the cache stack to represent. | // A simple map to track what we expect the cache stack to represent. | ||||
std::map<uint256, CCoins> result; | std::map<COutPoint, Coin> result; | ||||
// The cache stack. | // The cache stack. | ||||
// A CCoinsViewTest at the bottom. | // A CCoinsViewTest at the bottom. | ||||
CCoinsViewTest base; | CCoinsViewTest base; | ||||
// A stack of CCoinsViewCaches on top. | // A stack of CCoinsViewCaches on top. | ||||
std::vector<CCoinsViewCacheTest *> stack; | std::vector<CCoinsViewCacheTest *> stack; | ||||
// Start with one cache. | // Start with one cache. | ||||
stack.push_back(new CCoinsViewCacheTest(&base)); | stack.push_back(new CCoinsViewCacheTest(&base)); | ||||
// Use a limited set of random transaction ids, so we do test overwriting | // Use a limited set of random transaction ids, so we do test overwriting | ||||
// entries. | // entries. | ||||
std::vector<uint256> txids; | std::vector<uint256> txids; | ||||
txids.resize(NUM_SIMULATION_ITERATIONS / 8); | txids.resize(NUM_SIMULATION_ITERATIONS / 8); | ||||
for (unsigned int i = 0; i < txids.size(); i++) { | for (size_t i = 0; i < txids.size(); i++) { | ||||
txids[i] = GetRandHash(); | txids[i] = GetRandHash(); | ||||
} | } | ||||
for (unsigned int i = 0; i < NUM_SIMULATION_ITERATIONS; i++) { | for (unsigned int i = 0; i < NUM_SIMULATION_ITERATIONS; i++) { | ||||
// Do a random modification. | // Do a random modification. | ||||
{ | { | ||||
// txid we're going to modify in this iteration. | // txid we're going to modify in this iteration. | ||||
uint256 txid = txids[insecure_rand() % txids.size()]; | uint256 txid = txids[insecure_rand() % txids.size()]; | ||||
CCoins &coins = result[txid]; | Coin &coin = result[COutPoint(txid, 0)]; | ||||
CCoinsModifier entry = stack.back()->ModifyCoins(txid); | const Coin &entry = | ||||
BOOST_CHECK(coins == *entry); | (insecure_rand() % 500 == 0) | ||||
if (insecure_rand() % 5 == 0 || coins.IsPruned()) { | ? AccessByTxid(*stack.back(), txid) | ||||
if (coins.IsPruned()) { | : stack.back()->AccessCoin(COutPoint(txid, 0)); | ||||
added_an_entry = true; | BOOST_CHECK(coin == entry); | ||||
if (insecure_rand() % 5 == 0 || coin.IsSpent()) { | |||||
CTxOut txout; | |||||
txout.nValue = insecure_rand(); | |||||
if (insecure_rand() % 16 == 0 && coin.IsSpent()) { | |||||
txout.scriptPubKey.assign(1 + (insecure_rand() & 0x3F), | |||||
OP_RETURN); | |||||
BOOST_CHECK(txout.scriptPubKey.IsUnspendable()); | |||||
added_an_unspendable_entry = true; | |||||
} else { | } else { | ||||
updated_an_entry = true; | // Random sizes so we can test memory usage accounting | ||||
txout.scriptPubKey.assign(insecure_rand() & 0x3F, 0); | |||||
(coin.IsSpent() ? added_an_entry : updated_an_entry) = true; | |||||
coin = Coin(txout, 1, false); | |||||
} | } | ||||
coins.vout.resize(1); | |||||
coins.vout[0].nValue = insecure_rand(); | Coin newcoin(txout, 1, false); | ||||
*entry = coins; | stack.back()->AddCoin(COutPoint(txid, 0), newcoin, | ||||
!coin.IsSpent() || insecure_rand() & 1); | |||||
} else { | } else { | ||||
coins.Clear(); | |||||
entry->Clear(); | |||||
removed_an_entry = true; | removed_an_entry = true; | ||||
coin.Clear(); | |||||
stack.back()->SpendCoin(COutPoint(txid, 0)); | |||||
} | } | ||||
} | } | ||||
// One every 10 iterations, remove a random entry from the cache | // One every 10 iterations, remove a random entry from the cache | ||||
if (insecure_rand() % 10) { | if (insecure_rand() % 10) { | ||||
COutPoint out(txids[insecure_rand() % txids.size()], 0); | COutPoint out(txids[insecure_rand() % txids.size()], 0); | ||||
int cacheid = insecure_rand() % stack.size(); | int cacheid = insecure_rand() % stack.size(); | ||||
stack[cacheid]->Uncache(out); | stack[cacheid]->Uncache(out); | ||||
uncached_an_entry |= !stack[cacheid]->HaveCoinInCache(out); | uncached_an_entry |= !stack[cacheid]->HaveCoinInCache(out); | ||||
} | } | ||||
// Once every 1000 iterations and at the end, verify the full cache. | // Once every 1000 iterations and at the end, verify the full cache. | ||||
if (insecure_rand() % 1000 == 1 || i == NUM_SIMULATION_ITERATIONS - 1) { | if (insecure_rand() % 1000 == 1 || i == NUM_SIMULATION_ITERATIONS - 1) { | ||||
for (auto it = result.begin(); it != result.end(); it++) { | for (auto it = result.begin(); it != result.end(); it++) { | ||||
bool have = stack.back()->HaveCoin(COutPoint(it->first, 0)); | bool have = stack.back()->HaveCoin(it->first); | ||||
const Coin &coin = | const Coin &coin = stack.back()->AccessCoin(it->first); | ||||
stack.back()->AccessCoin(COutPoint(it->first, 0)); | |||||
BOOST_CHECK(have == !coin.IsSpent()); | BOOST_CHECK(have == !coin.IsSpent()); | ||||
BOOST_CHECK(coin == it->second); | BOOST_CHECK(coin == it->second); | ||||
if (coin.IsSpent()) { | if (coin.IsSpent()) { | ||||
missed_an_entry = true; | missed_an_entry = true; | ||||
} else { | } else { | ||||
BOOST_CHECK( | BOOST_CHECK(stack.back()->HaveCoinInCache(it->first)); | ||||
stack.back()->HaveCoinInCache(COutPoint(it->first, 0))); | |||||
found_an_entry = true; | found_an_entry = true; | ||||
} | } | ||||
} | } | ||||
for (const CCoinsViewCacheTest *test : stack) { | for (const CCoinsViewCacheTest *test : stack) { | ||||
test->SelfTest(); | test->SelfTest(); | ||||
} | } | ||||
} | } | ||||
if (insecure_rand() % 100 == 0) { | |||||
// Every 100 iterations, flush an intermediate cache | // Every 100 iterations, flush an intermediate cache | ||||
if (insecure_rand() % 100 == 0) { | |||||
if (stack.size() > 1 && insecure_rand() % 2 == 0) { | if (stack.size() > 1 && insecure_rand() % 2 == 0) { | ||||
unsigned int flushIndex = insecure_rand() % (stack.size() - 1); | unsigned int flushIndex = insecure_rand() % (stack.size() - 1); | ||||
stack[flushIndex]->Flush(); | stack[flushIndex]->Flush(); | ||||
} | } | ||||
} | } | ||||
if (insecure_rand() % 100 == 0) { | if (insecure_rand() % 100 == 0) { | ||||
// Every 100 iterations, change the cache stack. | // Every 100 iterations, change the cache stack. | ||||
if (stack.size() > 0 && insecure_rand() % 2 == 0) { | if (stack.size() > 0 && insecure_rand() % 2 == 0) { | ||||
Show All 24 Lines | while (stack.size() > 0) { | ||||
delete stack.back(); | delete stack.back(); | ||||
stack.pop_back(); | stack.pop_back(); | ||||
} | } | ||||
// Verify coverage. | // Verify coverage. | ||||
BOOST_CHECK(removed_all_caches); | BOOST_CHECK(removed_all_caches); | ||||
BOOST_CHECK(reached_4_caches); | BOOST_CHECK(reached_4_caches); | ||||
BOOST_CHECK(added_an_entry); | BOOST_CHECK(added_an_entry); | ||||
BOOST_CHECK(added_an_unspendable_entry); | |||||
BOOST_CHECK(removed_an_entry); | BOOST_CHECK(removed_an_entry); | ||||
BOOST_CHECK(updated_an_entry); | BOOST_CHECK(updated_an_entry); | ||||
BOOST_CHECK(found_an_entry); | BOOST_CHECK(found_an_entry); | ||||
BOOST_CHECK(missed_an_entry); | BOOST_CHECK(missed_an_entry); | ||||
BOOST_CHECK(uncached_an_entry); | BOOST_CHECK(uncached_an_entry); | ||||
} | } | ||||
typedef std::tuple<CTransaction, CTxUndo, CCoins> TxData; | |||||
// Store of all necessary tx and undo data for next test | // Store of all necessary tx and undo data for next test | ||||
std::map<uint256, TxData> alltxs; | typedef std::map<COutPoint, std::tuple<CTransaction, CTxUndo, Coin>> UtxoData; | ||||
UtxoData utxoData; | |||||
TxData &FindRandomFrom(const std::set<uint256> &txidset) { | UtxoData::iterator FindRandomFrom(const std::set<COutPoint> &utxoSet) { | ||||
assert(txidset.size()); | assert(utxoSet.size()); | ||||
std::set<uint256>::iterator txIt = txidset.lower_bound(GetRandHash()); | auto utxoSetIt = utxoSet.lower_bound(COutPoint(GetRandHash(), 0)); | ||||
if (txIt == txidset.end()) { | if (utxoSetIt == utxoSet.end()) { | ||||
txIt = txidset.begin(); | utxoSetIt = utxoSet.begin(); | ||||
} | } | ||||
std::map<uint256, TxData>::iterator txdit = alltxs.find(*txIt); | auto utxoDataIt = utxoData.find(*utxoSetIt); | ||||
assert(txdit != alltxs.end()); | assert(utxoDataIt != utxoData.end()); | ||||
return txdit->second; | return utxoDataIt; | ||||
} | } | ||||
// This test is similar to the previous test except the emphasis is on testing | // 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 | // 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 | // 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 | // duplicate coinbase tx has the expected effect (the other duplicate is | ||||
// overwitten at all cache levels) | // overwitten at all cache levels) | ||||
BOOST_AUTO_TEST_CASE(updatecoins_simulation_test) { | BOOST_AUTO_TEST_CASE(updatecoins_simulation_test) { | ||||
bool spent_a_duplicate_coinbase = false; | bool spent_a_duplicate_coinbase = false; | ||||
// A simple map to track what we expect the cache stack to represent. | // A simple map to track what we expect the cache stack to represent. | ||||
std::map<uint256, CCoins> result; | std::map<COutPoint, Coin> result; | ||||
// The cache stack. | // The cache stack. | ||||
// A CCoinsViewTest at the bottom. | // A CCoinsViewTest at the bottom. | ||||
CCoinsViewTest base; | CCoinsViewTest base; | ||||
// A stack of CCoinsViewCaches on top. | // A stack of CCoinsViewCaches on top. | ||||
std::vector<CCoinsViewCacheTest *> stack; | std::vector<CCoinsViewCacheTest *> stack; | ||||
// Start with one cache. | // Start with one cache. | ||||
stack.push_back(new CCoinsViewCacheTest(&base)); | stack.push_back(new CCoinsViewCacheTest(&base)); | ||||
// Track the txids we've used in various sets | // Track the txids we've used in various sets | ||||
std::set<uint256> coinbaseids; | std::set<COutPoint> coinbase_coins; | ||||
std::set<uint256> disconnectedids; | std::set<COutPoint> disconnected_coins; | ||||
std::set<uint256> duplicateids; | std::set<COutPoint> duplicate_coins; | ||||
std::set<uint256> utxoset; | std::set<COutPoint> utxoset; | ||||
for (unsigned int i = 0; i < NUM_SIMULATION_ITERATIONS; i++) { | for (unsigned int i = 0; i < NUM_SIMULATION_ITERATIONS; i++) { | ||||
uint32_t randiter = insecure_rand(); | uint32_t randiter = insecure_rand(); | ||||
// 19/20 txs add a new transaction | // 19/20 txs add a new transaction | ||||
if (randiter % 20 < 19) { | if (randiter % 20 < 19) { | ||||
CMutableTransaction tx; | CMutableTransaction tx; | ||||
tx.vin.resize(1); | tx.vin.resize(1); | ||||
tx.vout.resize(1); | tx.vout.resize(1); | ||||
// Keep txs unique unless intended to duplicate. | // Keep txs unique unless intended to duplicate. | ||||
tx.vout[0].nValue = i; | tx.vout[0].nValue = i; | ||||
// Random sizes so we can test memory usage accounting | // Random sizes so we can test memory usage accounting | ||||
tx.vout[0].scriptPubKey.assign(insecure_rand() & 0x3F, 0); | tx.vout[0].scriptPubKey.assign(insecure_rand() & 0x3F, 0); | ||||
unsigned int height = insecure_rand(); | unsigned int height = insecure_rand(); | ||||
CCoins oldcoins; | Coin old_coin; | ||||
// 2/20 times create a new coinbase | // 2/20 times create a new coinbase | ||||
if (randiter % 20 < 2 || coinbaseids.size() < 10) { | if (randiter % 20 < 2 || coinbase_coins.size() < 10) { | ||||
// 1/10 of those times create a duplicate coinbase | // 1/10 of those times create a duplicate coinbase | ||||
if (insecure_rand() % 10 == 0 && coinbaseids.size()) { | if (insecure_rand() % 10 == 0 && coinbase_coins.size()) { | ||||
TxData &txd = FindRandomFrom(coinbaseids); | auto utxod = FindRandomFrom(coinbase_coins); | ||||
// Reuse the exact same coinbase | // Reuse the exact same coinbase | ||||
tx = std::get<0>(txd); | tx = std::get<0>(utxod->second); | ||||
// shouldn't be available for reconnection if its been | // shouldn't be available for reconnection if its been | ||||
// duplicated | // duplicated | ||||
disconnectedids.erase(tx.GetId()); | disconnected_coins.erase(utxod->first); | ||||
duplicateids.insert(tx.GetId()); | duplicate_coins.insert(utxod->first); | ||||
} else { | } else { | ||||
coinbaseids.insert(tx.GetId()); | coinbase_coins.insert(COutPoint(tx.GetId(), 0)); | ||||
} | } | ||||
assert(CTransaction(tx).IsCoinBase()); | assert(CTransaction(tx).IsCoinBase()); | ||||
} | } | ||||
// 17/20 times reconnect previous or add a regular tx | // 17/20 times reconnect previous or add a regular tx | ||||
else { | else { | ||||
uint256 prevouthash; | COutPoint prevout; | ||||
// 1/20 times reconnect a previously disconnected tx | // 1/20 times reconnect a previously disconnected tx | ||||
if (randiter % 20 == 2 && disconnectedids.size()) { | if (randiter % 20 == 2 && disconnected_coins.size()) { | ||||
TxData &txd = FindRandomFrom(disconnectedids); | auto utxod = FindRandomFrom(disconnected_coins); | ||||
tx = std::get<0>(txd); | tx = std::get<0>(utxod->second); | ||||
prevouthash = tx.vin[0].prevout.hash; | prevout = tx.vin[0].prevout; | ||||
if (!CTransaction(tx).IsCoinBase() && | if (!CTransaction(tx).IsCoinBase() && | ||||
!utxoset.count(prevouthash)) { | !utxoset.count(prevout)) { | ||||
disconnectedids.erase(tx.GetId()); | disconnected_coins.erase(utxod->first); | ||||
continue; | continue; | ||||
} | } | ||||
// If this tx is already IN the UTXO, then it must be a | // If this tx is already IN the UTXO, then it must be a | ||||
// coinbase, and it must be a duplicate | // coinbase, and it must be a duplicate | ||||
if (utxoset.count(tx.GetId())) { | if (utxoset.count(utxod->first)) { | ||||
assert(CTransaction(tx).IsCoinBase()); | assert(CTransaction(tx).IsCoinBase()); | ||||
assert(duplicateids.count(tx.GetId())); | assert(duplicate_coins.count(utxod->first)); | ||||
} | } | ||||
disconnectedids.erase(tx.GetId()); | disconnected_coins.erase(utxod->first); | ||||
} | } | ||||
// 16/20 times create a regular tx | // 16/20 times create a regular tx | ||||
else { | else { | ||||
TxData &txd = FindRandomFrom(utxoset); | auto utxod = FindRandomFrom(utxoset); | ||||
prevouthash = std::get<0>(txd).GetId(); | prevout = utxod->first; | ||||
// Construct the tx to spend the coins of prevouthash | // Construct the tx to spend the coins of prevouthash | ||||
tx.vin[0].prevout.hash = prevouthash; | tx.vin[0].prevout = prevout; | ||||
tx.vin[0].prevout.n = 0; | tx.vin[0].prevout.n = 0; | ||||
assert(!CTransaction(tx).IsCoinBase()); | assert(!CTransaction(tx).IsCoinBase()); | ||||
} | } | ||||
// In this simple test coins only have two states, spent or | // In this simple test coins only have two states, spent or | ||||
// unspent, save the unspent state to restore | // unspent, save the unspent state to restore | ||||
oldcoins = result[prevouthash]; | old_coin = result[prevout]; | ||||
// Update the expected result of prevouthash to know these coins | // Update the expected result of prevouthash to know these coins | ||||
// are spent | // are spent | ||||
result[prevouthash].Clear(); | result[prevout].Clear(); | ||||
utxoset.erase(prevouthash); | utxoset.erase(prevout); | ||||
// The test is designed to ensure spending a duplicate coinbase | // The test is designed to ensure spending a duplicate coinbase | ||||
// will work properly if that ever happens and not resurrect the | // will work properly if that ever happens and not resurrect the | ||||
// previously overwritten coinbase | // previously overwritten coinbase | ||||
if (duplicateids.count(prevouthash)) | if (duplicate_coins.count(prevout)) { | ||||
spent_a_duplicate_coinbase = true; | spent_a_duplicate_coinbase = true; | ||||
} | } | ||||
} | |||||
// Update the expected result to know about the new output coins | // Update the expected result to know about the new output coins | ||||
result[tx.GetId()].FromTx(tx, height); | assert(tx.vout.size() == 1); | ||||
const COutPoint outpoint(tx.GetId(), 0); | |||||
result[outpoint] = | |||||
Coin(tx.vout[0], height, CTransaction(tx).IsCoinBase()); | |||||
// Call UpdateCoins on the top cache | // Call UpdateCoins on the top cache | ||||
CTxUndo undo; | CTxUndo undo; | ||||
UpdateCoins(tx, *(stack.back()), undo, height); | UpdateCoins(tx, *(stack.back()), undo, height); | ||||
// Update the utxo set for future spends | // Update the utxo set for future spends | ||||
utxoset.insert(tx.GetId()); | utxoset.insert(outpoint); | ||||
// Track this tx and undo info to use later | // Track this tx and undo info to use later | ||||
alltxs.insert(std::make_pair(tx.GetId(), | utxoData.emplace(outpoint, std::make_tuple(tx, undo, old_coin)); | ||||
std::make_tuple(tx, undo, oldcoins))); | |||||
} | } | ||||
// 1/20 times undo a previous transaction | // 1/20 times undo a previous transaction | ||||
else if (utxoset.size()) { | else if (utxoset.size()) { | ||||
TxData &txd = FindRandomFrom(utxoset); | auto utxod = FindRandomFrom(utxoset); | ||||
CTransaction &tx = std::get<0>(txd); | |||||
CTxUndo &undo = std::get<1>(txd); | |||||
CCoins &origcoins = std::get<2>(txd); | |||||
uint256 undohash = tx.GetId(); | CTransaction &tx = std::get<0>(utxod->second); | ||||
CTxUndo &undo = std::get<1>(utxod->second); | |||||
Coin &orig_coin = std::get<2>(utxod->second); | |||||
// Update the expected result | // Update the expected result | ||||
// Remove new outputs | // Remove new outputs | ||||
result[undohash].Clear(); | result[utxod->first].Clear(); | ||||
// If not coinbase restore prevout | // If not coinbase restore prevout | ||||
if (!tx.IsCoinBase()) { | if (!tx.IsCoinBase()) { | ||||
result[tx.vin[0].prevout.hash] = origcoins; | result[tx.vin[0].prevout] = orig_coin; | ||||
} | } | ||||
// Disconnect the tx from the current UTXO | // Disconnect the tx from the current UTXO | ||||
// See code in DisconnectBlock | // See code in DisconnectBlock | ||||
// remove outputs | // remove outputs | ||||
{ | stack.back()->SpendCoin(utxod->first); | ||||
CCoinsModifier outs = stack.back()->ModifyCoins(undohash); | |||||
outs->Clear(); | |||||
} | |||||
// restore inputs | // restore inputs | ||||
if (!tx.IsCoinBase()) { | if (!tx.IsCoinBase()) { | ||||
const COutPoint &out = tx.vin[0].prevout; | const COutPoint &out = tx.vin[0].prevout; | ||||
const Coin &undoin = undo.vprevout[0]; | UndoCoinSpend(undo.vprevout[0], *(stack.back()), out); | ||||
UndoCoinSpend(undoin, *(stack.back()), out); | |||||
} | } | ||||
// Store as a candidate for reconnection | // Store as a candidate for reconnection | ||||
disconnectedids.insert(undohash); | disconnected_coins.insert(utxod->first); | ||||
// Update the utxoset | // Update the utxoset | ||||
utxoset.erase(undohash); | utxoset.erase(utxod->first); | ||||
if (!tx.IsCoinBase()) { | if (!tx.IsCoinBase()) { | ||||
utxoset.insert(tx.vin[0].prevout.hash); | utxoset.insert(tx.vin[0].prevout); | ||||
} | } | ||||
} | } | ||||
// Once every 1000 iterations and at the end, verify the full cache. | // Once every 1000 iterations and at the end, verify the full cache. | ||||
if (insecure_rand() % 1000 == 1 || i == NUM_SIMULATION_ITERATIONS - 1) { | if (insecure_rand() % 1000 == 1 || i == NUM_SIMULATION_ITERATIONS - 1) { | ||||
for (auto it = result.begin(); it != result.end(); it++) { | for (auto it = result.begin(); it != result.end(); it++) { | ||||
bool have = stack.back()->HaveCoin(COutPoint(it->first, 0)); | bool have = stack.back()->HaveCoin(it->first); | ||||
const Coin &coin = | const Coin &coin = stack.back()->AccessCoin(it->first); | ||||
stack.back()->AccessCoin(COutPoint(it->first, 0)); | |||||
BOOST_CHECK(have == !coin.IsSpent()); | BOOST_CHECK(have == !coin.IsSpent()); | ||||
BOOST_CHECK(coin == it->second); | BOOST_CHECK(coin == it->second); | ||||
} | } | ||||
} | } | ||||
// One every 10 iterations, remove a random entry from the cache | |||||
if (utxoset.size() > 1 && insecure_rand() % 30) { | |||||
stack[insecure_rand() % stack.size()]->Uncache( | |||||
FindRandomFrom(utxoset)->first); | |||||
} | |||||
if (disconnected_coins.size() > 1 && insecure_rand() % 30) { | |||||
stack[insecure_rand() % stack.size()]->Uncache( | |||||
FindRandomFrom(disconnected_coins)->first); | |||||
} | |||||
if (duplicate_coins.size() > 1 && insecure_rand() % 30) { | |||||
stack[insecure_rand() % stack.size()]->Uncache( | |||||
FindRandomFrom(duplicate_coins)->first); | |||||
} | |||||
if (insecure_rand() % 100 == 0) { | if (insecure_rand() % 100 == 0) { | ||||
// Every 100 iterations, flush an intermediate cache | // Every 100 iterations, flush an intermediate cache | ||||
if (stack.size() > 1 && insecure_rand() % 2 == 0) { | if (stack.size() > 1 && insecure_rand() % 2 == 0) { | ||||
unsigned int flushIndex = insecure_rand() % (stack.size() - 1); | unsigned int flushIndex = insecure_rand() % (stack.size() - 1); | ||||
stack[flushIndex]->Flush(); | stack[flushIndex]->Flush(); | ||||
} | } | ||||
} | } | ||||
if (insecure_rand() % 100 == 0) { | if (insecure_rand() % 100 == 0) { | ||||
▲ Show 20 Lines • Show All 286 Lines • ▼ Show 20 Lines | BOOST_AUTO_TEST_CASE(coin_access) { | ||||
CheckAccessCoin(VALUE1, PRUNED, PRUNED, DIRTY, DIRTY); | CheckAccessCoin(VALUE1, PRUNED, PRUNED, DIRTY, DIRTY); | ||||
CheckAccessCoin(VALUE1, PRUNED, PRUNED, DIRTY | FRESH, DIRTY | FRESH); | CheckAccessCoin(VALUE1, PRUNED, PRUNED, DIRTY | FRESH, DIRTY | FRESH); | ||||
CheckAccessCoin(VALUE1, VALUE2, VALUE2, 0, 0); | CheckAccessCoin(VALUE1, VALUE2, VALUE2, 0, 0); | ||||
CheckAccessCoin(VALUE1, VALUE2, VALUE2, FRESH, FRESH); | CheckAccessCoin(VALUE1, VALUE2, VALUE2, FRESH, FRESH); | ||||
CheckAccessCoin(VALUE1, VALUE2, VALUE2, DIRTY, DIRTY); | CheckAccessCoin(VALUE1, VALUE2, VALUE2, DIRTY, DIRTY); | ||||
CheckAccessCoin(VALUE1, VALUE2, VALUE2, DIRTY | FRESH, DIRTY | FRESH); | CheckAccessCoin(VALUE1, VALUE2, VALUE2, DIRTY | FRESH, DIRTY | FRESH); | ||||
} | } | ||||
void CheckModifyCoins(CAmount base_value, CAmount cache_value, | |||||
CAmount modify_value, CAmount expected_value, | |||||
char cache_flags, char expected_flags) { | |||||
SingleEntryCacheTest test(base_value, cache_value, cache_flags); | |||||
SetCoinsValue(modify_value, *test.cache.ModifyCoins(TXID)); | |||||
test.cache.SelfTest(); | |||||
CAmount result_value; | |||||
char result_flags; | |||||
GetCoinsMapEntry(test.cache.map(), result_value, result_flags); | |||||
BOOST_CHECK_EQUAL(result_value, expected_value); | |||||
BOOST_CHECK_EQUAL(result_flags, expected_flags); | |||||
}; | |||||
BOOST_AUTO_TEST_CASE(ccoins_modify) { | |||||
/* Check ModifyCoin behavior, requesting a coin from a cache view layered on | |||||
* top of a base view, writing a modification to the coin, and then checking | |||||
* the resulting entry in the cache after the modification. | |||||
* | |||||
* Base Cache Write Result Cache Result | |||||
* Value Value Value Value Flags Flags | |||||
*/ | |||||
CheckModifyCoins(ABSENT, ABSENT, PRUNED, ABSENT, NO_ENTRY, NO_ENTRY); | |||||
CheckModifyCoins(ABSENT, ABSENT, VALUE3, VALUE3, NO_ENTRY, DIRTY | FRESH); | |||||
CheckModifyCoins(ABSENT, PRUNED, PRUNED, PRUNED, 0, DIRTY); | |||||
CheckModifyCoins(ABSENT, PRUNED, PRUNED, ABSENT, FRESH, NO_ENTRY); | |||||
CheckModifyCoins(ABSENT, PRUNED, PRUNED, PRUNED, DIRTY, DIRTY); | |||||
CheckModifyCoins(ABSENT, PRUNED, PRUNED, ABSENT, DIRTY | FRESH, NO_ENTRY); | |||||
CheckModifyCoins(ABSENT, PRUNED, VALUE3, VALUE3, 0, DIRTY); | |||||
CheckModifyCoins(ABSENT, PRUNED, VALUE3, VALUE3, FRESH, DIRTY | FRESH); | |||||
CheckModifyCoins(ABSENT, PRUNED, VALUE3, VALUE3, DIRTY, DIRTY); | |||||
CheckModifyCoins(ABSENT, PRUNED, VALUE3, VALUE3, DIRTY | FRESH, | |||||
DIRTY | FRESH); | |||||
CheckModifyCoins(ABSENT, VALUE2, PRUNED, PRUNED, 0, DIRTY); | |||||
CheckModifyCoins(ABSENT, VALUE2, PRUNED, ABSENT, FRESH, NO_ENTRY); | |||||
CheckModifyCoins(ABSENT, VALUE2, PRUNED, PRUNED, DIRTY, DIRTY); | |||||
CheckModifyCoins(ABSENT, VALUE2, PRUNED, ABSENT, DIRTY | FRESH, NO_ENTRY); | |||||
CheckModifyCoins(ABSENT, VALUE2, VALUE3, VALUE3, 0, DIRTY); | |||||
CheckModifyCoins(ABSENT, VALUE2, VALUE3, VALUE3, FRESH, DIRTY | FRESH); | |||||
CheckModifyCoins(ABSENT, VALUE2, VALUE3, VALUE3, DIRTY, DIRTY); | |||||
CheckModifyCoins(ABSENT, VALUE2, VALUE3, VALUE3, DIRTY | FRESH, | |||||
DIRTY | FRESH); | |||||
CheckModifyCoins(PRUNED, ABSENT, PRUNED, ABSENT, NO_ENTRY, NO_ENTRY); | |||||
CheckModifyCoins(PRUNED, ABSENT, VALUE3, VALUE3, NO_ENTRY, DIRTY | FRESH); | |||||
CheckModifyCoins(PRUNED, PRUNED, PRUNED, PRUNED, 0, DIRTY); | |||||
CheckModifyCoins(PRUNED, PRUNED, PRUNED, ABSENT, FRESH, NO_ENTRY); | |||||
CheckModifyCoins(PRUNED, PRUNED, PRUNED, PRUNED, DIRTY, DIRTY); | |||||
CheckModifyCoins(PRUNED, PRUNED, PRUNED, ABSENT, DIRTY | FRESH, NO_ENTRY); | |||||
CheckModifyCoins(PRUNED, PRUNED, VALUE3, VALUE3, 0, DIRTY); | |||||
CheckModifyCoins(PRUNED, PRUNED, VALUE3, VALUE3, FRESH, DIRTY | FRESH); | |||||
CheckModifyCoins(PRUNED, PRUNED, VALUE3, VALUE3, DIRTY, DIRTY); | |||||
CheckModifyCoins(PRUNED, PRUNED, VALUE3, VALUE3, DIRTY | FRESH, | |||||
DIRTY | FRESH); | |||||
CheckModifyCoins(PRUNED, VALUE2, PRUNED, PRUNED, 0, DIRTY); | |||||
CheckModifyCoins(PRUNED, VALUE2, PRUNED, ABSENT, FRESH, NO_ENTRY); | |||||
CheckModifyCoins(PRUNED, VALUE2, PRUNED, PRUNED, DIRTY, DIRTY); | |||||
CheckModifyCoins(PRUNED, VALUE2, PRUNED, ABSENT, DIRTY | FRESH, NO_ENTRY); | |||||
CheckModifyCoins(PRUNED, VALUE2, VALUE3, VALUE3, 0, DIRTY); | |||||
CheckModifyCoins(PRUNED, VALUE2, VALUE3, VALUE3, FRESH, DIRTY | FRESH); | |||||
CheckModifyCoins(PRUNED, VALUE2, VALUE3, VALUE3, DIRTY, DIRTY); | |||||
CheckModifyCoins(PRUNED, VALUE2, VALUE3, VALUE3, DIRTY | FRESH, | |||||
DIRTY | FRESH); | |||||
CheckModifyCoins(VALUE1, ABSENT, PRUNED, PRUNED, NO_ENTRY, DIRTY); | |||||
CheckModifyCoins(VALUE1, ABSENT, VALUE3, VALUE3, NO_ENTRY, DIRTY); | |||||
CheckModifyCoins(VALUE1, PRUNED, PRUNED, PRUNED, 0, DIRTY); | |||||
CheckModifyCoins(VALUE1, PRUNED, PRUNED, ABSENT, FRESH, NO_ENTRY); | |||||
CheckModifyCoins(VALUE1, PRUNED, PRUNED, PRUNED, DIRTY, DIRTY); | |||||
CheckModifyCoins(VALUE1, PRUNED, PRUNED, ABSENT, DIRTY | FRESH, NO_ENTRY); | |||||
CheckModifyCoins(VALUE1, PRUNED, VALUE3, VALUE3, 0, DIRTY); | |||||
CheckModifyCoins(VALUE1, PRUNED, VALUE3, VALUE3, FRESH, DIRTY | FRESH); | |||||
CheckModifyCoins(VALUE1, PRUNED, VALUE3, VALUE3, DIRTY, DIRTY); | |||||
CheckModifyCoins(VALUE1, PRUNED, VALUE3, VALUE3, DIRTY | FRESH, | |||||
DIRTY | FRESH); | |||||
CheckModifyCoins(VALUE1, VALUE2, PRUNED, PRUNED, 0, DIRTY); | |||||
CheckModifyCoins(VALUE1, VALUE2, PRUNED, ABSENT, FRESH, NO_ENTRY); | |||||
CheckModifyCoins(VALUE1, VALUE2, PRUNED, PRUNED, DIRTY, DIRTY); | |||||
CheckModifyCoins(VALUE1, VALUE2, PRUNED, ABSENT, DIRTY | FRESH, NO_ENTRY); | |||||
CheckModifyCoins(VALUE1, VALUE2, VALUE3, VALUE3, 0, DIRTY); | |||||
CheckModifyCoins(VALUE1, VALUE2, VALUE3, VALUE3, FRESH, DIRTY | FRESH); | |||||
CheckModifyCoins(VALUE1, VALUE2, VALUE3, VALUE3, DIRTY, DIRTY); | |||||
CheckModifyCoins(VALUE1, VALUE2, VALUE3, VALUE3, DIRTY | FRESH, | |||||
DIRTY | FRESH); | |||||
} | |||||
void CheckSpendCoins(CAmount base_value, CAmount cache_value, | void CheckSpendCoins(CAmount base_value, CAmount cache_value, | ||||
CAmount expected_value, char cache_flags, | CAmount expected_value, char cache_flags, | ||||
char expected_flags) { | char expected_flags) { | ||||
SingleEntryCacheTest test(base_value, cache_value, cache_flags); | SingleEntryCacheTest test(base_value, cache_value, cache_flags); | ||||
test.cache.SpendCoin(OUTPOINT); | test.cache.SpendCoin(OUTPOINT); | ||||
test.cache.SelfTest(); | test.cache.SelfTest(); | ||||
CAmount result_value; | CAmount result_value; | ||||
▲ Show 20 Lines • Show All 207 Lines • Show Last 20 Lines |