Skip to content

Commit e810842

Browse files
committed
[txorphanage] support multiple announcers
Add ability to add and track multiple announcers per orphan transaction, erasing announcers but not the entire orphan. The tx creation code in orphanage_tests needs to be updated so that each tx is unique, because the CountOrphans() check assumes that calling EraseForPeer necessarily means its orphans are deleted. Unused for now.
1 parent 62a9ff1 commit e810842

File tree

4 files changed

+68
-23
lines changed

4 files changed

+68
-23
lines changed

src/rpc/mempool.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -843,7 +843,9 @@ static UniValue OrphanToJSON(const TxOrphanage::OrphanTxBase& orphan)
843843
o.pushKV("entry", int64_t{TicksSinceEpoch<std::chrono::seconds>(orphan.nTimeExpire - ORPHAN_TX_EXPIRE_TIME)});
844844
o.pushKV("expiration", int64_t{TicksSinceEpoch<std::chrono::seconds>(orphan.nTimeExpire)});
845845
UniValue from(UniValue::VARR);
846-
from.push_back(orphan.fromPeer); // only one fromPeer for now
846+
for (const auto fromPeer: orphan.announcers) {
847+
from.push_back(fromPeer);
848+
}
847849
o.pushKV("from", from);
848850
return o;
849851
}

src/test/orphanage_tests.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ BOOST_AUTO_TEST_CASE(DoS_mapOrphans)
132132
tx.vin[0].prevout.hash = Txid::FromUint256(m_rng.rand256());
133133
tx.vin[0].scriptSig << OP_1;
134134
tx.vout.resize(1);
135-
tx.vout[0].nValue = 1*CENT;
135+
tx.vout[0].nValue = i*CENT;
136136
tx.vout[0].scriptPubKey = GetScriptForDestination(PKHash(key.GetPubKey()));
137137

138138
orphanage.AddTx(MakeTransactionRef(tx), i);
@@ -148,7 +148,7 @@ BOOST_AUTO_TEST_CASE(DoS_mapOrphans)
148148
tx.vin[0].prevout.n = 0;
149149
tx.vin[0].prevout.hash = txPrev->GetHash();
150150
tx.vout.resize(1);
151-
tx.vout[0].nValue = 1*CENT;
151+
tx.vout[0].nValue = i*CENT;
152152
tx.vout[0].scriptPubKey = GetScriptForDestination(PKHash(key.GetPubKey()));
153153
SignatureData empty;
154154
BOOST_CHECK(SignSignature(keystore, *txPrev, tx, 0, SIGHASH_ALL, empty));

src/txorphanage.cpp

Lines changed: 53 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,11 @@ bool TxOrphanage::AddTx(const CTransactionRef& tx, NodeId peer)
1616
{
1717
const Txid& hash = tx->GetHash();
1818
const Wtxid& wtxid = tx->GetWitnessHash();
19-
if (m_orphans.count(wtxid))
19+
if (auto it{m_orphans.find(wtxid)}; it != m_orphans.end()) {
20+
AddAnnouncer(wtxid, peer);
21+
// No new orphan entry was created. An announcer may have been added.
2022
return false;
23+
}
2124

2225
// Ignore big transactions, to avoid a
2326
// send-big-orphans memory exhaustion attack. If a peer has a legitimate
@@ -33,7 +36,7 @@ bool TxOrphanage::AddTx(const CTransactionRef& tx, NodeId peer)
3336
return false;
3437
}
3538

36-
auto ret = m_orphans.emplace(wtxid, OrphanTx{{tx, peer, Now<NodeSeconds>() + ORPHAN_TX_EXPIRE_TIME}, m_orphan_list.size()});
39+
auto ret = m_orphans.emplace(wtxid, OrphanTx{{tx, {peer}, Now<NodeSeconds>() + ORPHAN_TX_EXPIRE_TIME}, m_orphan_list.size()});
3740
assert(ret.second);
3841
m_orphan_list.push_back(ret.first);
3942
for (const CTxIn& txin : tx->vin) {
@@ -45,6 +48,20 @@ bool TxOrphanage::AddTx(const CTransactionRef& tx, NodeId peer)
4548
return true;
4649
}
4750

51+
bool TxOrphanage::AddAnnouncer(const Wtxid& wtxid, NodeId peer)
52+
{
53+
const auto it = m_orphans.find(wtxid);
54+
if (it != m_orphans.end()) {
55+
Assume(!it->second.announcers.empty());
56+
const auto ret = it->second.announcers.insert(peer);
57+
if (ret.second) {
58+
LogDebug(BCLog::TXPACKAGES, "added peer=%d as announcer of orphan tx %s\n", peer, wtxid.ToString());
59+
return true;
60+
}
61+
}
62+
return false;
63+
}
64+
4865
int TxOrphanage::EraseTx(const Wtxid& wtxid)
4966
{
5067
std::map<Wtxid, OrphanTx>::iterator it = m_orphans.find(wtxid);
@@ -89,9 +106,15 @@ void TxOrphanage::EraseForPeer(NodeId peer)
89106
while (iter != m_orphans.end())
90107
{
91108
// increment to avoid iterator becoming invalid after erasure
92-
const auto& [wtxid, orphan] = *iter++;
93-
if (orphan.fromPeer == peer) {
94-
nErased += EraseTx(wtxid);
109+
auto& [wtxid, orphan] = *iter++;
110+
auto orphan_it = orphan.announcers.find(peer);
111+
if (orphan_it != orphan.announcers.end()) {
112+
orphan.announcers.erase(peer);
113+
114+
// No remaining annnouncers: clean up entry
115+
if (orphan.announcers.empty()) {
116+
nErased += EraseTx(orphan.tx->GetWitnessHash());
117+
}
95118
}
96119
}
97120
if (nErased > 0) LogDebug(BCLog::TXPACKAGES, "Erased %d orphan transaction(s) from peer=%d\n", nErased, peer);
@@ -110,7 +133,7 @@ void TxOrphanage::LimitOrphans(unsigned int max_orphans, FastRandomContext& rng)
110133
{
111134
std::map<Wtxid, OrphanTx>::iterator maybeErase = iter++;
112135
if (maybeErase->second.nTimeExpire <= nNow) {
113-
nErased += EraseTx(maybeErase->second.tx->GetWitnessHash());
136+
nErased += EraseTx(maybeErase->first);
114137
} else {
115138
nMinExpTime = std::min(maybeErase->second.nTimeExpire, nMinExpTime);
116139
}
@@ -123,7 +146,7 @@ void TxOrphanage::LimitOrphans(unsigned int max_orphans, FastRandomContext& rng)
123146
{
124147
// Evict a random orphan:
125148
size_t randompos = rng.randrange(m_orphan_list.size());
126-
EraseTx(m_orphan_list[randompos]->second.tx->GetWitnessHash());
149+
EraseTx(m_orphan_list[randompos]->first);
127150
++nEvicted;
128151
}
129152
if (nEvicted > 0) LogDebug(BCLog::TXPACKAGES, "orphanage overflow, removed %u tx\n", nEvicted);
@@ -135,13 +158,17 @@ void TxOrphanage::AddChildrenToWorkSet(const CTransaction& tx)
135158
const auto it_by_prev = m_outpoint_to_orphan_it.find(COutPoint(tx.GetHash(), i));
136159
if (it_by_prev != m_outpoint_to_orphan_it.end()) {
137160
for (const auto& elem : it_by_prev->second) {
138-
// Get this source peer's work set, emplacing an empty set if it didn't exist
139-
// (note: if this peer wasn't still connected, we would have removed the orphan tx already)
140-
std::set<Wtxid>& orphan_work_set = m_peer_work_set.try_emplace(elem->second.fromPeer).first->second;
141-
// Add this tx to the work set
142-
orphan_work_set.insert(elem->first);
143-
LogDebug(BCLog::TXPACKAGES, "added %s (wtxid=%s) to peer %d workset\n",
144-
tx.GetHash().ToString(), tx.GetWitnessHash().ToString(), elem->second.fromPeer);
161+
// Belt and suspenders, each orphan should always have at least 1 announcer.
162+
if (!Assume(!elem->second.announcers.empty())) continue;
163+
for (const auto announcer: elem->second.announcers) {
164+
// Get this source peer's work set, emplacing an empty set if it didn't exist
165+
// (note: if this peer wasn't still connected, we would have removed the orphan tx already)
166+
std::set<Wtxid>& orphan_work_set = m_peer_work_set.try_emplace(announcer).first->second;
167+
// Add this tx to the work set
168+
orphan_work_set.insert(elem->first);
169+
LogDebug(BCLog::TXPACKAGES, "added %s (wtxid=%s) to peer %d workset\n",
170+
tx.GetHash().ToString(), tx.GetWitnessHash().ToString(), announcer);
171+
}
145172
}
146173
}
147174
}
@@ -152,6 +179,12 @@ bool TxOrphanage::HaveTx(const Wtxid& wtxid) const
152179
return m_orphans.count(wtxid);
153180
}
154181

182+
bool TxOrphanage::HaveTxFromPeer(const Wtxid& wtxid, NodeId peer) const
183+
{
184+
auto it = m_orphans.find(wtxid);
185+
return (it != m_orphans.end() && it->second.announcers.contains(peer));
186+
}
187+
155188
CTransactionRef TxOrphanage::GetTxToReconsider(NodeId peer)
156189
{
157190
auto work_set_it = m_peer_work_set.find(peer);
@@ -219,7 +252,7 @@ std::vector<CTransactionRef> TxOrphanage::GetChildrenFromSamePeer(const CTransac
219252
const auto it_by_prev = m_outpoint_to_orphan_it.find(COutPoint(parent->GetHash(), i));
220253
if (it_by_prev != m_outpoint_to_orphan_it.end()) {
221254
for (const auto& elem : it_by_prev->second) {
222-
if (elem->second.fromPeer == nodeid) {
255+
if (elem->second.announcers.contains(nodeid)) {
223256
iters.emplace_back(elem);
224257
}
225258
}
@@ -258,7 +291,7 @@ std::vector<std::pair<CTransactionRef, NodeId>> TxOrphanage::GetChildrenFromDiff
258291
const auto it_by_prev = m_outpoint_to_orphan_it.find(COutPoint(parent->GetHash(), i));
259292
if (it_by_prev != m_outpoint_to_orphan_it.end()) {
260293
for (const auto& elem : it_by_prev->second) {
261-
if (elem->second.fromPeer != nodeid) {
294+
if (!elem->second.announcers.contains(nodeid)) {
262295
iters.emplace_back(elem);
263296
}
264297
}
@@ -273,7 +306,9 @@ std::vector<std::pair<CTransactionRef, NodeId>> TxOrphanage::GetChildrenFromDiff
273306
std::vector<std::pair<CTransactionRef, NodeId>> children_found;
274307
children_found.reserve(iters.size());
275308
for (const auto& child_iter : iters) {
276-
children_found.emplace_back(child_iter->second.tx, child_iter->second.fromPeer);
309+
// Use first peer in announcers list
310+
auto peer = *(child_iter->second.announcers.begin());
311+
children_found.emplace_back(child_iter->second.tx, peer);
277312
}
278313
return children_found;
279314
}
@@ -283,7 +318,7 @@ std::vector<TxOrphanage::OrphanTxBase> TxOrphanage::GetOrphanTransactions() cons
283318
std::vector<OrphanTxBase> ret;
284319
ret.reserve(m_orphans.size());
285320
for (auto const& o : m_orphans) {
286-
ret.push_back({o.second.tx, o.second.fromPeer, o.second.nTimeExpire});
321+
ret.push_back({o.second.tx, o.second.announcers, o.second.nTimeExpire});
287322
}
288323
return ret;
289324
}

src/txorphanage.h

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,15 @@ class TxOrphanage {
3030
/** Add a new orphan transaction */
3131
bool AddTx(const CTransactionRef& tx, NodeId peer);
3232

33+
/** Add an additional announcer to an orphan if it exists. Otherwise, do nothing. */
34+
bool AddAnnouncer(const Wtxid& wtxid, NodeId peer);
35+
3336
/** Check if we already have an orphan transaction (by wtxid only) */
3437
bool HaveTx(const Wtxid& wtxid) const;
3538

39+
/** Check if a {tx, peer} exists in the orphanage.*/
40+
bool HaveTxFromPeer(const Wtxid& wtxid, NodeId peer) const;
41+
3642
/** Extract a transaction from a peer's work set
3743
* Returns nullptr if there are no transactions to work on.
3844
* Otherwise returns the transaction reference, and removes
@@ -43,7 +49,8 @@ class TxOrphanage {
4349
/** Erase an orphan by wtxid */
4450
int EraseTx(const Wtxid& wtxid);
4551

46-
/** Erase all orphans announced by a peer (eg, after that peer disconnects) */
52+
/** Maybe erase all orphans announced by a peer (eg, after that peer disconnects). If an orphan
53+
* has been announced by another peer, don't erase, just remove this peer from the list of announcers. */
4754
void EraseForPeer(NodeId peer);
4855

4956
/** Erase all orphans included in or invalidated by a new block */
@@ -75,7 +82,8 @@ class TxOrphanage {
7582
/** Allows providing orphan information externally */
7683
struct OrphanTxBase {
7784
CTransactionRef tx;
78-
NodeId fromPeer;
85+
/** Peers added with AddTx or AddAnnouncer. */
86+
std::set<NodeId> announcers;
7987
NodeSeconds nTimeExpire;
8088
};
8189

0 commit comments

Comments
 (0)