Merge #10195: Switch chainstate db and cache to per-txout model

589827975 scripted-diff: various renames for per-utxo consistency (Pieter Wuille)
a5e02bc7f Increase travis unit test timeout (Pieter Wuille)
73de2c1ff Rename CCoinsCacheEntry::coins to coin (Pieter Wuille)
119e552f7 Merge CCoinsViewCache's GetOutputFor and AccessCoin (Pieter Wuille)
580b02309 [MOVEONLY] Move old CCoins class to txdb.cpp (Pieter Wuille)
8b25d2c0c Upgrade from per-tx database to per-txout (Pieter Wuille)
b2af357f3 Reduce reserved memory space for flushing (Pieter Wuille)
41aa5b79a Pack Coin more tightly (Pieter Wuille)
97072d668 Remove unused CCoins methods (Pieter Wuille)
ce23efaa5 Extend coins_tests (Pieter Wuille)
508307968 Switch CCoinsView and chainstate db from per-txid to per-txout (Pieter Wuille)
4ec0d9e79 Refactor GetUTXOStats in preparation for per-COutPoint iteration (Pieter Wuille)
13870b56f Replace CCoins-based CTxMemPool::pruneSpent with isSpent (Pieter Wuille)
05293f3cb Remove ModifyCoins/ModifyNewCoins (Pieter Wuille)
961e48397 Switch tests from ModifyCoins to AddCoin/SpendCoin (Pieter Wuille)
8b3868c1b Switch CScriptCheck to use Coin instead of CCoins (Pieter Wuille)
c87b957a3 Only pass things committed to by tx's witness hash to CScriptCheck (Matt Corallo)
f68cdfe92 Switch from per-tx to per-txout CCoinsViewCache methods in some places (Pieter Wuille)
000391132 Introduce new per-txout CCoinsViewCache functions (Pieter Wuille)
bd83111a0 Optimization: Coin&& to ApplyTxInUndo (Pieter Wuille)
cb2c7fdac Replace CTxInUndo with Coin (Pieter Wuille)
422634e2f Introduce Coin, a single unspent output (Pieter Wuille)
7d991b55d Store/allow tx metadata in all undo records (Pieter Wuille)
c3aa0c119 Report on-disk size in gettxoutsetinfo (Pieter Wuille)
d34242430 Remove/ignore tx version in utxo and undo (Pieter Wuille)
7e0032290 Add specialization of SipHash for 256 + 32 bit data (Pieter Wuille)
e484652fc Introduce CHashVerifier to hash read data (Pieter Wuille)
f54580e7e error() in disconnect for disk corruption, not inconsistency (Pieter Wuille)
e66dbde6d Add SizeEstimate to CDBBatch (Pieter Wuille)

Tree-SHA512: ce1fb1e40c77d38915cd02189fab7a8b125c7f44d425c85579d872c3bede3a437760997907c99d7b3017ced1c2de54b2ac7223d99d83a6658fe5ef61edef1de3
This commit is contained in:
Pieter Wuille 2017-06-01 15:47:58 -07:00
commit 1088b02f0c
No known key found for this signature in database
GPG Key ID: A636E97631F767E0
31 changed files with 1118 additions and 1064 deletions

View File

@ -69,7 +69,7 @@ script:
- ./configure --cache-file=../config.cache $BITCOIN_CONFIG_ALL $BITCOIN_CONFIG || ( cat config.log && false) - ./configure --cache-file=../config.cache $BITCOIN_CONFIG_ALL $BITCOIN_CONFIG || ( cat config.log && false)
- make $MAKEJOBS $GOAL || ( echo "Build failure. Verbose build follows." && make $GOAL V=1 ; false ) - make $MAKEJOBS $GOAL || ( echo "Build failure. Verbose build follows." && make $GOAL V=1 ; false )
- export LD_LIBRARY_PATH=$TRAVIS_BUILD_DIR/depends/$HOST/lib - export LD_LIBRARY_PATH=$TRAVIS_BUILD_DIR/depends/$HOST/lib
- if [ "$RUN_TESTS" = "true" ]; then make $MAKEJOBS check VERBOSE=1; fi - if [ "$RUN_TESTS" = "true" ]; then travis_wait 30 make $MAKEJOBS check VERBOSE=1; fi
- if [ "$TRAVIS_EVENT_TYPE" = "cron" ]; then extended="--extended --exclude pruning"; fi - if [ "$TRAVIS_EVENT_TYPE" = "cron" ]; then extended="--extended --exclude pruning"; fi
- if [ "$RUN_TESTS" = "true" ]; then test/functional/test_runner.py --coverage --quiet ${extended}; fi - if [ "$RUN_TESTS" = "true" ]; then test/functional/test_runner.py --coverage --quiet ${extended}; fi
after_script: after_script:

View File

@ -36,6 +36,15 @@ Notable changes
Low-level RPC changes Low-level RPC changes
--------------------- ---------------------
- The new database model no longer stores information about transaction
versions of unspent outputs. This means that:
- The `gettxout` RPC no longer has a `version` field in the response.
- The `gettxoutsetinfo` RPC reports `hash_serialized_2` instead of `hash_serialized`,
which does not commit to the transaction versions of unspent outputs, but does
commit to the height and coinbase information.
- The `getutxos` REST path no longer reports the `txvers` field in JSON format,
and always reports 0 for transaction versions in the binary format
- Error codes have been updated to be more accurate for the following error cases: - Error codes have been updated to be more accurate for the following error cases:
- `getblock` now returns RPC_MISC_ERROR if the block can't be found on disk (for - `getblock` now returns RPC_MISC_ERROR if the block can't be found on disk (for
example if the block has been pruned). Previously returned RPC_INTERNAL_ERROR. example if the block has been pruned). Previously returned RPC_INTERNAL_ERROR.

View File

@ -35,14 +35,14 @@ SetupDummyInputs(CBasicKeyStore& keystoreRet, CCoinsViewCache& coinsRet)
dummyTransactions[0].vout[0].scriptPubKey << ToByteVector(key[0].GetPubKey()) << OP_CHECKSIG; dummyTransactions[0].vout[0].scriptPubKey << ToByteVector(key[0].GetPubKey()) << OP_CHECKSIG;
dummyTransactions[0].vout[1].nValue = 50 * CENT; dummyTransactions[0].vout[1].nValue = 50 * CENT;
dummyTransactions[0].vout[1].scriptPubKey << ToByteVector(key[1].GetPubKey()) << OP_CHECKSIG; dummyTransactions[0].vout[1].scriptPubKey << ToByteVector(key[1].GetPubKey()) << OP_CHECKSIG;
coinsRet.ModifyCoins(dummyTransactions[0].GetHash())->FromTx(dummyTransactions[0], 0); AddCoins(coinsRet, dummyTransactions[0], 0);
dummyTransactions[1].vout.resize(2); dummyTransactions[1].vout.resize(2);
dummyTransactions[1].vout[0].nValue = 21 * CENT; dummyTransactions[1].vout[0].nValue = 21 * CENT;
dummyTransactions[1].vout[0].scriptPubKey = GetScriptForDestination(key[2].GetPubKey().GetID()); dummyTransactions[1].vout[0].scriptPubKey = GetScriptForDestination(key[2].GetPubKey().GetID());
dummyTransactions[1].vout[1].nValue = 22 * CENT; dummyTransactions[1].vout[1].nValue = 22 * CENT;
dummyTransactions[1].vout[1].scriptPubKey = GetScriptForDestination(key[3].GetPubKey().GetID()); dummyTransactions[1].vout[1].scriptPubKey = GetScriptForDestination(key[3].GetPubKey().GetID());
coinsRet.ModifyCoins(dummyTransactions[1].GetHash())->FromTx(dummyTransactions[1], 0); AddCoins(coinsRet, dummyTransactions[1], 0);
return dummyTransactions; return dummyTransactions;
} }

View File

@ -556,24 +556,26 @@ static void MutateTxSign(CMutableTransaction& tx, const std::string& flagStr)
if (nOut < 0) if (nOut < 0)
throw std::runtime_error("vout must be positive"); throw std::runtime_error("vout must be positive");
COutPoint out(txid, nOut);
std::vector<unsigned char> pkData(ParseHexUV(prevOut["scriptPubKey"], "scriptPubKey")); std::vector<unsigned char> pkData(ParseHexUV(prevOut["scriptPubKey"], "scriptPubKey"));
CScript scriptPubKey(pkData.begin(), pkData.end()); CScript scriptPubKey(pkData.begin(), pkData.end());
{ {
CCoinsModifier coins = view.ModifyCoins(txid); const Coin& coin = view.AccessCoin(out);
if (coins->IsAvailable(nOut) && coins->vout[nOut].scriptPubKey != scriptPubKey) { if (!coin.IsSpent() && coin.out.scriptPubKey != scriptPubKey) {
std::string err("Previous output scriptPubKey mismatch:\n"); std::string err("Previous output scriptPubKey mismatch:\n");
err = err + ScriptToAsmStr(coins->vout[nOut].scriptPubKey) + "\nvs:\n"+ err = err + ScriptToAsmStr(coin.out.scriptPubKey) + "\nvs:\n"+
ScriptToAsmStr(scriptPubKey); ScriptToAsmStr(scriptPubKey);
throw std::runtime_error(err); throw std::runtime_error(err);
} }
if ((unsigned int)nOut >= coins->vout.size()) Coin newcoin;
coins->vout.resize(nOut+1); newcoin.out.scriptPubKey = scriptPubKey;
coins->vout[nOut].scriptPubKey = scriptPubKey; newcoin.out.nValue = 0;
coins->vout[nOut].nValue = 0;
if (prevOut.exists("amount")) { if (prevOut.exists("amount")) {
coins->vout[nOut].nValue = AmountFromValue(prevOut["amount"]); newcoin.out.nValue = AmountFromValue(prevOut["amount"]);
} }
newcoin.nHeight = 1;
view.AddCoin(out, std::move(newcoin), true);
} }
// if redeemScript given and private keys given, // if redeemScript given and private keys given,
@ -595,13 +597,13 @@ static void MutateTxSign(CMutableTransaction& tx, const std::string& flagStr)
// Sign what we can: // Sign what we can:
for (unsigned int i = 0; i < mergedTx.vin.size(); i++) { for (unsigned int i = 0; i < mergedTx.vin.size(); i++) {
CTxIn& txin = mergedTx.vin[i]; CTxIn& txin = mergedTx.vin[i];
const CCoins* coins = view.AccessCoins(txin.prevout.hash); const Coin& coin = view.AccessCoin(txin.prevout);
if (!coins || !coins->IsAvailable(txin.prevout.n)) { if (coin.IsSpent()) {
fComplete = false; fComplete = false;
continue; continue;
} }
const CScript& prevPubKey = coins->vout[txin.prevout.n].scriptPubKey; const CScript& prevPubKey = coin.out.scriptPubKey;
const CAmount& amount = coins->vout[txin.prevout.n].nValue; const CAmount& amount = coin.out.nValue;
SignatureData sigdata; SignatureData sigdata;
// Only sign SIGHASH_SINGLE if there's a corresponding output: // Only sign SIGHASH_SINGLE if there's a corresponding output:

View File

@ -4,174 +4,126 @@
#include "coins.h" #include "coins.h"
#include "consensus/consensus.h"
#include "memusage.h" #include "memusage.h"
#include "random.h" #include "random.h"
#include <assert.h> #include <assert.h>
/** bool CCoinsView::GetCoin(const COutPoint &outpoint, Coin &coin) const { return false; }
* calculate number of bytes for the bitmask, and its number of non-zero bytes bool CCoinsView::HaveCoin(const COutPoint &outpoint) const { return false; }
* each bit in the bitmask represents the availability of one output, but the
* availabilities of the first two outputs are encoded separately
*/
void CCoins::CalcMaskSize(unsigned int &nBytes, unsigned int &nNonzeroBytes) const {
unsigned int nLastUsedByte = 0;
for (unsigned int b = 0; 2+b*8 < vout.size(); b++) {
bool fZero = true;
for (unsigned int i = 0; i < 8 && 2+b*8+i < vout.size(); i++) {
if (!vout[2+b*8+i].IsNull()) {
fZero = false;
continue;
}
}
if (!fZero) {
nLastUsedByte = b + 1;
nNonzeroBytes++;
}
}
nBytes += nLastUsedByte;
}
bool CCoins::Spend(uint32_t nPos)
{
if (nPos >= vout.size() || vout[nPos].IsNull())
return false;
vout[nPos].SetNull();
Cleanup();
return true;
}
bool CCoinsView::GetCoins(const uint256 &txid, CCoins &coins) const { return false; }
bool CCoinsView::HaveCoins(const uint256 &txid) const { return false; }
uint256 CCoinsView::GetBestBlock() const { return uint256(); } uint256 CCoinsView::GetBestBlock() const { return uint256(); }
bool CCoinsView::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock) { return false; } bool CCoinsView::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock) { return false; }
CCoinsViewCursor *CCoinsView::Cursor() const { return 0; } CCoinsViewCursor *CCoinsView::Cursor() const { return 0; }
CCoinsViewBacked::CCoinsViewBacked(CCoinsView *viewIn) : base(viewIn) { } CCoinsViewBacked::CCoinsViewBacked(CCoinsView *viewIn) : base(viewIn) { }
bool CCoinsViewBacked::GetCoins(const uint256 &txid, CCoins &coins) const { return base->GetCoins(txid, coins); } bool CCoinsViewBacked::GetCoin(const COutPoint &outpoint, Coin &coin) const { return base->GetCoin(outpoint, coin); }
bool CCoinsViewBacked::HaveCoins(const uint256 &txid) const { return base->HaveCoins(txid); } bool CCoinsViewBacked::HaveCoin(const COutPoint &outpoint) const { return base->HaveCoin(outpoint); }
uint256 CCoinsViewBacked::GetBestBlock() const { return base->GetBestBlock(); } uint256 CCoinsViewBacked::GetBestBlock() const { return base->GetBestBlock(); }
void CCoinsViewBacked::SetBackend(CCoinsView &viewIn) { base = &viewIn; } void CCoinsViewBacked::SetBackend(CCoinsView &viewIn) { base = &viewIn; }
bool CCoinsViewBacked::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock) { return base->BatchWrite(mapCoins, hashBlock); } bool CCoinsViewBacked::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock) { return base->BatchWrite(mapCoins, hashBlock); }
CCoinsViewCursor *CCoinsViewBacked::Cursor() const { return base->Cursor(); } CCoinsViewCursor *CCoinsViewBacked::Cursor() const { return base->Cursor(); }
size_t CCoinsViewBacked::EstimateSize() const { return base->EstimateSize(); }
SaltedTxidHasher::SaltedTxidHasher() : k0(GetRand(std::numeric_limits<uint64_t>::max())), k1(GetRand(std::numeric_limits<uint64_t>::max())) {} SaltedOutpointHasher::SaltedOutpointHasher() : k0(GetRand(std::numeric_limits<uint64_t>::max())), k1(GetRand(std::numeric_limits<uint64_t>::max())) {}
CCoinsViewCache::CCoinsViewCache(CCoinsView *baseIn) : CCoinsViewBacked(baseIn), hasModifier(false), cachedCoinsUsage(0) { } CCoinsViewCache::CCoinsViewCache(CCoinsView *baseIn) : CCoinsViewBacked(baseIn), cachedCoinsUsage(0) {}
CCoinsViewCache::~CCoinsViewCache()
{
assert(!hasModifier);
}
size_t CCoinsViewCache::DynamicMemoryUsage() const { size_t CCoinsViewCache::DynamicMemoryUsage() const {
return memusage::DynamicUsage(cacheCoins) + cachedCoinsUsage; return memusage::DynamicUsage(cacheCoins) + cachedCoinsUsage;
} }
CCoinsMap::const_iterator CCoinsViewCache::FetchCoins(const uint256 &txid) const { CCoinsMap::iterator CCoinsViewCache::FetchCoin(const COutPoint &outpoint) const {
CCoinsMap::iterator it = cacheCoins.find(txid); CCoinsMap::iterator it = cacheCoins.find(outpoint);
if (it != cacheCoins.end()) if (it != cacheCoins.end())
return it; return it;
CCoins tmp; Coin tmp;
if (!base->GetCoins(txid, tmp)) if (!base->GetCoin(outpoint, tmp))
return cacheCoins.end(); return cacheCoins.end();
CCoinsMap::iterator ret = cacheCoins.insert(std::make_pair(txid, CCoinsCacheEntry())).first; CCoinsMap::iterator ret = cacheCoins.emplace(std::piecewise_construct, std::forward_as_tuple(outpoint), std::forward_as_tuple(std::move(tmp))).first;
tmp.swap(ret->second.coins); if (ret->second.coin.IsSpent()) {
if (ret->second.coins.IsPruned()) { // The parent only has an empty entry for this outpoint; we can consider our
// The parent only has an empty entry for this txid; we can consider our
// version as fresh. // version as fresh.
ret->second.flags = CCoinsCacheEntry::FRESH; ret->second.flags = CCoinsCacheEntry::FRESH;
} }
cachedCoinsUsage += ret->second.coins.DynamicMemoryUsage(); cachedCoinsUsage += ret->second.coin.DynamicMemoryUsage();
return ret; return ret;
} }
bool CCoinsViewCache::GetCoins(const uint256 &txid, CCoins &coins) const { bool CCoinsViewCache::GetCoin(const COutPoint &outpoint, Coin &coin) const {
CCoinsMap::const_iterator it = FetchCoins(txid); CCoinsMap::const_iterator it = FetchCoin(outpoint);
if (it != cacheCoins.end()) { if (it != cacheCoins.end()) {
coins = it->second.coins; coin = it->second.coin;
return true; return true;
} }
return false; return false;
} }
CCoinsModifier CCoinsViewCache::ModifyCoins(const uint256 &txid) { void CCoinsViewCache::AddCoin(const COutPoint &outpoint, Coin&& coin, bool possible_overwrite) {
assert(!hasModifier); assert(!coin.IsSpent());
std::pair<CCoinsMap::iterator, bool> ret = cacheCoins.insert(std::make_pair(txid, CCoinsCacheEntry())); if (coin.out.scriptPubKey.IsUnspendable()) return;
size_t cachedCoinUsage = 0; CCoinsMap::iterator it;
if (ret.second) { bool inserted;
if (!base->GetCoins(txid, ret.first->second.coins)) { std::tie(it, inserted) = cacheCoins.emplace(std::piecewise_construct, std::forward_as_tuple(outpoint), std::tuple<>());
// The parent view does not have this entry; mark it as fresh. bool fresh = false;
ret.first->second.coins.Clear(); if (!inserted) {
ret.first->second.flags = CCoinsCacheEntry::FRESH; cachedCoinsUsage -= it->second.coin.DynamicMemoryUsage();
} else if (ret.first->second.coins.IsPruned()) { }
// The parent view only has a pruned entry for this; mark it as fresh. if (!possible_overwrite) {
ret.first->second.flags = CCoinsCacheEntry::FRESH; if (!it->second.coin.IsSpent()) {
throw std::logic_error("Adding new coin that replaces non-pruned entry");
} }
fresh = !(it->second.flags & CCoinsCacheEntry::DIRTY);
}
it->second.coin = std::move(coin);
it->second.flags |= CCoinsCacheEntry::DIRTY | (fresh ? CCoinsCacheEntry::FRESH : 0);
cachedCoinsUsage += it->second.coin.DynamicMemoryUsage();
}
void AddCoins(CCoinsViewCache& cache, const CTransaction &tx, int nHeight) {
bool fCoinbase = tx.IsCoinBase();
const uint256& txid = tx.GetHash();
for (size_t i = 0; i < tx.vout.size(); ++i) {
// Pass fCoinbase as the possible_overwrite flag to AddCoin, in order to correctly
// deal with the pre-BIP30 occurrances of duplicate coinbase transactions.
cache.AddCoin(COutPoint(txid, i), Coin(tx.vout[i], nHeight, fCoinbase), fCoinbase);
}
}
void CCoinsViewCache::SpendCoin(const COutPoint &outpoint, Coin* moveout) {
CCoinsMap::iterator it = FetchCoin(outpoint);
if (it == cacheCoins.end()) return;
cachedCoinsUsage -= it->second.coin.DynamicMemoryUsage();
if (moveout) {
*moveout = std::move(it->second.coin);
}
if (it->second.flags & CCoinsCacheEntry::FRESH) {
cacheCoins.erase(it);
} else { } else {
cachedCoinUsage = ret.first->second.coins.DynamicMemoryUsage(); it->second.flags |= CCoinsCacheEntry::DIRTY;
it->second.coin.Clear();
} }
// Assume that whenever ModifyCoins is called, the entry will be modified.
ret.first->second.flags |= CCoinsCacheEntry::DIRTY;
return CCoinsModifier(*this, ret.first, cachedCoinUsage);
} }
/* ModifyNewCoins allows for faster coin modification when creating the new static const Coin coinEmpty;
* outputs from a transaction. It assumes that BIP 30 (no duplicate txids)
* applies and has already been tested for (or the test is not required due to
* BIP 34, height in coinbase). If we can assume BIP 30 then we know that any
* non-coinbase transaction we are adding to the UTXO must not already exist in
* the utxo unless it is fully spent. Thus we can check only if it exists DIRTY
* at the current level of the cache, in which case it is not safe to mark it
* FRESH (b/c then its spentness still needs to flushed). If it's not dirty and
* doesn't exist or is pruned in the current cache, we know it either doesn't
* exist or is pruned in parent caches, which is the definition of FRESH. The
* exception to this is the two historical violations of BIP 30 in the chain,
* both of which were coinbases. We do not mark these fresh so we we can ensure
* that they will still be properly overwritten when spent.
*/
CCoinsModifier CCoinsViewCache::ModifyNewCoins(const uint256 &txid, bool coinbase) {
assert(!hasModifier);
std::pair<CCoinsMap::iterator, bool> ret = cacheCoins.insert(std::make_pair(txid, CCoinsCacheEntry()));
if (!coinbase) {
// New coins must not already exist.
if (!ret.first->second.coins.IsPruned())
throw std::logic_error("ModifyNewCoins should not find pre-existing coins on a non-coinbase unless they are pruned!");
if (!(ret.first->second.flags & CCoinsCacheEntry::DIRTY)) { const Coin& CCoinsViewCache::AccessCoin(const COutPoint &outpoint) const {
// If the coin is known to be pruned (have no unspent outputs) in CCoinsMap::const_iterator it = FetchCoin(outpoint);
// the current view and the cache entry is not dirty, we know the
// coin also must be pruned in the parent view as well, so it is safe
// to mark this fresh.
ret.first->second.flags |= CCoinsCacheEntry::FRESH;
}
}
ret.first->second.coins.Clear();
ret.first->second.flags |= CCoinsCacheEntry::DIRTY;
return CCoinsModifier(*this, ret.first, 0);
}
const CCoins* CCoinsViewCache::AccessCoins(const uint256 &txid) const {
CCoinsMap::const_iterator it = FetchCoins(txid);
if (it == cacheCoins.end()) { if (it == cacheCoins.end()) {
return NULL; return coinEmpty;
} else { } else {
return &it->second.coins; return it->second.coin;
} }
} }
bool CCoinsViewCache::HaveCoins(const uint256 &txid) const { bool CCoinsViewCache::HaveCoin(const COutPoint &outpoint) const {
CCoinsMap::const_iterator it = FetchCoins(txid); CCoinsMap::const_iterator it = FetchCoin(outpoint);
// We're using vtx.empty() instead of IsPruned here for performance reasons, return (it != cacheCoins.end() && !it->second.coin.IsSpent());
// as we only care about the case where a transaction was replaced entirely
// in a reorganization (which wipes vout entirely, as opposed to spending
// which just cleans individual outputs).
return (it != cacheCoins.end() && !it->second.coins.vout.empty());
} }
bool CCoinsViewCache::HaveCoinsInCache(const uint256 &txid) const { bool CCoinsViewCache::HaveCoinInCache(const COutPoint &outpoint) const {
CCoinsMap::const_iterator it = cacheCoins.find(txid); CCoinsMap::const_iterator it = cacheCoins.find(outpoint);
return it != cacheCoins.end(); return it != cacheCoins.end();
} }
@ -186,19 +138,18 @@ void CCoinsViewCache::SetBestBlock(const uint256 &hashBlockIn) {
} }
bool CCoinsViewCache::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlockIn) { bool CCoinsViewCache::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlockIn) {
assert(!hasModifier);
for (CCoinsMap::iterator it = mapCoins.begin(); it != mapCoins.end();) { for (CCoinsMap::iterator it = mapCoins.begin(); it != mapCoins.end();) {
if (it->second.flags & CCoinsCacheEntry::DIRTY) { // Ignore non-dirty entries (optimization). if (it->second.flags & CCoinsCacheEntry::DIRTY) { // Ignore non-dirty entries (optimization).
CCoinsMap::iterator itUs = cacheCoins.find(it->first); CCoinsMap::iterator itUs = cacheCoins.find(it->first);
if (itUs == cacheCoins.end()) { if (itUs == cacheCoins.end()) {
// The parent cache does not have an entry, while the child does // The parent cache does not have an entry, while the child does
// We can ignore it if it's both FRESH and pruned in the child // We can ignore it if it's both FRESH and pruned in the child
if (!(it->second.flags & CCoinsCacheEntry::FRESH && it->second.coins.IsPruned())) { if (!(it->second.flags & CCoinsCacheEntry::FRESH && it->second.coin.IsSpent())) {
// Otherwise we will need to create it in the parent // Otherwise we will need to create it in the parent
// and move the data up and mark it as dirty // and move the data up and mark it as dirty
CCoinsCacheEntry& entry = cacheCoins[it->first]; CCoinsCacheEntry& entry = cacheCoins[it->first];
entry.coins.swap(it->second.coins); entry.coin = std::move(it->second.coin);
cachedCoinsUsage += entry.coins.DynamicMemoryUsage(); cachedCoinsUsage += entry.coin.DynamicMemoryUsage();
entry.flags = CCoinsCacheEntry::DIRTY; entry.flags = CCoinsCacheEntry::DIRTY;
// We can mark it FRESH in the parent if it was FRESH in the child // We can mark it FRESH in the parent if it was FRESH in the child
// Otherwise it might have just been flushed from the parent's cache // Otherwise it might have just been flushed from the parent's cache
@ -211,21 +162,21 @@ bool CCoinsViewCache::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlockIn
// parent cache entry has unspent outputs. If this ever happens, // parent cache entry has unspent outputs. If this ever happens,
// it means the FRESH flag was misapplied and there is a logic // it means the FRESH flag was misapplied and there is a logic
// error in the calling code. // error in the calling code.
if ((it->second.flags & CCoinsCacheEntry::FRESH) && !itUs->second.coins.IsPruned()) if ((it->second.flags & CCoinsCacheEntry::FRESH) && !itUs->second.coin.IsSpent())
throw std::logic_error("FRESH flag misapplied to cache entry for base transaction with spendable outputs"); throw std::logic_error("FRESH flag misapplied to cache entry for base transaction with spendable outputs");
// Found the entry in the parent cache // Found the entry in the parent cache
if ((itUs->second.flags & CCoinsCacheEntry::FRESH) && it->second.coins.IsPruned()) { if ((itUs->second.flags & CCoinsCacheEntry::FRESH) && it->second.coin.IsSpent()) {
// The grandparent does not have an entry, and the child is // The grandparent does not have an entry, and the child is
// modified and being pruned. This means we can just delete // modified and being pruned. This means we can just delete
// it from the parent. // it from the parent.
cachedCoinsUsage -= itUs->second.coins.DynamicMemoryUsage(); cachedCoinsUsage -= itUs->second.coin.DynamicMemoryUsage();
cacheCoins.erase(itUs); cacheCoins.erase(itUs);
} else { } else {
// A normal modification. // A normal modification.
cachedCoinsUsage -= itUs->second.coins.DynamicMemoryUsage(); cachedCoinsUsage -= itUs->second.coin.DynamicMemoryUsage();
itUs->second.coins.swap(it->second.coins); itUs->second.coin = std::move(it->second.coin);
cachedCoinsUsage += itUs->second.coins.DynamicMemoryUsage(); cachedCoinsUsage += itUs->second.coin.DynamicMemoryUsage();
itUs->second.flags |= CCoinsCacheEntry::DIRTY; itUs->second.flags |= CCoinsCacheEntry::DIRTY;
// NOTE: It is possible the child has a FRESH flag here in // NOTE: It is possible the child has a FRESH flag here in
// the event the entry we found in the parent is pruned. But // the event the entry we found in the parent is pruned. But
@ -249,11 +200,11 @@ bool CCoinsViewCache::Flush() {
return fOk; return fOk;
} }
void CCoinsViewCache::Uncache(const uint256& hash) void CCoinsViewCache::Uncache(const COutPoint& hash)
{ {
CCoinsMap::iterator it = cacheCoins.find(hash); CCoinsMap::iterator it = cacheCoins.find(hash);
if (it != cacheCoins.end() && it->second.flags == 0) { if (it != cacheCoins.end() && it->second.flags == 0) {
cachedCoinsUsage -= it->second.coins.DynamicMemoryUsage(); cachedCoinsUsage -= it->second.coin.DynamicMemoryUsage();
cacheCoins.erase(it); cacheCoins.erase(it);
} }
} }
@ -262,13 +213,6 @@ unsigned int CCoinsViewCache::GetCacheSize() const {
return cacheCoins.size(); return cacheCoins.size();
} }
const CTxOut &CCoinsViewCache::GetOutputFor(const CTxIn& input) const
{
const CCoins* coins = AccessCoins(input.prevout.hash);
assert(coins && coins->IsAvailable(input.prevout.n));
return coins->vout[input.prevout.n];
}
CAmount CCoinsViewCache::GetValueIn(const CTransaction& tx) const CAmount CCoinsViewCache::GetValueIn(const CTransaction& tx) const
{ {
if (tx.IsCoinBase()) if (tx.IsCoinBase())
@ -276,7 +220,7 @@ CAmount CCoinsViewCache::GetValueIn(const CTransaction& tx) const
CAmount nResult = 0; CAmount nResult = 0;
for (unsigned int i = 0; i < tx.vin.size(); i++) for (unsigned int i = 0; i < tx.vin.size(); i++)
nResult += GetOutputFor(tx.vin[i]).nValue; nResult += AccessCoin(tx.vin[i].prevout).out.nValue;
return nResult; return nResult;
} }
@ -285,9 +229,7 @@ bool CCoinsViewCache::HaveInputs(const CTransaction& tx) const
{ {
if (!tx.IsCoinBase()) { if (!tx.IsCoinBase()) {
for (unsigned int i = 0; i < tx.vin.size(); i++) { for (unsigned int i = 0; i < tx.vin.size(); i++) {
const COutPoint &prevout = tx.vin[i].prevout; if (!HaveCoin(tx.vin[i].prevout)) {
const CCoins* coins = AccessCoins(prevout.hash);
if (!coins || !coins->IsAvailable(prevout.n)) {
return false; return false;
} }
} }
@ -295,25 +237,15 @@ bool CCoinsViewCache::HaveInputs(const CTransaction& tx) const
return true; return true;
} }
CCoinsModifier::CCoinsModifier(CCoinsViewCache& cache_, CCoinsMap::iterator it_, size_t usage) : cache(cache_), it(it_), cachedCoinUsage(usage) { static const size_t MAX_OUTPUTS_PER_BLOCK = MAX_BLOCK_BASE_SIZE / ::GetSerializeSize(CTxOut(), SER_NETWORK, PROTOCOL_VERSION); // TODO: merge with similar definition in undo.h.
assert(!cache.hasModifier);
cache.hasModifier = true;
}
CCoinsModifier::~CCoinsModifier() const Coin& AccessByTxid(const CCoinsViewCache& view, const uint256& txid)
{ {
assert(cache.hasModifier); COutPoint iter(txid, 0);
cache.hasModifier = false; while (iter.n < MAX_OUTPUTS_PER_BLOCK) {
it->second.coins.Cleanup(); const Coin& alternate = view.AccessCoin(iter);
cache.cachedCoinsUsage -= cachedCoinUsage; // Subtract the old usage if (!alternate.IsSpent()) return alternate;
if ((it->second.flags & CCoinsCacheEntry::FRESH) && it->second.coins.IsPruned()) { ++iter.n;
cache.cacheCoins.erase(it);
} else {
// If the coin still exists after the modification, add the new usage
cache.cachedCoinsUsage += it->second.coins.DynamicMemoryUsage();
} }
} return coinEmpty;
CCoinsViewCursor::~CCoinsViewCursor()
{
} }

View File

@ -21,134 +21,36 @@
#include <unordered_map> #include <unordered_map>
/** /**
* Pruned version of CTransaction: only retains metadata and unspent transaction outputs * A UTXO entry.
* *
* Serialized format: * Serialized format:
* - VARINT(nVersion) * - VARINT((coinbase ? 1 : 0) | (height << 1))
* - VARINT(nCode) * - the non-spent CTxOut (via CTxOutCompressor)
* - unspentness bitvector, for vout[2] and further; least significant byte first
* - the non-spent CTxOuts (via CTxOutCompressor)
* - VARINT(nHeight)
*
* The nCode value consists of:
* - bit 0: IsCoinBase()
* - bit 1: vout[0] is not spent
* - bit 2: vout[1] is not spent
* - The higher bits encode N, the number of non-zero bytes in the following bitvector.
* - In case both bit 1 and bit 2 are unset, they encode N-1, as there must be at
* least one non-spent output).
*
* Example: 0104835800816115944e077fe7c803cfa57f29b36bf87c1d358bb85e
* <><><--------------------------------------------><---->
* | \ | /
* version code vout[1] height
*
* - version = 1
* - code = 4 (vout[1] is not spent, and 0 non-zero bytes of bitvector follow)
* - unspentness bitvector: as 0 non-zero bytes follow, it has length 0
* - vout[1]: 835800816115944e077fe7c803cfa57f29b36bf87c1d35
* * 8358: compact amount representation for 60000000000 (600 BTC)
* * 00: special txout type pay-to-pubkey-hash
* * 816115944e077fe7c803cfa57f29b36bf87c1d35: address uint160
* - height = 203998
*
*
* Example: 0109044086ef97d5790061b01caab50f1b8e9c50a5057eb43c2d9563a4eebbd123008c988f1a4a4de2161e0f50aac7f17e7f9555caa486af3b
* <><><--><--------------------------------------------------><----------------------------------------------><---->
* / \ \ | | /
* version code unspentness vout[4] vout[16] height
*
* - version = 1
* - code = 9 (coinbase, neither vout[0] or vout[1] are unspent,
* 2 (1, +1 because both bit 1 and bit 2 are unset) non-zero bitvector bytes follow)
* - unspentness bitvector: bits 2 (0x04) and 14 (0x4000) are set, so vout[2+2] and vout[14+2] are unspent
* - vout[4]: 86ef97d5790061b01caab50f1b8e9c50a5057eb43c2d9563a4ee
* * 86ef97d579: compact amount representation for 234925952 (2.35 BTC)
* * 00: special txout type pay-to-pubkey-hash
* * 61b01caab50f1b8e9c50a5057eb43c2d9563a4ee: address uint160
* - vout[16]: bbd123008c988f1a4a4de2161e0f50aac7f17e7f9555caa4
* * bbd123: compact amount representation for 110397 (0.001 BTC)
* * 00: special txout type pay-to-pubkey-hash
* * 8c988f1a4a4de2161e0f50aac7f17e7f9555caa4: address uint160
* - height = 120891
*/ */
class CCoins class Coin
{ {
public: public:
//! whether transaction is a coinbase //! unspent transaction output
bool fCoinBase; CTxOut out;
//! unspent transaction outputs; spent outputs are .IsNull(); spent outputs at the end of the array are dropped //! whether containing transaction was a coinbase
std::vector<CTxOut> vout; unsigned int fCoinBase : 1;
//! at which height this transaction was included in the active block chain //! at which height this containing transaction was included in the active block chain
int nHeight; uint32_t nHeight : 31;
//! version of the CTransaction; accesses to this value should probably check for nHeight as well, //! construct a Coin from a CTxOut and height/coinbase information.
//! as new tx version will probably only be introduced at certain heights Coin(CTxOut&& outIn, int nHeightIn, bool fCoinBaseIn) : out(std::move(outIn)), fCoinBase(fCoinBaseIn), nHeight(nHeightIn) {}
int nVersion; Coin(const CTxOut& outIn, int nHeightIn, bool fCoinBaseIn) : out(outIn), fCoinBase(fCoinBaseIn),nHeight(nHeightIn) {}
void FromTx(const CTransaction &tx, int nHeightIn) {
fCoinBase = tx.IsCoinBase();
vout = tx.vout;
nHeight = nHeightIn;
nVersion = tx.nVersion;
ClearUnspendable();
}
//! construct a CCoins from a CTransaction, at a given height
CCoins(const CTransaction &tx, int nHeightIn) {
FromTx(tx, nHeightIn);
}
void Clear() { void Clear() {
out.SetNull();
fCoinBase = false; fCoinBase = false;
std::vector<CTxOut>().swap(vout);
nHeight = 0; nHeight = 0;
nVersion = 0;
} }
//! empty constructor //! empty constructor
CCoins() : fCoinBase(false), vout(0), nHeight(0), nVersion(0) { } Coin() : fCoinBase(false), nHeight(0) { }
//!remove spent outputs at the end of vout
void Cleanup() {
while (vout.size() > 0 && vout.back().IsNull())
vout.pop_back();
if (vout.empty())
std::vector<CTxOut>().swap(vout);
}
void ClearUnspendable() {
BOOST_FOREACH(CTxOut &txout, vout) {
if (txout.scriptPubKey.IsUnspendable())
txout.SetNull();
}
Cleanup();
}
void swap(CCoins &to) {
std::swap(to.fCoinBase, fCoinBase);
to.vout.swap(vout);
std::swap(to.nHeight, nHeight);
std::swap(to.nVersion, nVersion);
}
//! equality test
friend bool operator==(const CCoins &a, const CCoins &b) {
// Empty CCoins objects are always equal.
if (a.IsPruned() && b.IsPruned())
return true;
return a.fCoinBase == b.fCoinBase &&
a.nHeight == b.nHeight &&
a.nVersion == b.nVersion &&
a.vout == b.vout;
}
friend bool operator!=(const CCoins &a, const CCoins &b) {
return !(a == b);
}
void CalcMaskSize(unsigned int &nBytes, unsigned int &nNonzeroBytes) const;
bool IsCoinBase() const { bool IsCoinBase() const {
return fCoinBase; return fCoinBase;
@ -156,115 +58,52 @@ public:
template<typename Stream> template<typename Stream>
void Serialize(Stream &s) const { void Serialize(Stream &s) const {
unsigned int nMaskSize = 0, nMaskCode = 0; assert(!IsSpent());
CalcMaskSize(nMaskSize, nMaskCode); uint32_t code = nHeight * 2 + fCoinBase;
bool fFirst = vout.size() > 0 && !vout[0].IsNull(); ::Serialize(s, VARINT(code));
bool fSecond = vout.size() > 1 && !vout[1].IsNull(); ::Serialize(s, CTxOutCompressor(REF(out)));
assert(fFirst || fSecond || nMaskCode);
unsigned int nCode = 8*(nMaskCode - (fFirst || fSecond ? 0 : 1)) + (fCoinBase ? 1 : 0) + (fFirst ? 2 : 0) + (fSecond ? 4 : 0);
// version
::Serialize(s, VARINT(this->nVersion));
// header code
::Serialize(s, VARINT(nCode));
// spentness bitmask
for (unsigned int b = 0; b<nMaskSize; b++) {
unsigned char chAvail = 0;
for (unsigned int i = 0; i < 8 && 2+b*8+i < vout.size(); i++)
if (!vout[2+b*8+i].IsNull())
chAvail |= (1 << i);
::Serialize(s, chAvail);
}
// txouts themself
for (unsigned int i = 0; i < vout.size(); i++) {
if (!vout[i].IsNull())
::Serialize(s, CTxOutCompressor(REF(vout[i])));
}
// coinbase height
::Serialize(s, VARINT(nHeight));
} }
template<typename Stream> template<typename Stream>
void Unserialize(Stream &s) { void Unserialize(Stream &s) {
unsigned int nCode = 0; uint32_t code = 0;
// version ::Unserialize(s, VARINT(code));
::Unserialize(s, VARINT(this->nVersion)); nHeight = code >> 1;
// header code fCoinBase = code & 1;
::Unserialize(s, VARINT(nCode)); ::Unserialize(s, REF(CTxOutCompressor(out)));
fCoinBase = nCode & 1;
std::vector<bool> vAvail(2, false);
vAvail[0] = (nCode & 2) != 0;
vAvail[1] = (nCode & 4) != 0;
unsigned int nMaskCode = (nCode / 8) + ((nCode & 6) != 0 ? 0 : 1);
// spentness bitmask
while (nMaskCode > 0) {
unsigned char chAvail = 0;
::Unserialize(s, chAvail);
for (unsigned int p = 0; p < 8; p++) {
bool f = (chAvail & (1 << p)) != 0;
vAvail.push_back(f);
}
if (chAvail != 0)
nMaskCode--;
}
// txouts themself
vout.assign(vAvail.size(), CTxOut());
for (unsigned int i = 0; i < vAvail.size(); i++) {
if (vAvail[i])
::Unserialize(s, REF(CTxOutCompressor(vout[i])));
}
// coinbase height
::Unserialize(s, VARINT(nHeight));
Cleanup();
} }
//! mark a vout spent bool IsSpent() const {
bool Spend(uint32_t nPos); return out.IsNull();
//! check whether a particular output is still available
bool IsAvailable(unsigned int nPos) const {
return (nPos < vout.size() && !vout[nPos].IsNull());
}
//! check whether the entire CCoins is spent
//! note that only !IsPruned() CCoins can be serialized
bool IsPruned() const {
BOOST_FOREACH(const CTxOut &out, vout)
if (!out.IsNull())
return false;
return true;
} }
size_t DynamicMemoryUsage() const { size_t DynamicMemoryUsage() const {
size_t ret = memusage::DynamicUsage(vout); return memusage::DynamicUsage(out.scriptPubKey);
BOOST_FOREACH(const CTxOut &out, vout) {
ret += RecursiveDynamicUsage(out.scriptPubKey);
}
return ret;
} }
}; };
class SaltedTxidHasher class SaltedOutpointHasher
{ {
private: private:
/** Salt */ /** Salt */
const uint64_t k0, k1; const uint64_t k0, k1;
public: public:
SaltedTxidHasher(); SaltedOutpointHasher();
/** /**
* This *must* return size_t. With Boost 1.46 on 32-bit systems the * This *must* return size_t. With Boost 1.46 on 32-bit systems the
* unordered_map will behave unpredictably if the custom hasher returns a * unordered_map will behave unpredictably if the custom hasher returns a
* uint64_t, resulting in failures when syncing the chain (#4634). * uint64_t, resulting in failures when syncing the chain (#4634).
*/ */
size_t operator()(const uint256& txid) const { size_t operator()(const COutPoint& id) const {
return SipHashUint256(k0, k1, txid); return SipHashUint256Extra(k0, k1, id.hash, id.n);
} }
}; };
struct CCoinsCacheEntry struct CCoinsCacheEntry
{ {
CCoins coins; // The actual cached data. Coin coin; // The actual cached data.
unsigned char flags; unsigned char flags;
enum Flags { enum Flags {
@ -277,20 +116,21 @@ struct CCoinsCacheEntry
*/ */
}; };
CCoinsCacheEntry() : coins(), flags(0) {} CCoinsCacheEntry() : flags(0) {}
explicit CCoinsCacheEntry(Coin&& coin_) : coin(std::move(coin_)), flags(0) {}
}; };
typedef std::unordered_map<uint256, CCoinsCacheEntry, SaltedTxidHasher> CCoinsMap; typedef std::unordered_map<COutPoint, CCoinsCacheEntry, SaltedOutpointHasher> CCoinsMap;
/** Cursor for iterating over CoinsView state */ /** Cursor for iterating over CoinsView state */
class CCoinsViewCursor class CCoinsViewCursor
{ {
public: public:
CCoinsViewCursor(const uint256 &hashBlockIn): hashBlock(hashBlockIn) {} CCoinsViewCursor(const uint256 &hashBlockIn): hashBlock(hashBlockIn) {}
virtual ~CCoinsViewCursor(); virtual ~CCoinsViewCursor() {}
virtual bool GetKey(uint256 &key) const = 0; virtual bool GetKey(COutPoint &key) const = 0;
virtual bool GetValue(CCoins &coins) const = 0; virtual bool GetValue(Coin &coin) const = 0;
virtual unsigned int GetValueSize() const = 0; virtual unsigned int GetValueSize() const = 0;
virtual bool Valid() const = 0; virtual bool Valid() const = 0;
@ -306,17 +146,17 @@ private:
class CCoinsView class CCoinsView
{ {
public: public:
//! Retrieve the CCoins (unspent transaction outputs) for a given txid //! Retrieve the Coin (unspent transaction output) for a given outpoint.
virtual bool GetCoins(const uint256 &txid, CCoins &coins) const; virtual bool GetCoin(const COutPoint &outpoint, Coin &coin) const;
//! Just check whether we have data for a given txid. //! Just check whether we have data for a given outpoint.
//! This may (but cannot always) return true for fully spent transactions //! This may (but cannot always) return true for spent outputs.
virtual bool HaveCoins(const uint256 &txid) const; virtual bool HaveCoin(const COutPoint &outpoint) const;
//! Retrieve the block hash whose state this CCoinsView currently represents //! Retrieve the block hash whose state this CCoinsView currently represents
virtual uint256 GetBestBlock() const; virtual uint256 GetBestBlock() const;
//! Do a bulk modification (multiple CCoins changes + BestBlock change). //! Do a bulk modification (multiple Coin changes + BestBlock change).
//! The passed mapCoins can be modified. //! The passed mapCoins can be modified.
virtual bool BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock); virtual bool BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock);
@ -325,6 +165,9 @@ public:
//! As we use CCoinsViews polymorphically, have a virtual destructor //! As we use CCoinsViews polymorphically, have a virtual destructor
virtual ~CCoinsView() {} virtual ~CCoinsView() {}
//! Estimate database size (0 if not implemented)
virtual size_t EstimateSize() const { return 0; }
}; };
@ -336,45 +179,20 @@ protected:
public: public:
CCoinsViewBacked(CCoinsView *viewIn); CCoinsViewBacked(CCoinsView *viewIn);
bool GetCoins(const uint256 &txid, CCoins &coins) const; bool GetCoin(const COutPoint &outpoint, Coin &coin) const override;
bool HaveCoins(const uint256 &txid) const; bool HaveCoin(const COutPoint &outpoint) const override;
uint256 GetBestBlock() const; uint256 GetBestBlock() const override;
void SetBackend(CCoinsView &viewIn); void SetBackend(CCoinsView &viewIn);
bool BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock); bool BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock) override;
CCoinsViewCursor *Cursor() const; CCoinsViewCursor *Cursor() const override;
size_t EstimateSize() const override;
}; };
class CCoinsViewCache;
/**
* A reference to a mutable cache entry. Encapsulating it allows us to run
* cleanup code after the modification is finished, and keeping track of
* concurrent modifications.
*/
class CCoinsModifier
{
private:
CCoinsViewCache& cache;
CCoinsMap::iterator it;
size_t cachedCoinUsage; // Cached memory usage of the CCoins object before modification
CCoinsModifier(CCoinsViewCache& cache_, CCoinsMap::iterator it_, size_t usage);
public:
CCoins* operator->() { return &it->second.coins; }
CCoins& operator*() { return it->second.coins; }
~CCoinsModifier();
friend class CCoinsViewCache;
};
/** CCoinsView that adds a memory cache for transactions to another CCoinsView */ /** CCoinsView that adds a memory cache for transactions to another CCoinsView */
class CCoinsViewCache : public CCoinsViewBacked class CCoinsViewCache : public CCoinsViewBacked
{ {
protected: protected:
/* Whether this cache has an active modifier. */
bool hasModifier;
/** /**
* Make mutable so that we can "fill the cache" even from Get-methods * Make mutable so that we can "fill the cache" even from Get-methods
* declared as "const". * declared as "const".
@ -382,51 +200,45 @@ protected:
mutable uint256 hashBlock; mutable uint256 hashBlock;
mutable CCoinsMap cacheCoins; mutable CCoinsMap cacheCoins;
/* Cached dynamic memory usage for the inner CCoins objects. */ /* Cached dynamic memory usage for the inner Coin objects. */
mutable size_t cachedCoinsUsage; mutable size_t cachedCoinsUsage;
public: public:
CCoinsViewCache(CCoinsView *baseIn); CCoinsViewCache(CCoinsView *baseIn);
~CCoinsViewCache();
// Standard CCoinsView methods // Standard CCoinsView methods
bool GetCoins(const uint256 &txid, CCoins &coins) const; bool GetCoin(const COutPoint &outpoint, Coin &coin) const;
bool HaveCoins(const uint256 &txid) const; bool HaveCoin(const COutPoint &outpoint) const;
uint256 GetBestBlock() const; uint256 GetBestBlock() const;
void SetBestBlock(const uint256 &hashBlock); void SetBestBlock(const uint256 &hashBlock);
bool BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock); bool BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock);
/** /**
* Check if we have the given tx already loaded in this cache. * Check if we have the given utxo already loaded in this cache.
* The semantics are the same as HaveCoins(), but no calls to * The semantics are the same as HaveCoin(), but no calls to
* the backing CCoinsView are made. * the backing CCoinsView are made.
*/ */
bool HaveCoinsInCache(const uint256 &txid) const; bool HaveCoinInCache(const COutPoint &outpoint) const;
/** /**
* Return a pointer to CCoins in the cache, or NULL if not found. This is * Return a reference to Coin in the cache, or a pruned one if not found. This is
* more efficient than GetCoins. Modifications to other cache entries are * more efficient than GetCoin. Modifications to other cache entries are
* allowed while accessing the returned pointer. * allowed while accessing the returned pointer.
*/ */
const CCoins* AccessCoins(const uint256 &txid) const; const Coin& AccessCoin(const COutPoint &output) const;
/** /**
* Return a modifiable reference to a CCoins. If no entry with the given * Add a coin. Set potential_overwrite to true if a non-pruned version may
* txid exists, a new one is created. Simultaneous modifications are not * already exist.
* allowed.
*/ */
CCoinsModifier ModifyCoins(const uint256 &txid); void AddCoin(const COutPoint& outpoint, Coin&& coin, bool potential_overwrite);
/** /**
* Return a modifiable reference to a CCoins. Assumes that no entry with the given * Spend a coin. Pass moveto in order to get the deleted data.
* txid exists and creates a new one. This saves a database access in the case where * If no unspent output exists for the passed outpoint, this call
* the coins were to be wiped out by FromTx anyway. This should not be called with * has no effect.
* the 2 historical coinbase duplicate pairs because the new coins are marked fresh, and
* in the event the duplicate coinbase was spent before a flush, the now pruned coins
* would not properly overwrite the first coinbase of the pair. Simultaneous modifications
* are not allowed.
*/ */
CCoinsModifier ModifyNewCoins(const uint256 &txid, bool coinbase); void SpendCoin(const COutPoint &outpoint, Coin* moveto = nullptr);
/** /**
* Push the modifications applied to this cache to its base. * Push the modifications applied to this cache to its base.
@ -436,12 +248,12 @@ public:
bool Flush(); bool Flush();
/** /**
* Removes the transaction with the given hash from the cache, if it is * Removes the UTXO with the given outpoint from the cache, if it is
* not modified. * not modified.
*/ */
void Uncache(const uint256 &txid); void Uncache(const COutPoint &outpoint);
//! Calculate the size of the cache (in number of transactions) //! Calculate the size of the cache (in number of transaction outputs)
unsigned int GetCacheSize() const; unsigned int GetCacheSize() const;
//! Calculate the size of the cache (in bytes) //! Calculate the size of the cache (in bytes)
@ -460,12 +272,8 @@ public:
//! Check whether all prevouts of the transaction are present in the UTXO set represented by this view //! Check whether all prevouts of the transaction are present in the UTXO set represented by this view
bool HaveInputs(const CTransaction& tx) const; bool HaveInputs(const CTransaction& tx) const;
const CTxOut &GetOutputFor(const CTxIn& input) const;
friend class CCoinsModifier;
private: private:
CCoinsMap::const_iterator FetchCoins(const uint256 &txid) const; CCoinsMap::iterator FetchCoin(const COutPoint &outpoint) const;
/** /**
* By making the copy constructor private, we prevent accidentally using it when one intends to create a cache on top of a base cache. * By making the copy constructor private, we prevent accidentally using it when one intends to create a cache on top of a base cache.
@ -473,4 +281,13 @@ private:
CCoinsViewCache(const CCoinsViewCache &); CCoinsViewCache(const CCoinsViewCache &);
}; };
//! Utility function to add all of a transaction's outputs to a cache.
// It assumes that overwrites are only possible for coinbase transactions,
// TODO: pass in a boolean to limit these possible overwrites to known
// (pre-BIP34) cases.
void AddCoins(CCoinsViewCache& cache, const CTransaction& tx, int nHeight);
//! Utility function to find any unspent output with a given txid.
const Coin& AccessByTxid(const CCoinsViewCache& cache, const uint256& txid);
#endif // BITCOIN_COINS_H #endif // BITCOIN_COINS_H

View File

@ -126,7 +126,7 @@ unsigned int GetP2SHSigOpCount(const CTransaction& tx, const CCoinsViewCache& in
unsigned int nSigOps = 0; unsigned int nSigOps = 0;
for (unsigned int i = 0; i < tx.vin.size(); i++) for (unsigned int i = 0; i < tx.vin.size(); i++)
{ {
const CTxOut &prevout = inputs.GetOutputFor(tx.vin[i]); const CTxOut &prevout = inputs.AccessCoin(tx.vin[i].prevout).out;
if (prevout.scriptPubKey.IsPayToScriptHash()) if (prevout.scriptPubKey.IsPayToScriptHash())
nSigOps += prevout.scriptPubKey.GetSigOpCount(tx.vin[i].scriptSig); nSigOps += prevout.scriptPubKey.GetSigOpCount(tx.vin[i].scriptSig);
} }
@ -146,7 +146,7 @@ int64_t GetTransactionSigOpCost(const CTransaction& tx, const CCoinsViewCache& i
for (unsigned int i = 0; i < tx.vin.size(); i++) for (unsigned int i = 0; i < tx.vin.size(); i++)
{ {
const CTxOut &prevout = inputs.GetOutputFor(tx.vin[i]); const CTxOut &prevout = inputs.AccessCoin(tx.vin[i].prevout).out;
nSigOps += CountWitnessSigOps(tx.vin[i].scriptSig, prevout.scriptPubKey, &tx.vin[i].scriptWitness, flags); nSigOps += CountWitnessSigOps(tx.vin[i].scriptSig, prevout.scriptPubKey, &tx.vin[i].scriptWitness, flags);
} }
return nSigOps; return nSigOps;
@ -213,20 +213,20 @@ bool Consensus::CheckTxInputs(const CTransaction& tx, CValidationState& state, c
for (unsigned int i = 0; i < tx.vin.size(); i++) for (unsigned int i = 0; i < tx.vin.size(); i++)
{ {
const COutPoint &prevout = tx.vin[i].prevout; const COutPoint &prevout = tx.vin[i].prevout;
const CCoins *coins = inputs.AccessCoins(prevout.hash); const Coin& coin = inputs.AccessCoin(prevout);
assert(coins); assert(!coin.IsSpent());
// If prev is coinbase, check that it's matured // If prev is coinbase, check that it's matured
if (coins->IsCoinBase()) { if (coin.IsCoinBase()) {
if (nSpendHeight - coins->nHeight < COINBASE_MATURITY) if (nSpendHeight - coin.nHeight < COINBASE_MATURITY)
return state.Invalid(false, return state.Invalid(false,
REJECT_INVALID, "bad-txns-premature-spend-of-coinbase", REJECT_INVALID, "bad-txns-premature-spend-of-coinbase",
strprintf("tried to spend coinbase at depth %d", nSpendHeight - coins->nHeight)); strprintf("tried to spend coinbase at depth %d", nSpendHeight - coin.nHeight));
} }
// Check for negative or overflow input values // Check for negative or overflow input values
nValueIn += coins->vout[prevout.n].nValue; nValueIn += coin.out.nValue;
if (!MoneyRange(coins->vout[prevout.n].nValue) || !MoneyRange(nValueIn)) if (!MoneyRange(coin.out.nValue) || !MoneyRange(nValueIn))
return state.DoS(100, false, REJECT_INVALID, "bad-txns-inputvalues-outofrange"); return state.DoS(100, false, REJECT_INVALID, "bad-txns-inputvalues-outofrange");
} }

View File

@ -55,11 +55,19 @@ private:
CDataStream ssKey; CDataStream ssKey;
CDataStream ssValue; CDataStream ssValue;
size_t size_estimate;
public: public:
/** /**
* @param[in] _parent CDBWrapper that this batch is to be submitted to * @param[in] _parent CDBWrapper that this batch is to be submitted to
*/ */
CDBBatch(const CDBWrapper &_parent) : parent(_parent), ssKey(SER_DISK, CLIENT_VERSION), ssValue(SER_DISK, CLIENT_VERSION) { }; CDBBatch(const CDBWrapper &_parent) : parent(_parent), ssKey(SER_DISK, CLIENT_VERSION), ssValue(SER_DISK, CLIENT_VERSION), size_estimate(0) { };
void Clear()
{
batch.Clear();
size_estimate = 0;
}
template <typename K, typename V> template <typename K, typename V>
void Write(const K& key, const V& value) void Write(const K& key, const V& value)
@ -74,6 +82,14 @@ public:
leveldb::Slice slValue(ssValue.data(), ssValue.size()); leveldb::Slice slValue(ssValue.data(), ssValue.size());
batch.Put(slKey, slValue); batch.Put(slKey, slValue);
// LevelDB serializes writes as:
// - byte: header
// - varint: key length (1 byte up to 127B, 2 bytes up to 16383B, ...)
// - byte[]: key
// - varint: value length
// - byte[]: value
// The formula below assumes the key and value are both less than 16k.
size_estimate += 3 + (slKey.size() > 127) + slKey.size() + (slValue.size() > 127) + slValue.size();
ssKey.clear(); ssKey.clear();
ssValue.clear(); ssValue.clear();
} }
@ -86,8 +102,16 @@ public:
leveldb::Slice slKey(ssKey.data(), ssKey.size()); leveldb::Slice slKey(ssKey.data(), ssKey.size());
batch.Delete(slKey); batch.Delete(slKey);
// LevelDB serializes erases as:
// - byte: header
// - varint: key length
// - byte[]: key
// The formula below assumes the key is less than 16kB.
size_estimate += 2 + (slKey.size() > 127) + slKey.size();
ssKey.clear(); ssKey.clear();
} }
size_t SizeEstimate() const { return size_estimate; }
}; };
class CDBIterator class CDBIterator
@ -281,7 +305,22 @@ public:
* Return true if the database managed by this class contains no entries. * Return true if the database managed by this class contains no entries.
*/ */
bool IsEmpty(); bool IsEmpty();
template<typename K>
size_t EstimateSize(const K& key_begin, const K& key_end) const
{
CDataStream ssKey1(SER_DISK, CLIENT_VERSION), ssKey2(SER_DISK, CLIENT_VERSION);
ssKey1.reserve(DBWRAPPER_PREALLOC_KEY_SIZE);
ssKey2.reserve(DBWRAPPER_PREALLOC_KEY_SIZE);
ssKey1 << key_begin;
ssKey2 << key_end;
leveldb::Slice slKey1(ssKey1.data(), ssKey1.size());
leveldb::Slice slKey2(ssKey2.data(), ssKey2.size());
uint64_t size = 0;
leveldb::Range range(slKey1, slKey2);
pdb->GetApproximateSizes(&range, 1, &size);
return size;
}
}; };
#endif // BITCOIN_DBWRAPPER_H #endif // BITCOIN_DBWRAPPER_H

View File

@ -208,3 +208,44 @@ uint64_t SipHashUint256(uint64_t k0, uint64_t k1, const uint256& val)
SIPROUND; SIPROUND;
return v0 ^ v1 ^ v2 ^ v3; return v0 ^ v1 ^ v2 ^ v3;
} }
uint64_t SipHashUint256Extra(uint64_t k0, uint64_t k1, const uint256& val, uint32_t extra)
{
/* Specialized implementation for efficiency */
uint64_t d = val.GetUint64(0);
uint64_t v0 = 0x736f6d6570736575ULL ^ k0;
uint64_t v1 = 0x646f72616e646f6dULL ^ k1;
uint64_t v2 = 0x6c7967656e657261ULL ^ k0;
uint64_t v3 = 0x7465646279746573ULL ^ k1 ^ d;
SIPROUND;
SIPROUND;
v0 ^= d;
d = val.GetUint64(1);
v3 ^= d;
SIPROUND;
SIPROUND;
v0 ^= d;
d = val.GetUint64(2);
v3 ^= d;
SIPROUND;
SIPROUND;
v0 ^= d;
d = val.GetUint64(3);
v3 ^= d;
SIPROUND;
SIPROUND;
v0 ^= d;
d = (((uint64_t)36) << 56) | extra;
v3 ^= d;
SIPROUND;
SIPROUND;
v0 ^= d;
v2 ^= 0xFF;
SIPROUND;
SIPROUND;
SIPROUND;
SIPROUND;
return v0 ^ v1 ^ v2 ^ v3;
}

View File

@ -160,6 +160,41 @@ public:
} }
}; };
/** Reads data from an underlying stream, while hashing the read data. */
template<typename Source>
class CHashVerifier : public CHashWriter
{
private:
Source* source;
public:
CHashVerifier(Source* source_) : CHashWriter(source_->GetType(), source_->GetVersion()), source(source_) {}
void read(char* pch, size_t nSize)
{
source->read(pch, nSize);
this->write(pch, nSize);
}
void ignore(size_t nSize)
{
char data[1024];
while (nSize > 0) {
size_t now = std::min<size_t>(nSize, 1024);
read(data, now);
nSize -= now;
}
}
template<typename T>
CHashVerifier<Source>& operator>>(T& obj)
{
// Unserialize from this stream
::Unserialize(*this, obj);
return (*this);
}
};
/** Compute the 256-bit hash of an object's serialization. */ /** Compute the 256-bit hash of an object's serialization. */
template<typename T> template<typename T>
uint256 SerializeHash(const T& obj, int nType=SER_GETHASH, int nVersion=PROTOCOL_VERSION) uint256 SerializeHash(const T& obj, int nType=SER_GETHASH, int nVersion=PROTOCOL_VERSION)
@ -206,5 +241,6 @@ public:
* .Finalize() * .Finalize()
*/ */
uint64_t SipHashUint256(uint64_t k0, uint64_t k1, const uint256& val); uint64_t SipHashUint256(uint64_t k0, uint64_t k1, const uint256& val);
uint64_t SipHashUint256Extra(uint64_t k0, uint64_t k1, const uint256& val, uint32_t extra);
#endif // BITCOIN_HASH_H #endif // BITCOIN_HASH_H

View File

@ -146,9 +146,9 @@ class CCoinsViewErrorCatcher : public CCoinsViewBacked
{ {
public: public:
CCoinsViewErrorCatcher(CCoinsView* view) : CCoinsViewBacked(view) {} CCoinsViewErrorCatcher(CCoinsView* view) : CCoinsViewBacked(view) {}
bool GetCoins(const uint256 &txid, CCoins &coins) const { bool GetCoin(const COutPoint &outpoint, Coin &coin) const override {
try { try {
return CCoinsViewBacked::GetCoins(txid, coins); return CCoinsViewBacked::GetCoin(outpoint, coin);
} catch(const std::runtime_error& e) { } catch(const std::runtime_error& e) {
uiInterface.ThreadSafeMessageBox(_("Error reading from database, shutting down."), "", CClientUIInterface::MSG_ERROR); uiInterface.ThreadSafeMessageBox(_("Error reading from database, shutting down."), "", CClientUIInterface::MSG_ERROR);
LogPrintf("Error reading from database: %s\n", e.what()); LogPrintf("Error reading from database: %s\n", e.what());
@ -1450,6 +1450,12 @@ bool AppInitMain(boost::thread_group& threadGroup, CScheduler& scheduler)
//If we're reindexing in prune mode, wipe away unusable block files and all undo data files //If we're reindexing in prune mode, wipe away unusable block files and all undo data files
if (fPruneMode) if (fPruneMode)
CleanupBlockRevFiles(); CleanupBlockRevFiles();
} else {
// If necessary, upgrade from older database format.
if (!pcoinsdbview->Upgrade()) {
strLoadError = _("Error upgrading chainstate database");
break;
}
} }
if (!LoadBlockIndex(chainparams)) { if (!LoadBlockIndex(chainparams)) {

View File

@ -911,12 +911,11 @@ bool static AlreadyHave(const CInv& inv) EXCLUSIVE_LOCKS_REQUIRED(cs_main)
recentRejects->reset(); recentRejects->reset();
} }
// Use pcoinsTip->HaveCoinsInCache as a quick approximation to exclude
// requesting or processing some txs which have already been included in a block
return recentRejects->contains(inv.hash) || return recentRejects->contains(inv.hash) ||
mempool.exists(inv.hash) || mempool.exists(inv.hash) ||
mapOrphanTransactions.count(inv.hash) || mapOrphanTransactions.count(inv.hash) ||
pcoinsTip->HaveCoinsInCache(inv.hash); pcoinsTip->HaveCoinInCache(COutPoint(inv.hash, 0)) || // Best effort: only try output 0 and 1
pcoinsTip->HaveCoinInCache(COutPoint(inv.hash, 1));
} }
case MSG_BLOCK: case MSG_BLOCK:
case MSG_WITNESS_BLOCK: case MSG_WITNESS_BLOCK:

View File

@ -165,7 +165,7 @@ bool AreInputsStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs)
for (unsigned int i = 0; i < tx.vin.size(); i++) for (unsigned int i = 0; i < tx.vin.size(); i++)
{ {
const CTxOut& prev = mapInputs.GetOutputFor(tx.vin[i]); const CTxOut& prev = mapInputs.AccessCoin(tx.vin[i].prevout).out;
std::vector<std::vector<unsigned char> > vSolutions; std::vector<std::vector<unsigned char> > vSolutions;
txnouttype whichType; txnouttype whichType;
@ -204,7 +204,7 @@ bool IsWitnessStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs)
if (tx.vin[i].scriptWitness.IsNull()) if (tx.vin[i].scriptWitness.IsNull())
continue; continue;
const CTxOut &prev = mapInputs.GetOutputFor(tx.vin[i]); const CTxOut &prev = mapInputs.AccessCoin(tx.vin[i].prevout).out;
// get the scriptPubKey corresponding to this input: // get the scriptPubKey corresponding to this input:
CScript prevScript = prev.scriptPubKey; CScript prevScript = prev.scriptPubKey;

View File

@ -293,13 +293,12 @@ QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx, TransactionReco
{ {
COutPoint prevout = txin.prevout; COutPoint prevout = txin.prevout;
CCoins prev; Coin prev;
if(pcoinsTip->GetCoins(prevout.hash, prev)) if(pcoinsTip->GetCoin(prevout, prev))
{ {
if (prevout.n < prev.vout.size())
{ {
strHTML += "<li>"; strHTML += "<li>";
const CTxOut &vout = prev.vout[prevout.n]; const CTxOut &vout = prev.out;
CTxDestination address; CTxDestination address;
if (ExtractDestination(vout.scriptPubKey, address)) if (ExtractDestination(vout.scriptPubKey, address))
{ {

View File

@ -42,16 +42,19 @@ static const struct {
}; };
struct CCoin { struct CCoin {
uint32_t nTxVer; // Don't call this nVersion, that name has a special meaning inside IMPLEMENT_SERIALIZE
uint32_t nHeight; uint32_t nHeight;
CTxOut out; CTxOut out;
ADD_SERIALIZE_METHODS; ADD_SERIALIZE_METHODS;
CCoin() : nHeight(0) {}
CCoin(Coin&& in) : nHeight(in.nHeight), out(std::move(in.out)) {}
template <typename Stream, typename Operation> template <typename Stream, typename Operation>
inline void SerializationOp(Stream& s, Operation ser_action) inline void SerializationOp(Stream& s, Operation ser_action)
{ {
READWRITE(nTxVer); uint32_t nTxVerDummy = 0;
READWRITE(nTxVerDummy);
READWRITE(nHeight); READWRITE(nHeight);
READWRITE(out); READWRITE(out);
} }
@ -509,22 +512,11 @@ static bool rest_getutxos(HTTPRequest* req, const std::string& strURIPart)
view.SetBackend(viewMempool); // switch cache backend to db+mempool in case user likes to query mempool view.SetBackend(viewMempool); // switch cache backend to db+mempool in case user likes to query mempool
for (size_t i = 0; i < vOutPoints.size(); i++) { for (size_t i = 0; i < vOutPoints.size(); i++) {
CCoins coins;
uint256 hash = vOutPoints[i].hash;
bool hit = false; bool hit = false;
if (view.GetCoins(hash, coins)) { Coin coin;
mempool.pruneSpent(hash, coins); if (view.GetCoin(vOutPoints[i], coin) && !mempool.isSpent(vOutPoints[i])) {
if (coins.IsAvailable(vOutPoints[i].n)) { hit = true;
hit = true; outs.emplace_back(std::move(coin));
// Safe to index into vout here because IsAvailable checked if it's off the end of the array, or if
// n is valid but points to an already spent output (IsNull).
CCoin coin;
coin.nTxVer = coins.nVersion;
coin.nHeight = coins.nHeight;
coin.out = coins.vout.at(vOutPoints[i].n);
assert(!coin.out.IsNull());
outs.push_back(coin);
}
} }
hits.push_back(hit); hits.push_back(hit);
@ -568,7 +560,6 @@ static bool rest_getutxos(HTTPRequest* req, const std::string& strURIPart)
UniValue utxos(UniValue::VARR); UniValue utxos(UniValue::VARR);
BOOST_FOREACH (const CCoin& coin, outs) { BOOST_FOREACH (const CCoin& coin, outs) {
UniValue utxo(UniValue::VOBJ); UniValue utxo(UniValue::VOBJ);
utxo.push_back(Pair("txvers", (int32_t)coin.nTxVer));
utxo.push_back(Pair("height", (int32_t)coin.nHeight)); utxo.push_back(Pair("height", (int32_t)coin.nHeight));
utxo.push_back(Pair("value", ValueFromAmount(coin.out.nValue))); utxo.push_back(Pair("value", ValueFromAmount(coin.out.nValue)));

View File

@ -781,13 +781,29 @@ struct CCoinsStats
uint256 hashBlock; uint256 hashBlock;
uint64_t nTransactions; uint64_t nTransactions;
uint64_t nTransactionOutputs; uint64_t nTransactionOutputs;
uint64_t nSerializedSize;
uint256 hashSerialized; uint256 hashSerialized;
uint64_t nDiskSize;
CAmount nTotalAmount; CAmount nTotalAmount;
CCoinsStats() : nHeight(0), nTransactions(0), nTransactionOutputs(0), nSerializedSize(0), nTotalAmount(0) {} CCoinsStats() : nHeight(0), nTransactions(0), nTransactionOutputs(0), nTotalAmount(0) {}
}; };
static void ApplyStats(CCoinsStats &stats, CHashWriter& ss, const uint256& hash, const std::map<uint32_t, Coin>& outputs)
{
assert(!outputs.empty());
ss << hash;
ss << VARINT(outputs.begin()->second.nHeight * 2 + outputs.begin()->second.fCoinBase);
stats.nTransactions++;
for (const auto output : outputs) {
ss << VARINT(output.first + 1);
ss << *(const CScriptBase*)(&output.second.out.scriptPubKey);
ss << VARINT(output.second.out.nValue);
stats.nTransactionOutputs++;
stats.nTotalAmount += output.second.out.nValue;
}
ss << VARINT(0);
}
//! Calculate statistics about the unspent transaction output set //! Calculate statistics about the unspent transaction output set
static bool GetUTXOStats(CCoinsView *view, CCoinsStats &stats) static bool GetUTXOStats(CCoinsView *view, CCoinsStats &stats)
{ {
@ -800,32 +816,29 @@ static bool GetUTXOStats(CCoinsView *view, CCoinsStats &stats)
stats.nHeight = mapBlockIndex.find(stats.hashBlock)->second->nHeight; stats.nHeight = mapBlockIndex.find(stats.hashBlock)->second->nHeight;
} }
ss << stats.hashBlock; ss << stats.hashBlock;
CAmount nTotalAmount = 0; uint256 prevkey;
std::map<uint32_t, Coin> outputs;
while (pcursor->Valid()) { while (pcursor->Valid()) {
boost::this_thread::interruption_point(); boost::this_thread::interruption_point();
uint256 key; COutPoint key;
CCoins coins; Coin coin;
if (pcursor->GetKey(key) && pcursor->GetValue(coins)) { if (pcursor->GetKey(key) && pcursor->GetValue(coin)) {
stats.nTransactions++; if (!outputs.empty() && key.hash != prevkey) {
ss << key; ApplyStats(stats, ss, prevkey, outputs);
for (unsigned int i=0; i<coins.vout.size(); i++) { outputs.clear();
const CTxOut &out = coins.vout[i];
if (!out.IsNull()) {
stats.nTransactionOutputs++;
ss << VARINT(i+1);
ss << out;
nTotalAmount += out.nValue;
}
} }
stats.nSerializedSize += 32 + pcursor->GetValueSize(); prevkey = key.hash;
ss << VARINT(0); outputs[key.n] = std::move(coin);
} else { } else {
return error("%s: unable to read value", __func__); return error("%s: unable to read value", __func__);
} }
pcursor->Next(); pcursor->Next();
} }
if (!outputs.empty()) {
ApplyStats(stats, ss, prevkey, outputs);
}
stats.hashSerialized = ss.GetHash(); stats.hashSerialized = ss.GetHash();
stats.nTotalAmount = nTotalAmount; stats.nDiskSize = view->EstimateSize();
return true; return true;
} }
@ -891,8 +904,8 @@ UniValue gettxoutsetinfo(const JSONRPCRequest& request)
" \"bestblock\": \"hex\", (string) the best block hash hex\n" " \"bestblock\": \"hex\", (string) the best block hash hex\n"
" \"transactions\": n, (numeric) The number of transactions\n" " \"transactions\": n, (numeric) The number of transactions\n"
" \"txouts\": n, (numeric) The number of output transactions\n" " \"txouts\": n, (numeric) The number of output transactions\n"
" \"bytes_serialized\": n, (numeric) The serialized size\n"
" \"hash_serialized\": \"hash\", (string) The serialized hash\n" " \"hash_serialized\": \"hash\", (string) The serialized hash\n"
" \"disk_size\": n, (numeric) The estimated size of the chainstate on disk\n"
" \"total_amount\": x.xxx (numeric) The total amount\n" " \"total_amount\": x.xxx (numeric) The total amount\n"
"}\n" "}\n"
"\nExamples:\n" "\nExamples:\n"
@ -909,8 +922,8 @@ UniValue gettxoutsetinfo(const JSONRPCRequest& request)
ret.push_back(Pair("bestblock", stats.hashBlock.GetHex())); ret.push_back(Pair("bestblock", stats.hashBlock.GetHex()));
ret.push_back(Pair("transactions", (int64_t)stats.nTransactions)); ret.push_back(Pair("transactions", (int64_t)stats.nTransactions));
ret.push_back(Pair("txouts", (int64_t)stats.nTransactionOutputs)); ret.push_back(Pair("txouts", (int64_t)stats.nTransactionOutputs));
ret.push_back(Pair("bytes_serialized", (int64_t)stats.nSerializedSize)); ret.push_back(Pair("hash_serialized_2", stats.hashSerialized.GetHex()));
ret.push_back(Pair("hash_serialized", stats.hashSerialized.GetHex())); ret.push_back(Pair("disk_size", stats.nDiskSize));
ret.push_back(Pair("total_amount", ValueFromAmount(stats.nTotalAmount))); ret.push_back(Pair("total_amount", ValueFromAmount(stats.nTotalAmount)));
} else { } else {
throw JSONRPCError(RPC_INTERNAL_ERROR, "Unable to read UTXO set"); throw JSONRPCError(RPC_INTERNAL_ERROR, "Unable to read UTXO set");
@ -963,37 +976,37 @@ UniValue gettxout(const JSONRPCRequest& request)
std::string strHash = request.params[0].get_str(); std::string strHash = request.params[0].get_str();
uint256 hash(uint256S(strHash)); uint256 hash(uint256S(strHash));
int n = request.params[1].get_int(); int n = request.params[1].get_int();
COutPoint out(hash, n);
bool fMempool = true; bool fMempool = true;
if (request.params.size() > 2) if (request.params.size() > 2)
fMempool = request.params[2].get_bool(); fMempool = request.params[2].get_bool();
CCoins coins; Coin coin;
if (fMempool) { if (fMempool) {
LOCK(mempool.cs); LOCK(mempool.cs);
CCoinsViewMemPool view(pcoinsTip, mempool); CCoinsViewMemPool view(pcoinsTip, mempool);
if (!view.GetCoins(hash, coins)) if (!view.GetCoin(out, coin) || mempool.isSpent(out)) { // TODO: filtering spent coins should be done by the CCoinsViewMemPool
return NullUniValue; return NullUniValue;
mempool.pruneSpent(hash, coins); // TODO: this should be done by the CCoinsViewMemPool }
} else { } else {
if (!pcoinsTip->GetCoins(hash, coins)) if (!pcoinsTip->GetCoin(out, coin)) {
return NullUniValue; return NullUniValue;
}
} }
if (n<0 || (unsigned int)n>=coins.vout.size() || coins.vout[n].IsNull())
return NullUniValue;
BlockMap::iterator it = mapBlockIndex.find(pcoinsTip->GetBestBlock()); BlockMap::iterator it = mapBlockIndex.find(pcoinsTip->GetBestBlock());
CBlockIndex *pindex = it->second; CBlockIndex *pindex = it->second;
ret.push_back(Pair("bestblock", pindex->GetBlockHash().GetHex())); ret.push_back(Pair("bestblock", pindex->GetBlockHash().GetHex()));
if ((unsigned int)coins.nHeight == MEMPOOL_HEIGHT) if (coin.nHeight == MEMPOOL_HEIGHT) {
ret.push_back(Pair("confirmations", 0)); ret.push_back(Pair("confirmations", 0));
else } else {
ret.push_back(Pair("confirmations", pindex->nHeight - coins.nHeight + 1)); ret.push_back(Pair("confirmations", (int64_t)(pindex->nHeight - coin.nHeight + 1)));
ret.push_back(Pair("value", ValueFromAmount(coins.vout[n].nValue))); }
ret.push_back(Pair("value", ValueFromAmount(coin.out.nValue)));
UniValue o(UniValue::VOBJ); UniValue o(UniValue::VOBJ);
ScriptPubKeyToUniv(coins.vout[n].scriptPubKey, o, true); ScriptPubKeyToUniv(coin.out.scriptPubKey, o, true);
ret.push_back(Pair("scriptPubKey", o)); ret.push_back(Pair("scriptPubKey", o));
ret.push_back(Pair("version", coins.nVersion)); ret.push_back(Pair("coinbase", (bool)coin.fCoinBase));
ret.push_back(Pair("coinbase", coins.fCoinBase));
return ret; return ret;
} }

View File

@ -219,9 +219,10 @@ UniValue gettxoutproof(const JSONRPCRequest& request)
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found"); throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
pblockindex = mapBlockIndex[hashBlock]; pblockindex = mapBlockIndex[hashBlock];
} else { } else {
CCoins coins; const Coin& coin = AccessByTxid(*pcoinsTip, oneTxid);
if (pcoinsTip->GetCoins(oneTxid, coins) && coins.nHeight > 0 && coins.nHeight <= chainActive.Height()) if (!coin.IsSpent() && coin.nHeight > 0 && coin.nHeight <= chainActive.Height()) {
pblockindex = chainActive[coins.nHeight]; pblockindex = chainActive[coin.nHeight];
}
} }
if (pblockindex == NULL) if (pblockindex == NULL)
@ -637,9 +638,7 @@ UniValue signrawtransaction(const JSONRPCRequest& request)
view.SetBackend(viewMempool); // temporarily switch cache backend to db+mempool view view.SetBackend(viewMempool); // temporarily switch cache backend to db+mempool view
BOOST_FOREACH(const CTxIn& txin, mergedTx.vin) { BOOST_FOREACH(const CTxIn& txin, mergedTx.vin) {
const uint256& prevHash = txin.prevout.hash; view.AccessCoin(txin.prevout); // Load entries from viewChain into view; can fail.
CCoins coins;
view.AccessCoins(prevHash); // this is certainly allowed to fail
} }
view.SetBackend(viewDummy); // switch back to avoid locking mempool for too long view.SetBackend(viewDummy); // switch back to avoid locking mempool for too long
@ -691,24 +690,26 @@ UniValue signrawtransaction(const JSONRPCRequest& request)
if (nOut < 0) if (nOut < 0)
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "vout must be positive"); throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "vout must be positive");
COutPoint out(txid, nOut);
std::vector<unsigned char> pkData(ParseHexO(prevOut, "scriptPubKey")); std::vector<unsigned char> pkData(ParseHexO(prevOut, "scriptPubKey"));
CScript scriptPubKey(pkData.begin(), pkData.end()); CScript scriptPubKey(pkData.begin(), pkData.end());
{ {
CCoinsModifier coins = view.ModifyCoins(txid); const Coin& coin = view.AccessCoin(out);
if (coins->IsAvailable(nOut) && coins->vout[nOut].scriptPubKey != scriptPubKey) { if (!coin.IsSpent() && coin.out.scriptPubKey != scriptPubKey) {
std::string err("Previous output scriptPubKey mismatch:\n"); std::string err("Previous output scriptPubKey mismatch:\n");
err = err + ScriptToAsmStr(coins->vout[nOut].scriptPubKey) + "\nvs:\n"+ err = err + ScriptToAsmStr(coin.out.scriptPubKey) + "\nvs:\n"+
ScriptToAsmStr(scriptPubKey); ScriptToAsmStr(scriptPubKey);
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, err); throw JSONRPCError(RPC_DESERIALIZATION_ERROR, err);
} }
if ((unsigned int)nOut >= coins->vout.size()) Coin newcoin;
coins->vout.resize(nOut+1); newcoin.out.scriptPubKey = scriptPubKey;
coins->vout[nOut].scriptPubKey = scriptPubKey; newcoin.out.nValue = 0;
coins->vout[nOut].nValue = 0;
if (prevOut.exists("amount")) { if (prevOut.exists("amount")) {
coins->vout[nOut].nValue = AmountFromValue(find_value(prevOut, "amount")); newcoin.out.nValue = AmountFromValue(find_value(prevOut, "amount"));
} }
newcoin.nHeight = 1;
view.AddCoin(out, std::move(newcoin), true);
} }
// if redeemScript given and not using the local wallet (private keys // if redeemScript given and not using the local wallet (private keys
@ -766,13 +767,13 @@ UniValue signrawtransaction(const JSONRPCRequest& request)
// Sign what we can: // Sign what we can:
for (unsigned int i = 0; i < mergedTx.vin.size(); i++) { for (unsigned int i = 0; i < mergedTx.vin.size(); i++) {
CTxIn& txin = mergedTx.vin[i]; CTxIn& txin = mergedTx.vin[i];
const CCoins* coins = view.AccessCoins(txin.prevout.hash); const Coin& coin = view.AccessCoin(txin.prevout);
if (coins == NULL || !coins->IsAvailable(txin.prevout.n)) { if (coin.IsSpent()) {
TxInErrorToJSON(txin, vErrors, "Input not found or already spent"); TxInErrorToJSON(txin, vErrors, "Input not found or already spent");
continue; continue;
} }
const CScript& prevPubKey = coins->vout[txin.prevout.n].scriptPubKey; const CScript& prevPubKey = coin.out.scriptPubKey;
const CAmount& amount = coins->vout[txin.prevout.n].nValue; const CAmount& amount = coin.out.nValue;
SignatureData sigdata; SignatureData sigdata;
// Only sign SIGHASH_SINGLE if there's a corresponding output: // Only sign SIGHASH_SINGLE if there's a corresponding output:
@ -844,9 +845,12 @@ UniValue sendrawtransaction(const JSONRPCRequest& request)
nMaxRawTxFee = 0; nMaxRawTxFee = 0;
CCoinsViewCache &view = *pcoinsTip; CCoinsViewCache &view = *pcoinsTip;
const CCoins* existingCoins = view.AccessCoins(hashTx); bool fHaveChain = false;
for (size_t o = 0; !fHaveChain && o < tx->vout.size(); o++) {
const Coin& existingCoin = view.AccessCoin(COutPoint(hashTx, o));
fHaveChain = !existingCoin.IsSpent();
}
bool fHaveMempool = mempool.exists(hashTx); bool fHaveMempool = mempool.exists(hashTx);
bool fHaveChain = existingCoins && existingCoins->nHeight < 1000000000;
if (!fHaveMempool && !fHaveChain) { if (!fHaveMempool && !fHaveChain) {
// push to local node and sync with wallets // push to local node and sync with wallets
CValidationState state; CValidationState state;

View File

@ -17,35 +17,44 @@
#include <boost/test/unit_test.hpp> #include <boost/test/unit_test.hpp>
bool ApplyTxInUndo(const CTxInUndo& undo, CCoinsViewCache& view, const COutPoint& out); int ApplyTxInUndo(Coin&& undo, CCoinsViewCache& view, const COutPoint& out);
void UpdateCoins(const CTransaction& tx, CCoinsViewCache& inputs, CTxUndo &txundo, int nHeight); void UpdateCoins(const CTransaction& tx, CCoinsViewCache& inputs, CTxUndo &txundo, int nHeight);
namespace namespace
{ {
//! equality test
bool operator==(const Coin &a, const Coin &b) {
// Empty Coin objects are always equal.
if (a.IsSpent() && b.IsSpent()) return true;
return a.fCoinBase == b.fCoinBase &&
a.nHeight == b.nHeight &&
a.out == b.out;
}
class CCoinsViewTest : public CCoinsView class CCoinsViewTest : public CCoinsView
{ {
uint256 hashBestBlock_; uint256 hashBestBlock_;
std::map<uint256, CCoins> map_; std::map<COutPoint, Coin> map_;
public: public:
bool GetCoins(const uint256& txid, CCoins& coins) const bool GetCoin(const COutPoint& outpoint, Coin& coin) const
{ {
std::map<uint256, CCoins>::const_iterator it = map_.find(txid); std::map<COutPoint, Coin>::const_iterator it = map_.find(outpoint);
if (it == map_.end()) { if (it == map_.end()) {
return false; return false;
} }
coins = it->second; coin = it->second;
if (coins.IsPruned() && insecure_rand() % 2 == 0) { if (coin.IsSpent() && insecure_rand() % 2 == 0) {
// Randomly return false in case of an empty entry. // Randomly return false in case of an empty entry.
return false; return false;
} }
return true; return true;
} }
bool HaveCoins(const uint256& txid) const bool HaveCoin(const COutPoint& outpoint) const
{ {
CCoins coins; Coin coin;
return GetCoins(txid, coins); return GetCoin(outpoint, coin);
} }
uint256 GetBestBlock() const { return hashBestBlock_; } uint256 GetBestBlock() const { return hashBestBlock_; }
@ -55,8 +64,8 @@ public:
for (CCoinsMap::iterator it = mapCoins.begin(); it != mapCoins.end(); ) { for (CCoinsMap::iterator it = mapCoins.begin(); it != mapCoins.end(); ) {
if (it->second.flags & CCoinsCacheEntry::DIRTY) { if (it->second.flags & CCoinsCacheEntry::DIRTY) {
// Same optimization used in CCoinsViewDB is to only write dirty entries. // Same optimization used in CCoinsViewDB is to only write dirty entries.
map_[it->first] = it->second.coins; map_[it->first] = it->second.coin;
if (it->second.coins.IsPruned() && insecure_rand() % 3 == 0) { if (it->second.coin.IsSpent() && insecure_rand() % 3 == 0) {
// Randomly delete empty entries on write. // Randomly delete empty entries on write.
map_.erase(it->first); map_.erase(it->first);
} }
@ -78,9 +87,12 @@ public:
{ {
// Manually recompute the dynamic usage of the whole data, and compare it. // Manually recompute the dynamic usage of the whole data, and compare it.
size_t ret = memusage::DynamicUsage(cacheCoins); size_t ret = memusage::DynamicUsage(cacheCoins);
size_t count = 0;
for (CCoinsMap::iterator it = cacheCoins.begin(); it != cacheCoins.end(); it++) { for (CCoinsMap::iterator it = cacheCoins.begin(); it != cacheCoins.end(); it++) {
ret += it->second.coins.DynamicMemoryUsage(); ret += it->second.coin.DynamicMemoryUsage();
++count;
} }
BOOST_CHECK_EQUAL(GetCacheSize(), count);
BOOST_CHECK_EQUAL(DynamicMemoryUsage(), ret); BOOST_CHECK_EQUAL(DynamicMemoryUsage(), ret);
} }
@ -97,7 +109,7 @@ static const unsigned int NUM_SIMULATION_ITERATIONS = 40000;
// This is a large randomized insert/remove simulation test on a variable-size // This is a large randomized insert/remove simulation test on a variable-size
// stack of caches on top of CCoinsViewTest. // stack of caches on top of CCoinsViewTest.
// //
// It will randomly create/update/delete CCoins entries to a tip of caches, with // It will randomly create/update/delete Coin entries to a tip of caches, with
// txids picked from a limited list of random 256-bit hashes. Occasionally, a // 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. // new tip is added to the stack of caches, or the tip is flushed and removed.
// //
@ -109,13 +121,15 @@ BOOST_AUTO_TEST_CASE(coins_cache_simulation_test)
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;
// 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.
CCoinsViewTest base; // A CCoinsViewTest at the bottom. CCoinsViewTest base; // A CCoinsViewTest at the bottom.
@ -133,36 +147,51 @@ BOOST_AUTO_TEST_CASE(coins_cache_simulation_test)
// Do a random modification. // Do a random modification.
{ {
uint256 txid = txids[insecure_rand() % txids.size()]; // txid we're going to modify in this iteration. uint256 txid = txids[insecure_rand() % txids.size()]; // txid we're going to modify in this iteration.
CCoins& coins = result[txid]; Coin& coin = result[COutPoint(txid, 0)];
CCoinsModifier entry = stack.back()->ModifyCoins(txid); const Coin& entry = (insecure_rand() % 500 == 0) ? AccessByTxid(*stack.back(), txid) : stack.back()->AccessCoin(COutPoint(txid, 0));
BOOST_CHECK(coins == *entry); BOOST_CHECK(coin == entry);
if (insecure_rand() % 5 == 0 || coins.IsPruned()) {
if (coins.IsPruned()) { if (insecure_rand() % 5 == 0 || coin.IsSpent()) {
added_an_entry = true; Coin newcoin;
newcoin.out.nValue = insecure_rand();
newcoin.nHeight = 1;
if (insecure_rand() % 16 == 0 && coin.IsSpent()) {
newcoin.out.scriptPubKey.assign(1 + (insecure_rand() & 0x3F), OP_RETURN);
BOOST_CHECK(newcoin.out.scriptPubKey.IsUnspendable());
added_an_unspendable_entry = true;
} else { } else {
updated_an_entry = true; newcoin.out.scriptPubKey.assign(insecure_rand() & 0x3F, 0); // Random sizes so we can test memory usage accounting
(coin.IsSpent() ? added_an_entry : updated_an_entry) = true;
coin = newcoin;
} }
coins.nVersion = insecure_rand(); stack.back()->AddCoin(COutPoint(txid, 0), std::move(newcoin), !coin.IsSpent() || insecure_rand() & 1);
coins.vout.resize(1);
coins.vout[0].nValue = insecure_rand();
*entry = coins;
} 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
if (insecure_rand() % 10) {
COutPoint out(txids[insecure_rand() % txids.size()], 0);
int cacheid = insecure_rand() % stack.size();
stack[cacheid]->Uncache(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 (std::map<uint256, CCoins>::iterator it = result.begin(); it != result.end(); it++) { for (auto it = result.begin(); it != result.end(); it++) {
const CCoins* coins = stack.back()->AccessCoins(it->first); bool have = stack.back()->HaveCoin(it->first);
if (coins) { const Coin& coin = stack.back()->AccessCoin(it->first);
BOOST_CHECK(*coins == it->second); BOOST_CHECK(have == !coin.IsSpent());
found_an_entry = true; BOOST_CHECK(coin == it->second);
} else { if (coin.IsSpent()) {
BOOST_CHECK(it->second.IsPruned());
missed_an_entry = true; missed_an_entry = true;
} else {
BOOST_CHECK(stack.back()->HaveCoinInCache(it->first));
found_an_entry = true;
} }
} }
BOOST_FOREACH(const CCoinsViewCacheTest *test, stack) { BOOST_FOREACH(const CCoinsViewCacheTest *test, stack) {
@ -211,25 +240,27 @@ BOOST_AUTO_TEST_CASE(coins_cache_simulation_test)
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);
} }
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;
} }
@ -242,7 +273,7 @@ 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.
CCoinsViewTest base; // A CCoinsViewTest at the bottom. CCoinsViewTest base; // A CCoinsViewTest at the bottom.
@ -250,10 +281,10 @@ BOOST_AUTO_TEST_CASE(updatecoins_simulation_test)
stack.push_back(new CCoinsViewCacheTest(&base)); // Start with one cache. stack.push_back(new CCoinsViewCacheTest(&base)); // Start with one cache.
// 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();
@ -264,23 +295,24 @@ BOOST_AUTO_TEST_CASE(updatecoins_simulation_test)
tx.vin.resize(1); tx.vin.resize(1);
tx.vout.resize(1); tx.vout.resize(1);
tx.vout[0].nValue = i; //Keep txs unique unless intended to duplicate tx.vout[0].nValue = i; //Keep txs unique unless intended to duplicate
tx.vout[0].scriptPubKey.assign(insecure_rand() & 0x3F, 0); // Random sizes so we can test memory usage accounting
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 duplicated // shouldn't be available for reconnection if its been duplicated
disconnectedids.erase(tx.GetHash()); disconnected_coins.erase(utxod->first);
duplicateids.insert(tx.GetHash()); duplicate_coins.insert(utxod->first);
} }
else { else {
coinbaseids.insert(tx.GetHash()); coinbase_coins.insert(COutPoint(tx.GetHash(), 0));
} }
assert(CTransaction(tx).IsCoinBase()); assert(CTransaction(tx).IsCoinBase());
} }
@ -288,114 +320,118 @@ BOOST_AUTO_TEST_CASE(updatecoins_simulation_test)
// 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() && !utxoset.count(prevouthash)) { if (!CTransaction(tx).IsCoinBase() && !utxoset.count(prevout)) {
disconnectedids.erase(tx.GetHash()); disconnected_coins.erase(utxod->first);
continue; continue;
} }
// If this tx is already IN the UTXO, then it must be a coinbase, and it must be a duplicate // If this tx is already IN the UTXO, then it must be a coinbase, and it must be a duplicate
if (utxoset.count(tx.GetHash())) { if (utxoset.count(utxod->first)) {
assert(CTransaction(tx).IsCoinBase()); assert(CTransaction(tx).IsCoinBase());
assert(duplicateids.count(tx.GetHash())); assert(duplicate_coins.count(utxod->first));
} }
disconnectedids.erase(tx.GetHash()); 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).GetHash(); 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;
assert(!CTransaction(tx).IsCoinBase()); assert(!CTransaction(tx).IsCoinBase());
} }
// In this simple test coins only have two states, spent or unspent, save the unspent state to restore // In this simple test coins only have two states, spent or unspent, save the unspent state to restore
oldcoins = result[prevouthash]; old_coin = result[prevout];
// Update the expected result of prevouthash to know these coins are spent // Update the expected result of prevouthash to know these coins are spent
result[prevouthash].Clear(); result[prevout].Clear();
utxoset.erase(prevouthash); utxoset.erase(prevout);
// The test is designed to ensure spending a duplicate coinbase will work properly // 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 that ever happens and not resurrect the 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.GetHash()].FromTx(tx, height); assert(tx.vout.size() == 1);
const COutPoint outpoint(tx.GetHash(), 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.GetHash()); 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.GetHash(),std::make_tuple(tx,undo,oldcoins))); utxoData.emplace(outpoint, std::make_tuple(tx,undo,old_coin));
} } else if (utxoset.size()) {
//1/20 times undo a previous transaction
auto utxod = FindRandomFrom(utxoset);
//1/20 times undo a previous transaction CTransaction &tx = std::get<0>(utxod->second);
else if (utxoset.size()) { CTxUndo &undo = std::get<1>(utxod->second);
TxData &txd = FindRandomFrom(utxoset); Coin &orig_coin = std::get<2>(utxod->second);
CTransaction &tx = std::get<0>(txd);
CTxUndo &undo = std::get<1>(txd);
CCoins &origcoins = std::get<2>(txd);
uint256 undohash = tx.GetHash();
// 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 CTxInUndo &undoin = undo.vprevout[0]; Coin coin = undo.vprevout[0];
ApplyTxInUndo(undoin, *(stack.back()), out); ApplyTxInUndo(std::move(coin), *(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 (std::map<uint256, CCoins>::iterator it = result.begin(); it != result.end(); it++) { for (auto it = result.begin(); it != result.end(); it++) {
const CCoins* coins = stack.back()->AccessCoins(it->first); bool have = stack.back()->HaveCoin(it->first);
if (coins) { const Coin& coin = stack.back()->AccessCoin(it->first);
BOOST_CHECK(*coins == it->second); BOOST_CHECK(have == !coin.IsSpent());
} else { BOOST_CHECK(coin == it->second);
BOOST_CHECK(it->second.IsPruned());
}
} }
} }
// 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) {
@ -433,53 +469,36 @@ BOOST_AUTO_TEST_CASE(updatecoins_simulation_test)
BOOST_AUTO_TEST_CASE(ccoins_serialization) BOOST_AUTO_TEST_CASE(ccoins_serialization)
{ {
// Good example // Good example
CDataStream ss1(ParseHex("0104835800816115944e077fe7c803cfa57f29b36bf87c1d358bb85e"), SER_DISK, CLIENT_VERSION); CDataStream ss1(ParseHex("97f23c835800816115944e077fe7c803cfa57f29b36bf87c1d35"), SER_DISK, CLIENT_VERSION);
CCoins cc1; Coin cc1;
ss1 >> cc1; ss1 >> cc1;
BOOST_CHECK_EQUAL(cc1.nVersion, 1);
BOOST_CHECK_EQUAL(cc1.fCoinBase, false); BOOST_CHECK_EQUAL(cc1.fCoinBase, false);
BOOST_CHECK_EQUAL(cc1.nHeight, 203998); BOOST_CHECK_EQUAL(cc1.nHeight, 203998);
BOOST_CHECK_EQUAL(cc1.vout.size(), 2); BOOST_CHECK_EQUAL(cc1.out.nValue, 60000000000ULL);
BOOST_CHECK_EQUAL(cc1.IsAvailable(0), false); BOOST_CHECK_EQUAL(HexStr(cc1.out.scriptPubKey), HexStr(GetScriptForDestination(CKeyID(uint160(ParseHex("816115944e077fe7c803cfa57f29b36bf87c1d35"))))));
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 // Good example
CDataStream ss2(ParseHex("0109044086ef97d5790061b01caab50f1b8e9c50a5057eb43c2d9563a4eebbd123008c988f1a4a4de2161e0f50aac7f17e7f9555caa486af3b"), SER_DISK, CLIENT_VERSION); CDataStream ss2(ParseHex("8ddf77bbd123008c988f1a4a4de2161e0f50aac7f17e7f9555caa4"), SER_DISK, CLIENT_VERSION);
CCoins cc2; Coin cc2;
ss2 >> cc2; ss2 >> cc2;
BOOST_CHECK_EQUAL(cc2.nVersion, 1);
BOOST_CHECK_EQUAL(cc2.fCoinBase, true); BOOST_CHECK_EQUAL(cc2.fCoinBase, true);
BOOST_CHECK_EQUAL(cc2.nHeight, 120891); BOOST_CHECK_EQUAL(cc2.nHeight, 120891);
BOOST_CHECK_EQUAL(cc2.vout.size(), 17); BOOST_CHECK_EQUAL(cc2.out.nValue, 110397);
for (int i = 0; i < 17; i++) { BOOST_CHECK_EQUAL(HexStr(cc2.out.scriptPubKey), HexStr(GetScriptForDestination(CKeyID(uint160(ParseHex("8c988f1a4a4de2161e0f50aac7f17e7f9555caa4"))))));
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 // Smallest possible example
CDataStream ssx(SER_DISK, CLIENT_VERSION); CDataStream ss3(ParseHex("000006"), SER_DISK, CLIENT_VERSION);
BOOST_CHECK_EQUAL(HexStr(ssx.begin(), ssx.end()), ""); Coin cc3;
CDataStream ss3(ParseHex("0002000600"), SER_DISK, CLIENT_VERSION);
CCoins cc3;
ss3 >> cc3; ss3 >> cc3;
BOOST_CHECK_EQUAL(cc3.nVersion, 0);
BOOST_CHECK_EQUAL(cc3.fCoinBase, false); BOOST_CHECK_EQUAL(cc3.fCoinBase, false);
BOOST_CHECK_EQUAL(cc3.nHeight, 0); BOOST_CHECK_EQUAL(cc3.nHeight, 0);
BOOST_CHECK_EQUAL(cc3.vout.size(), 1); BOOST_CHECK_EQUAL(cc3.out.nValue, 0);
BOOST_CHECK_EQUAL(cc3.IsAvailable(0), true); BOOST_CHECK_EQUAL(cc3.out.scriptPubKey.size(), 0);
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 // scriptPubKey that ends beyond the end of the stream
CDataStream ss4(ParseHex("0002000800"), SER_DISK, CLIENT_VERSION); CDataStream ss4(ParseHex("000007"), SER_DISK, CLIENT_VERSION);
try { try {
CCoins cc4; Coin cc4;
ss4 >> cc4; ss4 >> cc4;
BOOST_CHECK_MESSAGE(false, "We should have thrown"); BOOST_CHECK_MESSAGE(false, "We should have thrown");
} catch (const std::ios_base::failure& e) { } catch (const std::ios_base::failure& e) {
@ -490,16 +509,16 @@ BOOST_AUTO_TEST_CASE(ccoins_serialization)
uint64_t x = 3000000000ULL; uint64_t x = 3000000000ULL;
tmp << VARINT(x); tmp << VARINT(x);
BOOST_CHECK_EQUAL(HexStr(tmp.begin(), tmp.end()), "8a95c0bb00"); BOOST_CHECK_EQUAL(HexStr(tmp.begin(), tmp.end()), "8a95c0bb00");
CDataStream ss5(ParseHex("0002008a95c0bb0000"), SER_DISK, CLIENT_VERSION); CDataStream ss5(ParseHex("00008a95c0bb00"), SER_DISK, CLIENT_VERSION);
try { try {
CCoins cc5; Coin cc5;
ss5 >> cc5; ss5 >> cc5;
BOOST_CHECK_MESSAGE(false, "We should have thrown"); BOOST_CHECK_MESSAGE(false, "We should have thrown");
} catch (const std::ios_base::failure& e) { } catch (const std::ios_base::failure& e) {
} }
} }
const static uint256 TXID; const static COutPoint OUTPOINT;
const static CAmount PRUNED = -1; const static CAmount PRUNED = -1;
const static CAmount ABSENT = -2; const static CAmount ABSENT = -2;
const static CAmount FAIL = -3; const static CAmount FAIL = -3;
@ -514,15 +533,15 @@ const static auto FLAGS = {char(0), FRESH, DIRTY, char(DIRTY | FRESH)};
const static auto CLEAN_FLAGS = {char(0), FRESH}; const static auto CLEAN_FLAGS = {char(0), FRESH};
const static auto ABSENT_FLAGS = {NO_ENTRY}; const static auto ABSENT_FLAGS = {NO_ENTRY};
void SetCoinsValue(CAmount value, CCoins& coins) void SetCoinsValue(CAmount value, Coin& coin)
{ {
assert(value != ABSENT); assert(value != ABSENT);
coins.Clear(); coin.Clear();
assert(coins.IsPruned()); assert(coin.IsSpent());
if (value != PRUNED) { if (value != PRUNED) {
coins.vout.emplace_back(); coin.out.nValue = value;
coins.vout.back().nValue = value; coin.nHeight = 1;
assert(!coins.IsPruned()); assert(!coin.IsSpent());
} }
} }
@ -535,25 +554,23 @@ size_t InsertCoinsMapEntry(CCoinsMap& map, CAmount value, char flags)
assert(flags != NO_ENTRY); assert(flags != NO_ENTRY);
CCoinsCacheEntry entry; CCoinsCacheEntry entry;
entry.flags = flags; entry.flags = flags;
SetCoinsValue(value, entry.coins); SetCoinsValue(value, entry.coin);
auto inserted = map.emplace(TXID, std::move(entry)); auto inserted = map.emplace(OUTPOINT, std::move(entry));
assert(inserted.second); assert(inserted.second);
return inserted.first->second.coins.DynamicMemoryUsage(); return inserted.first->second.coin.DynamicMemoryUsage();
} }
void GetCoinsMapEntry(const CCoinsMap& map, CAmount& value, char& flags) void GetCoinsMapEntry(const CCoinsMap& map, CAmount& value, char& flags)
{ {
auto it = map.find(TXID); auto it = map.find(OUTPOINT);
if (it == map.end()) { if (it == map.end()) {
value = ABSENT; value = ABSENT;
flags = NO_ENTRY; flags = NO_ENTRY;
} else { } else {
if (it->second.coins.IsPruned()) { if (it->second.coin.IsSpent()) {
assert(it->second.coins.vout.size() == 0);
value = PRUNED; value = PRUNED;
} else { } else {
assert(it->second.coins.vout.size() == 1); value = it->second.coin.out.nValue;
value = it->second.coins.vout[0].nValue;
} }
flags = it->second.flags; flags = it->second.flags;
assert(flags != NO_ENTRY); assert(flags != NO_ENTRY);
@ -581,10 +598,10 @@ public:
CCoinsViewCacheTest cache{&base}; CCoinsViewCacheTest cache{&base};
}; };
void CheckAccessCoins(CAmount base_value, CAmount cache_value, CAmount expected_value, char cache_flags, char expected_flags) void CheckAccessCoin(CAmount base_value, CAmount cache_value, CAmount expected_value, char cache_flags, char expected_flags)
{ {
SingleEntryCacheTest test(base_value, cache_value, cache_flags); SingleEntryCacheTest test(base_value, cache_value, cache_flags);
test.cache.AccessCoins(TXID); test.cache.AccessCoin(OUTPOINT);
test.cache.SelfTest(); test.cache.SelfTest();
CAmount result_value; CAmount result_value;
@ -603,39 +620,39 @@ BOOST_AUTO_TEST_CASE(ccoins_access)
* Base Cache Result Cache Result * Base Cache Result Cache Result
* Value Value Value Flags Flags * Value Value Value Flags Flags
*/ */
CheckAccessCoins(ABSENT, ABSENT, ABSENT, NO_ENTRY , NO_ENTRY ); CheckAccessCoin(ABSENT, ABSENT, ABSENT, NO_ENTRY , NO_ENTRY );
CheckAccessCoins(ABSENT, PRUNED, PRUNED, 0 , 0 ); CheckAccessCoin(ABSENT, PRUNED, PRUNED, 0 , 0 );
CheckAccessCoins(ABSENT, PRUNED, PRUNED, FRESH , FRESH ); CheckAccessCoin(ABSENT, PRUNED, PRUNED, FRESH , FRESH );
CheckAccessCoins(ABSENT, PRUNED, PRUNED, DIRTY , DIRTY ); CheckAccessCoin(ABSENT, PRUNED, PRUNED, DIRTY , DIRTY );
CheckAccessCoins(ABSENT, PRUNED, PRUNED, DIRTY|FRESH, DIRTY|FRESH); CheckAccessCoin(ABSENT, PRUNED, PRUNED, DIRTY|FRESH, DIRTY|FRESH);
CheckAccessCoins(ABSENT, VALUE2, VALUE2, 0 , 0 ); CheckAccessCoin(ABSENT, VALUE2, VALUE2, 0 , 0 );
CheckAccessCoins(ABSENT, VALUE2, VALUE2, FRESH , FRESH ); CheckAccessCoin(ABSENT, VALUE2, VALUE2, FRESH , FRESH );
CheckAccessCoins(ABSENT, VALUE2, VALUE2, DIRTY , DIRTY ); CheckAccessCoin(ABSENT, VALUE2, VALUE2, DIRTY , DIRTY );
CheckAccessCoins(ABSENT, VALUE2, VALUE2, DIRTY|FRESH, DIRTY|FRESH); CheckAccessCoin(ABSENT, VALUE2, VALUE2, DIRTY|FRESH, DIRTY|FRESH);
CheckAccessCoins(PRUNED, ABSENT, PRUNED, NO_ENTRY , FRESH ); CheckAccessCoin(PRUNED, ABSENT, PRUNED, NO_ENTRY , FRESH );
CheckAccessCoins(PRUNED, PRUNED, PRUNED, 0 , 0 ); CheckAccessCoin(PRUNED, PRUNED, PRUNED, 0 , 0 );
CheckAccessCoins(PRUNED, PRUNED, PRUNED, FRESH , FRESH ); CheckAccessCoin(PRUNED, PRUNED, PRUNED, FRESH , FRESH );
CheckAccessCoins(PRUNED, PRUNED, PRUNED, DIRTY , DIRTY ); CheckAccessCoin(PRUNED, PRUNED, PRUNED, DIRTY , DIRTY );
CheckAccessCoins(PRUNED, PRUNED, PRUNED, DIRTY|FRESH, DIRTY|FRESH); CheckAccessCoin(PRUNED, PRUNED, PRUNED, DIRTY|FRESH, DIRTY|FRESH);
CheckAccessCoins(PRUNED, VALUE2, VALUE2, 0 , 0 ); CheckAccessCoin(PRUNED, VALUE2, VALUE2, 0 , 0 );
CheckAccessCoins(PRUNED, VALUE2, VALUE2, FRESH , FRESH ); CheckAccessCoin(PRUNED, VALUE2, VALUE2, FRESH , FRESH );
CheckAccessCoins(PRUNED, VALUE2, VALUE2, DIRTY , DIRTY ); CheckAccessCoin(PRUNED, VALUE2, VALUE2, DIRTY , DIRTY );
CheckAccessCoins(PRUNED, VALUE2, VALUE2, DIRTY|FRESH, DIRTY|FRESH); CheckAccessCoin(PRUNED, VALUE2, VALUE2, DIRTY|FRESH, DIRTY|FRESH);
CheckAccessCoins(VALUE1, ABSENT, VALUE1, NO_ENTRY , 0 ); CheckAccessCoin(VALUE1, ABSENT, VALUE1, NO_ENTRY , 0 );
CheckAccessCoins(VALUE1, PRUNED, PRUNED, 0 , 0 ); CheckAccessCoin(VALUE1, PRUNED, PRUNED, 0 , 0 );
CheckAccessCoins(VALUE1, PRUNED, PRUNED, FRESH , FRESH ); CheckAccessCoin(VALUE1, PRUNED, PRUNED, FRESH , FRESH );
CheckAccessCoins(VALUE1, PRUNED, PRUNED, DIRTY , DIRTY ); CheckAccessCoin(VALUE1, PRUNED, PRUNED, DIRTY , DIRTY );
CheckAccessCoins(VALUE1, PRUNED, PRUNED, DIRTY|FRESH, DIRTY|FRESH); CheckAccessCoin(VALUE1, PRUNED, PRUNED, DIRTY|FRESH, DIRTY|FRESH);
CheckAccessCoins(VALUE1, VALUE2, VALUE2, 0 , 0 ); CheckAccessCoin(VALUE1, VALUE2, VALUE2, 0 , 0 );
CheckAccessCoins(VALUE1, VALUE2, VALUE2, FRESH , FRESH ); CheckAccessCoin(VALUE1, VALUE2, VALUE2, FRESH , FRESH );
CheckAccessCoins(VALUE1, VALUE2, VALUE2, DIRTY , DIRTY ); CheckAccessCoin(VALUE1, VALUE2, VALUE2, DIRTY , DIRTY );
CheckAccessCoins(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) void CheckSpendCoins(CAmount base_value, CAmount cache_value, CAmount expected_value, char cache_flags, char expected_flags)
{ {
SingleEntryCacheTest test(base_value, cache_value, cache_flags); SingleEntryCacheTest test(base_value, cache_value, cache_flags);
SetCoinsValue(modify_value, *test.cache.ModifyCoins(TXID)); test.cache.SpendCoin(OUTPOINT);
test.cache.SelfTest(); test.cache.SelfTest();
CAmount result_value; CAmount result_value;
@ -645,79 +662,55 @@ void CheckModifyCoins(CAmount base_value, CAmount cache_value, CAmount modify_va
BOOST_CHECK_EQUAL(result_flags, expected_flags); BOOST_CHECK_EQUAL(result_flags, expected_flags);
}; };
BOOST_AUTO_TEST_CASE(ccoins_modify) BOOST_AUTO_TEST_CASE(ccoins_spend)
{ {
/* Check ModifyCoin behavior, requesting a coin from a cache view layered on /* Check SpendCoin behavior, requesting a coin from a cache view layered on
* top of a base view, writing a modification to the coin, and then checking * top of a base view, spending, and then checking
* the resulting entry in the cache after the modification. * the resulting entry in the cache after the modification.
* *
* Base Cache Write Result Cache Result * Base Cache Result Cache Result
* Value Value Value Value Flags Flags * Value Value Value Flags Flags
*/ */
CheckModifyCoins(ABSENT, ABSENT, PRUNED, ABSENT, NO_ENTRY , NO_ENTRY ); CheckSpendCoins(ABSENT, ABSENT, ABSENT, NO_ENTRY , NO_ENTRY );
CheckModifyCoins(ABSENT, ABSENT, VALUE3, VALUE3, NO_ENTRY , DIRTY|FRESH); CheckSpendCoins(ABSENT, PRUNED, PRUNED, 0 , DIRTY );
CheckModifyCoins(ABSENT, PRUNED, PRUNED, PRUNED, 0 , DIRTY ); CheckSpendCoins(ABSENT, PRUNED, ABSENT, FRESH , NO_ENTRY );
CheckModifyCoins(ABSENT, PRUNED, PRUNED, ABSENT, FRESH , NO_ENTRY ); CheckSpendCoins(ABSENT, PRUNED, PRUNED, DIRTY , DIRTY );
CheckModifyCoins(ABSENT, PRUNED, PRUNED, PRUNED, DIRTY , DIRTY ); CheckSpendCoins(ABSENT, PRUNED, ABSENT, DIRTY|FRESH, NO_ENTRY );
CheckModifyCoins(ABSENT, PRUNED, PRUNED, ABSENT, DIRTY|FRESH, NO_ENTRY ); CheckSpendCoins(ABSENT, VALUE2, PRUNED, 0 , DIRTY );
CheckModifyCoins(ABSENT, PRUNED, VALUE3, VALUE3, 0 , DIRTY ); CheckSpendCoins(ABSENT, VALUE2, ABSENT, FRESH , NO_ENTRY );
CheckModifyCoins(ABSENT, PRUNED, VALUE3, VALUE3, FRESH , DIRTY|FRESH); CheckSpendCoins(ABSENT, VALUE2, PRUNED, DIRTY , DIRTY );
CheckModifyCoins(ABSENT, PRUNED, VALUE3, VALUE3, DIRTY , DIRTY ); CheckSpendCoins(ABSENT, VALUE2, ABSENT, DIRTY|FRESH, NO_ENTRY );
CheckModifyCoins(ABSENT, PRUNED, VALUE3, VALUE3, DIRTY|FRESH, DIRTY|FRESH); CheckSpendCoins(PRUNED, ABSENT, ABSENT, NO_ENTRY , NO_ENTRY );
CheckModifyCoins(ABSENT, VALUE2, PRUNED, PRUNED, 0 , DIRTY ); CheckSpendCoins(PRUNED, PRUNED, PRUNED, 0 , DIRTY );
CheckModifyCoins(ABSENT, VALUE2, PRUNED, ABSENT, FRESH , NO_ENTRY ); CheckSpendCoins(PRUNED, PRUNED, ABSENT, FRESH , NO_ENTRY );
CheckModifyCoins(ABSENT, VALUE2, PRUNED, PRUNED, DIRTY , DIRTY ); CheckSpendCoins(PRUNED, PRUNED, PRUNED, DIRTY , DIRTY );
CheckModifyCoins(ABSENT, VALUE2, PRUNED, ABSENT, DIRTY|FRESH, NO_ENTRY ); CheckSpendCoins(PRUNED, PRUNED, ABSENT, DIRTY|FRESH, NO_ENTRY );
CheckModifyCoins(ABSENT, VALUE2, VALUE3, VALUE3, 0 , DIRTY ); CheckSpendCoins(PRUNED, VALUE2, PRUNED, 0 , DIRTY );
CheckModifyCoins(ABSENT, VALUE2, VALUE3, VALUE3, FRESH , DIRTY|FRESH); CheckSpendCoins(PRUNED, VALUE2, ABSENT, FRESH , NO_ENTRY );
CheckModifyCoins(ABSENT, VALUE2, VALUE3, VALUE3, DIRTY , DIRTY ); CheckSpendCoins(PRUNED, VALUE2, PRUNED, DIRTY , DIRTY );
CheckModifyCoins(ABSENT, VALUE2, VALUE3, VALUE3, DIRTY|FRESH, DIRTY|FRESH); CheckSpendCoins(PRUNED, VALUE2, ABSENT, DIRTY|FRESH, NO_ENTRY );
CheckModifyCoins(PRUNED, ABSENT, PRUNED, ABSENT, NO_ENTRY , NO_ENTRY ); CheckSpendCoins(VALUE1, ABSENT, PRUNED, NO_ENTRY , DIRTY );
CheckModifyCoins(PRUNED, ABSENT, VALUE3, VALUE3, NO_ENTRY , DIRTY|FRESH); CheckSpendCoins(VALUE1, PRUNED, PRUNED, 0 , DIRTY );
CheckModifyCoins(PRUNED, PRUNED, PRUNED, PRUNED, 0 , DIRTY ); CheckSpendCoins(VALUE1, PRUNED, ABSENT, FRESH , NO_ENTRY );
CheckModifyCoins(PRUNED, PRUNED, PRUNED, ABSENT, FRESH , NO_ENTRY ); CheckSpendCoins(VALUE1, PRUNED, PRUNED, DIRTY , DIRTY );
CheckModifyCoins(PRUNED, PRUNED, PRUNED, PRUNED, DIRTY , DIRTY ); CheckSpendCoins(VALUE1, PRUNED, ABSENT, DIRTY|FRESH, NO_ENTRY );
CheckModifyCoins(PRUNED, PRUNED, PRUNED, ABSENT, DIRTY|FRESH, NO_ENTRY ); CheckSpendCoins(VALUE1, VALUE2, PRUNED, 0 , DIRTY );
CheckModifyCoins(PRUNED, PRUNED, VALUE3, VALUE3, 0 , DIRTY ); CheckSpendCoins(VALUE1, VALUE2, ABSENT, FRESH , NO_ENTRY );
CheckModifyCoins(PRUNED, PRUNED, VALUE3, VALUE3, FRESH , DIRTY|FRESH); CheckSpendCoins(VALUE1, VALUE2, PRUNED, DIRTY , DIRTY );
CheckModifyCoins(PRUNED, PRUNED, VALUE3, VALUE3, DIRTY , DIRTY ); CheckSpendCoins(VALUE1, VALUE2, ABSENT, DIRTY|FRESH, NO_ENTRY );
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 CheckModifyNewCoinsBase(CAmount base_value, CAmount cache_value, CAmount modify_value, CAmount expected_value, char cache_flags, char expected_flags, bool coinbase) void CheckAddCoinBase(CAmount base_value, CAmount cache_value, CAmount modify_value, CAmount expected_value, char cache_flags, char expected_flags, bool coinbase)
{ {
SingleEntryCacheTest test(base_value, cache_value, cache_flags); SingleEntryCacheTest test(base_value, cache_value, cache_flags);
CAmount result_value; CAmount result_value;
char result_flags; char result_flags;
try { try {
SetCoinsValue(modify_value, *test.cache.ModifyNewCoins(TXID, coinbase)); CTxOut output;
output.nValue = modify_value;
test.cache.AddCoin(OUTPOINT, Coin(std::move(output), 1, coinbase), coinbase);
test.cache.SelfTest();
GetCoinsMapEntry(test.cache.map(), result_value, result_flags); GetCoinsMapEntry(test.cache.map(), result_value, result_flags);
} catch (std::logic_error& e) { } catch (std::logic_error& e) {
result_value = FAIL; result_value = FAIL;
@ -728,64 +721,46 @@ void CheckModifyNewCoinsBase(CAmount base_value, CAmount cache_value, CAmount mo
BOOST_CHECK_EQUAL(result_flags, expected_flags); BOOST_CHECK_EQUAL(result_flags, expected_flags);
} }
// Simple wrapper for CheckModifyNewCoinsBase function above that loops through // Simple wrapper for CheckAddCoinBase function above that loops through
// different possible base_values, making sure each one gives the same results. // different possible base_values, making sure each one gives the same results.
// This wrapper lets the modify_new test below be shorter and less repetitive, // This wrapper lets the coins_add test below be shorter and less repetitive,
// while still verifying that the CoinsViewCache::ModifyNewCoins implementation // while still verifying that the CoinsViewCache::AddCoin implementation
// ignores base values. // ignores base values.
template <typename... Args> template <typename... Args>
void CheckModifyNewCoins(Args&&... args) void CheckAddCoin(Args&&... args)
{ {
for (CAmount base_value : {ABSENT, PRUNED, VALUE1}) for (CAmount base_value : {ABSENT, PRUNED, VALUE1})
CheckModifyNewCoinsBase(base_value, std::forward<Args>(args)...); CheckAddCoinBase(base_value, std::forward<Args>(args)...);
} }
BOOST_AUTO_TEST_CASE(ccoins_modify_new) BOOST_AUTO_TEST_CASE(ccoins_add)
{ {
/* Check ModifyNewCoin behavior, requesting a new coin from a cache view, /* Check AddCoin behavior, requesting a new coin from a cache view,
* writing a modification to the coin, and then checking the resulting * writing a modification to the coin, and then checking the resulting
* entry in the cache after the modification. Verify behavior with the * entry in the cache after the modification. Verify behavior with the
* with the ModifyNewCoin coinbase argument set to false, and to true. * with the AddCoin potential_overwrite argument set to false, and to true.
* *
* Cache Write Result Cache Result Coinbase * Cache Write Result Cache Result potential_overwrite
* Value Value Value Flags Flags * Value Value Value Flags Flags
*/ */
CheckModifyNewCoins(ABSENT, PRUNED, ABSENT, NO_ENTRY , NO_ENTRY , false); CheckAddCoin(ABSENT, VALUE3, VALUE3, NO_ENTRY , DIRTY|FRESH, false);
CheckModifyNewCoins(ABSENT, PRUNED, PRUNED, NO_ENTRY , DIRTY , true ); CheckAddCoin(ABSENT, VALUE3, VALUE3, NO_ENTRY , DIRTY , true );
CheckModifyNewCoins(ABSENT, VALUE3, VALUE3, NO_ENTRY , DIRTY|FRESH, false); CheckAddCoin(PRUNED, VALUE3, VALUE3, 0 , DIRTY|FRESH, false);
CheckModifyNewCoins(ABSENT, VALUE3, VALUE3, NO_ENTRY , DIRTY , true ); CheckAddCoin(PRUNED, VALUE3, VALUE3, 0 , DIRTY , true );
CheckModifyNewCoins(PRUNED, PRUNED, ABSENT, 0 , NO_ENTRY , false); CheckAddCoin(PRUNED, VALUE3, VALUE3, FRESH , DIRTY|FRESH, false);
CheckModifyNewCoins(PRUNED, PRUNED, PRUNED, 0 , DIRTY , true ); CheckAddCoin(PRUNED, VALUE3, VALUE3, FRESH , DIRTY|FRESH, true );
CheckModifyNewCoins(PRUNED, PRUNED, ABSENT, FRESH , NO_ENTRY , false); CheckAddCoin(PRUNED, VALUE3, VALUE3, DIRTY , DIRTY , false);
CheckModifyNewCoins(PRUNED, PRUNED, ABSENT, FRESH , NO_ENTRY , true ); CheckAddCoin(PRUNED, VALUE3, VALUE3, DIRTY , DIRTY , true );
CheckModifyNewCoins(PRUNED, PRUNED, PRUNED, DIRTY , DIRTY , false); CheckAddCoin(PRUNED, VALUE3, VALUE3, DIRTY|FRESH, DIRTY|FRESH, false);
CheckModifyNewCoins(PRUNED, PRUNED, PRUNED, DIRTY , DIRTY , true ); CheckAddCoin(PRUNED, VALUE3, VALUE3, DIRTY|FRESH, DIRTY|FRESH, true );
CheckModifyNewCoins(PRUNED, PRUNED, ABSENT, DIRTY|FRESH, NO_ENTRY , false); CheckAddCoin(VALUE2, VALUE3, FAIL , 0 , NO_ENTRY , false);
CheckModifyNewCoins(PRUNED, PRUNED, ABSENT, DIRTY|FRESH, NO_ENTRY , true ); CheckAddCoin(VALUE2, VALUE3, VALUE3, 0 , DIRTY , true );
CheckModifyNewCoins(PRUNED, VALUE3, VALUE3, 0 , DIRTY|FRESH, false); CheckAddCoin(VALUE2, VALUE3, FAIL , FRESH , NO_ENTRY , false);
CheckModifyNewCoins(PRUNED, VALUE3, VALUE3, 0 , DIRTY , true ); CheckAddCoin(VALUE2, VALUE3, VALUE3, FRESH , DIRTY|FRESH, true );
CheckModifyNewCoins(PRUNED, VALUE3, VALUE3, FRESH , DIRTY|FRESH, false); CheckAddCoin(VALUE2, VALUE3, FAIL , DIRTY , NO_ENTRY , false);
CheckModifyNewCoins(PRUNED, VALUE3, VALUE3, FRESH , DIRTY|FRESH, true ); CheckAddCoin(VALUE2, VALUE3, VALUE3, DIRTY , DIRTY , true );
CheckModifyNewCoins(PRUNED, VALUE3, VALUE3, DIRTY , DIRTY , false); CheckAddCoin(VALUE2, VALUE3, FAIL , DIRTY|FRESH, NO_ENTRY , false);
CheckModifyNewCoins(PRUNED, VALUE3, VALUE3, DIRTY , DIRTY , true ); CheckAddCoin(VALUE2, VALUE3, VALUE3, DIRTY|FRESH, DIRTY|FRESH, true );
CheckModifyNewCoins(PRUNED, VALUE3, VALUE3, DIRTY|FRESH, DIRTY|FRESH, false);
CheckModifyNewCoins(PRUNED, VALUE3, VALUE3, DIRTY|FRESH, DIRTY|FRESH, true );
CheckModifyNewCoins(VALUE2, PRUNED, FAIL , 0 , NO_ENTRY , false);
CheckModifyNewCoins(VALUE2, PRUNED, PRUNED, 0 , DIRTY , true );
CheckModifyNewCoins(VALUE2, PRUNED, FAIL , FRESH , NO_ENTRY , false);
CheckModifyNewCoins(VALUE2, PRUNED, ABSENT, FRESH , NO_ENTRY , true );
CheckModifyNewCoins(VALUE2, PRUNED, FAIL , DIRTY , NO_ENTRY , false);
CheckModifyNewCoins(VALUE2, PRUNED, PRUNED, DIRTY , DIRTY , true );
CheckModifyNewCoins(VALUE2, PRUNED, FAIL , DIRTY|FRESH, NO_ENTRY , false);
CheckModifyNewCoins(VALUE2, PRUNED, ABSENT, DIRTY|FRESH, NO_ENTRY , true );
CheckModifyNewCoins(VALUE2, VALUE3, FAIL , 0 , NO_ENTRY , false);
CheckModifyNewCoins(VALUE2, VALUE3, VALUE3, 0 , DIRTY , true );
CheckModifyNewCoins(VALUE2, VALUE3, FAIL , FRESH , NO_ENTRY , false);
CheckModifyNewCoins(VALUE2, VALUE3, VALUE3, FRESH , DIRTY|FRESH, true );
CheckModifyNewCoins(VALUE2, VALUE3, FAIL , DIRTY , NO_ENTRY , false);
CheckModifyNewCoins(VALUE2, VALUE3, VALUE3, DIRTY , DIRTY , true );
CheckModifyNewCoins(VALUE2, VALUE3, FAIL , DIRTY|FRESH, NO_ENTRY , false);
CheckModifyNewCoins(VALUE2, VALUE3, VALUE3, DIRTY|FRESH, DIRTY|FRESH, true );
} }
void CheckWriteCoins(CAmount parent_value, CAmount child_value, CAmount expected_value, char parent_flags, char child_flags, char expected_flags) void CheckWriteCoins(CAmount parent_value, CAmount child_value, CAmount expected_value, char parent_flags, char child_flags, char expected_flags)

View File

@ -128,6 +128,23 @@ BOOST_AUTO_TEST_CASE(siphash)
tx.nVersion = 1; tx.nVersion = 1;
ss << tx; ss << tx;
BOOST_CHECK_EQUAL(SipHashUint256(1, 2, ss.GetHash()), 0x79751e980c2a0a35ULL); BOOST_CHECK_EQUAL(SipHashUint256(1, 2, ss.GetHash()), 0x79751e980c2a0a35ULL);
// Check consistency between CSipHasher and SipHashUint256[Extra].
FastRandomContext ctx;
for (int i = 0; i < 16; ++i) {
uint64_t k1 = ctx.rand64();
uint64_t k2 = ctx.rand64();
uint256 x = GetRandHash();
uint32_t n = ctx.rand32();
uint8_t nb[4];
WriteLE32(nb, n);
CSipHasher sip256(k1, k2);
sip256.Write(x.begin(), 32);
CSipHasher sip288 = sip256;
sip288.Write(nb, 4);
BOOST_CHECK_EQUAL(SipHashUint256(k1, k2, x), sip256.Finalize());
BOOST_CHECK_EQUAL(SipHashUint256Extra(k1, k2, x, n), sip288.Finalize());
}
} }
BOOST_AUTO_TEST_SUITE_END() BOOST_AUTO_TEST_SUITE_END()

View File

@ -112,7 +112,8 @@ BOOST_AUTO_TEST_CASE(sign)
{ {
CScript sigSave = txTo[i].vin[0].scriptSig; CScript sigSave = txTo[i].vin[0].scriptSig;
txTo[i].vin[0].scriptSig = txTo[j].vin[0].scriptSig; txTo[i].vin[0].scriptSig = txTo[j].vin[0].scriptSig;
bool sigOK = CScriptCheck(CCoins(txFrom, 0), txTo[i], 0, SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_STRICTENC, false, &txdata)(); const CTxOut& output = txFrom.vout[txTo[i].vin[0].prevout.n];
bool sigOK = CScriptCheck(output.scriptPubKey, output.nValue, txTo[i], 0, SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_STRICTENC, false, &txdata)();
if (i == j) if (i == j)
BOOST_CHECK_MESSAGE(sigOK, strprintf("VerifySignature %d %d", i, j)); BOOST_CHECK_MESSAGE(sigOK, strprintf("VerifySignature %d %d", i, j));
else else
@ -316,7 +317,7 @@ BOOST_AUTO_TEST_CASE(AreInputsStandard)
txFrom.vout[6].scriptPubKey = GetScriptForDestination(CScriptID(twentySigops)); txFrom.vout[6].scriptPubKey = GetScriptForDestination(CScriptID(twentySigops));
txFrom.vout[6].nValue = 6000; txFrom.vout[6].nValue = 6000;
coins.ModifyCoins(txFrom.GetHash())->FromTx(txFrom, 0); AddCoins(coins, txFrom, 0);
CMutableTransaction txTo; CMutableTransaction txTo;
txTo.vout.resize(1); txTo.vout.resize(1);

View File

@ -102,7 +102,7 @@ void BuildTxs(CMutableTransaction& spendingTx, CCoinsViewCache& coins, CMutableT
spendingTx.vout[0].nValue = 1; spendingTx.vout[0].nValue = 1;
spendingTx.vout[0].scriptPubKey = CScript(); spendingTx.vout[0].scriptPubKey = CScript();
coins.ModifyCoins(creationTx.GetHash())->FromTx(creationTx, 0); AddCoins(coins, creationTx, 0);
} }
BOOST_AUTO_TEST_CASE(GetTxSigOpCost) BOOST_AUTO_TEST_CASE(GetTxSigOpCost)

View File

@ -168,8 +168,8 @@ int do_fuzz()
{ {
try try
{ {
CCoins block; Coin coin;
ds >> block; ds >> coin;
} catch (const std::ios_base::failure& e) {return 0;} } catch (const std::ios_base::failure& e) {return 0;}
break; break;
} }

View File

@ -307,14 +307,14 @@ SetupDummyInputs(CBasicKeyStore& keystoreRet, CCoinsViewCache& coinsRet)
dummyTransactions[0].vout[0].scriptPubKey << ToByteVector(key[0].GetPubKey()) << OP_CHECKSIG; dummyTransactions[0].vout[0].scriptPubKey << ToByteVector(key[0].GetPubKey()) << OP_CHECKSIG;
dummyTransactions[0].vout[1].nValue = 50*CENT; dummyTransactions[0].vout[1].nValue = 50*CENT;
dummyTransactions[0].vout[1].scriptPubKey << ToByteVector(key[1].GetPubKey()) << OP_CHECKSIG; dummyTransactions[0].vout[1].scriptPubKey << ToByteVector(key[1].GetPubKey()) << OP_CHECKSIG;
coinsRet.ModifyCoins(dummyTransactions[0].GetHash())->FromTx(dummyTransactions[0], 0); AddCoins(coinsRet, dummyTransactions[0], 0);
dummyTransactions[1].vout.resize(2); dummyTransactions[1].vout.resize(2);
dummyTransactions[1].vout[0].nValue = 21*CENT; dummyTransactions[1].vout[0].nValue = 21*CENT;
dummyTransactions[1].vout[0].scriptPubKey = GetScriptForDestination(key[2].GetPubKey().GetID()); dummyTransactions[1].vout[0].scriptPubKey = GetScriptForDestination(key[2].GetPubKey().GetID());
dummyTransactions[1].vout[1].nValue = 22*CENT; dummyTransactions[1].vout[1].nValue = 22*CENT;
dummyTransactions[1].vout[1].scriptPubKey = GetScriptForDestination(key[3].GetPubKey().GetID()); dummyTransactions[1].vout[1].scriptPubKey = GetScriptForDestination(key[3].GetPubKey().GetID());
coinsRet.ModifyCoins(dummyTransactions[1].GetHash())->FromTx(dummyTransactions[1], 0); AddCoins(coinsRet, dummyTransactions[1], 0);
return dummyTransactions; return dummyTransactions;
} }
@ -470,19 +470,20 @@ BOOST_AUTO_TEST_CASE(test_big_witness_transaction) {
for (int i=0; i<20; i++) for (int i=0; i<20; i++)
threadGroup.create_thread(boost::bind(&CCheckQueue<CScriptCheck>::Thread, boost::ref(scriptcheckqueue))); threadGroup.create_thread(boost::bind(&CCheckQueue<CScriptCheck>::Thread, boost::ref(scriptcheckqueue)));
CCoins coins; std::vector<Coin> coins;
coins.nVersion = 1;
coins.fCoinBase = false;
for(uint32_t i = 0; i < mtx.vin.size(); i++) { for(uint32_t i = 0; i < mtx.vin.size(); i++) {
CTxOut txout; Coin coin;
txout.nValue = 1000; coin.nHeight = 1;
txout.scriptPubKey = scriptPubKey; coin.fCoinBase = false;
coins.vout.push_back(txout); coin.out.nValue = 1000;
coin.out.scriptPubKey = scriptPubKey;
coins.emplace_back(std::move(coin));
} }
for(uint32_t i = 0; i < mtx.vin.size(); i++) { for(uint32_t i = 0; i < mtx.vin.size(); i++) {
std::vector<CScriptCheck> vChecks; std::vector<CScriptCheck> vChecks;
CScriptCheck check(coins, tx, i, SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_WITNESS, false, &txdata); const CTxOut& output = coins[tx.vin[i].prevout.n].out;
CScriptCheck check(output.scriptPubKey, output.nValue, tx, i, SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_WITNESS, false, &txdata);
vChecks.push_back(CScriptCheck()); vChecks.push_back(CScriptCheck());
check.swap(vChecks.back()); check.swap(vChecks.back());
control.Add(vChecks); control.Add(vChecks);

View File

@ -14,6 +14,7 @@
#include <boost/thread.hpp> #include <boost/thread.hpp>
static const char DB_COIN = 'C';
static const char DB_COINS = 'c'; static const char DB_COINS = 'c';
static const char DB_BLOCK_FILES = 'f'; static const char DB_BLOCK_FILES = 'f';
static const char DB_TXINDEX = 't'; static const char DB_TXINDEX = 't';
@ -24,17 +25,40 @@ static const char DB_FLAG = 'F';
static const char DB_REINDEX_FLAG = 'R'; static const char DB_REINDEX_FLAG = 'R';
static const char DB_LAST_BLOCK = 'l'; static const char DB_LAST_BLOCK = 'l';
namespace {
struct CoinEntry {
COutPoint* outpoint;
char key;
CoinEntry(const COutPoint* ptr) : outpoint(const_cast<COutPoint*>(ptr)), key(DB_COIN) {}
template<typename Stream>
void Serialize(Stream &s) const {
s << key;
s << outpoint->hash;
s << VARINT(outpoint->n);
}
template<typename Stream>
void Unserialize(Stream& s) {
s >> key;
s >> outpoint->hash;
s >> VARINT(outpoint->n);
}
};
}
CCoinsViewDB::CCoinsViewDB(size_t nCacheSize, bool fMemory, bool fWipe) : db(GetDataDir() / "chainstate", nCacheSize, fMemory, fWipe, true) CCoinsViewDB::CCoinsViewDB(size_t nCacheSize, bool fMemory, bool fWipe) : db(GetDataDir() / "chainstate", nCacheSize, fMemory, fWipe, true)
{ {
} }
bool CCoinsViewDB::GetCoins(const uint256 &txid, CCoins &coins) const { bool CCoinsViewDB::GetCoin(const COutPoint &outpoint, Coin &coin) const {
return db.Read(std::make_pair(DB_COINS, txid), coins); return db.Read(CoinEntry(&outpoint), coin);
} }
bool CCoinsViewDB::HaveCoins(const uint256 &txid) const { bool CCoinsViewDB::HaveCoin(const COutPoint &outpoint) const {
return db.Exists(std::make_pair(DB_COINS, txid)); return db.Exists(CoinEntry(&outpoint));
} }
uint256 CCoinsViewDB::GetBestBlock() const { uint256 CCoinsViewDB::GetBestBlock() const {
@ -50,10 +74,11 @@ bool CCoinsViewDB::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock) {
size_t changed = 0; size_t changed = 0;
for (CCoinsMap::iterator it = mapCoins.begin(); it != mapCoins.end();) { for (CCoinsMap::iterator it = mapCoins.begin(); it != mapCoins.end();) {
if (it->second.flags & CCoinsCacheEntry::DIRTY) { if (it->second.flags & CCoinsCacheEntry::DIRTY) {
if (it->second.coins.IsPruned()) CoinEntry entry(&it->first);
batch.Erase(std::make_pair(DB_COINS, it->first)); if (it->second.coin.IsSpent())
batch.Erase(entry);
else else
batch.Write(std::make_pair(DB_COINS, it->first), it->second.coins); batch.Write(entry, it->second.coin);
changed++; changed++;
} }
count++; count++;
@ -63,8 +88,14 @@ bool CCoinsViewDB::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock) {
if (!hashBlock.IsNull()) if (!hashBlock.IsNull())
batch.Write(DB_BEST_BLOCK, hashBlock); batch.Write(DB_BEST_BLOCK, hashBlock);
LogPrint(BCLog::COINDB, "Committing %u changed transactions (out of %u) to coin database...\n", (unsigned int)changed, (unsigned int)count); bool ret = db.WriteBatch(batch);
return db.WriteBatch(batch); LogPrint(BCLog::COINDB, "Committed %u changed transaction outputs (out of %u) to coin database...\n", (unsigned int)changed, (unsigned int)count);
return ret;
}
size_t CCoinsViewDB::EstimateSize() const
{
return db.EstimateSize(DB_COIN, (char)(DB_COIN+1));
} }
CBlockTreeDB::CBlockTreeDB(size_t nCacheSize, bool fMemory, bool fWipe) : CDBWrapper(GetDataDir() / "blocks" / "index", nCacheSize, fMemory, fWipe) { CBlockTreeDB::CBlockTreeDB(size_t nCacheSize, bool fMemory, bool fWipe) : CDBWrapper(GetDataDir() / "blocks" / "index", nCacheSize, fMemory, fWipe) {
@ -96,29 +127,31 @@ CCoinsViewCursor *CCoinsViewDB::Cursor() const
/* It seems that there are no "const iterators" for LevelDB. Since we /* It seems that there are no "const iterators" for LevelDB. Since we
only need read operations on it, use a const-cast to get around only need read operations on it, use a const-cast to get around
that restriction. */ that restriction. */
i->pcursor->Seek(DB_COINS); i->pcursor->Seek(DB_COIN);
// Cache key of first record // Cache key of first record
if (i->pcursor->Valid()) { if (i->pcursor->Valid()) {
i->pcursor->GetKey(i->keyTmp); CoinEntry entry(&i->keyTmp.second);
i->pcursor->GetKey(entry);
i->keyTmp.first = entry.key;
} else { } else {
i->keyTmp.first = 0; // Make sure Valid() and GetKey() return false i->keyTmp.first = 0; // Make sure Valid() and GetKey() return false
} }
return i; return i;
} }
bool CCoinsViewDBCursor::GetKey(uint256 &key) const bool CCoinsViewDBCursor::GetKey(COutPoint &key) const
{ {
// Return cached key // Return cached key
if (keyTmp.first == DB_COINS) { if (keyTmp.first == DB_COIN) {
key = keyTmp.second; key = keyTmp.second;
return true; return true;
} }
return false; return false;
} }
bool CCoinsViewDBCursor::GetValue(CCoins &coins) const bool CCoinsViewDBCursor::GetValue(Coin &coin) const
{ {
return pcursor->GetValue(coins); return pcursor->GetValue(coin);
} }
unsigned int CCoinsViewDBCursor::GetValueSize() const unsigned int CCoinsViewDBCursor::GetValueSize() const
@ -128,14 +161,18 @@ unsigned int CCoinsViewDBCursor::GetValueSize() const
bool CCoinsViewDBCursor::Valid() const bool CCoinsViewDBCursor::Valid() const
{ {
return keyTmp.first == DB_COINS; return keyTmp.first == DB_COIN;
} }
void CCoinsViewDBCursor::Next() void CCoinsViewDBCursor::Next()
{ {
pcursor->Next(); pcursor->Next();
if (!pcursor->Valid() || !pcursor->GetKey(keyTmp)) CoinEntry entry(&keyTmp.second);
if (!pcursor->Valid() || !pcursor->GetKey(entry)) {
keyTmp.first = 0; // Invalidate cached key after last record so that Valid() and GetKey() return false keyTmp.first = 0; // Invalidate cached key after last record so that Valid() and GetKey() return false
} else {
keyTmp.first = entry.key;
}
} }
bool CBlockTreeDB::WriteBatchSync(const std::vector<std::pair<int, const CBlockFileInfo*> >& fileInfo, int nLastFile, const std::vector<const CBlockIndex*>& blockinfo) { bool CBlockTreeDB::WriteBatchSync(const std::vector<std::pair<int, const CBlockFileInfo*> >& fileInfo, int nLastFile, const std::vector<const CBlockIndex*>& blockinfo) {
@ -215,3 +252,103 @@ bool CBlockTreeDB::LoadBlockIndexGuts(std::function<CBlockIndex*(const uint256&)
return true; return true;
} }
namespace {
//! Legacy class to deserialize pre-pertxout database entries without reindex.
class CCoins
{
public:
//! whether transaction is a coinbase
bool fCoinBase;
//! unspent transaction outputs; spent outputs are .IsNull(); spent outputs at the end of the array are dropped
std::vector<CTxOut> vout;
//! at which height this transaction was included in the active block chain
int nHeight;
//! empty constructor
CCoins() : fCoinBase(false), vout(0), nHeight(0) { }
template<typename Stream>
void Unserialize(Stream &s) {
unsigned int nCode = 0;
// version
int nVersionDummy;
::Unserialize(s, VARINT(nVersionDummy));
// header code
::Unserialize(s, VARINT(nCode));
fCoinBase = nCode & 1;
std::vector<bool> vAvail(2, false);
vAvail[0] = (nCode & 2) != 0;
vAvail[1] = (nCode & 4) != 0;
unsigned int nMaskCode = (nCode / 8) + ((nCode & 6) != 0 ? 0 : 1);
// spentness bitmask
while (nMaskCode > 0) {
unsigned char chAvail = 0;
::Unserialize(s, chAvail);
for (unsigned int p = 0; p < 8; p++) {
bool f = (chAvail & (1 << p)) != 0;
vAvail.push_back(f);
}
if (chAvail != 0)
nMaskCode--;
}
// txouts themself
vout.assign(vAvail.size(), CTxOut());
for (unsigned int i = 0; i < vAvail.size(); i++) {
if (vAvail[i])
::Unserialize(s, REF(CTxOutCompressor(vout[i])));
}
// coinbase height
::Unserialize(s, VARINT(nHeight));
}
};
}
/** Upgrade the database from older formats.
*
* Currently implemented: from the per-tx utxo model (0.8..0.14.x) to per-txout.
*/
bool CCoinsViewDB::Upgrade() {
std::unique_ptr<CDBIterator> pcursor(db.NewIterator());
pcursor->Seek(std::make_pair(DB_COINS, uint256()));
if (!pcursor->Valid()) {
return true;
}
LogPrintf("Upgrading database...\n");
size_t batch_size = 1 << 24;
CDBBatch batch(db);
while (pcursor->Valid()) {
boost::this_thread::interruption_point();
std::pair<unsigned char, uint256> key;
if (pcursor->GetKey(key) && key.first == DB_COINS) {
CCoins old_coins;
if (!pcursor->GetValue(old_coins)) {
return error("%s: cannot parse CCoins record", __func__);
}
COutPoint outpoint(key.second, 0);
for (size_t i = 0; i < old_coins.vout.size(); ++i) {
if (!old_coins.vout[i].IsNull() && !old_coins.vout[i].scriptPubKey.IsUnspendable()) {
Coin newcoin(std::move(old_coins.vout[i]), old_coins.nHeight, old_coins.fCoinBase);
outpoint.n = i;
CoinEntry entry(&outpoint);
batch.Write(entry, newcoin);
}
}
batch.Erase(key);
if (batch.SizeEstimate() > batch_size) {
db.WriteBatch(batch);
batch.Clear();
}
pcursor->Next();
} else {
break;
}
}
db.WriteBatch(batch);
return true;
}

View File

@ -22,9 +22,7 @@ class uint256;
//! Compensate for extra memory peak (x1.5-x1.9) at flush time. //! Compensate for extra memory peak (x1.5-x1.9) at flush time.
static constexpr int DB_PEAK_USAGE_FACTOR = 2; static constexpr int DB_PEAK_USAGE_FACTOR = 2;
//! No need to periodic flush if at least this much space still available. //! No need to periodic flush if at least this much space still available.
static constexpr int MAX_BLOCK_COINSDB_USAGE = 200 * DB_PEAK_USAGE_FACTOR; static constexpr int MAX_BLOCK_COINSDB_USAGE = 10 * DB_PEAK_USAGE_FACTOR;
//! Always periodic flush if less than this much space still available.
static constexpr int MIN_BLOCK_COINSDB_USAGE = 50 * DB_PEAK_USAGE_FACTOR;
//! -dbcache default (MiB) //! -dbcache default (MiB)
static const int64_t nDefaultDbCache = 450; static const int64_t nDefaultDbCache = 450;
//! max. -dbcache (MiB) //! max. -dbcache (MiB)
@ -73,11 +71,15 @@ protected:
public: public:
CCoinsViewDB(size_t nCacheSize, bool fMemory = false, bool fWipe = false); CCoinsViewDB(size_t nCacheSize, bool fMemory = false, bool fWipe = false);
bool GetCoins(const uint256 &txid, CCoins &coins) const; bool GetCoin(const COutPoint &outpoint, Coin &coin) const override;
bool HaveCoins(const uint256 &txid) const; bool HaveCoin(const COutPoint &outpoint) const override;
uint256 GetBestBlock() const; uint256 GetBestBlock() const override;
bool BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock); bool BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock) override;
CCoinsViewCursor *Cursor() const; CCoinsViewCursor *Cursor() const override;
//! Attempt to update from an older database format. Returns whether an error occurred.
bool Upgrade();
size_t EstimateSize() const override;
}; };
/** Specialization of CCoinsViewCursor to iterate over a CCoinsViewDB */ /** Specialization of CCoinsViewCursor to iterate over a CCoinsViewDB */
@ -86,8 +88,8 @@ class CCoinsViewDBCursor: public CCoinsViewCursor
public: public:
~CCoinsViewDBCursor() {} ~CCoinsViewDBCursor() {}
bool GetKey(uint256 &key) const; bool GetKey(COutPoint &key) const;
bool GetValue(CCoins &coins) const; bool GetValue(Coin &coin) const;
unsigned int GetValueSize() const; unsigned int GetValueSize() const;
bool Valid() const; bool Valid() const;
@ -97,7 +99,7 @@ private:
CCoinsViewDBCursor(CDBIterator* pcursorIn, const uint256 &hashBlockIn): CCoinsViewDBCursor(CDBIterator* pcursorIn, const uint256 &hashBlockIn):
CCoinsViewCursor(hashBlockIn), pcursor(pcursorIn) {} CCoinsViewCursor(hashBlockIn), pcursor(pcursorIn) {}
std::unique_ptr<CDBIterator> pcursor; std::unique_ptr<CDBIterator> pcursor;
std::pair<char, uint256> keyTmp; std::pair<char, COutPoint> keyTmp;
friend class CCoinsViewDB; friend class CCoinsViewDB;
}; };

View File

@ -343,17 +343,10 @@ CTxMemPool::CTxMemPool(CBlockPolicyEstimator* estimator) :
nCheckFrequency = 0; nCheckFrequency = 0;
} }
void CTxMemPool::pruneSpent(const uint256 &hashTx, CCoins &coins) bool CTxMemPool::isSpent(const COutPoint& outpoint)
{ {
LOCK(cs); LOCK(cs);
return mapNextTx.count(outpoint);
auto it = mapNextTx.lower_bound(COutPoint(hashTx, 0));
// iterate over all COutPoints in mapNextTx whose hash equals the provided hashTx
while (it != mapNextTx.end() && it->first->hash == hashTx) {
coins.Spend(it->first->n); // and remove those outputs from coins
it++;
}
} }
unsigned int CTxMemPool::GetTransactionsUpdated() const unsigned int CTxMemPool::GetTransactionsUpdated() const
@ -531,9 +524,9 @@ void CTxMemPool::removeForReorg(const CCoinsViewCache *pcoins, unsigned int nMem
indexed_transaction_set::const_iterator it2 = mapTx.find(txin.prevout.hash); indexed_transaction_set::const_iterator it2 = mapTx.find(txin.prevout.hash);
if (it2 != mapTx.end()) if (it2 != mapTx.end())
continue; continue;
const CCoins *coins = pcoins->AccessCoins(txin.prevout.hash); const Coin &coin = pcoins->AccessCoin(txin.prevout);
if (nCheckFrequency != 0) assert(coins); if (nCheckFrequency != 0) assert(!coin.IsSpent());
if (!coins || (coins->IsCoinBase() && ((signed long)nMemPoolHeight) - coins->nHeight < COINBASE_MATURITY)) { if (coin.IsSpent() || (coin.IsCoinBase() && ((signed long)nMemPoolHeight) - coin.nHeight < COINBASE_MATURITY)) {
txToRemove.insert(it); txToRemove.insert(it);
break; break;
} }
@ -661,8 +654,7 @@ void CTxMemPool::check(const CCoinsViewCache *pcoins) const
parentSigOpCost += it2->GetSigOpCost(); parentSigOpCost += it2->GetSigOpCost();
} }
} else { } else {
const CCoins* coins = pcoins->AccessCoins(txin.prevout.hash); assert(pcoins->HaveCoin(txin.prevout));
assert(coins && coins->IsAvailable(txin.prevout.n));
} }
// Check whether its inputs are marked in mapNextTx. // Check whether its inputs are marked in mapNextTx.
auto it3 = mapNextTx.find(txin.prevout); auto it3 = mapNextTx.find(txin.prevout);
@ -898,20 +890,24 @@ bool CTxMemPool::HasNoInputsOf(const CTransaction &tx) const
CCoinsViewMemPool::CCoinsViewMemPool(CCoinsView* baseIn, const CTxMemPool& mempoolIn) : CCoinsViewBacked(baseIn), mempool(mempoolIn) { } CCoinsViewMemPool::CCoinsViewMemPool(CCoinsView* baseIn, const CTxMemPool& mempoolIn) : CCoinsViewBacked(baseIn), mempool(mempoolIn) { }
bool CCoinsViewMemPool::GetCoins(const uint256 &txid, CCoins &coins) const { bool CCoinsViewMemPool::GetCoin(const COutPoint &outpoint, Coin &coin) const {
// If an entry in the mempool exists, always return that one, as it's guaranteed to never // If an entry in the mempool exists, always return that one, as it's guaranteed to never
// conflict with the underlying cache, and it cannot have pruned entries (as it contains full) // conflict with the underlying cache, and it cannot have pruned entries (as it contains full)
// transactions. First checking the underlying cache risks returning a pruned entry instead. // transactions. First checking the underlying cache risks returning a pruned entry instead.
CTransactionRef ptx = mempool.get(txid); CTransactionRef ptx = mempool.get(outpoint.hash);
if (ptx) { if (ptx) {
coins = CCoins(*ptx, MEMPOOL_HEIGHT); if (outpoint.n < ptx->vout.size()) {
return true; coin = Coin(ptx->vout[outpoint.n], MEMPOOL_HEIGHT, false);
return true;
} else {
return false;
}
} }
return (base->GetCoins(txid, coins) && !coins.IsPruned()); return (base->GetCoin(outpoint, coin) && !coin.IsSpent());
} }
bool CCoinsViewMemPool::HaveCoins(const uint256 &txid) const { bool CCoinsViewMemPool::HaveCoin(const COutPoint &outpoint) const {
return mempool.exists(txid) || base->HaveCoins(txid); return mempool.exists(outpoint) || base->HaveCoin(outpoint);
} }
size_t CTxMemPool::DynamicMemoryUsage() const { size_t CTxMemPool::DynamicMemoryUsage() const {
@ -1022,7 +1018,7 @@ void CTxMemPool::trackPackageRemoved(const CFeeRate& rate) {
} }
} }
void CTxMemPool::TrimToSize(size_t sizelimit, std::vector<uint256>* pvNoSpendsRemaining) { void CTxMemPool::TrimToSize(size_t sizelimit, std::vector<COutPoint>* pvNoSpendsRemaining) {
LOCK(cs); LOCK(cs);
unsigned nTxnRemoved = 0; unsigned nTxnRemoved = 0;
@ -1053,11 +1049,10 @@ void CTxMemPool::TrimToSize(size_t sizelimit, std::vector<uint256>* pvNoSpendsRe
if (pvNoSpendsRemaining) { if (pvNoSpendsRemaining) {
BOOST_FOREACH(const CTransaction& tx, txn) { BOOST_FOREACH(const CTransaction& tx, txn) {
BOOST_FOREACH(const CTxIn& txin, tx.vin) { BOOST_FOREACH(const CTxIn& txin, tx.vin) {
if (exists(txin.prevout.hash)) if (exists(txin.prevout.hash)) continue;
continue; if (!mapNextTx.count(txin.prevout)) {
auto iter = mapNextTx.lower_bound(COutPoint(txin.prevout.hash, 0)); pvNoSpendsRemaining->push_back(txin.prevout);
if (iter == mapNextTx.end() || iter->first->hash != txin.prevout.hash) }
pvNoSpendsRemaining->push_back(txin.prevout.hash);
} }
} }
} }
@ -1074,3 +1069,5 @@ bool CTxMemPool::TransactionWithinChainLimit(const uint256& txid, size_t chainLi
return it == mapTx.end() || (it->GetCountWithAncestors() < chainLimit && return it == mapTx.end() || (it->GetCountWithAncestors() < chainLimit &&
it->GetCountWithDescendants() < chainLimit); it->GetCountWithDescendants() < chainLimit);
} }
SaltedTxidHasher::SaltedTxidHasher() : k0(GetRand(std::numeric_limits<uint64_t>::max())), k1(GetRand(std::numeric_limits<uint64_t>::max())) {}

View File

@ -31,8 +31,8 @@
class CAutoFile; class CAutoFile;
class CBlockIndex; class CBlockIndex;
/** Fake height value used in CCoins to signify they are only in the memory pool (since 0.8) */ /** Fake height value used in Coin to signify they are only in the memory pool (since 0.8) */
static const unsigned int MEMPOOL_HEIGHT = 0x7FFFFFFF; static const uint32_t MEMPOOL_HEIGHT = 0x7FFFFFFF;
struct LockPoints struct LockPoints
{ {
@ -327,6 +327,20 @@ enum class MemPoolRemovalReason {
REPLACED //! Removed for replacement REPLACED //! Removed for replacement
}; };
class SaltedTxidHasher
{
private:
/** Salt */
const uint64_t k0, k1;
public:
SaltedTxidHasher();
size_t operator()(const uint256& txid) const {
return SipHashUint256(k0, k1, txid);
}
};
/** /**
* CTxMemPool stores valid-according-to-the-current-best-chain transactions * CTxMemPool stores valid-according-to-the-current-best-chain transactions
* that may be included in the next block. * that may be included in the next block.
@ -515,7 +529,7 @@ public:
void _clear(); //lock free void _clear(); //lock free
bool CompareDepthAndScore(const uint256& hasha, const uint256& hashb); bool CompareDepthAndScore(const uint256& hasha, const uint256& hashb);
void queryHashes(std::vector<uint256>& vtxid); void queryHashes(std::vector<uint256>& vtxid);
void pruneSpent(const uint256& hash, CCoins &coins); bool isSpent(const COutPoint& outpoint);
unsigned int GetTransactionsUpdated() const; unsigned int GetTransactionsUpdated() const;
void AddTransactionsUpdated(unsigned int n); void AddTransactionsUpdated(unsigned int n);
/** /**
@ -576,10 +590,10 @@ public:
CFeeRate GetMinFee(size_t sizelimit) const; CFeeRate GetMinFee(size_t sizelimit) const;
/** Remove transactions from the mempool until its dynamic size is <= sizelimit. /** Remove transactions from the mempool until its dynamic size is <= sizelimit.
* pvNoSpendsRemaining, if set, will be populated with the list of transactions * pvNoSpendsRemaining, if set, will be populated with the list of outpoints
* which are not in mempool which no longer have any spends in this mempool. * which are not in mempool which no longer have any spends in this mempool.
*/ */
void TrimToSize(size_t sizelimit, std::vector<uint256>* pvNoSpendsRemaining=NULL); void TrimToSize(size_t sizelimit, std::vector<COutPoint>* pvNoSpendsRemaining=NULL);
/** Expire all transaction (and their dependencies) in the mempool older than time. Return the number of removed transactions. */ /** Expire all transaction (and their dependencies) in the mempool older than time. Return the number of removed transactions. */
int Expire(int64_t time); int Expire(int64_t time);
@ -605,6 +619,13 @@ public:
return (mapTx.count(hash) != 0); return (mapTx.count(hash) != 0);
} }
bool exists(const COutPoint& outpoint) const
{
LOCK(cs);
auto it = mapTx.find(outpoint.hash);
return (it != mapTx.end() && outpoint.n < it->GetTx().vout.size());
}
CTransactionRef get(const uint256& hash) const; CTransactionRef get(const uint256& hash) const;
TxMempoolInfo info(const uint256& hash) const; TxMempoolInfo info(const uint256& hash) const;
std::vector<TxMempoolInfo> infoAll() const; std::vector<TxMempoolInfo> infoAll() const;
@ -664,8 +685,8 @@ protected:
public: public:
CCoinsViewMemPool(CCoinsView* baseIn, const CTxMemPool& mempoolIn); CCoinsViewMemPool(CCoinsView* baseIn, const CTxMemPool& mempoolIn);
bool GetCoins(const uint256 &txid, CCoins &coins) const; bool GetCoin(const COutPoint &outpoint, Coin &coin) const;
bool HaveCoins(const uint256 &txid) const; bool HaveCoin(const COutPoint &outpoint) const;
}; };
/** /**

View File

@ -7,58 +7,90 @@
#define BITCOIN_UNDO_H #define BITCOIN_UNDO_H
#include "compressor.h" #include "compressor.h"
#include "consensus/consensus.h"
#include "primitives/transaction.h" #include "primitives/transaction.h"
#include "serialize.h" #include "serialize.h"
/** Undo information for a CTxIn /** Undo information for a CTxIn
* *
* Contains the prevout's CTxOut being spent, and if this was the * Contains the prevout's CTxOut being spent, and its metadata as well
* last output of the affected transaction, its metadata as well * (coinbase or not, height). The serialization contains a dummy value of
* (coinbase or not, height, transaction version) * zero. This is be compatible with older versions which expect to see
* the transaction version there.
*/ */
class CTxInUndo class TxInUndoSerializer
{ {
const Coin* txout;
public: public:
CTxOut txout; // the txout data before being spent
bool fCoinBase; // if the outpoint was the last unspent: whether it belonged to a coinbase
unsigned int nHeight; // if the outpoint was the last unspent: its height
int nVersion; // if the outpoint was the last unspent: its version
CTxInUndo() : txout(), fCoinBase(false), nHeight(0), nVersion(0) {}
CTxInUndo(const CTxOut &txoutIn, bool fCoinBaseIn = false, unsigned int nHeightIn = 0, int nVersionIn = 0) : txout(txoutIn), fCoinBase(fCoinBaseIn), nHeight(nHeightIn), nVersion(nVersionIn) { }
template<typename Stream> template<typename Stream>
void Serialize(Stream &s) const { void Serialize(Stream &s) const {
::Serialize(s, VARINT(nHeight*2+(fCoinBase ? 1 : 0))); ::Serialize(s, VARINT(txout->nHeight * 2 + (txout->fCoinBase ? 1 : 0)));
if (nHeight > 0) if (txout->nHeight > 0) {
::Serialize(s, VARINT(this->nVersion)); // Required to maintain compatibility with older undo format.
::Serialize(s, CTxOutCompressor(REF(txout))); ::Serialize(s, (unsigned char)0);
}
::Serialize(s, CTxOutCompressor(REF(txout->out)));
} }
TxInUndoSerializer(const Coin* coin) : txout(coin) {}
};
class TxInUndoDeserializer
{
Coin* txout;
public:
template<typename Stream> template<typename Stream>
void Unserialize(Stream &s) { void Unserialize(Stream &s) {
unsigned int nCode = 0; unsigned int nCode = 0;
::Unserialize(s, VARINT(nCode)); ::Unserialize(s, VARINT(nCode));
nHeight = nCode / 2; txout->nHeight = nCode / 2;
fCoinBase = nCode & 1; txout->fCoinBase = nCode & 1;
if (nHeight > 0) if (txout->nHeight > 0) {
::Unserialize(s, VARINT(this->nVersion)); // Old versions stored the version number for the last spend of
::Unserialize(s, REF(CTxOutCompressor(REF(txout)))); // a transaction's outputs. Non-final spends were indicated with
// height = 0.
int nVersionDummy;
::Unserialize(s, VARINT(nVersionDummy));
}
::Unserialize(s, REF(CTxOutCompressor(REF(txout->out))));
} }
TxInUndoDeserializer(Coin* coin) : txout(coin) {}
}; };
static const size_t MAX_INPUTS_PER_BLOCK = MAX_BLOCK_BASE_SIZE / ::GetSerializeSize(CTxIn(), SER_NETWORK, PROTOCOL_VERSION);
/** Undo information for a CTransaction */ /** Undo information for a CTransaction */
class CTxUndo class CTxUndo
{ {
public: public:
// undo information for all txins // undo information for all txins
std::vector<CTxInUndo> vprevout; std::vector<Coin> vprevout;
ADD_SERIALIZE_METHODS; template <typename Stream>
void Serialize(Stream& s) const {
// TODO: avoid reimplementing vector serializer
uint64_t count = vprevout.size();
::Serialize(s, COMPACTSIZE(REF(count)));
for (const auto& prevout : vprevout) {
::Serialize(s, REF(TxInUndoSerializer(&prevout)));
}
}
template <typename Stream, typename Operation> template <typename Stream>
inline void SerializationOp(Stream& s, Operation ser_action) { void Unserialize(Stream& s) {
READWRITE(vprevout); // TODO: avoid reimplementing vector deserializer
uint64_t count = 0;
::Unserialize(s, COMPACTSIZE(count));
if (count > MAX_INPUTS_PER_BLOCK) {
throw std::ios_base::failure("Too many input undo records");
}
vprevout.resize(count);
for (auto& prevout : vprevout) {
::Unserialize(s, REF(TxInUndoDeserializer(&prevout)));
}
} }
}; };

View File

@ -268,15 +268,15 @@ bool CheckSequenceLocks(const CTransaction &tx, int flags, LockPoints* lp, bool
prevheights.resize(tx.vin.size()); prevheights.resize(tx.vin.size());
for (size_t txinIndex = 0; txinIndex < tx.vin.size(); txinIndex++) { for (size_t txinIndex = 0; txinIndex < tx.vin.size(); txinIndex++) {
const CTxIn& txin = tx.vin[txinIndex]; const CTxIn& txin = tx.vin[txinIndex];
CCoins coins; Coin coin;
if (!viewMemPool.GetCoins(txin.prevout.hash, coins)) { if (!viewMemPool.GetCoin(txin.prevout, coin)) {
return error("%s: Missing input", __func__); return error("%s: Missing input", __func__);
} }
if (coins.nHeight == MEMPOOL_HEIGHT) { if (coin.nHeight == MEMPOOL_HEIGHT) {
// Assume all mempool transaction confirm in the next block // Assume all mempool transaction confirm in the next block
prevheights[txinIndex] = tip->nHeight + 1; prevheights[txinIndex] = tip->nHeight + 1;
} else { } else {
prevheights[txinIndex] = coins.nHeight; prevheights[txinIndex] = coin.nHeight;
} }
} }
lockPair = CalculateSequenceLocks(tx, flags, &prevheights, index); lockPair = CalculateSequenceLocks(tx, flags, &prevheights, index);
@ -315,9 +315,9 @@ void LimitMempoolSize(CTxMemPool& pool, size_t limit, unsigned long age) {
LogPrint(BCLog::MEMPOOL, "Expired %i transactions from the memory pool\n", expired); LogPrint(BCLog::MEMPOOL, "Expired %i transactions from the memory pool\n", expired);
} }
std::vector<uint256> vNoSpendsRemaining; std::vector<COutPoint> vNoSpendsRemaining;
pool.TrimToSize(limit, &vNoSpendsRemaining); pool.TrimToSize(limit, &vNoSpendsRemaining);
BOOST_FOREACH(const uint256& removed, vNoSpendsRemaining) BOOST_FOREACH(const COutPoint& removed, vNoSpendsRemaining)
pcoinsTip->Uncache(removed); pcoinsTip->Uncache(removed);
} }
@ -394,7 +394,7 @@ void UpdateMempoolForReorg(DisconnectedBlockTransactions &disconnectpool, bool f
bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState& state, const CTransactionRef& ptx, bool fLimitFree, bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState& state, const CTransactionRef& ptx, bool fLimitFree,
bool* pfMissingInputs, int64_t nAcceptTime, std::list<CTransactionRef>* plTxnReplaced, bool* pfMissingInputs, int64_t nAcceptTime, std::list<CTransactionRef>* plTxnReplaced,
bool fOverrideMempoolLimit, const CAmount& nAbsurdFee, std::vector<uint256>& vHashTxnToUncache) bool fOverrideMempoolLimit, const CAmount& nAbsurdFee, std::vector<COutPoint>& coins_to_uncache)
{ {
const CTransaction& tx = *ptx; const CTransaction& tx = *ptx;
const uint256 hash = tx.GetHash(); const uint256 hash = tx.GetHash();
@ -487,29 +487,29 @@ bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState& state, const C
view.SetBackend(viewMemPool); view.SetBackend(viewMemPool);
// do we already have it? // do we already have it?
bool fHadTxInCache = pcoinsTip->HaveCoinsInCache(hash); for (size_t out = 0; out < tx.vout.size(); out++) {
if (view.HaveCoins(hash)) { COutPoint outpoint(hash, out);
if (!fHadTxInCache) bool had_coin_in_cache = pcoinsTip->HaveCoinInCache(outpoint);
vHashTxnToUncache.push_back(hash); if (view.HaveCoin(outpoint)) {
return state.Invalid(false, REJECT_ALREADY_KNOWN, "txn-already-known"); if (!had_coin_in_cache) {
} coins_to_uncache.push_back(outpoint);
}
// do all inputs exist? return state.Invalid(false, REJECT_ALREADY_KNOWN, "txn-already-known");
// Note that this does not check for the presence of actual outputs (see the next check for that),
// and only helps with filling in pfMissingInputs (to determine missing vs spent).
BOOST_FOREACH(const CTxIn txin, tx.vin) {
if (!pcoinsTip->HaveCoinsInCache(txin.prevout.hash))
vHashTxnToUncache.push_back(txin.prevout.hash);
if (!view.HaveCoins(txin.prevout.hash)) {
if (pfMissingInputs)
*pfMissingInputs = true;
return false; // fMissingInputs and !state.IsInvalid() is used to detect this condition, don't set state.Invalid()
} }
} }
// are the actual inputs available? // do all inputs exist?
if (!view.HaveInputs(tx)) BOOST_FOREACH(const CTxIn txin, tx.vin) {
return state.Invalid(false, REJECT_DUPLICATE, "bad-txns-inputs-spent"); if (!pcoinsTip->HaveCoinInCache(txin.prevout)) {
coins_to_uncache.push_back(txin.prevout);
}
if (!view.HaveCoin(txin.prevout)) {
if (pfMissingInputs) {
*pfMissingInputs = true;
}
return false; // fMissingInputs and !state.IsInvalid() is used to detect this condition, don't set state.Invalid()
}
}
// Bring the best block into scope // Bring the best block into scope
view.GetBestBlock(); view.GetBestBlock();
@ -548,8 +548,8 @@ bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState& state, const C
// during reorgs to ensure COINBASE_MATURITY is still met. // during reorgs to ensure COINBASE_MATURITY is still met.
bool fSpendsCoinbase = false; bool fSpendsCoinbase = false;
BOOST_FOREACH(const CTxIn &txin, tx.vin) { BOOST_FOREACH(const CTxIn &txin, tx.vin) {
const CCoins *coins = view.AccessCoins(txin.prevout.hash); const Coin &coin = view.AccessCoin(txin.prevout);
if (coins->IsCoinBase()) { if (coin.IsCoinBase()) {
fSpendsCoinbase = true; fSpendsCoinbase = true;
break; break;
} }
@ -813,10 +813,10 @@ bool AcceptToMemoryPoolWithTime(CTxMemPool& pool, CValidationState &state, const
bool* pfMissingInputs, int64_t nAcceptTime, std::list<CTransactionRef>* plTxnReplaced, bool* pfMissingInputs, int64_t nAcceptTime, std::list<CTransactionRef>* plTxnReplaced,
bool fOverrideMempoolLimit, const CAmount nAbsurdFee) bool fOverrideMempoolLimit, const CAmount nAbsurdFee)
{ {
std::vector<uint256> vHashTxToUncache; std::vector<COutPoint> coins_to_uncache;
bool res = AcceptToMemoryPoolWorker(pool, state, tx, fLimitFree, pfMissingInputs, nAcceptTime, plTxnReplaced, fOverrideMempoolLimit, nAbsurdFee, vHashTxToUncache); bool res = AcceptToMemoryPoolWorker(pool, state, tx, fLimitFree, pfMissingInputs, nAcceptTime, plTxnReplaced, fOverrideMempoolLimit, nAbsurdFee, coins_to_uncache);
if (!res) { if (!res) {
BOOST_FOREACH(const uint256& hashTx, vHashTxToUncache) BOOST_FOREACH(const COutPoint& hashTx, coins_to_uncache)
pcoinsTip->Uncache(hashTx); pcoinsTip->Uncache(hashTx);
} }
// After we've (potentially) uncached entries, ensure our coins cache is still within its size limits // After we've (potentially) uncached entries, ensure our coins cache is still within its size limits
@ -868,15 +868,8 @@ bool GetTransaction(const uint256 &hash, CTransactionRef &txOut, const Consensus
} }
if (fAllowSlow) { // use coin database to locate block that contains transaction, and scan it if (fAllowSlow) { // use coin database to locate block that contains transaction, and scan it
int nHeight = -1; const Coin& coin = AccessByTxid(*pcoinsTip, hash);
{ if (!coin.IsSpent()) pindexSlow = chainActive[coin.nHeight];
const CCoinsViewCache& view = *pcoinsTip;
const CCoins* coins = view.AccessCoins(hash);
if (coins)
nHeight = coins->nHeight;
}
if (nHeight > 0)
pindexSlow = chainActive[nHeight];
} }
if (pindexSlow) { if (pindexSlow) {
@ -1124,24 +1117,12 @@ void UpdateCoins(const CTransaction& tx, CCoinsViewCache& inputs, CTxUndo &txund
if (!tx.IsCoinBase()) { if (!tx.IsCoinBase()) {
txundo.vprevout.reserve(tx.vin.size()); txundo.vprevout.reserve(tx.vin.size());
BOOST_FOREACH(const CTxIn &txin, tx.vin) { BOOST_FOREACH(const CTxIn &txin, tx.vin) {
CCoinsModifier coins = inputs.ModifyCoins(txin.prevout.hash); txundo.vprevout.emplace_back();
unsigned nPos = txin.prevout.n; inputs.SpendCoin(txin.prevout, &txundo.vprevout.back());
if (nPos >= coins->vout.size() || coins->vout[nPos].IsNull())
assert(false);
// mark an outpoint spent, and construct undo information
txundo.vprevout.push_back(CTxInUndo(coins->vout[nPos]));
coins->Spend(nPos);
if (coins->vout.size() == 0) {
CTxInUndo& undo = txundo.vprevout.back();
undo.nHeight = coins->nHeight;
undo.fCoinBase = coins->fCoinBase;
undo.nVersion = coins->nVersion;
}
} }
} }
// add outputs // add outputs
inputs.ModifyNewCoins(tx.GetHash(), tx.IsCoinBase())->FromTx(tx, nHeight); AddCoins(inputs, tx, nHeight);
} }
void UpdateCoins(const CTransaction& tx, CCoinsViewCache& inputs, int nHeight) void UpdateCoins(const CTransaction& tx, CCoinsViewCache& inputs, int nHeight)
@ -1185,11 +1166,19 @@ bool CheckInputs(const CTransaction& tx, CValidationState &state, const CCoinsVi
if (fScriptChecks) { if (fScriptChecks) {
for (unsigned int i = 0; i < tx.vin.size(); i++) { for (unsigned int i = 0; i < tx.vin.size(); i++) {
const COutPoint &prevout = tx.vin[i].prevout; const COutPoint &prevout = tx.vin[i].prevout;
const CCoins* coins = inputs.AccessCoins(prevout.hash); const Coin& coin = inputs.AccessCoin(prevout);
assert(coins); assert(!coin.IsSpent());
// We very carefully only pass in things to CScriptCheck which
// are clearly committed to by tx' witness hash. This provides
// a sanity check that our caching is not introducing consensus
// failures through additional data in, eg, the coins being
// spent being checked as a part of CScriptCheck.
const CScript& scriptPubKey = coin.out.scriptPubKey;
const CAmount amount = coin.out.nValue;
// Verify signature // Verify signature
CScriptCheck check(*coins, tx, i, flags, cacheStore, &txdata); CScriptCheck check(scriptPubKey, amount, tx, i, flags, cacheStore, &txdata);
if (pvChecks) { if (pvChecks) {
pvChecks->push_back(CScriptCheck()); pvChecks->push_back(CScriptCheck());
check.swap(pvChecks->back()); check.swap(pvChecks->back());
@ -1201,7 +1190,7 @@ bool CheckInputs(const CTransaction& tx, CValidationState &state, const CCoinsVi
// arguments; if so, don't trigger DoS protection to // arguments; if so, don't trigger DoS protection to
// avoid splitting the network between upgraded and // avoid splitting the network between upgraded and
// non-upgraded nodes. // non-upgraded nodes.
CScriptCheck check2(*coins, tx, i, CScriptCheck check2(scriptPubKey, amount, tx, i,
flags & ~STANDARD_NOT_MANDATORY_VERIFY_FLAGS, cacheStore, &txdata); flags & ~STANDARD_NOT_MANDATORY_VERIFY_FLAGS, cacheStore, &txdata);
if (check2()) if (check2())
return state.Invalid(false, REJECT_NONSTANDARD, strprintf("non-mandatory-script-verify-flag (%s)", ScriptErrorString(check.GetScriptError()))); return state.Invalid(false, REJECT_NONSTANDARD, strprintf("non-mandatory-script-verify-flag (%s)", ScriptErrorString(check.GetScriptError())));
@ -1260,8 +1249,10 @@ bool UndoReadFromDisk(CBlockUndo& blockundo, const CDiskBlockPos& pos, const uin
// Read block // Read block
uint256 hashChecksum; uint256 hashChecksum;
CHashVerifier<CAutoFile> verifier(&filein); // We need a CHashVerifier as reserializing may lose data
try { try {
filein >> blockundo; verifier << hashBlock;
verifier >> blockundo;
filein >> hashChecksum; filein >> hashChecksum;
} }
catch (const std::exception& e) { catch (const std::exception& e) {
@ -1269,10 +1260,7 @@ bool UndoReadFromDisk(CBlockUndo& blockundo, const CDiskBlockPos& pos, const uin
} }
// Verify checksum // Verify checksum
CHashWriter hasher(SER_GETHASH, PROTOCOL_VERSION); if (hashChecksum != verifier.GetHash())
hasher << hashBlock;
hasher << blockundo;
if (hashChecksum != hasher.GetHash())
return error("%s: Checksum mismatch", __func__); return error("%s: Checksum mismatch", __func__);
return true; return true;
@ -1298,39 +1286,6 @@ bool AbortNode(CValidationState& state, const std::string& strMessage, const std
} // anon namespace } // anon namespace
/**
* Apply the undo operation of a CTxInUndo to the given chain state.
* @param undo The undo object.
* @param view The coins view to which to apply the changes.
* @param out The out point that corresponds to the tx input.
* @return True on success.
*/
bool ApplyTxInUndo(const CTxInUndo& undo, CCoinsViewCache& view, const COutPoint& out)
{
bool fClean = true;
CCoinsModifier coins = view.ModifyCoins(out.hash);
if (undo.nHeight != 0) {
// undo data contains height: this is the last output of the prevout tx being spent
if (!coins->IsPruned())
fClean = fClean && error("%s: undo data overwriting existing transaction", __func__);
coins->Clear();
coins->fCoinBase = undo.fCoinBase;
coins->nHeight = undo.nHeight;
coins->nVersion = undo.nVersion;
} else {
if (coins->IsPruned())
fClean = fClean && error("%s: undo data adding output to missing transaction", __func__);
}
if (coins->IsAvailable(out.n))
fClean = fClean && error("%s: undo data overwriting existing output", __func__);
if (coins->vout.size() < out.n+1)
coins->vout.resize(out.n+1);
coins->vout[out.n] = undo.txout;
return fClean;
}
enum DisconnectResult enum DisconnectResult
{ {
DISCONNECT_OK, // All good. DISCONNECT_OK, // All good.
@ -1338,6 +1293,36 @@ enum DisconnectResult
DISCONNECT_FAILED // Something else went wrong. DISCONNECT_FAILED // Something else went wrong.
}; };
/**
* Restore the UTXO in a Coin at a given COutPoint
* @param undo The Coin to be restored.
* @param view The coins view to which to apply the changes.
* @param out The out point that corresponds to the tx input.
* @return A DisconnectResult as an int
*/
int ApplyTxInUndo(Coin&& undo, CCoinsViewCache& view, const COutPoint& out)
{
bool fClean = true;
if (view.HaveCoin(out)) fClean = false; // overwriting transaction output
if (undo.nHeight == 0) {
// Missing undo metadata (height and coinbase). Older versions included this
// information only in undo records for the last spend of a transactions'
// outputs. This implies that it must be present for some other output of the same tx.
const Coin& alternate = AccessByTxid(view, out.hash);
if (!alternate.IsSpent()) {
undo.nHeight = alternate.nHeight;
undo.fCoinBase = alternate.fCoinBase;
} else {
return DISCONNECT_FAILED; // adding output for transaction without known metadata
}
}
view.AddCoin(out, std::move(undo), undo.fCoinBase);
return fClean ? DISCONNECT_OK : DISCONNECT_UNCLEAN;
}
/** Undo the effects of this block (with given index) on the UTXO set represented by coins. /** Undo the effects of this block (with given index) on the UTXO set represented by coins.
* When UNCLEAN or FAILED is returned, view is left in an indeterminate state. */ * When UNCLEAN or FAILED is returned, view is left in an indeterminate state. */
static DisconnectResult DisconnectBlock(const CBlock& block, const CBlockIndex* pindex, CCoinsViewCache& view) static DisconnectResult DisconnectBlock(const CBlock& block, const CBlockIndex* pindex, CCoinsViewCache& view)
@ -1369,36 +1354,31 @@ static DisconnectResult DisconnectBlock(const CBlock& block, const CBlockIndex*
// Check that all outputs are available and match the outputs in the block itself // Check that all outputs are available and match the outputs in the block itself
// exactly. // exactly.
{ for (size_t o = 0; o < tx.vout.size(); o++) {
CCoinsModifier outs = view.ModifyCoins(hash); if (!tx.vout[o].scriptPubKey.IsUnspendable()) {
outs->ClearUnspendable(); COutPoint out(hash, o);
Coin coin;
CCoins outsBlock(tx, pindex->nHeight); view.SpendCoin(out, &coin);
// The CCoins serialization does not serialize negative numbers. if (tx.vout[o] != coin.out) {
// No network rules currently depend on the version here, so an inconsistency is harmless fClean = false; // transaction output mismatch
// but it must be corrected before txout nversion ever influences a network rule. }
if (outsBlock.nVersion < 0) }
outs->nVersion = outsBlock.nVersion;
if (*outs != outsBlock)
fClean = fClean && error("DisconnectBlock(): added transaction mismatch? database corrupted");
// remove outputs
outs->Clear();
} }
// restore inputs // restore inputs
if (i > 0) { // not coinbases if (i > 0) { // not coinbases
const CTxUndo &txundo = blockUndo.vtxundo[i-1]; CTxUndo &txundo = blockUndo.vtxundo[i-1];
if (txundo.vprevout.size() != tx.vin.size()) { if (txundo.vprevout.size() != tx.vin.size()) {
error("DisconnectBlock(): transaction and undo data inconsistent"); error("DisconnectBlock(): transaction and undo data inconsistent");
return DISCONNECT_FAILED; return DISCONNECT_FAILED;
} }
for (unsigned int j = tx.vin.size(); j-- > 0;) { for (unsigned int j = tx.vin.size(); j-- > 0;) {
const COutPoint &out = tx.vin[j].prevout; const COutPoint &out = tx.vin[j].prevout;
const CTxInUndo &undo = txundo.vprevout[j]; int res = ApplyTxInUndo(std::move(txundo.vprevout[j]), view, out);
if (!ApplyTxInUndo(undo, view, out)) if (res == DISCONNECT_FAILED) return DISCONNECT_FAILED;
fClean = false; fClean = fClean && res != DISCONNECT_UNCLEAN;
} }
// At this point, all of txundo.vprevout should have been moved out.
} }
} }
@ -1579,10 +1559,12 @@ static bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockInd
if (fEnforceBIP30) { if (fEnforceBIP30) {
for (const auto& tx : block.vtx) { for (const auto& tx : block.vtx) {
const CCoins* coins = view.AccessCoins(tx->GetHash()); for (size_t o = 0; o < tx->vout.size(); o++) {
if (coins && !coins->IsPruned()) if (view.HaveCoin(COutPoint(tx->GetHash(), o))) {
return state.DoS(100, error("ConnectBlock(): tried to overwrite transaction"), return state.DoS(100, error("ConnectBlock(): tried to overwrite transaction"),
REJECT_INVALID, "bad-txns-BIP30"); REJECT_INVALID, "bad-txns-BIP30");
}
}
} }
} }
@ -1649,7 +1631,7 @@ static bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockInd
// be in ConnectBlock because they require the UTXO set // be in ConnectBlock because they require the UTXO set
prevheights.resize(tx.vin.size()); prevheights.resize(tx.vin.size());
for (size_t j = 0; j < tx.vin.size(); j++) { for (size_t j = 0; j < tx.vin.size(); j++) {
prevheights[j] = view.AccessCoins(tx.vin[j].prevout.hash)->nHeight; prevheights[j] = view.AccessCoin(tx.vin[j].prevout).nHeight;
} }
if (!SequenceLocks(tx, nLockTimeFlags, &prevheights, *pindex)) { if (!SequenceLocks(tx, nLockTimeFlags, &prevheights, *pindex)) {
@ -1787,9 +1769,8 @@ bool static FlushStateToDisk(CValidationState &state, FlushStateMode mode, int n
int64_t nMempoolSizeMax = GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000; int64_t nMempoolSizeMax = GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000;
int64_t cacheSize = pcoinsTip->DynamicMemoryUsage() * DB_PEAK_USAGE_FACTOR; int64_t cacheSize = pcoinsTip->DynamicMemoryUsage() * DB_PEAK_USAGE_FACTOR;
int64_t nTotalSpace = nCoinCacheUsage + std::max<int64_t>(nMempoolSizeMax - nMempoolUsage, 0); int64_t nTotalSpace = nCoinCacheUsage + std::max<int64_t>(nMempoolSizeMax - nMempoolUsage, 0);
// The cache is large and we're within 10% and 200 MiB or 50% and 50MiB of the limit, but we have time now (not in the middle of a block processing). // The cache is large and we're within 10% and 10 MiB of the limit, but we have time now (not in the middle of a block processing).
bool fCacheLarge = mode == FLUSH_STATE_PERIODIC && cacheSize > std::min(std::max(nTotalSpace / 2, nTotalSpace - MIN_BLOCK_COINSDB_USAGE * 1024 * 1024), bool fCacheLarge = mode == FLUSH_STATE_PERIODIC && cacheSize > std::max((9 * nTotalSpace) / 10, nTotalSpace - MAX_BLOCK_COINSDB_USAGE * 1024 * 1024);
std::max((9 * nTotalSpace) / 10, nTotalSpace - MAX_BLOCK_COINSDB_USAGE * 1024 * 1024));
// The cache is over the limit, we have to write now. // The cache is over the limit, we have to write now.
bool fCacheCritical = mode == FLUSH_STATE_IF_NEEDED && cacheSize > nTotalSpace; bool fCacheCritical = mode == FLUSH_STATE_IF_NEEDED && cacheSize > nTotalSpace;
// It's been a while since we wrote the block index to disk. Do this frequently, so we don't need to redownload after a crash. // It's been a while since we wrote the block index to disk. Do this frequently, so we don't need to redownload after a crash.
@ -1830,12 +1811,12 @@ bool static FlushStateToDisk(CValidationState &state, FlushStateMode mode, int n
} }
// Flush best chain related state. This can only be done if the blocks / block index write was also done. // Flush best chain related state. This can only be done if the blocks / block index write was also done.
if (fDoFullFlush) { if (fDoFullFlush) {
// Typical CCoins structures on disk are around 128 bytes in size. // Typical Coin structures on disk are around 48 bytes in size.
// Pushing a new one to the database can cause it to be written // Pushing a new one to the database can cause it to be written
// twice (once in the log, and once in the tables). This is already // twice (once in the log, and once in the tables). This is already
// an overestimation, as most will delete an existing entry or // an overestimation, as most will delete an existing entry or
// overwrite one. Still, use a conservative safety factor of 2. // overwrite one. Still, use a conservative safety factor of 2.
if (!CheckDiskSpace(128 * 2 * 2 * pcoinsTip->GetCacheSize())) if (!CheckDiskSpace(48 * 2 * 2 * pcoinsTip->GetCacheSize()))
return state.Error("out of disk space"); return state.Error("out of disk space");
// Flush the chainstate (which may refer to block index entries). // Flush the chainstate (which may refer to block index entries).
if (!pcoinsTip->Flush()) if (!pcoinsTip->Flush())
@ -1917,7 +1898,7 @@ void static UpdateTip(CBlockIndex *pindexNew, const CChainParams& chainParams) {
DoWarning(strWarning); DoWarning(strWarning);
} }
} }
LogPrintf("%s: new best=%s height=%d version=0x%08x log2_work=%.8g tx=%lu date='%s' progress=%f cache=%.1fMiB(%utx)", __func__, LogPrintf("%s: new best=%s height=%d version=0x%08x log2_work=%.8g tx=%lu date='%s' progress=%f cache=%.1fMiB(%utxo)", __func__,
chainActive.Tip()->GetBlockHash().ToString(), chainActive.Height(), chainActive.Tip()->nVersion, chainActive.Tip()->GetBlockHash().ToString(), chainActive.Height(), chainActive.Tip()->nVersion,
log(chainActive.Tip()->nChainWork.getdouble())/log(2.0), (unsigned long)chainActive.Tip()->nChainTx, log(chainActive.Tip()->nChainWork.getdouble())/log(2.0), (unsigned long)chainActive.Tip()->nChainTx,
DateTimeStrFormat("%Y-%m-%d %H:%M:%S", chainActive.Tip()->GetBlockTime()), DateTimeStrFormat("%Y-%m-%d %H:%M:%S", chainActive.Tip()->GetBlockTime()),

View File

@ -406,8 +406,8 @@ private:
public: public:
CScriptCheck(): amount(0), ptxTo(0), nIn(0), nFlags(0), cacheStore(false), error(SCRIPT_ERR_UNKNOWN_ERROR) {} CScriptCheck(): amount(0), ptxTo(0), nIn(0), nFlags(0), cacheStore(false), error(SCRIPT_ERR_UNKNOWN_ERROR) {}
CScriptCheck(const CCoins& txFromIn, const CTransaction& txToIn, unsigned int nInIn, unsigned int nFlagsIn, bool cacheIn, PrecomputedTransactionData* txdataIn) : CScriptCheck(const CScript& scriptPubKeyIn, const CAmount amountIn, const CTransaction& txToIn, unsigned int nInIn, unsigned int nFlagsIn, bool cacheIn, PrecomputedTransactionData* txdataIn) :
scriptPubKey(txFromIn.vout[txToIn.vin[nInIn].prevout.n].scriptPubKey), amount(txFromIn.vout[txToIn.vin[nInIn].prevout.n].nValue), scriptPubKey(scriptPubKeyIn), amount(amountIn),
ptxTo(&txToIn), nIn(nInIn), nFlags(nFlagsIn), cacheStore(cacheIn), error(SCRIPT_ERR_UNKNOWN_ERROR), txdata(txdataIn) { } ptxTo(&txToIn), nIn(nInIn), nFlags(nFlagsIn), cacheStore(cacheIn), error(SCRIPT_ERR_UNKNOWN_ERROR), txdata(txdataIn) { }
bool operator()(); bool operator()();

View File

@ -49,10 +49,12 @@ class BlockchainTest(BitcoinTestFramework):
assert_equal(res['transactions'], 200) assert_equal(res['transactions'], 200)
assert_equal(res['height'], 200) assert_equal(res['height'], 200)
assert_equal(res['txouts'], 200) assert_equal(res['txouts'], 200)
assert_equal(res['bytes_serialized'], 13924),
assert_equal(res['bestblock'], node.getblockhash(200)) assert_equal(res['bestblock'], node.getblockhash(200))
size = res['disk_size']
assert size > 6400
assert size < 64000
assert_equal(len(res['bestblock']), 64) assert_equal(len(res['bestblock']), 64)
assert_equal(len(res['hash_serialized']), 64) assert_equal(len(res['hash_serialized_2']), 64)
self.log.info("Test that gettxoutsetinfo() works for blockchain with just the genesis block") self.log.info("Test that gettxoutsetinfo() works for blockchain with just the genesis block")
b1hash = node.getblockhash(1) b1hash = node.getblockhash(1)
@ -64,7 +66,7 @@ class BlockchainTest(BitcoinTestFramework):
assert_equal(res2['height'], 0) assert_equal(res2['height'], 0)
assert_equal(res2['txouts'], 0) assert_equal(res2['txouts'], 0)
assert_equal(res2['bestblock'], node.getblockhash(0)) assert_equal(res2['bestblock'], node.getblockhash(0))
assert_equal(len(res2['hash_serialized']), 64) assert_equal(len(res2['hash_serialized_2']), 64)
self.log.info("Test that gettxoutsetinfo() returns the same result after invalidate/reconsider block") self.log.info("Test that gettxoutsetinfo() returns the same result after invalidate/reconsider block")
node.reconsiderblock(b1hash) node.reconsiderblock(b1hash)
@ -75,7 +77,7 @@ class BlockchainTest(BitcoinTestFramework):
assert_equal(res['height'], res3['height']) assert_equal(res['height'], res3['height'])
assert_equal(res['txouts'], res3['txouts']) assert_equal(res['txouts'], res3['txouts'])
assert_equal(res['bestblock'], res3['bestblock']) assert_equal(res['bestblock'], res3['bestblock'])
assert_equal(res['hash_serialized'], res3['hash_serialized']) assert_equal(res['hash_serialized_2'], res3['hash_serialized_2'])
def _test_getblockheader(self): def _test_getblockheader(self):
node = self.nodes[0] node = self.nodes[0]