mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-01-28 12:07:32 -03:00
5eaaa83ac1
There are only a few uses of `insecure_random` outside the tests. This PR replaces uses of insecure_random (and its accompanying global state) in the core code with an FastRandomContext that is automatically seeded on creation. This is meant to be used for inner loops. The FastRandomContext can be in the outer scope, or the class itself, then rand32() is used inside the loop. Useful e.g. for pushing addresses in CNode or the fee rounding, or randomization for coin selection. As a context is created per purpose, thus it gets rid of cross-thread unprotected shared usage of a single set of globals, this should also get rid of the potential race conditions. - I'd say TxMempool::check is not called enough to warrant using a special fast random context, this is switched to GetRand() (open for discussion...) - The use of `insecure_rand` in ConnectThroughProxy has been replaced by an atomic integer counter. The only goal here is to have a different credentials pair for each connection to go on a different Tor circuit, it does not need to be random nor unpredictable. - To avoid having a FastRandomContext on every CNode, the context is passed into PushAddress as appropriate. There remains an insecure_random for test usage in `test_random.h`.
418 lines
16 KiB
C++
418 lines
16 KiB
C++
// Copyright (c) 2014-2015 The Bitcoin Core developers
|
|
// Distributed under the MIT software license, see the accompanying
|
|
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
|
|
|
#include "coins.h"
|
|
#include "test_random.h"
|
|
#include "script/standard.h"
|
|
#include "uint256.h"
|
|
#include "utilstrencodings.h"
|
|
#include "test/test_bitcoin.h"
|
|
#include "main.h"
|
|
#include "consensus/validation.h"
|
|
|
|
#include <vector>
|
|
#include <map>
|
|
|
|
#include <boost/test/unit_test.hpp>
|
|
|
|
namespace
|
|
{
|
|
class CCoinsViewTest : public CCoinsView
|
|
{
|
|
uint256 hashBestBlock_;
|
|
std::map<uint256, CCoins> map_;
|
|
|
|
public:
|
|
bool GetCoins(const uint256& txid, CCoins& coins) const
|
|
{
|
|
std::map<uint256, CCoins>::const_iterator it = map_.find(txid);
|
|
if (it == map_.end()) {
|
|
return false;
|
|
}
|
|
coins = it->second;
|
|
if (coins.IsPruned() && insecure_rand() % 2 == 0) {
|
|
// Randomly return false in case of an empty entry.
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool HaveCoins(const uint256& txid) const
|
|
{
|
|
CCoins coins;
|
|
return GetCoins(txid, coins);
|
|
}
|
|
|
|
uint256 GetBestBlock() const { return hashBestBlock_; }
|
|
|
|
bool BatchWrite(CCoinsMap& mapCoins, const uint256& hashBlock)
|
|
{
|
|
for (CCoinsMap::iterator it = mapCoins.begin(); it != mapCoins.end(); ) {
|
|
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++);
|
|
}
|
|
if (!hashBlock.IsNull())
|
|
hashBestBlock_ = hashBlock;
|
|
return true;
|
|
}
|
|
};
|
|
|
|
class CCoinsViewCacheTest : public CCoinsViewCache
|
|
{
|
|
public:
|
|
CCoinsViewCacheTest(CCoinsView* base) : CCoinsViewCache(base) {}
|
|
|
|
void SelfTest() const
|
|
{
|
|
// Manually recompute the dynamic usage of the whole data, and compare it.
|
|
size_t ret = memusage::DynamicUsage(cacheCoins);
|
|
for (CCoinsMap::iterator it = cacheCoins.begin(); it != cacheCoins.end(); it++) {
|
|
ret += it->second.coins.DynamicMemoryUsage();
|
|
}
|
|
BOOST_CHECK_EQUAL(DynamicMemoryUsage(), ret);
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
BOOST_FIXTURE_TEST_SUITE(coins_tests, BasicTestingSetup)
|
|
|
|
static const unsigned int NUM_SIMULATION_ITERATIONS = 40000;
|
|
|
|
// This is a large randomized insert/remove simulation test on a variable-size
|
|
// stack of caches on top of CCoinsViewTest.
|
|
//
|
|
// It will randomly create/update/delete CCoins entries to a tip of caches, with
|
|
// txids picked from a limited list of random 256-bit hashes. Occasionally, a
|
|
// new tip is added to the stack of caches, or the tip is flushed and removed.
|
|
//
|
|
// During the process, booleans are kept to make sure that the randomized
|
|
// operation hits all branches.
|
|
BOOST_AUTO_TEST_CASE(coins_cache_simulation_test)
|
|
{
|
|
// Various coverage trackers.
|
|
bool removed_all_caches = false;
|
|
bool reached_4_caches = false;
|
|
bool added_an_entry = false;
|
|
bool removed_an_entry = false;
|
|
bool updated_an_entry = false;
|
|
bool found_an_entry = false;
|
|
bool missed_an_entry = 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.
|
|
|
|
// Use a limited set of random transaction ids, so we do test overwriting entries.
|
|
std::vector<uint256> txids;
|
|
txids.resize(NUM_SIMULATION_ITERATIONS / 8);
|
|
for (unsigned int i = 0; i < txids.size(); i++) {
|
|
txids[i] = GetRandHash();
|
|
}
|
|
|
|
for (unsigned int i = 0; i < NUM_SIMULATION_ITERATIONS; i++) {
|
|
// Do a random modification.
|
|
{
|
|
uint256 txid = txids[insecure_rand() % txids.size()]; // txid we're going to modify in this iteration.
|
|
CCoins& coins = result[txid];
|
|
CCoinsModifier entry = stack.back()->ModifyCoins(txid);
|
|
BOOST_CHECK(coins == *entry);
|
|
if (insecure_rand() % 5 == 0 || coins.IsPruned()) {
|
|
if (coins.IsPruned()) {
|
|
added_an_entry = true;
|
|
} else {
|
|
updated_an_entry = true;
|
|
}
|
|
coins.nVersion = insecure_rand();
|
|
coins.vout.resize(1);
|
|
coins.vout[0].nValue = insecure_rand();
|
|
*entry = coins;
|
|
} else {
|
|
coins.Clear();
|
|
entry->Clear();
|
|
removed_an_entry = true;
|
|
}
|
|
}
|
|
|
|
// 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);
|
|
found_an_entry = true;
|
|
} else {
|
|
BOOST_CHECK(it->second.IsPruned());
|
|
missed_an_entry = true;
|
|
}
|
|
}
|
|
BOOST_FOREACH(const CCoinsViewCacheTest *test, stack) {
|
|
test->SelfTest();
|
|
}
|
|
}
|
|
|
|
if (insecure_rand() % 100 == 0) {
|
|
// Every 100 iterations, flush an intermediate cache
|
|
if (stack.size() > 1 && insecure_rand() % 2 == 0) {
|
|
unsigned int flushIndex = insecure_rand() % (stack.size() - 1);
|
|
stack[flushIndex]->Flush();
|
|
}
|
|
}
|
|
if (insecure_rand() % 100 == 0) {
|
|
// Every 100 iterations, change the cache stack.
|
|
if (stack.size() > 0 && insecure_rand() % 2 == 0) {
|
|
//Remove the top cache
|
|
stack.back()->Flush();
|
|
delete stack.back();
|
|
stack.pop_back();
|
|
}
|
|
if (stack.size() == 0 || (stack.size() < 4 && insecure_rand() % 2)) {
|
|
//Add a new cache
|
|
CCoinsView* tip = &base;
|
|
if (stack.size() > 0) {
|
|
tip = stack.back();
|
|
} else {
|
|
removed_all_caches = true;
|
|
}
|
|
stack.push_back(new CCoinsViewCacheTest(tip));
|
|
if (stack.size() == 4) {
|
|
reached_4_caches = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Clean up the stack.
|
|
while (stack.size() > 0) {
|
|
delete stack.back();
|
|
stack.pop_back();
|
|
}
|
|
|
|
// Verify coverage.
|
|
BOOST_CHECK(removed_all_caches);
|
|
BOOST_CHECK(reached_4_caches);
|
|
BOOST_CHECK(added_an_entry);
|
|
BOOST_CHECK(removed_an_entry);
|
|
BOOST_CHECK(updated_an_entry);
|
|
BOOST_CHECK(found_an_entry);
|
|
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);
|
|
|
|
UpdateCoins(tx, *(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, flush an intermediate cache
|
|
if (stack.size() > 1 && insecure_rand() % 2 == 0) {
|
|
unsigned int flushIndex = insecure_rand() % (stack.size() - 1);
|
|
stack[flushIndex]->Flush();
|
|
}
|
|
}
|
|
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_CASE(ccoins_serialization)
|
|
{
|
|
// Good example
|
|
CDataStream ss1(ParseHex("0104835800816115944e077fe7c803cfa57f29b36bf87c1d358bb85e"), SER_DISK, CLIENT_VERSION);
|
|
CCoins cc1;
|
|
ss1 >> cc1;
|
|
BOOST_CHECK_EQUAL(cc1.nVersion, 1);
|
|
BOOST_CHECK_EQUAL(cc1.fCoinBase, false);
|
|
BOOST_CHECK_EQUAL(cc1.nHeight, 203998);
|
|
BOOST_CHECK_EQUAL(cc1.vout.size(), 2);
|
|
BOOST_CHECK_EQUAL(cc1.IsAvailable(0), false);
|
|
BOOST_CHECK_EQUAL(cc1.IsAvailable(1), true);
|
|
BOOST_CHECK_EQUAL(cc1.vout[1].nValue, 60000000000ULL);
|
|
BOOST_CHECK_EQUAL(HexStr(cc1.vout[1].scriptPubKey), HexStr(GetScriptForDestination(CKeyID(uint160(ParseHex("816115944e077fe7c803cfa57f29b36bf87c1d35"))))));
|
|
|
|
// Good example
|
|
CDataStream ss2(ParseHex("0109044086ef97d5790061b01caab50f1b8e9c50a5057eb43c2d9563a4eebbd123008c988f1a4a4de2161e0f50aac7f17e7f9555caa486af3b"), SER_DISK, CLIENT_VERSION);
|
|
CCoins cc2;
|
|
ss2 >> cc2;
|
|
BOOST_CHECK_EQUAL(cc2.nVersion, 1);
|
|
BOOST_CHECK_EQUAL(cc2.fCoinBase, true);
|
|
BOOST_CHECK_EQUAL(cc2.nHeight, 120891);
|
|
BOOST_CHECK_EQUAL(cc2.vout.size(), 17);
|
|
for (int i = 0; i < 17; i++) {
|
|
BOOST_CHECK_EQUAL(cc2.IsAvailable(i), i == 4 || i == 16);
|
|
}
|
|
BOOST_CHECK_EQUAL(cc2.vout[4].nValue, 234925952);
|
|
BOOST_CHECK_EQUAL(HexStr(cc2.vout[4].scriptPubKey), HexStr(GetScriptForDestination(CKeyID(uint160(ParseHex("61b01caab50f1b8e9c50a5057eb43c2d9563a4ee"))))));
|
|
BOOST_CHECK_EQUAL(cc2.vout[16].nValue, 110397);
|
|
BOOST_CHECK_EQUAL(HexStr(cc2.vout[16].scriptPubKey), HexStr(GetScriptForDestination(CKeyID(uint160(ParseHex("8c988f1a4a4de2161e0f50aac7f17e7f9555caa4"))))));
|
|
|
|
// Smallest possible example
|
|
CDataStream ssx(SER_DISK, CLIENT_VERSION);
|
|
BOOST_CHECK_EQUAL(HexStr(ssx.begin(), ssx.end()), "");
|
|
|
|
CDataStream ss3(ParseHex("0002000600"), SER_DISK, CLIENT_VERSION);
|
|
CCoins cc3;
|
|
ss3 >> cc3;
|
|
BOOST_CHECK_EQUAL(cc3.nVersion, 0);
|
|
BOOST_CHECK_EQUAL(cc3.fCoinBase, false);
|
|
BOOST_CHECK_EQUAL(cc3.nHeight, 0);
|
|
BOOST_CHECK_EQUAL(cc3.vout.size(), 1);
|
|
BOOST_CHECK_EQUAL(cc3.IsAvailable(0), true);
|
|
BOOST_CHECK_EQUAL(cc3.vout[0].nValue, 0);
|
|
BOOST_CHECK_EQUAL(cc3.vout[0].scriptPubKey.size(), 0);
|
|
|
|
// scriptPubKey that ends beyond the end of the stream
|
|
CDataStream ss4(ParseHex("0002000800"), SER_DISK, CLIENT_VERSION);
|
|
try {
|
|
CCoins cc4;
|
|
ss4 >> cc4;
|
|
BOOST_CHECK_MESSAGE(false, "We should have thrown");
|
|
} catch (const std::ios_base::failure& e) {
|
|
}
|
|
|
|
// Very large scriptPubKey (3*10^9 bytes) past the end of the stream
|
|
CDataStream tmp(SER_DISK, CLIENT_VERSION);
|
|
uint64_t x = 3000000000ULL;
|
|
tmp << VARINT(x);
|
|
BOOST_CHECK_EQUAL(HexStr(tmp.begin(), tmp.end()), "8a95c0bb00");
|
|
CDataStream ss5(ParseHex("0002008a95c0bb0000"), SER_DISK, CLIENT_VERSION);
|
|
try {
|
|
CCoins cc5;
|
|
ss5 >> cc5;
|
|
BOOST_CHECK_MESSAGE(false, "We should have thrown");
|
|
} catch (const std::ios_base::failure& e) {
|
|
}
|
|
}
|
|
|
|
BOOST_AUTO_TEST_SUITE_END()
|