Optimize orphan transaction handling

Changes suggested by Sergio Demian Lerner to
help prevent potential DoS attacks.
This commit is contained in:
Gavin Andresen 2012-05-15 15:53:30 -04:00
parent f718aedd9f
commit 77b99cf7ad
2 changed files with 58 additions and 18 deletions

View file

@ -43,7 +43,7 @@ map<uint256, CBlock*> mapOrphanBlocks;
multimap<uint256, CBlock*> mapOrphanBlocksByPrev; multimap<uint256, CBlock*> mapOrphanBlocksByPrev;
map<uint256, CDataStream*> mapOrphanTransactions; map<uint256, CDataStream*> mapOrphanTransactions;
multimap<uint256, CDataStream*> mapOrphanTransactionsByPrev; map<uint256, map<uint256, CDataStream*> > mapOrphanTransactionsByPrev;
// Constant stuff for coinbase transactions we create: // Constant stuff for coinbase transactions we create:
CScript COINBASE_FLAGS; CScript COINBASE_FLAGS;
@ -160,17 +160,37 @@ void static ResendWalletTransactions()
// mapOrphanTransactions // mapOrphanTransactions
// //
void AddOrphanTx(const CDataStream& vMsg) bool AddOrphanTx(const CDataStream& vMsg)
{ {
CTransaction tx; CTransaction tx;
CDataStream(vMsg) >> tx; CDataStream(vMsg) >> tx;
uint256 hash = tx.GetHash(); uint256 hash = tx.GetHash();
if (mapOrphanTransactions.count(hash)) if (mapOrphanTransactions.count(hash))
return; return false;
CDataStream* pvMsg = mapOrphanTransactions[hash] = new CDataStream(vMsg); CDataStream* pvMsg = new CDataStream(vMsg);
// Ignore big transactions, to avoid a
// send-big-orphans memory exhaustion attack. If a peer has a legitimate
// large transaction with a missing parent then we assume
// it will rebroadcast it later, after the parent transaction(s)
// have been mined or received.
// 10,000 orphans, each of which is at most 5,000 bytes big is
// at most 500 megabytes of orphans:
if (pvMsg->size() > 5000)
{
delete pvMsg;
printf("ignoring large orphan tx (size: %u, hash: %s)\n", pvMsg->size(), hash.ToString().substr(0,10).c_str());
return false;
}
mapOrphanTransactions[hash] = pvMsg;
BOOST_FOREACH(const CTxIn& txin, tx.vin) BOOST_FOREACH(const CTxIn& txin, tx.vin)
mapOrphanTransactionsByPrev.insert(make_pair(txin.prevout.hash, pvMsg)); mapOrphanTransactionsByPrev[txin.prevout.hash].insert(make_pair(hash, pvMsg));
printf("stored orphan tx %s (mapsz %u)\n", hash.ToString().substr(0,10).c_str(),
mapOrphanTransactions.size());
return true;
} }
void static EraseOrphanTx(uint256 hash) void static EraseOrphanTx(uint256 hash)
@ -182,14 +202,9 @@ void static EraseOrphanTx(uint256 hash)
CDataStream(*pvMsg) >> tx; CDataStream(*pvMsg) >> tx;
BOOST_FOREACH(const CTxIn& txin, tx.vin) BOOST_FOREACH(const CTxIn& txin, tx.vin)
{ {
for (multimap<uint256, CDataStream*>::iterator mi = mapOrphanTransactionsByPrev.lower_bound(txin.prevout.hash); mapOrphanTransactionsByPrev[txin.prevout.hash].erase(hash);
mi != mapOrphanTransactionsByPrev.upper_bound(txin.prevout.hash);) if (mapOrphanTransactionsByPrev[txin.prevout.hash].empty())
{ mapOrphanTransactionsByPrev.erase(txin.prevout.hash);
if ((*mi).second == pvMsg)
mapOrphanTransactionsByPrev.erase(mi++);
else
mi++;
}
} }
delete pvMsg; delete pvMsg;
mapOrphanTransactions.erase(hash); mapOrphanTransactions.erase(hash);
@ -2569,8 +2584,8 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv)
for (unsigned int i = 0; i < vWorkQueue.size(); i++) for (unsigned int i = 0; i < vWorkQueue.size(); i++)
{ {
uint256 hashPrev = vWorkQueue[i]; uint256 hashPrev = vWorkQueue[i];
for (multimap<uint256, CDataStream*>::iterator mi = mapOrphanTransactionsByPrev.lower_bound(hashPrev); for (map<uint256, CDataStream*>::iterator mi = mapOrphanTransactionsByPrev[hashPrev].begin();
mi != mapOrphanTransactionsByPrev.upper_bound(hashPrev); mi != mapOrphanTransactionsByPrev[hashPrev].end();
++mi) ++mi)
{ {
const CDataStream& vMsg = *((*mi).second); const CDataStream& vMsg = *((*mi).second);
@ -2594,7 +2609,6 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv)
} }
else if (fMissingInputs) else if (fMissingInputs)
{ {
printf("storing orphan tx %s\n", inv.hash.ToString().substr(0,10).c_str());
AddOrphanTx(vMsg); AddOrphanTx(vMsg);
// DoS prevention: do not allow mapOrphanTransactions to grow unbounded // DoS prevention: do not allow mapOrphanTransactions to grow unbounded

View file

@ -13,10 +13,10 @@
#include <stdint.h> #include <stdint.h>
// Tests this internal-to-main.cpp method: // Tests this internal-to-main.cpp method:
extern void AddOrphanTx(const CDataStream& vMsg); extern bool AddOrphanTx(const CDataStream& vMsg);
extern unsigned int LimitOrphanTxSize(unsigned int nMaxOrphans); extern unsigned int LimitOrphanTxSize(unsigned int nMaxOrphans);
extern std::map<uint256, CDataStream*> mapOrphanTransactions; extern std::map<uint256, CDataStream*> mapOrphanTransactions;
extern std::multimap<uint256, CDataStream*> mapOrphanTransactionsByPrev; extern std::map<uint256, std::map<uint256, CDataStream*> > mapOrphanTransactionsByPrev;
CService ip(uint32_t i) CService ip(uint32_t i)
{ {
@ -184,6 +184,32 @@ BOOST_AUTO_TEST_CASE(DoS_mapOrphans)
AddOrphanTx(ds); AddOrphanTx(ds);
} }
// This really-big orphan should be ignored:
for (int i = 0; i < 10; i++)
{
CTransaction txPrev = RandomOrphan();
CTransaction tx;
tx.vout.resize(1);
tx.vout[0].nValue = 1*CENT;
tx.vout[0].scriptPubKey.SetBitcoinAddress(key.GetPubKey());
tx.vin.resize(500);
for (int j = 0; j < tx.vin.size(); j++)
{
tx.vin[j].prevout.n = j;
tx.vin[j].prevout.hash = txPrev.GetHash();
}
SignSignature(keystore, txPrev, tx, 0);
// Re-use same signature for other inputs
// (they don't have to be valid for this test)
for (int j = 1; j < tx.vin.size(); j++)
tx.vin[j].scriptSig = tx.vin[0].scriptSig;
CDataStream ds(SER_DISK, CLIENT_VERSION);
ds << tx;
BOOST_CHECK(!AddOrphanTx(ds));
}
// Test LimitOrphanTxSize() function: // Test LimitOrphanTxSize() function:
LimitOrphanTxSize(40); LimitOrphanTxSize(40);
BOOST_CHECK(mapOrphanTransactions.size() <= 40); BOOST_CHECK(mapOrphanTransactions.size() <= 40);