Changeset View
Changeset View
Standalone View
Standalone View
src/txorphanage.cpp
Show All 11 Lines | |||||
/** Expiration time for orphan transactions in seconds */ | /** Expiration time for orphan transactions in seconds */ | ||||
static constexpr int64_t ORPHAN_TX_EXPIRE_TIME = 20 * 60; | static constexpr int64_t ORPHAN_TX_EXPIRE_TIME = 20 * 60; | ||||
/** Minimum time between orphan transactions expire time checks in seconds */ | /** Minimum time between orphan transactions expire time checks in seconds */ | ||||
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; | ||||
std::map<TxId, COrphanTx> mapOrphanTransactions GUARDED_BY(g_cs_orphans); | bool TxOrphanage::AddTx(const CTransactionRef &tx, NodeId peer) { | ||||
std::map<COutPoint, | |||||
std::set<std::map<TxId, COrphanTx>::iterator, IteratorComparator>> | |||||
mapOrphanTransactionsByPrev GUARDED_BY(g_cs_orphans); | |||||
std::vector<std::map<TxId, COrphanTx>::iterator> | |||||
g_orphan_list GUARDED_BY(g_cs_orphans); | |||||
bool OrphanageAddTx(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 (mapOrphanTransactions.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 | ||||
Show All 20 Lines | bool TxOrphanage::AddTx(const CTransactionRef &tx, NodeId peer) { | ||||
} | } | ||||
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(), mapOrphanTransactions.size(), | ||||
mapOrphanTransactionsByPrev.size()); | mapOrphanTransactionsByPrev.size()); | ||||
return true; | return true; | ||||
} | } | ||||
int EraseOrphanTx(const TxId &txid) { | int TxOrphanage::EraseTx(const TxId &txid) { | ||||
AssertLockHeld(g_cs_orphans); | |||||
std::map<TxId, COrphanTx>::iterator it = mapOrphanTransactions.find(txid); | std::map<TxId, COrphanTx>::iterator it = mapOrphanTransactions.find(txid); | ||||
if (it == mapOrphanTransactions.end()) { | if (it == mapOrphanTransactions.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 = mapOrphanTransactionsByPrev.find(txin.prevout); | ||||
if (itPrev == mapOrphanTransactionsByPrev.end()) { | if (itPrev == mapOrphanTransactionsByPrev.end()) { | ||||
continue; | continue; | ||||
Show All 14 Lines | if (old_pos + 1 != g_orphan_list.size()) { | ||||
it_last->second.list_pos = old_pos; | it_last->second.list_pos = old_pos; | ||||
} | } | ||||
g_orphan_list.pop_back(); | g_orphan_list.pop_back(); | ||||
mapOrphanTransactions.erase(it); | mapOrphanTransactions.erase(it); | ||||
return 1; | return 1; | ||||
} | } | ||||
void EraseOrphansFor(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, COrphanTx>::iterator iter = mapOrphanTransactions.begin(); | ||||
while (iter != mapOrphanTransactions.end()) { | while (iter != mapOrphanTransactions.end()) { | ||||
std::map<TxId, COrphanTx>::iterator maybeErase = | std::map<TxId, COrphanTx>::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 += EraseOrphanTx(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 LimitOrphanTxSize(unsigned int nMaxOrphans) { | unsigned int TxOrphanage::LimitOrphans(unsigned int nMaxOrphans) { | ||||
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, COrphanTx>::iterator iter = | ||||
mapOrphanTransactions.begin(); | mapOrphanTransactions.begin(); | ||||
while (iter != mapOrphanTransactions.end()) { | while (iter != mapOrphanTransactions.end()) { | ||||
std::map<TxId, COrphanTx>::iterator maybeErase = iter++; | std::map<TxId, COrphanTx>::iterator maybeErase = iter++; | ||||
if (maybeErase->second.nTimeExpire <= nNow) { | if (maybeErase->second.nTimeExpire <= nNow) { | ||||
nErased += EraseOrphanTx(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 (mapOrphanTransactions.size() > nMaxOrphans) { | ||||
// Evict a random orphan: | // Evict a random orphan: | ||||
size_t randompos = rng.randrange(g_orphan_list.size()); | size_t randompos = rng.randrange(g_orphan_list.size()); | ||||
EraseOrphanTx(g_orphan_list[randompos]->first); | EraseTx(g_orphan_list[randompos]->first); | ||||
++nEvicted; | ++nEvicted; | ||||
} | } | ||||
return nEvicted; | return nEvicted; | ||||
} | } | ||||
void AddChildrenToWorkSet(const CTransaction &tx, | void TxOrphanage::AddChildrenToWorkSet(const CTransaction &tx, | ||||
std::set<TxId> &orphan_work_set) { | 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)); | mapOrphanTransactionsByPrev.find(COutPoint(tx.GetId(), i)); | ||||
if (it_by_prev != mapOrphanTransactionsByPrev.end()) { | if (it_by_prev != mapOrphanTransactionsByPrev.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 HaveOrphanTx(const TxId &txid) { | bool TxOrphanage::HaveTx(const TxId &txid) const { | ||||
LOCK(g_cs_orphans); | LOCK(g_cs_orphans); | ||||
return mapOrphanTransactions.count(txid); | return mapOrphanTransactions.count(txid); | ||||
} | } | ||||
std::pair<CTransactionRef, NodeId> GetOrphanTx(const TxId &txid) { | 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 = mapOrphanTransactions.find(txid); | ||||
if (it == mapOrphanTransactions.end()) { | if (it == mapOrphanTransactions.end()) { | ||||
return {nullptr, -1}; | return {nullptr, -1}; | ||||
} | } | ||||
return {it->second.tx, it->second.fromPeer}; | return {it->second.tx, it->second.fromPeer}; | ||||
} | } | ||||
void EraseOrphansForBlock(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? | ||||
Show All 11 Lines | for (const CTransactionRef &ptx : block.vtx) { | ||||
} | } | ||||
} | } | ||||
} | } | ||||
// Erase orphan transactions included or precluded by this block | // Erase orphan transactions included or precluded by this block | ||||
if (vOrphanErase.size()) { | if (vOrphanErase.size()) { | ||||
int nErased = 0; | int nErased = 0; | ||||
for (const auto &orphanId : vOrphanErase) { | for (const auto &orphanId : vOrphanErase) { | ||||
nErased += EraseOrphanTx(orphanId); | nErased += EraseTx(orphanId); | ||||
} | } | ||||
LogPrint(BCLog::MEMPOOL, | LogPrint(BCLog::MEMPOOL, | ||||
"Erased %d orphan tx included or conflicted by block\n", | "Erased %d orphan tx included or conflicted by block\n", | ||||
nErased); | nErased); | ||||
} | } | ||||
} | } |