Changeset View
Changeset View
Standalone View
Standalone View
src/net_processing.cpp
Show All 35 Lines | |||||
#if defined(NDEBUG) | #if defined(NDEBUG) | ||||
#error "Bitcoin cannot be compiled without assertions." | #error "Bitcoin cannot be compiled without assertions." | ||||
#endif | #endif | ||||
// Used only to inform the wallet of when we last received a block. | // Used only to inform the wallet of when we last received a block. | ||||
std::atomic<int64_t> nTimeBestReceived(0); | std::atomic<int64_t> nTimeBestReceived(0); | ||||
struct IteratorComparator { | OrphanPool g_orphanPool; | ||||
template <typename I> bool operator()(const I &a, const I &b) { | |||||
return &(*a) < &(*b); | |||||
} | |||||
}; | |||||
struct COrphanTx { | |||||
// When modifying, adapt the copy of this definition in tests/DoS_tests. | |||||
CTransactionRef tx; | |||||
NodeId fromPeer; | |||||
int64_t nTimeExpire; | |||||
}; | |||||
static CCriticalSection g_cs_orphans; | |||||
std::map<uint256, COrphanTx> mapOrphanTransactions GUARDED_BY(g_cs_orphans); | |||||
std::map<COutPoint, | |||||
std::set<std::map<uint256, COrphanTx>::iterator, IteratorComparator>> | |||||
mapOrphanTransactionsByPrev GUARDED_BY(g_cs_orphans); | |||||
void EraseOrphansFor(NodeId peer); | |||||
static size_t vExtraTxnForCompactIt GUARDED_BY(g_cs_orphans) = 0; | |||||
static std::vector<std::pair<uint256, CTransactionRef>> | |||||
vExtraTxnForCompact GUARDED_BY(g_cs_orphans); | |||||
// SHA256("main address relay")[0:8] | // SHA256("main address relay")[0:8] | ||||
static const uint64_t RANDOMIZER_ID_ADDRESS_RELAY = 0x3cac0035b5866b90ULL; | static const uint64_t RANDOMIZER_ID_ADDRESS_RELAY = 0x3cac0035b5866b90ULL; | ||||
/// Age after which a stale block will no longer be served if requested as | /// Age after which a stale block will no longer be served if requested as | ||||
/// protection against fingerprinting. Set to one month, denominated in seconds. | /// protection against fingerprinting. Set to one month, denominated in seconds. | ||||
static const int STALE_RELAY_AGE_LIMIT = 30 * 24 * 60 * 60; | static const int STALE_RELAY_AGE_LIMIT = 30 * 24 * 60 * 60; | ||||
▲ Show 20 Lines • Show All 635 Lines • ▼ Show 20 Lines | void PeerLogicValidation::FinalizeNode(const Config &config, NodeId nodeid, | ||||
if (state->nMisbehavior == 0 && state->fCurrentlyConnected) { | if (state->nMisbehavior == 0 && state->fCurrentlyConnected) { | ||||
fUpdateConnectionTime = true; | fUpdateConnectionTime = true; | ||||
} | } | ||||
for (const QueuedBlock &entry : state->vBlocksInFlight) { | for (const QueuedBlock &entry : state->vBlocksInFlight) { | ||||
mapBlocksInFlight.erase(entry.hash); | mapBlocksInFlight.erase(entry.hash); | ||||
} | } | ||||
EraseOrphansFor(nodeid); | g_orphanPool.EraseOrphansFor(nodeid); | ||||
nPreferredDownload -= state->fPreferredDownload; | nPreferredDownload -= state->fPreferredDownload; | ||||
nPeersWithValidatedDownloads -= (state->nBlocksInFlightValidHeaders != 0); | nPeersWithValidatedDownloads -= (state->nBlocksInFlightValidHeaders != 0); | ||||
assert(nPeersWithValidatedDownloads >= 0); | assert(nPeersWithValidatedDownloads >= 0); | ||||
g_outbound_peers_with_protect_from_disconnect -= | g_outbound_peers_with_protect_from_disconnect -= | ||||
state->m_chain_sync.m_protect; | state->m_chain_sync.m_protect; | ||||
assert(g_outbound_peers_with_protect_from_disconnect >= 0); | assert(g_outbound_peers_with_protect_from_disconnect >= 0); | ||||
mapNodeState.erase(nodeid); | mapNodeState.erase(nodeid); | ||||
Show All 28 Lines | bool GetNodeStateStats(NodeId nodeid, CNodeStateStats &stats) { | ||||
return true; | return true; | ||||
} | } | ||||
////////////////////////////////////////////////////////////////////////////// | ////////////////////////////////////////////////////////////////////////////// | ||||
// | // | ||||
// mapOrphanTransactions | // mapOrphanTransactions | ||||
// | // | ||||
static void AddToCompactExtraTransactions(const CTransactionRef &tx) | void OrphanPool::AddToCompactExtraTransactions(const CTransactionRef &tx) { | ||||
EXCLUSIVE_LOCKS_REQUIRED(g_cs_orphans) { | LOCK(cs_extraTxn); | ||||
size_t max_extra_txn = gArgs.GetArg("-blockreconstructionextratxn", | size_t max_extra_txn = gArgs.GetArg("-blockreconstructionextratxn", | ||||
DEFAULT_BLOCK_RECONSTRUCTION_EXTRA_TXN); | DEFAULT_BLOCK_RECONSTRUCTION_EXTRA_TXN); | ||||
if (max_extra_txn <= 0) { | if (max_extra_txn <= 0) { | ||||
return; | return; | ||||
} | } | ||||
if (!vExtraTxnForCompact.size()) { | auto extraTxns = vExtraTxnForCompact.getWriteView(); | ||||
vExtraTxnForCompact.resize(max_extra_txn); | if (!extraTxns->size()) { | ||||
extraTxns->resize(max_extra_txn); | |||||
} | } | ||||
vExtraTxnForCompact[vExtraTxnForCompactIt] = | extraTxns[vExtraTxnForCompactIt] = std::make_pair(tx->GetId(), tx); | ||||
std::make_pair(tx->GetId(), tx); | |||||
vExtraTxnForCompactIt = (vExtraTxnForCompactIt + 1) % max_extra_txn; | vExtraTxnForCompactIt = (vExtraTxnForCompactIt + 1) % max_extra_txn; | ||||
} | } | ||||
bool AddOrphanTx(const CTransactionRef &tx, NodeId peer) | bool OrphanPool::AddOrphanTx(const CTransactionRef &tx, NodeId peer) { | ||||
EXCLUSIVE_LOCKS_REQUIRED(g_cs_orphans) { | |||||
const uint256 &txid = tx->GetId(); | const uint256 &txid = tx->GetId(); | ||||
if (mapOrphanTransactions.count(txid)) { | auto orphanTxns = mapOrphanTransactions.getWriteView(); | ||||
auto orphanTxnsByPrev = mapOrphanTransactionsByPrev.getWriteView(); | |||||
if (orphanTxns->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 99,999 bytes big is at most 10 | // 100 orphans, each of which is at most 99,999 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 = orphanTxns->emplace( | ||||
txid, COrphanTx{tx, peer, GetTime() + ORPHAN_TX_EXPIRE_TIME}); | txid, COrphanTx{tx, peer, GetTime() + ORPHAN_TX_EXPIRE_TIME}); | ||||
assert(ret.second); | assert(ret.second); | ||||
for (const CTxIn &txin : tx->vin) { | for (const CTxIn &txin : tx->vin) { | ||||
mapOrphanTransactionsByPrev[txin.prevout].insert(ret.first); | orphanTxnsByPrev[txin.prevout].insert(ret.first); | ||||
} | } | ||||
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(), orphanTxns->size(), orphanTxnsByPrev->size()); | ||||
mapOrphanTransactionsByPrev.size()); | |||||
return true; | return true; | ||||
} | } | ||||
static int EraseOrphanTx(uint256 hash) EXCLUSIVE_LOCKS_REQUIRED(g_cs_orphans) { | int OrphanPool::EraseOrphanTxInternal( | ||||
std::map<uint256, COrphanTx>::iterator it = | uint256 hash, MapTxns::WriteView &orphanTxns, | ||||
mapOrphanTransactions.find(hash); | MapTxnsByPrev::WriteView &orphanTxnsByPrev) { | ||||
if (it == mapOrphanTransactions.end()) { | std::map<uint256, COrphanTx>::iterator it = orphanTxns->find(hash); | ||||
if (it == orphanTxns.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 = orphanTxnsByPrev->find(txin.prevout); | ||||
if (itPrev == mapOrphanTransactionsByPrev.end()) { | if (itPrev == orphanTxnsByPrev.end()) { | ||||
continue; | continue; | ||||
} | } | ||||
itPrev->second.erase(it); | itPrev->second.erase(it); | ||||
if (itPrev->second.empty()) { | if (itPrev->second.empty()) { | ||||
mapOrphanTransactionsByPrev.erase(itPrev); | orphanTxnsByPrev->erase(itPrev); | ||||
} | } | ||||
} | } | ||||
mapOrphanTransactions.erase(it); | orphanTxns->erase(it); | ||||
return 1; | return 1; | ||||
} | } | ||||
void EraseOrphansFor(NodeId peer) { | int OrphanPool::EraseOrphanTx(uint256 hash) { | ||||
LOCK(g_cs_orphans); | auto orphanTxns = mapOrphanTransactions.getWriteView(); | ||||
auto orphanTxnsByPrev = mapOrphanTransactionsByPrev.getWriteView(); | |||||
return EraseOrphanTxInternal(hash, orphanTxns, orphanTxnsByPrev); | |||||
} | |||||
void OrphanPool::EraseOrphansFor(NodeId peer) { | |||||
int nErased = 0; | int nErased = 0; | ||||
std::map<uint256, COrphanTx>::iterator iter = mapOrphanTransactions.begin(); | auto orphanTxns = mapOrphanTransactions.getWriteView(); | ||||
while (iter != mapOrphanTransactions.end()) { | auto orphanTxnsByPrev = mapOrphanTransactionsByPrev.getWriteView(); | ||||
// Increment to avoid iterator becoming invalid. | |||||
std::map<uint256, COrphanTx>::iterator maybeErase = iter++; | for (const auto &maybeErase : orphanTxns) { | ||||
if (maybeErase->second.fromPeer == peer) { | if (maybeErase.second.fromPeer == peer) { | ||||
nErased += EraseOrphanTx(maybeErase->second.tx->GetId()); | nErased += EraseOrphanTxInternal(maybeErase.second.tx->GetId(), | ||||
orphanTxns, orphanTxnsByPrev); | |||||
} | } | ||||
} | } | ||||
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 OrphanPool::LimitOrphanTxSize(unsigned int nMaxOrphans) { | ||||
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(); | ||||
auto orphanTxns = mapOrphanTransactions.getWriteView(); | |||||
auto orphanTxnsByPrev = mapOrphanTransactionsByPrev.getWriteView(); | |||||
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 = | for (const auto &maybeErase : orphanTxns) { | ||||
mapOrphanTransactions.begin(); | if (maybeErase.second.nTimeExpire <= nNow) { | ||||
while (iter != mapOrphanTransactions.end()) { | EraseOrphanTxInternal(maybeErase.second.tx->GetId(), orphanTxns, | ||||
std::map<uint256, COrphanTx>::iterator maybeErase = iter++; | orphanTxnsByPrev); | ||||
if (maybeErase->second.nTimeExpire <= nNow) { | nErased++; | ||||
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); | ||||
} | } | ||||
} | } | ||||
while (mapOrphanTransactions.size() > nMaxOrphans) { | while (orphanTxns->size() > nMaxOrphans) { | ||||
// Evict a random orphan: | // Evict a random orphan: | ||||
uint256 randomhash = GetRandHash(); | uint256 randomhash = GetRandHash(); | ||||
std::map<uint256, COrphanTx>::iterator it = | std::map<uint256, COrphanTx>::iterator it = | ||||
mapOrphanTransactions.lower_bound(randomhash); | orphanTxns->lower_bound(randomhash); | ||||
if (it == mapOrphanTransactions.end()) { | if (it == orphanTxns.end()) { | ||||
it = mapOrphanTransactions.begin(); | it = orphanTxns.begin(); | ||||
} | } | ||||
EraseOrphanTx(it->first); | EraseOrphanTxInternal(it->first, orphanTxns, orphanTxnsByPrev); | ||||
++nEvicted; | ++nEvicted; | ||||
} | } | ||||
return nEvicted; | return nEvicted; | ||||
} | } | ||||
/** | /** | ||||
* Mark a misbehaving peer to be banned depending upon the value of `-banscore`. | * Mark a misbehaving peer to be banned depending upon the value of `-banscore`. | ||||
*/ | */ | ||||
▲ Show 20 Lines • Show All 74 Lines • ▼ Show 20 Lines | scheduler.scheduleEvery( | ||||
return true; | return true; | ||||
}, | }, | ||||
EXTRA_PEER_CHECK_INTERVAL * 1000); | EXTRA_PEER_CHECK_INTERVAL * 1000); | ||||
} | } | ||||
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); | |||||
std::vector<uint256> vOrphanErase; | std::vector<uint256> vOrphanErase; | ||||
{ | |||||
auto orphansByPrev = g_orphanPool.OrphanTransactionsByPrev(); | |||||
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 (size_t j = 0; j < tx.vin.size(); j++) { | for (size_t j = 0; j < tx.vin.size(); j++) { | ||||
auto itByPrev = mapOrphanTransactionsByPrev.find(tx.vin[j].prevout); | auto itByPrev = orphansByPrev->find(tx.vin[j].prevout); | ||||
if (itByPrev == mapOrphanTransactionsByPrev.end()) { | if (itByPrev == orphansByPrev.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 uint256 &orphanHash = orphanTx.GetHash(); | ||||
vOrphanErase.push_back(orphanHash); | vOrphanErase.push_back(orphanHash); | ||||
} | } | ||||
} | } | ||||
} | } | ||||
} | |||||
// Erase orphan transactions include or precluded by this block | // Erase orphan transactions include or precluded by this block | ||||
if (vOrphanErase.size()) { | if (vOrphanErase.size()) { | ||||
int nErased = 0; | int nErased = 0; | ||||
for (uint256 &orphanId : vOrphanErase) { | for (uint256 &orphanId : vOrphanErase) { | ||||
nErased += EraseOrphanTx(orphanId); | nErased += g_orphanPool.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 149 Lines • ▼ Show 20 Lines | switch (inv.type) { | ||||
// If the chain tip has changed previously rejected transactions | // If the chain tip has changed previously rejected transactions | ||||
// might be now valid, e.g. due to a nLockTime'd tx becoming | // might be now valid, e.g. due to a nLockTime'd tx becoming | ||||
// valid, or a double-spend. Reset the rejects filter and give | // valid, or a double-spend. Reset the rejects filter and give | ||||
// those txs a second chance. | // those txs a second chance. | ||||
hashRecentRejectsChainTip = chainActive.Tip()->GetBlockHash(); | hashRecentRejectsChainTip = chainActive.Tip()->GetBlockHash(); | ||||
recentRejects->reset(); | recentRejects->reset(); | ||||
} | } | ||||
{ | |||||
LOCK(g_cs_orphans); | |||||
if (mapOrphanTransactions.count(inv.hash)) { | |||||
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 | ||||
// diminishing returns with 2 onward. | // diminishing returns with 2 onward. | ||||
return recentRejects->contains(inv.hash) || | return recentRejects->contains(inv.hash) || | ||||
g_mempool.exists(inv.hash) || | g_mempool.exists(inv.hash) || | ||||
g_orphanPool.HaveTransaction(inv.hash) || | |||||
pcoinsTip->HaveCoinInCache(COutPoint(inv.hash, 0)) || | pcoinsTip->HaveCoinInCache(COutPoint(inv.hash, 0)) || | ||||
pcoinsTip->HaveCoinInCache(COutPoint(inv.hash, 1)); | pcoinsTip->HaveCoinInCache(COutPoint(inv.hash, 1)); | ||||
} | } | ||||
case MSG_BLOCK: | case MSG_BLOCK: | ||||
return mapBlockIndex.count(inv.hash); | return mapBlockIndex.count(inv.hash); | ||||
} | } | ||||
// Don't know what it is, just say we already got one | // Don't know what it is, just say we already got one | ||||
return true; | return true; | ||||
▲ Show 20 Lines • Show All 1,283 Lines • ▼ Show 20 Lines | else if (strCommand == NetMsgType::TX) { | ||||
std::vector<uint256> vEraseQueue; | std::vector<uint256> vEraseQueue; | ||||
CTransactionRef ptx; | CTransactionRef ptx; | ||||
vRecv >> ptx; | vRecv >> ptx; | ||||
const CTransaction &tx = *ptx; | const CTransaction &tx = *ptx; | ||||
CInv inv(MSG_TX, tx.GetId()); | CInv inv(MSG_TX, tx.GetId()); | ||||
pfrom->AddInventoryKnown(inv); | pfrom->AddInventoryKnown(inv); | ||||
LOCK2(cs_main, g_cs_orphans); | LOCK(cs_main); | ||||
bool fMissingInputs = false; | bool fMissingInputs = false; | ||||
CValidationState state; | CValidationState state; | ||||
pfrom->setAskFor.erase(inv.hash); | pfrom->setAskFor.erase(inv.hash); | ||||
mapAlreadyAskedFor.erase(inv.hash); | mapAlreadyAskedFor.erase(inv.hash); | ||||
if (!AlreadyHave(inv) && | if (!AlreadyHave(inv) && | ||||
Show All 11 Lines | else if (strCommand == NetMsgType::TX) { | ||||
"AcceptToMemoryPool: peer=%d: accepted %s " | "AcceptToMemoryPool: peer=%d: accepted %s " | ||||
"(poolsz %u txn, %u kB)\n", | "(poolsz %u txn, %u kB)\n", | ||||
pfrom->GetId(), tx.GetId().ToString(), g_mempool.size(), | pfrom->GetId(), tx.GetId().ToString(), g_mempool.size(), | ||||
g_mempool.DynamicMemoryUsage() / 1000); | g_mempool.DynamicMemoryUsage() / 1000); | ||||
// Recursively process any orphan transactions that depended on this | // Recursively process any orphan transactions that depended on this | ||||
// one | // one | ||||
std::unordered_map<NodeId, uint32_t> rejectCountPerNode; | std::unordered_map<NodeId, uint32_t> rejectCountPerNode; | ||||
{ | |||||
auto orphanByPrev = g_orphanPool.OrphanTransactionsByPrev(); | |||||
while (!vWorkQueue.empty()) { | while (!vWorkQueue.empty()) { | ||||
auto itByPrev = | auto itByPrev = orphanByPrev->find(vWorkQueue.front()); | ||||
mapOrphanTransactionsByPrev.find(vWorkQueue.front()); | |||||
vWorkQueue.pop_front(); | vWorkQueue.pop_front(); | ||||
if (itByPrev == mapOrphanTransactionsByPrev.end()) { | if (itByPrev == orphanByPrev.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 CTransactionRef &porphanTx = (*mi)->second.tx; | const CTransactionRef &porphanTx = (*mi)->second.tx; | ||||
const CTransaction &orphanTx = *porphanTx; | const CTransaction &orphanTx = *porphanTx; | ||||
const uint256 &orphanId = orphanTx.GetId(); | const uint256 &orphanId = orphanTx.GetId(); | ||||
NodeId fromPeer = (*mi)->second.fromPeer; | NodeId fromPeer = (*mi)->second.fromPeer; | ||||
bool fMissingInputs2 = false; | bool fMissingInputs2 = false; | ||||
// Use a dummy CValidationState so someone can't setup nodes | // Use a dummy CValidationState so someone can't setup | ||||
// to counter-DoS based on orphan resolution (that is, | // nodes to counter-DoS based on orphan resolution (that | ||||
// feeding people an invalid transaction based on LegitTxX | // is, feeding people an invalid transaction based on | ||||
// in order to get anyone relaying LegitTxX banned) | // LegitTxX in order to get anyone relaying LegitTxX | ||||
// banned) | |||||
CValidationState stateDummy; | CValidationState stateDummy; | ||||
auto it = rejectCountPerNode.find(fromPeer); | auto it = rejectCountPerNode.find(fromPeer); | ||||
if (it != rejectCountPerNode.end() && | if (it != rejectCountPerNode.end() && | ||||
it->second > MAX_NON_STANDARD_ORPHAN_PER_NODE) { | it->second > MAX_NON_STANDARD_ORPHAN_PER_NODE) { | ||||
continue; | continue; | ||||
} | } | ||||
if (AcceptToMemoryPool(config, g_mempool, stateDummy, | if (AcceptToMemoryPool(config, g_mempool, stateDummy, | ||||
porphanTx, true, &fMissingInputs2)) { | porphanTx, true, | ||||
LogPrint(BCLog::MEMPOOL, " accepted orphan tx %s\n", | &fMissingInputs2)) { | ||||
LogPrint(BCLog::MEMPOOL, | |||||
" accepted orphan tx %s\n", | |||||
orphanId.ToString()); | orphanId.ToString()); | ||||
RelayTransaction(orphanTx, connman); | RelayTransaction(orphanTx, connman); | ||||
for (size_t i = 0; i < orphanTx.vout.size(); i++) { | for (size_t i = 0; i < orphanTx.vout.size(); i++) { | ||||
vWorkQueue.emplace_back(orphanId, i); | vWorkQueue.emplace_back(orphanId, i); | ||||
} | } | ||||
vEraseQueue.push_back(orphanId); | vEraseQueue.push_back(orphanId); | ||||
} else if (!fMissingInputs2) { | } else if (!fMissingInputs2) { | ||||
int nDos = 0; | int nDos = 0; | ||||
if (stateDummy.IsInvalid(nDos)) { | if (stateDummy.IsInvalid(nDos)) { | ||||
rejectCountPerNode[fromPeer]++; | rejectCountPerNode[fromPeer]++; | ||||
if (nDos > 0) { | if (nDos > 0) { | ||||
// Punish peer that gave us an invalid orphan tx | // Punish peer that gave us an invalid | ||||
// orphan tx | |||||
Misbehaving(fromPeer, nDos, | Misbehaving(fromPeer, nDos, | ||||
"invalid-orphan-tx"); | "invalid-orphan-tx"); | ||||
LogPrint(BCLog::MEMPOOL, | LogPrint(BCLog::MEMPOOL, | ||||
" invalid orphan tx %s\n", | " invalid orphan tx %s\n", | ||||
orphanId.ToString()); | orphanId.ToString()); | ||||
} | } | ||||
} | } | ||||
// Has inputs but not accepted to mempool | // Has inputs but not accepted to mempool | ||||
// Probably non-standard or insufficient fee/priority | // Probably non-standard or insufficient | ||||
LogPrint(BCLog::MEMPOOL, " removed orphan tx %s\n", | // fee/priority | ||||
LogPrint(BCLog::MEMPOOL, | |||||
" removed orphan tx %s\n", | |||||
orphanId.ToString()); | orphanId.ToString()); | ||||
vEraseQueue.push_back(orphanId); | vEraseQueue.push_back(orphanId); | ||||
if (!stateDummy.CorruptionPossible()) { | if (!stateDummy.CorruptionPossible()) { | ||||
// Do not use rejection cache for witness | // Do not use rejection cache for witness | ||||
// transactions or witness-stripped transactions, as | // transactions or witness-stripped | ||||
// they can have been malleated. See | // transactions, as they can have been | ||||
// malleated. See | |||||
// https://github.com/bitcoin/bitcoin/issues/8279 | // https://github.com/bitcoin/bitcoin/issues/8279 | ||||
// for details. | // for details. | ||||
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 uint256 &hash : vEraseQueue) { | ||||
EraseOrphanTx(hash); | g_orphanPool.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())) { | ||||
fRejectedParents = true; | fRejectedParents = true; | ||||
break; | break; | ||||
} | } | ||||
} | } | ||||
if (!fRejectedParents) { | if (!fRejectedParents) { | ||||
for (const CTxIn &txin : tx.vin) { | for (const CTxIn &txin : tx.vin) { | ||||
// FIXME: MSG_TX should use a TxHash, not a TxId. | // FIXME: MSG_TX should use a TxHash, not a TxId. | ||||
CInv _inv(MSG_TX, txin.prevout.GetTxId()); | CInv _inv(MSG_TX, txin.prevout.GetTxId()); | ||||
pfrom->AddInventoryKnown(_inv); | pfrom->AddInventoryKnown(_inv); | ||||
if (!AlreadyHave(_inv)) { | if (!AlreadyHave(_inv)) { | ||||
pfrom->AskFor(_inv); | pfrom->AskFor(_inv); | ||||
} | } | ||||
} | } | ||||
AddOrphanTx(ptx, pfrom->GetId()); | g_orphanPool.AddOrphanTx(ptx, pfrom->GetId()); | ||||
// DoS prevention: do not allow mapOrphanTransactions to grow | // DoS prevention: do not allow mapOrphanTransactions to grow | ||||
// unbounded | // unbounded | ||||
unsigned int nMaxOrphanTx = (unsigned int)std::max( | unsigned int nMaxOrphanTx = (unsigned int)std::max( | ||||
int64_t(0), gArgs.GetArg("-maxorphantx", | int64_t(0), gArgs.GetArg("-maxorphantx", | ||||
DEFAULT_MAX_ORPHAN_TRANSACTIONS)); | DEFAULT_MAX_ORPHAN_TRANSACTIONS)); | ||||
unsigned int nEvicted = LimitOrphanTxSize(nMaxOrphanTx); | unsigned int nEvicted = | ||||
g_orphanPool.LimitOrphanTxSize(nMaxOrphanTx); | |||||
if (nEvicted > 0) { | if (nEvicted > 0) { | ||||
LogPrint(BCLog::MEMPOOL, | LogPrint(BCLog::MEMPOOL, | ||||
"mapOrphan overflow, removed %u tx\n", nEvicted); | "mapOrphan overflow, removed %u tx\n", nEvicted); | ||||
} | } | ||||
} else { | } else { | ||||
LogPrint(BCLog::MEMPOOL, | LogPrint(BCLog::MEMPOOL, | ||||
"not keeping orphan with rejected parents %s\n", | "not keeping orphan with rejected parents %s\n", | ||||
tx.GetId().ToString()); | tx.GetId().ToString()); | ||||
// We will continue to reject this tx since it has rejected | // We will continue to reject this tx since it has rejected | ||||
// parents so avoid re-requesting it from other peers. | // parents so avoid re-requesting it from other peers. | ||||
recentRejects->insert(tx.GetId()); | recentRejects->insert(tx.GetId()); | ||||
} | } | ||||
} else { | } else { | ||||
if (!state.CorruptionPossible()) { | if (!state.CorruptionPossible()) { | ||||
// Do not use rejection cache for witness transactions or | // Do not use rejection cache for witness transactions or | ||||
// witness-stripped transactions, as they can have been | // witness-stripped transactions, as they can have been | ||||
// malleated. See https://github.com/bitcoin/bitcoin/issues/8279 | // malleated. See https://github.com/bitcoin/bitcoin/issues/8279 | ||||
// for details. | // for details. | ||||
assert(recentRejects); | assert(recentRejects); | ||||
recentRejects->insert(tx.GetId()); | recentRejects->insert(tx.GetId()); | ||||
if (RecursiveDynamicUsage(*ptx) < 100000) { | if (RecursiveDynamicUsage(*ptx) < 100000) { | ||||
AddToCompactExtraTransactions(ptx); | g_orphanPool.AddToCompactExtraTransactions(ptx); | ||||
} | } | ||||
} | } | ||||
if (pfrom->fWhitelisted && | if (pfrom->fWhitelisted && | ||||
gArgs.GetBoolArg("-whitelistforcerelay", | gArgs.GetBoolArg("-whitelistforcerelay", | ||||
DEFAULT_WHITELISTFORCERELAY)) { | DEFAULT_WHITELISTFORCERELAY)) { | ||||
// Always relay transactions received from whitelisted peers, | // Always relay transactions received from whitelisted peers, | ||||
// even if they were already in the mempool or rejected from it | // even if they were already in the mempool or rejected from it | ||||
▲ Show 20 Lines • Show All 102 Lines • ▼ Show 20 Lines | else if (strCommand == NetMsgType::CMPCTBLOCK && !fImporting && !fReindex) { | ||||
bool fRevertToHeaderProcessing = false; | bool fRevertToHeaderProcessing = false; | ||||
// Keep a CBlock for "optimistic" compactblock reconstructions (see | // Keep a CBlock for "optimistic" compactblock reconstructions (see | ||||
// below) | // below) | ||||
std::shared_ptr<CBlock> pblock = std::make_shared<CBlock>(); | std::shared_ptr<CBlock> pblock = std::make_shared<CBlock>(); | ||||
bool fBlockReconstructed = false; | bool fBlockReconstructed = false; | ||||
{ | { | ||||
LOCK2(cs_main, g_cs_orphans); | LOCK(cs_main); | ||||
// If AcceptBlockHeader returned true, it set pindex | // If AcceptBlockHeader returned true, it set pindex | ||||
assert(pindex); | assert(pindex); | ||||
UpdateBlockAvailability(pfrom->GetId(), pindex->GetBlockHash()); | UpdateBlockAvailability(pfrom->GetId(), pindex->GetBlockHash()); | ||||
CNodeState *nodestate = State(pfrom->GetId()); | CNodeState *nodestate = State(pfrom->GetId()); | ||||
// If this was a new header with more work than our tip, update the | // If this was a new header with more work than our tip, update the | ||||
// peer's last block announcement time | // peer's last block announcement time | ||||
▲ Show 20 Lines • Show All 59 Lines • ▼ Show 20 Lines | else if (strCommand == NetMsgType::CMPCTBLOCK && !fImporting && !fReindex) { | ||||
LogPrint(BCLog::NET, "Peer sent us compact block " | LogPrint(BCLog::NET, "Peer sent us compact block " | ||||
"we were already syncing!\n"); | "we were already syncing!\n"); | ||||
return true; | return true; | ||||
} | } | ||||
} | } | ||||
PartiallyDownloadedBlock &partialBlock = | PartiallyDownloadedBlock &partialBlock = | ||||
*(*queuedBlockIt)->partialBlock; | *(*queuedBlockIt)->partialBlock; | ||||
ReadStatus status = | ReadStatus status = partialBlock.InitData( | ||||
partialBlock.InitData(cmpctblock, vExtraTxnForCompact); | cmpctblock, g_orphanPool.ExtraTxnForCompact()); | ||||
if (status == READ_STATUS_INVALID) { | if (status == READ_STATUS_INVALID) { | ||||
// Reset in-flight state in case of whitelist | // Reset in-flight state in case of whitelist | ||||
MarkBlockAsReceived(pindex->GetBlockHash()); | MarkBlockAsReceived(pindex->GetBlockHash()); | ||||
Misbehaving(pfrom, 100, "invalid-cmpctblk"); | Misbehaving(pfrom, 100, "invalid-cmpctblk"); | ||||
LogPrintf("Peer %d sent us invalid compact block\n", | LogPrintf("Peer %d sent us invalid compact block\n", | ||||
pfrom->GetId()); | pfrom->GetId()); | ||||
return true; | return true; | ||||
} else if (status == READ_STATUS_FAILED) { | } else if (status == READ_STATUS_FAILED) { | ||||
Show All 25 Lines | else if (strCommand == NetMsgType::CMPCTBLOCK && !fImporting && !fReindex) { | ||||
pfrom, msgMaker.Make(NetMsgType::GETBLOCKTXN, req)); | pfrom, msgMaker.Make(NetMsgType::GETBLOCKTXN, req)); | ||||
} | } | ||||
} else { | } else { | ||||
// This block is either already in flight from a different | // This block is either already in flight from a different | ||||
// peer, or this peer has too many blocks outstanding to | // peer, or this peer has too many blocks outstanding to | ||||
// download from. Optimistically try to reconstruct anyway | // download from. Optimistically try to reconstruct anyway | ||||
// since we might be able to without any round trips. | // since we might be able to without any round trips. | ||||
PartiallyDownloadedBlock tempBlock(config, &g_mempool); | PartiallyDownloadedBlock tempBlock(config, &g_mempool); | ||||
ReadStatus status = | ReadStatus status = tempBlock.InitData( | ||||
tempBlock.InitData(cmpctblock, vExtraTxnForCompact); | cmpctblock, g_orphanPool.ExtraTxnForCompact()); | ||||
if (status != READ_STATUS_OK) { | if (status != READ_STATUS_OK) { | ||||
// TODO: don't ignore failures | // TODO: don't ignore failures | ||||
return true; | return true; | ||||
} | } | ||||
std::vector<CTransactionRef> dummy; | std::vector<CTransactionRef> dummy; | ||||
status = tempBlock.FillBlock(*pblock, dummy); | status = tempBlock.FillBlock(*pblock, dummy); | ||||
if (status == READ_STATUS_OK) { | if (status == READ_STATUS_OK) { | ||||
fBlockReconstructed = true; | fBlockReconstructed = true; | ||||
▲ Show 20 Lines • Show All 1,469 Lines • ▼ Show 20 Lines | bool PeerLogicValidation::SendMessages(const Config &config, CNode *pto, | ||||
return true; | return true; | ||||
} | } | ||||
class CNetProcessingCleanup { | class CNetProcessingCleanup { | ||||
public: | public: | ||||
CNetProcessingCleanup() {} | CNetProcessingCleanup() {} | ||||
~CNetProcessingCleanup() { | ~CNetProcessingCleanup() { | ||||
// orphan transactions | // orphan transactions | ||||
mapOrphanTransactions.clear(); | g_orphanPool.clear(); | ||||
mapOrphanTransactionsByPrev.clear(); | |||||
} | } | ||||
} instance_of_cnetprocessingcleanup; | } instance_of_cnetprocessingcleanup; |