Browse Source

Direct CCoins references

To prevent excessive copying of CCoins in and out of the CCoinsView
implementations, introduce a GetCoins() function in CCoinsViewCache
with returns a direct reference. The block validation and connection
logic is updated to require caching CCoinsViews, and exploits the
GetCoins() function heavily.
0.8
Pieter Wuille 12 years ago
parent
commit
13c51f20f6
  1. 102
      src/main.cpp
  2. 23
      src/main.h
  3. 3
      src/test/transaction_tests.cpp

102
src/main.cpp

@ -170,6 +170,7 @@ CBlockIndex *CCoinsView::GetBestBlock() { return NULL; }
bool CCoinsView::SetBestBlock(CBlockIndex *pindex) { return false; } bool CCoinsView::SetBestBlock(CBlockIndex *pindex) { return false; }
bool CCoinsView::BatchWrite(const std::map<uint256, CCoins> &mapCoins, CBlockIndex *pindex) { return false; } bool CCoinsView::BatchWrite(const std::map<uint256, CCoins> &mapCoins, CBlockIndex *pindex) { return false; }
CCoinsViewBacked::CCoinsViewBacked(CCoinsView &viewIn) : base(&viewIn) { } CCoinsViewBacked::CCoinsViewBacked(CCoinsView &viewIn) : base(&viewIn) { }
bool CCoinsViewBacked::GetCoins(uint256 txid, CCoins &coins) { return base->GetCoins(txid, coins); } bool CCoinsViewBacked::GetCoins(uint256 txid, CCoins &coins) { return base->GetCoins(txid, coins); }
bool CCoinsViewBacked::SetCoins(uint256 txid, const CCoins &coins) { return base->SetCoins(txid, coins); } bool CCoinsViewBacked::SetCoins(uint256 txid, const CCoins &coins) { return base->SetCoins(txid, coins); }
@ -193,13 +194,30 @@ bool CCoinsViewCache::GetCoins(uint256 txid, CCoins &coins) {
return false; return false;
} }
std::map<uint256,CCoins>::iterator CCoinsViewCache::FetchCoins(uint256 txid) {
std::map<uint256,CCoins>::iterator it = cacheCoins.find(txid);
if (it != cacheCoins.end())
return it;
CCoins tmp;
if (!base->GetCoins(txid,tmp))
return it;
std::pair<std::map<uint256,CCoins>::iterator,bool> ret = cacheCoins.insert(std::make_pair(txid, tmp));
return ret.first;
}
CCoins &CCoinsViewCache::GetCoins(uint256 txid) {
std::map<uint256,CCoins>::iterator it = FetchCoins(txid);
assert(it != cacheCoins.end());
return it->second;
}
bool CCoinsViewCache::SetCoins(uint256 txid, const CCoins &coins) { bool CCoinsViewCache::SetCoins(uint256 txid, const CCoins &coins) {
cacheCoins[txid] = coins; cacheCoins[txid] = coins;
return true; return true;
} }
bool CCoinsViewCache::HaveCoins(uint256 txid) { bool CCoinsViewCache::HaveCoins(uint256 txid) {
return cacheCoins.count(txid) || base->HaveCoins(txid); return FetchCoins(txid) != cacheCoins.end();
} }
CBlockIndex *CCoinsViewCache::GetBestBlock() { CBlockIndex *CCoinsViewCache::GetBestBlock() {
@ -369,7 +387,7 @@ bool CTransaction::IsStandard() const
// expensive-to-check-upon-redemption script like: // expensive-to-check-upon-redemption script like:
// DUP CHECKSIG DROP ... repeated 100 times... OP_1 // DUP CHECKSIG DROP ... repeated 100 times... OP_1
// //
bool CTransaction::AreInputsStandard(CCoinsView& mapInputs) const bool CTransaction::AreInputsStandard(CCoinsViewCache& mapInputs) const
{ {
if (IsCoinBase()) if (IsCoinBase())
return true; // Coinbases don't use vin normally return true; // Coinbases don't use vin normally
@ -683,6 +701,9 @@ bool CTxMemPool::accept(CTransaction &tx, bool fCheckInputs,
} }
} }
if (!tx.HaveInputs(view))
return error("CTxMemPool::accept() : inputs already spent");
// Check for non-standard pay-to-script-hash in inputs // Check for non-standard pay-to-script-hash in inputs
if (!tx.AreInputsStandard(view) && !fTestNet) if (!tx.AreInputsStandard(view) && !fTestNet)
return error("CTxMemPool::accept() : nonstandard transaction input"); return error("CTxMemPool::accept() : nonstandard transaction input");
@ -1154,23 +1175,14 @@ void CBlock::UpdateTime(const CBlockIndex* pindexPrev)
CTxOut CTransaction::GetOutputFor(const CTxIn& input, CCoinsView& view) const CTxOut &CTransaction::GetOutputFor(const CTxIn& input, CCoinsViewCache& view)
{ {
CCoins coins; const CCoins &coins = view.GetCoins(input.prevout.hash);
if (!view.GetCoins(input.prevout.hash, coins)) assert(coins.IsAvailable(input.prevout.n));
throw std::runtime_error("CTransaction::GetOutputFor() : prevout.hash not found"); return coins.vout[input.prevout.n];
if (input.prevout.n >= coins.vout.size())
throw std::runtime_error("CTransaction::GetOutputFor() : prevout.n out of range or already spent");
const CTxOut &out = coins.vout[input.prevout.n];
if (out.IsNull())
throw std::runtime_error("CTransaction::GetOutputFor() : already spent");
return out;
} }
int64 CTransaction::GetValueIn(CCoinsView& inputs) const int64 CTransaction::GetValueIn(CCoinsViewCache& inputs) const
{ {
if (IsCoinBase()) if (IsCoinBase())
return 0; return 0;
@ -1182,7 +1194,7 @@ int64 CTransaction::GetValueIn(CCoinsView& inputs) const
return nResult; return nResult;
} }
unsigned int CTransaction::GetP2SHSigOpCount(CCoinsView& inputs) const unsigned int CTransaction::GetP2SHSigOpCount(CCoinsViewCache& inputs) const
{ {
if (IsCoinBase()) if (IsCoinBase())
return 0; return 0;
@ -1190,27 +1202,23 @@ unsigned int CTransaction::GetP2SHSigOpCount(CCoinsView& inputs) const
unsigned int nSigOps = 0; unsigned int nSigOps = 0;
for (unsigned int i = 0; i < vin.size(); i++) for (unsigned int i = 0; i < vin.size(); i++)
{ {
CTxOut prevout = GetOutputFor(vin[i], inputs); const CTxOut &prevout = GetOutputFor(vin[i], inputs);
if (prevout.scriptPubKey.IsPayToScriptHash()) if (prevout.scriptPubKey.IsPayToScriptHash())
nSigOps += prevout.scriptPubKey.GetSigOpCount(vin[i].scriptSig); nSigOps += prevout.scriptPubKey.GetSigOpCount(vin[i].scriptSig);
} }
return nSigOps; return nSigOps;
} }
bool CTransaction::UpdateCoins(CCoinsView &inputs, CTxUndo &txundo, int nHeight, const uint256 &txhash) const bool CTransaction::UpdateCoins(CCoinsViewCache &inputs, CTxUndo &txundo, int nHeight, const uint256 &txhash) const
{ {
// mark inputs spent // mark inputs spent
if (!IsCoinBase()) { if (!IsCoinBase()) {
BOOST_FOREACH(const CTxIn &txin, vin) { BOOST_FOREACH(const CTxIn &txin, vin) {
CCoins coins; CCoins &coins = inputs.GetCoins(txin.prevout.hash);
if (!inputs.GetCoins(txin.prevout.hash, coins))
return error("UpdateCoins() : cannot find prevtx");
CTxInUndo undo; CTxInUndo undo;
if (!coins.Spend(txin.prevout, undo)) if (!coins.Spend(txin.prevout, undo))
return error("UpdateCoins() : cannot spend input"); return error("UpdateCoins() : cannot spend input");
txundo.vprevout.push_back(undo); txundo.vprevout.push_back(undo);
if (!inputs.SetCoins(txin.prevout.hash, coins))
return error("UpdateCoins() : cannot update input");
} }
} }
@ -1221,7 +1229,7 @@ bool CTransaction::UpdateCoins(CCoinsView &inputs, CTxUndo &txundo, int nHeight,
return true; return true;
} }
bool CTransaction::HaveInputs(CCoinsView &inputs) const bool CTransaction::HaveInputs(CCoinsViewCache &inputs) const
{ {
if (!IsCoinBase()) { if (!IsCoinBase()) {
// first check whether information about the prevout hash is available // first check whether information about the prevout hash is available
@ -1234,8 +1242,7 @@ bool CTransaction::HaveInputs(CCoinsView &inputs) const
// then check whether the actual outputs are available // then check whether the actual outputs are available
for (unsigned int i = 0; i < vin.size(); i++) { for (unsigned int i = 0; i < vin.size(); i++) {
const COutPoint &prevout = vin[i].prevout; const COutPoint &prevout = vin[i].prevout;
CCoins coins; const CCoins &coins = inputs.GetCoins(prevout.hash);
inputs.GetCoins(prevout.hash, coins);
if (!coins.IsAvailable(prevout.n)) if (!coins.IsAvailable(prevout.n))
return false; return false;
} }
@ -1243,28 +1250,25 @@ bool CTransaction::HaveInputs(CCoinsView &inputs) const
return true; return true;
} }
bool CTransaction::CheckInputs(CCoinsView &inputs, enum CheckSig_mode csmode, bool fStrictPayToScriptHash, bool fStrictEncodings) const bool CTransaction::CheckInputs(CCoinsViewCache &inputs, enum CheckSig_mode csmode, bool fStrictPayToScriptHash, bool fStrictEncodings) const
{ {
if (!IsCoinBase()) if (!IsCoinBase())
{ {
// This doesn't trigger the DoS code on purpose; if it did, it would make it easier
// for an attacker to attempt to split the network.
if (!HaveInputs(inputs))
return error("CheckInputs() : %s inputs unavailable", GetHash().ToString().substr(0,10).c_str());
CBlockIndex *pindexBlock = inputs.GetBestBlock();
int64 nValueIn = 0; int64 nValueIn = 0;
int64 nFees = 0; int64 nFees = 0;
for (unsigned int i = 0; i < vin.size(); i++) for (unsigned int i = 0; i < vin.size(); i++)
{ {
const COutPoint &prevout = vin[i].prevout; const COutPoint &prevout = vin[i].prevout;
CCoins coins; const CCoins &coins = inputs.GetCoins(prevout.hash);
if (!inputs.GetCoins(prevout.hash, coins))
return error("CheckInputs() : cannot find prevout tx");
// Check for conflicts (double-spend)
// This doesn't trigger the DoS code on purpose; if it did, it would make it easier
// for an attacker to attempt to split the network.
if (!coins.IsAvailable(prevout.n))
return error("CheckInputs() : %s prev tx already used", GetHash().ToString().substr(0,10).c_str());
// If prev is coinbase, check that it's matured // If prev is coinbase, check that it's matured
if (coins.IsCoinBase()) { if (coins.IsCoinBase()) {
CBlockIndex *pindexBlock = inputs.GetBestBlock();
if (pindexBlock->nHeight - coins.nHeight < COINBASE_MATURITY) if (pindexBlock->nHeight - coins.nHeight < COINBASE_MATURITY)
return error("CheckInputs() : tried to spend coinbase at depth %d", pindexBlock->nHeight - coins.nHeight); return error("CheckInputs() : tried to spend coinbase at depth %d", pindexBlock->nHeight - coins.nHeight);
} }
@ -1298,8 +1302,7 @@ bool CTransaction::CheckInputs(CCoinsView &inputs, enum CheckSig_mode csmode, bo
(csmode == CS_AFTER_CHECKPOINT && inputs.GetBestBlock()->nHeight >= Checkpoints::GetTotalBlocksEstimate())) { (csmode == CS_AFTER_CHECKPOINT && inputs.GetBestBlock()->nHeight >= Checkpoints::GetTotalBlocksEstimate())) {
for (unsigned int i = 0; i < vin.size(); i++) { for (unsigned int i = 0; i < vin.size(); i++) {
const COutPoint &prevout = vin[i].prevout; const COutPoint &prevout = vin[i].prevout;
CCoins coins; const CCoins &coins = inputs.GetCoins(prevout.hash);
inputs.GetCoins(prevout.hash, coins);
// Verify signature // Verify signature
if (!VerifySignature(coins, *this, i, fStrictPayToScriptHash, fStrictEncodings, 0)) { if (!VerifySignature(coins, *this, i, fStrictPayToScriptHash, fStrictEncodings, 0)) {
@ -1367,7 +1370,7 @@ bool CTransaction::ClientCheckInputs() const
bool CBlock::DisconnectBlock(CBlockIndex *pindex, CCoinsView &view) bool CBlock::DisconnectBlock(CBlockIndex *pindex, CCoinsViewCache &view)
{ {
assert(pindex == view.GetBestBlock()); assert(pindex == view.GetBestBlock());
@ -1391,17 +1394,16 @@ bool CBlock::DisconnectBlock(CBlockIndex *pindex, CCoinsView &view)
uint256 hash = tx.GetHash(); uint256 hash = tx.GetHash();
// check that all outputs are available // check that all outputs are available
CCoins outs; if (!view.HaveCoins(hash))
if (!view.GetCoins(hash, outs))
return error("DisconnectBlock() : outputs still spent? database corrupted"); return error("DisconnectBlock() : outputs still spent? database corrupted");
CCoins &outs = view.GetCoins(hash);
CCoins outsBlock = CCoins(tx, pindex->nHeight); CCoins outsBlock = CCoins(tx, pindex->nHeight);
if (outs != outsBlock) if (outs != outsBlock)
return error("DisconnectBlock() : added transaction mismatch? database corrupted"); return error("DisconnectBlock() : added transaction mismatch? database corrupted");
// remove outputs // remove outputs
if (!view.SetCoins(hash, CCoins())) outs = CCoins();
return error("DisconnectBlock() : cannot delete coin outputs");
// restore inputs // restore inputs
if (i > 0) { // not coinbases if (i > 0) { // not coinbases
@ -1441,7 +1443,7 @@ bool CBlock::DisconnectBlock(CBlockIndex *pindex, CCoinsView &view)
bool FindUndoPos(CChainDB &chaindb, int nFile, CDiskBlockPos &pos, unsigned int nAddSize); bool FindUndoPos(CChainDB &chaindb, int nFile, CDiskBlockPos &pos, unsigned int nAddSize);
bool CBlock::ConnectBlock(CBlockIndex* pindex, CCoinsView &view, bool fJustCheck) bool CBlock::ConnectBlock(CBlockIndex* pindex, CCoinsViewCache &view, bool fJustCheck)
{ {
// Check it again in case a previous version let a bad block in // Check it again in case a previous version let a bad block in
if (!CheckBlock(!fJustCheck, !fJustCheck)) if (!CheckBlock(!fJustCheck, !fJustCheck))
@ -1467,8 +1469,7 @@ bool CBlock::ConnectBlock(CBlockIndex* pindex, CCoinsView &view, bool fJustCheck
if (fEnforceBIP30) { if (fEnforceBIP30) {
for (unsigned int i=0; i<vtx.size(); i++) { for (unsigned int i=0; i<vtx.size(); i++) {
uint256 hash = GetTxHash(i); uint256 hash = GetTxHash(i);
CCoins coins; if (view.HaveCoins(hash) && !view.GetCoins(hash).IsPruned())
if (view.GetCoins(hash, coins) && !coins.IsPruned())
return error("ConnectBlock() : tried to overwrite transaction"); return error("ConnectBlock() : tried to overwrite transaction");
} }
} }
@ -3719,7 +3720,7 @@ CBlock* CreateNewBlock(CReserveKey& reservekey)
std::make_heap(vecPriority.begin(), vecPriority.end(), comparer); std::make_heap(vecPriority.begin(), vecPriority.end(), comparer);
} }
if (!tx.CheckInputs(viewTemp, CS_ALWAYS, true, false)) if (!tx.HaveInputs(viewTemp))
continue; continue;
int64 nTxFees = tx.GetValueIn(viewTemp)-tx.GetValueOut(); int64 nTxFees = tx.GetValueIn(viewTemp)-tx.GetValueOut();
@ -3728,6 +3729,9 @@ CBlock* CreateNewBlock(CReserveKey& reservekey)
if (nBlockSigOps + nTxSigOps >= MAX_BLOCK_SIGOPS) if (nBlockSigOps + nTxSigOps >= MAX_BLOCK_SIGOPS)
continue; continue;
if (!tx.CheckInputs(viewTemp, CS_ALWAYS, true, false))
continue;
CTxUndo txundo; CTxUndo txundo;
uint256 hash = tx.GetHash(); uint256 hash = tx.GetHash();
if (!tx.UpdateCoins(viewTemp, txundo, pindexPrev->nHeight+1, hash)) if (!tx.UpdateCoins(viewTemp, txundo, pindexPrev->nHeight+1, hash))

23
src/main.h

@ -88,6 +88,7 @@ class CDiskBlockPos;
class CCoins; class CCoins;
class CTxUndo; class CTxUndo;
class CCoinsView; class CCoinsView;
class CCoinsViewCache;
void RegisterWallet(CWallet* pwalletIn); void RegisterWallet(CWallet* pwalletIn);
void UnregisterWallet(CWallet* pwalletIn); void UnregisterWallet(CWallet* pwalletIn);
@ -480,7 +481,7 @@ public:
@return True if all inputs (scriptSigs) use only standard transaction forms @return True if all inputs (scriptSigs) use only standard transaction forms
@see CTransaction::FetchInputs @see CTransaction::FetchInputs
*/ */
bool AreInputsStandard(CCoinsView& mapInputs) const; bool AreInputsStandard(CCoinsViewCache& mapInputs) const;
/** Count ECDSA signature operations the old-fashioned (pre-0.6) way /** Count ECDSA signature operations the old-fashioned (pre-0.6) way
@return number of sigops this transaction's outputs will produce when spent @return number of sigops this transaction's outputs will produce when spent
@ -494,7 +495,7 @@ public:
@return maximum number of sigops required to validate this transaction's inputs @return maximum number of sigops required to validate this transaction's inputs
@see CTransaction::FetchInputs @see CTransaction::FetchInputs
*/ */
unsigned int GetP2SHSigOpCount(CCoinsView& mapInputs) const; unsigned int GetP2SHSigOpCount(CCoinsViewCache& mapInputs) const;
/** Amount of bitcoins spent by this transaction. /** Amount of bitcoins spent by this transaction.
@return sum of all outputs (note: does not include fees) @return sum of all outputs (note: does not include fees)
@ -519,7 +520,7 @@ public:
@return Sum of value of all inputs (scriptSigs) @return Sum of value of all inputs (scriptSigs)
@see CTransaction::FetchInputs @see CTransaction::FetchInputs
*/ */
int64 GetValueIn(CCoinsView& mapInputs) const; int64 GetValueIn(CCoinsViewCache& mapInputs) const;
static bool AllowFree(double dPriority) static bool AllowFree(double dPriority)
{ {
@ -570,14 +571,14 @@ public:
bool ClientCheckInputs() const; bool ClientCheckInputs() const;
// Check whether all prevouts of this transaction are present in the UTXO set represented by view // Check whether all prevouts of this transaction are present in the UTXO set represented by view
bool HaveInputs(CCoinsView &view) const; bool HaveInputs(CCoinsViewCache &view) const;
// Check whether all inputs of this transaction are valid (no double spends, scripts & sigs, amounts) // Check whether all inputs of this transaction are valid (no double spends, scripts & sigs, amounts)
// This does not modify the UTXO set // This does not modify the UTXO set
bool CheckInputs(CCoinsView &view, enum CheckSig_mode csmode, bool fStrictPayToScriptHash=true, bool fStrictEncodings=true) const; bool CheckInputs(CCoinsViewCache &view, enum CheckSig_mode csmode, bool fStrictPayToScriptHash=true, bool fStrictEncodings=true) const;
// Apply the effects of this transaction on the UTXO set represented by view // Apply the effects of this transaction on the UTXO set represented by view
bool UpdateCoins(CCoinsView &view, CTxUndo &txundo, int nHeight, const uint256 &txhash) const; bool UpdateCoins(CCoinsViewCache &view, CTxUndo &txundo, int nHeight, const uint256 &txhash) const;
// Context-independent validity checks // Context-independent validity checks
bool CheckTransaction() const; bool CheckTransaction() const;
@ -586,7 +587,7 @@ public:
bool AcceptToMemoryPool(bool fCheckInputs=true, bool* pfMissingInputs=NULL); bool AcceptToMemoryPool(bool fCheckInputs=true, bool* pfMissingInputs=NULL);
protected: protected:
static CTxOut GetOutputFor(const CTxIn& input, CCoinsView& mapInputs); static const CTxOut &GetOutputFor(const CTxIn& input, CCoinsViewCache& mapInputs);
}; };
/** wrapper for CTxOut that provides a more compact serialization */ /** wrapper for CTxOut that provides a more compact serialization */
@ -1225,10 +1226,10 @@ public:
// Undo the effects of this block (with given index) on the UTXO set represented by coins // Undo the effects of this block (with given index) on the UTXO set represented by coins
bool DisconnectBlock(CBlockIndex *pindex, CCoinsView &coins); bool DisconnectBlock(CBlockIndex *pindex, CCoinsViewCache &coins);
// Apply the effects of this block (with given index) on the UTXO set represented by coins // Apply the effects of this block (with given index) on the UTXO set represented by coins
bool ConnectBlock(CBlockIndex *pindex, CCoinsView &coins, bool fJustCheck=false); bool ConnectBlock(CBlockIndex *pindex, CCoinsViewCache &coins, bool fJustCheck=false);
// Read a block from disk // Read a block from disk
bool ReadFromDisk(const CBlockIndex* pindex, bool fReadTransactions=true); bool ReadFromDisk(const CBlockIndex* pindex, bool fReadTransactions=true);
@ -1759,11 +1760,15 @@ public:
bool GetCoins(uint256 txid, CCoins &coins); bool GetCoins(uint256 txid, CCoins &coins);
bool SetCoins(uint256 txid, const CCoins &coins); bool SetCoins(uint256 txid, const CCoins &coins);
bool HaveCoins(uint256 txid); bool HaveCoins(uint256 txid);
CCoins &GetCoins(uint256 txid);
CBlockIndex *GetBestBlock(); CBlockIndex *GetBestBlock();
bool SetBestBlock(CBlockIndex *pindex); bool SetBestBlock(CBlockIndex *pindex);
bool BatchWrite(const std::map<uint256, CCoins> &mapCoins, CBlockIndex *pindex); bool BatchWrite(const std::map<uint256, CCoins> &mapCoins, CBlockIndex *pindex);
bool Flush(); bool Flush();
unsigned int GetCacheSize(); unsigned int GetCacheSize();
private:
std::map<uint256,CCoins>::iterator FetchCoins(uint256 txid);
}; };
/** CCoinsView that brings transactions from a memorypool into view. /** CCoinsView that brings transactions from a memorypool into view.

3
src/test/transaction_tests.cpp

@ -256,9 +256,6 @@ BOOST_AUTO_TEST_CASE(test_GetThrow)
t1.vout.resize(2); t1.vout.resize(2);
t1.vout[0].nValue = 90*CENT; t1.vout[0].nValue = 90*CENT;
t1.vout[0].scriptPubKey << OP_1; t1.vout[0].scriptPubKey << OP_1;
BOOST_CHECK_THROW(t1.AreInputsStandard(coinsDummy), runtime_error);
BOOST_CHECK_THROW(t1.GetValueIn(coinsDummy), runtime_error);
} }
BOOST_AUTO_TEST_SUITE_END() BOOST_AUTO_TEST_SUITE_END()

Loading…
Cancel
Save