Skip to content

Commit 8d7849b

Browse files
committed
Refactored ConnectInputs, so valid-transaction-checks are done before ECDSA-verifying signatures.
1 parent 922e8e2 commit 8d7849b

File tree

3 files changed

+255
-64
lines changed

3 files changed

+255
-64
lines changed

src/main.cpp

Lines changed: 106 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -275,26 +275,22 @@ bool CTransaction::IsStandard() const
275275
// expensive-to-check-upon-redemption script like:
276276
// DUP CHECKSIG DROP ... repeated 100 times... OP_1
277277
//
278-
bool CTransaction::AreInputsStandard(const std::map<uint256, std::pair<CTxIndex, CTransaction> >& mapInputs) const
278+
bool CTransaction::AreInputsStandard(const MapPrevTx& mapInputs) const
279279
{
280280
if (fTestNet)
281281
return true; // Allow non-standard on testnet
282282

283+
if (IsCoinBase())
284+
return true; // Coinbases are allowed to have any input
285+
283286
for (int i = 0; i < vin.size(); i++)
284287
{
285-
COutPoint prevout = vin[i].prevout;
286-
287-
std::map<uint256, std::pair<CTxIndex, CTransaction> >::const_iterator mi = mapInputs.find(prevout.hash);
288-
if (mi == mapInputs.end())
289-
return false;
290-
291-
const CTransaction& txPrev = (mi->second).second;
292-
assert(prevout.n < txPrev.vout.size());
288+
const CTxOut& prev = GetOutputFor(vin[i], mapInputs);
293289

294290
vector<vector<unsigned char> > vSolutions;
295291
txnouttype whichType;
296292
// get the scriptPubKey corresponding to this input:
297-
const CScript& prevScript = txPrev.vout[prevout.n].scriptPubKey;
293+
const CScript& prevScript = prev.scriptPubKey;
298294
if (!Solver(prevScript, whichType, vSolutions))
299295
return false;
300296
if (whichType == TX_SCRIPTHASH)
@@ -494,7 +490,7 @@ bool CTransaction::AcceptToMemoryPool(CTxDB& txdb, bool fCheckInputs, bool* pfMi
494490

495491
if (fCheckInputs)
496492
{
497-
map<uint256, pair<CTxIndex, CTransaction> > mapInputs;
493+
MapPrevTx mapInputs;
498494
map<uint256, CTxIndex> mapUnused;
499495
if (!FetchInputs(txdb, mapUnused, false, false, mapInputs))
500496
{
@@ -507,27 +503,20 @@ bool CTransaction::AcceptToMemoryPool(CTxDB& txdb, bool fCheckInputs, bool* pfMi
507503
if (!AreInputsStandard(mapInputs))
508504
return error("AcceptToMemoryPool() : nonstandard transaction input");
509505

510-
// Check against previous transactions
511-
int64 nFees = 0;
512-
int nSigOps = 0;
513-
if (!ConnectInputs(mapInputs, mapUnused, CDiskTxPos(1,1,1), pindexBest, nFees, false, false, nSigOps))
514-
{
515-
if (pfMissingInputs)
516-
*pfMissingInputs = true;
517-
return error("AcceptToMemoryPool() : ConnectInputs failed %s", hash.ToString().substr(0,10).c_str());
518-
}
506+
int64 nFees = GetValueIn(mapInputs)-GetValueOut();
507+
int nSigOps = GetSigOpCount(mapInputs);
508+
unsigned int nSize = ::GetSerializeSize(*this, SER_NETWORK);
509+
510+
// Don't accept it if it can't get into a block
511+
if (nFees < GetMinFee(1000, true, GMF_RELAY))
512+
return error("AcceptToMemoryPool() : not enough fees");
519513

520514
// Checking ECDSA signatures is a CPU bottleneck, so to avoid denial-of-service
521515
// attacks disallow transactions with more than one SigOp per 65 bytes.
522516
// 65 bytes because that is the minimum size of an ECDSA signature
523-
unsigned int nSize = ::GetSerializeSize(*this, SER_NETWORK);
524517
if (nSigOps > nSize / 65 || nSize < 100)
525518
return error("AcceptToMemoryPool() : transaction with out-of-bounds SigOpCount");
526519

527-
// Don't accept it if it can't get into a block
528-
if (nFees < GetMinFee(1000, true, GMF_RELAY))
529-
return error("AcceptToMemoryPool() : not enough fees");
530-
531520
// Continuously rate-limit free transactions
532521
// This mitigates 'penny-flooding' -- sending thousands of free transactions just to
533522
// be annoying or make other's transactions take longer to confirm.
@@ -552,6 +541,15 @@ bool CTransaction::AcceptToMemoryPool(CTxDB& txdb, bool fCheckInputs, bool* pfMi
552541
dFreeCount += nSize;
553542
}
554543
}
544+
545+
// Check against previous transactions
546+
// This is done last to help prevent CPU exhaustion denial-of-service attacks.
547+
if (!ConnectInputs(mapInputs, mapUnused, CDiskTxPos(1,1,1), pindexBest, false, false))
548+
{
549+
if (pfMissingInputs)
550+
*pfMissingInputs = true;
551+
return error("AcceptToMemoryPool() : ConnectInputs failed %s", hash.ToString().substr(0,10).c_str());
552+
}
555553
}
556554

557555
// Store transaction in memory
@@ -925,7 +923,7 @@ bool CTransaction::DisconnectInputs(CTxDB& txdb)
925923

926924

927925
bool CTransaction::FetchInputs(CTxDB& txdb, const map<uint256, CTxIndex>& mapTestPool,
928-
bool fBlock, bool fMiner, map<uint256, pair<CTxIndex, CTransaction> >& inputsRet)
926+
bool fBlock, bool fMiner, MapPrevTx& inputsRet)
929927
{
930928
if (IsCoinBase())
931929
return true; // Coinbase transactions have no inputs to fetch.
@@ -978,6 +976,7 @@ bool CTransaction::FetchInputs(CTxDB& txdb, const map<uint256, CTxIndex>& mapTes
978976
for (int i = 0; i < vin.size(); i++)
979977
{
980978
const COutPoint prevout = vin[i].prevout;
979+
assert(inputsRet.count(prevout.hash) != 0);
981980
const CTxIndex& txindex = inputsRet[prevout.hash].first;
982981
const CTransaction& txPrev = inputsRet[prevout.hash].second;
983982
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
987986
return true;
988987
}
989988

990-
bool CTransaction::ConnectInputs(map<uint256, pair<CTxIndex, CTransaction> > inputs,
989+
const CTxOut& CTransaction::GetOutputFor(const CTxIn& input, const MapPrevTx& inputs) const
990+
{
991+
MapPrevTx::const_iterator mi = inputs.find(input.prevout.hash);
992+
if (mi == inputs.end())
993+
throw std::runtime_error("CTransaction::GetOutputFor() : prevout.hash not found");
994+
995+
const CTransaction& txPrev = (mi->second).second;
996+
if (input.prevout.n >= txPrev.vout.size())
997+
throw std::runtime_error("CTransaction::GetOutputFor() : prevout.n out of range");
998+
999+
return txPrev.vout[input.prevout.n];
1000+
}
1001+
1002+
int64 CTransaction::GetValueIn(const MapPrevTx& inputs) const
1003+
{
1004+
if (IsCoinBase())
1005+
return 0;
1006+
1007+
int64 nResult = 0;
1008+
for (int i = 0; i < vin.size(); i++)
1009+
{
1010+
nResult += GetOutputFor(vin[i], inputs).nValue;
1011+
}
1012+
return nResult;
1013+
1014+
}
1015+
1016+
int CTransaction::GetSigOpCount(const MapPrevTx& inputs) const
1017+
{
1018+
if (IsCoinBase())
1019+
return 0;
1020+
1021+
int nSigOps = 0;
1022+
for (int i = 0; i < vin.size(); i++)
1023+
{
1024+
nSigOps += GetOutputFor(vin[i], inputs).scriptPubKey.GetSigOpCount(vin[i].scriptSig);
1025+
}
1026+
return nSigOps;
1027+
}
1028+
1029+
bool CTransaction::ConnectInputs(MapPrevTx inputs,
9911030
map<uint256, CTxIndex>& mapTestPool, const CDiskTxPos& posThisTx,
992-
const CBlockIndex* pindexBlock, int64& nFees, bool fBlock, bool fMiner, int& nSigOpsRet, int64 nMinFee)
1031+
const CBlockIndex* pindexBlock, bool fBlock, bool fMiner)
9931032
{
9941033
// Take over previous transactions' spent pointers
9951034
// 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
9981037
if (!IsCoinBase())
9991038
{
10001039
int64 nValueIn = 0;
1040+
int64 nFees = 0;
10011041
for (int i = 0; i < vin.size(); i++)
10021042
{
10031043
COutPoint prevout = vin[i].prevout;
@@ -1014,6 +1054,17 @@ bool CTransaction::ConnectInputs(map<uint256, pair<CTxIndex, CTransaction> > inp
10141054
if (pindex->nBlockPos == txindex.pos.nBlockPos && pindex->nFile == txindex.pos.nFile)
10151055
return error("ConnectInputs() : tried to spend coinbase at depth %d", pindexBlock->nHeight - pindex->nHeight);
10161056

1057+
// Check for conflicts (double-spend)
1058+
// This doesn't trigger the DoS code on purpose; if it did, it would make it easier
1059+
// for an attacker to attempt to split the network.
1060+
if (!txindex.vSpent[prevout.n].IsNull())
1061+
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());
1062+
1063+
// Check for negative or overflow input values
1064+
nValueIn += txPrev.vout[prevout.n].nValue;
1065+
if (!MoneyRange(txPrev.vout[prevout.n].nValue) || !MoneyRange(nValueIn))
1066+
return DoS(100, error("ConnectInputs() : txin values out of range"));
1067+
10171068
bool fStrictPayToScriptHash = true;
10181069
if (fBlock)
10191070
{
@@ -1038,20 +1089,6 @@ bool CTransaction::ConnectInputs(map<uint256, pair<CTxIndex, CTransaction> > inp
10381089
return DoS(100,error("ConnectInputs() : %s VerifySignature failed", GetHash().ToString().substr(0,10).c_str()));
10391090
}
10401091

1041-
// Check for conflicts (double-spend)
1042-
// This doesn't trigger the DoS code on purpose; if it did, it would make it easier
1043-
// for an attacker to attempt to split the network.
1044-
if (!txindex.vSpent[prevout.n].IsNull())
1045-
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());
1046-
1047-
// Check for negative or overflow input values
1048-
nValueIn += txPrev.vout[prevout.n].nValue;
1049-
if (!MoneyRange(txPrev.vout[prevout.n].nValue) || !MoneyRange(nValueIn))
1050-
return DoS(100, error("ConnectInputs() : txin values out of range"));
1051-
1052-
// Calculate sigOps accurately:
1053-
nSigOpsRet += txPrev.vout[prevout.n].scriptPubKey.GetSigOpCount(vin[i].scriptSig);
1054-
10551092
// Mark outpoints as spent
10561093
txindex.vSpent[prevout.n] = posThisTx;
10571094

@@ -1069,8 +1106,6 @@ bool CTransaction::ConnectInputs(map<uint256, pair<CTxIndex, CTransaction> > inp
10691106
int64 nTxFee = nValueIn - GetValueOut();
10701107
if (nTxFee < 0)
10711108
return DoS(100, error("ConnectInputs() : %s nTxFee < 0", GetHash().ToString().substr(0,10).c_str()));
1072-
if (nTxFee < nMinFee)
1073-
return false;
10741109
nFees += nTxFee;
10751110
if (!MoneyRange(nFees))
10761111
return DoS(100, error("ConnectInputs() : nFees out of range"));
@@ -1176,20 +1211,27 @@ bool CBlock::ConnectBlock(CTxDB& txdb, CBlockIndex* pindex)
11761211
CDiskTxPos posThisTx(pindex->nFile, pindex->nBlockPos, nTxPos);
11771212
nTxPos += ::GetSerializeSize(tx, SER_DISK);
11781213

1179-
map<uint256, pair<CTxIndex, CTransaction> > mapInputs;
1180-
if (!tx.FetchInputs(txdb, mapQueuedChanges, true, false, mapInputs))
1181-
return false;
1214+
MapPrevTx mapInputs;
1215+
if (!tx.IsCoinBase())
1216+
{
1217+
if (!tx.FetchInputs(txdb, mapQueuedChanges, true, false, mapInputs))
1218+
return false;
11821219

1183-
int nTxOps = 0;
1184-
if (!tx.ConnectInputs(mapInputs, mapQueuedChanges, posThisTx, pindex, nFees, true, false, nTxOps))
1185-
return false;
1220+
int nTxOps = tx.GetSigOpCount(mapInputs);
1221+
nSigOps += nTxOps;
1222+
if (nSigOps > MAX_BLOCK_SIGOPS)
1223+
return DoS(100, error("ConnectBlock() : too many sigops"));
1224+
// There is a different MAX_BLOCK_SIGOPS check in AcceptBlock();
1225+
// a block must satisfy both to make it into the best-chain
1226+
// (AcceptBlock() is always called before ConnectBlock())
11861227

1187-
nSigOps += nTxOps;
1188-
if (nSigOps > MAX_BLOCK_SIGOPS)
1189-
return DoS(100, error("ConnectBlock() : too many sigops"));
1190-
// There is a different MAX_BLOCK_SIGOPS check in AcceptBlock();
1191-
// a block must satisfy both to make it into the best-chain
1192-
// (AcceptBlock() is always called before ConnectBlock())
1228+
nFees += tx.GetValueIn(mapInputs)-tx.GetValueOut();
1229+
}
1230+
1231+
// It seems wrong that ConnectInputs must be called on the coinbase transaction
1232+
// (which has no inputs) : TODO: refactor the code at the end of ConnectInputs out...
1233+
if (!tx.ConnectInputs(mapInputs, mapQueuedChanges, posThisTx, pindex, true, false))
1234+
return false;
11931235
}
11941236

11951237
// Write queued txindex changes
@@ -3031,15 +3073,20 @@ CBlock* CreateNewBlock(CReserveKey& reservekey)
30313073
// Connecting shouldn't fail due to dependency on other memory pool transactions
30323074
// because we're already processing them in order of dependency
30333075
map<uint256, CTxIndex> mapTestPoolTmp(mapTestPool);
3034-
map<uint256, pair<CTxIndex, CTransaction> > mapInputs;
3076+
MapPrevTx mapInputs;
30353077
if (!tx.FetchInputs(txdb, mapTestPoolTmp, false, true, mapInputs))
30363078
continue;
30373079

3038-
int nTxSigOps2 = 0;
3039-
if (!tx.ConnectInputs(mapInputs, mapTestPoolTmp, CDiskTxPos(1,1,1), pindexPrev, nFees, false, true, nTxSigOps2, nMinFee))
3080+
int64 nFees = tx.GetValueIn(mapInputs)-tx.GetValueOut();
3081+
if (nFees < nMinFee)
30403082
continue;
3083+
3084+
int nTxSigOps2 = tx.GetSigOpCount(mapInputs);
30413085
if (nBlockSigOps2 + nTxSigOps2 >= MAX_BLOCK_SIGOPS)
30423086
continue;
3087+
3088+
if (!tx.ConnectInputs(mapInputs, mapTestPoolTmp, CDiskTxPos(1,1,1), pindexPrev, false, true))
3089+
continue;
30433090
swap(mapTestPool, mapTestPoolTmp);
30443091

30453092
// Added

src/main.h

Lines changed: 64 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -402,6 +402,8 @@ enum GetMinFee_mode
402402
GMF_SEND,
403403
};
404404

405+
typedef std::map<uint256, std::pair<CTxIndex, CTransaction> > MapPrevTx;
406+
405407
//
406408
// The basic transaction that is broadcasted on the network and contained in
407409
// blocks. A transaction can contain multiple inputs and outputs.
@@ -502,11 +504,36 @@ class CTransaction
502504
return (vin.size() == 1 && vin[0].prevout.IsNull());
503505
}
504506

507+
/** Check for standard transaction types
508+
@return True if all outputs (scriptPubKeys) use only standard transaction forms
509+
*/
505510
bool IsStandard() const;
506-
bool AreInputsStandard(const std::map<uint256, std::pair<CTxIndex, CTransaction> >& mapInputs) const;
507511

512+
/** Check for standard transaction types
513+
@param[in] mapInputs Map of previous transactions that have outputs we're spending
514+
@return True if all inputs (scriptSigs) use only standard transaction forms
515+
@see CTransaction::FetchInputs
516+
*/
517+
bool AreInputsStandard(const MapPrevTx& mapInputs) const;
518+
519+
/** Count ECDSA signature operations the old-fashioned (pre-0.6) way
520+
@return number of sigops this transaction's outputs will produce when spent
521+
@see CTransaction::FetchInputs
522+
*/
508523
int GetLegacySigOpCount() const;
509524

525+
/** Count ECDSA signature operations the new (0.6-and-later) way
526+
This is a better measure of how expensive it is to process this transaction.
527+
528+
@param[in] mapInputs Map of previous transactions that have outputs we're spending
529+
@return maximum number of sigops required to validate this transaction's inputs
530+
@see CTransaction::FetchInputs
531+
*/
532+
int GetSigOpCount(const MapPrevTx& mapInputs) const;
533+
534+
/** Amount of bitcoins spent by this transaction.
535+
@return sum of all outputs (note: does not include fees)
536+
*/
510537
int64 GetValueOut() const
511538
{
512539
int64 nValueOut = 0;
@@ -519,6 +546,16 @@ class CTransaction
519546
return nValueOut;
520547
}
521548

549+
/** Amount of bitcoins coming in to this transaction
550+
Note that lightweight clients may not know anything besides the hash of previous transactions,
551+
so may not be able to calculate this.
552+
553+
@param[in] mapInputs Map of previous transactions that have outputs we're spending
554+
@return Sum of value of all inputs (scriptSigs)
555+
@see CTransaction::FetchInputs
556+
*/
557+
int64 GetValueIn(const MapPrevTx& mapInputs) const;
558+
522559
static bool AllowFree(double dPriority)
523560
{
524561
// Large (in bytes) low-priority (new, small-coin) transactions
@@ -634,17 +671,39 @@ class CTransaction
634671
bool ReadFromDisk(COutPoint prevout);
635672
bool DisconnectInputs(CTxDB& txdb);
636673

637-
// Fetch from memory and/or disk. inputsRet keys are transaction hashes.
674+
/** Fetch from memory and/or disk. inputsRet keys are transaction hashes.
675+
676+
@param[in] txdb Transaction database
677+
@param[in] mapTestPool List of pending changes to the transaction index database
678+
@param[in] fBlock True if being called to add a new best-block to the chain
679+
@param[in] fMiner True if being called by CreateNewBlock
680+
@param[out] inputsRet Pointers to this transaction's inputs
681+
@return Returns true if all inputs are in txdb or mapTestPool
682+
*/
638683
bool FetchInputs(CTxDB& txdb, const std::map<uint256, CTxIndex>& mapTestPool,
639-
bool fBlock, bool fMiner, std::map<uint256, std::pair<CTxIndex, CTransaction> >& inputsRet);
640-
bool ConnectInputs(std::map<uint256, std::pair<CTxIndex, CTransaction> > inputs,
684+
bool fBlock, bool fMiner, MapPrevTx& inputsRet);
685+
686+
/** Sanity check previous transactions, then, if all checks succeed,
687+
mark them as spent by this transaction.
688+
689+
@param[in] inputs Previous transactions (from FetchInputs)
690+
@param[out] mapTestPool Keeps track of inputs that need to be updated on disk
691+
@param[in] posThisTx Position of this transaction on disk
692+
@param[in] pindexBlock
693+
@param[in] fBlock true if called from ConnectBlock
694+
@param[in] fMiner true if called from CreateNewBlock
695+
@return Returns true if all checks succeed
696+
*/
697+
bool ConnectInputs(MapPrevTx inputs,
641698
std::map<uint256, CTxIndex>& mapTestPool, const CDiskTxPos& posThisTx,
642-
const CBlockIndex* pindexBlock, int64& nFees, bool fBlock, bool fMiner, int& nSigOpsRet, int64 nMinFee=0);
699+
const CBlockIndex* pindexBlock, bool fBlock, bool fMiner);
643700
bool ClientConnectInputs();
644701
bool CheckTransaction() const;
645702
bool AcceptToMemoryPool(CTxDB& txdb, bool fCheckInputs=true, bool* pfMissingInputs=NULL);
646703
bool AcceptToMemoryPool(bool fCheckInputs=true, bool* pfMissingInputs=NULL);
704+
647705
protected:
706+
const CTxOut& GetOutputFor(const CTxIn& input, const MapPrevTx& inputs) const;
648707
bool AddToMemoryPoolUnchecked();
649708
public:
650709
bool RemoveFromMemoryPool();

0 commit comments

Comments
 (0)