Use ModifyCoins instead of mutable GetCoins.
Replace the mutable non-copying GetCoins method with a ModifyCoins, which returns an encapsulated iterator, so we can keep track of concurrent modifications (as iterators can be invalidated by those) and run cleanup code after a modification is finished. This also removes the overloading of the 'GetCoins' name.
This commit is contained in:
parent
7a04f3d708
commit
f28aec014e
5 changed files with 108 additions and 62 deletions
|
@ -384,21 +384,19 @@ static void MutateTxSign(CMutableTransaction& tx, const string& flagStr)
|
||||||
vector<unsigned char> pkData(ParseHexUV(prevOut, "scriptPubKey"));
|
vector<unsigned char> pkData(ParseHexUV(prevOut, "scriptPubKey"));
|
||||||
CScript scriptPubKey(pkData.begin(), pkData.end());
|
CScript scriptPubKey(pkData.begin(), pkData.end());
|
||||||
|
|
||||||
CCoins coins;
|
{
|
||||||
if (view.GetCoins(txid, coins)) {
|
CCoinsModifier coins = view.ModifyCoins(txid);
|
||||||
if (coins.IsAvailable(nOut) && coins.vout[nOut].scriptPubKey != scriptPubKey) {
|
if (coins->IsAvailable(nOut) && coins->vout[nOut].scriptPubKey != scriptPubKey) {
|
||||||
string err("Previous output scriptPubKey mismatch:\n");
|
string err("Previous output scriptPubKey mismatch:\n");
|
||||||
err = err + coins.vout[nOut].scriptPubKey.ToString() + "\nvs:\n"+
|
err = err + coins->vout[nOut].scriptPubKey.ToString() + "\nvs:\n"+
|
||||||
scriptPubKey.ToString();
|
scriptPubKey.ToString();
|
||||||
throw runtime_error(err);
|
throw runtime_error(err);
|
||||||
}
|
}
|
||||||
// what todo if txid is known, but the actual output isn't?
|
if ((unsigned int)nOut >= coins->vout.size())
|
||||||
|
coins->vout.resize(nOut+1);
|
||||||
|
coins->vout[nOut].scriptPubKey = scriptPubKey;
|
||||||
|
coins->vout[nOut].nValue = 0; // we don't know the actual output value
|
||||||
}
|
}
|
||||||
if ((unsigned int)nOut >= coins.vout.size())
|
|
||||||
coins.vout.resize(nOut+1);
|
|
||||||
coins.vout[nOut].scriptPubKey = scriptPubKey;
|
|
||||||
coins.vout[nOut].nValue = 0; // we don't know the actual output value
|
|
||||||
view.SetCoins(txid, coins);
|
|
||||||
|
|
||||||
// if redeemScript given and private keys given,
|
// if redeemScript given and private keys given,
|
||||||
// add redeemScript to the tempKeystore so it can be signed:
|
// add redeemScript to the tempKeystore so it can be signed:
|
||||||
|
|
|
@ -73,7 +73,7 @@ bool CCoinsViewBacked::GetStats(CCoinsStats &stats) const { return base->GetStat
|
||||||
|
|
||||||
CCoinsKeyHasher::CCoinsKeyHasher() : salt(GetRandHash()) {}
|
CCoinsKeyHasher::CCoinsKeyHasher() : salt(GetRandHash()) {}
|
||||||
|
|
||||||
CCoinsViewCache::CCoinsViewCache(CCoinsView &baseIn, bool fDummy) : CCoinsViewBacked(baseIn), hashBlock(0) { }
|
CCoinsViewCache::CCoinsViewCache(CCoinsView &baseIn, bool fDummy) : CCoinsViewBacked(baseIn), hasModifier(false), hashBlock(0) { }
|
||||||
|
|
||||||
bool CCoinsViewCache::GetCoins(const uint256 &txid, CCoins &coins) const {
|
bool CCoinsViewCache::GetCoins(const uint256 &txid, CCoins &coins) const {
|
||||||
if (cacheCoins.count(txid)) {
|
if (cacheCoins.count(txid)) {
|
||||||
|
@ -87,7 +87,12 @@ bool CCoinsViewCache::GetCoins(const uint256 &txid, CCoins &coins) const {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
CCoinsMap::iterator CCoinsViewCache::FetchCoins(const uint256 &txid) {
|
CCoinsViewCache::~CCoinsViewCache()
|
||||||
|
{
|
||||||
|
assert(!hasModifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
CCoinsMap::const_iterator CCoinsViewCache::FetchCoins(const uint256 &txid) const {
|
||||||
CCoinsMap::iterator it = cacheCoins.find(txid);
|
CCoinsMap::iterator it = cacheCoins.find(txid);
|
||||||
if (it != cacheCoins.end())
|
if (it != cacheCoins.end())
|
||||||
return it;
|
return it;
|
||||||
|
@ -99,15 +104,15 @@ CCoinsMap::iterator CCoinsViewCache::FetchCoins(const uint256 &txid) {
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
CCoinsMap::const_iterator CCoinsViewCache::FetchCoins(const uint256 &txid) const {
|
CCoinsModifier CCoinsViewCache::ModifyCoins(const uint256 &txid) {
|
||||||
/* Avoid redundant implementation with the const-cast. */
|
assert(!hasModifier);
|
||||||
return const_cast<CCoinsViewCache*>(this)->FetchCoins(txid);
|
hasModifier = true;
|
||||||
}
|
std::pair<CCoinsMap::iterator, bool> ret = cacheCoins.insert(std::make_pair(txid, CCoins()));
|
||||||
|
if (ret.second) {
|
||||||
CCoins &CCoinsViewCache::GetCoins(const uint256 &txid) {
|
if (!base->GetCoins(txid, ret.first->second))
|
||||||
CCoinsMap::iterator it = FetchCoins(txid);
|
ret.first->second.Clear();
|
||||||
assert(it != cacheCoins.end());
|
}
|
||||||
return it->second;
|
return CCoinsModifier(*this, ret.first);
|
||||||
}
|
}
|
||||||
|
|
||||||
const CCoins* CCoinsViewCache::AccessCoins(const uint256 &txid) const {
|
const CCoins* CCoinsViewCache::AccessCoins(const uint256 &txid) const {
|
||||||
|
@ -145,6 +150,7 @@ bool CCoinsViewCache::SetBestBlock(const uint256 &hashBlockIn) {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CCoinsViewCache::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlockIn) {
|
bool CCoinsViewCache::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlockIn) {
|
||||||
|
assert(!hasModifier);
|
||||||
for (CCoinsMap::iterator it = mapCoins.begin(); it != mapCoins.end();) {
|
for (CCoinsMap::iterator it = mapCoins.begin(); it != mapCoins.end();) {
|
||||||
cacheCoins[it->first].swap(it->second);
|
cacheCoins[it->first].swap(it->second);
|
||||||
CCoinsMap::iterator itOld = it++;
|
CCoinsMap::iterator itOld = it++;
|
||||||
|
@ -213,3 +219,11 @@ double CCoinsViewCache::GetPriority(const CTransaction &tx, int nHeight) const
|
||||||
}
|
}
|
||||||
return tx.ComputePriority(dResult);
|
return tx.ComputePriority(dResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CCoinsModifier::CCoinsModifier(CCoinsViewCache& cache_, CCoinsMap::iterator it_) : cache(cache_), it(it_) {}
|
||||||
|
|
||||||
|
CCoinsModifier::~CCoinsModifier() {
|
||||||
|
assert(cache.hasModifier);
|
||||||
|
cache.hasModifier = false;
|
||||||
|
it->second.Cleanup();
|
||||||
|
}
|
||||||
|
|
49
src/coins.h
49
src/coins.h
|
@ -83,11 +83,26 @@ public:
|
||||||
// as new tx version will probably only be introduced at certain heights
|
// as new tx version will probably only be introduced at certain heights
|
||||||
int nVersion;
|
int nVersion;
|
||||||
|
|
||||||
// construct a CCoins from a CTransaction, at a given height
|
void FromTx(const CTransaction &tx, int nHeightIn) {
|
||||||
CCoins(const CTransaction &tx, int nHeightIn) : fCoinBase(tx.IsCoinBase()), vout(tx.vout), nHeight(nHeightIn), nVersion(tx.nVersion) {
|
fCoinBase = tx.IsCoinBase();
|
||||||
|
vout = tx.vout;
|
||||||
|
nHeight = nHeightIn;
|
||||||
|
nVersion = tx.nVersion;
|
||||||
ClearUnspendable();
|
ClearUnspendable();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// construct a CCoins from a CTransaction, at a given height
|
||||||
|
CCoins(const CTransaction &tx, int nHeightIn) {
|
||||||
|
FromTx(tx, nHeightIn);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Clear() {
|
||||||
|
fCoinBase = false;
|
||||||
|
std::vector<CTxOut>().swap(vout);
|
||||||
|
nHeight = 0;
|
||||||
|
nVersion = 0;
|
||||||
|
}
|
||||||
|
|
||||||
// empty constructor
|
// empty constructor
|
||||||
CCoins() : fCoinBase(false), vout(0), nHeight(0), nVersion(0) { }
|
CCoins() : fCoinBase(false), vout(0), nHeight(0), nVersion(0) { }
|
||||||
|
|
||||||
|
@ -323,10 +338,31 @@ public:
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
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; }
|
||||||
|
CCoins& operator*() { return it->second; }
|
||||||
|
~CCoinsModifier();
|
||||||
|
friend class CCoinsViewCache;
|
||||||
|
};
|
||||||
|
|
||||||
/** CCoinsView that adds a memory cache for transactions to another CCoinsView */
|
/** CCoinsView that adds a memory cache for transactions to another CCoinsView */
|
||||||
class CCoinsViewCache : public CCoinsViewBacked
|
class CCoinsViewCache : public CCoinsViewBacked
|
||||||
{
|
{
|
||||||
protected:
|
protected:
|
||||||
|
/* Whether this cache has an active modifier. */
|
||||||
|
bool hasModifier;
|
||||||
|
|
||||||
/* Make mutable so that we can "fill the cache" even from Get-methods
|
/* Make mutable so that we can "fill the cache" even from Get-methods
|
||||||
declared as "const". */
|
declared as "const". */
|
||||||
|
@ -335,6 +371,7 @@ protected:
|
||||||
|
|
||||||
public:
|
public:
|
||||||
CCoinsViewCache(CCoinsView &baseIn, bool fDummy = false);
|
CCoinsViewCache(CCoinsView &baseIn, bool fDummy = false);
|
||||||
|
~CCoinsViewCache();
|
||||||
|
|
||||||
// Standard CCoinsView methods
|
// Standard CCoinsView methods
|
||||||
bool GetCoins(const uint256 &txid, CCoins &coins) const;
|
bool GetCoins(const uint256 &txid, CCoins &coins) const;
|
||||||
|
@ -349,8 +386,10 @@ public:
|
||||||
// allowed while accessing the returned pointer.
|
// allowed while accessing the returned pointer.
|
||||||
const CCoins* AccessCoins(const uint256 &txid) const;
|
const CCoins* AccessCoins(const uint256 &txid) const;
|
||||||
|
|
||||||
// Return a modifiable reference to a CCoins. Check HaveCoins first.
|
// Return a modifiable reference to a CCoins. If no entry with the given
|
||||||
CCoins &GetCoins(const uint256 &txid);
|
// txid exists, a new one is created. Simultaneous modifications are not
|
||||||
|
// allowed.
|
||||||
|
CCoinsModifier ModifyCoins(const uint256 &txid);
|
||||||
|
|
||||||
// Push the modifications applied to this cache to its base.
|
// Push the modifications applied to this cache to its base.
|
||||||
// Failure to call this method before destruction will cause the changes to be forgotten.
|
// Failure to call this method before destruction will cause the changes to be forgotten.
|
||||||
|
@ -377,6 +416,8 @@ public:
|
||||||
|
|
||||||
const CTxOut &GetOutputFor(const CTxIn& input) const;
|
const CTxOut &GetOutputFor(const CTxIn& input) const;
|
||||||
|
|
||||||
|
friend class CCoinsModifier;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
CCoinsMap::iterator FetchCoins(const uint256 &txid);
|
CCoinsMap::iterator FetchCoins(const uint256 &txid);
|
||||||
CCoinsMap::const_iterator FetchCoins(const uint256 &txid) const;
|
CCoinsMap::const_iterator FetchCoins(const uint256 &txid) const;
|
||||||
|
|
49
src/main.cpp
49
src/main.cpp
|
@ -1348,22 +1348,18 @@ void static InvalidBlockFound(CBlockIndex *pindex, const CValidationState &state
|
||||||
|
|
||||||
void UpdateCoins(const CTransaction& tx, CValidationState &state, CCoinsViewCache &inputs, CTxUndo &txundo, int nHeight)
|
void UpdateCoins(const CTransaction& tx, CValidationState &state, CCoinsViewCache &inputs, CTxUndo &txundo, int nHeight)
|
||||||
{
|
{
|
||||||
bool ret;
|
|
||||||
// mark inputs spent
|
// mark inputs spent
|
||||||
if (!tx.IsCoinBase()) {
|
if (!tx.IsCoinBase()) {
|
||||||
txundo.vprevout.reserve(tx.vin.size());
|
txundo.vprevout.reserve(tx.vin.size());
|
||||||
for (unsigned int i = 0; i < tx.vin.size(); i++) {
|
BOOST_FOREACH(const CTxIn &txin, tx.vin) {
|
||||||
const CTxIn &txin = tx.vin[i];
|
|
||||||
CCoins &coins = inputs.GetCoins(txin.prevout.hash);
|
|
||||||
txundo.vprevout.push_back(CTxInUndo());
|
txundo.vprevout.push_back(CTxInUndo());
|
||||||
ret = coins.Spend(txin.prevout, txundo.vprevout.back());
|
bool ret = inputs.ModifyCoins(txin.prevout.hash)->Spend(txin.prevout, txundo.vprevout.back());
|
||||||
assert(ret);
|
assert(ret);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// add outputs
|
// add outputs
|
||||||
ret = inputs.SetCoins(tx.GetHash(), CCoins(tx, nHeight));
|
inputs.ModifyCoins(tx.GetHash())->FromTx(tx, nHeight);
|
||||||
assert(ret);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CScriptCheck::operator()() const {
|
bool CScriptCheck::operator()() const {
|
||||||
|
@ -1504,21 +1500,23 @@ bool DisconnectBlock(CBlock& block, CValidationState& state, CBlockIndex* pindex
|
||||||
// exactly. Note that transactions with only provably unspendable outputs won't
|
// exactly. Note that transactions with only provably unspendable outputs won't
|
||||||
// have outputs available even in the block itself, so we handle that case
|
// have outputs available even in the block itself, so we handle that case
|
||||||
// specially with outsEmpty.
|
// specially with outsEmpty.
|
||||||
|
{
|
||||||
CCoins outsEmpty;
|
CCoins outsEmpty;
|
||||||
CCoins &outs = view.HaveCoins(hash) ? view.GetCoins(hash) : outsEmpty;
|
CCoinsModifier outs = view.ModifyCoins(hash);
|
||||||
outs.ClearUnspendable();
|
outs->ClearUnspendable();
|
||||||
|
|
||||||
CCoins outsBlock = CCoins(tx, pindex->nHeight);
|
CCoins outsBlock(tx, pindex->nHeight);
|
||||||
// The CCoins serialization does not serialize negative numbers.
|
// The CCoins serialization does not serialize negative numbers.
|
||||||
// No network rules currently depend on the version here, so an inconsistency is harmless
|
// No network rules currently depend on the version here, so an inconsistency is harmless
|
||||||
// but it must be corrected before txout nversion ever influences a network rule.
|
// but it must be corrected before txout nversion ever influences a network rule.
|
||||||
if (outsBlock.nVersion < 0)
|
if (outsBlock.nVersion < 0)
|
||||||
outs.nVersion = outsBlock.nVersion;
|
outs->nVersion = outsBlock.nVersion;
|
||||||
if (outs != outsBlock)
|
if (*outs != outsBlock)
|
||||||
fClean = fClean && error("DisconnectBlock() : added transaction mismatch? database corrupted");
|
fClean = fClean && error("DisconnectBlock() : added transaction mismatch? database corrupted");
|
||||||
|
|
||||||
// remove outputs
|
// remove outputs
|
||||||
outs = CCoins();
|
outs->Clear();
|
||||||
|
}
|
||||||
|
|
||||||
// restore inputs
|
// restore inputs
|
||||||
if (i > 0) { // not coinbases
|
if (i > 0) { // not coinbases
|
||||||
|
@ -1528,27 +1526,24 @@ bool DisconnectBlock(CBlock& block, CValidationState& state, CBlockIndex* pindex
|
||||||
for (unsigned int j = tx.vin.size(); j-- > 0;) {
|
for (unsigned int j = tx.vin.size(); j-- > 0;) {
|
||||||
const COutPoint &out = tx.vin[j].prevout;
|
const COutPoint &out = tx.vin[j].prevout;
|
||||||
const CTxInUndo &undo = txundo.vprevout[j];
|
const CTxInUndo &undo = txundo.vprevout[j];
|
||||||
CCoins coins;
|
CCoinsModifier coins = view.ModifyCoins(out.hash);
|
||||||
view.GetCoins(out.hash, coins); // this can fail if the prevout was already entirely spent
|
|
||||||
if (undo.nHeight != 0) {
|
if (undo.nHeight != 0) {
|
||||||
// undo data contains height: this is the last output of the prevout tx being spent
|
// undo data contains height: this is the last output of the prevout tx being spent
|
||||||
if (!coins.IsPruned())
|
if (!coins->IsPruned())
|
||||||
fClean = fClean && error("DisconnectBlock() : undo data overwriting existing transaction");
|
fClean = fClean && error("DisconnectBlock() : undo data overwriting existing transaction");
|
||||||
coins = CCoins();
|
coins->Clear();
|
||||||
coins.fCoinBase = undo.fCoinBase;
|
coins->fCoinBase = undo.fCoinBase;
|
||||||
coins.nHeight = undo.nHeight;
|
coins->nHeight = undo.nHeight;
|
||||||
coins.nVersion = undo.nVersion;
|
coins->nVersion = undo.nVersion;
|
||||||
} else {
|
} else {
|
||||||
if (coins.IsPruned())
|
if (coins->IsPruned())
|
||||||
fClean = fClean && error("DisconnectBlock() : undo data adding output to missing transaction");
|
fClean = fClean && error("DisconnectBlock() : undo data adding output to missing transaction");
|
||||||
}
|
}
|
||||||
if (coins.IsAvailable(out.n))
|
if (coins->IsAvailable(out.n))
|
||||||
fClean = fClean && error("DisconnectBlock() : undo data overwriting existing output");
|
fClean = fClean && error("DisconnectBlock() : undo data overwriting existing output");
|
||||||
if (coins.vout.size() < out.n+1)
|
if (coins->vout.size() < out.n+1)
|
||||||
coins.vout.resize(out.n+1);
|
coins->vout.resize(out.n+1);
|
||||||
coins.vout[out.n] = undo.txout;
|
coins->vout[out.n] = undo.txout;
|
||||||
if (!view.SetCoins(out.hash, coins))
|
|
||||||
return error("DisconnectBlock() : cannot restore coin inputs");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -612,21 +612,19 @@ Value signrawtransaction(const Array& params, bool fHelp)
|
||||||
vector<unsigned char> pkData(ParseHexO(prevOut, "scriptPubKey"));
|
vector<unsigned char> pkData(ParseHexO(prevOut, "scriptPubKey"));
|
||||||
CScript scriptPubKey(pkData.begin(), pkData.end());
|
CScript scriptPubKey(pkData.begin(), pkData.end());
|
||||||
|
|
||||||
CCoins coins;
|
{
|
||||||
if (view.GetCoins(txid, coins)) {
|
CCoinsModifier coins = view.ModifyCoins(txid);
|
||||||
if (coins.IsAvailable(nOut) && coins.vout[nOut].scriptPubKey != scriptPubKey) {
|
if (coins->IsAvailable(nOut) && coins->vout[nOut].scriptPubKey != scriptPubKey) {
|
||||||
string err("Previous output scriptPubKey mismatch:\n");
|
string err("Previous output scriptPubKey mismatch:\n");
|
||||||
err = err + coins.vout[nOut].scriptPubKey.ToString() + "\nvs:\n"+
|
err = err + coins->vout[nOut].scriptPubKey.ToString() + "\nvs:\n"+
|
||||||
scriptPubKey.ToString();
|
scriptPubKey.ToString();
|
||||||
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, err);
|
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, err);
|
||||||
}
|
}
|
||||||
// what todo if txid is known, but the actual output isn't?
|
if ((unsigned int)nOut >= coins->vout.size())
|
||||||
|
coins->vout.resize(nOut+1);
|
||||||
|
coins->vout[nOut].scriptPubKey = scriptPubKey;
|
||||||
|
coins->vout[nOut].nValue = 0; // we don't know the actual output value
|
||||||
}
|
}
|
||||||
if ((unsigned int)nOut >= coins.vout.size())
|
|
||||||
coins.vout.resize(nOut+1);
|
|
||||||
coins.vout[nOut].scriptPubKey = scriptPubKey;
|
|
||||||
coins.vout[nOut].nValue = 0; // we don't know the actual output value
|
|
||||||
view.SetCoins(txid, coins);
|
|
||||||
|
|
||||||
// if redeemScript given and not using the local wallet (private keys
|
// if redeemScript given and not using the local wallet (private keys
|
||||||
// given), add redeemScript to the tempKeystore so it can be signed:
|
// given), add redeemScript to the tempKeystore so it can be signed:
|
||||||
|
|
Loading…
Reference in a new issue