aboutsummaryrefslogtreecommitdiff
path: root/src/main.cpp
diff options
context:
space:
mode:
authorGavin Andresen <gavinandresen@gmail.com>2012-01-10 20:18:00 -0500
committerGavin Andresen <gavinandresen@gmail.com>2012-01-13 10:22:24 -0500
commit8d7849b6db5f54dc32fe4f8c6c7283068473cd21 (patch)
tree44c3f275be983eaa8ca69c422e77d693fb02b8e8 /src/main.cpp
parent922e8e2929a2e78270868385aa46f96002fbcff3 (diff)
downloadbitcoin-8d7849b6db5f54dc32fe4f8c6c7283068473cd21.tar.xz
Refactored ConnectInputs, so valid-transaction-checks are done before ECDSA-verifying signatures.
Diffstat (limited to 'src/main.cpp')
-rw-r--r--src/main.cpp165
1 files changed, 106 insertions, 59 deletions
diff --git a/src/main.cpp b/src/main.cpp
index 68583361dd..3a43f5eb36 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -275,26 +275,22 @@ bool CTransaction::IsStandard() const
// expensive-to-check-upon-redemption script like:
// DUP CHECKSIG DROP ... repeated 100 times... OP_1
//
-bool CTransaction::AreInputsStandard(const std::map<uint256, std::pair<CTxIndex, CTransaction> >& mapInputs) const
+bool CTransaction::AreInputsStandard(const MapPrevTx& mapInputs) const
{
if (fTestNet)
return true; // Allow non-standard on testnet
+ if (IsCoinBase())
+ return true; // Coinbases are allowed to have any input
+
for (int i = 0; i < vin.size(); i++)
{
- COutPoint prevout = vin[i].prevout;
-
- std::map<uint256, std::pair<CTxIndex, CTransaction> >::const_iterator mi = mapInputs.find(prevout.hash);
- if (mi == mapInputs.end())
- return false;
-
- const CTransaction& txPrev = (mi->second).second;
- assert(prevout.n < txPrev.vout.size());
+ const CTxOut& prev = GetOutputFor(vin[i], mapInputs);
vector<vector<unsigned char> > vSolutions;
txnouttype whichType;
// get the scriptPubKey corresponding to this input:
- const CScript& prevScript = txPrev.vout[prevout.n].scriptPubKey;
+ const CScript& prevScript = prev.scriptPubKey;
if (!Solver(prevScript, whichType, vSolutions))
return false;
if (whichType == TX_SCRIPTHASH)
@@ -494,7 +490,7 @@ bool CTransaction::AcceptToMemoryPool(CTxDB& txdb, bool fCheckInputs, bool* pfMi
if (fCheckInputs)
{
- map<uint256, pair<CTxIndex, CTransaction> > mapInputs;
+ MapPrevTx mapInputs;
map<uint256, CTxIndex> mapUnused;
if (!FetchInputs(txdb, mapUnused, false, false, mapInputs))
{
@@ -507,27 +503,20 @@ bool CTransaction::AcceptToMemoryPool(CTxDB& txdb, bool fCheckInputs, bool* pfMi
if (!AreInputsStandard(mapInputs))
return error("AcceptToMemoryPool() : nonstandard transaction input");
- // Check against previous transactions
- int64 nFees = 0;
- int nSigOps = 0;
- if (!ConnectInputs(mapInputs, mapUnused, CDiskTxPos(1,1,1), pindexBest, nFees, false, false, nSigOps))
- {
- if (pfMissingInputs)
- *pfMissingInputs = true;
- return error("AcceptToMemoryPool() : ConnectInputs failed %s", hash.ToString().substr(0,10).c_str());
- }
+ int64 nFees = GetValueIn(mapInputs)-GetValueOut();
+ int nSigOps = GetSigOpCount(mapInputs);
+ unsigned int nSize = ::GetSerializeSize(*this, SER_NETWORK);
+
+ // Don't accept it if it can't get into a block
+ if (nFees < GetMinFee(1000, true, GMF_RELAY))
+ return error("AcceptToMemoryPool() : not enough fees");
// Checking ECDSA signatures is a CPU bottleneck, so to avoid denial-of-service
// attacks disallow transactions with more than one SigOp per 65 bytes.
// 65 bytes because that is the minimum size of an ECDSA signature
- unsigned int nSize = ::GetSerializeSize(*this, SER_NETWORK);
if (nSigOps > nSize / 65 || nSize < 100)
return error("AcceptToMemoryPool() : transaction with out-of-bounds SigOpCount");
- // Don't accept it if it can't get into a block
- if (nFees < GetMinFee(1000, true, GMF_RELAY))
- return error("AcceptToMemoryPool() : not enough fees");
-
// Continuously rate-limit free transactions
// This mitigates 'penny-flooding' -- sending thousands of free transactions just to
// be annoying or make other's transactions take longer to confirm.
@@ -552,6 +541,15 @@ bool CTransaction::AcceptToMemoryPool(CTxDB& txdb, bool fCheckInputs, bool* pfMi
dFreeCount += nSize;
}
}
+
+ // Check against previous transactions
+ // This is done last to help prevent CPU exhaustion denial-of-service attacks.
+ if (!ConnectInputs(mapInputs, mapUnused, CDiskTxPos(1,1,1), pindexBest, false, false))
+ {
+ if (pfMissingInputs)
+ *pfMissingInputs = true;
+ return error("AcceptToMemoryPool() : ConnectInputs failed %s", hash.ToString().substr(0,10).c_str());
+ }
}
// Store transaction in memory
@@ -925,7 +923,7 @@ bool CTransaction::DisconnectInputs(CTxDB& txdb)
bool CTransaction::FetchInputs(CTxDB& txdb, const map<uint256, CTxIndex>& mapTestPool,
- bool fBlock, bool fMiner, map<uint256, pair<CTxIndex, CTransaction> >& inputsRet)
+ bool fBlock, bool fMiner, MapPrevTx& inputsRet)
{
if (IsCoinBase())
return true; // Coinbase transactions have no inputs to fetch.
@@ -978,6 +976,7 @@ bool CTransaction::FetchInputs(CTxDB& txdb, const map<uint256, CTxIndex>& mapTes
for (int i = 0; i < vin.size(); i++)
{
const COutPoint prevout = vin[i].prevout;
+ assert(inputsRet.count(prevout.hash) != 0);
const CTxIndex& txindex = inputsRet[prevout.hash].first;
const CTransaction& txPrev = inputsRet[prevout.hash].second;
if (prevout.n >= txPrev.vout.size() || prevout.n >= txindex.vSpent.size())
@@ -987,9 +986,49 @@ bool CTransaction::FetchInputs(CTxDB& txdb, const map<uint256, CTxIndex>& mapTes
return true;
}
-bool CTransaction::ConnectInputs(map<uint256, pair<CTxIndex, CTransaction> > inputs,
+const CTxOut& CTransaction::GetOutputFor(const CTxIn& input, const MapPrevTx& inputs) const
+{
+ MapPrevTx::const_iterator mi = inputs.find(input.prevout.hash);
+ if (mi == inputs.end())
+ throw std::runtime_error("CTransaction::GetOutputFor() : prevout.hash not found");
+
+ const CTransaction& txPrev = (mi->second).second;
+ if (input.prevout.n >= txPrev.vout.size())
+ throw std::runtime_error("CTransaction::GetOutputFor() : prevout.n out of range");
+
+ return txPrev.vout[input.prevout.n];
+}
+
+int64 CTransaction::GetValueIn(const MapPrevTx& inputs) const
+{
+ if (IsCoinBase())
+ return 0;
+
+ int64 nResult = 0;
+ for (int i = 0; i < vin.size(); i++)
+ {
+ nResult += GetOutputFor(vin[i], inputs).nValue;
+ }
+ return nResult;
+
+}
+
+int CTransaction::GetSigOpCount(const MapPrevTx& inputs) const
+{
+ if (IsCoinBase())
+ return 0;
+
+ int nSigOps = 0;
+ for (int i = 0; i < vin.size(); i++)
+ {
+ nSigOps += GetOutputFor(vin[i], inputs).scriptPubKey.GetSigOpCount(vin[i].scriptSig);
+ }
+ return nSigOps;
+}
+
+bool CTransaction::ConnectInputs(MapPrevTx inputs,
map<uint256, CTxIndex>& mapTestPool, const CDiskTxPos& posThisTx,
- const CBlockIndex* pindexBlock, int64& nFees, bool fBlock, bool fMiner, int& nSigOpsRet, int64 nMinFee)
+ const CBlockIndex* pindexBlock, bool fBlock, bool fMiner)
{
// Take over previous transactions' spent pointers
// fBlock is true when this is called from AcceptBlock when a new best-block is added to the blockchain
@@ -998,6 +1037,7 @@ bool CTransaction::ConnectInputs(map<uint256, pair<CTxIndex, CTransaction> > inp
if (!IsCoinBase())
{
int64 nValueIn = 0;
+ int64 nFees = 0;
for (int i = 0; i < vin.size(); i++)
{
COutPoint prevout = vin[i].prevout;
@@ -1014,6 +1054,17 @@ bool CTransaction::ConnectInputs(map<uint256, pair<CTxIndex, CTransaction> > inp
if (pindex->nBlockPos == txindex.pos.nBlockPos && pindex->nFile == txindex.pos.nFile)
return error("ConnectInputs() : tried to spend coinbase at depth %d", pindexBlock->nHeight - pindex->nHeight);
+ // 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 (!txindex.vSpent[prevout.n].IsNull())
+ return fMiner ? false : error("ConnectInputs() : %s prev tx already used at %s", GetHash().ToString().substr(0,10).c_str(), txindex.vSpent[prevout.n].ToString().c_str());
+
+ // Check for negative or overflow input values
+ nValueIn += txPrev.vout[prevout.n].nValue;
+ if (!MoneyRange(txPrev.vout[prevout.n].nValue) || !MoneyRange(nValueIn))
+ return DoS(100, error("ConnectInputs() : txin values out of range"));
+
bool fStrictPayToScriptHash = true;
if (fBlock)
{
@@ -1038,20 +1089,6 @@ bool CTransaction::ConnectInputs(map<uint256, pair<CTxIndex, CTransaction> > inp
return DoS(100,error("ConnectInputs() : %s VerifySignature failed", GetHash().ToString().substr(0,10).c_str()));
}
- // 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 (!txindex.vSpent[prevout.n].IsNull())
- return fMiner ? false : error("ConnectInputs() : %s prev tx already used at %s", GetHash().ToString().substr(0,10).c_str(), txindex.vSpent[prevout.n].ToString().c_str());
-
- // Check for negative or overflow input values
- nValueIn += txPrev.vout[prevout.n].nValue;
- if (!MoneyRange(txPrev.vout[prevout.n].nValue) || !MoneyRange(nValueIn))
- return DoS(100, error("ConnectInputs() : txin values out of range"));
-
- // Calculate sigOps accurately:
- nSigOpsRet += txPrev.vout[prevout.n].scriptPubKey.GetSigOpCount(vin[i].scriptSig);
-
// Mark outpoints as spent
txindex.vSpent[prevout.n] = posThisTx;
@@ -1069,8 +1106,6 @@ bool CTransaction::ConnectInputs(map<uint256, pair<CTxIndex, CTransaction> > inp
int64 nTxFee = nValueIn - GetValueOut();
if (nTxFee < 0)
return DoS(100, error("ConnectInputs() : %s nTxFee < 0", GetHash().ToString().substr(0,10).c_str()));
- if (nTxFee < nMinFee)
- return false;
nFees += nTxFee;
if (!MoneyRange(nFees))
return DoS(100, error("ConnectInputs() : nFees out of range"));
@@ -1176,20 +1211,27 @@ bool CBlock::ConnectBlock(CTxDB& txdb, CBlockIndex* pindex)
CDiskTxPos posThisTx(pindex->nFile, pindex->nBlockPos, nTxPos);
nTxPos += ::GetSerializeSize(tx, SER_DISK);
- map<uint256, pair<CTxIndex, CTransaction> > mapInputs;
- if (!tx.FetchInputs(txdb, mapQueuedChanges, true, false, mapInputs))
- return false;
+ MapPrevTx mapInputs;
+ if (!tx.IsCoinBase())
+ {
+ if (!tx.FetchInputs(txdb, mapQueuedChanges, true, false, mapInputs))
+ return false;
- int nTxOps = 0;
- if (!tx.ConnectInputs(mapInputs, mapQueuedChanges, posThisTx, pindex, nFees, true, false, nTxOps))
- return false;
+ int nTxOps = tx.GetSigOpCount(mapInputs);
+ nSigOps += nTxOps;
+ if (nSigOps > MAX_BLOCK_SIGOPS)
+ return DoS(100, error("ConnectBlock() : too many sigops"));
+ // There is a different MAX_BLOCK_SIGOPS check in AcceptBlock();
+ // a block must satisfy both to make it into the best-chain
+ // (AcceptBlock() is always called before ConnectBlock())
- nSigOps += nTxOps;
- if (nSigOps > MAX_BLOCK_SIGOPS)
- return DoS(100, error("ConnectBlock() : too many sigops"));
- // There is a different MAX_BLOCK_SIGOPS check in AcceptBlock();
- // a block must satisfy both to make it into the best-chain
- // (AcceptBlock() is always called before ConnectBlock())
+ nFees += tx.GetValueIn(mapInputs)-tx.GetValueOut();
+ }
+
+ // It seems wrong that ConnectInputs must be called on the coinbase transaction
+ // (which has no inputs) : TODO: refactor the code at the end of ConnectInputs out...
+ if (!tx.ConnectInputs(mapInputs, mapQueuedChanges, posThisTx, pindex, true, false))
+ return false;
}
// Write queued txindex changes
@@ -3031,15 +3073,20 @@ CBlock* CreateNewBlock(CReserveKey& reservekey)
// Connecting shouldn't fail due to dependency on other memory pool transactions
// because we're already processing them in order of dependency
map<uint256, CTxIndex> mapTestPoolTmp(mapTestPool);
- map<uint256, pair<CTxIndex, CTransaction> > mapInputs;
+ MapPrevTx mapInputs;
if (!tx.FetchInputs(txdb, mapTestPoolTmp, false, true, mapInputs))
continue;
- int nTxSigOps2 = 0;
- if (!tx.ConnectInputs(mapInputs, mapTestPoolTmp, CDiskTxPos(1,1,1), pindexPrev, nFees, false, true, nTxSigOps2, nMinFee))
+ int64 nFees = tx.GetValueIn(mapInputs)-tx.GetValueOut();
+ if (nFees < nMinFee)
continue;
+
+ int nTxSigOps2 = tx.GetSigOpCount(mapInputs);
if (nBlockSigOps2 + nTxSigOps2 >= MAX_BLOCK_SIGOPS)
continue;
+
+ if (!tx.ConnectInputs(mapInputs, mapTestPoolTmp, CDiskTxPos(1,1,1), pindexPrev, false, true))
+ continue;
swap(mapTestPool, mapTestPoolTmp);
// Added