Skip to content

Commit 92b7efc

Browse files
committed
Merge #21148: Split orphan handling from net_processing into txorphanage
5e50e2d txorphanage: comment improvements (Anthony Towns) eeeafb3 net_processing: move AddToCompactExtraTransactions into PeerManagerImpl (Anthony Towns) f8c0688 scripted-diff: Update txorphanage naming convention (Anthony Towns) 6bd4963 txorphanage: Move functions and data into class (Anthony Towns) 03257b8 txorphanage: Extract EraseOrphansForBlock (Anthony Towns) 3c4c3c2 net_processing: drop AddOrphanTx (Anthony Towns) 26d1a6c denialofservices_tests: check txorphanage's AddTx (Anthony Towns) 1041616 txorphanage: Extract OrphanageAddTx (Anthony Towns) f294da7 txorphanage: Extract GetOrphanTx (Anthony Towns) 83679ff txorphanage: Extract HaveOrphanTx (Anthony Towns) ee135c8 txorphanage: Extract AddChildrenToWorkSet (Anthony Towns) 38a11c3 txorphanage: Add lock annotations (Anthony Towns) 81dd57e txorphanage: Pass uint256 by reference instead of value (Anthony Towns) 9d5313d move-only: Add txorphanage module (Anthony Towns) Pull request description: Splits orphan handling into its own module and reduces global usage. ACKs for top commit: jnewbery: utACK 5e50e2d amitiuttarwar: utACK 5e50e2d glozow: re ACK bitcoin/bitcoin@5e50e2d, comment updates laanwj: Code review ACK 5e50e2d Tree-SHA512: 92a959bb5dd414c96f78cb8dcaa68adb85faf16b8b843a2cbe0bb2aa08df13ad6bd9424d29b98f57a82ec29c942fbdbea3011883d00bf0b0feb643e295174e46
2 parents d099894 + 5e50e2d commit 92b7efc

File tree

10 files changed

+362
-290
lines changed

10 files changed

+362
-290
lines changed

src/Makefile.am

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -225,8 +225,9 @@ BITCOIN_CORE_H = \
225225
timedata.h \
226226
torcontrol.h \
227227
txdb.h \
228-
txrequest.h \
229228
txmempool.h \
229+
txorphanage.h \
230+
txrequest.h \
230231
undo.h \
231232
util/asmap.h \
232233
util/bip32.h \
@@ -350,8 +351,9 @@ libbitcoin_server_a_SOURCES = \
350351
timedata.cpp \
351352
torcontrol.cpp \
352353
txdb.cpp \
353-
txrequest.cpp \
354354
txmempool.cpp \
355+
txorphanage.cpp \
356+
txrequest.cpp \
355357
validation.cpp \
356358
validationinterface.cpp \
357359
versionbits.cpp \

src/init.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
#include <torcontrol.h>
5353
#include <txdb.h>
5454
#include <txmempool.h>
55+
#include <txorphanage.h>
5556
#include <util/asmap.h>
5657
#include <util/check.h>
5758
#include <util/moneystr.h>

src/net_processing.cpp

Lines changed: 33 additions & 251 deletions
Large diffs are not rendered by default.

src/net_processing.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ class CTxMemPool;
1515
class ChainstateManager;
1616

1717
extern RecursiveMutex cs_main;
18-
extern RecursiveMutex g_cs_orphans;
1918

2019
/** Default for -maxorphantx, maximum number of orphan transactions kept in memory */
2120
static const unsigned int DEFAULT_MAX_ORPHAN_TRANSACTIONS = 100;

src/test/denialofservice_tests.cpp

Lines changed: 34 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include <script/signingprovider.h>
1515
#include <script/standard.h>
1616
#include <serialize.h>
17+
#include <txorphanage.h>
1718
#include <util/memory.h>
1819
#include <util/string.h>
1920
#include <util/system.h>
@@ -43,18 +44,6 @@ struct CConnmanTest : public CConnman {
4344
}
4445
};
4546

46-
// Tests these internal-to-net_processing.cpp methods:
47-
extern bool AddOrphanTx(const CTransactionRef& tx, NodeId peer);
48-
extern void EraseOrphansFor(NodeId peer);
49-
extern unsigned int LimitOrphanTxSize(unsigned int nMaxOrphans);
50-
51-
struct COrphanTx {
52-
CTransactionRef tx;
53-
NodeId fromPeer;
54-
int64_t nTimeExpire;
55-
};
56-
extern std::map<uint256, COrphanTx> mapOrphanTransactions GUARDED_BY(g_cs_orphans);
57-
5847
static CService ip(uint32_t i)
5948
{
6049
struct in_addr s;
@@ -295,15 +284,23 @@ BOOST_AUTO_TEST_CASE(DoS_bantime)
295284
peerLogic->FinalizeNode(dummyNode, dummy);
296285
}
297286

298-
static CTransactionRef RandomOrphan()
287+
class TxOrphanageTest : public TxOrphanage
299288
{
300-
std::map<uint256, COrphanTx>::iterator it;
301-
LOCK2(cs_main, g_cs_orphans);
302-
it = mapOrphanTransactions.lower_bound(InsecureRand256());
303-
if (it == mapOrphanTransactions.end())
304-
it = mapOrphanTransactions.begin();
305-
return it->second.tx;
306-
}
289+
public:
290+
inline size_t CountOrphans() const EXCLUSIVE_LOCKS_REQUIRED(g_cs_orphans)
291+
{
292+
return m_orphans.size();
293+
}
294+
295+
CTransactionRef RandomOrphan() EXCLUSIVE_LOCKS_REQUIRED(g_cs_orphans)
296+
{
297+
std::map<uint256, OrphanTx>::iterator it;
298+
it = m_orphans.lower_bound(InsecureRand256());
299+
if (it == m_orphans.end())
300+
it = m_orphans.begin();
301+
return it->second.tx;
302+
}
303+
};
307304

308305
static void MakeNewKeyWithFastRandomContext(CKey& key)
309306
{
@@ -323,11 +320,14 @@ BOOST_AUTO_TEST_CASE(DoS_mapOrphans)
323320
// signature's R and S values have leading zeros.
324321
g_insecure_rand_ctx = FastRandomContext(ArithToUint256(arith_uint256(33)));
325322

323+
TxOrphanageTest orphanage;
326324
CKey key;
327325
MakeNewKeyWithFastRandomContext(key);
328326
FillableSigningProvider keystore;
329327
BOOST_CHECK(keystore.AddKey(key));
330328

329+
LOCK(g_cs_orphans);
330+
331331
// 50 orphan transactions:
332332
for (int i = 0; i < 50; i++)
333333
{
@@ -340,13 +340,13 @@ BOOST_AUTO_TEST_CASE(DoS_mapOrphans)
340340
tx.vout[0].nValue = 1*CENT;
341341
tx.vout[0].scriptPubKey = GetScriptForDestination(PKHash(key.GetPubKey()));
342342

343-
AddOrphanTx(MakeTransactionRef(tx), i);
343+
orphanage.AddTx(MakeTransactionRef(tx), i);
344344
}
345345

346346
// ... and 50 that depend on other orphans:
347347
for (int i = 0; i < 50; i++)
348348
{
349-
CTransactionRef txPrev = RandomOrphan();
349+
CTransactionRef txPrev = orphanage.RandomOrphan();
350350

351351
CMutableTransaction tx;
352352
tx.vin.resize(1);
@@ -357,13 +357,13 @@ BOOST_AUTO_TEST_CASE(DoS_mapOrphans)
357357
tx.vout[0].scriptPubKey = GetScriptForDestination(PKHash(key.GetPubKey()));
358358
BOOST_CHECK(SignSignature(keystore, *txPrev, tx, 0, SIGHASH_ALL));
359359

360-
AddOrphanTx(MakeTransactionRef(tx), i);
360+
orphanage.AddTx(MakeTransactionRef(tx), i);
361361
}
362362

363363
// This really-big orphan should be ignored:
364364
for (int i = 0; i < 10; i++)
365365
{
366-
CTransactionRef txPrev = RandomOrphan();
366+
CTransactionRef txPrev = orphanage.RandomOrphan();
367367

368368
CMutableTransaction tx;
369369
tx.vout.resize(1);
@@ -381,25 +381,24 @@ BOOST_AUTO_TEST_CASE(DoS_mapOrphans)
381381
for (unsigned int j = 1; j < tx.vin.size(); j++)
382382
tx.vin[j].scriptSig = tx.vin[0].scriptSig;
383383

384-
BOOST_CHECK(!AddOrphanTx(MakeTransactionRef(tx), i));
384+
BOOST_CHECK(!orphanage.AddTx(MakeTransactionRef(tx), i));
385385
}
386386

387-
LOCK2(cs_main, g_cs_orphans);
388387
// Test EraseOrphansFor:
389388
for (NodeId i = 0; i < 3; i++)
390389
{
391-
size_t sizeBefore = mapOrphanTransactions.size();
392-
EraseOrphansFor(i);
393-
BOOST_CHECK(mapOrphanTransactions.size() < sizeBefore);
390+
size_t sizeBefore = orphanage.CountOrphans();
391+
orphanage.EraseForPeer(i);
392+
BOOST_CHECK(orphanage.CountOrphans() < sizeBefore);
394393
}
395394

396395
// Test LimitOrphanTxSize() function:
397-
LimitOrphanTxSize(40);
398-
BOOST_CHECK(mapOrphanTransactions.size() <= 40);
399-
LimitOrphanTxSize(10);
400-
BOOST_CHECK(mapOrphanTransactions.size() <= 10);
401-
LimitOrphanTxSize(0);
402-
BOOST_CHECK(mapOrphanTransactions.empty());
396+
orphanage.LimitOrphans(40);
397+
BOOST_CHECK(orphanage.CountOrphans() <= 40);
398+
orphanage.LimitOrphans(10);
399+
BOOST_CHECK(orphanage.CountOrphans() <= 10);
400+
orphanage.LimitOrphans(0);
401+
BOOST_CHECK(orphanage.CountOrphans() == 0);
403402
}
404403

405404
BOOST_AUTO_TEST_SUITE_END()

src/test/fuzz/process_message.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include <test/util/net.h>
1919
#include <test/util/setup_common.h>
2020
#include <test/util/validation.h>
21+
#include <txorphanage.h>
2122
#include <util/memory.h>
2223
#include <validationinterface.h>
2324
#include <version.h>

src/test/fuzz/process_messages.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include <test/util/net.h>
1414
#include <test/util/setup_common.h>
1515
#include <test/util/validation.h>
16+
#include <txorphanage.h>
1617
#include <util/memory.h>
1718
#include <validation.h>
1819
#include <validationinterface.h>

src/txorphanage.cpp

Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
// Copyright (c) 2021 The Bitcoin Core developers
2+
// Distributed under the MIT software license, see the accompanying
3+
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4+
5+
#include <txorphanage.h>
6+
7+
#include <consensus/validation.h>
8+
#include <logging.h>
9+
#include <policy/policy.h>
10+
11+
#include <cassert>
12+
13+
/** Expiration time for orphan transactions in seconds */
14+
static constexpr int64_t ORPHAN_TX_EXPIRE_TIME = 20 * 60;
15+
/** Minimum time between orphan transactions expire time checks in seconds */
16+
static constexpr int64_t ORPHAN_TX_EXPIRE_INTERVAL = 5 * 60;
17+
18+
RecursiveMutex g_cs_orphans;
19+
20+
bool TxOrphanage::AddTx(const CTransactionRef& tx, NodeId peer)
21+
{
22+
AssertLockHeld(g_cs_orphans);
23+
24+
const uint256& hash = tx->GetHash();
25+
if (m_orphans.count(hash))
26+
return false;
27+
28+
// Ignore big transactions, to avoid a
29+
// send-big-orphans memory exhaustion attack. If a peer has a legitimate
30+
// large transaction with a missing parent then we assume
31+
// it will rebroadcast it later, after the parent transaction(s)
32+
// have been mined or received.
33+
// 100 orphans, each of which is at most 100,000 bytes big is
34+
// at most 10 megabytes of orphans and somewhat more byprev index (in the worst case):
35+
unsigned int sz = GetTransactionWeight(*tx);
36+
if (sz > MAX_STANDARD_TX_WEIGHT)
37+
{
38+
LogPrint(BCLog::MEMPOOL, "ignoring large orphan tx (size: %u, hash: %s)\n", sz, hash.ToString());
39+
return false;
40+
}
41+
42+
auto ret = m_orphans.emplace(hash, OrphanTx{tx, peer, GetTime() + ORPHAN_TX_EXPIRE_TIME, m_orphan_list.size()});
43+
assert(ret.second);
44+
m_orphan_list.push_back(ret.first);
45+
// Allow for lookups in the orphan pool by wtxid, as well as txid
46+
m_wtxid_to_orphan_it.emplace(tx->GetWitnessHash(), ret.first);
47+
for (const CTxIn& txin : tx->vin) {
48+
m_outpoint_to_orphan_it[txin.prevout].insert(ret.first);
49+
}
50+
51+
LogPrint(BCLog::MEMPOOL, "stored orphan tx %s (mapsz %u outsz %u)\n", hash.ToString(),
52+
m_orphans.size(), m_outpoint_to_orphan_it.size());
53+
return true;
54+
}
55+
56+
int TxOrphanage::EraseTx(const uint256& txid)
57+
{
58+
AssertLockHeld(g_cs_orphans);
59+
std::map<uint256, OrphanTx>::iterator it = m_orphans.find(txid);
60+
if (it == m_orphans.end())
61+
return 0;
62+
for (const CTxIn& txin : it->second.tx->vin)
63+
{
64+
auto itPrev = m_outpoint_to_orphan_it.find(txin.prevout);
65+
if (itPrev == m_outpoint_to_orphan_it.end())
66+
continue;
67+
itPrev->second.erase(it);
68+
if (itPrev->second.empty())
69+
m_outpoint_to_orphan_it.erase(itPrev);
70+
}
71+
72+
size_t old_pos = it->second.list_pos;
73+
assert(m_orphan_list[old_pos] == it);
74+
if (old_pos + 1 != m_orphan_list.size()) {
75+
// Unless we're deleting the last entry in m_orphan_list, move the last
76+
// entry to the position we're deleting.
77+
auto it_last = m_orphan_list.back();
78+
m_orphan_list[old_pos] = it_last;
79+
it_last->second.list_pos = old_pos;
80+
}
81+
m_orphan_list.pop_back();
82+
m_wtxid_to_orphan_it.erase(it->second.tx->GetWitnessHash());
83+
84+
m_orphans.erase(it);
85+
return 1;
86+
}
87+
88+
void TxOrphanage::EraseForPeer(NodeId peer)
89+
{
90+
AssertLockHeld(g_cs_orphans);
91+
92+
int nErased = 0;
93+
std::map<uint256, OrphanTx>::iterator iter = m_orphans.begin();
94+
while (iter != m_orphans.end())
95+
{
96+
std::map<uint256, OrphanTx>::iterator maybeErase = iter++; // increment to avoid iterator becoming invalid
97+
if (maybeErase->second.fromPeer == peer)
98+
{
99+
nErased += EraseTx(maybeErase->second.tx->GetHash());
100+
}
101+
}
102+
if (nErased > 0) LogPrint(BCLog::MEMPOOL, "Erased %d orphan tx from peer=%d\n", nErased, peer);
103+
}
104+
105+
unsigned int TxOrphanage::LimitOrphans(unsigned int max_orphans)
106+
{
107+
AssertLockHeld(g_cs_orphans);
108+
109+
unsigned int nEvicted = 0;
110+
static int64_t nNextSweep;
111+
int64_t nNow = GetTime();
112+
if (nNextSweep <= nNow) {
113+
// Sweep out expired orphan pool entries:
114+
int nErased = 0;
115+
int64_t nMinExpTime = nNow + ORPHAN_TX_EXPIRE_TIME - ORPHAN_TX_EXPIRE_INTERVAL;
116+
std::map<uint256, OrphanTx>::iterator iter = m_orphans.begin();
117+
while (iter != m_orphans.end())
118+
{
119+
std::map<uint256, OrphanTx>::iterator maybeErase = iter++;
120+
if (maybeErase->second.nTimeExpire <= nNow) {
121+
nErased += EraseTx(maybeErase->second.tx->GetHash());
122+
} else {
123+
nMinExpTime = std::min(maybeErase->second.nTimeExpire, nMinExpTime);
124+
}
125+
}
126+
// Sweep again 5 minutes after the next entry that expires in order to batch the linear scan.
127+
nNextSweep = nMinExpTime + ORPHAN_TX_EXPIRE_INTERVAL;
128+
if (nErased > 0) LogPrint(BCLog::MEMPOOL, "Erased %d orphan tx due to expiration\n", nErased);
129+
}
130+
FastRandomContext rng;
131+
while (m_orphans.size() > max_orphans)
132+
{
133+
// Evict a random orphan:
134+
size_t randompos = rng.randrange(m_orphan_list.size());
135+
EraseTx(m_orphan_list[randompos]->first);
136+
++nEvicted;
137+
}
138+
return nEvicted;
139+
}
140+
141+
void TxOrphanage::AddChildrenToWorkSet(const CTransaction& tx, std::set<uint256>& orphan_work_set) const
142+
{
143+
AssertLockHeld(g_cs_orphans);
144+
for (unsigned int i = 0; i < tx.vout.size(); i++) {
145+
const auto it_by_prev = m_outpoint_to_orphan_it.find(COutPoint(tx.GetHash(), i));
146+
if (it_by_prev != m_outpoint_to_orphan_it.end()) {
147+
for (const auto& elem : it_by_prev->second) {
148+
orphan_work_set.insert(elem->first);
149+
}
150+
}
151+
}
152+
}
153+
154+
bool TxOrphanage::HaveTx(const GenTxid& gtxid) const
155+
{
156+
LOCK(g_cs_orphans);
157+
if (gtxid.IsWtxid()) {
158+
return m_wtxid_to_orphan_it.count(gtxid.GetHash());
159+
} else {
160+
return m_orphans.count(gtxid.GetHash());
161+
}
162+
}
163+
164+
std::pair<CTransactionRef, NodeId> TxOrphanage::GetTx(const uint256& txid) const
165+
{
166+
AssertLockHeld(g_cs_orphans);
167+
168+
const auto it = m_orphans.find(txid);
169+
if (it == m_orphans.end()) return {nullptr, -1};
170+
return {it->second.tx, it->second.fromPeer};
171+
}
172+
173+
void TxOrphanage::EraseForBlock(const CBlock& block)
174+
{
175+
LOCK(g_cs_orphans);
176+
177+
std::vector<uint256> vOrphanErase;
178+
179+
for (const CTransactionRef& ptx : block.vtx) {
180+
const CTransaction& tx = *ptx;
181+
182+
// Which orphan pool entries must we evict?
183+
for (const auto& txin : tx.vin) {
184+
auto itByPrev = m_outpoint_to_orphan_it.find(txin.prevout);
185+
if (itByPrev == m_outpoint_to_orphan_it.end()) continue;
186+
for (auto mi = itByPrev->second.begin(); mi != itByPrev->second.end(); ++mi) {
187+
const CTransaction& orphanTx = *(*mi)->second.tx;
188+
const uint256& orphanHash = orphanTx.GetHash();
189+
vOrphanErase.push_back(orphanHash);
190+
}
191+
}
192+
}
193+
194+
// Erase orphan transactions included or precluded by this block
195+
if (vOrphanErase.size()) {
196+
int nErased = 0;
197+
for (const uint256& orphanHash : vOrphanErase) {
198+
nErased += EraseTx(orphanHash);
199+
}
200+
LogPrint(BCLog::MEMPOOL, "Erased %d orphan tx included or conflicted by block\n", nErased);
201+
}
202+
}

0 commit comments

Comments
 (0)