aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorPieter Wuille <pieter.wuille@gmail.com>2014-10-08 14:57:31 -0700
committerPieter Wuille <pieter.wuille@gmail.com>2014-10-08 14:58:16 -0700
commitd4a42334d447cad48fb3996cad0fd5c945b75571 (patch)
treeed1c616af0038cf7fb5a5b95e3c2dc5eddbf5fdc /src
parent97a34c28d568cb8b2171d8dd8eb12ca8afb08a7b (diff)
parent7c70438dc67547e83953ba0343a071fae304ce65 (diff)
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)
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.test.include1
-rw-r--r--src/bitcoin-tx.cpp20
-rw-r--r--src/coins.cpp117
-rw-r--r--src/coins.h81
-rw-r--r--src/init.cpp2
-rw-r--r--src/main.cpp67
-rw-r--r--src/miner.cpp4
-rw-r--r--src/rpcblockchain.cpp2
-rw-r--r--src/rpcrawtransaction.cpp22
-rw-r--r--src/test/coins_tests.cpp178
-rw-r--r--src/test/script_P2SH_tests.cpp5
-rw-r--r--src/test/test_bitcoin.cpp2
-rw-r--r--src/test/transaction_tests.cpp10
-rw-r--r--src/txdb.cpp23
-rw-r--r--src/txdb.h2
-rw-r--r--src/txmempool.cpp2
-rw-r--r--src/txmempool.h2
17 files changed, 393 insertions, 147 deletions
diff --git a/src/Makefile.test.include b/src/Makefile.test.include
index ab449f3e71..99ac09e1a4 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 a198eb586e..da37e60c7f 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<unsigned char> 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 9b8d63d4e4..e4f3e67aeb 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<CCoinsViewCache*>(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<CCoinsMap::iterator, bool> 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 2583475323..b8f1e5bcc5 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<CTxOut>().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<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
{
@@ -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 97d0f58527..8dcd35fb8f 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 5d46c30a9b..de3876944e 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 280349e8c2..fd6c52ca69 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<COrphan> 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 4b3beae20c..24175215bf 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 23e647b17a..78372da685 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<unsigned char> 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 0000000000..3ecd301bc7
--- /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 <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()
diff --git a/src/test/script_P2SH_tests.cpp b/src/test/script_P2SH_tests.cpp
index 6c32a263a9..f8361a0dc8 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<CPubKey> 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 6e5f0e3fac..afd63d2717 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 823afa1680..f14f600bbe 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<CMutableTransaction>
-SetupDummyInputs(CBasicKeyStore& keystoreRet, CCoinsView & coinsRet)
+SetupDummyInputs(CBasicKeyStore& keystoreRet, CCoinsViewCache& coinsRet)
{
std::vector<CMutableTransaction> 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<CMutableTransaction> 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<CMutableTransaction> dummyTransactions = SetupDummyInputs(keystore, coins);
CMutableTransaction t;
diff --git a/src/txdb.cpp b/src/txdb.cpp
index d5f424fabd..cb9f150011 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 f0b6b9e1dd..8f2bd9af4d 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 d923c2204a..fa1802ad31 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 c35ea52d4e..c63fd6f590 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;
};