aboutsummaryrefslogtreecommitdiff
path: root/src/main.cpp
diff options
context:
space:
mode:
authorLuke Dashjr <luke-jr+git@utopios.org>2012-03-21 13:39:57 -0400
committerLuke Dashjr <luke-jr+git@utopios.org>2012-03-21 13:49:00 -0400
commit2ac8af4534ad026cec5e281852d2e42616d15610 (patch)
treea64210f65e7453d40b66eed290128c8682e7ed33 /src/main.cpp
parent17a5fd9248ce62ca412f7098ca261d4b4271cc99 (diff)
parentcdc6b8d6a6ee4f0bfa721561c340cf8f6b81bc6a (diff)
downloadbitcoin-2ac8af4534ad026cec5e281852d2e42616d15610.tar.xz
Merge branch '0.4.x' into 0.5.0.x
Conflicts: src/main.cpp
Diffstat (limited to 'src/main.cpp')
-rw-r--r--src/main.cpp294
1 files changed, 210 insertions, 84 deletions
diff --git a/src/main.cpp b/src/main.cpp
index 46e367391f..54a039f8cc 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -381,15 +381,6 @@ bool CTransaction::AcceptToMemoryPool(CTxDB& txdb, bool fCheckInputs, bool* pfMi
if ((int64)nLockTime > INT_MAX)
return error("AcceptToMemoryPool() : not accepting nLockTime beyond 2038 yet");
- // Safety limits
- unsigned int nSize = ::GetSerializeSize(*this, SER_NETWORK);
- // Checking ECDSA signatures is a CPU bottleneck, so to avoid denial-of-service
- // attacks disallow transactions with more than one SigOp per 34 bytes.
- // 34 bytes because a TxOut is:
- // 20-byte address + 8 byte bitcoin amount + 5 bytes of ops + 1 byte script length
- if (GetSigOpCount() > nSize / 34 || nSize < 100)
- return error("AcceptToMemoryPool() : transaction with out-of-bounds SigOpCount");
-
// Rather not work on nonstandard transactions (unless -testnet)
if (!fTestNet && !IsStandard())
return error("AcceptToMemoryPool() : nonstandard transaction type");
@@ -433,17 +424,29 @@ bool CTransaction::AcceptToMemoryPool(CTxDB& txdb, bool fCheckInputs, bool* pfMi
if (fCheckInputs)
{
- // Check against previous transactions
+ MapPrevTx mapInputs;
map<uint256, CTxIndex> mapUnused;
- int64 nFees = 0;
bool fInvalid = false;
- if (!ConnectInputs(txdb, mapUnused, CDiskTxPos(1,1,1), pindexBest, nFees, false, false, 0, fInvalid))
+ if (!FetchInputs(txdb, mapUnused, false, false, mapInputs, fInvalid))
{
if (fInvalid)
return error("AcceptToMemoryPool() : FetchInputs found invalid tx %s", hash.ToString().substr(0,10).c_str());
- return error("AcceptToMemoryPool() : ConnectInputs failed %s", hash.ToString().substr(0,10).c_str());
+ if (pfMissingInputs)
+ *pfMissingInputs = true;
+ return error("AcceptToMemoryPool() : FetchInputs failed %s", hash.ToString().substr(0,10).c_str());
}
+ // Safety limits
+ unsigned int nSize = ::GetSerializeSize(*this, SER_NETWORK);
+ // Checking ECDSA signatures is a CPU bottleneck, so to avoid denial-of-service
+ // attacks disallow transactions with more than one SigOp per 34 bytes.
+ // 34 bytes because a TxOut is:
+ // 20-byte address + 8 byte bitcoin amount + 5 bytes of ops + 1 byte script length
+ if (GetSigOpCount() > nSize / 34 || nSize < 100)
+ return error("AcceptToMemoryPool() : transaction with out-of-bounds SigOpCount");
+
+ int64 nFees = GetValueIn(mapInputs)-GetValueOut();
+
// Don't accept it if it can't get into a block
if (nFees < GetMinFee(1000, true, true))
return error("AcceptToMemoryPool() : not enough fees");
@@ -472,6 +475,13 @@ 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))
+ {
+ return error("AcceptToMemoryPool() : ConnectInputs failed %s", hash.ToString().substr(0,10).c_str());
+ }
}
// Store transaction in memory
@@ -875,9 +885,8 @@ bool CTransaction::DisconnectInputs(CTxDB& txdb)
}
-bool CTransaction::ConnectInputs(CTxDB& txdb, map<uint256, CTxIndex>& mapTestPool, CDiskTxPos posThisTx,
- CBlockIndex* pindexBlock, int64& nFees, bool fBlock, bool fMiner, int64 nMinFee,
- bool& fInvalid)
+bool CTransaction::FetchInputs(CTxDB& txdb, const map<uint256, CTxIndex>& mapTestPool,
+ bool fBlock, bool fMiner, MapPrevTx& inputsRet, bool& fInvalid)
{
// FetchInputs can return false either because we just haven't seen some inputs
// (in which case the transaction should be stored as an orphan)
@@ -885,6 +894,118 @@ bool CTransaction::ConnectInputs(CTxDB& txdb, map<uint256, CTxIndex>& mapTestPoo
// be dropped). If tx is definitely invalid, fInvalid will be set to true.
fInvalid = false;
+ if (IsCoinBase())
+ return true; // Coinbase transactions have no inputs to fetch.
+
+ for (int i = 0; i < vin.size(); i++)
+ {
+ COutPoint prevout = vin[i].prevout;
+ if (inputsRet.count(prevout.hash))
+ continue; // Got it already
+
+ // Read txindex
+ CTxIndex& txindex = inputsRet[prevout.hash].first;
+ bool fFound = true;
+ if ((fBlock || fMiner) && mapTestPool.count(prevout.hash))
+ {
+ // Get txindex from current proposed changes
+ txindex = mapTestPool.find(prevout.hash)->second;
+ }
+ else
+ {
+ // Read txindex from txdb
+ fFound = txdb.ReadTxIndex(prevout.hash, txindex);
+ }
+ if (!fFound && (fBlock || fMiner))
+ return fMiner ? false : error("FetchInputs() : %s prev tx %s index entry not found", GetHash().ToString().substr(0,10).c_str(), prevout.hash.ToString().substr(0,10).c_str());
+
+ // Read txPrev
+ CTransaction& txPrev = inputsRet[prevout.hash].second;
+ if (!fFound || txindex.pos == CDiskTxPos(1,1,1))
+ {
+ // Get prev tx from single transactions in memory
+ CRITICAL_BLOCK(cs_mapTransactions)
+ {
+ if (!mapTransactions.count(prevout.hash))
+ return error("FetchInputs() : %s mapTransactions prev not found %s", GetHash().ToString().substr(0,10).c_str(), prevout.hash.ToString().substr(0,10).c_str());
+ txPrev = mapTransactions[prevout.hash];
+ }
+ if (!fFound)
+ txindex.vSpent.resize(txPrev.vout.size());
+ }
+ else
+ {
+ // Get prev tx from disk
+ if (!txPrev.ReadFromDisk(txindex.pos))
+ return error("FetchInputs() : %s ReadFromDisk prev tx %s failed", GetHash().ToString().substr(0,10).c_str(), prevout.hash.ToString().substr(0,10).c_str());
+ }
+ }
+
+ // Make sure all prevout.n's are valid:
+ 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())
+ {
+ // Revisit this if/when transaction replacement is implemented and allows
+ // adding inputs:
+ fInvalid = true;
+ return DoS(100, error("FetchInputs() : %s prevout.n out of range %d %d %d prev tx %s\n%s", GetHash().ToString().substr(0,10).c_str(), prevout.n, txPrev.vout.size(), txindex.vSpent.size(), prevout.hash.ToString().substr(0,10).c_str(), txPrev.ToString().c_str()));
+ }
+ }
+
+ return true;
+}
+
+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::GetP2SHSigOpCount(const MapPrevTx& inputs) const
+{
+ if (IsCoinBase())
+ return 0;
+
+ int nSigOps = 0;
+ for (int i = 0; i < vin.size(); i++)
+ {
+ const CTxOut& prevout = GetOutputFor(vin[i], inputs);
+ if (prevout.scriptPubKey.IsPayToScriptHash())
+ nSigOps += prevout.scriptPubKey.GetSigOpCount(vin[i].scriptSig);
+ }
+ return nSigOps;
+}
+
+bool CTransaction::ConnectInputs(MapPrevTx inputs,
+ map<uint256, CTxIndex>& mapTestPool, const CDiskTxPos& posThisTx,
+ const CBlockIndex* pindexBlock, bool fBlock, bool fMiner, bool fStrictPayToScriptHash)
+{
// 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
// fMiner is true when called from the internal bitcoin miner
@@ -892,69 +1013,23 @@ bool CTransaction::ConnectInputs(CTxDB& txdb, map<uint256, CTxIndex>& mapTestPoo
if (!IsCoinBase())
{
int64 nValueIn = 0;
+ int64 nFees = 0;
for (int i = 0; i < vin.size(); i++)
{
COutPoint prevout = vin[i].prevout;
-
- // Read txindex
- CTxIndex txindex;
- bool fFound = true;
- if ((fBlock || fMiner) && mapTestPool.count(prevout.hash))
- {
- // Get txindex from current proposed changes
- txindex = mapTestPool[prevout.hash];
- }
- else
- {
- // Read txindex from txdb
- fFound = txdb.ReadTxIndex(prevout.hash, txindex);
- }
- if (!fFound && (fBlock || fMiner))
- return fMiner ? false : error("ConnectInputs() : %s prev tx %s index entry not found", GetHash().ToString().substr(0,10).c_str(), prevout.hash.ToString().substr(0,10).c_str());
-
- // Read txPrev
- CTransaction txPrev;
- if (!fFound || txindex.pos == CDiskTxPos(1,1,1))
- {
- // Get prev tx from single transactions in memory
- CRITICAL_BLOCK(cs_mapTransactions)
- {
- if (!mapTransactions.count(prevout.hash))
- return error("ConnectInputs() : %s mapTransactions prev not found %s", GetHash().ToString().substr(0,10).c_str(), prevout.hash.ToString().substr(0,10).c_str());
- txPrev = mapTransactions[prevout.hash];
- }
- if (!fFound)
- txindex.vSpent.resize(txPrev.vout.size());
- }
- else
- {
- // Get prev tx from disk
- if (!txPrev.ReadFromDisk(txindex.pos))
- return error("ConnectInputs() : %s ReadFromDisk prev tx %s failed", GetHash().ToString().substr(0,10).c_str(), prevout.hash.ToString().substr(0,10).c_str());
- }
+ assert(inputs.count(prevout.hash) > 0);
+ CTxIndex& txindex = inputs[prevout.hash].first;
+ CTransaction& txPrev = inputs[prevout.hash].second;
if (prevout.n >= txPrev.vout.size() || prevout.n >= txindex.vSpent.size())
- {
- // Revisit this if/when transaction replacement is implemented and allows
- // adding inputs:
- fInvalid = true;
return DoS(100, error("ConnectInputs() : %s prevout.n out of range %d %d %d prev tx %s\n%s", GetHash().ToString().substr(0,10).c_str(), prevout.n, txPrev.vout.size(), txindex.vSpent.size(), prevout.hash.ToString().substr(0,10).c_str(), txPrev.ToString().c_str()));
- }
// If prev is coinbase, check that it's matured
if (txPrev.IsCoinBase())
- for (CBlockIndex* pindex = pindexBlock; pindex && pindexBlock->nHeight - pindex->nHeight < COINBASE_MATURITY; pindex = pindex->pprev)
+ for (const CBlockIndex* pindex = pindexBlock; pindex && pindexBlock->nHeight - pindex->nHeight < COINBASE_MATURITY; pindex = pindex->pprev)
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);
- // Skip ECDSA signature verification when connecting blocks (fBlock=true)
- // before the last blockchain checkpoint. This is safe because block merkle hashes are
- // still computed and checked, and any change will be caught at the next checkpoint.
- if (!(fBlock && (nBestHeight < Checkpoints::GetTotalBlocksEstimate())))
- // Verify signature
- if (!VerifySignature(txPrev, *this, i))
- 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.
@@ -966,6 +1041,23 @@ bool CTransaction::ConnectInputs(CTxDB& txdb, map<uint256, CTxIndex>& mapTestPoo
if (!MoneyRange(txPrev.vout[prevout.n].nValue) || !MoneyRange(nValueIn))
return DoS(100, error("ConnectInputs() : txin values out of range"));
+ // Skip ECDSA signature verification when connecting blocks (fBlock=true)
+ // before the last blockchain checkpoint. This is safe because block merkle hashes are
+ // still computed and checked, and any change will be caught at the next checkpoint.
+ if (!(fBlock && (nBestHeight < Checkpoints::GetTotalBlocksEstimate())))
+ {
+ // Verify signature
+ if (!VerifySignature(txPrev, *this, i, fStrictPayToScriptHash, 0))
+ {
+ // only during transition phase for P2SH: do not invoke anti-DoS code for
+ // potentially old clients relaying bad P2SH transactions
+ if (fStrictPayToScriptHash && VerifySignature(txPrev, *this, i, false, 0))
+ return error("ConnectInputs() : %s P2SH VerifySignature failed", GetHash().ToString().substr(0,10).c_str());
+
+ return DoS(100,error("ConnectInputs() : %s VerifySignature failed", GetHash().ToString().substr(0,10).c_str()));
+ }
+ }
+
// Mark outpoints as spent
txindex.vSpent[prevout.n] = posThisTx;
@@ -983,24 +1075,11 @@ bool CTransaction::ConnectInputs(CTxDB& txdb, map<uint256, CTxIndex>& mapTestPoo
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"));
}
- if (fBlock)
- {
- // Add transaction to changes
- mapTestPool[GetHash()] = CTxIndex(posThisTx, vout.size());
- }
- else if (fMiner)
- {
- // Add transaction to test pool
- mapTestPool[GetHash()] = CTxIndex(CDiskTxPos(1,1,1), vout.size());
- }
-
return true;
}
@@ -1026,7 +1105,7 @@ bool CTransaction::ClientConnectInputs()
return false;
// Verify signature
- if (!VerifySignature(txPrev, *this, i))
+ if (!VerifySignature(txPrev, *this, i, true, 0))
return error("ConnectInputs() : VerifySignature failed");
///// this is redundant with the mapNextTx stuff, not sure which I want to get rid of
@@ -1099,20 +1178,51 @@ bool CBlock::ConnectBlock(CTxDB& txdb, CBlockIndex* pindex)
return false;
}
+ // P2SH didn't become active until Apr 1 2012 (Feb 15 on testnet)
+ int64 nEvalSwitchTime = fTestNet ? 1329264000 : 1333238400;
+ bool fStrictPayToScriptHash = (pindex->nTime >= nEvalSwitchTime);
+
//// issue here: it doesn't know the version
unsigned int nTxPos = pindex->nBlockPos + ::GetSerializeSize(CBlock(), SER_DISK) - 1 + GetSizeOfCompactSize(vtx.size());
map<uint256, CTxIndex> mapQueuedChanges;
int64 nFees = 0;
+ int nSigOps = 0;
BOOST_FOREACH(CTransaction& tx, vtx)
{
+ nSigOps += tx.GetSigOpCount();
+ if (nSigOps > MAX_BLOCK_SIGOPS)
+ return DoS(100, error("ConnectBlock() : too many sigops"));
+
CDiskTxPos posThisTx(pindex->nFile, pindex->nBlockPos, nTxPos);
nTxPos += ::GetSerializeSize(tx, SER_DISK);
bool fInvalid;
- if (!tx.ConnectInputs(txdb, mapQueuedChanges, posThisTx, pindex, nFees, true, false, 0, fInvalid))
- return false;
+ MapPrevTx mapInputs;
+ if (!tx.IsCoinBase())
+ {
+ if (!tx.FetchInputs(txdb, mapQueuedChanges, true, false, mapInputs, fInvalid))
+ return false;
+
+ if (fStrictPayToScriptHash)
+ {
+ // Add in sigops done by pay-to-script-hash inputs;
+ // this is to prevent a "rogue miner" from creating
+ // an incredibly-expensive-to-validate block.
+ nSigOps += tx.GetP2SHSigOpCount(mapInputs);
+ if (nSigOps > MAX_BLOCK_SIGOPS)
+ return DoS(100, error("ConnectBlock() : too many sigops"));
+ }
+
+ nFees += tx.GetValueIn(mapInputs)-tx.GetValueOut();
+
+ if (!tx.ConnectInputs(mapInputs, mapQueuedChanges, posThisTx, pindex, true, false, fStrictPayToScriptHash))
+ return false;
+ }
+
+ mapQueuedChanges[tx.GetHash()] = CTxIndex(posThisTx, tx.vout.size());
}
+
// Write queued txindex changes
for (map<uint256, CTxIndex>::iterator mi = mapQueuedChanges.begin(); mi != mapQueuedChanges.end(); ++mi)
{
@@ -2916,6 +3026,8 @@ CBlock* CreateNewBlock(CReserveKey& reservekey)
unsigned int nTxSize = ::GetSerializeSize(tx, SER_NETWORK);
if (nBlockSize + nTxSize >= MAX_BLOCK_SIZE_GEN)
continue;
+
+ // Legacy limits on sigOps:
int nTxSigOps = tx.GetSigOpCount();
if (nBlockSigOps + nTxSigOps >= MAX_BLOCK_SIGOPS)
continue;
@@ -2928,14 +3040,28 @@ CBlock* CreateNewBlock(CReserveKey& reservekey)
// because we're already processing them in order of dependency
map<uint256, CTxIndex> mapTestPoolTmp(mapTestPool);
bool fInvalid;
- if (!tx.ConnectInputs(txdb, mapTestPoolTmp, CDiskTxPos(1,1,1), pindexPrev, nFees, false, true, nMinFee, fInvalid))
+ MapPrevTx mapInputs;
+ if (!tx.FetchInputs(txdb, mapTestPoolTmp, false, true, mapInputs, fInvalid))
+ continue;
+
+ int64 nTxFees = tx.GetValueIn(mapInputs)-tx.GetValueOut();
+ if (nTxFees < nMinFee)
+ continue;
+
+ nTxSigOps += tx.GetP2SHSigOpCount(mapInputs);
+ if (nBlockSigOps + nTxSigOps >= MAX_BLOCK_SIGOPS)
+ continue;
+
+ if (!tx.ConnectInputs(mapInputs, mapTestPoolTmp, CDiskTxPos(1,1,1), pindexPrev, false, true))
continue;
+ mapTestPoolTmp[tx.GetHash()] = CTxIndex(CDiskTxPos(1,1,1), tx.vout.size());
swap(mapTestPool, mapTestPoolTmp);
// Added
pblock->vtx.push_back(tx);
nBlockSize += nTxSize;
nBlockSigOps += nTxSigOps;
+ nFees += nTxFees;
// Add transactions that depend on this one to the priority queue
uint256 hash = tx.GetHash();