Merge pull request #4834

7c70438 Get rid of the dummy CCoinsViewCache constructor arg (Pieter Wuille)
ed27e53 Add coins_tests with a large randomized CCoinViewCache test. (Pieter Wuille)
058b08c Do not keep fully spent but unwritten CCoins entries cached. (Pieter Wuille)
c9d1a81 Get rid of CCoinsView's SetCoins and SetBestBlock. (Pieter Wuille)
f28aec0 Use ModifyCoins instead of mutable GetCoins. (Pieter Wuille)
This commit is contained in:
Pieter Wuille 2014-10-08 14:57:31 -07:00
commit d4a42334d4
No known key found for this signature in database
GPG key ID: 57896D2FF8F0B657
17 changed files with 399 additions and 153 deletions

View file

@ -44,6 +44,7 @@ BITCOIN_TESTS =\
test/checkblock_tests.cpp \ test/checkblock_tests.cpp \
test/Checkpoints_tests.cpp \ test/Checkpoints_tests.cpp \
test/compress_tests.cpp \ test/compress_tests.cpp \
test/coins_tests.cpp \
test/crypto_tests.cpp \ test/crypto_tests.cpp \
test/DoS_tests.cpp \ test/DoS_tests.cpp \
test/getarg_tests.cpp \ test/getarg_tests.cpp \

View file

@ -340,7 +340,7 @@ static void MutateTxSign(CMutableTransaction& tx, const string& flagStr)
CMutableTransaction mergedTx(txVariants[0]); CMutableTransaction mergedTx(txVariants[0]);
bool fComplete = true; bool fComplete = true;
CCoinsView viewDummy; CCoinsView viewDummy;
CCoinsViewCache view(viewDummy); CCoinsViewCache view(&viewDummy);
if (!registers.count("privatekeys")) if (!registers.count("privatekeys"))
throw runtime_error("privatekeys register variable must be set."); throw runtime_error("privatekeys register variable must be set.");
@ -384,21 +384,19 @@ static void MutateTxSign(CMutableTransaction& tx, const string& flagStr)
vector<unsigned char> pkData(ParseHexUV(prevOut, "scriptPubKey")); vector<unsigned char> pkData(ParseHexUV(prevOut, "scriptPubKey"));
CScript scriptPubKey(pkData.begin(), pkData.end()); CScript scriptPubKey(pkData.begin(), pkData.end());
CCoins coins; {
if (view.GetCoins(txid, coins)) { CCoinsModifier coins = view.ModifyCoins(txid);
if (coins.IsAvailable(nOut) && coins.vout[nOut].scriptPubKey != scriptPubKey) { if (coins->IsAvailable(nOut) && coins->vout[nOut].scriptPubKey != scriptPubKey) {
string err("Previous output scriptPubKey mismatch:\n"); string err("Previous output scriptPubKey mismatch:\n");
err = err + coins.vout[nOut].scriptPubKey.ToString() + "\nvs:\n"+ err = err + coins->vout[nOut].scriptPubKey.ToString() + "\nvs:\n"+
scriptPubKey.ToString(); scriptPubKey.ToString();
throw runtime_error(err); throw runtime_error(err);
} }
// what todo if txid is known, but the actual output isn't? if ((unsigned int)nOut >= coins->vout.size())
coins->vout.resize(nOut+1);
coins->vout[nOut].scriptPubKey = scriptPubKey;
coins->vout[nOut].nValue = 0; // we don't know the actual output value
} }
if ((unsigned int)nOut >= coins.vout.size())
coins.vout.resize(nOut+1);
coins.vout[nOut].scriptPubKey = scriptPubKey;
coins.vout[nOut].nValue = 0; // we don't know the actual output value
view.SetCoins(txid, coins);
// if redeemScript given and private keys given, // if redeemScript given and private keys given,
// add redeemScript to the tempKeystore so it can be signed: // add redeemScript to the tempKeystore so it can be signed:

View file

@ -53,61 +53,72 @@ bool CCoins::Spend(int nPos) {
bool CCoinsView::GetCoins(const uint256 &txid, CCoins &coins) const { return false; } bool CCoinsView::GetCoins(const uint256 &txid, CCoins &coins) const { return false; }
bool CCoinsView::SetCoins(const uint256 &txid, const CCoins &coins) { return false; }
bool CCoinsView::HaveCoins(const uint256 &txid) const { return false; } bool CCoinsView::HaveCoins(const uint256 &txid) const { return false; }
uint256 CCoinsView::GetBestBlock() const { return uint256(0); } uint256 CCoinsView::GetBestBlock() const { return uint256(0); }
bool CCoinsView::SetBestBlock(const uint256 &hashBlock) { return false; }
bool CCoinsView::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock) { return false; } bool CCoinsView::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock) { return false; }
bool CCoinsView::GetStats(CCoinsStats &stats) const { return false; } bool CCoinsView::GetStats(CCoinsStats &stats) const { return false; }
CCoinsViewBacked::CCoinsViewBacked(CCoinsView &viewIn) : base(&viewIn) { } CCoinsViewBacked::CCoinsViewBacked(CCoinsView *viewIn) : base(viewIn) { }
bool CCoinsViewBacked::GetCoins(const uint256 &txid, CCoins &coins) const { return base->GetCoins(txid, coins); } bool CCoinsViewBacked::GetCoins(const uint256 &txid, CCoins &coins) const { return base->GetCoins(txid, coins); }
bool CCoinsViewBacked::SetCoins(const uint256 &txid, const CCoins &coins) { return base->SetCoins(txid, coins); }
bool CCoinsViewBacked::HaveCoins(const uint256 &txid) const { return base->HaveCoins(txid); } bool CCoinsViewBacked::HaveCoins(const uint256 &txid) const { return base->HaveCoins(txid); }
uint256 CCoinsViewBacked::GetBestBlock() const { return base->GetBestBlock(); } uint256 CCoinsViewBacked::GetBestBlock() const { return base->GetBestBlock(); }
bool CCoinsViewBacked::SetBestBlock(const uint256 &hashBlock) { return base->SetBestBlock(hashBlock); }
void CCoinsViewBacked::SetBackend(CCoinsView &viewIn) { base = &viewIn; } void CCoinsViewBacked::SetBackend(CCoinsView &viewIn) { base = &viewIn; }
bool CCoinsViewBacked::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock) { return base->BatchWrite(mapCoins, hashBlock); } bool CCoinsViewBacked::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock) { return base->BatchWrite(mapCoins, hashBlock); }
bool CCoinsViewBacked::GetStats(CCoinsStats &stats) const { return base->GetStats(stats); } bool CCoinsViewBacked::GetStats(CCoinsStats &stats) const { return base->GetStats(stats); }
CCoinsKeyHasher::CCoinsKeyHasher() : salt(GetRandHash()) {} CCoinsKeyHasher::CCoinsKeyHasher() : salt(GetRandHash()) {}
CCoinsViewCache::CCoinsViewCache(CCoinsView &baseIn, bool fDummy) : CCoinsViewBacked(baseIn), hashBlock(0) { } CCoinsViewCache::CCoinsViewCache(CCoinsView *baseIn) : CCoinsViewBacked(baseIn), hasModifier(false), hashBlock(0) { }
CCoinsViewCache::~CCoinsViewCache()
{
assert(!hasModifier);
}
CCoinsMap::const_iterator CCoinsViewCache::FetchCoins(const uint256 &txid) const {
CCoinsMap::iterator it = cacheCoins.find(txid);
if (it != cacheCoins.end())
return it;
CCoins tmp;
if (!base->GetCoins(txid, 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
// version as fresh.
ret->second.flags = CCoinsCacheEntry::FRESH;
}
return ret;
}
bool CCoinsViewCache::GetCoins(const uint256 &txid, CCoins &coins) const { bool CCoinsViewCache::GetCoins(const uint256 &txid, CCoins &coins) const {
if (cacheCoins.count(txid)) { CCoinsMap::const_iterator it = FetchCoins(txid);
coins = cacheCoins[txid]; if (it != cacheCoins.end()) {
return true; coins = it->second.coins;
}
if (base->GetCoins(txid, coins)) {
cacheCoins[txid] = coins;
return true; return true;
} }
return false; return false;
} }
CCoinsMap::iterator CCoinsViewCache::FetchCoins(const uint256 &txid) { CCoinsModifier CCoinsViewCache::ModifyCoins(const uint256 &txid) {
CCoinsMap::iterator it = cacheCoins.find(txid); assert(!hasModifier);
if (it != cacheCoins.end()) hasModifier = true;
return it; std::pair<CCoinsMap::iterator, bool> ret = cacheCoins.insert(std::make_pair(txid, CCoinsCacheEntry()));
CCoins tmp; if (ret.second) {
if (!base->GetCoins(txid,tmp)) if (!base->GetCoins(txid, ret.first->second.coins)) {
return cacheCoins.end(); // The parent view does not have this entry; mark it as fresh.
CCoinsMap::iterator ret = cacheCoins.insert(it, std::make_pair(txid, CCoins())); ret.first->second.coins.Clear();
tmp.swap(ret->second); ret.first->second.flags = CCoinsCacheEntry::FRESH;
return ret; } 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;
CCoinsMap::const_iterator CCoinsViewCache::FetchCoins(const uint256 &txid) const { }
/* Avoid redundant implementation with the const-cast. */ }
return const_cast<CCoinsViewCache*>(this)->FetchCoins(txid); // Assume that whenever ModifyCoins is called, the entry will be modified.
} ret.first->second.flags |= CCoinsCacheEntry::DIRTY;
return CCoinsModifier(*this, ret.first);
CCoins &CCoinsViewCache::GetCoins(const uint256 &txid) {
CCoinsMap::iterator it = FetchCoins(txid);
assert(it != cacheCoins.end());
return it->second;
} }
const CCoins* CCoinsViewCache::AccessCoins(const uint256 &txid) const { const CCoins* CCoinsViewCache::AccessCoins(const uint256 &txid) const {
@ -115,22 +126,17 @@ const CCoins* CCoinsViewCache::AccessCoins(const uint256 &txid) const {
if (it == cacheCoins.end()) { if (it == cacheCoins.end()) {
return NULL; return NULL;
} else { } else {
return &it->second; return &it->second.coins;
} }
} }
bool CCoinsViewCache::SetCoins(const uint256 &txid, const CCoins &coins) {
cacheCoins[txid] = coins;
return true;
}
bool CCoinsViewCache::HaveCoins(const uint256 &txid) const { bool CCoinsViewCache::HaveCoins(const uint256 &txid) const {
CCoinsMap::const_iterator it = FetchCoins(txid); CCoinsMap::const_iterator it = FetchCoins(txid);
// We're using vtx.empty() instead of IsPruned here for performance reasons, // We're using vtx.empty() instead of IsPruned here for performance reasons,
// as we only care about the case where an transaction was replaced entirely // as we only care about the case where an transaction was replaced entirely
// in a reorganization (which wipes vout entirely, as opposed to spending // in a reorganization (which wipes vout entirely, as opposed to spending
// which just cleans individual outputs). // which just cleans individual outputs).
return (it != cacheCoins.end() && !it->second.vout.empty()); return (it != cacheCoins.end() && !it->second.coins.vout.empty());
} }
uint256 CCoinsViewCache::GetBestBlock() const { uint256 CCoinsViewCache::GetBestBlock() const {
@ -139,14 +145,39 @@ uint256 CCoinsViewCache::GetBestBlock() const {
return hashBlock; return hashBlock;
} }
bool CCoinsViewCache::SetBestBlock(const uint256 &hashBlockIn) { void CCoinsViewCache::SetBestBlock(const uint256 &hashBlockIn) {
hashBlock = hashBlockIn; hashBlock = hashBlockIn;
return true;
} }
bool CCoinsViewCache::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlockIn) { bool CCoinsViewCache::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlockIn) {
assert(!hasModifier);
for (CCoinsMap::iterator it = mapCoins.begin(); it != mapCoins.end();) { for (CCoinsMap::iterator it = mapCoins.begin(); it != mapCoins.end();) {
cacheCoins[it->first].swap(it->second); if (it->second.flags & CCoinsCacheEntry::DIRTY) { // Ignore non-dirty entries (optimization).
CCoinsMap::iterator itUs = cacheCoins.find(it->first);
if (itUs == cacheCoins.end()) {
if (!it->second.coins.IsPruned()) {
// The parent cache does not have an entry, while the child
// cache does have (a non-pruned) one. Move the data up, and
// mark it as fresh (if the grandparent did have it, we
// would have pulled it in at first GetCoins).
assert(it->second.flags & CCoinsCacheEntry::FRESH);
CCoinsCacheEntry& entry = cacheCoins[it->first];
entry.coins.swap(it->second.coins);
entry.flags = CCoinsCacheEntry::DIRTY | CCoinsCacheEntry::FRESH;
}
} else {
if ((itUs->second.flags & CCoinsCacheEntry::FRESH) && it->second.coins.IsPruned()) {
// 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.
cacheCoins.erase(itUs);
} else {
// A normal modification.
itUs->second.coins.swap(it->second.coins);
itUs->second.flags |= CCoinsCacheEntry::DIRTY;
}
}
}
CCoinsMap::iterator itOld = it++; CCoinsMap::iterator itOld = it++;
mapCoins.erase(itOld); mapCoins.erase(itOld);
} }
@ -213,3 +244,15 @@ double CCoinsViewCache::GetPriority(const CTransaction &tx, int nHeight) const
} }
return tx.ComputePriority(dResult); return tx.ComputePriority(dResult);
} }
CCoinsModifier::CCoinsModifier(CCoinsViewCache& cache_, CCoinsMap::iterator it_) : cache(cache_), it(it_) {}
CCoinsModifier::~CCoinsModifier()
{
assert(cache.hasModifier);
cache.hasModifier = false;
it->second.coins.Cleanup();
if ((it->second.flags & CCoinsCacheEntry::FRESH) && it->second.coins.IsPruned()) {
cache.cacheCoins.erase(it);
}
}

View file

@ -83,11 +83,26 @@ public:
// as new tx version will probably only be introduced at certain heights // as new tx version will probably only be introduced at certain heights
int nVersion; int nVersion;
// construct a CCoins from a CTransaction, at a given height void FromTx(const CTransaction &tx, int nHeightIn) {
CCoins(const CTransaction &tx, int nHeightIn) : fCoinBase(tx.IsCoinBase()), vout(tx.vout), nHeight(nHeightIn), nVersion(tx.nVersion) { fCoinBase = tx.IsCoinBase();
vout = tx.vout;
nHeight = nHeightIn;
nVersion = tx.nVersion;
ClearUnspendable(); ClearUnspendable();
} }
// construct a CCoins from a CTransaction, at a given height
CCoins(const CTransaction &tx, int nHeightIn) {
FromTx(tx, nHeightIn);
}
void Clear() {
fCoinBase = false;
std::vector<CTxOut>().swap(vout);
nHeight = 0;
nVersion = 0;
}
// empty constructor // empty constructor
CCoins() : fCoinBase(false), vout(0), nHeight(0), nVersion(0) { } CCoins() : fCoinBase(false), vout(0), nHeight(0), nVersion(0) { }
@ -256,7 +271,20 @@ public:
} }
}; };
typedef boost::unordered_map<uint256, CCoins, CCoinsKeyHasher> CCoinsMap; struct CCoinsCacheEntry
{
CCoins coins; // The actual cached data.
unsigned char flags;
enum Flags {
DIRTY = (1 << 0), // This cache entry is potentially different from the version in the parent view.
FRESH = (1 << 1), // The parent view does not have this entry (or it is pruned).
};
CCoinsCacheEntry() : coins(), flags(0) {}
};
typedef boost::unordered_map<uint256, CCoinsCacheEntry, CCoinsKeyHasher> CCoinsMap;
struct CCoinsStats struct CCoinsStats
{ {
@ -279,9 +307,6 @@ public:
// Retrieve the CCoins (unspent transaction outputs) for a given txid // Retrieve the CCoins (unspent transaction outputs) for a given txid
virtual bool GetCoins(const uint256 &txid, CCoins &coins) const; virtual bool GetCoins(const uint256 &txid, CCoins &coins) const;
// Modify the CCoins for a given txid
virtual bool SetCoins(const uint256 &txid, const CCoins &coins);
// Just check whether we have data for a given txid. // Just check whether we have data for a given txid.
// This may (but cannot always) return true for fully spent transactions // This may (but cannot always) return true for fully spent transactions
virtual bool HaveCoins(const uint256 &txid) const; virtual bool HaveCoins(const uint256 &txid) const;
@ -289,10 +314,7 @@ public:
// Retrieve the block hash whose state this CCoinsView currently represents // Retrieve the block hash whose state this CCoinsView currently represents
virtual uint256 GetBestBlock() const; virtual uint256 GetBestBlock() const;
// Modify the currently active block hash // Do a bulk modification (multiple CCoins changes + BestBlock change).
virtual bool SetBestBlock(const uint256 &hashBlock);
// Do a bulk modification (multiple SetCoins + one SetBestBlock).
// The passed mapCoins can be modified. // The passed mapCoins can be modified.
virtual bool BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock); virtual bool BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock);
@ -311,22 +333,41 @@ protected:
CCoinsView *base; CCoinsView *base;
public: public:
CCoinsViewBacked(CCoinsView &viewIn); CCoinsViewBacked(CCoinsView *viewIn);
bool GetCoins(const uint256 &txid, CCoins &coins) const; bool GetCoins(const uint256 &txid, CCoins &coins) const;
bool SetCoins(const uint256 &txid, const CCoins &coins);
bool HaveCoins(const uint256 &txid) const; bool HaveCoins(const uint256 &txid) const;
uint256 GetBestBlock() const; uint256 GetBestBlock() const;
bool SetBestBlock(const uint256 &hashBlock);
void SetBackend(CCoinsView &viewIn); void SetBackend(CCoinsView &viewIn);
bool BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock); bool BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock);
bool GetStats(CCoinsStats &stats) const; bool GetStats(CCoinsStats &stats) const;
}; };
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;
CCoinsModifier(CCoinsViewCache& cache_, CCoinsMap::iterator it_);
public:
CCoins* operator->() { return &it->second.coins; }
CCoins& operator*() { return it->second.coins; }
~CCoinsModifier();
friend class CCoinsViewCache;
};
/** CCoinsView that adds a memory cache for transactions to another CCoinsView */ /** CCoinsView that adds a memory cache for transactions to another CCoinsView */
class CCoinsViewCache : public CCoinsViewBacked class CCoinsViewCache : public CCoinsViewBacked
{ {
protected: protected:
/* Whether this cache has an active modifier. */
bool hasModifier;
/* Make mutable so that we can "fill the cache" even from Get-methods /* Make mutable so that we can "fill the cache" even from Get-methods
declared as "const". */ declared as "const". */
@ -334,14 +375,14 @@ protected:
mutable CCoinsMap cacheCoins; mutable CCoinsMap cacheCoins;
public: public:
CCoinsViewCache(CCoinsView &baseIn, bool fDummy = false); CCoinsViewCache(CCoinsView *baseIn);
~CCoinsViewCache();
// Standard CCoinsView methods // Standard CCoinsView methods
bool GetCoins(const uint256 &txid, CCoins &coins) const; bool GetCoins(const uint256 &txid, CCoins &coins) const;
bool SetCoins(const uint256 &txid, const CCoins &coins);
bool HaveCoins(const uint256 &txid) const; bool HaveCoins(const uint256 &txid) const;
uint256 GetBestBlock() const; uint256 GetBestBlock() const;
bool SetBestBlock(const uint256 &hashBlock); void SetBestBlock(const uint256 &hashBlock);
bool BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock); bool BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock);
// Return a pointer to CCoins in the cache, or NULL if not found. This is // Return a pointer to CCoins in the cache, or NULL if not found. This is
@ -349,8 +390,10 @@ public:
// allowed while accessing the returned pointer. // allowed while accessing the returned pointer.
const CCoins* AccessCoins(const uint256 &txid) const; const CCoins* AccessCoins(const uint256 &txid) const;
// Return a modifiable reference to a CCoins. Check HaveCoins first. // Return a modifiable reference to a CCoins. If no entry with the given
CCoins &GetCoins(const uint256 &txid); // txid exists, a new one is created. Simultaneous modifications are not
// allowed.
CCoinsModifier ModifyCoins(const uint256 &txid);
// Push the modifications applied to this cache to its base. // Push the modifications applied to this cache to its base.
// Failure to call this method before destruction will cause the changes to be forgotten. // Failure to call this method before destruction will cause the changes to be forgotten.
@ -377,6 +420,8 @@ public:
const CTxOut &GetOutputFor(const CTxIn& input) const; const CTxOut &GetOutputFor(const CTxIn& input) const;
friend class CCoinsModifier;
private: private:
CCoinsMap::iterator FetchCoins(const uint256 &txid); CCoinsMap::iterator FetchCoins(const uint256 &txid);
CCoinsMap::const_iterator FetchCoins(const uint256 &txid) const; CCoinsMap::const_iterator FetchCoins(const uint256 &txid) const;

View file

@ -955,7 +955,7 @@ bool AppInit2(boost::thread_group& threadGroup)
pblocktree = new CBlockTreeDB(nBlockTreeDBCache, false, fReindex); pblocktree = new CBlockTreeDB(nBlockTreeDBCache, false, fReindex);
pcoinsdbview = new CCoinsViewDB(nCoinDBCache, false, fReindex); pcoinsdbview = new CCoinsViewDB(nCoinDBCache, false, fReindex);
pcoinsTip = new CCoinsViewCache(*pcoinsdbview); pcoinsTip = new CCoinsViewCache(pcoinsdbview);
if (fReindex) if (fReindex)
pblocktree->WriteReindexing(true); pblocktree->WriteReindexing(true);

View file

@ -844,12 +844,12 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa
{ {
CCoinsView dummy; CCoinsView dummy;
CCoinsViewCache view(dummy); CCoinsViewCache view(&dummy);
CAmount nValueIn = 0; CAmount nValueIn = 0;
{ {
LOCK(pool.cs); LOCK(pool.cs);
CCoinsViewMemPool viewMemPool(*pcoinsTip, pool); CCoinsViewMemPool viewMemPool(pcoinsTip, pool);
view.SetBackend(viewMemPool); view.SetBackend(viewMemPool);
// do we already have it? // do we already have it?
@ -1296,22 +1296,18 @@ void static InvalidBlockFound(CBlockIndex *pindex, const CValidationState &state
void UpdateCoins(const CTransaction& tx, CValidationState &state, CCoinsViewCache &inputs, CTxUndo &txundo, int nHeight) void UpdateCoins(const CTransaction& tx, CValidationState &state, CCoinsViewCache &inputs, CTxUndo &txundo, int nHeight)
{ {
bool ret;
// mark inputs spent // mark inputs spent
if (!tx.IsCoinBase()) { if (!tx.IsCoinBase()) {
txundo.vprevout.reserve(tx.vin.size()); txundo.vprevout.reserve(tx.vin.size());
for (unsigned int i = 0; i < tx.vin.size(); i++) { BOOST_FOREACH(const CTxIn &txin, tx.vin) {
const CTxIn &txin = tx.vin[i];
CCoins &coins = inputs.GetCoins(txin.prevout.hash);
txundo.vprevout.push_back(CTxInUndo()); txundo.vprevout.push_back(CTxInUndo());
ret = coins.Spend(txin.prevout, txundo.vprevout.back()); bool ret = inputs.ModifyCoins(txin.prevout.hash)->Spend(txin.prevout, txundo.vprevout.back());
assert(ret); assert(ret);
} }
} }
// add outputs // add outputs
ret = inputs.SetCoins(tx.GetHash(), CCoins(tx, nHeight)); inputs.ModifyCoins(tx.GetHash())->FromTx(tx, nHeight);
assert(ret);
} }
bool CScriptCheck::operator()() const { bool CScriptCheck::operator()() const {
@ -1453,21 +1449,23 @@ bool DisconnectBlock(CBlock& block, CValidationState& state, CBlockIndex* pindex
// exactly. Note that transactions with only provably unspendable outputs won't // exactly. Note that transactions with only provably unspendable outputs won't
// have outputs available even in the block itself, so we handle that case // have outputs available even in the block itself, so we handle that case
// specially with outsEmpty. // specially with outsEmpty.
{
CCoins outsEmpty; CCoins outsEmpty;
CCoins &outs = view.HaveCoins(hash) ? view.GetCoins(hash) : outsEmpty; CCoinsModifier outs = view.ModifyCoins(hash);
outs.ClearUnspendable(); outs->ClearUnspendable();
CCoins outsBlock = CCoins(tx, pindex->nHeight); CCoins outsBlock(tx, pindex->nHeight);
// The CCoins serialization does not serialize negative numbers. // The CCoins serialization does not serialize negative numbers.
// No network rules currently depend on the version here, so an inconsistency is harmless // 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. // but it must be corrected before txout nversion ever influences a network rule.
if (outsBlock.nVersion < 0) if (outsBlock.nVersion < 0)
outs.nVersion = outsBlock.nVersion; outs->nVersion = outsBlock.nVersion;
if (outs != outsBlock) if (*outs != outsBlock)
fClean = fClean && error("DisconnectBlock() : added transaction mismatch? database corrupted"); fClean = fClean && error("DisconnectBlock() : added transaction mismatch? database corrupted");
// remove outputs // remove outputs
outs = CCoins(); outs->Clear();
}
// restore inputs // restore inputs
if (i > 0) { // not coinbases if (i > 0) { // not coinbases
@ -1477,27 +1475,24 @@ bool DisconnectBlock(CBlock& block, CValidationState& state, CBlockIndex* pindex
for (unsigned int j = tx.vin.size(); j-- > 0;) { for (unsigned int j = tx.vin.size(); j-- > 0;) {
const COutPoint &out = tx.vin[j].prevout; const COutPoint &out = tx.vin[j].prevout;
const CTxInUndo &undo = txundo.vprevout[j]; const CTxInUndo &undo = txundo.vprevout[j];
CCoins coins; CCoinsModifier coins = view.ModifyCoins(out.hash);
view.GetCoins(out.hash, coins); // this can fail if the prevout was already entirely spent
if (undo.nHeight != 0) { if (undo.nHeight != 0) {
// undo data contains height: this is the last output of the prevout tx being spent // undo data contains height: this is the last output of the prevout tx being spent
if (!coins.IsPruned()) if (!coins->IsPruned())
fClean = fClean && error("DisconnectBlock() : undo data overwriting existing transaction"); fClean = fClean && error("DisconnectBlock() : undo data overwriting existing transaction");
coins = CCoins(); coins->Clear();
coins.fCoinBase = undo.fCoinBase; coins->fCoinBase = undo.fCoinBase;
coins.nHeight = undo.nHeight; coins->nHeight = undo.nHeight;
coins.nVersion = undo.nVersion; coins->nVersion = undo.nVersion;
} else { } else {
if (coins.IsPruned()) if (coins->IsPruned())
fClean = fClean && error("DisconnectBlock() : undo data adding output to missing transaction"); fClean = fClean && error("DisconnectBlock() : undo data adding output to missing transaction");
} }
if (coins.IsAvailable(out.n)) if (coins->IsAvailable(out.n))
fClean = fClean && error("DisconnectBlock() : undo data overwriting existing output"); fClean = fClean && error("DisconnectBlock() : undo data overwriting existing output");
if (coins.vout.size() < out.n+1) if (coins->vout.size() < out.n+1)
coins.vout.resize(out.n+1); coins->vout.resize(out.n+1);
coins.vout[out.n] = undo.txout; coins->vout[out.n] = undo.txout;
if (!view.SetCoins(out.hash, coins))
return error("DisconnectBlock() : cannot restore coin inputs");
} }
} }
} }
@ -1706,9 +1701,7 @@ bool ConnectBlock(CBlock& block, CValidationState& state, CBlockIndex* pindex, C
return state.Abort("Failed to write transaction index"); return state.Abort("Failed to write transaction index");
// add this block to the view's block chain // add this block to the view's block chain
bool ret; view.SetBestBlock(pindex->GetBlockHash());
ret = view.SetBestBlock(pindex->GetBlockHash());
assert(ret);
int64_t nTime3 = GetTimeMicros(); nTimeIndex += nTime3 - nTime2; int64_t nTime3 = GetTimeMicros(); nTimeIndex += nTime3 - nTime2;
LogPrint("bench", " - Index writing: %.2fms [%.2fs]\n", 0.001 * (nTime3 - nTime2), nTimeIndex * 0.000001); LogPrint("bench", " - Index writing: %.2fms [%.2fs]\n", 0.001 * (nTime3 - nTime2), nTimeIndex * 0.000001);
@ -1752,10 +1745,10 @@ void static UpdateTip(CBlockIndex *pindexNew) {
nTimeBestReceived = GetTime(); nTimeBestReceived = GetTime();
mempool.AddTransactionsUpdated(1); mempool.AddTransactionsUpdated(1);
LogPrintf("UpdateTip: new best=%s height=%d log2_work=%.8g tx=%lu date=%s progress=%f\n", LogPrintf("UpdateTip: new best=%s height=%d log2_work=%.8g tx=%lu date=%s progress=%f cache=%u\n",
chainActive.Tip()->GetBlockHash().ToString(), chainActive.Height(), log(chainActive.Tip()->nChainWork.getdouble())/log(2.0), (unsigned long)chainActive.Tip()->nChainTx, chainActive.Tip()->GetBlockHash().ToString(), chainActive.Height(), log(chainActive.Tip()->nChainWork.getdouble())/log(2.0), (unsigned long)chainActive.Tip()->nChainTx,
DateTimeStrFormat("%Y-%m-%d %H:%M:%S", chainActive.Tip()->GetBlockTime()), DateTimeStrFormat("%Y-%m-%d %H:%M:%S", chainActive.Tip()->GetBlockTime()),
Checkpoints::GuessVerificationProgress(chainActive.Tip())); Checkpoints::GuessVerificationProgress(chainActive.Tip()), (unsigned int)pcoinsTip->GetCacheSize());
cvBlockChange.notify_all(); cvBlockChange.notify_all();
@ -1790,7 +1783,7 @@ bool static DisconnectTip(CValidationState &state) {
// Apply the block atomically to the chain state. // Apply the block atomically to the chain state.
int64_t nStart = GetTimeMicros(); int64_t nStart = GetTimeMicros();
{ {
CCoinsViewCache view(*pcoinsTip, true); CCoinsViewCache view(pcoinsTip);
if (!DisconnectBlock(block, state, pindexDelete, view)) if (!DisconnectBlock(block, state, pindexDelete, view))
return error("DisconnectTip() : DisconnectBlock %s failed", pindexDelete->GetBlockHash().ToString()); return error("DisconnectTip() : DisconnectBlock %s failed", pindexDelete->GetBlockHash().ToString());
assert(view.Flush()); assert(view.Flush());
@ -1843,7 +1836,7 @@ bool static ConnectTip(CValidationState &state, CBlockIndex *pindexNew, CBlock *
int64_t nTime3; int64_t nTime3;
LogPrint("bench", " - Load block from disk: %.2fms [%.2fs]\n", (nTime2 - nTime1) * 0.001, nTimeReadFromDisk * 0.000001); LogPrint("bench", " - Load block from disk: %.2fms [%.2fs]\n", (nTime2 - nTime1) * 0.001, nTimeReadFromDisk * 0.000001);
{ {
CCoinsViewCache view(*pcoinsTip, true); CCoinsViewCache view(pcoinsTip);
CInv inv(MSG_BLOCK, pindexNew->GetBlockHash()); CInv inv(MSG_BLOCK, pindexNew->GetBlockHash());
if (!ConnectBlock(*pblock, state, pindexNew, view)) { if (!ConnectBlock(*pblock, state, pindexNew, view)) {
if (state.IsInvalid()) if (state.IsInvalid())
@ -2893,7 +2886,7 @@ bool CVerifyDB::VerifyDB(CCoinsView *coinsview, int nCheckLevel, int nCheckDepth
nCheckDepth = chainActive.Height(); nCheckDepth = chainActive.Height();
nCheckLevel = std::max(0, std::min(4, nCheckLevel)); nCheckLevel = std::max(0, std::min(4, nCheckLevel));
LogPrintf("Verifying last %i blocks at level %i\n", nCheckDepth, nCheckLevel); LogPrintf("Verifying last %i blocks at level %i\n", nCheckDepth, nCheckLevel);
CCoinsViewCache coins(*coinsview, true); CCoinsViewCache coins(coinsview);
CBlockIndex* pindexState = chainActive.Tip(); CBlockIndex* pindexState = chainActive.Tip();
CBlockIndex* pindexFailure = NULL; CBlockIndex* pindexFailure = NULL;
int nGoodTransactions = 0; int nGoodTransactions = 0;

View file

@ -116,7 +116,7 @@ CBlockTemplate* CreateNewBlock(const CScript& scriptPubKeyIn)
{ {
LOCK2(cs_main, mempool.cs); LOCK2(cs_main, mempool.cs);
CBlockIndex* pindexPrev = chainActive.Tip(); CBlockIndex* pindexPrev = chainActive.Tip();
CCoinsViewCache view(*pcoinsTip, true); CCoinsViewCache view(pcoinsTip);
// Priority order to process transactions // Priority order to process transactions
list<COrphan> vOrphan; // list memory doesn't move list<COrphan> vOrphan; // list memory doesn't move
@ -316,7 +316,7 @@ CBlockTemplate* CreateNewBlock(const CScript& scriptPubKeyIn)
CBlockIndex indexDummy(*pblock); CBlockIndex indexDummy(*pblock);
indexDummy.pprev = pindexPrev; indexDummy.pprev = pindexPrev;
indexDummy.nHeight = pindexPrev->nHeight + 1; indexDummy.nHeight = pindexPrev->nHeight + 1;
CCoinsViewCache viewNew(*pcoinsTip, true); CCoinsViewCache viewNew(pcoinsTip);
CValidationState state; CValidationState state;
if (!ConnectBlock(*pblock, state, &indexDummy, viewNew, true)) if (!ConnectBlock(*pblock, state, &indexDummy, viewNew, true))
throw std::runtime_error("CreateNewBlock() : ConnectBlock failed"); throw std::runtime_error("CreateNewBlock() : ConnectBlock failed");

View file

@ -381,7 +381,7 @@ Value gettxout(const Array& params, bool fHelp)
CCoins coins; CCoins coins;
if (fMempool) { if (fMempool) {
LOCK(mempool.cs); LOCK(mempool.cs);
CCoinsViewMemPool view(*pcoinsTip, mempool); CCoinsViewMemPool view(pcoinsTip, mempool);
if (!view.GetCoins(hash, coins)) if (!view.GetCoins(hash, coins))
return Value::null; return Value::null;
mempool.pruneSpent(hash, coins); // TODO: this should be done by the CCoinsViewMemPool mempool.pruneSpent(hash, coins); // TODO: this should be done by the CCoinsViewMemPool

View file

@ -560,11 +560,11 @@ Value signrawtransaction(const Array& params, bool fHelp)
// Fetch previous transactions (inputs): // Fetch previous transactions (inputs):
CCoinsView viewDummy; CCoinsView viewDummy;
CCoinsViewCache view(viewDummy); CCoinsViewCache view(&viewDummy);
{ {
LOCK(mempool.cs); LOCK(mempool.cs);
CCoinsViewCache &viewChain = *pcoinsTip; CCoinsViewCache &viewChain = *pcoinsTip;
CCoinsViewMemPool viewMempool(viewChain, mempool); CCoinsViewMemPool viewMempool(&viewChain, mempool);
view.SetBackend(viewMempool); // temporarily switch cache backend to db+mempool view view.SetBackend(viewMempool); // temporarily switch cache backend to db+mempool view
BOOST_FOREACH(const CTxIn& txin, mergedTx.vin) { BOOST_FOREACH(const CTxIn& txin, mergedTx.vin) {
@ -615,21 +615,19 @@ Value signrawtransaction(const Array& params, bool fHelp)
vector<unsigned char> pkData(ParseHexO(prevOut, "scriptPubKey")); vector<unsigned char> pkData(ParseHexO(prevOut, "scriptPubKey"));
CScript scriptPubKey(pkData.begin(), pkData.end()); CScript scriptPubKey(pkData.begin(), pkData.end());
CCoins coins; {
if (view.GetCoins(txid, coins)) { CCoinsModifier coins = view.ModifyCoins(txid);
if (coins.IsAvailable(nOut) && coins.vout[nOut].scriptPubKey != scriptPubKey) { if (coins->IsAvailable(nOut) && coins->vout[nOut].scriptPubKey != scriptPubKey) {
string err("Previous output scriptPubKey mismatch:\n"); string err("Previous output scriptPubKey mismatch:\n");
err = err + coins.vout[nOut].scriptPubKey.ToString() + "\nvs:\n"+ err = err + coins->vout[nOut].scriptPubKey.ToString() + "\nvs:\n"+
scriptPubKey.ToString(); scriptPubKey.ToString();
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, err); throw JSONRPCError(RPC_DESERIALIZATION_ERROR, err);
} }
// what todo if txid is known, but the actual output isn't? if ((unsigned int)nOut >= coins->vout.size())
coins->vout.resize(nOut+1);
coins->vout[nOut].scriptPubKey = scriptPubKey;
coins->vout[nOut].nValue = 0; // we don't know the actual output value
} }
if ((unsigned int)nOut >= coins.vout.size())
coins.vout.resize(nOut+1);
coins.vout[nOut].scriptPubKey = scriptPubKey;
coins.vout[nOut].nValue = 0; // we don't know the actual output value
view.SetCoins(txid, coins);
// if redeemScript given and not using the local wallet (private keys // if redeemScript given and not using the local wallet (private keys
// given), add redeemScript to the tempKeystore so it can be signed: // given), add redeemScript to the tempKeystore so it can be signed:

178
src/test/coins_tests.cpp Normal file
View file

@ -0,0 +1,178 @@
// Copyright (c) 2014 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include "coins.h"
#include "random.h"
#include "uint256.h"
#include <vector>
#include <map>
#include <boost/test/unit_test.hpp>
namespace
{
class CCoinsViewTest : public CCoinsView
{
uint256 hashBestBlock_;
std::map<uint256, CCoins> map_;
public:
bool GetCoins(const uint256& txid, CCoins& coins) const
{
std::map<uint256, CCoins>::const_iterator it = map_.find(txid);
if (it == map_.end()) {
return false;
}
coins = it->second;
if (coins.IsPruned() && insecure_rand() % 2 == 0) {
// Randomly return false in case of an empty entry.
return false;
}
return true;
}
bool HaveCoins(const uint256& txid) const
{
CCoins coins;
return GetCoins(txid, coins);
}
uint256 GetBestBlock() const { return hashBestBlock_; }
bool BatchWrite(CCoinsMap& mapCoins, const uint256& hashBlock)
{
for (CCoinsMap::iterator it = mapCoins.begin(); it != mapCoins.end(); ) {
map_[it->first] = it->second.coins;
if (it->second.coins.IsPruned() && insecure_rand() % 3 == 0) {
// Randomly delete empty entries on write.
map_.erase(it->first);
}
mapCoins.erase(it++);
}
mapCoins.clear();
hashBestBlock_ = hashBlock;
return true;
}
bool GetStats(CCoinsStats& stats) const { return false; }
};
}
BOOST_AUTO_TEST_SUITE(coins_tests)
static const unsigned int NUM_SIMULATION_ITERATIONS = 40000;
// This is a large randomized insert/remove simulation test on a variable-size
// stack of caches on top of CCoinsViewTest.
//
// It will randomly create/update/delete CCoins entries to a tip of caches, with
// txids picked from a limited list of random 256-bit hashes. Occasionally, a
// new tip is added to the stack of caches, or the tip is flushed and removed.
//
// During the process, booleans are kept to make sure that the randomized
// operation hits all branches.
BOOST_AUTO_TEST_CASE(coins_cache_simulation_test)
{
// Various coverage trackers.
bool removed_all_caches = false;
bool reached_4_caches = false;
bool added_an_entry = false;
bool removed_an_entry = false;
bool updated_an_entry = false;
bool found_an_entry = false;
bool missed_an_entry = false;
// A simple map to track what we expect the cache stack to represent.
std::map<uint256, CCoins> result;
// The cache stack.
CCoinsViewTest base; // A CCoinsViewTest at the bottom.
std::vector<CCoinsViewCache*> stack; // A stack of CCoinsViewCaches on top.
stack.push_back(new CCoinsViewCache(&base)); // Start with one cache.
// Use a limited set of random transaction ids, so we do test overwriting entries.
std::vector<uint256> txids;
txids.resize(NUM_SIMULATION_ITERATIONS / 8);
for (unsigned int i = 0; i < txids.size(); i++) {
txids[i] = GetRandHash();
}
for (unsigned int i = 0; i < NUM_SIMULATION_ITERATIONS; i++) {
// Do a random modification.
{
uint256 txid = txids[insecure_rand() % txids.size()]; // txid we're going to modify in this iteration.
CCoins& coins = result[txid];
CCoinsModifier entry = stack.back()->ModifyCoins(txid);
BOOST_CHECK(coins == *entry);
if (insecure_rand() % 5 == 0 || coins.IsPruned()) {
if (coins.IsPruned()) {
added_an_entry = true;
} else {
updated_an_entry = true;
}
coins.nVersion = insecure_rand();
coins.vout.resize(1);
coins.vout[0].nValue = insecure_rand();
*entry = coins;
} else {
coins.Clear();
entry->Clear();
removed_an_entry = true;
}
}
// Once every 1000 iterations and at the end, verify the full cache.
if (insecure_rand() % 1000 == 1 || i == NUM_SIMULATION_ITERATIONS - 1) {
for (std::map<uint256, CCoins>::iterator it = result.begin(); it != result.end(); it++) {
const CCoins* coins = stack.back()->AccessCoins(it->first);
if (coins) {
BOOST_CHECK(*coins == it->second);
found_an_entry = true;
} else {
BOOST_CHECK(it->second.IsPruned());
missed_an_entry = true;
}
}
}
if (insecure_rand() % 100 == 0) {
// Every 100 iterations, change the cache stack.
if (stack.size() > 0 && insecure_rand() % 2 == 0) {
stack.back()->Flush();
delete stack.back();
stack.pop_back();
}
if (stack.size() == 0 || (stack.size() < 4 && insecure_rand() % 2)) {
CCoinsView* tip = &base;
if (stack.size() > 0) {
tip = stack.back();
} else {
removed_all_caches = true;
}
stack.push_back(new CCoinsViewCache(tip));
if (stack.size() == 4) {
reached_4_caches = true;
}
}
}
}
// Clean up the stack.
while (stack.size() > 0) {
delete stack.back();
stack.pop_back();
}
// Verify coverage.
BOOST_CHECK(removed_all_caches);
BOOST_CHECK(reached_4_caches);
BOOST_CHECK(added_an_entry);
BOOST_CHECK(removed_an_entry);
BOOST_CHECK(updated_an_entry);
BOOST_CHECK(found_an_entry);
BOOST_CHECK(missed_an_entry);
}
BOOST_AUTO_TEST_SUITE_END()

View file

@ -255,7 +255,7 @@ BOOST_AUTO_TEST_CASE(AreInputsStandard)
{ {
LOCK(cs_main); LOCK(cs_main);
CCoinsView coinsDummy; CCoinsView coinsDummy;
CCoinsViewCache coins(coinsDummy); CCoinsViewCache coins(&coinsDummy);
CBasicKeyStore keystore; CBasicKeyStore keystore;
CKey key[6]; CKey key[6];
vector<CPubKey> keys; vector<CPubKey> keys;
@ -312,8 +312,7 @@ BOOST_AUTO_TEST_CASE(AreInputsStandard)
txFrom.vout[6].scriptPubKey = GetScriptForDestination(twentySigops.GetID()); txFrom.vout[6].scriptPubKey = GetScriptForDestination(twentySigops.GetID());
txFrom.vout[6].nValue = 6000; txFrom.vout[6].nValue = 6000;
coins.ModifyCoins(txFrom.GetHash())->FromTx(txFrom, 0);
coins.SetCoins(txFrom.GetHash(), CCoins(txFrom, 0));
CMutableTransaction txTo; CMutableTransaction txTo;
txTo.vout.resize(1); txTo.vout.resize(1);

View file

@ -41,7 +41,7 @@ struct TestingSetup {
mapArgs["-datadir"] = pathTemp.string(); mapArgs["-datadir"] = pathTemp.string();
pblocktree = new CBlockTreeDB(1 << 20, true); pblocktree = new CBlockTreeDB(1 << 20, true);
pcoinsdbview = new CCoinsViewDB(1 << 23, true); pcoinsdbview = new CCoinsViewDB(1 << 23, true);
pcoinsTip = new CCoinsViewCache(*pcoinsdbview); pcoinsTip = new CCoinsViewCache(pcoinsdbview);
InitBlockIndex(); InitBlockIndex();
#ifdef ENABLE_WALLET #ifdef ENABLE_WALLET
bool fFirstRun; bool fFirstRun;

View file

@ -242,7 +242,7 @@ BOOST_AUTO_TEST_CASE(basic_transaction_tests)
// paid to a TX_PUBKEYHASH. // paid to a TX_PUBKEYHASH.
// //
static std::vector<CMutableTransaction> static std::vector<CMutableTransaction>
SetupDummyInputs(CBasicKeyStore& keystoreRet, CCoinsView & coinsRet) SetupDummyInputs(CBasicKeyStore& keystoreRet, CCoinsViewCache& coinsRet)
{ {
std::vector<CMutableTransaction> dummyTransactions; std::vector<CMutableTransaction> dummyTransactions;
dummyTransactions.resize(2); dummyTransactions.resize(2);
@ -261,14 +261,14 @@ SetupDummyInputs(CBasicKeyStore& keystoreRet, CCoinsView & coinsRet)
dummyTransactions[0].vout[0].scriptPubKey << key[0].GetPubKey() << OP_CHECKSIG; dummyTransactions[0].vout[0].scriptPubKey << key[0].GetPubKey() << OP_CHECKSIG;
dummyTransactions[0].vout[1].nValue = 50*CENT; dummyTransactions[0].vout[1].nValue = 50*CENT;
dummyTransactions[0].vout[1].scriptPubKey << key[1].GetPubKey() << OP_CHECKSIG; dummyTransactions[0].vout[1].scriptPubKey << key[1].GetPubKey() << OP_CHECKSIG;
coinsRet.SetCoins(dummyTransactions[0].GetHash(), CCoins(dummyTransactions[0], 0)); coinsRet.ModifyCoins(dummyTransactions[0].GetHash())->FromTx(dummyTransactions[0], 0);
dummyTransactions[1].vout.resize(2); dummyTransactions[1].vout.resize(2);
dummyTransactions[1].vout[0].nValue = 21*CENT; dummyTransactions[1].vout[0].nValue = 21*CENT;
dummyTransactions[1].vout[0].scriptPubKey = GetScriptForDestination(key[2].GetPubKey().GetID()); dummyTransactions[1].vout[0].scriptPubKey = GetScriptForDestination(key[2].GetPubKey().GetID());
dummyTransactions[1].vout[1].nValue = 22*CENT; dummyTransactions[1].vout[1].nValue = 22*CENT;
dummyTransactions[1].vout[1].scriptPubKey = GetScriptForDestination(key[3].GetPubKey().GetID()); dummyTransactions[1].vout[1].scriptPubKey = GetScriptForDestination(key[3].GetPubKey().GetID());
coinsRet.SetCoins(dummyTransactions[1].GetHash(), CCoins(dummyTransactions[1], 0)); coinsRet.ModifyCoins(dummyTransactions[1].GetHash())->FromTx(dummyTransactions[1], 0);
return dummyTransactions; return dummyTransactions;
} }
@ -277,7 +277,7 @@ BOOST_AUTO_TEST_CASE(test_Get)
{ {
CBasicKeyStore keystore; CBasicKeyStore keystore;
CCoinsView coinsDummy; CCoinsView coinsDummy;
CCoinsViewCache coins(coinsDummy); CCoinsViewCache coins(&coinsDummy);
std::vector<CMutableTransaction> dummyTransactions = SetupDummyInputs(keystore, coins); std::vector<CMutableTransaction> dummyTransactions = SetupDummyInputs(keystore, coins);
CMutableTransaction t1; CMutableTransaction t1;
@ -312,7 +312,7 @@ BOOST_AUTO_TEST_CASE(test_IsStandard)
LOCK(cs_main); LOCK(cs_main);
CBasicKeyStore keystore; CBasicKeyStore keystore;
CCoinsView coinsDummy; CCoinsView coinsDummy;
CCoinsViewCache coins(coinsDummy); CCoinsViewCache coins(&coinsDummy);
std::vector<CMutableTransaction> dummyTransactions = SetupDummyInputs(keystore, coins); std::vector<CMutableTransaction> dummyTransactions = SetupDummyInputs(keystore, coins);
CMutableTransaction t; CMutableTransaction t;

View file

@ -33,12 +33,6 @@ bool CCoinsViewDB::GetCoins(const uint256 &txid, CCoins &coins) const {
return db.Read(make_pair('c', txid), coins); return db.Read(make_pair('c', txid), coins);
} }
bool CCoinsViewDB::SetCoins(const uint256 &txid, const CCoins &coins) {
CLevelDBBatch batch;
BatchWriteCoins(batch, txid, coins);
return db.WriteBatch(batch);
}
bool CCoinsViewDB::HaveCoins(const uint256 &txid) const { bool CCoinsViewDB::HaveCoins(const uint256 &txid) const {
return db.Exists(make_pair('c', txid)); return db.Exists(make_pair('c', txid));
} }
@ -50,24 +44,23 @@ uint256 CCoinsViewDB::GetBestBlock() const {
return hashBestChain; return hashBestChain;
} }
bool CCoinsViewDB::SetBestBlock(const uint256 &hashBlock) {
CLevelDBBatch batch;
BatchWriteHashBestChain(batch, hashBlock);
return db.WriteBatch(batch);
}
bool CCoinsViewDB::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock) { bool CCoinsViewDB::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock) {
LogPrint("coindb", "Committing %u changed transactions to coin database...\n", (unsigned int)mapCoins.size());
CLevelDBBatch batch; CLevelDBBatch batch;
size_t count = 0;
size_t changed = 0;
for (CCoinsMap::iterator it = mapCoins.begin(); it != mapCoins.end();) { for (CCoinsMap::iterator it = mapCoins.begin(); it != mapCoins.end();) {
BatchWriteCoins(batch, it->first, it->second); if (it->second.flags & CCoinsCacheEntry::DIRTY) {
BatchWriteCoins(batch, it->first, it->second.coins);
changed++;
}
count++;
CCoinsMap::iterator itOld = it++; CCoinsMap::iterator itOld = it++;
mapCoins.erase(itOld); mapCoins.erase(itOld);
} }
if (hashBlock != uint256(0)) if (hashBlock != uint256(0))
BatchWriteHashBestChain(batch, hashBlock); BatchWriteHashBestChain(batch, hashBlock);
LogPrint("coindb", "Committing %u changed transactions (out of %u) to coin database...\n", (unsigned int)changed, (unsigned int)count);
return db.WriteBatch(batch); return db.WriteBatch(batch);
} }

View file

@ -33,10 +33,8 @@ public:
CCoinsViewDB(size_t nCacheSize, bool fMemory = false, bool fWipe = false); CCoinsViewDB(size_t nCacheSize, bool fMemory = false, bool fWipe = false);
bool GetCoins(const uint256 &txid, CCoins &coins) const; bool GetCoins(const uint256 &txid, CCoins &coins) const;
bool SetCoins(const uint256 &txid, const CCoins &coins);
bool HaveCoins(const uint256 &txid) const; bool HaveCoins(const uint256 &txid) const;
uint256 GetBestBlock() const; uint256 GetBestBlock() const;
bool SetBestBlock(const uint256 &hashBlock);
bool BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock); bool BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock);
bool GetStats(CCoinsStats &stats) const; bool GetStats(CCoinsStats &stats) const;
}; };

View file

@ -631,7 +631,7 @@ void CTxMemPool::ClearPrioritisation(const uint256 hash)
} }
CCoinsViewMemPool::CCoinsViewMemPool(CCoinsView &baseIn, CTxMemPool &mempoolIn) : CCoinsViewBacked(baseIn), mempool(mempoolIn) { } CCoinsViewMemPool::CCoinsViewMemPool(CCoinsView *baseIn, CTxMemPool &mempoolIn) : CCoinsViewBacked(baseIn), mempool(mempoolIn) { }
bool CCoinsViewMemPool::GetCoins(const uint256 &txid, CCoins &coins) const { bool CCoinsViewMemPool::GetCoins(const uint256 &txid, CCoins &coins) const {
// If an entry in the mempool exists, always return that one, as it's guaranteed to never // If an entry in the mempool exists, always return that one, as it's guaranteed to never

View file

@ -144,7 +144,7 @@ protected:
CTxMemPool &mempool; CTxMemPool &mempool;
public: public:
CCoinsViewMemPool(CCoinsView &baseIn, CTxMemPool &mempoolIn); CCoinsViewMemPool(CCoinsView *baseIn, CTxMemPool &mempoolIn);
bool GetCoins(const uint256 &txid, CCoins &coins) const; bool GetCoins(const uint256 &txid, CCoins &coins) const;
bool HaveCoins(const uint256 &txid) const; bool HaveCoins(const uint256 &txid) const;
}; };