mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-01-13 13:22:38 -03:00
77a36033b5
c58c249a5b
net_processing: indicate more work to do when orphans are ready to reconsider (Anthony Towns)ecb0a3e425
net_processing: Don't process tx after processing orphans (Anthony Towns)c583775706
net_processing: only process orphans before messages (Anthony Towns)be2304676b
txorphange: Drop redundant originator arg from GetTxToReconsider (Anthony Towns)a4fe09973a
txorphanage: index workset by originating peer (Anthony Towns) Pull request description: We currently process orphans by assigning them to the peer that provided a missing parent; instead assign them to the peer that provided the orphan in the first place. This prevents a peer from being able to marginally delay another peer's transactions and also simplifies the internal API slightly. Because we're now associating orphan processing with the peer that provided the orphan originally, we no longer process orphans immediately after receiving the parent, but defer until a future call to `ProcessMessage`. Based on #26295 ACKs for top commit: naumenkogs: utACKc58c249a5b
glozow: ACKc58c249a5b
mzumsande: Code Review ACKc58c249a5b
Tree-SHA512: 3186c346f21e60440266a2a80a9d23d7b96071414e14b2b3bfe50457c04c18b1eab109c3d8c2a7726a6b10a2eda1f0512510a52c102da112820a26f5d96f12de
238 lines
8 KiB
C++
238 lines
8 KiB
C++
// Copyright (c) 2021-2022 The Bitcoin Core developers
|
|
// Distributed under the MIT software license, see the accompanying
|
|
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
|
|
|
#include <txorphanage.h>
|
|
|
|
#include <consensus/validation.h>
|
|
#include <logging.h>
|
|
#include <policy/policy.h>
|
|
|
|
#include <cassert>
|
|
|
|
/** Expiration time for orphan transactions in seconds */
|
|
static constexpr int64_t ORPHAN_TX_EXPIRE_TIME = 20 * 60;
|
|
/** Minimum time between orphan transactions expire time checks in seconds */
|
|
static constexpr int64_t ORPHAN_TX_EXPIRE_INTERVAL = 5 * 60;
|
|
|
|
|
|
bool TxOrphanage::AddTx(const CTransactionRef& tx, NodeId peer)
|
|
{
|
|
LOCK(m_mutex);
|
|
|
|
const uint256& hash = tx->GetHash();
|
|
if (m_orphans.count(hash))
|
|
return false;
|
|
|
|
// Ignore big transactions, to avoid a
|
|
// send-big-orphans memory exhaustion attack. If a peer has a legitimate
|
|
// large transaction with a missing parent then we assume
|
|
// it will rebroadcast it later, after the parent transaction(s)
|
|
// have been mined or received.
|
|
// 100 orphans, each of which is at most 100,000 bytes big is
|
|
// at most 10 megabytes of orphans and somewhat more byprev index (in the worst case):
|
|
unsigned int sz = GetTransactionWeight(*tx);
|
|
if (sz > MAX_STANDARD_TX_WEIGHT)
|
|
{
|
|
LogPrint(BCLog::MEMPOOL, "ignoring large orphan tx (size: %u, hash: %s)\n", sz, hash.ToString());
|
|
return false;
|
|
}
|
|
|
|
auto ret = m_orphans.emplace(hash, OrphanTx{tx, peer, GetTime() + ORPHAN_TX_EXPIRE_TIME, m_orphan_list.size()});
|
|
assert(ret.second);
|
|
m_orphan_list.push_back(ret.first);
|
|
// Allow for lookups in the orphan pool by wtxid, as well as txid
|
|
m_wtxid_to_orphan_it.emplace(tx->GetWitnessHash(), ret.first);
|
|
for (const CTxIn& txin : tx->vin) {
|
|
m_outpoint_to_orphan_it[txin.prevout].insert(ret.first);
|
|
}
|
|
|
|
LogPrint(BCLog::MEMPOOL, "stored orphan tx %s (mapsz %u outsz %u)\n", hash.ToString(),
|
|
m_orphans.size(), m_outpoint_to_orphan_it.size());
|
|
return true;
|
|
}
|
|
|
|
int TxOrphanage::EraseTx(const uint256& txid)
|
|
{
|
|
LOCK(m_mutex);
|
|
return _EraseTx(txid);
|
|
}
|
|
|
|
int TxOrphanage::_EraseTx(const uint256& txid)
|
|
{
|
|
AssertLockHeld(m_mutex);
|
|
std::map<uint256, OrphanTx>::iterator it = m_orphans.find(txid);
|
|
if (it == m_orphans.end())
|
|
return 0;
|
|
for (const CTxIn& txin : it->second.tx->vin)
|
|
{
|
|
auto itPrev = m_outpoint_to_orphan_it.find(txin.prevout);
|
|
if (itPrev == m_outpoint_to_orphan_it.end())
|
|
continue;
|
|
itPrev->second.erase(it);
|
|
if (itPrev->second.empty())
|
|
m_outpoint_to_orphan_it.erase(itPrev);
|
|
}
|
|
|
|
size_t old_pos = it->second.list_pos;
|
|
assert(m_orphan_list[old_pos] == it);
|
|
if (old_pos + 1 != m_orphan_list.size()) {
|
|
// Unless we're deleting the last entry in m_orphan_list, move the last
|
|
// entry to the position we're deleting.
|
|
auto it_last = m_orphan_list.back();
|
|
m_orphan_list[old_pos] = it_last;
|
|
it_last->second.list_pos = old_pos;
|
|
}
|
|
m_orphan_list.pop_back();
|
|
m_wtxid_to_orphan_it.erase(it->second.tx->GetWitnessHash());
|
|
|
|
m_orphans.erase(it);
|
|
return 1;
|
|
}
|
|
|
|
void TxOrphanage::EraseForPeer(NodeId peer)
|
|
{
|
|
LOCK(m_mutex);
|
|
|
|
m_peer_work_set.erase(peer);
|
|
|
|
int nErased = 0;
|
|
std::map<uint256, OrphanTx>::iterator iter = m_orphans.begin();
|
|
while (iter != m_orphans.end())
|
|
{
|
|
std::map<uint256, OrphanTx>::iterator maybeErase = iter++; // increment to avoid iterator becoming invalid
|
|
if (maybeErase->second.fromPeer == peer)
|
|
{
|
|
nErased += _EraseTx(maybeErase->second.tx->GetHash());
|
|
}
|
|
}
|
|
if (nErased > 0) LogPrint(BCLog::MEMPOOL, "Erased %d orphan tx from peer=%d\n", nErased, peer);
|
|
}
|
|
|
|
void TxOrphanage::LimitOrphans(unsigned int max_orphans)
|
|
{
|
|
LOCK(m_mutex);
|
|
|
|
unsigned int nEvicted = 0;
|
|
static int64_t nNextSweep;
|
|
int64_t nNow = GetTime();
|
|
if (nNextSweep <= nNow) {
|
|
// Sweep out expired orphan pool entries:
|
|
int nErased = 0;
|
|
int64_t nMinExpTime = nNow + ORPHAN_TX_EXPIRE_TIME - ORPHAN_TX_EXPIRE_INTERVAL;
|
|
std::map<uint256, OrphanTx>::iterator iter = m_orphans.begin();
|
|
while (iter != m_orphans.end())
|
|
{
|
|
std::map<uint256, OrphanTx>::iterator maybeErase = iter++;
|
|
if (maybeErase->second.nTimeExpire <= nNow) {
|
|
nErased += _EraseTx(maybeErase->second.tx->GetHash());
|
|
} else {
|
|
nMinExpTime = std::min(maybeErase->second.nTimeExpire, nMinExpTime);
|
|
}
|
|
}
|
|
// Sweep again 5 minutes after the next entry that expires in order to batch the linear scan.
|
|
nNextSweep = nMinExpTime + ORPHAN_TX_EXPIRE_INTERVAL;
|
|
if (nErased > 0) LogPrint(BCLog::MEMPOOL, "Erased %d orphan tx due to expiration\n", nErased);
|
|
}
|
|
FastRandomContext rng;
|
|
while (m_orphans.size() > max_orphans)
|
|
{
|
|
// Evict a random orphan:
|
|
size_t randompos = rng.randrange(m_orphan_list.size());
|
|
_EraseTx(m_orphan_list[randompos]->first);
|
|
++nEvicted;
|
|
}
|
|
if (nEvicted > 0) LogPrint(BCLog::MEMPOOL, "orphanage overflow, removed %u tx\n", nEvicted);
|
|
}
|
|
|
|
void TxOrphanage::AddChildrenToWorkSet(const CTransaction& tx)
|
|
{
|
|
LOCK(m_mutex);
|
|
|
|
|
|
for (unsigned int i = 0; i < tx.vout.size(); i++) {
|
|
const auto it_by_prev = m_outpoint_to_orphan_it.find(COutPoint(tx.GetHash(), i));
|
|
if (it_by_prev != m_outpoint_to_orphan_it.end()) {
|
|
for (const auto& elem : it_by_prev->second) {
|
|
// Get this source peer's work set, emplacing an empty set if it didn't exist
|
|
// (note: if this peer wasn't still connected, we would have removed the orphan tx already)
|
|
std::set<uint256>& orphan_work_set = m_peer_work_set.try_emplace(elem->second.fromPeer).first->second;
|
|
// Add this tx to the work set
|
|
orphan_work_set.insert(elem->first);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool TxOrphanage::HaveTx(const GenTxid& gtxid) const
|
|
{
|
|
LOCK(m_mutex);
|
|
if (gtxid.IsWtxid()) {
|
|
return m_wtxid_to_orphan_it.count(gtxid.GetHash());
|
|
} else {
|
|
return m_orphans.count(gtxid.GetHash());
|
|
}
|
|
}
|
|
|
|
CTransactionRef TxOrphanage::GetTxToReconsider(NodeId peer)
|
|
{
|
|
LOCK(m_mutex);
|
|
|
|
auto work_set_it = m_peer_work_set.find(peer);
|
|
if (work_set_it != m_peer_work_set.end()) {
|
|
auto& work_set = work_set_it->second;
|
|
while (!work_set.empty()) {
|
|
uint256 txid = *work_set.begin();
|
|
work_set.erase(work_set.begin());
|
|
|
|
const auto orphan_it = m_orphans.find(txid);
|
|
if (orphan_it != m_orphans.end()) {
|
|
return orphan_it->second.tx;
|
|
}
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
bool TxOrphanage::HaveTxToReconsider(NodeId peer)
|
|
{
|
|
LOCK(m_mutex);
|
|
|
|
auto work_set_it = m_peer_work_set.find(peer);
|
|
if (work_set_it != m_peer_work_set.end()) {
|
|
auto& work_set = work_set_it->second;
|
|
return !work_set.empty();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void TxOrphanage::EraseForBlock(const CBlock& block)
|
|
{
|
|
LOCK(m_mutex);
|
|
|
|
std::vector<uint256> vOrphanErase;
|
|
|
|
for (const CTransactionRef& ptx : block.vtx) {
|
|
const CTransaction& tx = *ptx;
|
|
|
|
// Which orphan pool entries must we evict?
|
|
for (const auto& txin : tx.vin) {
|
|
auto itByPrev = m_outpoint_to_orphan_it.find(txin.prevout);
|
|
if (itByPrev == m_outpoint_to_orphan_it.end()) continue;
|
|
for (auto mi = itByPrev->second.begin(); mi != itByPrev->second.end(); ++mi) {
|
|
const CTransaction& orphanTx = *(*mi)->second.tx;
|
|
const uint256& orphanHash = orphanTx.GetHash();
|
|
vOrphanErase.push_back(orphanHash);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Erase orphan transactions included or precluded by this block
|
|
if (vOrphanErase.size()) {
|
|
int nErased = 0;
|
|
for (const uint256& orphanHash : vOrphanErase) {
|
|
nErased += _EraseTx(orphanHash);
|
|
}
|
|
LogPrint(BCLog::MEMPOOL, "Erased %d orphan tx included or conflicted by block\n", nErased);
|
|
}
|
|
}
|