txorphanage.cpp raw

   1  // Copyright (c) 2021-2022 The Limenka 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  #include <util/time.h>
  12  
  13  #include <cassert>
  14  
  15  bool TxOrphanage::AddTx(const CTransactionRef& tx, NodeId peer)
  16  {
  17      const Txid& hash = tx->GetHash();
  18      const Wtxid& wtxid = tx->GetWitnessHash();
  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.
  22          return false;
  23      }
  24  
  25      // Ignore big transactions, to avoid a
  26      // send-big-orphans memory exhaustion attack. If a peer has a legitimate
  27      // large transaction with a missing parent then we assume
  28      // it will rebroadcast it later, after the parent transaction(s)
  29      // have been mined or received.
  30      // 100 orphans, each of which is at most 100,000 bytes big is
  31      // at most 10 megabytes of orphans and somewhat more byprev index (in the worst case):
  32      unsigned int sz = GetTransactionWeight(*tx);
  33      if (sz > MAX_STANDARD_TX_WEIGHT)
  34      {
  35          LogDebug(BCLog::TXPACKAGES, "ignoring large orphan tx (size: %u, txid: %s, wtxid: %s)\n", sz, hash.ToString(), wtxid.ToString());
  36          return false;
  37      }
  38  
  39      auto ret = m_orphans.emplace(wtxid, OrphanTx{{tx, {peer}, Now<NodeSeconds>() + ORPHAN_TX_EXPIRE_TIME}, m_orphan_list.size()});
  40      assert(ret.second);
  41      m_orphan_list.push_back(ret.first);
  42      for (const CTxIn& txin : tx->vin) {
  43          m_outpoint_to_orphan_it[txin.prevout].insert(ret.first);
  44      }
  45      m_total_orphan_usage += sz;
  46      m_total_announcements += 1;
  47      auto& peer_info = m_peer_orphanage_info.try_emplace(peer).first->second;
  48      peer_info.m_total_usage += sz;
  49  
  50      LogDebug(BCLog::TXPACKAGES, "stored orphan tx %s (wtxid=%s), weight: %u (mapsz %u outsz %u)\n", hash.ToString(), wtxid.ToString(), sz,
  51               m_orphans.size(), m_outpoint_to_orphan_it.size());
  52      return true;
  53  }
  54  
  55  bool TxOrphanage::AddAnnouncer(const Wtxid& wtxid, NodeId peer)
  56  {
  57      const auto it = m_orphans.find(wtxid);
  58      if (it != m_orphans.end()) {
  59          Assume(!it->second.announcers.empty());
  60          const auto ret = it->second.announcers.insert(peer);
  61          if (ret.second) {
  62              auto& peer_info = m_peer_orphanage_info.try_emplace(peer).first->second;
  63              peer_info.m_total_usage += it->second.GetUsage();
  64              m_total_announcements += 1;
  65              LogDebug(BCLog::TXPACKAGES, "added peer=%d as announcer of orphan tx %s\n", peer, wtxid.ToString());
  66              return true;
  67          }
  68      }
  69      return false;
  70  }
  71  
  72  int TxOrphanage::EraseTx(const Wtxid& wtxid)
  73  {
  74      std::map<Wtxid, OrphanTx>::iterator it = m_orphans.find(wtxid);
  75      if (it == m_orphans.end())
  76          return 0;
  77      for (const CTxIn& txin : it->second.tx->vin)
  78      {
  79          auto itPrev = m_outpoint_to_orphan_it.find(txin.prevout);
  80          if (itPrev == m_outpoint_to_orphan_it.end())
  81              continue;
  82          itPrev->second.erase(it);
  83          if (itPrev->second.empty())
  84              m_outpoint_to_orphan_it.erase(itPrev);
  85      }
  86  
  87      const auto tx_size{it->second.GetUsage()};
  88      m_total_orphan_usage -= tx_size;
  89      m_total_announcements -= it->second.announcers.size();
  90      // Decrement each announcer's m_total_usage
  91      for (const auto& peer : it->second.announcers) {
  92          auto peer_it = m_peer_orphanage_info.find(peer);
  93          if (Assume(peer_it != m_peer_orphanage_info.end())) {
  94              peer_it->second.m_total_usage -= tx_size;
  95          }
  96      }
  97  
  98      size_t old_pos = it->second.list_pos;
  99      assert(m_orphan_list[old_pos] == it);
 100      if (old_pos + 1 != m_orphan_list.size()) {
 101          // Unless we're deleting the last entry in m_orphan_list, move the last
 102          // entry to the position we're deleting.
 103          auto it_last = m_orphan_list.back();
 104          m_orphan_list[old_pos] = it_last;
 105          it_last->second.list_pos = old_pos;
 106      }
 107      const auto& txid = it->second.tx->GetHash();
 108      // Time spent in orphanage = difference between current and entry time.
 109      // Entry time is equal to ORPHAN_TX_EXPIRE_TIME earlier than entry's expiry.
 110      LogDebug(BCLog::TXPACKAGES, "   removed orphan tx %s (wtxid=%s) after %ds\n", txid.ToString(), wtxid.ToString(),
 111               Ticks<std::chrono::seconds>(NodeClock::now() + ORPHAN_TX_EXPIRE_TIME - it->second.nTimeExpire));
 112      m_orphan_list.pop_back();
 113  
 114      m_orphans.erase(it);
 115      return 1;
 116  }
 117  
 118  void TxOrphanage::EraseForPeer(NodeId peer)
 119  {
 120      // Zeroes out this peer's m_total_usage.
 121      m_peer_orphanage_info.erase(peer);
 122  
 123      int nErased = 0;
 124      std::map<Wtxid, OrphanTx>::iterator iter = m_orphans.begin();
 125      while (iter != m_orphans.end())
 126      {
 127          // increment to avoid iterator becoming invalid after erasure
 128          auto& [wtxid, orphan] = *iter++;
 129          auto orphan_it = orphan.announcers.find(peer);
 130          if (orphan_it != orphan.announcers.end()) {
 131              orphan.announcers.erase(peer);
 132              m_total_announcements -= 1;
 133  
 134              // No remaining announcers: clean up entry
 135              if (orphan.announcers.empty()) {
 136                  nErased += EraseTx(orphan.tx->GetWitnessHash());
 137              }
 138          }
 139      }
 140      if (nErased > 0) LogDebug(BCLog::TXPACKAGES, "Erased %d orphan transaction(s) from peer=%d\n", nErased, peer);
 141  }
 142  
 143  void TxOrphanage::LimitOrphans(unsigned int max_orphans, FastRandomContext& rng)
 144  {
 145      unsigned int nEvicted = 0;
 146      auto nNow{Now<NodeSeconds>()};
 147      if (m_next_sweep <= nNow) {
 148          // Sweep out expired orphan pool entries:
 149          int nErased = 0;
 150          auto nMinExpTime{nNow + ORPHAN_TX_EXPIRE_TIME - ORPHAN_TX_EXPIRE_INTERVAL};
 151          std::map<Wtxid, OrphanTx>::iterator iter = m_orphans.begin();
 152          while (iter != m_orphans.end())
 153          {
 154              std::map<Wtxid, OrphanTx>::iterator maybeErase = iter++;
 155              if (maybeErase->second.nTimeExpire <= nNow) {
 156                  nErased += EraseTx(maybeErase->first);
 157              } else {
 158                  nMinExpTime = std::min(maybeErase->second.nTimeExpire, nMinExpTime);
 159              }
 160          }
 161          // Sweep again 5 minutes after the next entry that expires in order to batch the linear scan.
 162          m_next_sweep = nMinExpTime + ORPHAN_TX_EXPIRE_INTERVAL;
 163          if (nErased > 0) LogDebug(BCLog::TXPACKAGES, "Erased %d orphan tx due to expiration\n", nErased);
 164      }
 165      while (m_orphans.size() > max_orphans)
 166      {
 167          // Evict a random orphan:
 168          size_t randompos = rng.randrange(m_orphan_list.size());
 169          EraseTx(m_orphan_list[randompos]->first);
 170          ++nEvicted;
 171      }
 172      if (nEvicted > 0) LogDebug(BCLog::TXPACKAGES, "orphanage overflow, removed %u tx\n", nEvicted);
 173  }
 174  
 175  void TxOrphanage::AddChildrenToWorkSet(const CTransaction& tx, FastRandomContext& rng)
 176  {
 177      for (unsigned int i = 0; i < tx.vout.size(); i++) {
 178          const auto it_by_prev = m_outpoint_to_orphan_it.find(COutPoint(tx.GetHash(), i));
 179          if (it_by_prev != m_outpoint_to_orphan_it.end()) {
 180              for (const auto& elem : it_by_prev->second) {
 181                  // Belt and suspenders, each orphan should always have at least 1 announcer.
 182                  if (!Assume(!elem->second.announcers.empty())) continue;
 183  
 184                  // Select a random peer to assign orphan processing, reducing wasted work if the orphan is still missing
 185                  // inputs. However, we don't want to create an issue in which the assigned peer can purposefully stop us
 186                  // from processing the orphan by disconnecting.
 187                  auto announcer_iter = std::begin(elem->second.announcers);
 188                  std::advance(announcer_iter, rng.randrange(elem->second.announcers.size()));
 189                  auto announcer = *(announcer_iter);
 190  
 191                  // Get this source peer's work set, emplacing an empty set if it didn't exist
 192                  // (note: if this peer wasn't still connected, we would have removed the orphan tx already)
 193                  std::set<Wtxid>& orphan_work_set = m_peer_orphanage_info.try_emplace(announcer).first->second.m_work_set;
 194                  // Add this tx to the work set
 195                  orphan_work_set.insert(elem->first);
 196                  LogDebug(BCLog::TXPACKAGES, "added %s (wtxid=%s) to peer %d workset\n",
 197                           tx.GetHash().ToString(), tx.GetWitnessHash().ToString(), announcer);
 198              }
 199          }
 200      }
 201  }
 202  
 203  bool TxOrphanage::HaveTx(const Wtxid& wtxid) const
 204  {
 205      return m_orphans.count(wtxid);
 206  }
 207  
 208  CTransactionRef TxOrphanage::GetTx(const Wtxid& wtxid) const
 209  {
 210      auto it = m_orphans.find(wtxid);
 211      return it != m_orphans.end() ? it->second.tx : nullptr;
 212  }
 213  
 214  bool TxOrphanage::HaveTxFromPeer(const Wtxid& wtxid, NodeId peer) const
 215  {
 216      auto it = m_orphans.find(wtxid);
 217      return (it != m_orphans.end() && it->second.announcers.contains(peer));
 218  }
 219  
 220  CTransactionRef TxOrphanage::GetTxToReconsider(NodeId peer)
 221  {
 222      auto peer_it = m_peer_orphanage_info.find(peer);
 223      if (peer_it == m_peer_orphanage_info.end()) return nullptr;
 224  
 225      auto& work_set = peer_it->second.m_work_set;
 226      while (!work_set.empty()) {
 227          Wtxid wtxid = *work_set.begin();
 228          work_set.erase(work_set.begin());
 229  
 230          const auto orphan_it = m_orphans.find(wtxid);
 231          if (orphan_it != m_orphans.end()) {
 232              return orphan_it->second.tx;
 233          }
 234      }
 235      return nullptr;
 236  }
 237  
 238  bool TxOrphanage::HaveTxToReconsider(NodeId peer)
 239  {
 240      auto peer_it = m_peer_orphanage_info.find(peer);
 241      if (peer_it == m_peer_orphanage_info.end()) return false;
 242  
 243      auto& work_set = peer_it->second.m_work_set;
 244      return !work_set.empty();
 245  }
 246  
 247  void TxOrphanage::EraseForBlock(const CBlock& block)
 248  {
 249      if (m_orphans.empty()) return;
 250  
 251      std::vector<Wtxid> vOrphanErase;
 252  
 253      for (const CTransactionRef& ptx : block.vtx) {
 254          const CTransaction& tx = *ptx;
 255  
 256          // Which orphan pool entries must we evict?
 257          for (const auto& txin : tx.vin) {
 258              auto itByPrev = m_outpoint_to_orphan_it.find(txin.prevout);
 259              if (itByPrev == m_outpoint_to_orphan_it.end()) continue;
 260              for (auto mi = itByPrev->second.begin(); mi != itByPrev->second.end(); ++mi) {
 261                  const CTransaction& orphanTx = *(*mi)->second.tx;
 262                  vOrphanErase.push_back(orphanTx.GetWitnessHash());
 263              }
 264          }
 265      }
 266  
 267      // Erase orphan transactions included or precluded by this block
 268      if (vOrphanErase.size()) {
 269          int nErased = 0;
 270          for (const auto& orphanHash : vOrphanErase) {
 271              nErased += EraseTx(orphanHash);
 272          }
 273          LogDebug(BCLog::TXPACKAGES, "Erased %d orphan transaction(s) included or conflicted by block\n", nErased);
 274      }
 275  }
 276  
 277  std::vector<CTransactionRef> TxOrphanage::GetChildrenFromSamePeer(const CTransactionRef& parent, NodeId nodeid) const
 278  {
 279      // First construct a vector of iterators to ensure we do not return duplicates of the same tx
 280      // and so we can sort by nTimeExpire.
 281      std::vector<OrphanMap::iterator> iters;
 282  
 283      // For each output, get all entries spending this prevout, filtering for ones from the specified peer.
 284      for (unsigned int i = 0; i < parent->vout.size(); i++) {
 285          const auto it_by_prev = m_outpoint_to_orphan_it.find(COutPoint(parent->GetHash(), i));
 286          if (it_by_prev != m_outpoint_to_orphan_it.end()) {
 287              for (const auto& elem : it_by_prev->second) {
 288                  if (elem->second.announcers.contains(nodeid)) {
 289                      iters.emplace_back(elem);
 290                  }
 291              }
 292          }
 293      }
 294  
 295      // Sort by address so that duplicates can be deleted. At the same time, sort so that more recent
 296      // orphans (which expire later) come first.  Break ties based on address, as nTimeExpire is
 297      // quantified in seconds and it is possible for orphans to have the same expiry.
 298      std::sort(iters.begin(), iters.end(), [](const auto& lhs, const auto& rhs) {
 299          if (lhs->second.nTimeExpire == rhs->second.nTimeExpire) {
 300              return &(*lhs) < &(*rhs);
 301          } else {
 302              return lhs->second.nTimeExpire > rhs->second.nTimeExpire;
 303          }
 304      });
 305      // Erase duplicates
 306      iters.erase(std::unique(iters.begin(), iters.end()), iters.end());
 307  
 308      // Convert to a vector of CTransactionRef
 309      std::vector<CTransactionRef> children_found;
 310      children_found.reserve(iters.size());
 311      for (const auto& child_iter : iters) {
 312          children_found.emplace_back(child_iter->second.tx);
 313      }
 314      return children_found;
 315  }
 316  
 317  std::vector<TxOrphanage::OrphanTxBase> TxOrphanage::GetOrphanTransactions() const
 318  {
 319      std::vector<OrphanTxBase> ret;
 320      ret.reserve(m_orphans.size());
 321      for (auto const& o : m_orphans) {
 322          ret.push_back({o.second.tx, o.second.announcers, o.second.nTimeExpire});
 323      }
 324      return ret;
 325  }
 326  
 327  void TxOrphanage::SanityCheck() const
 328  {
 329      // Check that cached m_total_announcements is correct
 330      unsigned int counted_total_announcements{0};
 331      // Check that m_total_orphan_usage is correct
 332      unsigned int counted_total_usage{0};
 333  
 334      // Check that cached PeerOrphanInfo::m_total_size is correct
 335      std::map<NodeId, unsigned int> counted_size_per_peer;
 336  
 337      for (const auto& [wtxid, orphan] : m_orphans) {
 338          counted_total_announcements += orphan.announcers.size();
 339          counted_total_usage += orphan.GetUsage();
 340  
 341          Assume(!orphan.announcers.empty());
 342          for (const auto& peer : orphan.announcers) {
 343              auto& count_peer_entry = counted_size_per_peer.try_emplace(peer).first->second;
 344              count_peer_entry += orphan.GetUsage();
 345          }
 346      }
 347  
 348      Assume(m_total_announcements >= m_orphans.size());
 349      Assume(counted_total_announcements == m_total_announcements);
 350      Assume(counted_total_usage == m_total_orphan_usage);
 351  
 352      // There must be an entry in m_peer_orphanage_info for each peer
 353      // However, there may be m_peer_orphanage_info entries corresponding to peers for whom we
 354      // previously had orphans but no longer do.
 355      Assume(counted_size_per_peer.size() <= m_peer_orphanage_info.size());
 356  
 357      for (const auto& [peerid, info] : m_peer_orphanage_info) {
 358          auto it_counted = counted_size_per_peer.find(peerid);
 359          if (it_counted == counted_size_per_peer.end()) {
 360              Assume(info.m_total_usage == 0);
 361          } else {
 362              Assume(it_counted->second == info.m_total_usage);
 363          }
 364      }
 365  }
 366