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:
commit
1088b02f0c
31 changed files with 1118 additions and 1064 deletions
|
@ -69,7 +69,7 @@ script:
|
|||
- ./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 )
|
||||
- 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 [ "$RUN_TESTS" = "true" ]; then test/functional/test_runner.py --coverage --quiet ${extended}; fi
|
||||
after_script:
|
||||
|
|
|
@ -36,6 +36,15 @@ Notable 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:
|
||||
- `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.
|
||||
|
|
|
@ -35,14 +35,14 @@ SetupDummyInputs(CBasicKeyStore& keystoreRet, CCoinsViewCache& coinsRet)
|
|||
dummyTransactions[0].vout[0].scriptPubKey << ToByteVector(key[0].GetPubKey()) << OP_CHECKSIG;
|
||||
dummyTransactions[0].vout[1].nValue = 50 * CENT;
|
||||
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[0].nValue = 21 * CENT;
|
||||
dummyTransactions[1].vout[0].scriptPubKey = GetScriptForDestination(key[2].GetPubKey().GetID());
|
||||
dummyTransactions[1].vout[1].nValue = 22 * CENT;
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -556,24 +556,26 @@ static void MutateTxSign(CMutableTransaction& tx, const std::string& flagStr)
|
|||
if (nOut < 0)
|
||||
throw std::runtime_error("vout must be positive");
|
||||
|
||||
COutPoint out(txid, nOut);
|
||||
std::vector<unsigned char> pkData(ParseHexUV(prevOut["scriptPubKey"], "scriptPubKey"));
|
||||
CScript scriptPubKey(pkData.begin(), pkData.end());
|
||||
|
||||
{
|
||||
CCoinsModifier coins = view.ModifyCoins(txid);
|
||||
if (coins->IsAvailable(nOut) && coins->vout[nOut].scriptPubKey != scriptPubKey) {
|
||||
const Coin& coin = view.AccessCoin(out);
|
||||
if (!coin.IsSpent() && coin.out.scriptPubKey != scriptPubKey) {
|
||||
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);
|
||||
throw std::runtime_error(err);
|
||||
}
|
||||
if ((unsigned int)nOut >= coins->vout.size())
|
||||
coins->vout.resize(nOut+1);
|
||||
coins->vout[nOut].scriptPubKey = scriptPubKey;
|
||||
coins->vout[nOut].nValue = 0;
|
||||
Coin newcoin;
|
||||
newcoin.out.scriptPubKey = scriptPubKey;
|
||||
newcoin.out.nValue = 0;
|
||||
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,
|
||||
|
@ -595,13 +597,13 @@ static void MutateTxSign(CMutableTransaction& tx, const std::string& flagStr)
|
|||
// Sign what we can:
|
||||
for (unsigned int i = 0; i < mergedTx.vin.size(); i++) {
|
||||
CTxIn& txin = mergedTx.vin[i];
|
||||
const CCoins* coins = view.AccessCoins(txin.prevout.hash);
|
||||
if (!coins || !coins->IsAvailable(txin.prevout.n)) {
|
||||
const Coin& coin = view.AccessCoin(txin.prevout);
|
||||
if (coin.IsSpent()) {
|
||||
fComplete = false;
|
||||
continue;
|
||||
}
|
||||
const CScript& prevPubKey = coins->vout[txin.prevout.n].scriptPubKey;
|
||||
const CAmount& amount = coins->vout[txin.prevout.n].nValue;
|
||||
const CScript& prevPubKey = coin.out.scriptPubKey;
|
||||
const CAmount& amount = coin.out.nValue;
|
||||
|
||||
SignatureData sigdata;
|
||||
// Only sign SIGHASH_SINGLE if there's a corresponding output:
|
||||
|
|
250
src/coins.cpp
250
src/coins.cpp
|
@ -4,174 +4,126 @@
|
|||
|
||||
#include "coins.h"
|
||||
|
||||
#include "consensus/consensus.h"
|
||||
#include "memusage.h"
|
||||
#include "random.h"
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
/**
|
||||
* calculate number of bytes for the bitmask, and its number of non-zero bytes
|
||||
* 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; }
|
||||
bool CCoinsView::GetCoin(const COutPoint &outpoint, Coin &coin) const { return false; }
|
||||
bool CCoinsView::HaveCoin(const COutPoint &outpoint) const { return false; }
|
||||
uint256 CCoinsView::GetBestBlock() const { return uint256(); }
|
||||
bool CCoinsView::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock) { return false; }
|
||||
CCoinsViewCursor *CCoinsView::Cursor() const { return 0; }
|
||||
|
||||
|
||||
CCoinsViewBacked::CCoinsViewBacked(CCoinsView *viewIn) : base(viewIn) { }
|
||||
bool CCoinsViewBacked::GetCoins(const uint256 &txid, CCoins &coins) const { return base->GetCoins(txid, coins); }
|
||||
bool CCoinsViewBacked::HaveCoins(const uint256 &txid) const { return base->HaveCoins(txid); }
|
||||
bool CCoinsViewBacked::GetCoin(const COutPoint &outpoint, Coin &coin) const { return base->GetCoin(outpoint, coin); }
|
||||
bool CCoinsViewBacked::HaveCoin(const COutPoint &outpoint) const { return base->HaveCoin(outpoint); }
|
||||
uint256 CCoinsViewBacked::GetBestBlock() const { return base->GetBestBlock(); }
|
||||
void CCoinsViewBacked::SetBackend(CCoinsView &viewIn) { base = &viewIn; }
|
||||
bool CCoinsViewBacked::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock) { return base->BatchWrite(mapCoins, hashBlock); }
|
||||
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()
|
||||
{
|
||||
assert(!hasModifier);
|
||||
}
|
||||
CCoinsViewCache::CCoinsViewCache(CCoinsView *baseIn) : CCoinsViewBacked(baseIn), cachedCoinsUsage(0) {}
|
||||
|
||||
size_t CCoinsViewCache::DynamicMemoryUsage() const {
|
||||
return memusage::DynamicUsage(cacheCoins) + cachedCoinsUsage;
|
||||
}
|
||||
|
||||
CCoinsMap::const_iterator CCoinsViewCache::FetchCoins(const uint256 &txid) const {
|
||||
CCoinsMap::iterator it = cacheCoins.find(txid);
|
||||
CCoinsMap::iterator CCoinsViewCache::FetchCoin(const COutPoint &outpoint) const {
|
||||
CCoinsMap::iterator it = cacheCoins.find(outpoint);
|
||||
if (it != cacheCoins.end())
|
||||
return it;
|
||||
CCoins tmp;
|
||||
if (!base->GetCoins(txid, tmp))
|
||||
Coin tmp;
|
||||
if (!base->GetCoin(outpoint, tmp))
|
||||
return cacheCoins.end();
|
||||
CCoinsMap::iterator ret = cacheCoins.insert(std::make_pair(txid, CCoinsCacheEntry())).first;
|
||||
tmp.swap(ret->second.coins);
|
||||
if (ret->second.coins.IsPruned()) {
|
||||
// The parent only has an empty entry for this txid; we can consider our
|
||||
CCoinsMap::iterator ret = cacheCoins.emplace(std::piecewise_construct, std::forward_as_tuple(outpoint), std::forward_as_tuple(std::move(tmp))).first;
|
||||
if (ret->second.coin.IsSpent()) {
|
||||
// The parent only has an empty entry for this outpoint; we can consider our
|
||||
// version as fresh.
|
||||
ret->second.flags = CCoinsCacheEntry::FRESH;
|
||||
}
|
||||
cachedCoinsUsage += ret->second.coins.DynamicMemoryUsage();
|
||||
cachedCoinsUsage += ret->second.coin.DynamicMemoryUsage();
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool CCoinsViewCache::GetCoins(const uint256 &txid, CCoins &coins) const {
|
||||
CCoinsMap::const_iterator it = FetchCoins(txid);
|
||||
bool CCoinsViewCache::GetCoin(const COutPoint &outpoint, Coin &coin) const {
|
||||
CCoinsMap::const_iterator it = FetchCoin(outpoint);
|
||||
if (it != cacheCoins.end()) {
|
||||
coins = it->second.coins;
|
||||
coin = it->second.coin;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
CCoinsModifier CCoinsViewCache::ModifyCoins(const uint256 &txid) {
|
||||
assert(!hasModifier);
|
||||
std::pair<CCoinsMap::iterator, bool> ret = cacheCoins.insert(std::make_pair(txid, CCoinsCacheEntry()));
|
||||
size_t cachedCoinUsage = 0;
|
||||
if (ret.second) {
|
||||
if (!base->GetCoins(txid, ret.first->second.coins)) {
|
||||
// The parent view does not have this entry; mark it as fresh.
|
||||
ret.first->second.coins.Clear();
|
||||
ret.first->second.flags = CCoinsCacheEntry::FRESH;
|
||||
} else if (ret.first->second.coins.IsPruned()) {
|
||||
// The parent view only has a pruned entry for this; mark it as fresh.
|
||||
ret.first->second.flags = CCoinsCacheEntry::FRESH;
|
||||
void CCoinsViewCache::AddCoin(const COutPoint &outpoint, Coin&& coin, bool possible_overwrite) {
|
||||
assert(!coin.IsSpent());
|
||||
if (coin.out.scriptPubKey.IsUnspendable()) return;
|
||||
CCoinsMap::iterator it;
|
||||
bool inserted;
|
||||
std::tie(it, inserted) = cacheCoins.emplace(std::piecewise_construct, std::forward_as_tuple(outpoint), std::tuple<>());
|
||||
bool fresh = false;
|
||||
if (!inserted) {
|
||||
cachedCoinsUsage -= it->second.coin.DynamicMemoryUsage();
|
||||
}
|
||||
if (!possible_overwrite) {
|
||||
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 {
|
||||
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
|
||||
* 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!");
|
||||
static const Coin coinEmpty;
|
||||
|
||||
if (!(ret.first->second.flags & CCoinsCacheEntry::DIRTY)) {
|
||||
// If the coin is known to be pruned (have no unspent outputs) in
|
||||
// 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);
|
||||
const Coin& CCoinsViewCache::AccessCoin(const COutPoint &outpoint) const {
|
||||
CCoinsMap::const_iterator it = FetchCoin(outpoint);
|
||||
if (it == cacheCoins.end()) {
|
||||
return NULL;
|
||||
return coinEmpty;
|
||||
} else {
|
||||
return &it->second.coins;
|
||||
return it->second.coin;
|
||||
}
|
||||
}
|
||||
|
||||
bool CCoinsViewCache::HaveCoins(const uint256 &txid) const {
|
||||
CCoinsMap::const_iterator it = FetchCoins(txid);
|
||||
// We're using vtx.empty() instead of IsPruned here for performance reasons,
|
||||
// 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::HaveCoin(const COutPoint &outpoint) const {
|
||||
CCoinsMap::const_iterator it = FetchCoin(outpoint);
|
||||
return (it != cacheCoins.end() && !it->second.coin.IsSpent());
|
||||
}
|
||||
|
||||
bool CCoinsViewCache::HaveCoinsInCache(const uint256 &txid) const {
|
||||
CCoinsMap::const_iterator it = cacheCoins.find(txid);
|
||||
bool CCoinsViewCache::HaveCoinInCache(const COutPoint &outpoint) const {
|
||||
CCoinsMap::const_iterator it = cacheCoins.find(outpoint);
|
||||
return it != cacheCoins.end();
|
||||
}
|
||||
|
||||
|
@ -186,19 +138,18 @@ void CCoinsViewCache::SetBestBlock(const uint256 &hashBlockIn) {
|
|||
}
|
||||
|
||||
bool CCoinsViewCache::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlockIn) {
|
||||
assert(!hasModifier);
|
||||
for (CCoinsMap::iterator it = mapCoins.begin(); it != mapCoins.end();) {
|
||||
if (it->second.flags & CCoinsCacheEntry::DIRTY) { // Ignore non-dirty entries (optimization).
|
||||
CCoinsMap::iterator itUs = cacheCoins.find(it->first);
|
||||
if (itUs == cacheCoins.end()) {
|
||||
// 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
|
||||
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
|
||||
// and move the data up and mark it as dirty
|
||||
CCoinsCacheEntry& entry = cacheCoins[it->first];
|
||||
entry.coins.swap(it->second.coins);
|
||||
cachedCoinsUsage += entry.coins.DynamicMemoryUsage();
|
||||
entry.coin = std::move(it->second.coin);
|
||||
cachedCoinsUsage += entry.coin.DynamicMemoryUsage();
|
||||
entry.flags = CCoinsCacheEntry::DIRTY;
|
||||
// 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
|
||||
|
@ -211,21 +162,21 @@ bool CCoinsViewCache::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlockIn
|
|||
// parent cache entry has unspent outputs. If this ever happens,
|
||||
// it means the FRESH flag was misapplied and there is a logic
|
||||
// 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");
|
||||
|
||||
// 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
|
||||
// modified and being pruned. This means we can just delete
|
||||
// it from the parent.
|
||||
cachedCoinsUsage -= itUs->second.coins.DynamicMemoryUsage();
|
||||
cachedCoinsUsage -= itUs->second.coin.DynamicMemoryUsage();
|
||||
cacheCoins.erase(itUs);
|
||||
} else {
|
||||
// A normal modification.
|
||||
cachedCoinsUsage -= itUs->second.coins.DynamicMemoryUsage();
|
||||
itUs->second.coins.swap(it->second.coins);
|
||||
cachedCoinsUsage += itUs->second.coins.DynamicMemoryUsage();
|
||||
cachedCoinsUsage -= itUs->second.coin.DynamicMemoryUsage();
|
||||
itUs->second.coin = std::move(it->second.coin);
|
||||
cachedCoinsUsage += itUs->second.coin.DynamicMemoryUsage();
|
||||
itUs->second.flags |= CCoinsCacheEntry::DIRTY;
|
||||
// NOTE: It is possible the child has a FRESH flag here in
|
||||
// the event the entry we found in the parent is pruned. But
|
||||
|
@ -249,11 +200,11 @@ bool CCoinsViewCache::Flush() {
|
|||
return fOk;
|
||||
}
|
||||
|
||||
void CCoinsViewCache::Uncache(const uint256& hash)
|
||||
void CCoinsViewCache::Uncache(const COutPoint& hash)
|
||||
{
|
||||
CCoinsMap::iterator it = cacheCoins.find(hash);
|
||||
if (it != cacheCoins.end() && it->second.flags == 0) {
|
||||
cachedCoinsUsage -= it->second.coins.DynamicMemoryUsage();
|
||||
cachedCoinsUsage -= it->second.coin.DynamicMemoryUsage();
|
||||
cacheCoins.erase(it);
|
||||
}
|
||||
}
|
||||
|
@ -262,13 +213,6 @@ unsigned int CCoinsViewCache::GetCacheSize() const {
|
|||
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
|
||||
{
|
||||
if (tx.IsCoinBase())
|
||||
|
@ -276,7 +220,7 @@ CAmount CCoinsViewCache::GetValueIn(const CTransaction& tx) const
|
|||
|
||||
CAmount nResult = 0;
|
||||
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;
|
||||
}
|
||||
|
@ -285,9 +229,7 @@ bool CCoinsViewCache::HaveInputs(const CTransaction& tx) const
|
|||
{
|
||||
if (!tx.IsCoinBase()) {
|
||||
for (unsigned int i = 0; i < tx.vin.size(); i++) {
|
||||
const COutPoint &prevout = tx.vin[i].prevout;
|
||||
const CCoins* coins = AccessCoins(prevout.hash);
|
||||
if (!coins || !coins->IsAvailable(prevout.n)) {
|
||||
if (!HaveCoin(tx.vin[i].prevout)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -295,25 +237,15 @@ bool CCoinsViewCache::HaveInputs(const CTransaction& tx) const
|
|||
return true;
|
||||
}
|
||||
|
||||
CCoinsModifier::CCoinsModifier(CCoinsViewCache& cache_, CCoinsMap::iterator it_, size_t usage) : cache(cache_), it(it_), cachedCoinUsage(usage) {
|
||||
assert(!cache.hasModifier);
|
||||
cache.hasModifier = true;
|
||||
}
|
||||
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.
|
||||
|
||||
CCoinsModifier::~CCoinsModifier()
|
||||
const Coin& AccessByTxid(const CCoinsViewCache& view, const uint256& txid)
|
||||
{
|
||||
assert(cache.hasModifier);
|
||||
cache.hasModifier = false;
|
||||
it->second.coins.Cleanup();
|
||||
cache.cachedCoinsUsage -= cachedCoinUsage; // Subtract the old usage
|
||||
if ((it->second.flags & CCoinsCacheEntry::FRESH) && it->second.coins.IsPruned()) {
|
||||
cache.cacheCoins.erase(it);
|
||||
} else {
|
||||
// If the coin still exists after the modification, add the new usage
|
||||
cache.cachedCoinsUsage += it->second.coins.DynamicMemoryUsage();
|
||||
COutPoint iter(txid, 0);
|
||||
while (iter.n < MAX_OUTPUTS_PER_BLOCK) {
|
||||
const Coin& alternate = view.AccessCoin(iter);
|
||||
if (!alternate.IsSpent()) return alternate;
|
||||
++iter.n;
|
||||
}
|
||||
}
|
||||
|
||||
CCoinsViewCursor::~CCoinsViewCursor()
|
||||
{
|
||||
return coinEmpty;
|
||||
}
|
||||
|
|
349
src/coins.h
349
src/coins.h
|
@ -20,135 +20,37 @@
|
|||
#include <boost/foreach.hpp>
|
||||
#include <unordered_map>
|
||||
|
||||
/**
|
||||
* Pruned version of CTransaction: only retains metadata and unspent transaction outputs
|
||||
/**
|
||||
* A UTXO entry.
|
||||
*
|
||||
* Serialized format:
|
||||
* - VARINT(nVersion)
|
||||
* - VARINT(nCode)
|
||||
* - 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
|
||||
* - VARINT((coinbase ? 1 : 0) | (height << 1))
|
||||
* - the non-spent CTxOut (via CTxOutCompressor)
|
||||
*/
|
||||
class CCoins
|
||||
class Coin
|
||||
{
|
||||
public:
|
||||
//! whether transaction is a coinbase
|
||||
bool fCoinBase;
|
||||
//! unspent transaction output
|
||||
CTxOut out;
|
||||
|
||||
//! unspent transaction outputs; spent outputs are .IsNull(); spent outputs at the end of the array are dropped
|
||||
std::vector<CTxOut> vout;
|
||||
//! whether containing transaction was a coinbase
|
||||
unsigned int fCoinBase : 1;
|
||||
|
||||
//! at which height this transaction was included in the active block chain
|
||||
int nHeight;
|
||||
//! at which height this containing transaction was included in the active block chain
|
||||
uint32_t nHeight : 31;
|
||||
|
||||
//! version of the CTransaction; accesses to this value should probably check for nHeight as well,
|
||||
//! as new tx version will probably only be introduced at certain heights
|
||||
int nVersion;
|
||||
|
||||
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);
|
||||
}
|
||||
//! construct a Coin from a CTxOut and height/coinbase information.
|
||||
Coin(CTxOut&& outIn, int nHeightIn, bool fCoinBaseIn) : out(std::move(outIn)), fCoinBase(fCoinBaseIn), nHeight(nHeightIn) {}
|
||||
Coin(const CTxOut& outIn, int nHeightIn, bool fCoinBaseIn) : out(outIn), fCoinBase(fCoinBaseIn),nHeight(nHeightIn) {}
|
||||
|
||||
void Clear() {
|
||||
out.SetNull();
|
||||
fCoinBase = false;
|
||||
std::vector<CTxOut>().swap(vout);
|
||||
nHeight = 0;
|
||||
nVersion = 0;
|
||||
}
|
||||
|
||||
//! empty constructor
|
||||
CCoins() : fCoinBase(false), vout(0), nHeight(0), nVersion(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;
|
||||
Coin() : fCoinBase(false), nHeight(0) { }
|
||||
|
||||
bool IsCoinBase() const {
|
||||
return fCoinBase;
|
||||
|
@ -156,115 +58,52 @@ public:
|
|||
|
||||
template<typename Stream>
|
||||
void Serialize(Stream &s) const {
|
||||
unsigned int nMaskSize = 0, nMaskCode = 0;
|
||||
CalcMaskSize(nMaskSize, nMaskCode);
|
||||
bool fFirst = vout.size() > 0 && !vout[0].IsNull();
|
||||
bool fSecond = vout.size() > 1 && !vout[1].IsNull();
|
||||
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));
|
||||
assert(!IsSpent());
|
||||
uint32_t code = nHeight * 2 + fCoinBase;
|
||||
::Serialize(s, VARINT(code));
|
||||
::Serialize(s, CTxOutCompressor(REF(out)));
|
||||
}
|
||||
|
||||
template<typename Stream>
|
||||
void Unserialize(Stream &s) {
|
||||
unsigned int nCode = 0;
|
||||
// version
|
||||
::Unserialize(s, VARINT(this->nVersion));
|
||||
// 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));
|
||||
Cleanup();
|
||||
uint32_t code = 0;
|
||||
::Unserialize(s, VARINT(code));
|
||||
nHeight = code >> 1;
|
||||
fCoinBase = code & 1;
|
||||
::Unserialize(s, REF(CTxOutCompressor(out)));
|
||||
}
|
||||
|
||||
//! mark a vout spent
|
||||
bool Spend(uint32_t nPos);
|
||||
|
||||
//! 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;
|
||||
bool IsSpent() const {
|
||||
return out.IsNull();
|
||||
}
|
||||
|
||||
size_t DynamicMemoryUsage() const {
|
||||
size_t ret = memusage::DynamicUsage(vout);
|
||||
BOOST_FOREACH(const CTxOut &out, vout) {
|
||||
ret += RecursiveDynamicUsage(out.scriptPubKey);
|
||||
}
|
||||
return ret;
|
||||
return memusage::DynamicUsage(out.scriptPubKey);
|
||||
}
|
||||
};
|
||||
|
||||
class SaltedTxidHasher
|
||||
class SaltedOutpointHasher
|
||||
{
|
||||
private:
|
||||
/** Salt */
|
||||
const uint64_t k0, k1;
|
||||
|
||||
public:
|
||||
SaltedTxidHasher();
|
||||
SaltedOutpointHasher();
|
||||
|
||||
/**
|
||||
* 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
|
||||
* uint64_t, resulting in failures when syncing the chain (#4634).
|
||||
*/
|
||||
size_t operator()(const uint256& txid) const {
|
||||
return SipHashUint256(k0, k1, txid);
|
||||
size_t operator()(const COutPoint& id) const {
|
||||
return SipHashUint256Extra(k0, k1, id.hash, id.n);
|
||||
}
|
||||
};
|
||||
|
||||
struct CCoinsCacheEntry
|
||||
{
|
||||
CCoins coins; // The actual cached data.
|
||||
Coin coin; // The actual cached data.
|
||||
unsigned char 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 */
|
||||
class CCoinsViewCursor
|
||||
{
|
||||
public:
|
||||
CCoinsViewCursor(const uint256 &hashBlockIn): hashBlock(hashBlockIn) {}
|
||||
virtual ~CCoinsViewCursor();
|
||||
virtual ~CCoinsViewCursor() {}
|
||||
|
||||
virtual bool GetKey(uint256 &key) const = 0;
|
||||
virtual bool GetValue(CCoins &coins) const = 0;
|
||||
virtual bool GetKey(COutPoint &key) const = 0;
|
||||
virtual bool GetValue(Coin &coin) const = 0;
|
||||
virtual unsigned int GetValueSize() const = 0;
|
||||
|
||||
virtual bool Valid() const = 0;
|
||||
|
@ -306,17 +146,17 @@ private:
|
|||
class CCoinsView
|
||||
{
|
||||
public:
|
||||
//! Retrieve the CCoins (unspent transaction outputs) for a given txid
|
||||
virtual bool GetCoins(const uint256 &txid, CCoins &coins) const;
|
||||
//! Retrieve the Coin (unspent transaction output) for a given outpoint.
|
||||
virtual bool GetCoin(const COutPoint &outpoint, Coin &coin) const;
|
||||
|
||||
//! Just check whether we have data for a given txid.
|
||||
//! This may (but cannot always) return true for fully spent transactions
|
||||
virtual bool HaveCoins(const uint256 &txid) const;
|
||||
//! Just check whether we have data for a given outpoint.
|
||||
//! This may (but cannot always) return true for spent outputs.
|
||||
virtual bool HaveCoin(const COutPoint &outpoint) const;
|
||||
|
||||
//! Retrieve the block hash whose state this CCoinsView currently represents
|
||||
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.
|
||||
virtual bool BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock);
|
||||
|
||||
|
@ -325,6 +165,9 @@ public:
|
|||
|
||||
//! As we use CCoinsViews polymorphically, have a virtual destructor
|
||||
virtual ~CCoinsView() {}
|
||||
|
||||
//! Estimate database size (0 if not implemented)
|
||||
virtual size_t EstimateSize() const { return 0; }
|
||||
};
|
||||
|
||||
|
||||
|
@ -336,45 +179,20 @@ protected:
|
|||
|
||||
public:
|
||||
CCoinsViewBacked(CCoinsView *viewIn);
|
||||
bool GetCoins(const uint256 &txid, CCoins &coins) const;
|
||||
bool HaveCoins(const uint256 &txid) const;
|
||||
uint256 GetBestBlock() const;
|
||||
bool GetCoin(const COutPoint &outpoint, Coin &coin) const override;
|
||||
bool HaveCoin(const COutPoint &outpoint) const override;
|
||||
uint256 GetBestBlock() const override;
|
||||
void SetBackend(CCoinsView &viewIn);
|
||||
bool BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock);
|
||||
CCoinsViewCursor *Cursor() const;
|
||||
bool BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock) override;
|
||||
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 */
|
||||
class CCoinsViewCache : public CCoinsViewBacked
|
||||
{
|
||||
protected:
|
||||
/* Whether this cache has an active modifier. */
|
||||
bool hasModifier;
|
||||
|
||||
|
||||
/**
|
||||
* Make mutable so that we can "fill the cache" even from Get-methods
|
||||
* declared as "const".
|
||||
|
@ -382,51 +200,45 @@ protected:
|
|||
mutable uint256 hashBlock;
|
||||
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;
|
||||
|
||||
public:
|
||||
CCoinsViewCache(CCoinsView *baseIn);
|
||||
~CCoinsViewCache();
|
||||
|
||||
// Standard CCoinsView methods
|
||||
bool GetCoins(const uint256 &txid, CCoins &coins) const;
|
||||
bool HaveCoins(const uint256 &txid) const;
|
||||
bool GetCoin(const COutPoint &outpoint, Coin &coin) const;
|
||||
bool HaveCoin(const COutPoint &outpoint) const;
|
||||
uint256 GetBestBlock() const;
|
||||
void SetBestBlock(const uint256 &hashBlock);
|
||||
bool BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock);
|
||||
|
||||
/**
|
||||
* Check if we have the given tx already loaded in this cache.
|
||||
* The semantics are the same as HaveCoins(), but no calls to
|
||||
* Check if we have the given utxo already loaded in this cache.
|
||||
* The semantics are the same as HaveCoin(), but no calls to
|
||||
* 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
|
||||
* more efficient than GetCoins. Modifications to other cache entries are
|
||||
* Return a reference to Coin in the cache, or a pruned one if not found. This is
|
||||
* more efficient than GetCoin. Modifications to other cache entries are
|
||||
* 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
|
||||
* txid exists, a new one is created. Simultaneous modifications are not
|
||||
* allowed.
|
||||
* Add a coin. Set potential_overwrite to true if a non-pruned version may
|
||||
* already exist.
|
||||
*/
|
||||
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
|
||||
* txid exists and creates a new one. This saves a database access in the case where
|
||||
* the coins were to be wiped out by FromTx anyway. This should not be called with
|
||||
* 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.
|
||||
* Spend a coin. Pass moveto in order to get the deleted data.
|
||||
* If no unspent output exists for the passed outpoint, this call
|
||||
* has no effect.
|
||||
*/
|
||||
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.
|
||||
|
@ -436,12 +248,12 @@ public:
|
|||
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.
|
||||
*/
|
||||
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;
|
||||
|
||||
//! 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
|
||||
bool HaveInputs(const CTransaction& tx) const;
|
||||
|
||||
const CTxOut &GetOutputFor(const CTxIn& input) const;
|
||||
|
||||
friend class CCoinsModifier;
|
||||
|
||||
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.
|
||||
|
@ -473,4 +281,13 @@ private:
|
|||
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
|
||||
|
|
|
@ -126,7 +126,7 @@ unsigned int GetP2SHSigOpCount(const CTransaction& tx, const CCoinsViewCache& in
|
|||
unsigned int nSigOps = 0;
|
||||
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())
|
||||
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++)
|
||||
{
|
||||
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);
|
||||
}
|
||||
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++)
|
||||
{
|
||||
const COutPoint &prevout = tx.vin[i].prevout;
|
||||
const CCoins *coins = inputs.AccessCoins(prevout.hash);
|
||||
assert(coins);
|
||||
const Coin& coin = inputs.AccessCoin(prevout);
|
||||
assert(!coin.IsSpent());
|
||||
|
||||
// If prev is coinbase, check that it's matured
|
||||
if (coins->IsCoinBase()) {
|
||||
if (nSpendHeight - coins->nHeight < COINBASE_MATURITY)
|
||||
if (coin.IsCoinBase()) {
|
||||
if (nSpendHeight - coin.nHeight < COINBASE_MATURITY)
|
||||
return state.Invalid(false,
|
||||
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
|
||||
nValueIn += coins->vout[prevout.n].nValue;
|
||||
if (!MoneyRange(coins->vout[prevout.n].nValue) || !MoneyRange(nValueIn))
|
||||
nValueIn += coin.out.nValue;
|
||||
if (!MoneyRange(coin.out.nValue) || !MoneyRange(nValueIn))
|
||||
return state.DoS(100, false, REJECT_INVALID, "bad-txns-inputvalues-outofrange");
|
||||
|
||||
}
|
||||
|
|
|
@ -55,11 +55,19 @@ private:
|
|||
CDataStream ssKey;
|
||||
CDataStream ssValue;
|
||||
|
||||
size_t size_estimate;
|
||||
|
||||
public:
|
||||
/**
|
||||
* @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>
|
||||
void Write(const K& key, const V& value)
|
||||
|
@ -74,6 +82,14 @@ public:
|
|||
leveldb::Slice slValue(ssValue.data(), ssValue.size());
|
||||
|
||||
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();
|
||||
ssValue.clear();
|
||||
}
|
||||
|
@ -86,8 +102,16 @@ public:
|
|||
leveldb::Slice slKey(ssKey.data(), ssKey.size());
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
size_t SizeEstimate() const { return size_estimate; }
|
||||
};
|
||||
|
||||
class CDBIterator
|
||||
|
@ -281,7 +305,22 @@ public:
|
|||
* Return true if the database managed by this class contains no entries.
|
||||
*/
|
||||
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
|
||||
|
||||
|
|
41
src/hash.cpp
41
src/hash.cpp
|
@ -208,3 +208,44 @@ uint64_t SipHashUint256(uint64_t k0, uint64_t k1, const uint256& val)
|
|||
SIPROUND;
|
||||
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;
|
||||
}
|
||||
|
|
36
src/hash.h
36
src/hash.h
|
@ -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. */
|
||||
template<typename T>
|
||||
uint256 SerializeHash(const T& obj, int nType=SER_GETHASH, int nVersion=PROTOCOL_VERSION)
|
||||
|
@ -206,5 +241,6 @@ public:
|
|||
* .Finalize()
|
||||
*/
|
||||
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
|
||||
|
|
10
src/init.cpp
10
src/init.cpp
|
@ -146,9 +146,9 @@ class CCoinsViewErrorCatcher : public CCoinsViewBacked
|
|||
{
|
||||
public:
|
||||
CCoinsViewErrorCatcher(CCoinsView* view) : CCoinsViewBacked(view) {}
|
||||
bool GetCoins(const uint256 &txid, CCoins &coins) const {
|
||||
bool GetCoin(const COutPoint &outpoint, Coin &coin) const override {
|
||||
try {
|
||||
return CCoinsViewBacked::GetCoins(txid, coins);
|
||||
return CCoinsViewBacked::GetCoin(outpoint, coin);
|
||||
} catch(const std::runtime_error& e) {
|
||||
uiInterface.ThreadSafeMessageBox(_("Error reading from database, shutting down."), "", CClientUIInterface::MSG_ERROR);
|
||||
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 (fPruneMode)
|
||||
CleanupBlockRevFiles();
|
||||
} else {
|
||||
// If necessary, upgrade from older database format.
|
||||
if (!pcoinsdbview->Upgrade()) {
|
||||
strLoadError = _("Error upgrading chainstate database");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!LoadBlockIndex(chainparams)) {
|
||||
|
|
|
@ -911,12 +911,11 @@ bool static AlreadyHave(const CInv& inv) EXCLUSIVE_LOCKS_REQUIRED(cs_main)
|
|||
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) ||
|
||||
mempool.exists(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_WITNESS_BLOCK:
|
||||
|
|
|
@ -165,7 +165,7 @@ bool AreInputsStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs)
|
|||
|
||||
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;
|
||||
txnouttype whichType;
|
||||
|
@ -204,7 +204,7 @@ bool IsWitnessStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs)
|
|||
if (tx.vin[i].scriptWitness.IsNull())
|
||||
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:
|
||||
CScript prevScript = prev.scriptPubKey;
|
||||
|
|
|
@ -293,13 +293,12 @@ QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx, TransactionReco
|
|||
{
|
||||
COutPoint prevout = txin.prevout;
|
||||
|
||||
CCoins prev;
|
||||
if(pcoinsTip->GetCoins(prevout.hash, prev))
|
||||
Coin prev;
|
||||
if(pcoinsTip->GetCoin(prevout, prev))
|
||||
{
|
||||
if (prevout.n < prev.vout.size())
|
||||
{
|
||||
strHTML += "<li>";
|
||||
const CTxOut &vout = prev.vout[prevout.n];
|
||||
const CTxOut &vout = prev.out;
|
||||
CTxDestination address;
|
||||
if (ExtractDestination(vout.scriptPubKey, address))
|
||||
{
|
||||
|
|
27
src/rest.cpp
27
src/rest.cpp
|
@ -42,16 +42,19 @@ static const struct {
|
|||
};
|
||||
|
||||
struct CCoin {
|
||||
uint32_t nTxVer; // Don't call this nVersion, that name has a special meaning inside IMPLEMENT_SERIALIZE
|
||||
uint32_t nHeight;
|
||||
CTxOut out;
|
||||
|
||||
ADD_SERIALIZE_METHODS;
|
||||
|
||||
CCoin() : nHeight(0) {}
|
||||
CCoin(Coin&& in) : nHeight(in.nHeight), out(std::move(in.out)) {}
|
||||
|
||||
template <typename Stream, typename Operation>
|
||||
inline void SerializationOp(Stream& s, Operation ser_action)
|
||||
{
|
||||
READWRITE(nTxVer);
|
||||
uint32_t nTxVerDummy = 0;
|
||||
READWRITE(nTxVerDummy);
|
||||
READWRITE(nHeight);
|
||||
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
|
||||
|
||||
for (size_t i = 0; i < vOutPoints.size(); i++) {
|
||||
CCoins coins;
|
||||
uint256 hash = vOutPoints[i].hash;
|
||||
bool hit = false;
|
||||
if (view.GetCoins(hash, coins)) {
|
||||
mempool.pruneSpent(hash, coins);
|
||||
if (coins.IsAvailable(vOutPoints[i].n)) {
|
||||
hit = true;
|
||||
// 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);
|
||||
}
|
||||
Coin coin;
|
||||
if (view.GetCoin(vOutPoints[i], coin) && !mempool.isSpent(vOutPoints[i])) {
|
||||
hit = true;
|
||||
outs.emplace_back(std::move(coin));
|
||||
}
|
||||
|
||||
hits.push_back(hit);
|
||||
|
@ -568,7 +560,6 @@ static bool rest_getutxos(HTTPRequest* req, const std::string& strURIPart)
|
|||
UniValue utxos(UniValue::VARR);
|
||||
BOOST_FOREACH (const CCoin& coin, outs) {
|
||||
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("value", ValueFromAmount(coin.out.nValue)));
|
||||
|
||||
|
|
|
@ -781,13 +781,29 @@ struct CCoinsStats
|
|||
uint256 hashBlock;
|
||||
uint64_t nTransactions;
|
||||
uint64_t nTransactionOutputs;
|
||||
uint64_t nSerializedSize;
|
||||
uint256 hashSerialized;
|
||||
uint64_t nDiskSize;
|
||||
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
|
||||
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;
|
||||
}
|
||||
ss << stats.hashBlock;
|
||||
CAmount nTotalAmount = 0;
|
||||
uint256 prevkey;
|
||||
std::map<uint32_t, Coin> outputs;
|
||||
while (pcursor->Valid()) {
|
||||
boost::this_thread::interruption_point();
|
||||
uint256 key;
|
||||
CCoins coins;
|
||||
if (pcursor->GetKey(key) && pcursor->GetValue(coins)) {
|
||||
stats.nTransactions++;
|
||||
ss << key;
|
||||
for (unsigned int i=0; i<coins.vout.size(); i++) {
|
||||
const CTxOut &out = coins.vout[i];
|
||||
if (!out.IsNull()) {
|
||||
stats.nTransactionOutputs++;
|
||||
ss << VARINT(i+1);
|
||||
ss << out;
|
||||
nTotalAmount += out.nValue;
|
||||
}
|
||||
COutPoint key;
|
||||
Coin coin;
|
||||
if (pcursor->GetKey(key) && pcursor->GetValue(coin)) {
|
||||
if (!outputs.empty() && key.hash != prevkey) {
|
||||
ApplyStats(stats, ss, prevkey, outputs);
|
||||
outputs.clear();
|
||||
}
|
||||
stats.nSerializedSize += 32 + pcursor->GetValueSize();
|
||||
ss << VARINT(0);
|
||||
prevkey = key.hash;
|
||||
outputs[key.n] = std::move(coin);
|
||||
} else {
|
||||
return error("%s: unable to read value", __func__);
|
||||
}
|
||||
pcursor->Next();
|
||||
}
|
||||
if (!outputs.empty()) {
|
||||
ApplyStats(stats, ss, prevkey, outputs);
|
||||
}
|
||||
stats.hashSerialized = ss.GetHash();
|
||||
stats.nTotalAmount = nTotalAmount;
|
||||
stats.nDiskSize = view->EstimateSize();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -891,8 +904,8 @@ UniValue gettxoutsetinfo(const JSONRPCRequest& request)
|
|||
" \"bestblock\": \"hex\", (string) the best block hash hex\n"
|
||||
" \"transactions\": n, (numeric) The number of 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"
|
||||
" \"disk_size\": n, (numeric) The estimated size of the chainstate on disk\n"
|
||||
" \"total_amount\": x.xxx (numeric) The total amount\n"
|
||||
"}\n"
|
||||
"\nExamples:\n"
|
||||
|
@ -909,8 +922,8 @@ UniValue gettxoutsetinfo(const JSONRPCRequest& request)
|
|||
ret.push_back(Pair("bestblock", stats.hashBlock.GetHex()));
|
||||
ret.push_back(Pair("transactions", (int64_t)stats.nTransactions));
|
||||
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", stats.hashSerialized.GetHex()));
|
||||
ret.push_back(Pair("hash_serialized_2", stats.hashSerialized.GetHex()));
|
||||
ret.push_back(Pair("disk_size", stats.nDiskSize));
|
||||
ret.push_back(Pair("total_amount", ValueFromAmount(stats.nTotalAmount)));
|
||||
} else {
|
||||
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();
|
||||
uint256 hash(uint256S(strHash));
|
||||
int n = request.params[1].get_int();
|
||||
COutPoint out(hash, n);
|
||||
bool fMempool = true;
|
||||
if (request.params.size() > 2)
|
||||
fMempool = request.params[2].get_bool();
|
||||
|
||||
CCoins coins;
|
||||
Coin coin;
|
||||
if (fMempool) {
|
||||
LOCK(mempool.cs);
|
||||
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;
|
||||
mempool.pruneSpent(hash, coins); // TODO: this should be done by the CCoinsViewMemPool
|
||||
}
|
||||
} else {
|
||||
if (!pcoinsTip->GetCoins(hash, coins))
|
||||
if (!pcoinsTip->GetCoin(out, coin)) {
|
||||
return NullUniValue;
|
||||
}
|
||||
}
|
||||
if (n<0 || (unsigned int)n>=coins.vout.size() || coins.vout[n].IsNull())
|
||||
return NullUniValue;
|
||||
|
||||
BlockMap::iterator it = mapBlockIndex.find(pcoinsTip->GetBestBlock());
|
||||
CBlockIndex *pindex = it->second;
|
||||
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));
|
||||
else
|
||||
ret.push_back(Pair("confirmations", pindex->nHeight - coins.nHeight + 1));
|
||||
ret.push_back(Pair("value", ValueFromAmount(coins.vout[n].nValue)));
|
||||
} else {
|
||||
ret.push_back(Pair("confirmations", (int64_t)(pindex->nHeight - coin.nHeight + 1)));
|
||||
}
|
||||
ret.push_back(Pair("value", ValueFromAmount(coin.out.nValue)));
|
||||
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("version", coins.nVersion));
|
||||
ret.push_back(Pair("coinbase", coins.fCoinBase));
|
||||
ret.push_back(Pair("coinbase", (bool)coin.fCoinBase));
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
|
|
@ -219,9 +219,10 @@ UniValue gettxoutproof(const JSONRPCRequest& request)
|
|||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
|
||||
pblockindex = mapBlockIndex[hashBlock];
|
||||
} else {
|
||||
CCoins coins;
|
||||
if (pcoinsTip->GetCoins(oneTxid, coins) && coins.nHeight > 0 && coins.nHeight <= chainActive.Height())
|
||||
pblockindex = chainActive[coins.nHeight];
|
||||
const Coin& coin = AccessByTxid(*pcoinsTip, oneTxid);
|
||||
if (!coin.IsSpent() && coin.nHeight > 0 && coin.nHeight <= chainActive.Height()) {
|
||||
pblockindex = chainActive[coin.nHeight];
|
||||
}
|
||||
}
|
||||
|
||||
if (pblockindex == NULL)
|
||||
|
@ -637,9 +638,7 @@ UniValue signrawtransaction(const JSONRPCRequest& request)
|
|||
view.SetBackend(viewMempool); // temporarily switch cache backend to db+mempool view
|
||||
|
||||
BOOST_FOREACH(const CTxIn& txin, mergedTx.vin) {
|
||||
const uint256& prevHash = txin.prevout.hash;
|
||||
CCoins coins;
|
||||
view.AccessCoins(prevHash); // this is certainly allowed to fail
|
||||
view.AccessCoin(txin.prevout); // Load entries from viewChain into view; can fail.
|
||||
}
|
||||
|
||||
view.SetBackend(viewDummy); // switch back to avoid locking mempool for too long
|
||||
|
@ -691,24 +690,26 @@ UniValue signrawtransaction(const JSONRPCRequest& request)
|
|||
if (nOut < 0)
|
||||
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "vout must be positive");
|
||||
|
||||
COutPoint out(txid, nOut);
|
||||
std::vector<unsigned char> pkData(ParseHexO(prevOut, "scriptPubKey"));
|
||||
CScript scriptPubKey(pkData.begin(), pkData.end());
|
||||
|
||||
{
|
||||
CCoinsModifier coins = view.ModifyCoins(txid);
|
||||
if (coins->IsAvailable(nOut) && coins->vout[nOut].scriptPubKey != scriptPubKey) {
|
||||
const Coin& coin = view.AccessCoin(out);
|
||||
if (!coin.IsSpent() && coin.out.scriptPubKey != scriptPubKey) {
|
||||
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);
|
||||
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, err);
|
||||
}
|
||||
if ((unsigned int)nOut >= coins->vout.size())
|
||||
coins->vout.resize(nOut+1);
|
||||
coins->vout[nOut].scriptPubKey = scriptPubKey;
|
||||
coins->vout[nOut].nValue = 0;
|
||||
Coin newcoin;
|
||||
newcoin.out.scriptPubKey = scriptPubKey;
|
||||
newcoin.out.nValue = 0;
|
||||
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
|
||||
|
@ -766,13 +767,13 @@ UniValue signrawtransaction(const JSONRPCRequest& request)
|
|||
// Sign what we can:
|
||||
for (unsigned int i = 0; i < mergedTx.vin.size(); i++) {
|
||||
CTxIn& txin = mergedTx.vin[i];
|
||||
const CCoins* coins = view.AccessCoins(txin.prevout.hash);
|
||||
if (coins == NULL || !coins->IsAvailable(txin.prevout.n)) {
|
||||
const Coin& coin = view.AccessCoin(txin.prevout);
|
||||
if (coin.IsSpent()) {
|
||||
TxInErrorToJSON(txin, vErrors, "Input not found or already spent");
|
||||
continue;
|
||||
}
|
||||
const CScript& prevPubKey = coins->vout[txin.prevout.n].scriptPubKey;
|
||||
const CAmount& amount = coins->vout[txin.prevout.n].nValue;
|
||||
const CScript& prevPubKey = coin.out.scriptPubKey;
|
||||
const CAmount& amount = coin.out.nValue;
|
||||
|
||||
SignatureData sigdata;
|
||||
// Only sign SIGHASH_SINGLE if there's a corresponding output:
|
||||
|
@ -844,9 +845,12 @@ UniValue sendrawtransaction(const JSONRPCRequest& request)
|
|||
nMaxRawTxFee = 0;
|
||||
|
||||
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 fHaveChain = existingCoins && existingCoins->nHeight < 1000000000;
|
||||
if (!fHaveMempool && !fHaveChain) {
|
||||
// push to local node and sync with wallets
|
||||
CValidationState state;
|
||||
|
|
|
@ -17,35 +17,44 @@
|
|||
|
||||
#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);
|
||||
|
||||
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
|
||||
{
|
||||
uint256 hashBestBlock_;
|
||||
std::map<uint256, CCoins> map_;
|
||||
std::map<COutPoint, Coin> map_;
|
||||
|
||||
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()) {
|
||||
return false;
|
||||
}
|
||||
coins = it->second;
|
||||
if (coins.IsPruned() && insecure_rand() % 2 == 0) {
|
||||
coin = it->second;
|
||||
if (coin.IsSpent() && insecure_rand() % 2 == 0) {
|
||||
// Randomly return false in case of an empty entry.
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HaveCoins(const uint256& txid) const
|
||||
bool HaveCoin(const COutPoint& outpoint) const
|
||||
{
|
||||
CCoins coins;
|
||||
return GetCoins(txid, coins);
|
||||
Coin coin;
|
||||
return GetCoin(outpoint, coin);
|
||||
}
|
||||
|
||||
uint256 GetBestBlock() const { return hashBestBlock_; }
|
||||
|
@ -55,8 +64,8 @@ public:
|
|||
for (CCoinsMap::iterator it = mapCoins.begin(); it != mapCoins.end(); ) {
|
||||
if (it->second.flags & CCoinsCacheEntry::DIRTY) {
|
||||
// Same optimization used in CCoinsViewDB is to only write dirty entries.
|
||||
map_[it->first] = it->second.coins;
|
||||
if (it->second.coins.IsPruned() && insecure_rand() % 3 == 0) {
|
||||
map_[it->first] = it->second.coin;
|
||||
if (it->second.coin.IsSpent() && insecure_rand() % 3 == 0) {
|
||||
// Randomly delete empty entries on write.
|
||||
map_.erase(it->first);
|
||||
}
|
||||
|
@ -78,9 +87,12 @@ public:
|
|||
{
|
||||
// Manually recompute the dynamic usage of the whole data, and compare it.
|
||||
size_t ret = memusage::DynamicUsage(cacheCoins);
|
||||
size_t count = 0;
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
// 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
|
||||
// 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 reached_4_caches = false;
|
||||
bool added_an_entry = false;
|
||||
bool added_an_unspendable_entry = false;
|
||||
bool removed_an_entry = false;
|
||||
bool updated_an_entry = false;
|
||||
bool found_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.
|
||||
std::map<uint256, CCoins> result;
|
||||
std::map<COutPoint, Coin> result;
|
||||
|
||||
// The cache stack.
|
||||
CCoinsViewTest base; // A CCoinsViewTest at the bottom.
|
||||
|
@ -133,36 +147,51 @@ BOOST_AUTO_TEST_CASE(coins_cache_simulation_test)
|
|||
// Do a random modification.
|
||||
{
|
||||
uint256 txid = txids[insecure_rand() % txids.size()]; // txid we're going to modify in this iteration.
|
||||
CCoins& coins = result[txid];
|
||||
CCoinsModifier entry = stack.back()->ModifyCoins(txid);
|
||||
BOOST_CHECK(coins == *entry);
|
||||
if (insecure_rand() % 5 == 0 || coins.IsPruned()) {
|
||||
if (coins.IsPruned()) {
|
||||
added_an_entry = true;
|
||||
Coin& coin = result[COutPoint(txid, 0)];
|
||||
const Coin& entry = (insecure_rand() % 500 == 0) ? AccessByTxid(*stack.back(), txid) : stack.back()->AccessCoin(COutPoint(txid, 0));
|
||||
BOOST_CHECK(coin == entry);
|
||||
|
||||
if (insecure_rand() % 5 == 0 || coin.IsSpent()) {
|
||||
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 {
|
||||
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();
|
||||
coins.vout.resize(1);
|
||||
coins.vout[0].nValue = insecure_rand();
|
||||
*entry = coins;
|
||||
stack.back()->AddCoin(COutPoint(txid, 0), std::move(newcoin), !coin.IsSpent() || insecure_rand() & 1);
|
||||
} else {
|
||||
coins.Clear();
|
||||
entry->Clear();
|
||||
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.
|
||||
if (insecure_rand() % 1000 == 1 || i == NUM_SIMULATION_ITERATIONS - 1) {
|
||||
for (std::map<uint256, CCoins>::iterator it = result.begin(); it != result.end(); it++) {
|
||||
const CCoins* coins = stack.back()->AccessCoins(it->first);
|
||||
if (coins) {
|
||||
BOOST_CHECK(*coins == it->second);
|
||||
found_an_entry = true;
|
||||
} else {
|
||||
BOOST_CHECK(it->second.IsPruned());
|
||||
for (auto it = result.begin(); it != result.end(); it++) {
|
||||
bool have = stack.back()->HaveCoin(it->first);
|
||||
const Coin& coin = stack.back()->AccessCoin(it->first);
|
||||
BOOST_CHECK(have == !coin.IsSpent());
|
||||
BOOST_CHECK(coin == it->second);
|
||||
if (coin.IsSpent()) {
|
||||
missed_an_entry = true;
|
||||
} else {
|
||||
BOOST_CHECK(stack.back()->HaveCoinInCache(it->first));
|
||||
found_an_entry = true;
|
||||
}
|
||||
}
|
||||
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(reached_4_caches);
|
||||
BOOST_CHECK(added_an_entry);
|
||||
BOOST_CHECK(added_an_unspendable_entry);
|
||||
BOOST_CHECK(removed_an_entry);
|
||||
BOOST_CHECK(updated_an_entry);
|
||||
BOOST_CHECK(found_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
|
||||
std::map<uint256, TxData> alltxs;
|
||||
typedef std::map<COutPoint, std::tuple<CTransaction,CTxUndo,Coin>> UtxoData;
|
||||
UtxoData utxoData;
|
||||
|
||||
TxData &FindRandomFrom(const std::set<uint256> &txidset) {
|
||||
assert(txidset.size());
|
||||
std::set<uint256>::iterator txIt = txidset.lower_bound(GetRandHash());
|
||||
if (txIt == txidset.end()) {
|
||||
txIt = txidset.begin();
|
||||
UtxoData::iterator FindRandomFrom(const std::set<COutPoint> &utxoSet) {
|
||||
assert(utxoSet.size());
|
||||
auto utxoSetIt = utxoSet.lower_bound(COutPoint(GetRandHash(), 0));
|
||||
if (utxoSetIt == utxoSet.end()) {
|
||||
utxoSetIt = utxoSet.begin();
|
||||
}
|
||||
std::map<uint256, TxData>::iterator txdit = alltxs.find(*txIt);
|
||||
assert(txdit != alltxs.end());
|
||||
return txdit->second;
|
||||
auto utxoDataIt = utxoData.find(*utxoSetIt);
|
||||
assert(utxoDataIt != utxoData.end());
|
||||
return utxoDataIt;
|
||||
}
|
||||
|
||||
|
||||
|
@ -242,7 +273,7 @@ BOOST_AUTO_TEST_CASE(updatecoins_simulation_test)
|
|||
{
|
||||
bool spent_a_duplicate_coinbase = false;
|
||||
// A simple map to track what we expect the cache stack to represent.
|
||||
std::map<uint256, CCoins> result;
|
||||
std::map<COutPoint, Coin> result;
|
||||
|
||||
// The cache stack.
|
||||
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.
|
||||
|
||||
// Track the txids we've used in various sets
|
||||
std::set<uint256> coinbaseids;
|
||||
std::set<uint256> disconnectedids;
|
||||
std::set<uint256> duplicateids;
|
||||
std::set<uint256> utxoset;
|
||||
std::set<COutPoint> coinbase_coins;
|
||||
std::set<COutPoint> disconnected_coins;
|
||||
std::set<COutPoint> duplicate_coins;
|
||||
std::set<COutPoint> utxoset;
|
||||
|
||||
for (unsigned int i = 0; i < NUM_SIMULATION_ITERATIONS; i++) {
|
||||
uint32_t randiter = insecure_rand();
|
||||
|
@ -264,23 +295,24 @@ BOOST_AUTO_TEST_CASE(updatecoins_simulation_test)
|
|||
tx.vin.resize(1);
|
||||
tx.vout.resize(1);
|
||||
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();
|
||||
CCoins oldcoins;
|
||||
Coin old_coin;
|
||||
|
||||
// 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
|
||||
if (insecure_rand() % 10 == 0 && coinbaseids.size()) {
|
||||
TxData &txd = FindRandomFrom(coinbaseids);
|
||||
if (insecure_rand() % 10 == 0 && coinbase_coins.size()) {
|
||||
auto utxod = FindRandomFrom(coinbase_coins);
|
||||
// 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
|
||||
disconnectedids.erase(tx.GetHash());
|
||||
disconnected_coins.erase(utxod->first);
|
||||
|
||||
duplicateids.insert(tx.GetHash());
|
||||
duplicate_coins.insert(utxod->first);
|
||||
}
|
||||
else {
|
||||
coinbaseids.insert(tx.GetHash());
|
||||
coinbase_coins.insert(COutPoint(tx.GetHash(), 0));
|
||||
}
|
||||
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
|
||||
else {
|
||||
|
||||
uint256 prevouthash;
|
||||
COutPoint prevout;
|
||||
// 1/20 times reconnect a previously disconnected tx
|
||||
if (randiter % 20 == 2 && disconnectedids.size()) {
|
||||
TxData &txd = FindRandomFrom(disconnectedids);
|
||||
tx = std::get<0>(txd);
|
||||
prevouthash = tx.vin[0].prevout.hash;
|
||||
if (!CTransaction(tx).IsCoinBase() && !utxoset.count(prevouthash)) {
|
||||
disconnectedids.erase(tx.GetHash());
|
||||
if (randiter % 20 == 2 && disconnected_coins.size()) {
|
||||
auto utxod = FindRandomFrom(disconnected_coins);
|
||||
tx = std::get<0>(utxod->second);
|
||||
prevout = tx.vin[0].prevout;
|
||||
if (!CTransaction(tx).IsCoinBase() && !utxoset.count(prevout)) {
|
||||
disconnected_coins.erase(utxod->first);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 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(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
|
||||
else {
|
||||
TxData &txd = FindRandomFrom(utxoset);
|
||||
prevouthash = std::get<0>(txd).GetHash();
|
||||
auto utxod = FindRandomFrom(utxoset);
|
||||
prevout = utxod->first;
|
||||
|
||||
// Construct the tx to spend the coins of prevouthash
|
||||
tx.vin[0].prevout.hash = prevouthash;
|
||||
tx.vin[0].prevout.n = 0;
|
||||
tx.vin[0].prevout = prevout;
|
||||
assert(!CTransaction(tx).IsCoinBase());
|
||||
}
|
||||
// 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
|
||||
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
|
||||
// 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;
|
||||
}
|
||||
|
||||
}
|
||||
// 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
|
||||
CTxUndo undo;
|
||||
UpdateCoins(tx, *(stack.back()), undo, height);
|
||||
|
||||
// Update the utxo set for future spends
|
||||
utxoset.insert(tx.GetHash());
|
||||
utxoset.insert(outpoint);
|
||||
|
||||
// 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
|
||||
else if (utxoset.size()) {
|
||||
TxData &txd = FindRandomFrom(utxoset);
|
||||
|
||||
CTransaction &tx = std::get<0>(txd);
|
||||
CTxUndo &undo = std::get<1>(txd);
|
||||
CCoins &origcoins = std::get<2>(txd);
|
||||
|
||||
uint256 undohash = tx.GetHash();
|
||||
CTransaction &tx = std::get<0>(utxod->second);
|
||||
CTxUndo &undo = std::get<1>(utxod->second);
|
||||
Coin &orig_coin = std::get<2>(utxod->second);
|
||||
|
||||
// Update the expected result
|
||||
// Remove new outputs
|
||||
result[undohash].Clear();
|
||||
result[utxod->first].Clear();
|
||||
// If not coinbase restore prevout
|
||||
if (!tx.IsCoinBase()) {
|
||||
result[tx.vin[0].prevout.hash] = origcoins;
|
||||
result[tx.vin[0].prevout] = orig_coin;
|
||||
}
|
||||
|
||||
// Disconnect the tx from the current UTXO
|
||||
// See code in DisconnectBlock
|
||||
// remove outputs
|
||||
{
|
||||
CCoinsModifier outs = stack.back()->ModifyCoins(undohash);
|
||||
outs->Clear();
|
||||
}
|
||||
stack.back()->SpendCoin(utxod->first);
|
||||
// restore inputs
|
||||
if (!tx.IsCoinBase()) {
|
||||
const COutPoint &out = tx.vin[0].prevout;
|
||||
const CTxInUndo &undoin = undo.vprevout[0];
|
||||
ApplyTxInUndo(undoin, *(stack.back()), out);
|
||||
Coin coin = undo.vprevout[0];
|
||||
ApplyTxInUndo(std::move(coin), *(stack.back()), out);
|
||||
}
|
||||
// Store as a candidate for reconnection
|
||||
disconnectedids.insert(undohash);
|
||||
disconnected_coins.insert(utxod->first);
|
||||
|
||||
// Update the utxoset
|
||||
utxoset.erase(undohash);
|
||||
utxoset.erase(utxod->first);
|
||||
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.
|
||||
if (insecure_rand() % 1000 == 1 || i == NUM_SIMULATION_ITERATIONS - 1) {
|
||||
for (std::map<uint256, CCoins>::iterator it = result.begin(); it != result.end(); it++) {
|
||||
const CCoins* coins = stack.back()->AccessCoins(it->first);
|
||||
if (coins) {
|
||||
BOOST_CHECK(*coins == it->second);
|
||||
} else {
|
||||
BOOST_CHECK(it->second.IsPruned());
|
||||
}
|
||||
for (auto it = result.begin(); it != result.end(); it++) {
|
||||
bool have = stack.back()->HaveCoin(it->first);
|
||||
const Coin& coin = stack.back()->AccessCoin(it->first);
|
||||
BOOST_CHECK(have == !coin.IsSpent());
|
||||
BOOST_CHECK(coin == it->second);
|
||||
}
|
||||
}
|
||||
|
||||
// One every 10 iterations, remove a random entry from the cache
|
||||
if (utxoset.size() > 1 && insecure_rand() % 30) {
|
||||
stack[insecure_rand() % stack.size()]->Uncache(FindRandomFrom(utxoset)->first);
|
||||
}
|
||||
if (disconnected_coins.size() > 1 && insecure_rand() % 30) {
|
||||
stack[insecure_rand() % stack.size()]->Uncache(FindRandomFrom(disconnected_coins)->first);
|
||||
}
|
||||
if (duplicate_coins.size() > 1 && insecure_rand() % 30) {
|
||||
stack[insecure_rand() % stack.size()]->Uncache(FindRandomFrom(duplicate_coins)->first);
|
||||
}
|
||||
|
||||
if (insecure_rand() % 100 == 0) {
|
||||
// Every 100 iterations, flush an intermediate cache
|
||||
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)
|
||||
{
|
||||
// Good example
|
||||
CDataStream ss1(ParseHex("0104835800816115944e077fe7c803cfa57f29b36bf87c1d358bb85e"), SER_DISK, CLIENT_VERSION);
|
||||
CCoins cc1;
|
||||
CDataStream ss1(ParseHex("97f23c835800816115944e077fe7c803cfa57f29b36bf87c1d35"), SER_DISK, CLIENT_VERSION);
|
||||
Coin cc1;
|
||||
ss1 >> cc1;
|
||||
BOOST_CHECK_EQUAL(cc1.nVersion, 1);
|
||||
BOOST_CHECK_EQUAL(cc1.fCoinBase, false);
|
||||
BOOST_CHECK_EQUAL(cc1.nHeight, 203998);
|
||||
BOOST_CHECK_EQUAL(cc1.vout.size(), 2);
|
||||
BOOST_CHECK_EQUAL(cc1.IsAvailable(0), false);
|
||||
BOOST_CHECK_EQUAL(cc1.IsAvailable(1), true);
|
||||
BOOST_CHECK_EQUAL(cc1.vout[1].nValue, 60000000000ULL);
|
||||
BOOST_CHECK_EQUAL(HexStr(cc1.vout[1].scriptPubKey), HexStr(GetScriptForDestination(CKeyID(uint160(ParseHex("816115944e077fe7c803cfa57f29b36bf87c1d35"))))));
|
||||
BOOST_CHECK_EQUAL(cc1.out.nValue, 60000000000ULL);
|
||||
BOOST_CHECK_EQUAL(HexStr(cc1.out.scriptPubKey), HexStr(GetScriptForDestination(CKeyID(uint160(ParseHex("816115944e077fe7c803cfa57f29b36bf87c1d35"))))));
|
||||
|
||||
// Good example
|
||||
CDataStream ss2(ParseHex("0109044086ef97d5790061b01caab50f1b8e9c50a5057eb43c2d9563a4eebbd123008c988f1a4a4de2161e0f50aac7f17e7f9555caa486af3b"), SER_DISK, CLIENT_VERSION);
|
||||
CCoins cc2;
|
||||
CDataStream ss2(ParseHex("8ddf77bbd123008c988f1a4a4de2161e0f50aac7f17e7f9555caa4"), SER_DISK, CLIENT_VERSION);
|
||||
Coin cc2;
|
||||
ss2 >> cc2;
|
||||
BOOST_CHECK_EQUAL(cc2.nVersion, 1);
|
||||
BOOST_CHECK_EQUAL(cc2.fCoinBase, true);
|
||||
BOOST_CHECK_EQUAL(cc2.nHeight, 120891);
|
||||
BOOST_CHECK_EQUAL(cc2.vout.size(), 17);
|
||||
for (int i = 0; i < 17; i++) {
|
||||
BOOST_CHECK_EQUAL(cc2.IsAvailable(i), i == 4 || i == 16);
|
||||
}
|
||||
BOOST_CHECK_EQUAL(cc2.vout[4].nValue, 234925952);
|
||||
BOOST_CHECK_EQUAL(HexStr(cc2.vout[4].scriptPubKey), HexStr(GetScriptForDestination(CKeyID(uint160(ParseHex("61b01caab50f1b8e9c50a5057eb43c2d9563a4ee"))))));
|
||||
BOOST_CHECK_EQUAL(cc2.vout[16].nValue, 110397);
|
||||
BOOST_CHECK_EQUAL(HexStr(cc2.vout[16].scriptPubKey), HexStr(GetScriptForDestination(CKeyID(uint160(ParseHex("8c988f1a4a4de2161e0f50aac7f17e7f9555caa4"))))));
|
||||
BOOST_CHECK_EQUAL(cc2.out.nValue, 110397);
|
||||
BOOST_CHECK_EQUAL(HexStr(cc2.out.scriptPubKey), HexStr(GetScriptForDestination(CKeyID(uint160(ParseHex("8c988f1a4a4de2161e0f50aac7f17e7f9555caa4"))))));
|
||||
|
||||
// Smallest possible example
|
||||
CDataStream ssx(SER_DISK, CLIENT_VERSION);
|
||||
BOOST_CHECK_EQUAL(HexStr(ssx.begin(), ssx.end()), "");
|
||||
|
||||
CDataStream ss3(ParseHex("0002000600"), SER_DISK, CLIENT_VERSION);
|
||||
CCoins cc3;
|
||||
CDataStream ss3(ParseHex("000006"), SER_DISK, CLIENT_VERSION);
|
||||
Coin cc3;
|
||||
ss3 >> cc3;
|
||||
BOOST_CHECK_EQUAL(cc3.nVersion, 0);
|
||||
BOOST_CHECK_EQUAL(cc3.fCoinBase, false);
|
||||
BOOST_CHECK_EQUAL(cc3.nHeight, 0);
|
||||
BOOST_CHECK_EQUAL(cc3.vout.size(), 1);
|
||||
BOOST_CHECK_EQUAL(cc3.IsAvailable(0), true);
|
||||
BOOST_CHECK_EQUAL(cc3.vout[0].nValue, 0);
|
||||
BOOST_CHECK_EQUAL(cc3.vout[0].scriptPubKey.size(), 0);
|
||||
BOOST_CHECK_EQUAL(cc3.out.nValue, 0);
|
||||
BOOST_CHECK_EQUAL(cc3.out.scriptPubKey.size(), 0);
|
||||
|
||||
// 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 {
|
||||
CCoins cc4;
|
||||
Coin cc4;
|
||||
ss4 >> cc4;
|
||||
BOOST_CHECK_MESSAGE(false, "We should have thrown");
|
||||
} catch (const std::ios_base::failure& e) {
|
||||
|
@ -490,16 +509,16 @@ BOOST_AUTO_TEST_CASE(ccoins_serialization)
|
|||
uint64_t x = 3000000000ULL;
|
||||
tmp << VARINT(x);
|
||||
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 {
|
||||
CCoins cc5;
|
||||
Coin cc5;
|
||||
ss5 >> cc5;
|
||||
BOOST_CHECK_MESSAGE(false, "We should have thrown");
|
||||
} catch (const std::ios_base::failure& e) {
|
||||
}
|
||||
}
|
||||
|
||||
const static uint256 TXID;
|
||||
const static COutPoint OUTPOINT;
|
||||
const static CAmount PRUNED = -1;
|
||||
const static CAmount ABSENT = -2;
|
||||
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 ABSENT_FLAGS = {NO_ENTRY};
|
||||
|
||||
void SetCoinsValue(CAmount value, CCoins& coins)
|
||||
void SetCoinsValue(CAmount value, Coin& coin)
|
||||
{
|
||||
assert(value != ABSENT);
|
||||
coins.Clear();
|
||||
assert(coins.IsPruned());
|
||||
coin.Clear();
|
||||
assert(coin.IsSpent());
|
||||
if (value != PRUNED) {
|
||||
coins.vout.emplace_back();
|
||||
coins.vout.back().nValue = value;
|
||||
assert(!coins.IsPruned());
|
||||
coin.out.nValue = value;
|
||||
coin.nHeight = 1;
|
||||
assert(!coin.IsSpent());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -535,25 +554,23 @@ size_t InsertCoinsMapEntry(CCoinsMap& map, CAmount value, char flags)
|
|||
assert(flags != NO_ENTRY);
|
||||
CCoinsCacheEntry entry;
|
||||
entry.flags = flags;
|
||||
SetCoinsValue(value, entry.coins);
|
||||
auto inserted = map.emplace(TXID, std::move(entry));
|
||||
SetCoinsValue(value, entry.coin);
|
||||
auto inserted = map.emplace(OUTPOINT, std::move(entry));
|
||||
assert(inserted.second);
|
||||
return inserted.first->second.coins.DynamicMemoryUsage();
|
||||
return inserted.first->second.coin.DynamicMemoryUsage();
|
||||
}
|
||||
|
||||
void GetCoinsMapEntry(const CCoinsMap& map, CAmount& value, char& flags)
|
||||
{
|
||||
auto it = map.find(TXID);
|
||||
auto it = map.find(OUTPOINT);
|
||||
if (it == map.end()) {
|
||||
value = ABSENT;
|
||||
flags = NO_ENTRY;
|
||||
} else {
|
||||
if (it->second.coins.IsPruned()) {
|
||||
assert(it->second.coins.vout.size() == 0);
|
||||
if (it->second.coin.IsSpent()) {
|
||||
value = PRUNED;
|
||||
} else {
|
||||
assert(it->second.coins.vout.size() == 1);
|
||||
value = it->second.coins.vout[0].nValue;
|
||||
value = it->second.coin.out.nValue;
|
||||
}
|
||||
flags = it->second.flags;
|
||||
assert(flags != NO_ENTRY);
|
||||
|
@ -581,10 +598,10 @@ public:
|
|||
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);
|
||||
test.cache.AccessCoins(TXID);
|
||||
test.cache.AccessCoin(OUTPOINT);
|
||||
test.cache.SelfTest();
|
||||
|
||||
CAmount result_value;
|
||||
|
@ -603,39 +620,39 @@ BOOST_AUTO_TEST_CASE(ccoins_access)
|
|||
* Base Cache Result Cache Result
|
||||
* Value Value Value Flags Flags
|
||||
*/
|
||||
CheckAccessCoins(ABSENT, ABSENT, ABSENT, NO_ENTRY , NO_ENTRY );
|
||||
CheckAccessCoins(ABSENT, PRUNED, PRUNED, 0 , 0 );
|
||||
CheckAccessCoins(ABSENT, PRUNED, PRUNED, FRESH , FRESH );
|
||||
CheckAccessCoins(ABSENT, PRUNED, PRUNED, DIRTY , DIRTY );
|
||||
CheckAccessCoins(ABSENT, PRUNED, PRUNED, DIRTY|FRESH, DIRTY|FRESH);
|
||||
CheckAccessCoins(ABSENT, VALUE2, VALUE2, 0 , 0 );
|
||||
CheckAccessCoins(ABSENT, VALUE2, VALUE2, FRESH , FRESH );
|
||||
CheckAccessCoins(ABSENT, VALUE2, VALUE2, DIRTY , DIRTY );
|
||||
CheckAccessCoins(ABSENT, VALUE2, VALUE2, DIRTY|FRESH, DIRTY|FRESH);
|
||||
CheckAccessCoins(PRUNED, ABSENT, PRUNED, NO_ENTRY , FRESH );
|
||||
CheckAccessCoins(PRUNED, PRUNED, PRUNED, 0 , 0 );
|
||||
CheckAccessCoins(PRUNED, PRUNED, PRUNED, FRESH , FRESH );
|
||||
CheckAccessCoins(PRUNED, PRUNED, PRUNED, DIRTY , DIRTY );
|
||||
CheckAccessCoins(PRUNED, PRUNED, PRUNED, DIRTY|FRESH, DIRTY|FRESH);
|
||||
CheckAccessCoins(PRUNED, VALUE2, VALUE2, 0 , 0 );
|
||||
CheckAccessCoins(PRUNED, VALUE2, VALUE2, FRESH , FRESH );
|
||||
CheckAccessCoins(PRUNED, VALUE2, VALUE2, DIRTY , DIRTY );
|
||||
CheckAccessCoins(PRUNED, VALUE2, VALUE2, DIRTY|FRESH, DIRTY|FRESH);
|
||||
CheckAccessCoins(VALUE1, ABSENT, VALUE1, NO_ENTRY , 0 );
|
||||
CheckAccessCoins(VALUE1, PRUNED, PRUNED, 0 , 0 );
|
||||
CheckAccessCoins(VALUE1, PRUNED, PRUNED, FRESH , FRESH );
|
||||
CheckAccessCoins(VALUE1, PRUNED, PRUNED, DIRTY , DIRTY );
|
||||
CheckAccessCoins(VALUE1, PRUNED, PRUNED, DIRTY|FRESH, DIRTY|FRESH);
|
||||
CheckAccessCoins(VALUE1, VALUE2, VALUE2, 0 , 0 );
|
||||
CheckAccessCoins(VALUE1, VALUE2, VALUE2, FRESH , FRESH );
|
||||
CheckAccessCoins(VALUE1, VALUE2, VALUE2, DIRTY , DIRTY );
|
||||
CheckAccessCoins(VALUE1, VALUE2, VALUE2, DIRTY|FRESH, DIRTY|FRESH);
|
||||
CheckAccessCoin(ABSENT, ABSENT, ABSENT, NO_ENTRY , NO_ENTRY );
|
||||
CheckAccessCoin(ABSENT, PRUNED, PRUNED, 0 , 0 );
|
||||
CheckAccessCoin(ABSENT, PRUNED, PRUNED, FRESH , FRESH );
|
||||
CheckAccessCoin(ABSENT, PRUNED, PRUNED, DIRTY , DIRTY );
|
||||
CheckAccessCoin(ABSENT, PRUNED, PRUNED, DIRTY|FRESH, DIRTY|FRESH);
|
||||
CheckAccessCoin(ABSENT, VALUE2, VALUE2, 0 , 0 );
|
||||
CheckAccessCoin(ABSENT, VALUE2, VALUE2, FRESH , FRESH );
|
||||
CheckAccessCoin(ABSENT, VALUE2, VALUE2, DIRTY , DIRTY );
|
||||
CheckAccessCoin(ABSENT, VALUE2, VALUE2, DIRTY|FRESH, DIRTY|FRESH);
|
||||
CheckAccessCoin(PRUNED, ABSENT, PRUNED, NO_ENTRY , FRESH );
|
||||
CheckAccessCoin(PRUNED, PRUNED, PRUNED, 0 , 0 );
|
||||
CheckAccessCoin(PRUNED, PRUNED, PRUNED, FRESH , FRESH );
|
||||
CheckAccessCoin(PRUNED, PRUNED, PRUNED, DIRTY , DIRTY );
|
||||
CheckAccessCoin(PRUNED, PRUNED, PRUNED, DIRTY|FRESH, DIRTY|FRESH);
|
||||
CheckAccessCoin(PRUNED, VALUE2, VALUE2, 0 , 0 );
|
||||
CheckAccessCoin(PRUNED, VALUE2, VALUE2, FRESH , FRESH );
|
||||
CheckAccessCoin(PRUNED, VALUE2, VALUE2, DIRTY , DIRTY );
|
||||
CheckAccessCoin(PRUNED, VALUE2, VALUE2, DIRTY|FRESH, DIRTY|FRESH);
|
||||
CheckAccessCoin(VALUE1, ABSENT, VALUE1, NO_ENTRY , 0 );
|
||||
CheckAccessCoin(VALUE1, PRUNED, PRUNED, 0 , 0 );
|
||||
CheckAccessCoin(VALUE1, PRUNED, PRUNED, FRESH , FRESH );
|
||||
CheckAccessCoin(VALUE1, PRUNED, PRUNED, DIRTY , DIRTY );
|
||||
CheckAccessCoin(VALUE1, PRUNED, PRUNED, DIRTY|FRESH, DIRTY|FRESH);
|
||||
CheckAccessCoin(VALUE1, VALUE2, VALUE2, 0 , 0 );
|
||||
CheckAccessCoin(VALUE1, VALUE2, VALUE2, FRESH , FRESH );
|
||||
CheckAccessCoin(VALUE1, VALUE2, VALUE2, DIRTY , DIRTY );
|
||||
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);
|
||||
SetCoinsValue(modify_value, *test.cache.ModifyCoins(TXID));
|
||||
test.cache.SpendCoin(OUTPOINT);
|
||||
test.cache.SelfTest();
|
||||
|
||||
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_AUTO_TEST_CASE(ccoins_modify)
|
||||
BOOST_AUTO_TEST_CASE(ccoins_spend)
|
||||
{
|
||||
/* Check ModifyCoin behavior, requesting a coin from a cache view layered on
|
||||
* top of a base view, writing a modification to the coin, and then checking
|
||||
/* Check SpendCoin behavior, requesting a coin from a cache view layered on
|
||||
* top of a base view, spending, and then checking
|
||||
* the resulting entry in the cache after the modification.
|
||||
*
|
||||
* Base Cache Write Result Cache Result
|
||||
* Value Value Value Value Flags Flags
|
||||
* Base Cache Result Cache Result
|
||||
* Value Value Value Flags Flags
|
||||
*/
|
||||
CheckModifyCoins(ABSENT, ABSENT, PRUNED, ABSENT, NO_ENTRY , NO_ENTRY );
|
||||
CheckModifyCoins(ABSENT, ABSENT, VALUE3, VALUE3, NO_ENTRY , DIRTY|FRESH);
|
||||
CheckModifyCoins(ABSENT, PRUNED, PRUNED, PRUNED, 0 , DIRTY );
|
||||
CheckModifyCoins(ABSENT, PRUNED, PRUNED, ABSENT, FRESH , NO_ENTRY );
|
||||
CheckModifyCoins(ABSENT, PRUNED, PRUNED, PRUNED, DIRTY , DIRTY );
|
||||
CheckModifyCoins(ABSENT, PRUNED, PRUNED, ABSENT, DIRTY|FRESH, NO_ENTRY );
|
||||
CheckModifyCoins(ABSENT, PRUNED, VALUE3, VALUE3, 0 , DIRTY );
|
||||
CheckModifyCoins(ABSENT, PRUNED, VALUE3, VALUE3, FRESH , DIRTY|FRESH);
|
||||
CheckModifyCoins(ABSENT, PRUNED, VALUE3, VALUE3, DIRTY , DIRTY );
|
||||
CheckModifyCoins(ABSENT, PRUNED, VALUE3, VALUE3, DIRTY|FRESH, DIRTY|FRESH);
|
||||
CheckModifyCoins(ABSENT, VALUE2, PRUNED, PRUNED, 0 , DIRTY );
|
||||
CheckModifyCoins(ABSENT, VALUE2, PRUNED, ABSENT, FRESH , NO_ENTRY );
|
||||
CheckModifyCoins(ABSENT, VALUE2, PRUNED, PRUNED, DIRTY , DIRTY );
|
||||
CheckModifyCoins(ABSENT, VALUE2, PRUNED, ABSENT, DIRTY|FRESH, NO_ENTRY );
|
||||
CheckModifyCoins(ABSENT, VALUE2, VALUE3, VALUE3, 0 , DIRTY );
|
||||
CheckModifyCoins(ABSENT, VALUE2, VALUE3, VALUE3, FRESH , DIRTY|FRESH);
|
||||
CheckModifyCoins(ABSENT, VALUE2, VALUE3, VALUE3, DIRTY , DIRTY );
|
||||
CheckModifyCoins(ABSENT, VALUE2, VALUE3, VALUE3, DIRTY|FRESH, DIRTY|FRESH);
|
||||
CheckModifyCoins(PRUNED, ABSENT, PRUNED, ABSENT, NO_ENTRY , NO_ENTRY );
|
||||
CheckModifyCoins(PRUNED, ABSENT, VALUE3, VALUE3, NO_ENTRY , DIRTY|FRESH);
|
||||
CheckModifyCoins(PRUNED, PRUNED, PRUNED, PRUNED, 0 , DIRTY );
|
||||
CheckModifyCoins(PRUNED, PRUNED, PRUNED, ABSENT, FRESH , NO_ENTRY );
|
||||
CheckModifyCoins(PRUNED, PRUNED, PRUNED, PRUNED, DIRTY , DIRTY );
|
||||
CheckModifyCoins(PRUNED, PRUNED, PRUNED, ABSENT, DIRTY|FRESH, NO_ENTRY );
|
||||
CheckModifyCoins(PRUNED, PRUNED, VALUE3, VALUE3, 0 , DIRTY );
|
||||
CheckModifyCoins(PRUNED, PRUNED, VALUE3, VALUE3, FRESH , DIRTY|FRESH);
|
||||
CheckModifyCoins(PRUNED, PRUNED, VALUE3, VALUE3, DIRTY , DIRTY );
|
||||
CheckModifyCoins(PRUNED, PRUNED, VALUE3, VALUE3, DIRTY|FRESH, DIRTY|FRESH);
|
||||
CheckModifyCoins(PRUNED, VALUE2, PRUNED, PRUNED, 0 , DIRTY );
|
||||
CheckModifyCoins(PRUNED, VALUE2, PRUNED, ABSENT, FRESH , NO_ENTRY );
|
||||
CheckModifyCoins(PRUNED, VALUE2, PRUNED, PRUNED, DIRTY , DIRTY );
|
||||
CheckModifyCoins(PRUNED, VALUE2, PRUNED, ABSENT, DIRTY|FRESH, NO_ENTRY );
|
||||
CheckModifyCoins(PRUNED, VALUE2, VALUE3, VALUE3, 0 , DIRTY );
|
||||
CheckModifyCoins(PRUNED, VALUE2, VALUE3, VALUE3, FRESH , DIRTY|FRESH);
|
||||
CheckModifyCoins(PRUNED, VALUE2, VALUE3, VALUE3, DIRTY , DIRTY );
|
||||
CheckModifyCoins(PRUNED, VALUE2, VALUE3, VALUE3, DIRTY|FRESH, DIRTY|FRESH);
|
||||
CheckModifyCoins(VALUE1, ABSENT, PRUNED, PRUNED, NO_ENTRY , DIRTY );
|
||||
CheckModifyCoins(VALUE1, ABSENT, VALUE3, VALUE3, NO_ENTRY , DIRTY );
|
||||
CheckModifyCoins(VALUE1, PRUNED, PRUNED, PRUNED, 0 , DIRTY );
|
||||
CheckModifyCoins(VALUE1, PRUNED, PRUNED, ABSENT, FRESH , NO_ENTRY );
|
||||
CheckModifyCoins(VALUE1, PRUNED, PRUNED, PRUNED, DIRTY , DIRTY );
|
||||
CheckModifyCoins(VALUE1, PRUNED, PRUNED, ABSENT, DIRTY|FRESH, NO_ENTRY );
|
||||
CheckModifyCoins(VALUE1, PRUNED, VALUE3, VALUE3, 0 , DIRTY );
|
||||
CheckModifyCoins(VALUE1, PRUNED, VALUE3, VALUE3, FRESH , DIRTY|FRESH);
|
||||
CheckModifyCoins(VALUE1, PRUNED, VALUE3, VALUE3, DIRTY , DIRTY );
|
||||
CheckModifyCoins(VALUE1, PRUNED, VALUE3, VALUE3, DIRTY|FRESH, DIRTY|FRESH);
|
||||
CheckModifyCoins(VALUE1, VALUE2, PRUNED, PRUNED, 0 , DIRTY );
|
||||
CheckModifyCoins(VALUE1, VALUE2, PRUNED, ABSENT, FRESH , NO_ENTRY );
|
||||
CheckModifyCoins(VALUE1, VALUE2, PRUNED, PRUNED, DIRTY , DIRTY );
|
||||
CheckModifyCoins(VALUE1, VALUE2, PRUNED, ABSENT, DIRTY|FRESH, NO_ENTRY );
|
||||
CheckModifyCoins(VALUE1, VALUE2, VALUE3, VALUE3, 0 , DIRTY );
|
||||
CheckModifyCoins(VALUE1, VALUE2, VALUE3, VALUE3, FRESH , DIRTY|FRESH);
|
||||
CheckModifyCoins(VALUE1, VALUE2, VALUE3, VALUE3, DIRTY , DIRTY );
|
||||
CheckModifyCoins(VALUE1, VALUE2, VALUE3, VALUE3, DIRTY|FRESH, DIRTY|FRESH);
|
||||
CheckSpendCoins(ABSENT, ABSENT, ABSENT, NO_ENTRY , NO_ENTRY );
|
||||
CheckSpendCoins(ABSENT, PRUNED, PRUNED, 0 , DIRTY );
|
||||
CheckSpendCoins(ABSENT, PRUNED, ABSENT, FRESH , NO_ENTRY );
|
||||
CheckSpendCoins(ABSENT, PRUNED, PRUNED, DIRTY , DIRTY );
|
||||
CheckSpendCoins(ABSENT, PRUNED, ABSENT, DIRTY|FRESH, NO_ENTRY );
|
||||
CheckSpendCoins(ABSENT, VALUE2, PRUNED, 0 , DIRTY );
|
||||
CheckSpendCoins(ABSENT, VALUE2, ABSENT, FRESH , NO_ENTRY );
|
||||
CheckSpendCoins(ABSENT, VALUE2, PRUNED, DIRTY , DIRTY );
|
||||
CheckSpendCoins(ABSENT, VALUE2, ABSENT, DIRTY|FRESH, NO_ENTRY );
|
||||
CheckSpendCoins(PRUNED, ABSENT, ABSENT, NO_ENTRY , NO_ENTRY );
|
||||
CheckSpendCoins(PRUNED, PRUNED, PRUNED, 0 , DIRTY );
|
||||
CheckSpendCoins(PRUNED, PRUNED, ABSENT, FRESH , NO_ENTRY );
|
||||
CheckSpendCoins(PRUNED, PRUNED, PRUNED, DIRTY , DIRTY );
|
||||
CheckSpendCoins(PRUNED, PRUNED, ABSENT, DIRTY|FRESH, NO_ENTRY );
|
||||
CheckSpendCoins(PRUNED, VALUE2, PRUNED, 0 , DIRTY );
|
||||
CheckSpendCoins(PRUNED, VALUE2, ABSENT, FRESH , NO_ENTRY );
|
||||
CheckSpendCoins(PRUNED, VALUE2, PRUNED, DIRTY , DIRTY );
|
||||
CheckSpendCoins(PRUNED, VALUE2, ABSENT, DIRTY|FRESH, NO_ENTRY );
|
||||
CheckSpendCoins(VALUE1, ABSENT, PRUNED, NO_ENTRY , DIRTY );
|
||||
CheckSpendCoins(VALUE1, PRUNED, PRUNED, 0 , DIRTY );
|
||||
CheckSpendCoins(VALUE1, PRUNED, ABSENT, FRESH , NO_ENTRY );
|
||||
CheckSpendCoins(VALUE1, PRUNED, PRUNED, DIRTY , DIRTY );
|
||||
CheckSpendCoins(VALUE1, PRUNED, ABSENT, DIRTY|FRESH, NO_ENTRY );
|
||||
CheckSpendCoins(VALUE1, VALUE2, PRUNED, 0 , DIRTY );
|
||||
CheckSpendCoins(VALUE1, VALUE2, ABSENT, FRESH , NO_ENTRY );
|
||||
CheckSpendCoins(VALUE1, VALUE2, PRUNED, DIRTY , DIRTY );
|
||||
CheckSpendCoins(VALUE1, VALUE2, ABSENT, DIRTY|FRESH, NO_ENTRY );
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
CAmount result_value;
|
||||
char result_flags;
|
||||
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);
|
||||
} catch (std::logic_error& e) {
|
||||
result_value = FAIL;
|
||||
|
@ -728,64 +721,46 @@ void CheckModifyNewCoinsBase(CAmount base_value, CAmount cache_value, CAmount mo
|
|||
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.
|
||||
// This wrapper lets the modify_new test below be shorter and less repetitive,
|
||||
// while still verifying that the CoinsViewCache::ModifyNewCoins implementation
|
||||
// This wrapper lets the coins_add test below be shorter and less repetitive,
|
||||
// while still verifying that the CoinsViewCache::AddCoin implementation
|
||||
// ignores base values.
|
||||
template <typename... Args>
|
||||
void CheckModifyNewCoins(Args&&... args)
|
||||
void CheckAddCoin(Args&&... args)
|
||||
{
|
||||
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
|
||||
* 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
|
||||
* Value Value Value Flags Flags
|
||||
* Cache Write Result Cache Result potential_overwrite
|
||||
* Value Value Value Flags Flags
|
||||
*/
|
||||
CheckModifyNewCoins(ABSENT, PRUNED, ABSENT, NO_ENTRY , NO_ENTRY , false);
|
||||
CheckModifyNewCoins(ABSENT, PRUNED, PRUNED, NO_ENTRY , DIRTY , true );
|
||||
CheckModifyNewCoins(ABSENT, VALUE3, VALUE3, NO_ENTRY , DIRTY|FRESH, false);
|
||||
CheckModifyNewCoins(ABSENT, VALUE3, VALUE3, NO_ENTRY , DIRTY , true );
|
||||
CheckModifyNewCoins(PRUNED, PRUNED, ABSENT, 0 , NO_ENTRY , false);
|
||||
CheckModifyNewCoins(PRUNED, PRUNED, PRUNED, 0 , DIRTY , true );
|
||||
CheckModifyNewCoins(PRUNED, PRUNED, ABSENT, FRESH , NO_ENTRY , false);
|
||||
CheckModifyNewCoins(PRUNED, PRUNED, ABSENT, FRESH , NO_ENTRY , true );
|
||||
CheckModifyNewCoins(PRUNED, PRUNED, PRUNED, DIRTY , DIRTY , false);
|
||||
CheckModifyNewCoins(PRUNED, PRUNED, PRUNED, DIRTY , DIRTY , true );
|
||||
CheckModifyNewCoins(PRUNED, PRUNED, ABSENT, DIRTY|FRESH, NO_ENTRY , false);
|
||||
CheckModifyNewCoins(PRUNED, PRUNED, ABSENT, DIRTY|FRESH, NO_ENTRY , true );
|
||||
CheckModifyNewCoins(PRUNED, VALUE3, VALUE3, 0 , DIRTY|FRESH, false);
|
||||
CheckModifyNewCoins(PRUNED, VALUE3, VALUE3, 0 , DIRTY , true );
|
||||
CheckModifyNewCoins(PRUNED, VALUE3, VALUE3, FRESH , DIRTY|FRESH, false);
|
||||
CheckModifyNewCoins(PRUNED, VALUE3, VALUE3, FRESH , DIRTY|FRESH, true );
|
||||
CheckModifyNewCoins(PRUNED, VALUE3, VALUE3, DIRTY , DIRTY , false);
|
||||
CheckModifyNewCoins(PRUNED, VALUE3, VALUE3, DIRTY , DIRTY , 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 );
|
||||
CheckAddCoin(ABSENT, VALUE3, VALUE3, NO_ENTRY , DIRTY|FRESH, false);
|
||||
CheckAddCoin(ABSENT, VALUE3, VALUE3, NO_ENTRY , DIRTY , true );
|
||||
CheckAddCoin(PRUNED, VALUE3, VALUE3, 0 , DIRTY|FRESH, false);
|
||||
CheckAddCoin(PRUNED, VALUE3, VALUE3, 0 , DIRTY , true );
|
||||
CheckAddCoin(PRUNED, VALUE3, VALUE3, FRESH , DIRTY|FRESH, false);
|
||||
CheckAddCoin(PRUNED, VALUE3, VALUE3, FRESH , DIRTY|FRESH, true );
|
||||
CheckAddCoin(PRUNED, VALUE3, VALUE3, DIRTY , DIRTY , false);
|
||||
CheckAddCoin(PRUNED, VALUE3, VALUE3, DIRTY , DIRTY , true );
|
||||
CheckAddCoin(PRUNED, VALUE3, VALUE3, DIRTY|FRESH, DIRTY|FRESH, false);
|
||||
CheckAddCoin(PRUNED, VALUE3, VALUE3, DIRTY|FRESH, DIRTY|FRESH, true );
|
||||
CheckAddCoin(VALUE2, VALUE3, FAIL , 0 , NO_ENTRY , false);
|
||||
CheckAddCoin(VALUE2, VALUE3, VALUE3, 0 , DIRTY , true );
|
||||
CheckAddCoin(VALUE2, VALUE3, FAIL , FRESH , NO_ENTRY , false);
|
||||
CheckAddCoin(VALUE2, VALUE3, VALUE3, FRESH , DIRTY|FRESH, true );
|
||||
CheckAddCoin(VALUE2, VALUE3, FAIL , DIRTY , NO_ENTRY , false);
|
||||
CheckAddCoin(VALUE2, VALUE3, VALUE3, DIRTY , DIRTY , true );
|
||||
CheckAddCoin(VALUE2, VALUE3, FAIL , DIRTY|FRESH, NO_ENTRY , false);
|
||||
CheckAddCoin(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)
|
||||
|
|
|
@ -128,6 +128,23 @@ BOOST_AUTO_TEST_CASE(siphash)
|
|||
tx.nVersion = 1;
|
||||
ss << tx;
|
||||
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()
|
||||
|
|
|
@ -112,7 +112,8 @@ BOOST_AUTO_TEST_CASE(sign)
|
|||
{
|
||||
CScript sigSave = txTo[i].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)
|
||||
BOOST_CHECK_MESSAGE(sigOK, strprintf("VerifySignature %d %d", i, j));
|
||||
else
|
||||
|
@ -316,7 +317,7 @@ BOOST_AUTO_TEST_CASE(AreInputsStandard)
|
|||
txFrom.vout[6].scriptPubKey = GetScriptForDestination(CScriptID(twentySigops));
|
||||
txFrom.vout[6].nValue = 6000;
|
||||
|
||||
coins.ModifyCoins(txFrom.GetHash())->FromTx(txFrom, 0);
|
||||
AddCoins(coins, txFrom, 0);
|
||||
|
||||
CMutableTransaction txTo;
|
||||
txTo.vout.resize(1);
|
||||
|
|
|
@ -102,7 +102,7 @@ void BuildTxs(CMutableTransaction& spendingTx, CCoinsViewCache& coins, CMutableT
|
|||
spendingTx.vout[0].nValue = 1;
|
||||
spendingTx.vout[0].scriptPubKey = CScript();
|
||||
|
||||
coins.ModifyCoins(creationTx.GetHash())->FromTx(creationTx, 0);
|
||||
AddCoins(coins, creationTx, 0);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(GetTxSigOpCost)
|
||||
|
|
|
@ -168,8 +168,8 @@ int do_fuzz()
|
|||
{
|
||||
try
|
||||
{
|
||||
CCoins block;
|
||||
ds >> block;
|
||||
Coin coin;
|
||||
ds >> coin;
|
||||
} catch (const std::ios_base::failure& e) {return 0;}
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -307,14 +307,14 @@ SetupDummyInputs(CBasicKeyStore& keystoreRet, CCoinsViewCache& coinsRet)
|
|||
dummyTransactions[0].vout[0].scriptPubKey << ToByteVector(key[0].GetPubKey()) << OP_CHECKSIG;
|
||||
dummyTransactions[0].vout[1].nValue = 50*CENT;
|
||||
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[0].nValue = 21*CENT;
|
||||
dummyTransactions[1].vout[0].scriptPubKey = GetScriptForDestination(key[2].GetPubKey().GetID());
|
||||
dummyTransactions[1].vout[1].nValue = 22*CENT;
|
||||
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;
|
||||
}
|
||||
|
@ -470,19 +470,20 @@ BOOST_AUTO_TEST_CASE(test_big_witness_transaction) {
|
|||
for (int i=0; i<20; i++)
|
||||
threadGroup.create_thread(boost::bind(&CCheckQueue<CScriptCheck>::Thread, boost::ref(scriptcheckqueue)));
|
||||
|
||||
CCoins coins;
|
||||
coins.nVersion = 1;
|
||||
coins.fCoinBase = false;
|
||||
std::vector<Coin> coins;
|
||||
for(uint32_t i = 0; i < mtx.vin.size(); i++) {
|
||||
CTxOut txout;
|
||||
txout.nValue = 1000;
|
||||
txout.scriptPubKey = scriptPubKey;
|
||||
coins.vout.push_back(txout);
|
||||
Coin coin;
|
||||
coin.nHeight = 1;
|
||||
coin.fCoinBase = false;
|
||||
coin.out.nValue = 1000;
|
||||
coin.out.scriptPubKey = scriptPubKey;
|
||||
coins.emplace_back(std::move(coin));
|
||||
}
|
||||
|
||||
for(uint32_t i = 0; i < mtx.vin.size(); i++) {
|
||||
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());
|
||||
check.swap(vChecks.back());
|
||||
control.Add(vChecks);
|
||||
|
|
171
src/txdb.cpp
171
src/txdb.cpp
|
@ -14,6 +14,7 @@
|
|||
|
||||
#include <boost/thread.hpp>
|
||||
|
||||
static const char DB_COIN = 'C';
|
||||
static const char DB_COINS = 'c';
|
||||
static const char DB_BLOCK_FILES = 'f';
|
||||
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_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)
|
||||
{
|
||||
}
|
||||
|
||||
bool CCoinsViewDB::GetCoins(const uint256 &txid, CCoins &coins) const {
|
||||
return db.Read(std::make_pair(DB_COINS, txid), coins);
|
||||
bool CCoinsViewDB::GetCoin(const COutPoint &outpoint, Coin &coin) const {
|
||||
return db.Read(CoinEntry(&outpoint), coin);
|
||||
}
|
||||
|
||||
bool CCoinsViewDB::HaveCoins(const uint256 &txid) const {
|
||||
return db.Exists(std::make_pair(DB_COINS, txid));
|
||||
bool CCoinsViewDB::HaveCoin(const COutPoint &outpoint) const {
|
||||
return db.Exists(CoinEntry(&outpoint));
|
||||
}
|
||||
|
||||
uint256 CCoinsViewDB::GetBestBlock() const {
|
||||
|
@ -50,10 +74,11 @@ bool CCoinsViewDB::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock) {
|
|||
size_t changed = 0;
|
||||
for (CCoinsMap::iterator it = mapCoins.begin(); it != mapCoins.end();) {
|
||||
if (it->second.flags & CCoinsCacheEntry::DIRTY) {
|
||||
if (it->second.coins.IsPruned())
|
||||
batch.Erase(std::make_pair(DB_COINS, it->first));
|
||||
CoinEntry entry(&it->first);
|
||||
if (it->second.coin.IsSpent())
|
||||
batch.Erase(entry);
|
||||
else
|
||||
batch.Write(std::make_pair(DB_COINS, it->first), it->second.coins);
|
||||
batch.Write(entry, it->second.coin);
|
||||
changed++;
|
||||
}
|
||||
count++;
|
||||
|
@ -63,8 +88,14 @@ bool CCoinsViewDB::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock) {
|
|||
if (!hashBlock.IsNull())
|
||||
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);
|
||||
return db.WriteBatch(batch);
|
||||
bool ret = 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) {
|
||||
|
@ -96,29 +127,31 @@ CCoinsViewCursor *CCoinsViewDB::Cursor() const
|
|||
/* 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
|
||||
that restriction. */
|
||||
i->pcursor->Seek(DB_COINS);
|
||||
i->pcursor->Seek(DB_COIN);
|
||||
// Cache key of first record
|
||||
if (i->pcursor->Valid()) {
|
||||
i->pcursor->GetKey(i->keyTmp);
|
||||
CoinEntry entry(&i->keyTmp.second);
|
||||
i->pcursor->GetKey(entry);
|
||||
i->keyTmp.first = entry.key;
|
||||
} else {
|
||||
i->keyTmp.first = 0; // Make sure Valid() and GetKey() return false
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
bool CCoinsViewDBCursor::GetKey(uint256 &key) const
|
||||
bool CCoinsViewDBCursor::GetKey(COutPoint &key) const
|
||||
{
|
||||
// Return cached key
|
||||
if (keyTmp.first == DB_COINS) {
|
||||
if (keyTmp.first == DB_COIN) {
|
||||
key = keyTmp.second;
|
||||
return true;
|
||||
}
|
||||
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
|
||||
|
@ -128,14 +161,18 @@ unsigned int CCoinsViewDBCursor::GetValueSize() const
|
|||
|
||||
bool CCoinsViewDBCursor::Valid() const
|
||||
{
|
||||
return keyTmp.first == DB_COINS;
|
||||
return keyTmp.first == DB_COIN;
|
||||
}
|
||||
|
||||
void CCoinsViewDBCursor::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
|
||||
} 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) {
|
||||
|
@ -215,3 +252,103 @@ bool CBlockTreeDB::LoadBlockIndexGuts(std::function<CBlockIndex*(const uint256&)
|
|||
|
||||
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;
|
||||
}
|
||||
|
|
24
src/txdb.h
24
src/txdb.h
|
@ -22,9 +22,7 @@ class uint256;
|
|||
//! Compensate for extra memory peak (x1.5-x1.9) at flush time.
|
||||
static constexpr int DB_PEAK_USAGE_FACTOR = 2;
|
||||
//! 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;
|
||||
//! Always periodic flush if less than this much space still available.
|
||||
static constexpr int MIN_BLOCK_COINSDB_USAGE = 50 * DB_PEAK_USAGE_FACTOR;
|
||||
static constexpr int MAX_BLOCK_COINSDB_USAGE = 10 * DB_PEAK_USAGE_FACTOR;
|
||||
//! -dbcache default (MiB)
|
||||
static const int64_t nDefaultDbCache = 450;
|
||||
//! max. -dbcache (MiB)
|
||||
|
@ -73,11 +71,15 @@ protected:
|
|||
public:
|
||||
CCoinsViewDB(size_t nCacheSize, bool fMemory = false, bool fWipe = false);
|
||||
|
||||
bool GetCoins(const uint256 &txid, CCoins &coins) const;
|
||||
bool HaveCoins(const uint256 &txid) const;
|
||||
uint256 GetBestBlock() const;
|
||||
bool BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock);
|
||||
CCoinsViewCursor *Cursor() const;
|
||||
bool GetCoin(const COutPoint &outpoint, Coin &coin) const override;
|
||||
bool HaveCoin(const COutPoint &outpoint) const override;
|
||||
uint256 GetBestBlock() const override;
|
||||
bool BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock) override;
|
||||
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 */
|
||||
|
@ -86,8 +88,8 @@ class CCoinsViewDBCursor: public CCoinsViewCursor
|
|||
public:
|
||||
~CCoinsViewDBCursor() {}
|
||||
|
||||
bool GetKey(uint256 &key) const;
|
||||
bool GetValue(CCoins &coins) const;
|
||||
bool GetKey(COutPoint &key) const;
|
||||
bool GetValue(Coin &coin) const;
|
||||
unsigned int GetValueSize() const;
|
||||
|
||||
bool Valid() const;
|
||||
|
@ -97,7 +99,7 @@ private:
|
|||
CCoinsViewDBCursor(CDBIterator* pcursorIn, const uint256 &hashBlockIn):
|
||||
CCoinsViewCursor(hashBlockIn), pcursor(pcursorIn) {}
|
||||
std::unique_ptr<CDBIterator> pcursor;
|
||||
std::pair<char, uint256> keyTmp;
|
||||
std::pair<char, COutPoint> keyTmp;
|
||||
|
||||
friend class CCoinsViewDB;
|
||||
};
|
||||
|
|
|
@ -343,17 +343,10 @@ CTxMemPool::CTxMemPool(CBlockPolicyEstimator* estimator) :
|
|||
nCheckFrequency = 0;
|
||||
}
|
||||
|
||||
void CTxMemPool::pruneSpent(const uint256 &hashTx, CCoins &coins)
|
||||
bool CTxMemPool::isSpent(const COutPoint& outpoint)
|
||||
{
|
||||
LOCK(cs);
|
||||
|
||||
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++;
|
||||
}
|
||||
return mapNextTx.count(outpoint);
|
||||
}
|
||||
|
||||
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);
|
||||
if (it2 != mapTx.end())
|
||||
continue;
|
||||
const CCoins *coins = pcoins->AccessCoins(txin.prevout.hash);
|
||||
if (nCheckFrequency != 0) assert(coins);
|
||||
if (!coins || (coins->IsCoinBase() && ((signed long)nMemPoolHeight) - coins->nHeight < COINBASE_MATURITY)) {
|
||||
const Coin &coin = pcoins->AccessCoin(txin.prevout);
|
||||
if (nCheckFrequency != 0) assert(!coin.IsSpent());
|
||||
if (coin.IsSpent() || (coin.IsCoinBase() && ((signed long)nMemPoolHeight) - coin.nHeight < COINBASE_MATURITY)) {
|
||||
txToRemove.insert(it);
|
||||
break;
|
||||
}
|
||||
|
@ -661,8 +654,7 @@ void CTxMemPool::check(const CCoinsViewCache *pcoins) const
|
|||
parentSigOpCost += it2->GetSigOpCost();
|
||||
}
|
||||
} else {
|
||||
const CCoins* coins = pcoins->AccessCoins(txin.prevout.hash);
|
||||
assert(coins && coins->IsAvailable(txin.prevout.n));
|
||||
assert(pcoins->HaveCoin(txin.prevout));
|
||||
}
|
||||
// Check whether its inputs are marked in mapNextTx.
|
||||
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) { }
|
||||
|
||||
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
|
||||
// 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.
|
||||
CTransactionRef ptx = mempool.get(txid);
|
||||
CTransactionRef ptx = mempool.get(outpoint.hash);
|
||||
if (ptx) {
|
||||
coins = CCoins(*ptx, MEMPOOL_HEIGHT);
|
||||
return true;
|
||||
if (outpoint.n < ptx->vout.size()) {
|
||||
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 {
|
||||
return mempool.exists(txid) || base->HaveCoins(txid);
|
||||
bool CCoinsViewMemPool::HaveCoin(const COutPoint &outpoint) const {
|
||||
return mempool.exists(outpoint) || base->HaveCoin(outpoint);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
unsigned nTxnRemoved = 0;
|
||||
|
@ -1053,11 +1049,10 @@ void CTxMemPool::TrimToSize(size_t sizelimit, std::vector<uint256>* pvNoSpendsRe
|
|||
if (pvNoSpendsRemaining) {
|
||||
BOOST_FOREACH(const CTransaction& tx, txn) {
|
||||
BOOST_FOREACH(const CTxIn& txin, tx.vin) {
|
||||
if (exists(txin.prevout.hash))
|
||||
continue;
|
||||
auto iter = mapNextTx.lower_bound(COutPoint(txin.prevout.hash, 0));
|
||||
if (iter == mapNextTx.end() || iter->first->hash != txin.prevout.hash)
|
||||
pvNoSpendsRemaining->push_back(txin.prevout.hash);
|
||||
if (exists(txin.prevout.hash)) continue;
|
||||
if (!mapNextTx.count(txin.prevout)) {
|
||||
pvNoSpendsRemaining->push_back(txin.prevout);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1074,3 +1069,5 @@ bool CTxMemPool::TransactionWithinChainLimit(const uint256& txid, size_t chainLi
|
|||
return it == mapTx.end() || (it->GetCountWithAncestors() < chainLimit &&
|
||||
it->GetCountWithDescendants() < chainLimit);
|
||||
}
|
||||
|
||||
SaltedTxidHasher::SaltedTxidHasher() : k0(GetRand(std::numeric_limits<uint64_t>::max())), k1(GetRand(std::numeric_limits<uint64_t>::max())) {}
|
||||
|
|
|
@ -31,8 +31,8 @@
|
|||
class CAutoFile;
|
||||
class CBlockIndex;
|
||||
|
||||
/** Fake height value used in CCoins to signify they are only in the memory pool (since 0.8) */
|
||||
static const unsigned int MEMPOOL_HEIGHT = 0x7FFFFFFF;
|
||||
/** Fake height value used in Coin to signify they are only in the memory pool (since 0.8) */
|
||||
static const uint32_t MEMPOOL_HEIGHT = 0x7FFFFFFF;
|
||||
|
||||
struct LockPoints
|
||||
{
|
||||
|
@ -327,6 +327,20 @@ enum class MemPoolRemovalReason {
|
|||
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
|
||||
* that may be included in the next block.
|
||||
|
@ -515,7 +529,7 @@ public:
|
|||
void _clear(); //lock free
|
||||
bool CompareDepthAndScore(const uint256& hasha, const uint256& hashb);
|
||||
void queryHashes(std::vector<uint256>& vtxid);
|
||||
void pruneSpent(const uint256& hash, CCoins &coins);
|
||||
bool isSpent(const COutPoint& outpoint);
|
||||
unsigned int GetTransactionsUpdated() const;
|
||||
void AddTransactionsUpdated(unsigned int n);
|
||||
/**
|
||||
|
@ -576,10 +590,10 @@ public:
|
|||
CFeeRate GetMinFee(size_t sizelimit) const;
|
||||
|
||||
/** 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.
|
||||
*/
|
||||
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. */
|
||||
int Expire(int64_t time);
|
||||
|
@ -605,6 +619,13 @@ public:
|
|||
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;
|
||||
TxMempoolInfo info(const uint256& hash) const;
|
||||
std::vector<TxMempoolInfo> infoAll() const;
|
||||
|
@ -664,8 +685,8 @@ protected:
|
|||
|
||||
public:
|
||||
CCoinsViewMemPool(CCoinsView* baseIn, const CTxMemPool& mempoolIn);
|
||||
bool GetCoins(const uint256 &txid, CCoins &coins) const;
|
||||
bool HaveCoins(const uint256 &txid) const;
|
||||
bool GetCoin(const COutPoint &outpoint, Coin &coin) const;
|
||||
bool HaveCoin(const COutPoint &outpoint) const;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
84
src/undo.h
84
src/undo.h
|
@ -7,58 +7,90 @@
|
|||
#define BITCOIN_UNDO_H
|
||||
|
||||
#include "compressor.h"
|
||||
#include "consensus/consensus.h"
|
||||
#include "primitives/transaction.h"
|
||||
#include "serialize.h"
|
||||
|
||||
/** Undo information for a CTxIn
|
||||
*
|
||||
* Contains the prevout's CTxOut being spent, and if this was the
|
||||
* last output of the affected transaction, its metadata as well
|
||||
* (coinbase or not, height, transaction version)
|
||||
* Contains the prevout's CTxOut being spent, and its metadata as well
|
||||
* (coinbase or not, height). The serialization contains a dummy value of
|
||||
* zero. This is be compatible with older versions which expect to see
|
||||
* the transaction version there.
|
||||
*/
|
||||
class CTxInUndo
|
||||
class TxInUndoSerializer
|
||||
{
|
||||
const Coin* txout;
|
||||
|
||||
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>
|
||||
void Serialize(Stream &s) const {
|
||||
::Serialize(s, VARINT(nHeight*2+(fCoinBase ? 1 : 0)));
|
||||
if (nHeight > 0)
|
||||
::Serialize(s, VARINT(this->nVersion));
|
||||
::Serialize(s, CTxOutCompressor(REF(txout)));
|
||||
::Serialize(s, VARINT(txout->nHeight * 2 + (txout->fCoinBase ? 1 : 0)));
|
||||
if (txout->nHeight > 0) {
|
||||
// Required to maintain compatibility with older undo format.
|
||||
::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>
|
||||
void Unserialize(Stream &s) {
|
||||
unsigned int nCode = 0;
|
||||
::Unserialize(s, VARINT(nCode));
|
||||
nHeight = nCode / 2;
|
||||
fCoinBase = nCode & 1;
|
||||
if (nHeight > 0)
|
||||
::Unserialize(s, VARINT(this->nVersion));
|
||||
::Unserialize(s, REF(CTxOutCompressor(REF(txout))));
|
||||
txout->nHeight = nCode / 2;
|
||||
txout->fCoinBase = nCode & 1;
|
||||
if (txout->nHeight > 0) {
|
||||
// Old versions stored the version number for the last spend of
|
||||
// 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 */
|
||||
class CTxUndo
|
||||
{
|
||||
public:
|
||||
// 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>
|
||||
inline void SerializationOp(Stream& s, Operation ser_action) {
|
||||
READWRITE(vprevout);
|
||||
template <typename Stream>
|
||||
void Unserialize(Stream& s) {
|
||||
// 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)));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -268,15 +268,15 @@ bool CheckSequenceLocks(const CTransaction &tx, int flags, LockPoints* lp, bool
|
|||
prevheights.resize(tx.vin.size());
|
||||
for (size_t txinIndex = 0; txinIndex < tx.vin.size(); txinIndex++) {
|
||||
const CTxIn& txin = tx.vin[txinIndex];
|
||||
CCoins coins;
|
||||
if (!viewMemPool.GetCoins(txin.prevout.hash, coins)) {
|
||||
Coin coin;
|
||||
if (!viewMemPool.GetCoin(txin.prevout, coin)) {
|
||||
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
|
||||
prevheights[txinIndex] = tip->nHeight + 1;
|
||||
} else {
|
||||
prevheights[txinIndex] = coins.nHeight;
|
||||
prevheights[txinIndex] = coin.nHeight;
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
std::vector<uint256> vNoSpendsRemaining;
|
||||
std::vector<COutPoint> vNoSpendsRemaining;
|
||||
pool.TrimToSize(limit, &vNoSpendsRemaining);
|
||||
BOOST_FOREACH(const uint256& removed, vNoSpendsRemaining)
|
||||
BOOST_FOREACH(const COutPoint& removed, vNoSpendsRemaining)
|
||||
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* 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 uint256 hash = tx.GetHash();
|
||||
|
@ -487,29 +487,29 @@ bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState& state, const C
|
|||
view.SetBackend(viewMemPool);
|
||||
|
||||
// do we already have it?
|
||||
bool fHadTxInCache = pcoinsTip->HaveCoinsInCache(hash);
|
||||
if (view.HaveCoins(hash)) {
|
||||
if (!fHadTxInCache)
|
||||
vHashTxnToUncache.push_back(hash);
|
||||
return state.Invalid(false, REJECT_ALREADY_KNOWN, "txn-already-known");
|
||||
}
|
||||
|
||||
// do all inputs exist?
|
||||
// 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()
|
||||
for (size_t out = 0; out < tx.vout.size(); out++) {
|
||||
COutPoint outpoint(hash, out);
|
||||
bool had_coin_in_cache = pcoinsTip->HaveCoinInCache(outpoint);
|
||||
if (view.HaveCoin(outpoint)) {
|
||||
if (!had_coin_in_cache) {
|
||||
coins_to_uncache.push_back(outpoint);
|
||||
}
|
||||
return state.Invalid(false, REJECT_ALREADY_KNOWN, "txn-already-known");
|
||||
}
|
||||
}
|
||||
|
||||
// are the actual inputs available?
|
||||
if (!view.HaveInputs(tx))
|
||||
return state.Invalid(false, REJECT_DUPLICATE, "bad-txns-inputs-spent");
|
||||
// do all inputs exist?
|
||||
BOOST_FOREACH(const CTxIn txin, tx.vin) {
|
||||
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
|
||||
view.GetBestBlock();
|
||||
|
@ -548,8 +548,8 @@ bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState& state, const C
|
|||
// during reorgs to ensure COINBASE_MATURITY is still met.
|
||||
bool fSpendsCoinbase = false;
|
||||
BOOST_FOREACH(const CTxIn &txin, tx.vin) {
|
||||
const CCoins *coins = view.AccessCoins(txin.prevout.hash);
|
||||
if (coins->IsCoinBase()) {
|
||||
const Coin &coin = view.AccessCoin(txin.prevout);
|
||||
if (coin.IsCoinBase()) {
|
||||
fSpendsCoinbase = true;
|
||||
break;
|
||||
}
|
||||
|
@ -813,10 +813,10 @@ bool AcceptToMemoryPoolWithTime(CTxMemPool& pool, CValidationState &state, const
|
|||
bool* pfMissingInputs, int64_t nAcceptTime, std::list<CTransactionRef>* plTxnReplaced,
|
||||
bool fOverrideMempoolLimit, const CAmount nAbsurdFee)
|
||||
{
|
||||
std::vector<uint256> vHashTxToUncache;
|
||||
bool res = AcceptToMemoryPoolWorker(pool, state, tx, fLimitFree, pfMissingInputs, nAcceptTime, plTxnReplaced, fOverrideMempoolLimit, nAbsurdFee, vHashTxToUncache);
|
||||
std::vector<COutPoint> coins_to_uncache;
|
||||
bool res = AcceptToMemoryPoolWorker(pool, state, tx, fLimitFree, pfMissingInputs, nAcceptTime, plTxnReplaced, fOverrideMempoolLimit, nAbsurdFee, coins_to_uncache);
|
||||
if (!res) {
|
||||
BOOST_FOREACH(const uint256& hashTx, vHashTxToUncache)
|
||||
BOOST_FOREACH(const COutPoint& hashTx, coins_to_uncache)
|
||||
pcoinsTip->Uncache(hashTx);
|
||||
}
|
||||
// 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
|
||||
int nHeight = -1;
|
||||
{
|
||||
const CCoinsViewCache& view = *pcoinsTip;
|
||||
const CCoins* coins = view.AccessCoins(hash);
|
||||
if (coins)
|
||||
nHeight = coins->nHeight;
|
||||
}
|
||||
if (nHeight > 0)
|
||||
pindexSlow = chainActive[nHeight];
|
||||
const Coin& coin = AccessByTxid(*pcoinsTip, hash);
|
||||
if (!coin.IsSpent()) pindexSlow = chainActive[coin.nHeight];
|
||||
}
|
||||
|
||||
if (pindexSlow) {
|
||||
|
@ -1124,24 +1117,12 @@ void UpdateCoins(const CTransaction& tx, CCoinsViewCache& inputs, CTxUndo &txund
|
|||
if (!tx.IsCoinBase()) {
|
||||
txundo.vprevout.reserve(tx.vin.size());
|
||||
BOOST_FOREACH(const CTxIn &txin, tx.vin) {
|
||||
CCoinsModifier coins = inputs.ModifyCoins(txin.prevout.hash);
|
||||
unsigned nPos = txin.prevout.n;
|
||||
|
||||
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;
|
||||
}
|
||||
txundo.vprevout.emplace_back();
|
||||
inputs.SpendCoin(txin.prevout, &txundo.vprevout.back());
|
||||
}
|
||||
}
|
||||
// add outputs
|
||||
inputs.ModifyNewCoins(tx.GetHash(), tx.IsCoinBase())->FromTx(tx, nHeight);
|
||||
AddCoins(inputs, tx, 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) {
|
||||
for (unsigned int i = 0; i < tx.vin.size(); i++) {
|
||||
const COutPoint &prevout = tx.vin[i].prevout;
|
||||
const CCoins* coins = inputs.AccessCoins(prevout.hash);
|
||||
assert(coins);
|
||||
const Coin& coin = inputs.AccessCoin(prevout);
|
||||
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
|
||||
CScriptCheck check(*coins, tx, i, flags, cacheStore, &txdata);
|
||||
CScriptCheck check(scriptPubKey, amount, tx, i, flags, cacheStore, &txdata);
|
||||
if (pvChecks) {
|
||||
pvChecks->push_back(CScriptCheck());
|
||||
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
|
||||
// avoid splitting the network between upgraded and
|
||||
// non-upgraded nodes.
|
||||
CScriptCheck check2(*coins, tx, i,
|
||||
CScriptCheck check2(scriptPubKey, amount, tx, i,
|
||||
flags & ~STANDARD_NOT_MANDATORY_VERIFY_FLAGS, cacheStore, &txdata);
|
||||
if (check2())
|
||||
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
|
||||
uint256 hashChecksum;
|
||||
CHashVerifier<CAutoFile> verifier(&filein); // We need a CHashVerifier as reserializing may lose data
|
||||
try {
|
||||
filein >> blockundo;
|
||||
verifier << hashBlock;
|
||||
verifier >> blockundo;
|
||||
filein >> hashChecksum;
|
||||
}
|
||||
catch (const std::exception& e) {
|
||||
|
@ -1269,10 +1260,7 @@ bool UndoReadFromDisk(CBlockUndo& blockundo, const CDiskBlockPos& pos, const uin
|
|||
}
|
||||
|
||||
// Verify checksum
|
||||
CHashWriter hasher(SER_GETHASH, PROTOCOL_VERSION);
|
||||
hasher << hashBlock;
|
||||
hasher << blockundo;
|
||||
if (hashChecksum != hasher.GetHash())
|
||||
if (hashChecksum != verifier.GetHash())
|
||||
return error("%s: Checksum mismatch", __func__);
|
||||
|
||||
return true;
|
||||
|
@ -1298,39 +1286,6 @@ bool AbortNode(CValidationState& state, const std::string& strMessage, const std
|
|||
|
||||
} // 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
|
||||
{
|
||||
DISCONNECT_OK, // All good.
|
||||
|
@ -1338,6 +1293,36 @@ enum DisconnectResult
|
|||
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.
|
||||
* When UNCLEAN or FAILED is returned, view is left in an indeterminate state. */
|
||||
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
|
||||
// exactly.
|
||||
{
|
||||
CCoinsModifier outs = view.ModifyCoins(hash);
|
||||
outs->ClearUnspendable();
|
||||
|
||||
CCoins outsBlock(tx, pindex->nHeight);
|
||||
// The CCoins serialization does not serialize negative numbers.
|
||||
// No network rules currently depend on the version here, so an inconsistency is harmless
|
||||
// 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();
|
||||
for (size_t o = 0; o < tx.vout.size(); o++) {
|
||||
if (!tx.vout[o].scriptPubKey.IsUnspendable()) {
|
||||
COutPoint out(hash, o);
|
||||
Coin coin;
|
||||
view.SpendCoin(out, &coin);
|
||||
if (tx.vout[o] != coin.out) {
|
||||
fClean = false; // transaction output mismatch
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// restore inputs
|
||||
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()) {
|
||||
error("DisconnectBlock(): transaction and undo data inconsistent");
|
||||
return DISCONNECT_FAILED;
|
||||
}
|
||||
for (unsigned int j = tx.vin.size(); j-- > 0;) {
|
||||
const COutPoint &out = tx.vin[j].prevout;
|
||||
const CTxInUndo &undo = txundo.vprevout[j];
|
||||
if (!ApplyTxInUndo(undo, view, out))
|
||||
fClean = false;
|
||||
int res = ApplyTxInUndo(std::move(txundo.vprevout[j]), view, out);
|
||||
if (res == DISCONNECT_FAILED) return DISCONNECT_FAILED;
|
||||
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) {
|
||||
for (const auto& tx : block.vtx) {
|
||||
const CCoins* coins = view.AccessCoins(tx->GetHash());
|
||||
if (coins && !coins->IsPruned())
|
||||
return state.DoS(100, error("ConnectBlock(): tried to overwrite transaction"),
|
||||
REJECT_INVALID, "bad-txns-BIP30");
|
||||
for (size_t o = 0; o < tx->vout.size(); o++) {
|
||||
if (view.HaveCoin(COutPoint(tx->GetHash(), o))) {
|
||||
return state.DoS(100, error("ConnectBlock(): tried to overwrite transaction"),
|
||||
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
|
||||
prevheights.resize(tx.vin.size());
|
||||
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)) {
|
||||
|
@ -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 cacheSize = pcoinsTip->DynamicMemoryUsage() * DB_PEAK_USAGE_FACTOR;
|
||||
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).
|
||||
bool fCacheLarge = mode == FLUSH_STATE_PERIODIC && cacheSize > std::min(std::max(nTotalSpace / 2, nTotalSpace - MIN_BLOCK_COINSDB_USAGE * 1024 * 1024),
|
||||
std::max((9 * nTotalSpace) / 10, nTotalSpace - MAX_BLOCK_COINSDB_USAGE * 1024 * 1024));
|
||||
// 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::max((9 * nTotalSpace) / 10, nTotalSpace - MAX_BLOCK_COINSDB_USAGE * 1024 * 1024);
|
||||
// The cache is over the limit, we have to write now.
|
||||
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.
|
||||
|
@ -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.
|
||||
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
|
||||
// twice (once in the log, and once in the tables). This is already
|
||||
// an overestimation, as most will delete an existing entry or
|
||||
// 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");
|
||||
// Flush the chainstate (which may refer to block index entries).
|
||||
if (!pcoinsTip->Flush())
|
||||
|
@ -1917,7 +1898,7 @@ void static UpdateTip(CBlockIndex *pindexNew, const CChainParams& chainParams) {
|
|||
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,
|
||||
log(chainActive.Tip()->nChainWork.getdouble())/log(2.0), (unsigned long)chainActive.Tip()->nChainTx,
|
||||
DateTimeStrFormat("%Y-%m-%d %H:%M:%S", chainActive.Tip()->GetBlockTime()),
|
||||
|
|
|
@ -406,8 +406,8 @@ private:
|
|||
|
||||
public:
|
||||
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) :
|
||||
scriptPubKey(txFromIn.vout[txToIn.vin[nInIn].prevout.n].scriptPubKey), amount(txFromIn.vout[txToIn.vin[nInIn].prevout.n].nValue),
|
||||
CScriptCheck(const CScript& scriptPubKeyIn, const CAmount amountIn, const CTransaction& txToIn, unsigned int nInIn, unsigned int nFlagsIn, bool cacheIn, PrecomputedTransactionData* txdataIn) :
|
||||
scriptPubKey(scriptPubKeyIn), amount(amountIn),
|
||||
ptxTo(&txToIn), nIn(nInIn), nFlags(nFlagsIn), cacheStore(cacheIn), error(SCRIPT_ERR_UNKNOWN_ERROR), txdata(txdataIn) { }
|
||||
|
||||
bool operator()();
|
||||
|
|
|
@ -49,10 +49,12 @@ class BlockchainTest(BitcoinTestFramework):
|
|||
assert_equal(res['transactions'], 200)
|
||||
assert_equal(res['height'], 200)
|
||||
assert_equal(res['txouts'], 200)
|
||||
assert_equal(res['bytes_serialized'], 13924),
|
||||
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['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")
|
||||
b1hash = node.getblockhash(1)
|
||||
|
@ -64,7 +66,7 @@ class BlockchainTest(BitcoinTestFramework):
|
|||
assert_equal(res2['height'], 0)
|
||||
assert_equal(res2['txouts'], 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")
|
||||
node.reconsiderblock(b1hash)
|
||||
|
@ -75,7 +77,7 @@ class BlockchainTest(BitcoinTestFramework):
|
|||
assert_equal(res['height'], res3['height'])
|
||||
assert_equal(res['txouts'], res3['txouts'])
|
||||
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):
|
||||
node = self.nodes[0]
|
||||
|
|
Loading…
Reference in a new issue