diff --git a/src/Makefile.test.include b/src/Makefile.test.include index ab449f3e7..99ac09e1a 100644 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -44,6 +44,7 @@ BITCOIN_TESTS =\ test/checkblock_tests.cpp \ test/Checkpoints_tests.cpp \ test/compress_tests.cpp \ + test/coins_tests.cpp \ test/crypto_tests.cpp \ test/DoS_tests.cpp \ test/getarg_tests.cpp \ diff --git a/src/bitcoin-tx.cpp b/src/bitcoin-tx.cpp index a198eb586..da37e60c7 100644 --- a/src/bitcoin-tx.cpp +++ b/src/bitcoin-tx.cpp @@ -340,7 +340,7 @@ static void MutateTxSign(CMutableTransaction& tx, const string& flagStr) CMutableTransaction mergedTx(txVariants[0]); bool fComplete = true; CCoinsView viewDummy; - CCoinsViewCache view(viewDummy); + CCoinsViewCache view(&viewDummy); if (!registers.count("privatekeys")) throw runtime_error("privatekeys register variable must be set."); @@ -384,21 +384,19 @@ static void MutateTxSign(CMutableTransaction& tx, const string& flagStr) vector pkData(ParseHexUV(prevOut, "scriptPubKey")); CScript scriptPubKey(pkData.begin(), pkData.end()); - CCoins coins; - if (view.GetCoins(txid, coins)) { - if (coins.IsAvailable(nOut) && coins.vout[nOut].scriptPubKey != scriptPubKey) { + { + CCoinsModifier coins = view.ModifyCoins(txid); + if (coins->IsAvailable(nOut) && coins->vout[nOut].scriptPubKey != scriptPubKey) { 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(); 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, // add redeemScript to the tempKeystore so it can be signed: diff --git a/src/coins.cpp b/src/coins.cpp index 9b8d63d4e..e4f3e67ae 100644 --- a/src/coins.cpp +++ b/src/coins.cpp @@ -53,61 +53,72 @@ bool CCoins::Spend(int nPos) { 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; } 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::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::SetCoins(const uint256 &txid, const CCoins &coins) { return base->SetCoins(txid, coins); } bool CCoinsViewBacked::HaveCoins(const uint256 &txid) const { return base->HaveCoins(txid); } uint256 CCoinsViewBacked::GetBestBlock() const { return base->GetBestBlock(); } -bool CCoinsViewBacked::SetBestBlock(const uint256 &hashBlock) { return base->SetBestBlock(hashBlock); } void CCoinsViewBacked::SetBackend(CCoinsView &viewIn) { base = &viewIn; } bool CCoinsViewBacked::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock) { return base->BatchWrite(mapCoins, hashBlock); } bool CCoinsViewBacked::GetStats(CCoinsStats &stats) const { return base->GetStats(stats); } CCoinsKeyHasher::CCoinsKeyHasher() : salt(GetRandHash()) {} -CCoinsViewCache::CCoinsViewCache(CCoinsView &baseIn, bool fDummy) : CCoinsViewBacked(baseIn), hashBlock(0) { } +CCoinsViewCache::CCoinsViewCache(CCoinsView *baseIn) : CCoinsViewBacked(baseIn), hasModifier(false), hashBlock(0) { } -bool CCoinsViewCache::GetCoins(const uint256 &txid, CCoins &coins) const { - if (cacheCoins.count(txid)) { - coins = cacheCoins[txid]; - return true; - } - if (base->GetCoins(txid, coins)) { - cacheCoins[txid] = coins; - return true; - } - return false; +CCoinsViewCache::~CCoinsViewCache() +{ + assert(!hasModifier); } -CCoinsMap::iterator CCoinsViewCache::FetchCoins(const uint256 &txid) { +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)) + if (!base->GetCoins(txid, tmp)) return cacheCoins.end(); - CCoinsMap::iterator ret = cacheCoins.insert(it, std::make_pair(txid, CCoins())); - tmp.swap(ret->second); + 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; } -CCoinsMap::const_iterator CCoinsViewCache::FetchCoins(const uint256 &txid) const { - /* Avoid redundant implementation with the const-cast. */ - return const_cast(this)->FetchCoins(txid); +bool CCoinsViewCache::GetCoins(const uint256 &txid, CCoins &coins) const { + CCoinsMap::const_iterator it = FetchCoins(txid); + if (it != cacheCoins.end()) { + coins = it->second.coins; + return true; + } + return false; } -CCoins &CCoinsViewCache::GetCoins(const uint256 &txid) { - CCoinsMap::iterator it = FetchCoins(txid); - assert(it != cacheCoins.end()); - return it->second; +CCoinsModifier CCoinsViewCache::ModifyCoins(const uint256 &txid) { + assert(!hasModifier); + hasModifier = true; + std::pair ret = cacheCoins.insert(std::make_pair(txid, CCoinsCacheEntry())); + 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; + } + } + // Assume that whenever ModifyCoins is called, the entry will be modified. + ret.first->second.flags |= CCoinsCacheEntry::DIRTY; + return CCoinsModifier(*this, ret.first); } const CCoins* CCoinsViewCache::AccessCoins(const uint256 &txid) const { @@ -115,22 +126,17 @@ const CCoins* CCoinsViewCache::AccessCoins(const uint256 &txid) const { if (it == cacheCoins.end()) { return NULL; } 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 { 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 an 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.vout.empty()); + return (it != cacheCoins.end() && !it->second.coins.vout.empty()); } uint256 CCoinsViewCache::GetBestBlock() const { @@ -139,14 +145,39 @@ uint256 CCoinsViewCache::GetBestBlock() const { return hashBlock; } -bool CCoinsViewCache::SetBestBlock(const uint256 &hashBlockIn) { +void CCoinsViewCache::SetBestBlock(const uint256 &hashBlockIn) { hashBlock = hashBlockIn; - return true; } bool CCoinsViewCache::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlockIn) { + assert(!hasModifier); 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++; mapCoins.erase(itOld); } @@ -213,3 +244,15 @@ double CCoinsViewCache::GetPriority(const CTransaction &tx, int nHeight) const } 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); + } +} diff --git a/src/coins.h b/src/coins.h index 258347532..b8f1e5bcc 100644 --- a/src/coins.h +++ b/src/coins.h @@ -83,11 +83,26 @@ public: // as new tx version will probably only be introduced at certain heights int nVersion; - // construct a CCoins from a CTransaction, at a given height - CCoins(const CTransaction &tx, int nHeightIn) : fCoinBase(tx.IsCoinBase()), vout(tx.vout), nHeight(nHeightIn), nVersion(tx.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); + } + + void Clear() { + fCoinBase = false; + std::vector().swap(vout); + nHeight = 0; + nVersion = 0; + } + // empty constructor CCoins() : fCoinBase(false), vout(0), nHeight(0), nVersion(0) { } @@ -256,7 +271,20 @@ public: } }; -typedef boost::unordered_map 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 CCoinsMap; struct CCoinsStats { @@ -279,9 +307,6 @@ public: // Retrieve the CCoins (unspent transaction outputs) for a given txid 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. // This may (but cannot always) return true for fully spent transactions virtual bool HaveCoins(const uint256 &txid) const; @@ -289,10 +314,7 @@ public: // Retrieve the block hash whose state this CCoinsView currently represents virtual uint256 GetBestBlock() const; - // Modify the currently active block hash - virtual bool SetBestBlock(const uint256 &hashBlock); - - // Do a bulk modification (multiple SetCoins + one SetBestBlock). + // Do a bulk modification (multiple CCoins changes + BestBlock change). // The passed mapCoins can be modified. virtual bool BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock); @@ -311,22 +333,41 @@ protected: CCoinsView *base; public: - CCoinsViewBacked(CCoinsView &viewIn); + CCoinsViewBacked(CCoinsView *viewIn); bool GetCoins(const uint256 &txid, CCoins &coins) const; - bool SetCoins(const uint256 &txid, const CCoins &coins); bool HaveCoins(const uint256 &txid) const; uint256 GetBestBlock() const; - bool SetBestBlock(const uint256 &hashBlock); void SetBackend(CCoinsView &viewIn); bool BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock); 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 */ 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". */ @@ -334,14 +375,14 @@ protected: mutable CCoinsMap cacheCoins; public: - CCoinsViewCache(CCoinsView &baseIn, bool fDummy = false); + CCoinsViewCache(CCoinsView *baseIn); + ~CCoinsViewCache(); // Standard CCoinsView methods bool GetCoins(const uint256 &txid, CCoins &coins) const; - bool SetCoins(const uint256 &txid, const CCoins &coins); bool HaveCoins(const uint256 &txid) const; uint256 GetBestBlock() const; - bool SetBestBlock(const uint256 &hashBlock); + void SetBestBlock(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 @@ -349,8 +390,10 @@ public: // allowed while accessing the returned pointer. const CCoins* AccessCoins(const uint256 &txid) const; - // Return a modifiable reference to a CCoins. Check HaveCoins first. - CCoins &GetCoins(const uint256 &txid); + // 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. + CCoinsModifier ModifyCoins(const uint256 &txid); // Push the modifications applied to this cache to its base. // 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; + friend class CCoinsModifier; + private: CCoinsMap::iterator FetchCoins(const uint256 &txid); CCoinsMap::const_iterator FetchCoins(const uint256 &txid) const; diff --git a/src/init.cpp b/src/init.cpp index 97d0f5852..8dcd35fb8 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -955,7 +955,7 @@ bool AppInit2(boost::thread_group& threadGroup) pblocktree = new CBlockTreeDB(nBlockTreeDBCache, false, fReindex); pcoinsdbview = new CCoinsViewDB(nCoinDBCache, false, fReindex); - pcoinsTip = new CCoinsViewCache(*pcoinsdbview); + pcoinsTip = new CCoinsViewCache(pcoinsdbview); if (fReindex) pblocktree->WriteReindexing(true); diff --git a/src/main.cpp b/src/main.cpp index 5d46c30a9..de3876944 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -844,12 +844,12 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa { CCoinsView dummy; - CCoinsViewCache view(dummy); + CCoinsViewCache view(&dummy); CAmount nValueIn = 0; { LOCK(pool.cs); - CCoinsViewMemPool viewMemPool(*pcoinsTip, pool); + CCoinsViewMemPool viewMemPool(pcoinsTip, pool); view.SetBackend(viewMemPool); // 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) { - bool ret; // mark inputs spent if (!tx.IsCoinBase()) { txundo.vprevout.reserve(tx.vin.size()); - for (unsigned int i = 0; i < tx.vin.size(); i++) { - const CTxIn &txin = tx.vin[i]; - CCoins &coins = inputs.GetCoins(txin.prevout.hash); + BOOST_FOREACH(const CTxIn &txin, tx.vin) { 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); } } // add outputs - ret = inputs.SetCoins(tx.GetHash(), CCoins(tx, nHeight)); - assert(ret); + inputs.ModifyCoins(tx.GetHash())->FromTx(tx, nHeight); } 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 // have outputs available even in the block itself, so we handle that case // specially with outsEmpty. + { CCoins outsEmpty; - CCoins &outs = view.HaveCoins(hash) ? view.GetCoins(hash) : outsEmpty; - outs.ClearUnspendable(); + CCoinsModifier outs = view.ModifyCoins(hash); + outs->ClearUnspendable(); - CCoins outsBlock = CCoins(tx, pindex->nHeight); + 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) + outs->nVersion = outsBlock.nVersion; + if (*outs != outsBlock) fClean = fClean && error("DisconnectBlock() : added transaction mismatch? database corrupted"); // remove outputs - outs = CCoins(); + outs->Clear(); + } // restore inputs 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;) { const COutPoint &out = tx.vin[j].prevout; const CTxInUndo &undo = txundo.vprevout[j]; - CCoins coins; - view.GetCoins(out.hash, coins); // this can fail if the prevout was already entirely spent + 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()) + if (!coins->IsPruned()) fClean = fClean && error("DisconnectBlock() : undo data overwriting existing transaction"); - coins = CCoins(); - coins.fCoinBase = undo.fCoinBase; - coins.nHeight = undo.nHeight; - coins.nVersion = undo.nVersion; + coins->Clear(); + coins->fCoinBase = undo.fCoinBase; + coins->nHeight = undo.nHeight; + coins->nVersion = undo.nVersion; } else { - if (coins.IsPruned()) + if (coins->IsPruned()) 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"); - if (coins.vout.size() < out.n+1) - coins.vout.resize(out.n+1); - coins.vout[out.n] = undo.txout; - if (!view.SetCoins(out.hash, coins)) - return error("DisconnectBlock() : cannot restore coin inputs"); + if (coins->vout.size() < out.n+1) + coins->vout.resize(out.n+1); + coins->vout[out.n] = undo.txout; } } } @@ -1706,9 +1701,7 @@ bool ConnectBlock(CBlock& block, CValidationState& state, CBlockIndex* pindex, C return state.Abort("Failed to write transaction index"); // add this block to the view's block chain - bool ret; - ret = view.SetBestBlock(pindex->GetBlockHash()); - assert(ret); + view.SetBestBlock(pindex->GetBlockHash()); int64_t nTime3 = GetTimeMicros(); nTimeIndex += nTime3 - nTime2; 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(); 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, 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(); @@ -1790,7 +1783,7 @@ bool static DisconnectTip(CValidationState &state) { // Apply the block atomically to the chain state. int64_t nStart = GetTimeMicros(); { - CCoinsViewCache view(*pcoinsTip, true); + CCoinsViewCache view(pcoinsTip); if (!DisconnectBlock(block, state, pindexDelete, view)) return error("DisconnectTip() : DisconnectBlock %s failed", pindexDelete->GetBlockHash().ToString()); assert(view.Flush()); @@ -1843,7 +1836,7 @@ bool static ConnectTip(CValidationState &state, CBlockIndex *pindexNew, CBlock * int64_t nTime3; 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()); if (!ConnectBlock(*pblock, state, pindexNew, view)) { if (state.IsInvalid()) @@ -2893,7 +2886,7 @@ bool CVerifyDB::VerifyDB(CCoinsView *coinsview, int nCheckLevel, int nCheckDepth nCheckDepth = chainActive.Height(); nCheckLevel = std::max(0, std::min(4, nCheckLevel)); LogPrintf("Verifying last %i blocks at level %i\n", nCheckDepth, nCheckLevel); - CCoinsViewCache coins(*coinsview, true); + CCoinsViewCache coins(coinsview); CBlockIndex* pindexState = chainActive.Tip(); CBlockIndex* pindexFailure = NULL; int nGoodTransactions = 0; diff --git a/src/miner.cpp b/src/miner.cpp index 280349e8c..fd6c52ca6 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -116,7 +116,7 @@ CBlockTemplate* CreateNewBlock(const CScript& scriptPubKeyIn) { LOCK2(cs_main, mempool.cs); CBlockIndex* pindexPrev = chainActive.Tip(); - CCoinsViewCache view(*pcoinsTip, true); + CCoinsViewCache view(pcoinsTip); // Priority order to process transactions list vOrphan; // list memory doesn't move @@ -316,7 +316,7 @@ CBlockTemplate* CreateNewBlock(const CScript& scriptPubKeyIn) CBlockIndex indexDummy(*pblock); indexDummy.pprev = pindexPrev; indexDummy.nHeight = pindexPrev->nHeight + 1; - CCoinsViewCache viewNew(*pcoinsTip, true); + CCoinsViewCache viewNew(pcoinsTip); CValidationState state; if (!ConnectBlock(*pblock, state, &indexDummy, viewNew, true)) throw std::runtime_error("CreateNewBlock() : ConnectBlock failed"); diff --git a/src/rpcblockchain.cpp b/src/rpcblockchain.cpp index 4b3beae20..24175215b 100644 --- a/src/rpcblockchain.cpp +++ b/src/rpcblockchain.cpp @@ -381,7 +381,7 @@ Value gettxout(const Array& params, bool fHelp) CCoins coins; if (fMempool) { LOCK(mempool.cs); - CCoinsViewMemPool view(*pcoinsTip, mempool); + CCoinsViewMemPool view(pcoinsTip, mempool); if (!view.GetCoins(hash, coins)) return Value::null; mempool.pruneSpent(hash, coins); // TODO: this should be done by the CCoinsViewMemPool diff --git a/src/rpcrawtransaction.cpp b/src/rpcrawtransaction.cpp index 23e647b17..78372da68 100644 --- a/src/rpcrawtransaction.cpp +++ b/src/rpcrawtransaction.cpp @@ -560,11 +560,11 @@ Value signrawtransaction(const Array& params, bool fHelp) // Fetch previous transactions (inputs): CCoinsView viewDummy; - CCoinsViewCache view(viewDummy); + CCoinsViewCache view(&viewDummy); { LOCK(mempool.cs); CCoinsViewCache &viewChain = *pcoinsTip; - CCoinsViewMemPool viewMempool(viewChain, mempool); + CCoinsViewMemPool viewMempool(&viewChain, mempool); view.SetBackend(viewMempool); // temporarily switch cache backend to db+mempool view BOOST_FOREACH(const CTxIn& txin, mergedTx.vin) { @@ -615,21 +615,19 @@ Value signrawtransaction(const Array& params, bool fHelp) vector pkData(ParseHexO(prevOut, "scriptPubKey")); CScript scriptPubKey(pkData.begin(), pkData.end()); - CCoins coins; - if (view.GetCoins(txid, coins)) { - if (coins.IsAvailable(nOut) && coins.vout[nOut].scriptPubKey != scriptPubKey) { + { + CCoinsModifier coins = view.ModifyCoins(txid); + if (coins->IsAvailable(nOut) && coins->vout[nOut].scriptPubKey != scriptPubKey) { 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(); 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 // given), add redeemScript to the tempKeystore so it can be signed: diff --git a/src/test/coins_tests.cpp b/src/test/coins_tests.cpp new file mode 100644 index 000000000..3ecd301bc --- /dev/null +++ b/src/test/coins_tests.cpp @@ -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 +#include + +#include + +namespace +{ +class CCoinsViewTest : public CCoinsView +{ + uint256 hashBestBlock_; + std::map map_; + +public: + bool GetCoins(const uint256& txid, CCoins& coins) const + { + std::map::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 result; + + // The cache stack. + CCoinsViewTest base; // A CCoinsViewTest at the bottom. + std::vector 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 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::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() diff --git a/src/test/script_P2SH_tests.cpp b/src/test/script_P2SH_tests.cpp index 6c32a263a..f8361a0dc 100644 --- a/src/test/script_P2SH_tests.cpp +++ b/src/test/script_P2SH_tests.cpp @@ -255,7 +255,7 @@ BOOST_AUTO_TEST_CASE(AreInputsStandard) { LOCK(cs_main); CCoinsView coinsDummy; - CCoinsViewCache coins(coinsDummy); + CCoinsViewCache coins(&coinsDummy); CBasicKeyStore keystore; CKey key[6]; vector keys; @@ -312,8 +312,7 @@ BOOST_AUTO_TEST_CASE(AreInputsStandard) txFrom.vout[6].scriptPubKey = GetScriptForDestination(twentySigops.GetID()); txFrom.vout[6].nValue = 6000; - - coins.SetCoins(txFrom.GetHash(), CCoins(txFrom, 0)); + coins.ModifyCoins(txFrom.GetHash())->FromTx(txFrom, 0); CMutableTransaction txTo; txTo.vout.resize(1); diff --git a/src/test/test_bitcoin.cpp b/src/test/test_bitcoin.cpp index 6e5f0e3fa..afd63d271 100644 --- a/src/test/test_bitcoin.cpp +++ b/src/test/test_bitcoin.cpp @@ -41,7 +41,7 @@ struct TestingSetup { mapArgs["-datadir"] = pathTemp.string(); pblocktree = new CBlockTreeDB(1 << 20, true); pcoinsdbview = new CCoinsViewDB(1 << 23, true); - pcoinsTip = new CCoinsViewCache(*pcoinsdbview); + pcoinsTip = new CCoinsViewCache(pcoinsdbview); InitBlockIndex(); #ifdef ENABLE_WALLET bool fFirstRun; diff --git a/src/test/transaction_tests.cpp b/src/test/transaction_tests.cpp index 823afa168..f14f600bb 100644 --- a/src/test/transaction_tests.cpp +++ b/src/test/transaction_tests.cpp @@ -242,7 +242,7 @@ BOOST_AUTO_TEST_CASE(basic_transaction_tests) // paid to a TX_PUBKEYHASH. // static std::vector -SetupDummyInputs(CBasicKeyStore& keystoreRet, CCoinsView & coinsRet) +SetupDummyInputs(CBasicKeyStore& keystoreRet, CCoinsViewCache& coinsRet) { std::vector dummyTransactions; 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[1].nValue = 50*CENT; 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[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.SetCoins(dummyTransactions[1].GetHash(), CCoins(dummyTransactions[1], 0)); + coinsRet.ModifyCoins(dummyTransactions[1].GetHash())->FromTx(dummyTransactions[1], 0); return dummyTransactions; } @@ -277,7 +277,7 @@ BOOST_AUTO_TEST_CASE(test_Get) { CBasicKeyStore keystore; CCoinsView coinsDummy; - CCoinsViewCache coins(coinsDummy); + CCoinsViewCache coins(&coinsDummy); std::vector dummyTransactions = SetupDummyInputs(keystore, coins); CMutableTransaction t1; @@ -312,7 +312,7 @@ BOOST_AUTO_TEST_CASE(test_IsStandard) LOCK(cs_main); CBasicKeyStore keystore; CCoinsView coinsDummy; - CCoinsViewCache coins(coinsDummy); + CCoinsViewCache coins(&coinsDummy); std::vector dummyTransactions = SetupDummyInputs(keystore, coins); CMutableTransaction t; diff --git a/src/txdb.cpp b/src/txdb.cpp index d5f424fab..cb9f15001 100644 --- a/src/txdb.cpp +++ b/src/txdb.cpp @@ -33,12 +33,6 @@ bool CCoinsViewDB::GetCoins(const uint256 &txid, CCoins &coins) const { 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 { return db.Exists(make_pair('c', txid)); } @@ -50,24 +44,23 @@ uint256 CCoinsViewDB::GetBestBlock() const { 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) { - LogPrint("coindb", "Committing %u changed transactions to coin database...\n", (unsigned int)mapCoins.size()); - CLevelDBBatch batch; + size_t count = 0; + size_t changed = 0; 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++; mapCoins.erase(itOld); } if (hashBlock != uint256(0)) 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); } diff --git a/src/txdb.h b/src/txdb.h index f0b6b9e1d..8f2bd9af4 100644 --- a/src/txdb.h +++ b/src/txdb.h @@ -33,10 +33,8 @@ public: CCoinsViewDB(size_t nCacheSize, bool fMemory = false, bool fWipe = false); bool GetCoins(const uint256 &txid, CCoins &coins) const; - bool SetCoins(const uint256 &txid, const CCoins &coins); bool HaveCoins(const uint256 &txid) const; uint256 GetBestBlock() const; - bool SetBestBlock(const uint256 &hashBlock); bool BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock); bool GetStats(CCoinsStats &stats) const; }; diff --git a/src/txmempool.cpp b/src/txmempool.cpp index d923c2204..fa1802ad3 100644 --- a/src/txmempool.cpp +++ b/src/txmempool.cpp @@ -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 { // If an entry in the mempool exists, always return that one, as it's guaranteed to never diff --git a/src/txmempool.h b/src/txmempool.h index c35ea52d4..c63fd6f59 100644 --- a/src/txmempool.h +++ b/src/txmempool.h @@ -144,7 +144,7 @@ protected: CTxMemPool &mempool; public: - CCoinsViewMemPool(CCoinsView &baseIn, CTxMemPool &mempoolIn); + CCoinsViewMemPool(CCoinsView *baseIn, CTxMemPool &mempoolIn); bool GetCoins(const uint256 &txid, CCoins &coins) const; bool HaveCoins(const uint256 &txid) const; };