Bitcoin Core  27.99.0
P2P Digital Currency
txorphanage.cpp
Go to the documentation of this file.
1 // Copyright (c) 2021-2022 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 #include <primitives/transaction.h>
11 
12 #include <cassert>
13 
15 static constexpr int64_t ORPHAN_TX_EXPIRE_TIME = 20 * 60;
17 static constexpr int64_t ORPHAN_TX_EXPIRE_INTERVAL = 5 * 60;
18 
19 
21 {
22  LOCK(m_mutex);
23 
24  const Txid& hash = tx->GetHash();
25  const Wtxid& wtxid = tx->GetWitnessHash();
26  if (m_orphans.count(hash))
27  return false;
28 
29  // Ignore big transactions, to avoid a
30  // send-big-orphans memory exhaustion attack. If a peer has a legitimate
31  // large transaction with a missing parent then we assume
32  // it will rebroadcast it later, after the parent transaction(s)
33  // have been mined or received.
34  // 100 orphans, each of which is at most 100,000 bytes big is
35  // at most 10 megabytes of orphans and somewhat more byprev index (in the worst case):
36  unsigned int sz = GetTransactionWeight(*tx);
37  if (sz > MAX_STANDARD_TX_WEIGHT)
38  {
39  LogPrint(BCLog::TXPACKAGES, "ignoring large orphan tx (size: %u, txid: %s, wtxid: %s)\n", sz, hash.ToString(), wtxid.ToString());
40  return false;
41  }
42 
43  auto ret = m_orphans.emplace(hash, OrphanTx{tx, peer, GetTime() + ORPHAN_TX_EXPIRE_TIME, m_orphan_list.size()});
44  assert(ret.second);
45  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);
48  for (const CTxIn& txin : tx->vin) {
49  m_outpoint_to_orphan_it[txin.prevout].insert(ret.first);
50  }
51 
52  LogPrint(BCLog::TXPACKAGES, "stored orphan tx %s (wtxid=%s) (mapsz %u outsz %u)\n", hash.ToString(), wtxid.ToString(),
53  m_orphans.size(), m_outpoint_to_orphan_it.size());
54  return true;
55 }
56 
57 int TxOrphanage::EraseTx(const Txid& txid)
58 {
59  LOCK(m_mutex);
60  return EraseTxNoLock(txid);
61 }
62 
64 {
66  std::map<Txid, OrphanTx>::iterator it = m_orphans.find(txid);
67  if (it == m_orphans.end())
68  return 0;
69  for (const CTxIn& txin : it->second.tx->vin)
70  {
71  auto itPrev = m_outpoint_to_orphan_it.find(txin.prevout);
72  if (itPrev == m_outpoint_to_orphan_it.end())
73  continue;
74  itPrev->second.erase(it);
75  if (itPrev->second.empty())
76  m_outpoint_to_orphan_it.erase(itPrev);
77  }
78 
79  size_t old_pos = it->second.list_pos;
80  assert(m_orphan_list[old_pos] == it);
81  if (old_pos + 1 != m_orphan_list.size()) {
82  // Unless we're deleting the last entry in m_orphan_list, move the last
83  // entry to the position we're deleting.
84  auto it_last = m_orphan_list.back();
85  m_orphan_list[old_pos] = it_last;
86  it_last->second.list_pos = old_pos;
87  }
88  const auto& wtxid = it->second.tx->GetWitnessHash();
89  LogPrint(BCLog::TXPACKAGES, " removed orphan tx %s (wtxid=%s)\n", txid.ToString(), wtxid.ToString());
90  m_orphan_list.pop_back();
91  m_wtxid_to_orphan_it.erase(it->second.tx->GetWitnessHash());
92 
93  m_orphans.erase(it);
94  return 1;
95 }
96 
98 {
99  LOCK(m_mutex);
100 
101  m_peer_work_set.erase(peer);
102 
103  int nErased = 0;
104  std::map<Txid, OrphanTx>::iterator iter = m_orphans.begin();
105  while (iter != m_orphans.end())
106  {
107  std::map<Txid, OrphanTx>::iterator maybeErase = iter++; // increment to avoid iterator becoming invalid
108  if (maybeErase->second.fromPeer == peer)
109  {
110  nErased += EraseTxNoLock(maybeErase->second.tx->GetHash());
111  }
112  }
113  if (nErased > 0) LogPrint(BCLog::TXPACKAGES, "Erased %d orphan tx from peer=%d\n", nErased, peer);
114 }
115 
116 void TxOrphanage::LimitOrphans(unsigned int max_orphans, FastRandomContext& rng)
117 {
118  LOCK(m_mutex);
119 
120  unsigned int nEvicted = 0;
121  static int64_t nNextSweep;
122  int64_t nNow = GetTime();
123  if (nNextSweep <= nNow) {
124  // Sweep out expired orphan pool entries:
125  int nErased = 0;
126  int64_t nMinExpTime = nNow + ORPHAN_TX_EXPIRE_TIME - ORPHAN_TX_EXPIRE_INTERVAL;
127  std::map<Txid, OrphanTx>::iterator iter = m_orphans.begin();
128  while (iter != m_orphans.end())
129  {
130  std::map<Txid, OrphanTx>::iterator maybeErase = iter++;
131  if (maybeErase->second.nTimeExpire <= nNow) {
132  nErased += EraseTxNoLock(maybeErase->second.tx->GetHash());
133  } else {
134  nMinExpTime = std::min(maybeErase->second.nTimeExpire, nMinExpTime);
135  }
136  }
137  // Sweep again 5 minutes after the next entry that expires in order to batch the linear scan.
138  nNextSweep = nMinExpTime + ORPHAN_TX_EXPIRE_INTERVAL;
139  if (nErased > 0) LogPrint(BCLog::TXPACKAGES, "Erased %d orphan tx due to expiration\n", nErased);
140  }
141  while (m_orphans.size() > max_orphans)
142  {
143  // Evict a random orphan:
144  size_t randompos = rng.randrange(m_orphan_list.size());
145  EraseTxNoLock(m_orphan_list[randompos]->first);
146  ++nEvicted;
147  }
148  if (nEvicted > 0) LogPrint(BCLog::TXPACKAGES, "orphanage overflow, removed %u tx\n", nEvicted);
149 }
150 
152 {
153  LOCK(m_mutex);
154 
155 
156  for (unsigned int i = 0; i < tx.vout.size(); i++) {
157  const auto it_by_prev = m_outpoint_to_orphan_it.find(COutPoint(tx.GetHash(), i));
158  if (it_by_prev != m_outpoint_to_orphan_it.end()) {
159  for (const auto& elem : it_by_prev->second) {
160  // Get this source peer's work set, emplacing an empty set if it didn't exist
161  // (note: if this peer wasn't still connected, we would have removed the orphan tx already)
162  std::set<Txid>& orphan_work_set = m_peer_work_set.try_emplace(elem->second.fromPeer).first->second;
163  // Add this tx to the work set
164  orphan_work_set.insert(elem->first);
165  LogPrint(BCLog::TXPACKAGES, "added %s (wtxid=%s) to peer %d workset\n",
166  tx.GetHash().ToString(), tx.GetWitnessHash().ToString(), elem->second.fromPeer);
167  }
168  }
169  }
170 }
171 
172 bool TxOrphanage::HaveTx(const GenTxid& gtxid) const
173 {
174  LOCK(m_mutex);
175  if (gtxid.IsWtxid()) {
176  return m_wtxid_to_orphan_it.count(Wtxid::FromUint256(gtxid.GetHash()));
177  } else {
178  return m_orphans.count(Txid::FromUint256(gtxid.GetHash()));
179  }
180 }
181 
183 {
184  LOCK(m_mutex);
185 
186  auto work_set_it = m_peer_work_set.find(peer);
187  if (work_set_it != m_peer_work_set.end()) {
188  auto& work_set = work_set_it->second;
189  while (!work_set.empty()) {
190  Txid txid = *work_set.begin();
191  work_set.erase(work_set.begin());
192 
193  const auto orphan_it = m_orphans.find(txid);
194  if (orphan_it != m_orphans.end()) {
195  return orphan_it->second.tx;
196  }
197  }
198  }
199  return nullptr;
200 }
201 
203 {
204  LOCK(m_mutex);
205 
206  auto work_set_it = m_peer_work_set.find(peer);
207  if (work_set_it != m_peer_work_set.end()) {
208  auto& work_set = work_set_it->second;
209  return !work_set.empty();
210  }
211  return false;
212 }
213 
215 {
216  LOCK(m_mutex);
217 
218  std::vector<Txid> vOrphanErase;
219 
220  for (const CTransactionRef& ptx : block.vtx) {
221  const CTransaction& tx = *ptx;
222 
223  // Which orphan pool entries must we evict?
224  for (const auto& txin : tx.vin) {
225  auto itByPrev = m_outpoint_to_orphan_it.find(txin.prevout);
226  if (itByPrev == m_outpoint_to_orphan_it.end()) continue;
227  for (auto mi = itByPrev->second.begin(); mi != itByPrev->second.end(); ++mi) {
228  const CTransaction& orphanTx = *(*mi)->second.tx;
229  const auto& orphanHash = orphanTx.GetHash();
230  vOrphanErase.push_back(orphanHash);
231  }
232  }
233  }
234 
235  // Erase orphan transactions included or precluded by this block
236  if (vOrphanErase.size()) {
237  int nErased = 0;
238  for (const auto& orphanHash : vOrphanErase) {
239  nErased += EraseTxNoLock(orphanHash);
240  }
241  LogPrint(BCLog::TXPACKAGES, "Erased %d orphan tx included or conflicted by block\n", nErased);
242  }
243 }
int ret
Definition: block.h:69
std::vector< CTransactionRef > vtx
Definition: block.h:72
An outpoint - a combination of a transaction hash and an index n into its vout.
Definition: transaction.h:29
The basic transaction that is broadcasted on the network and contained in blocks.
Definition: transaction.h:296
const Txid & GetHash() const LIFETIMEBOUND
Definition: transaction.h:343
const Wtxid & GetWitnessHash() const LIFETIMEBOUND
Definition: transaction.h:344
const std::vector< CTxOut > vout
Definition: transaction.h:307
const std::vector< CTxIn > vin
Definition: transaction.h:306
An input of a transaction.
Definition: transaction.h:67
COutPoint prevout
Definition: transaction.h:69
Fast randomness source.
Definition: random.h:145
uint64_t randrange(uint64_t range) noexcept
Generate a random integer in the range [0..range).
Definition: random.h:203
A generic txid reference (txid or wtxid).
Definition: transaction.h:428
bool IsWtxid() const
Definition: transaction.h:436
const uint256 & GetHash() const LIFETIMEBOUND
Definition: transaction.h:437
bool HaveTxToReconsider(NodeId peer) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
Does this peer have any work to do?
int EraseTxNoLock(const Txid &txid) EXCLUSIVE_LOCKS_REQUIRED(m_mutex)
Erase an orphan by txid.
Definition: txorphanage.cpp:63
void AddChildrenToWorkSet(const CTransaction &tx) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
Add any orphans that list a particular tx as a parent into the from peer's work set.
void LimitOrphans(unsigned int max_orphans, FastRandomContext &rng) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
Limit the orphanage to the given maximum.
void EraseForBlock(const CBlock &block) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
Erase all orphans included in or invalidated by a new block.
Mutex m_mutex
Guards orphan transactions.
Definition: txorphanage.h:63
bool AddTx(const CTransactionRef &tx, NodeId peer) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
Add a new orphan transaction.
Definition: txorphanage.cpp:20
bool HaveTx(const GenTxid &gtxid) const EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
Check if we already have an orphan transaction (by txid or wtxid)
CTransactionRef GetTxToReconsider(NodeId peer) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
Extract a transaction from a peer's work set Returns nullptr if there are no transactions to work on.
int EraseTx(const Txid &txid) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
Erase an orphan by txid.
Definition: txorphanage.cpp:57
void EraseForPeer(NodeId peer) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
Erase all orphans announced by a peer (eg, after that peer disconnects)
Definition: txorphanage.cpp:97
std::string ToString() const
constexpr const std::byte * begin() const
static transaction_identifier FromUint256(const uint256 &id)
static int32_t GetTransactionWeight(const CTransaction &tx)
Definition: validation.h:149
#define LogPrint(category,...)
Definition: logging.h:263
@ TXPACKAGES
Definition: logging.h:71
int64_t NodeId
Definition: net.h:97
static constexpr int32_t MAX_STANDARD_TX_WEIGHT
The maximum weight for transactions we're willing to relay/mine.
Definition: policy.h:27
std::shared_ptr< const CTransaction > CTransactionRef
Definition: transaction.h:423
#define LOCK(cs)
Definition: sync.h:257
int64_t GetTime()
Definition: time.cpp:48
static constexpr int64_t ORPHAN_TX_EXPIRE_INTERVAL
Minimum time between orphan transactions expire time checks in seconds.
Definition: txorphanage.cpp:17
static constexpr int64_t ORPHAN_TX_EXPIRE_TIME
Expiration time for orphan transactions in seconds.
Definition: txorphanage.cpp:15
AssertLockHeld(pool.cs)
assert(!tx.IsCoinBase())