aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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;
};