txorphanage.cpp
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 14 /** Expiration time for orphan transactions in seconds */ 15 static constexpr int64_t ORPHAN_TX_EXPIRE_TIME = 20 * 60; 16 /** Minimum time between orphan transactions expire time checks in seconds */ 17 static constexpr int64_t ORPHAN_TX_EXPIRE_INTERVAL = 5 * 60; 18 19 20 bool TxOrphanage::AddTx(const CTransactionRef& tx, NodeId peer) 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 63 int TxOrphanage::EraseTxNoLock(const Txid& txid) 64 { 65 AssertLockHeld(m_mutex); 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 97 void TxOrphanage::EraseForPeer(NodeId peer) 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 151 void TxOrphanage::AddChildrenToWorkSet(const CTransaction& tx) 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 182 CTransactionRef TxOrphanage::GetTxToReconsider(NodeId peer) 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 202 bool TxOrphanage::HaveTxToReconsider(NodeId peer) 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 214 void TxOrphanage::EraseForBlock(const CBlock& block) 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 }