Changeset View
Changeset View
Standalone View
Standalone View
src/net_processing.cpp
Show First 20 Lines • Show All 125 Lines • ▼ Show 20 Lines | |||||
struct COrphanTx { | struct COrphanTx { | ||||
// When modifying, adapt the copy of this definition in tests/DoS_tests. | // When modifying, adapt the copy of this definition in tests/DoS_tests. | ||||
CTransactionRef tx; | CTransactionRef tx; | ||||
NodeId fromPeer; | NodeId fromPeer; | ||||
int64_t nTimeExpire; | int64_t nTimeExpire; | ||||
}; | }; | ||||
RecursiveMutex g_cs_orphans; | RecursiveMutex g_cs_orphans; | ||||
std::map<uint256, COrphanTx> mapOrphanTransactions GUARDED_BY(g_cs_orphans); | std::map<TxId, COrphanTx> mapOrphanTransactions GUARDED_BY(g_cs_orphans); | ||||
void EraseOrphansFor(NodeId peer); | void EraseOrphansFor(NodeId peer); | ||||
/** | /** | ||||
* Average delay between local address broadcasts in seconds. | * Average delay between local address broadcasts in seconds. | ||||
*/ | */ | ||||
static constexpr unsigned int AVG_LOCAL_ADDRESS_BROADCAST_INTERVAL = | static constexpr unsigned int AVG_LOCAL_ADDRESS_BROADCAST_INTERVAL = | ||||
24 * 60 * 60; | 24 * 60 * 60; | ||||
▲ Show 20 Lines • Show All 97 Lines • ▼ Show 20 Lines | std::deque<std::pair<int64_t, MapRelay::iterator>> | ||||
vRelayExpiration GUARDED_BY(cs_main); | vRelayExpiration GUARDED_BY(cs_main); | ||||
struct IteratorComparator { | struct IteratorComparator { | ||||
template <typename I> bool operator()(const I &a, const I &b) const { | template <typename I> bool operator()(const I &a, const I &b) const { | ||||
return &(*a) < &(*b); | return &(*a) < &(*b); | ||||
} | } | ||||
}; | }; | ||||
std::map<COutPoint, | std::map<COutPoint, | ||||
std::set<std::map<uint256, COrphanTx>::iterator, IteratorComparator>> | std::set<std::map<TxId, COrphanTx>::iterator, IteratorComparator>> | ||||
mapOrphanTransactionsByPrev GUARDED_BY(g_cs_orphans); | mapOrphanTransactionsByPrev GUARDED_BY(g_cs_orphans); | ||||
static size_t vExtraTxnForCompactIt GUARDED_BY(g_cs_orphans) = 0; | static size_t vExtraTxnForCompactIt GUARDED_BY(g_cs_orphans) = 0; | ||||
static std::vector<std::pair<TxHash, CTransactionRef>> | static std::vector<std::pair<TxHash, CTransactionRef>> | ||||
vExtraTxnForCompact GUARDED_BY(g_cs_orphans); | vExtraTxnForCompact GUARDED_BY(g_cs_orphans); | ||||
} // namespace | } // namespace | ||||
namespace { | namespace { | ||||
▲ Show 20 Lines • Show All 764 Lines • ▼ Show 20 Lines | static void AddToCompactExtraTransactions(const CTransactionRef &tx) | ||||
vExtraTxnForCompact[vExtraTxnForCompactIt] = | vExtraTxnForCompact[vExtraTxnForCompactIt] = | ||||
std::make_pair(tx->GetHash(), tx); | std::make_pair(tx->GetHash(), tx); | ||||
vExtraTxnForCompactIt = (vExtraTxnForCompactIt + 1) % max_extra_txn; | vExtraTxnForCompactIt = (vExtraTxnForCompactIt + 1) % max_extra_txn; | ||||
} | } | ||||
bool AddOrphanTx(const CTransactionRef &tx, NodeId peer) | bool AddOrphanTx(const CTransactionRef &tx, NodeId peer) | ||||
EXCLUSIVE_LOCKS_REQUIRED(g_cs_orphans) { | EXCLUSIVE_LOCKS_REQUIRED(g_cs_orphans) { | ||||
const uint256 &txid = tx->GetId(); | const auto &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 | ||||
// 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. | ||||
Show All 17 Lines | bool AddOrphanTx(const CTransactionRef &tx, NodeId peer) | ||||
AddToCompactExtraTransactions(tx); | AddToCompactExtraTransactions(tx); | ||||
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; | ||||
} | } | ||||
static int EraseOrphanTx(uint256 hash) EXCLUSIVE_LOCKS_REQUIRED(g_cs_orphans) { | static int EraseOrphanTx(TxId hash) EXCLUSIVE_LOCKS_REQUIRED(g_cs_orphans) { | ||||
deadalnix: So it's not a hash, is it? | |||||
std::map<uint256, COrphanTx>::iterator it = | const auto it = mapOrphanTransactions.find(hash); | ||||
mapOrphanTransactions.find(hash); | |||||
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); | const auto itPrev = mapOrphanTransactionsByPrev.find(txin.prevout); | ||||
if (itPrev == mapOrphanTransactionsByPrev.end()) { | if (itPrev == mapOrphanTransactionsByPrev.end()) { | ||||
continue; | continue; | ||||
} | } | ||||
itPrev->second.erase(it); | itPrev->second.erase(it); | ||||
if (itPrev->second.empty()) { | if (itPrev->second.empty()) { | ||||
mapOrphanTransactionsByPrev.erase(itPrev); | mapOrphanTransactionsByPrev.erase(itPrev); | ||||
} | } | ||||
} | } | ||||
mapOrphanTransactions.erase(it); | mapOrphanTransactions.erase(it); | ||||
return 1; | return 1; | ||||
} | } | ||||
void EraseOrphansFor(NodeId peer) { | void EraseOrphansFor(NodeId peer) { | ||||
LOCK(g_cs_orphans); | LOCK(g_cs_orphans); | ||||
int nErased = 0; | int nErased = 0; | ||||
std::map<uint256, COrphanTx>::iterator iter = mapOrphanTransactions.begin(); | auto iter = mapOrphanTransactions.begin(); | ||||
while (iter != mapOrphanTransactions.end()) { | while (iter != mapOrphanTransactions.end()) { | ||||
// Increment to avoid iterator becoming invalid. | // Increment to avoid iterator becoming invalid. | ||||
std::map<uint256, COrphanTx>::iterator maybeErase = iter++; | const auto maybeErase = iter++; | ||||
if (maybeErase->second.fromPeer == peer) { | if (maybeErase->second.fromPeer == peer) { | ||||
nErased += EraseOrphanTx(maybeErase->second.tx->GetId()); | nErased += EraseOrphanTx(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 LimitOrphanTxSize(unsigned int nMaxOrphans) { | ||||
LOCK(g_cs_orphans); | LOCK(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<uint256, COrphanTx>::iterator iter = | auto iter = mapOrphanTransactions.begin(); | ||||
mapOrphanTransactions.begin(); | |||||
while (iter != mapOrphanTransactions.end()) { | while (iter != mapOrphanTransactions.end()) { | ||||
std::map<uint256, COrphanTx>::iterator maybeErase = iter++; | const auto maybeErase = iter++; | ||||
if (maybeErase->second.nTimeExpire <= nNow) { | if (maybeErase->second.nTimeExpire <= nNow) { | ||||
nErased += EraseOrphanTx(maybeErase->second.tx->GetId()); | nErased += EraseOrphanTx(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: | ||||
uint256 randomhash = rng.rand256(); | const auto randomhash = TxId{rng.rand256()}; | ||||
deadalnixUnsubmitted Not Done Inline ActionsSo it is not a random hash, is it? deadalnix: So it is not a random hash, is it? | |||||
std::map<uint256, COrphanTx>::iterator it = | auto it = mapOrphanTransactions.lower_bound(randomhash); | ||||
mapOrphanTransactions.lower_bound(randomhash); | |||||
if (it == mapOrphanTransactions.end()) { | if (it == mapOrphanTransactions.end()) { | ||||
it = mapOrphanTransactions.begin(); | it = mapOrphanTransactions.begin(); | ||||
} | } | ||||
EraseOrphanTx(it->first); | EraseOrphanTx(it->first); | ||||
++nEvicted; | ++nEvicted; | ||||
} | } | ||||
return nEvicted; | return nEvicted; | ||||
} | } | ||||
▲ Show 20 Lines • Show All 87 Lines • ▼ Show 20 Lines | |||||
* Evict orphan txn pool entries (EraseOrphanTx) based on a newly connected | * Evict orphan txn pool entries (EraseOrphanTx) based on a newly connected | ||||
* block. Also save the time of the last tip update. | * block. Also save the time of the last tip update. | ||||
*/ | */ | ||||
void PeerLogicValidation::BlockConnected( | void PeerLogicValidation::BlockConnected( | ||||
const std::shared_ptr<const CBlock> &pblock, const CBlockIndex *pindex, | const std::shared_ptr<const CBlock> &pblock, const CBlockIndex *pindex, | ||||
const std::vector<CTransactionRef> &vtxConflicted) { | const std::vector<CTransactionRef> &vtxConflicted) { | ||||
LOCK(g_cs_orphans); | LOCK(g_cs_orphans); | ||||
std::vector<uint256> vOrphanErase; | std::vector<TxId> vOrphanErase; | ||||
for (const CTransactionRef &ptx : pblock->vtx) { | for (const CTransactionRef &ptx : pblock->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 = mapOrphanTransactionsByPrev.find(txin.prevout); | ||||
if (itByPrev == mapOrphanTransactionsByPrev.end()) { | if (itByPrev == mapOrphanTransactionsByPrev.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 uint256 &orphanHash = orphanTx.GetHash(); | const auto &orphanHash = orphanTx.GetId(); | ||||
vOrphanErase.push_back(orphanHash); | vOrphanErase.push_back(orphanHash); | ||||
} | } | ||||
} | } | ||||
} | } | ||||
// 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 uint256 &orphanId : vOrphanErase) { | for (const auto &orphanId : vOrphanErase) { | ||||
nErased += EraseOrphanTx(orphanId); | nErased += EraseOrphanTx(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); | ||||
} | } | ||||
g_last_tip_update = GetTime(); | g_last_tip_update = GetTime(); | ||||
▲ Show 20 Lines • Show All 163 Lines • ▼ Show 20 Lines | switch (inv.type) { | ||||
// those txs a second chance. | // those txs a second chance. | ||||
hashRecentRejectsChainTip = | hashRecentRejectsChainTip = | ||||
::ChainActive().Tip()->GetBlockHash(); | ::ChainActive().Tip()->GetBlockHash(); | ||||
recentRejects->reset(); | recentRejects->reset(); | ||||
} | } | ||||
{ | { | ||||
LOCK(g_cs_orphans); | LOCK(g_cs_orphans); | ||||
if (mapOrphanTransactions.count(inv.hash)) { | if (mapOrphanTransactions.count(TxId(inv.hash))) { | ||||
return true; | return true; | ||||
} | } | ||||
} | } | ||||
// Use pcoinsTip->HaveCoinInCache as a quick approximation to | // Use pcoinsTip->HaveCoinInCache as a quick approximation to | ||||
// exclude requesting or processing some txs which have already been | // exclude requesting or processing some txs which have already been | ||||
// included in a block. As this is best effort, we only check for | // included in a block. As this is best effort, we only check for | ||||
// output 0 and 1. This works well enough in practice and we get | // output 0 and 1. This works well enough in practice and we get | ||||
▲ Show 20 Lines • Show All 1,325 Lines • ▼ Show 20 Lines | if (strCommand == NetMsgType::TX) { | ||||
if (!fRelayTxes && !pfrom->HasPermission(PF_RELAY)) { | if (!fRelayTxes && !pfrom->HasPermission(PF_RELAY)) { | ||||
LogPrint(BCLog::NET, | LogPrint(BCLog::NET, | ||||
"transaction sent in violation of protocol peer=%d\n", | "transaction sent in violation of protocol peer=%d\n", | ||||
pfrom->GetId()); | pfrom->GetId()); | ||||
return true; | return true; | ||||
} | } | ||||
std::deque<COutPoint> vWorkQueue; | std::deque<COutPoint> vWorkQueue; | ||||
std::vector<uint256> vEraseQueue; | std::vector<TxId> vEraseQueue; | ||||
CTransactionRef ptx; | CTransactionRef ptx; | ||||
vRecv >> ptx; | vRecv >> ptx; | ||||
const CTransaction &tx = *ptx; | const CTransaction &tx = *ptx; | ||||
const TxId &txid = tx.GetId(); | const TxId &txid = tx.GetId(); | ||||
CInv inv(MSG_TX, txid); | CInv inv(MSG_TX, txid); | ||||
pfrom->AddInventoryKnown(inv); | pfrom->AddInventoryKnown(inv); | ||||
▲ Show 20 Lines • Show All 92 Lines • ▼ Show 20 Lines | if (strCommand == NetMsgType::TX) { | ||||
assert(recentRejects); | assert(recentRejects); | ||||
recentRejects->insert(orphanId); | recentRejects->insert(orphanId); | ||||
} | } | ||||
} | } | ||||
g_mempool.check(pcoinsTip.get()); | g_mempool.check(pcoinsTip.get()); | ||||
} | } | ||||
} | } | ||||
for (const uint256 &hash : vEraseQueue) { | for (const auto &hash : vEraseQueue) { | ||||
deadalnixUnsubmitted Not Done Inline Actionsdito deadalnix: dito | |||||
EraseOrphanTx(hash); | EraseOrphanTx(hash); | ||||
} | } | ||||
} else if (fMissingInputs) { | } else if (fMissingInputs) { | ||||
// It may be the case that the orphans parents have all been | // It may be the case that the orphans parents have all been | ||||
// rejected. | // rejected. | ||||
bool fRejectedParents = false; | bool fRejectedParents = false; | ||||
for (const CTxIn &txin : tx.vin) { | for (const CTxIn &txin : tx.vin) { | ||||
if (recentRejects->contains(txin.prevout.GetTxId())) { | if (recentRejects->contains(txin.prevout.GetTxId())) { | ||||
▲ Show 20 Lines • Show All 1,983 Lines • Show Last 20 Lines |
So it's not a hash, is it?