Skip to content

Commit 8923edf

Browse files
committed
[p2p] allow entries with the same txid in TxOrphanage
Index by wtxid instead of txid to allow entries with the same txid but different witnesses in orphanage. This prevents an attacker from blocking a transaction from entering the orphanage by sending a mutated version of it.
1 parent c31f148 commit 8923edf

File tree

4 files changed

+23
-30
lines changed

4 files changed

+23
-30
lines changed

src/net_processing.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2302,7 +2302,10 @@ bool PeerManagerImpl::AlreadyHaveTx(const GenTxid& gtxid, bool include_reconside
23022302
// Never query by txid: it is possible that the transaction in the orphanage has the same
23032303
// txid but a different witness, which would give us a false positive result. If we decided
23042304
// not to request the transaction based on this result, an attacker could prevent us from
2305-
// downloading a transaction by intentionally creating a malleated version of it.
2305+
// downloading a transaction by intentionally creating a malleated version of it. While
2306+
// only one (or none!) of these transactions can ultimately be confirmed, we have no way of
2307+
// discerning which one that is, so the orphanage can store multiple transactions with the
2308+
// same txid.
23062309
//
23072310
// While we won't query by txid, we can try to "guess" what the wtxid is based on the txid.
23082311
// A non-segwit transaction's txid == wtxid. Query this txid "casted" to a wtxid. This will

src/test/orphanage_tests.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ class TxOrphanageTest : public TxOrphanage
3030
CTransactionRef RandomOrphan() EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
3131
{
3232
LOCK(m_mutex);
33-
std::map<Txid, OrphanTx>::iterator it;
34-
it = m_orphans.lower_bound(Txid::FromUint256(InsecureRand256()));
33+
std::map<Wtxid, OrphanTx>::iterator it;
34+
it = m_orphans.lower_bound(Wtxid::FromUint256(InsecureRand256()));
3535
if (it == m_orphans.end())
3636
it = m_orphans.begin();
3737
return it->second.tx;

src/txorphanage.cpp

Lines changed: 14 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ bool TxOrphanage::AddTx(const CTransactionRef& tx, NodeId peer)
2323

2424
const Txid& hash = tx->GetHash();
2525
const Wtxid& wtxid = tx->GetWitnessHash();
26-
if (m_orphans.count(hash))
26+
if (m_orphans.count(wtxid))
2727
return false;
2828

2929
// Ignore big transactions, to avoid a
@@ -40,11 +40,9 @@ bool TxOrphanage::AddTx(const CTransactionRef& tx, NodeId peer)
4040
return false;
4141
}
4242

43-
auto ret = m_orphans.emplace(hash, OrphanTx{tx, peer, GetTime() + ORPHAN_TX_EXPIRE_TIME, m_orphan_list.size()});
43+
auto ret = m_orphans.emplace(wtxid, OrphanTx{tx, peer, GetTime() + ORPHAN_TX_EXPIRE_TIME, m_orphan_list.size()});
4444
assert(ret.second);
4545
m_orphan_list.push_back(ret.first);
46-
// Allow for lookups in the orphan pool by wtxid, as well as txid
47-
m_wtxid_to_orphan_it.emplace(tx->GetWitnessHash(), ret.first);
4846
for (const CTxIn& txin : tx->vin) {
4947
m_outpoint_to_orphan_it[txin.prevout].insert(ret.first);
5048
}
@@ -63,10 +61,7 @@ int TxOrphanage::EraseTx(const Wtxid& wtxid)
6361
int TxOrphanage::EraseTxNoLock(const Wtxid& wtxid)
6462
{
6563
AssertLockHeld(m_mutex);
66-
auto it_by_wtxid = m_wtxid_to_orphan_it.find(wtxid);
67-
if (it_by_wtxid == m_wtxid_to_orphan_it.end()) return 0;
68-
69-
std::map<Txid, OrphanTx>::iterator it = it_by_wtxid->second;
64+
std::map<Wtxid, OrphanTx>::iterator it = m_orphans.find(wtxid);
7065
if (it == m_orphans.end())
7166
return 0;
7267
for (const CTxIn& txin : it->second.tx->vin)
@@ -91,7 +86,6 @@ int TxOrphanage::EraseTxNoLock(const Wtxid& wtxid)
9186
const auto& txid = it->second.tx->GetHash();
9287
LogPrint(BCLog::TXPACKAGES, " removed orphan tx %s (wtxid=%s)\n", txid.ToString(), wtxid.ToString());
9388
m_orphan_list.pop_back();
94-
m_wtxid_to_orphan_it.erase(wtxid);
9589

9690
m_orphans.erase(it);
9791
return 1;
@@ -104,13 +98,13 @@ void TxOrphanage::EraseForPeer(NodeId peer)
10498
m_peer_work_set.erase(peer);
10599

106100
int nErased = 0;
107-
std::map<Txid, OrphanTx>::iterator iter = m_orphans.begin();
101+
std::map<Wtxid, OrphanTx>::iterator iter = m_orphans.begin();
108102
while (iter != m_orphans.end())
109103
{
110-
std::map<Txid, OrphanTx>::iterator maybeErase = iter++; // increment to avoid iterator becoming invalid
111-
if (maybeErase->second.fromPeer == peer)
112-
{
113-
nErased += EraseTxNoLock(maybeErase->second.tx->GetWitnessHash());
104+
// increment to avoid iterator becoming invalid after erasure
105+
const auto& [wtxid, orphan] = *iter++;
106+
if (orphan.fromPeer == peer) {
107+
nErased += EraseTxNoLock(wtxid);
114108
}
115109
}
116110
if (nErased > 0) LogPrint(BCLog::TXPACKAGES, "Erased %d orphan tx from peer=%d\n", nErased, peer);
@@ -127,10 +121,10 @@ void TxOrphanage::LimitOrphans(unsigned int max_orphans, FastRandomContext& rng)
127121
// Sweep out expired orphan pool entries:
128122
int nErased = 0;
129123
int64_t nMinExpTime = nNow + ORPHAN_TX_EXPIRE_TIME - ORPHAN_TX_EXPIRE_INTERVAL;
130-
std::map<Txid, OrphanTx>::iterator iter = m_orphans.begin();
124+
std::map<Wtxid, OrphanTx>::iterator iter = m_orphans.begin();
131125
while (iter != m_orphans.end())
132126
{
133-
std::map<Txid, OrphanTx>::iterator maybeErase = iter++;
127+
std::map<Wtxid, OrphanTx>::iterator maybeErase = iter++;
134128
if (maybeErase->second.nTimeExpire <= nNow) {
135129
nErased += EraseTxNoLock(maybeErase->second.tx->GetWitnessHash());
136130
} else {
@@ -162,7 +156,7 @@ void TxOrphanage::AddChildrenToWorkSet(const CTransaction& tx)
162156
for (const auto& elem : it_by_prev->second) {
163157
// Get this source peer's work set, emplacing an empty set if it didn't exist
164158
// (note: if this peer wasn't still connected, we would have removed the orphan tx already)
165-
std::set<Txid>& orphan_work_set = m_peer_work_set.try_emplace(elem->second.fromPeer).first->second;
159+
std::set<Wtxid>& orphan_work_set = m_peer_work_set.try_emplace(elem->second.fromPeer).first->second;
166160
// Add this tx to the work set
167161
orphan_work_set.insert(elem->first);
168162
LogPrint(BCLog::TXPACKAGES, "added %s (wtxid=%s) to peer %d workset\n",
@@ -175,7 +169,7 @@ void TxOrphanage::AddChildrenToWorkSet(const CTransaction& tx)
175169
bool TxOrphanage::HaveTx(const Wtxid& wtxid) const
176170
{
177171
LOCK(m_mutex);
178-
return m_wtxid_to_orphan_it.count(wtxid);
172+
return m_orphans.count(wtxid);
179173
}
180174

181175
CTransactionRef TxOrphanage::GetTxToReconsider(NodeId peer)
@@ -186,10 +180,10 @@ CTransactionRef TxOrphanage::GetTxToReconsider(NodeId peer)
186180
if (work_set_it != m_peer_work_set.end()) {
187181
auto& work_set = work_set_it->second;
188182
while (!work_set.empty()) {
189-
Txid txid = *work_set.begin();
183+
Wtxid wtxid = *work_set.begin();
190184
work_set.erase(work_set.begin());
191185

192-
const auto orphan_it = m_orphans.find(txid);
186+
const auto orphan_it = m_orphans.find(wtxid);
193187
if (orphan_it != m_orphans.end()) {
194188
return orphan_it->second.tx;
195189
}

src/txorphanage.h

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -77,12 +77,12 @@ class TxOrphanage {
7777
size_t list_pos;
7878
};
7979

80-
/** Map from txid to orphan transaction record. Limited by
80+
/** Map from wtxid to orphan transaction record. Limited by
8181
* -maxorphantx/DEFAULT_MAX_ORPHAN_TRANSACTIONS */
82-
std::map<Txid, OrphanTx> m_orphans GUARDED_BY(m_mutex);
82+
std::map<Wtxid, OrphanTx> m_orphans GUARDED_BY(m_mutex);
8383

8484
/** Which peer provided the orphans that need to be reconsidered */
85-
std::map<NodeId, std::set<Txid>> m_peer_work_set GUARDED_BY(m_mutex);
85+
std::map<NodeId, std::set<Wtxid>> m_peer_work_set GUARDED_BY(m_mutex);
8686

8787
using OrphanMap = decltype(m_orphans);
8888

@@ -102,10 +102,6 @@ class TxOrphanage {
102102
/** Orphan transactions in vector for quick random eviction */
103103
std::vector<OrphanMap::iterator> m_orphan_list GUARDED_BY(m_mutex);
104104

105-
/** Index from wtxid into the m_orphans to lookup orphan
106-
* transactions using their witness ids. */
107-
std::map<Wtxid, OrphanMap::iterator> m_wtxid_to_orphan_it GUARDED_BY(m_mutex);
108-
109105
/** Erase an orphan by wtxid */
110106
int EraseTxNoLock(const Wtxid& wtxid) EXCLUSIVE_LOCKS_REQUIRED(m_mutex);
111107
};

0 commit comments

Comments
 (0)