Changeset View
Changeset View
Standalone View
Standalone View
src/txorphanage.cpp
Show All 15 Lines | |||||
static constexpr int64_t ORPHAN_TX_EXPIRE_INTERVAL = 5 * 60; | static constexpr int64_t ORPHAN_TX_EXPIRE_INTERVAL = 5 * 60; | ||||
RecursiveMutex g_cs_orphans; | RecursiveMutex g_cs_orphans; | ||||
bool TxOrphanage::AddTx(const CTransactionRef &tx, NodeId peer) { | bool TxOrphanage::AddTx(const CTransactionRef &tx, NodeId peer) { | ||||
AssertLockHeld(g_cs_orphans); | AssertLockHeld(g_cs_orphans); | ||||
const TxId &txid = tx->GetId(); | const TxId &txid = tx->GetId(); | ||||
if (mapOrphanTransactions.count(txid)) { | if (m_orphans.count(txid)) { | ||||
return false; | return false; | ||||
} | } | ||||
// Ignore big transactions, to avoid a send-big-orphans memory exhaustion | // Ignore big transactions, to avoid a send-big-orphans memory exhaustion | ||||
// attack. If a peer has a legitimate large transaction with a missing | // attack. If a peer has a legitimate large transaction with a missing | ||||
// parent then we assume it will rebroadcast it later, after the parent | // parent then we assume it will rebroadcast it later, after the parent | ||||
// transaction(s) have been mined or received. | // transaction(s) have been mined or received. | ||||
// 100 orphans, each of which is at most 100,000 bytes big is at most 10 | // 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): | // megabytes of orphans and somewhat more byprev index (in the worst case): | ||||
unsigned int sz = tx->GetTotalSize(); | unsigned int sz = tx->GetTotalSize(); | ||||
if (sz > MAX_STANDARD_TX_SIZE) { | if (sz > MAX_STANDARD_TX_SIZE) { | ||||
LogPrint(BCLog::MEMPOOL, | LogPrint(BCLog::MEMPOOL, | ||||
"ignoring large orphan tx (size: %u, hash: %s)\n", sz, | "ignoring large orphan tx (size: %u, hash: %s)\n", sz, | ||||
txid.ToString()); | txid.ToString()); | ||||
return false; | return false; | ||||
} | } | ||||
auto ret = mapOrphanTransactions.emplace( | auto ret = m_orphans.emplace( | ||||
txid, COrphanTx{tx, peer, GetTime() + ORPHAN_TX_EXPIRE_TIME, | txid, OrphanTx{tx, peer, GetTime() + ORPHAN_TX_EXPIRE_TIME, | ||||
g_orphan_list.size()}); | m_orphan_list.size()}); | ||||
assert(ret.second); | assert(ret.second); | ||||
g_orphan_list.push_back(ret.first); | m_orphan_list.push_back(ret.first); | ||||
for (const CTxIn &txin : tx->vin) { | for (const CTxIn &txin : tx->vin) { | ||||
mapOrphanTransactionsByPrev[txin.prevout].insert(ret.first); | m_outpoint_to_orphan_it[txin.prevout].insert(ret.first); | ||||
} | } | ||||
LogPrint(BCLog::MEMPOOL, "stored orphan tx %s (mapsz %u outsz %u)\n", | LogPrint(BCLog::MEMPOOL, "stored orphan tx %s (mapsz %u outsz %u)\n", | ||||
txid.ToString(), mapOrphanTransactions.size(), | txid.ToString(), m_orphans.size(), m_outpoint_to_orphan_it.size()); | ||||
mapOrphanTransactionsByPrev.size()); | |||||
return true; | return true; | ||||
} | } | ||||
int TxOrphanage::EraseTx(const TxId &txid) { | int TxOrphanage::EraseTx(const TxId &txid) { | ||||
AssertLockHeld(g_cs_orphans); | AssertLockHeld(g_cs_orphans); | ||||
std::map<TxId, COrphanTx>::iterator it = mapOrphanTransactions.find(txid); | std::map<TxId, OrphanTx>::iterator it = m_orphans.find(txid); | ||||
if (it == mapOrphanTransactions.end()) { | if (it == m_orphans.end()) { | ||||
return 0; | return 0; | ||||
} | } | ||||
for (const CTxIn &txin : it->second.tx->vin) { | for (const CTxIn &txin : it->second.tx->vin) { | ||||
auto itPrev = mapOrphanTransactionsByPrev.find(txin.prevout); | auto itPrev = m_outpoint_to_orphan_it.find(txin.prevout); | ||||
if (itPrev == mapOrphanTransactionsByPrev.end()) { | if (itPrev == m_outpoint_to_orphan_it.end()) { | ||||
continue; | continue; | ||||
} | } | ||||
itPrev->second.erase(it); | itPrev->second.erase(it); | ||||
if (itPrev->second.empty()) { | if (itPrev->second.empty()) { | ||||
mapOrphanTransactionsByPrev.erase(itPrev); | m_outpoint_to_orphan_it.erase(itPrev); | ||||
} | } | ||||
} | } | ||||
size_t old_pos = it->second.list_pos; | size_t old_pos = it->second.list_pos; | ||||
assert(g_orphan_list[old_pos] == it); | assert(m_orphan_list[old_pos] == it); | ||||
if (old_pos + 1 != g_orphan_list.size()) { | if (old_pos + 1 != m_orphan_list.size()) { | ||||
// Unless we're deleting the last entry in g_orphan_list, move the last | // Unless we're deleting the last entry in m_orphan_list, move the last | ||||
// entry to the position we're deleting. | // entry to the position we're deleting. | ||||
auto it_last = g_orphan_list.back(); | auto it_last = m_orphan_list.back(); | ||||
g_orphan_list[old_pos] = it_last; | m_orphan_list[old_pos] = it_last; | ||||
it_last->second.list_pos = old_pos; | it_last->second.list_pos = old_pos; | ||||
} | } | ||||
g_orphan_list.pop_back(); | m_orphan_list.pop_back(); | ||||
mapOrphanTransactions.erase(it); | m_orphans.erase(it); | ||||
return 1; | return 1; | ||||
} | } | ||||
void TxOrphanage::EraseForPeer(NodeId peer) { | void TxOrphanage::EraseForPeer(NodeId peer) { | ||||
AssertLockHeld(g_cs_orphans); | AssertLockHeld(g_cs_orphans); | ||||
int nErased = 0; | int nErased = 0; | ||||
std::map<TxId, COrphanTx>::iterator iter = mapOrphanTransactions.begin(); | std::map<TxId, OrphanTx>::iterator iter = m_orphans.begin(); | ||||
while (iter != mapOrphanTransactions.end()) { | while (iter != m_orphans.end()) { | ||||
std::map<TxId, COrphanTx>::iterator maybeErase = | std::map<TxId, OrphanTx>::iterator maybeErase = | ||||
iter++; // increment to avoid iterator becoming invalid | iter++; // increment to avoid iterator becoming invalid | ||||
if (maybeErase->second.fromPeer == peer) { | if (maybeErase->second.fromPeer == peer) { | ||||
nErased += EraseTx(maybeErase->second.tx->GetId()); | nErased += EraseTx(maybeErase->second.tx->GetId()); | ||||
} | } | ||||
} | } | ||||
if (nErased > 0) { | if (nErased > 0) { | ||||
LogPrint(BCLog::MEMPOOL, "Erased %d orphan tx from peer=%d\n", nErased, | LogPrint(BCLog::MEMPOOL, "Erased %d orphan tx from peer=%d\n", nErased, | ||||
peer); | peer); | ||||
} | } | ||||
} | } | ||||
unsigned int TxOrphanage::LimitOrphans(unsigned int nMaxOrphans) { | unsigned int TxOrphanage::LimitOrphans(unsigned int max_orphans) { | ||||
AssertLockHeld(g_cs_orphans); | AssertLockHeld(g_cs_orphans); | ||||
unsigned int nEvicted = 0; | unsigned int nEvicted = 0; | ||||
static int64_t nNextSweep; | static int64_t nNextSweep; | ||||
int64_t nNow = GetTime(); | int64_t nNow = GetTime(); | ||||
if (nNextSweep <= nNow) { | if (nNextSweep <= nNow) { | ||||
// Sweep out expired orphan pool entries: | // Sweep out expired orphan pool entries: | ||||
int nErased = 0; | int nErased = 0; | ||||
int64_t nMinExpTime = | int64_t nMinExpTime = | ||||
nNow + ORPHAN_TX_EXPIRE_TIME - ORPHAN_TX_EXPIRE_INTERVAL; | nNow + ORPHAN_TX_EXPIRE_TIME - ORPHAN_TX_EXPIRE_INTERVAL; | ||||
std::map<TxId, COrphanTx>::iterator iter = | std::map<TxId, OrphanTx>::iterator iter = m_orphans.begin(); | ||||
mapOrphanTransactions.begin(); | while (iter != m_orphans.end()) { | ||||
while (iter != mapOrphanTransactions.end()) { | std::map<TxId, OrphanTx>::iterator maybeErase = iter++; | ||||
std::map<TxId, COrphanTx>::iterator maybeErase = iter++; | |||||
if (maybeErase->second.nTimeExpire <= nNow) { | if (maybeErase->second.nTimeExpire <= nNow) { | ||||
nErased += EraseTx(maybeErase->second.tx->GetId()); | nErased += EraseTx(maybeErase->second.tx->GetId()); | ||||
} else { | } else { | ||||
nMinExpTime = | nMinExpTime = | ||||
std::min(maybeErase->second.nTimeExpire, nMinExpTime); | std::min(maybeErase->second.nTimeExpire, nMinExpTime); | ||||
} | } | ||||
} | } | ||||
// Sweep again 5 minutes after the next entry that expires in order to | // Sweep again 5 minutes after the next entry that expires in order to | ||||
// batch the linear scan. | // batch the linear scan. | ||||
nNextSweep = nMinExpTime + ORPHAN_TX_EXPIRE_INTERVAL; | nNextSweep = nMinExpTime + ORPHAN_TX_EXPIRE_INTERVAL; | ||||
if (nErased > 0) { | if (nErased > 0) { | ||||
LogPrint(BCLog::MEMPOOL, "Erased %d orphan tx due to expiration\n", | LogPrint(BCLog::MEMPOOL, "Erased %d orphan tx due to expiration\n", | ||||
nErased); | nErased); | ||||
} | } | ||||
} | } | ||||
FastRandomContext rng; | FastRandomContext rng; | ||||
while (mapOrphanTransactions.size() > nMaxOrphans) { | while (m_orphans.size() > max_orphans) { | ||||
// Evict a random orphan: | // Evict a random orphan: | ||||
size_t randompos = rng.randrange(g_orphan_list.size()); | size_t randompos = rng.randrange(m_orphan_list.size()); | ||||
EraseTx(g_orphan_list[randompos]->first); | EraseTx(m_orphan_list[randompos]->first); | ||||
++nEvicted; | ++nEvicted; | ||||
} | } | ||||
return nEvicted; | return nEvicted; | ||||
} | } | ||||
void TxOrphanage::AddChildrenToWorkSet(const CTransaction &tx, | void TxOrphanage::AddChildrenToWorkSet(const CTransaction &tx, | ||||
std::set<TxId> &orphan_work_set) const { | std::set<TxId> &orphan_work_set) const { | ||||
AssertLockHeld(g_cs_orphans); | AssertLockHeld(g_cs_orphans); | ||||
for (size_t i = 0; i < tx.vout.size(); i++) { | for (size_t i = 0; i < tx.vout.size(); i++) { | ||||
const auto it_by_prev = | const auto it_by_prev = | ||||
mapOrphanTransactionsByPrev.find(COutPoint(tx.GetId(), i)); | m_outpoint_to_orphan_it.find(COutPoint(tx.GetId(), i)); | ||||
if (it_by_prev != mapOrphanTransactionsByPrev.end()) { | if (it_by_prev != m_outpoint_to_orphan_it.end()) { | ||||
for (const auto &elem : it_by_prev->second) { | for (const auto &elem : it_by_prev->second) { | ||||
orphan_work_set.insert(elem->first); | orphan_work_set.insert(elem->first); | ||||
} | } | ||||
} | } | ||||
} | } | ||||
} | } | ||||
bool TxOrphanage::HaveTx(const TxId &txid) const { | bool TxOrphanage::HaveTx(const TxId &txid) const { | ||||
LOCK(g_cs_orphans); | LOCK(g_cs_orphans); | ||||
return mapOrphanTransactions.count(txid); | return m_orphans.count(txid); | ||||
} | } | ||||
std::pair<CTransactionRef, NodeId> TxOrphanage::GetTx(const TxId &txid) const { | std::pair<CTransactionRef, NodeId> TxOrphanage::GetTx(const TxId &txid) const { | ||||
AssertLockHeld(g_cs_orphans); | AssertLockHeld(g_cs_orphans); | ||||
const auto it = mapOrphanTransactions.find(txid); | const auto it = m_orphans.find(txid); | ||||
if (it == mapOrphanTransactions.end()) { | if (it == m_orphans.end()) { | ||||
return {nullptr, -1}; | return {nullptr, -1}; | ||||
} | } | ||||
return {it->second.tx, it->second.fromPeer}; | return {it->second.tx, it->second.fromPeer}; | ||||
} | } | ||||
void TxOrphanage::EraseForBlock(const CBlock &block) { | void TxOrphanage::EraseForBlock(const CBlock &block) { | ||||
LOCK(g_cs_orphans); | LOCK(g_cs_orphans); | ||||
std::vector<TxId> vOrphanErase; | std::vector<TxId> vOrphanErase; | ||||
for (const CTransactionRef &ptx : block.vtx) { | for (const CTransactionRef &ptx : block.vtx) { | ||||
const CTransaction &tx = *ptx; | const CTransaction &tx = *ptx; | ||||
// Which orphan pool entries must we evict? | // Which orphan pool entries must we evict? | ||||
for (const auto &txin : tx.vin) { | for (const auto &txin : tx.vin) { | ||||
auto itByPrev = mapOrphanTransactionsByPrev.find(txin.prevout); | auto itByPrev = m_outpoint_to_orphan_it.find(txin.prevout); | ||||
if (itByPrev == mapOrphanTransactionsByPrev.end()) { | if (itByPrev == m_outpoint_to_orphan_it.end()) { | ||||
continue; | continue; | ||||
} | } | ||||
for (auto mi = itByPrev->second.begin(); | for (auto mi = itByPrev->second.begin(); | ||||
mi != itByPrev->second.end(); ++mi) { | mi != itByPrev->second.end(); ++mi) { | ||||
const CTransaction &orphanTx = *(*mi)->second.tx; | const CTransaction &orphanTx = *(*mi)->second.tx; | ||||
const TxId &orphanId = orphanTx.GetId(); | const TxId &orphanId = orphanTx.GetId(); | ||||
vOrphanErase.push_back(orphanId); | vOrphanErase.push_back(orphanId); | ||||
Show All 15 Lines |