1
1
mirror of https://github.com/bitcoin/bitcoin.git synced 2024-05-17 23:56:39 +00:00

Merge bitcoin/bitcoin#26295: Replace global g_cs_orphans lock with local

7082ce3e88d77456d60a5a66bd38807fbd66f311 scripted-diff: rename and de-globalise g_cs_orphans (Anthony Towns)
733d85f79cde353d8c9b54370f296b1031fa33d9 Move all g_cs_orphans locking to txorphanage (Anthony Towns)
a936f41a5d5f7bb97425f82ec64dfae62e840a56 txorphanage: make m_peer_work_set private (Anthony Towns)
3614819864a84ac32f6d53c6ace79b5e71bc174a txorphange: move orphan workset to txorphanage (Anthony Towns)
6f8e442ba61378489a6e2e2ab5bcfbccca1a29ec net_processing: Localise orphan_work_set handling to ProcessOrphanTx (Anthony Towns)
0027174b396cacbaac5fd52f13be3dca9fcbbfb8 net_processing: move ProcessOrphanTx docs to declaration (Anthony Towns)
9910ed755c3dfd7669707b44d993a20030dd7477 net_processing: Pass a Peer& to ProcessOrphanTx (Anthony Towns)
89e2e0da0bcd0b0757d7b42907e2d2214da9f68b net_processing: move extra transactions to msgproc mutex (Anthony Towns)
ff8d44d1967d8ff9fb9b9ea0b87c0470d8cc2550 Remove unnecessary includes of txorphange.h (Anthony Towns)

Pull request description:

  Moves extra transactions to be under the `m_msgproc_mutex` lock rather than `g_cs_orphans` and refactors orphan handling so that the lock can be internal to the `TxOrphange` class.

ACKs for top commit:
  dergoegge:
    Code review ACK 7082ce3e88d77456d60a5a66bd38807fbd66f311
  glozow:
    ACK 7082ce3e88d77456d60a5a66bd38807fbd66f311 via code review and some [basic testing](https://github.com/glozow/bitcoin/blob/review-26295/src/test/orphanage_tests.cpp#L150). I think putting txorphanage in charge of handling peer work sets is the right direction.

Tree-SHA512: 1ec454c3a69ebd45ff652770d6a55c6b183db71aba4d12639ed70f525f0035e069a81d06e9b65b66e87929c607080a1c5e5dcd2ca91eaa2cf202dc6c02aa6818
This commit is contained in:
glozow 2022-11-28 10:51:15 +00:00
commit a79b720092
No known key found for this signature in database
GPG Key ID: BA03F4DBE0C63FB4
8 changed files with 124 additions and 97 deletions

View File

@ -67,7 +67,6 @@
#include <torcontrol.h> #include <torcontrol.h>
#include <txdb.h> #include <txdb.h>
#include <txmempool.h> #include <txmempool.h>
#include <txorphanage.h>
#include <util/asmap.h> #include <util/asmap.h>
#include <util/check.h> #include <util/check.h>
#include <util/moneystr.h> #include <util/moneystr.h>

View File

@ -365,9 +365,6 @@ struct Peer {
/** Total number of addresses that were processed (excludes rate-limited ones). */ /** Total number of addresses that were processed (excludes rate-limited ones). */
std::atomic<uint64_t> m_addr_processed{0}; std::atomic<uint64_t> m_addr_processed{0};
/** Set of txids to reconsider once their parent transactions have been accepted **/
std::set<uint256> m_orphan_work_set GUARDED_BY(g_cs_orphans);
/** Whether we've sent this peer a getheaders in response to an inv prior to initial-headers-sync completing */ /** Whether we've sent this peer a getheaders in response to an inv prior to initial-headers-sync completing */
bool m_inv_triggered_getheaders_before_sync GUARDED_BY(NetEventsInterface::g_msgproc_mutex){false}; bool m_inv_triggered_getheaders_before_sync GUARDED_BY(NetEventsInterface::g_msgproc_mutex){false};
@ -584,8 +581,17 @@ private:
*/ */
bool MaybeDiscourageAndDisconnect(CNode& pnode, Peer& peer); bool MaybeDiscourageAndDisconnect(CNode& pnode, Peer& peer);
void ProcessOrphanTx(std::set<uint256>& orphan_work_set) EXCLUSIVE_LOCKS_REQUIRED(cs_main, g_cs_orphans) /**
EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex); * Reconsider orphan transactions after a parent has been accepted to the mempool.
*
* @peer[in] peer The peer whose orphan transactions we will reconsider. Generally only one
* orphan will be reconsidered on each call of this function. This set
* may be added to if accepting an orphan causes its children to be
* reconsidered.
* @return True if there are still orphans in this peer's work set.
*/
bool ProcessOrphanTx(Peer& peer)
EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex, g_msgproc_mutex, cs_main);
/** Process a single headers message from a peer. /** Process a single headers message from a peer.
* *
* @param[in] pfrom CNode of the peer * @param[in] pfrom CNode of the peer
@ -919,14 +925,14 @@ private:
/** Storage for orphan information */ /** Storage for orphan information */
TxOrphanage m_orphanage; TxOrphanage m_orphanage;
void AddToCompactExtraTransactions(const CTransactionRef& tx) EXCLUSIVE_LOCKS_REQUIRED(g_cs_orphans); void AddToCompactExtraTransactions(const CTransactionRef& tx) EXCLUSIVE_LOCKS_REQUIRED(g_msgproc_mutex);
/** Orphan/conflicted/etc transactions that are kept for compact block reconstruction. /** Orphan/conflicted/etc transactions that are kept for compact block reconstruction.
* The last -blockreconstructionextratxn/DEFAULT_BLOCK_RECONSTRUCTION_EXTRA_TXN of * The last -blockreconstructionextratxn/DEFAULT_BLOCK_RECONSTRUCTION_EXTRA_TXN of
* these are kept in a ring buffer */ * these are kept in a ring buffer */
std::vector<std::pair<uint256, CTransactionRef>> vExtraTxnForCompact GUARDED_BY(g_cs_orphans); std::vector<std::pair<uint256, CTransactionRef>> vExtraTxnForCompact GUARDED_BY(g_msgproc_mutex);
/** Offset into vExtraTxnForCompact to insert the next tx */ /** Offset into vExtraTxnForCompact to insert the next tx */
size_t vExtraTxnForCompactIt GUARDED_BY(g_cs_orphans) = 0; size_t vExtraTxnForCompactIt GUARDED_BY(g_msgproc_mutex) = 0;
/** Check whether the last unknown block a peer advertised is not yet known. */ /** Check whether the last unknown block a peer advertised is not yet known. */
void ProcessBlockAvailability(NodeId nodeid) EXCLUSIVE_LOCKS_REQUIRED(cs_main); void ProcessBlockAvailability(NodeId nodeid) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
@ -1490,7 +1496,7 @@ void PeerManagerImpl::FinalizeNode(const CNode& node)
for (const QueuedBlock& entry : state->vBlocksInFlight) { for (const QueuedBlock& entry : state->vBlocksInFlight) {
mapBlocksInFlight.erase(entry.pindex->GetBlockHash()); mapBlocksInFlight.erase(entry.pindex->GetBlockHash());
} }
WITH_LOCK(g_cs_orphans, m_orphanage.EraseForPeer(nodeid)); m_orphanage.EraseForPeer(nodeid);
m_txrequest.DisconnectedPeer(nodeid); m_txrequest.DisconnectedPeer(nodeid);
if (m_txreconciliation) m_txreconciliation->ForgetPeer(nodeid); if (m_txreconciliation) m_txreconciliation->ForgetPeer(nodeid);
m_num_preferred_download_peers -= state->fPreferredDownload; m_num_preferred_download_peers -= state->fPreferredDownload;
@ -2880,33 +2886,24 @@ void PeerManagerImpl::ProcessHeadersMessage(CNode& pfrom, Peer& peer,
return; return;
} }
/** bool PeerManagerImpl::ProcessOrphanTx(Peer& peer)
* Reconsider orphan transactions after a parent has been accepted to the mempool.
*
* @param[in,out] orphan_work_set The set of orphan transactions to reconsider. Generally only one
* orphan will be reconsidered on each call of this function. This set
* may be added to if accepting an orphan causes its children to be
* reconsidered.
*/
void PeerManagerImpl::ProcessOrphanTx(std::set<uint256>& orphan_work_set)
{ {
AssertLockHeld(g_msgproc_mutex);
AssertLockHeld(cs_main); AssertLockHeld(cs_main);
AssertLockHeld(g_cs_orphans);
while (!orphan_work_set.empty()) { CTransactionRef porphanTx = nullptr;
const uint256 orphanHash = *orphan_work_set.begin(); NodeId from_peer = -1;
orphan_work_set.erase(orphan_work_set.begin()); bool more = false;
const auto [porphanTx, from_peer] = m_orphanage.GetTx(orphanHash);
if (porphanTx == nullptr) continue;
while (CTransactionRef porphanTx = m_orphanage.GetTxToReconsider(peer.m_id, from_peer, more)) {
const MempoolAcceptResult result = m_chainman.ProcessTransaction(porphanTx); const MempoolAcceptResult result = m_chainman.ProcessTransaction(porphanTx);
const TxValidationState& state = result.m_state; const TxValidationState& state = result.m_state;
const uint256& orphanHash = porphanTx->GetHash();
if (result.m_result_type == MempoolAcceptResult::ResultType::VALID) { if (result.m_result_type == MempoolAcceptResult::ResultType::VALID) {
LogPrint(BCLog::MEMPOOL, " accepted orphan tx %s\n", orphanHash.ToString()); LogPrint(BCLog::MEMPOOL, " accepted orphan tx %s\n", orphanHash.ToString());
RelayTransaction(orphanHash, porphanTx->GetWitnessHash()); RelayTransaction(orphanHash, porphanTx->GetWitnessHash());
m_orphanage.AddChildrenToWorkSet(*porphanTx, orphan_work_set); m_orphanage.AddChildrenToWorkSet(*porphanTx, peer.m_id);
m_orphanage.EraseTx(orphanHash); m_orphanage.EraseTx(orphanHash);
for (const CTransactionRef& removedTx : result.m_replaced_transactions.value()) { for (const CTransactionRef& removedTx : result.m_replaced_transactions.value()) {
AddToCompactExtraTransactions(removedTx); AddToCompactExtraTransactions(removedTx);
@ -2957,6 +2954,8 @@ void PeerManagerImpl::ProcessOrphanTx(std::set<uint256>& orphan_work_set)
break; break;
} }
} }
return more;
} }
bool PeerManagerImpl::PrepareBlockFilterRequest(CNode& node, Peer& peer, bool PeerManagerImpl::PrepareBlockFilterRequest(CNode& node, Peer& peer,
@ -3990,7 +3989,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
AddKnownTx(*peer, txid); AddKnownTx(*peer, txid);
} }
LOCK2(cs_main, g_cs_orphans); LOCK(cs_main);
m_txrequest.ReceivedResponse(pfrom.GetId(), txid); m_txrequest.ReceivedResponse(pfrom.GetId(), txid);
if (tx.HasWitness()) m_txrequest.ReceivedResponse(pfrom.GetId(), wtxid); if (tx.HasWitness()) m_txrequest.ReceivedResponse(pfrom.GetId(), wtxid);
@ -4031,7 +4030,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
m_txrequest.ForgetTxHash(tx.GetHash()); m_txrequest.ForgetTxHash(tx.GetHash());
m_txrequest.ForgetTxHash(tx.GetWitnessHash()); m_txrequest.ForgetTxHash(tx.GetWitnessHash());
RelayTransaction(tx.GetHash(), tx.GetWitnessHash()); RelayTransaction(tx.GetHash(), tx.GetWitnessHash());
m_orphanage.AddChildrenToWorkSet(tx, peer->m_orphan_work_set); m_orphanage.AddChildrenToWorkSet(tx, peer->m_id);
pfrom.m_last_tx_time = GetTime<std::chrono::seconds>(); pfrom.m_last_tx_time = GetTime<std::chrono::seconds>();
@ -4045,7 +4044,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
} }
// Recursively process any orphan transactions that depended on this one // Recursively process any orphan transactions that depended on this one
ProcessOrphanTx(peer->m_orphan_work_set); ProcessOrphanTx(*peer);
} }
else if (state.GetResult() == TxValidationResult::TX_MISSING_INPUTS) else if (state.GetResult() == TxValidationResult::TX_MISSING_INPUTS)
{ {
@ -4226,7 +4225,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
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());
@ -4854,16 +4853,17 @@ bool PeerManagerImpl::ProcessMessages(CNode* pfrom, std::atomic<bool>& interrupt
} }
} }
bool has_more_orphans;
{ {
LOCK2(cs_main, g_cs_orphans); LOCK(cs_main);
if (!peer->m_orphan_work_set.empty()) { has_more_orphans = ProcessOrphanTx(*peer);
ProcessOrphanTx(peer->m_orphan_work_set);
}
} }
if (pfrom->fDisconnect) if (pfrom->fDisconnect)
return false; return false;
if (has_more_orphans) return true;
// this maintains the order of responses // this maintains the order of responses
// and prevents m_getdata_requests to grow unbounded // and prevents m_getdata_requests to grow unbounded
{ {
@ -4871,11 +4871,6 @@ bool PeerManagerImpl::ProcessMessages(CNode* pfrom, std::atomic<bool>& interrupt
if (!peer->m_getdata_requests.empty()) return true; if (!peer->m_getdata_requests.empty()) return true;
} }
{
LOCK(g_cs_orphans);
if (!peer->m_orphan_work_set.empty()) return true;
}
// Don't bother if send buffer is too full to respond anyway // Don't bother if send buffer is too full to respond anyway
if (pfrom->fPauseSend) return false; if (pfrom->fPauseSend) return false;

View File

@ -19,7 +19,6 @@
#include <test/util/net.h> #include <test/util/net.h>
#include <test/util/setup_common.h> #include <test/util/setup_common.h>
#include <test/util/validation.h> #include <test/util/validation.h>
#include <txorphanage.h>
#include <validationinterface.h> #include <validationinterface.h>
#include <version.h> #include <version.h>

View File

@ -14,7 +14,6 @@
#include <test/util/net.h> #include <test/util/net.h>
#include <test/util/setup_common.h> #include <test/util/setup_common.h>
#include <test/util/validation.h> #include <test/util/validation.h>
#include <txorphanage.h>
#include <validation.h> #include <validation.h>
#include <validationinterface.h> #include <validationinterface.h>

View File

@ -36,7 +36,6 @@ FUZZ_TARGET_INIT(txorphan, initialize_orphanage)
SetMockTime(ConsumeTime(fuzzed_data_provider)); SetMockTime(ConsumeTime(fuzzed_data_provider));
TxOrphanage orphanage; TxOrphanage orphanage;
std::set<uint256> orphan_work_set;
std::vector<COutPoint> outpoints; std::vector<COutPoint> outpoints;
// initial outpoints used to construct transactions later // initial outpoints used to construct transactions later
for (uint8_t i = 0; i < 4; i++) { for (uint8_t i = 0; i < 4; i++) {
@ -86,15 +85,19 @@ FUZZ_TARGET_INIT(txorphan, initialize_orphanage)
CallOneOf( CallOneOf(
fuzzed_data_provider, fuzzed_data_provider,
[&] { [&] {
LOCK(g_cs_orphans); orphanage.AddChildrenToWorkSet(*tx, peer_id);
orphanage.AddChildrenToWorkSet(*tx, orphan_work_set);
}, },
[&] { [&] {
bool have_tx = orphanage.HaveTx(GenTxid::Txid(tx->GetHash())) || orphanage.HaveTx(GenTxid::Wtxid(tx->GetHash()));
{ {
LOCK(g_cs_orphans); NodeId originator;
bool get_tx = orphanage.GetTx(tx->GetHash()).first != nullptr; bool more = true;
Assert(have_tx == get_tx); CTransactionRef ref = orphanage.GetTxToReconsider(peer_id, originator, more);
if (!ref) {
Assert(!more);
} else {
bool have_tx = orphanage.HaveTx(GenTxid::Txid(ref->GetHash())) || orphanage.HaveTx(GenTxid::Wtxid(ref->GetHash()));
Assert(have_tx);
}
} }
}, },
[&] { [&] {
@ -102,14 +105,12 @@ FUZZ_TARGET_INIT(txorphan, initialize_orphanage)
// AddTx should return false if tx is too big or already have it // AddTx should return false if tx is too big or already have it
// tx weight is unknown, we only check when tx is already in orphanage // tx weight is unknown, we only check when tx is already in orphanage
{ {
LOCK(g_cs_orphans);
bool add_tx = orphanage.AddTx(tx, peer_id); bool add_tx = orphanage.AddTx(tx, peer_id);
// have_tx == true -> add_tx == false // have_tx == true -> add_tx == false
Assert(!have_tx || !add_tx); Assert(!have_tx || !add_tx);
} }
have_tx = orphanage.HaveTx(GenTxid::Txid(tx->GetHash())) || orphanage.HaveTx(GenTxid::Wtxid(tx->GetHash())); have_tx = orphanage.HaveTx(GenTxid::Txid(tx->GetHash())) || orphanage.HaveTx(GenTxid::Wtxid(tx->GetHash()));
{ {
LOCK(g_cs_orphans);
bool add_tx = orphanage.AddTx(tx, peer_id); bool add_tx = orphanage.AddTx(tx, peer_id);
// if have_tx is still false, it must be too big // if have_tx is still false, it must be too big
Assert(!have_tx == (GetTransactionWeight(*tx) > MAX_STANDARD_TX_WEIGHT)); Assert(!have_tx == (GetTransactionWeight(*tx) > MAX_STANDARD_TX_WEIGHT));
@ -120,25 +121,22 @@ FUZZ_TARGET_INIT(txorphan, initialize_orphanage)
bool have_tx = orphanage.HaveTx(GenTxid::Txid(tx->GetHash())) || orphanage.HaveTx(GenTxid::Wtxid(tx->GetHash())); bool have_tx = orphanage.HaveTx(GenTxid::Txid(tx->GetHash())) || orphanage.HaveTx(GenTxid::Wtxid(tx->GetHash()));
// EraseTx should return 0 if m_orphans doesn't have the tx // EraseTx should return 0 if m_orphans doesn't have the tx
{ {
LOCK(g_cs_orphans);
Assert(have_tx == orphanage.EraseTx(tx->GetHash())); Assert(have_tx == orphanage.EraseTx(tx->GetHash()));
} }
have_tx = orphanage.HaveTx(GenTxid::Txid(tx->GetHash())) || orphanage.HaveTx(GenTxid::Wtxid(tx->GetHash())); have_tx = orphanage.HaveTx(GenTxid::Txid(tx->GetHash())) || orphanage.HaveTx(GenTxid::Wtxid(tx->GetHash()));
// have_tx should be false and EraseTx should fail // have_tx should be false and EraseTx should fail
{ {
LOCK(g_cs_orphans);
Assert(!have_tx && !orphanage.EraseTx(tx->GetHash())); Assert(!have_tx && !orphanage.EraseTx(tx->GetHash()));
} }
}, },
[&] { [&] {
LOCK(g_cs_orphans);
orphanage.EraseForPeer(peer_id); orphanage.EraseForPeer(peer_id);
}, },
[&] { [&] {
// test mocktime and expiry // test mocktime and expiry
SetMockTime(ConsumeTime(fuzzed_data_provider)); SetMockTime(ConsumeTime(fuzzed_data_provider));
auto limit = fuzzed_data_provider.ConsumeIntegral<unsigned int>(); auto limit = fuzzed_data_provider.ConsumeIntegral<unsigned int>();
WITH_LOCK(g_cs_orphans, orphanage.LimitOrphans(limit)); orphanage.LimitOrphans(limit);
Assert(orphanage.Size() <= limit); Assert(orphanage.Size() <= limit);
}); });
} }

View File

@ -20,13 +20,15 @@ BOOST_FIXTURE_TEST_SUITE(orphanage_tests, TestingSetup)
class TxOrphanageTest : public TxOrphanage class TxOrphanageTest : public TxOrphanage
{ {
public: public:
inline size_t CountOrphans() const EXCLUSIVE_LOCKS_REQUIRED(g_cs_orphans) inline size_t CountOrphans() const EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
{ {
LOCK(m_mutex);
return m_orphans.size(); return m_orphans.size();
} }
CTransactionRef RandomOrphan() EXCLUSIVE_LOCKS_REQUIRED(g_cs_orphans) CTransactionRef RandomOrphan() EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
{ {
LOCK(m_mutex);
std::map<uint256, OrphanTx>::iterator it; std::map<uint256, OrphanTx>::iterator it;
it = m_orphans.lower_bound(InsecureRand256()); it = m_orphans.lower_bound(InsecureRand256());
if (it == m_orphans.end()) if (it == m_orphans.end())
@ -59,8 +61,6 @@ BOOST_AUTO_TEST_CASE(DoS_mapOrphans)
FillableSigningProvider keystore; FillableSigningProvider keystore;
BOOST_CHECK(keystore.AddKey(key)); BOOST_CHECK(keystore.AddKey(key));
LOCK(g_cs_orphans);
// 50 orphan transactions: // 50 orphan transactions:
for (int i = 0; i < 50; i++) for (int i = 0; i < 50; i++)
{ {

View File

@ -15,11 +15,10 @@ 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;
bool TxOrphanage::AddTx(const CTransactionRef& tx, NodeId peer) bool TxOrphanage::AddTx(const CTransactionRef& tx, NodeId peer)
{ {
AssertLockHeld(g_cs_orphans); LOCK(m_mutex);
const uint256& hash = tx->GetHash(); const uint256& hash = tx->GetHash();
if (m_orphans.count(hash)) if (m_orphans.count(hash))
@ -55,7 +54,13 @@ bool TxOrphanage::AddTx(const CTransactionRef& tx, NodeId peer)
int TxOrphanage::EraseTx(const uint256& txid) int TxOrphanage::EraseTx(const uint256& txid)
{ {
AssertLockHeld(g_cs_orphans); 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); std::map<uint256, OrphanTx>::iterator it = m_orphans.find(txid);
if (it == m_orphans.end()) if (it == m_orphans.end())
return 0; return 0;
@ -87,7 +92,9 @@ int TxOrphanage::EraseTx(const uint256& txid)
void TxOrphanage::EraseForPeer(NodeId peer) void TxOrphanage::EraseForPeer(NodeId peer)
{ {
AssertLockHeld(g_cs_orphans); LOCK(m_mutex);
m_peer_work_set.erase(peer);
int nErased = 0; int nErased = 0;
std::map<uint256, OrphanTx>::iterator iter = m_orphans.begin(); std::map<uint256, OrphanTx>::iterator iter = m_orphans.begin();
@ -96,7 +103,7 @@ void TxOrphanage::EraseForPeer(NodeId peer)
std::map<uint256, OrphanTx>::iterator maybeErase = iter++; // increment to avoid iterator becoming invalid std::map<uint256, OrphanTx>::iterator maybeErase = iter++; // increment to avoid iterator becoming invalid
if (maybeErase->second.fromPeer == peer) if (maybeErase->second.fromPeer == peer)
{ {
nErased += EraseTx(maybeErase->second.tx->GetHash()); nErased += _EraseTx(maybeErase->second.tx->GetHash());
} }
} }
if (nErased > 0) LogPrint(BCLog::MEMPOOL, "Erased %d orphan tx from peer=%d\n", nErased, peer); if (nErased > 0) LogPrint(BCLog::MEMPOOL, "Erased %d orphan tx from peer=%d\n", nErased, peer);
@ -104,7 +111,7 @@ void TxOrphanage::EraseForPeer(NodeId peer)
void TxOrphanage::LimitOrphans(unsigned int max_orphans) void TxOrphanage::LimitOrphans(unsigned int max_orphans)
{ {
AssertLockHeld(g_cs_orphans); LOCK(m_mutex);
unsigned int nEvicted = 0; unsigned int nEvicted = 0;
static int64_t nNextSweep; static int64_t nNextSweep;
@ -118,7 +125,7 @@ void TxOrphanage::LimitOrphans(unsigned int max_orphans)
{ {
std::map<uint256, OrphanTx>::iterator maybeErase = iter++; std::map<uint256, OrphanTx>::iterator maybeErase = iter++;
if (maybeErase->second.nTimeExpire <= nNow) { if (maybeErase->second.nTimeExpire <= nNow) {
nErased += EraseTx(maybeErase->second.tx->GetHash()); nErased += _EraseTx(maybeErase->second.tx->GetHash());
} else { } else {
nMinExpTime = std::min(maybeErase->second.nTimeExpire, nMinExpTime); nMinExpTime = std::min(maybeErase->second.nTimeExpire, nMinExpTime);
} }
@ -132,15 +139,19 @@ void TxOrphanage::LimitOrphans(unsigned int max_orphans)
{ {
// Evict a random orphan: // Evict a random orphan:
size_t randompos = rng.randrange(m_orphan_list.size()); size_t randompos = rng.randrange(m_orphan_list.size());
EraseTx(m_orphan_list[randompos]->first); _EraseTx(m_orphan_list[randompos]->first);
++nEvicted; ++nEvicted;
} }
if (nEvicted > 0) LogPrint(BCLog::MEMPOOL, "orphanage overflow, removed %u tx\n", nEvicted); if (nEvicted > 0) LogPrint(BCLog::MEMPOOL, "orphanage overflow, removed %u tx\n", nEvicted);
} }
void TxOrphanage::AddChildrenToWorkSet(const CTransaction& tx, std::set<uint256>& orphan_work_set) const void TxOrphanage::AddChildrenToWorkSet(const CTransaction& tx, NodeId peer)
{ {
AssertLockHeld(g_cs_orphans); LOCK(m_mutex);
// Get this peer's work set, emplacing an empty set it didn't exist
std::set<uint256>& orphan_work_set = m_peer_work_set.try_emplace(peer).first->second;
for (unsigned int i = 0; i < tx.vout.size(); i++) { 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)); 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()) { if (it_by_prev != m_outpoint_to_orphan_it.end()) {
@ -153,7 +164,7 @@ void TxOrphanage::AddChildrenToWorkSet(const CTransaction& tx, std::set<uint256>
bool TxOrphanage::HaveTx(const GenTxid& gtxid) const bool TxOrphanage::HaveTx(const GenTxid& gtxid) const
{ {
LOCK(g_cs_orphans); LOCK(m_mutex);
if (gtxid.IsWtxid()) { if (gtxid.IsWtxid()) {
return m_wtxid_to_orphan_it.count(gtxid.GetHash()); return m_wtxid_to_orphan_it.count(gtxid.GetHash());
} else { } else {
@ -161,18 +172,32 @@ bool TxOrphanage::HaveTx(const GenTxid& gtxid) const
} }
} }
std::pair<CTransactionRef, NodeId> TxOrphanage::GetTx(const uint256& txid) const CTransactionRef TxOrphanage::GetTxToReconsider(NodeId peer, NodeId& originator, bool& more)
{ {
AssertLockHeld(g_cs_orphans); LOCK(m_mutex);
const auto it = m_orphans.find(txid); auto work_set_it = m_peer_work_set.find(peer);
if (it == m_orphans.end()) return {nullptr, -1}; if (work_set_it != m_peer_work_set.end()) {
return {it->second.tx, it->second.fromPeer}; 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()) {
more = !work_set.empty();
originator = orphan_it->second.fromPeer;
return orphan_it->second.tx;
}
}
}
more = false;
return nullptr;
} }
void TxOrphanage::EraseForBlock(const CBlock& block) void TxOrphanage::EraseForBlock(const CBlock& block)
{ {
LOCK(g_cs_orphans); LOCK(m_mutex);
std::vector<uint256> vOrphanErase; std::vector<uint256> vOrphanErase;
@ -195,7 +220,7 @@ void TxOrphanage::EraseForBlock(const CBlock& block)
if (vOrphanErase.size()) { if (vOrphanErase.size()) {
int nErased = 0; int nErased = 0;
for (const uint256& orphanHash : vOrphanErase) { for (const uint256& orphanHash : vOrphanErase) {
nErased += EraseTx(orphanHash); nErased += _EraseTx(orphanHash);
} }
LogPrint(BCLog::MEMPOOL, "Erased %d orphan tx included or conflicted by block\n", nErased); LogPrint(BCLog::MEMPOOL, "Erased %d orphan tx included or conflicted by block\n", nErased);
} }

View File

@ -10,8 +10,8 @@
#include <primitives/transaction.h> #include <primitives/transaction.h>
#include <sync.h> #include <sync.h>
/** Guards orphan transactions and extra txs for compact blocks */ #include <map>
extern RecursiveMutex g_cs_orphans; #include <set>
/** A class to track orphan transactions (failed on TX_MISSING_INPUTS) /** A class to track orphan transactions (failed on TX_MISSING_INPUTS)
* Since we cannot distinguish orphans from bad transactions with * Since we cannot distinguish orphans from bad transactions with
@ -21,40 +21,46 @@ extern RecursiveMutex g_cs_orphans;
class TxOrphanage { class TxOrphanage {
public: public:
/** Add a new orphan transaction */ /** Add a new orphan transaction */
bool AddTx(const CTransactionRef& tx, NodeId peer) EXCLUSIVE_LOCKS_REQUIRED(g_cs_orphans); bool AddTx(const CTransactionRef& tx, NodeId peer) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex);
/** Check if we already have an orphan transaction (by txid or wtxid) */ /** Check if we already have an orphan transaction (by txid or wtxid) */
bool HaveTx(const GenTxid& gtxid) const LOCKS_EXCLUDED(::g_cs_orphans); bool HaveTx(const GenTxid& gtxid) const EXCLUSIVE_LOCKS_REQUIRED(!m_mutex);
/** Get an orphan transaction and its originating peer /** Extract a transaction from a peer's work set
* (Transaction ref will be nullptr if not found) * Returns nullptr and sets more to false if there are no transactions
* to work on. Otherwise returns the transaction reference, removes
* the transaction from the work set, and populates its arguments with
* the originating peer, and whether there are more orphans for this peer
* to work on after this tx.
*/ */
std::pair<CTransactionRef, NodeId> GetTx(const uint256& txid) const EXCLUSIVE_LOCKS_REQUIRED(g_cs_orphans); CTransactionRef GetTxToReconsider(NodeId peer, NodeId& originator, bool& more) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex);
/** Erase an orphan by txid */ /** Erase an orphan by txid */
int EraseTx(const uint256& txid) EXCLUSIVE_LOCKS_REQUIRED(g_cs_orphans); int EraseTx(const uint256& txid) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex);
/** Erase all orphans announced by a peer (eg, after that peer disconnects) */ /** Erase all orphans announced by a peer (eg, after that peer disconnects) */
void EraseForPeer(NodeId peer) EXCLUSIVE_LOCKS_REQUIRED(g_cs_orphans); void EraseForPeer(NodeId peer) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex);
/** Erase all orphans included in or invalidated by a new block */ /** Erase all orphans included in or invalidated by a new block */
void EraseForBlock(const CBlock& block) LOCKS_EXCLUDED(::g_cs_orphans); void EraseForBlock(const CBlock& block) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex);
/** Limit the orphanage to the given maximum */ /** Limit the orphanage to the given maximum */
void LimitOrphans(unsigned int max_orphans) EXCLUSIVE_LOCKS_REQUIRED(g_cs_orphans); void LimitOrphans(unsigned int max_orphans) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex);
/** Add any orphans that list a particular tx as a parent into a peer's work set /** Add any orphans that list a particular tx as a parent into a peer's work set */
* (ie orphans that may have found their final missing parent, and so should be reconsidered for the mempool) */ void AddChildrenToWorkSet(const CTransaction& tx, NodeId peer) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex);
void AddChildrenToWorkSet(const CTransaction& tx, std::set<uint256>& orphan_work_set) const EXCLUSIVE_LOCKS_REQUIRED(g_cs_orphans);
/** Return how many entries exist in the orphange */ /** Return how many entries exist in the orphange */
size_t Size() LOCKS_EXCLUDED(::g_cs_orphans) size_t Size() EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
{ {
LOCK(::g_cs_orphans); LOCK(m_mutex);
return m_orphans.size(); return m_orphans.size();
} }
protected: protected:
/** Guards orphan transactions */
mutable Mutex m_mutex;
struct OrphanTx { struct OrphanTx {
CTransactionRef tx; CTransactionRef tx;
NodeId fromPeer; NodeId fromPeer;
@ -64,7 +70,10 @@ protected:
/** Map from txid to orphan transaction record. Limited by /** Map from txid to orphan transaction record. Limited by
* -maxorphantx/DEFAULT_MAX_ORPHAN_TRANSACTIONS */ * -maxorphantx/DEFAULT_MAX_ORPHAN_TRANSACTIONS */
std::map<uint256, OrphanTx> m_orphans GUARDED_BY(g_cs_orphans); std::map<uint256, OrphanTx> m_orphans GUARDED_BY(m_mutex);
/** Which peer provided a parent tx of orphans that need to be reconsidered */
std::map<NodeId, std::set<uint256>> m_peer_work_set GUARDED_BY(m_mutex);
using OrphanMap = decltype(m_orphans); using OrphanMap = decltype(m_orphans);
@ -79,14 +88,17 @@ protected:
/** Index from the parents' COutPoint into the m_orphans. Used /** Index from the parents' COutPoint into the m_orphans. Used
* to remove orphan transactions from the m_orphans */ * to remove orphan transactions from the m_orphans */
std::map<COutPoint, std::set<OrphanMap::iterator, IteratorComparator>> m_outpoint_to_orphan_it GUARDED_BY(g_cs_orphans); std::map<COutPoint, std::set<OrphanMap::iterator, IteratorComparator>> m_outpoint_to_orphan_it GUARDED_BY(m_mutex);
/** Orphan transactions in vector for quick random eviction */ /** Orphan transactions in vector for quick random eviction */
std::vector<OrphanMap::iterator> m_orphan_list GUARDED_BY(g_cs_orphans); std::vector<OrphanMap::iterator> m_orphan_list GUARDED_BY(m_mutex);
/** Index from wtxid into the m_orphans to lookup orphan /** Index from wtxid into the m_orphans to lookup orphan
* transactions using their witness ids. */ * transactions using their witness ids. */
std::map<uint256, OrphanMap::iterator> m_wtxid_to_orphan_it GUARDED_BY(g_cs_orphans); std::map<uint256, OrphanMap::iterator> m_wtxid_to_orphan_it GUARDED_BY(m_mutex);
/** Erase an orphan by txid */
int _EraseTx(const uint256& txid) EXCLUSIVE_LOCKS_REQUIRED(m_mutex);
}; };
#endif // BITCOIN_TXORPHANAGE_H #endif // BITCOIN_TXORPHANAGE_H