Skip to content

Conversation

w0xlt
Copy link
Contributor

@w0xlt w0xlt commented Jan 22, 2022

This PR is related to #19303 and gets rid of the RecursiveMutex m_tx_inventory_mutex and also adds AssertLockNotHeld macros combined with LOCKS_EXCLUDED thread safety annotations to avoid recursive locking.

@w0xlt w0xlt marked this pull request as draft January 22, 2022 02:36
@DrahtBot DrahtBot added the P2P label Jan 22, 2022
@w0xlt w0xlt force-pushed the cs_tx_inventory_mutex branch 2 times, most recently from 77c522b to 50f1534 Compare January 22, 2022 08:11
@DrahtBot
Copy link
Contributor

DrahtBot commented Jan 22, 2022

The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

Reviews

See the guideline for information on the review process.

Type Reviewers
Concept ACK hebasto
Approach ACK shaavan

If your review is incorrectly listed, please react with 👎 to this comment and the bot will ignore it on the next update.

Conflicts

Reviewers, this pull request conflicts with the following ones:

  • #26593 (tracing: Only prepare tracepoint arguments when actually tracing by 0xB10C)
  • #26569 (p2p: Ensure transaction announcements are only queued for fully connected peers by dergoegge)
  • #26283 (p2p: Fill reconciliation sets and request reconciliation (Erlay) by naumenkogs)
  • #26140 (refactor: Move CNodeState members guarded by g_msgproc_mutex to Peer by dergoegge)

If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first.

@w0xlt w0xlt force-pushed the cs_tx_inventory_mutex branch from 50f1534 to d097e5d Compare January 22, 2022 08:27
@w0xlt w0xlt marked this pull request as ready for review January 22, 2022 09:54
Copy link
Contributor

@shaavan shaavan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approach ACK d097e5d

  • I agree with the name change of cs_tx_inventory -> m_tx_inventory_mutex.
  • Checked that all the locking instances of m_tx_inventory_mutex are protected by a preceding AssertLockNotHeld() statement.
  • LOCKS_EXCLUDED macro is appropriately used with all the function definitions where AssertLockNotHeld() is used.

There is a minor nit that you should address before merging this PR. 🥂

@hebasto
Copy link
Member

hebasto commented Jan 24, 2022

Concept ACK.

But reviewing changes in PeerManagerImpl::SendMessages() is not trivial because of a huge critical section size. Also there are concerns about external synchronizing (similar to #24122 (review)).

@w0xlt
Copy link
Contributor Author

w0xlt commented Jan 25, 2022

To address the external synchronizing, the code snippets below should be encapsulated as CNode::TxRelay methods?

bitcoin/src/net_processing.cpp

Lines 2038 to 2041 in bd482b3

if (WITH_LOCK(pfrom.m_tx_relay->cs_tx_inventory, return !pfrom.m_tx_relay->filterInventoryKnown.contains(parent_txid))) {
LOCK(cs_main);
State(pfrom.GetId())->m_recently_announced_invs.insert(parent_txid);
}

bitcoin/src/net_processing.cpp

Lines 1904 to 1910 in bd482b3

if (pfrom.m_tx_relay != nullptr) {
LOCK(pfrom.m_tx_relay->cs_filter);
if (pfrom.m_tx_relay->pfilter) {
sendMerkleBlock = true;
merkleBlock = CMerkleBlock(*pblock, *pfrom.m_tx_relay->pfilter);
}
}

bitcoin/src/net_processing.cpp

Lines 4807 to 4935 in bd482b3

if (pto->m_tx_relay != nullptr) {
LOCK(pto->m_tx_relay->cs_tx_inventory);
// Check whether periodic sends should happen
bool fSendTrickle = pto->HasPermission(NetPermissionFlags::NoBan);
if (pto->m_tx_relay->nNextInvSend < current_time) {
fSendTrickle = true;
if (pto->IsInboundConn()) {
pto->m_tx_relay->nNextInvSend = NextInvToInbounds(current_time, INBOUND_INVENTORY_BROADCAST_INTERVAL);
} else {
pto->m_tx_relay->nNextInvSend = GetExponentialRand(current_time, OUTBOUND_INVENTORY_BROADCAST_INTERVAL);
}
}
// Time to send but the peer has requested we not relay transactions.
if (fSendTrickle) {
LOCK(pto->m_tx_relay->cs_filter);
if (!pto->m_tx_relay->fRelayTxes) pto->m_tx_relay->setInventoryTxToSend.clear();
}
// Respond to BIP35 mempool requests
if (fSendTrickle && pto->m_tx_relay->fSendMempool) {
auto vtxinfo = m_mempool.infoAll();
pto->m_tx_relay->fSendMempool = false;
const CFeeRate filterrate{pto->m_tx_relay->minFeeFilter.load()};
LOCK(pto->m_tx_relay->cs_filter);
for (const auto& txinfo : vtxinfo) {
const uint256& hash = state.m_wtxid_relay ? txinfo.tx->GetWitnessHash() : txinfo.tx->GetHash();
CInv inv(state.m_wtxid_relay ? MSG_WTX : MSG_TX, hash);
pto->m_tx_relay->setInventoryTxToSend.erase(hash);
// Don't send transactions that peers will not put into their mempool
if (txinfo.fee < filterrate.GetFee(txinfo.vsize)) {
continue;
}
if (pto->m_tx_relay->pfilter) {
if (!pto->m_tx_relay->pfilter->IsRelevantAndUpdate(*txinfo.tx)) continue;
}
pto->m_tx_relay->filterInventoryKnown.insert(hash);
// Responses to MEMPOOL requests bypass the m_recently_announced_invs filter.
vInv.push_back(inv);
if (vInv.size() == MAX_INV_SZ) {
m_connman.PushMessage(pto, msgMaker.Make(NetMsgType::INV, vInv));
vInv.clear();
}
}
pto->m_tx_relay->m_last_mempool_req = std::chrono::duration_cast<std::chrono::seconds>(current_time);
}
// Determine transactions to relay
if (fSendTrickle) {
// Produce a vector with all candidates for sending
std::vector<std::set<uint256>::iterator> vInvTx;
vInvTx.reserve(pto->m_tx_relay->setInventoryTxToSend.size());
for (std::set<uint256>::iterator it = pto->m_tx_relay->setInventoryTxToSend.begin(); it != pto->m_tx_relay->setInventoryTxToSend.end(); it++) {
vInvTx.push_back(it);
}
const CFeeRate filterrate{pto->m_tx_relay->minFeeFilter.load()};
// Topologically and fee-rate sort the inventory we send for privacy and priority reasons.
// A heap is used so that not all items need sorting if only a few are being sent.
CompareInvMempoolOrder compareInvMempoolOrder(&m_mempool, state.m_wtxid_relay);
std::make_heap(vInvTx.begin(), vInvTx.end(), compareInvMempoolOrder);
// No reason to drain out at many times the network's capacity,
// especially since we have many peers and some will draw much shorter delays.
unsigned int nRelayedTransactions = 0;
LOCK(pto->m_tx_relay->cs_filter);
while (!vInvTx.empty() && nRelayedTransactions < INVENTORY_BROADCAST_MAX) {
// Fetch the top element from the heap
std::pop_heap(vInvTx.begin(), vInvTx.end(), compareInvMempoolOrder);
std::set<uint256>::iterator it = vInvTx.back();
vInvTx.pop_back();
uint256 hash = *it;
CInv inv(state.m_wtxid_relay ? MSG_WTX : MSG_TX, hash);
// Remove it from the to-be-sent set
pto->m_tx_relay->setInventoryTxToSend.erase(it);
// Check if not in the filter already
if (pto->m_tx_relay->filterInventoryKnown.contains(hash)) {
continue;
}
// Not in the mempool anymore? don't bother sending it.
auto txinfo = m_mempool.info(ToGenTxid(inv));
if (!txinfo.tx) {
continue;
}
auto txid = txinfo.tx->GetHash();
auto wtxid = txinfo.tx->GetWitnessHash();
// Peer told you to not send transactions at that feerate? Don't bother sending it.
if (txinfo.fee < filterrate.GetFee(txinfo.vsize)) {
continue;
}
if (pto->m_tx_relay->pfilter && !pto->m_tx_relay->pfilter->IsRelevantAndUpdate(*txinfo.tx)) continue;
// Send
State(pto->GetId())->m_recently_announced_invs.insert(hash);
vInv.push_back(inv);
nRelayedTransactions++;
{
// Expire old relay messages
while (!g_relay_expiration.empty() && g_relay_expiration.front().first < current_time)
{
mapRelay.erase(g_relay_expiration.front().second);
g_relay_expiration.pop_front();
}
auto ret = mapRelay.emplace(txid, std::move(txinfo.tx));
if (ret.second) {
g_relay_expiration.emplace_back(current_time + RELAY_TX_CACHE_TIME, ret.first);
}
// Add wtxid-based lookup into mapRelay as well, so that peers can request by wtxid
auto ret2 = mapRelay.emplace(wtxid, ret.first->second);
if (ret2.second) {
g_relay_expiration.emplace_back(current_time + RELAY_TX_CACHE_TIME, ret2.first);
}
}
if (vInv.size() == MAX_INV_SZ) {
m_connman.PushMessage(pto, msgMaker.Make(NetMsgType::INV, vInv));
vInv.clear();
}
pto->m_tx_relay->filterInventoryKnown.insert(hash);
if (hash != txid) {
// Insert txid into filterInventoryKnown, even for
// wtxidrelay peers. This prevents re-adding of
// unconfirmed parents to the recently_announced
// filter, when a child tx is requested. See
// ProcessGetData().
pto->m_tx_relay->filterInventoryKnown.insert(txid);
}
}
}
}

@hebasto
Copy link
Member

hebasto commented Jan 25, 2022

To address the external synchronizing, the code snippets below should be encapsulated as CNode::TxRelay methods?

That is my guess. Of course, it would be nice to hear other devs opinion.

@w0xlt
Copy link
Contributor Author

w0xlt commented Feb 12, 2022

The new commit addresses most of #24125 (comment).

All code snippets protected by cs_tx_inventory\m_tx_inventory_mutex (except for the one in PeerManagerImpl::SendMessages(CNode* pto)) were encapsulated in struct CNode::TxRelay.

Annotations that use negative capabilities were also added.

It doesn't seem trivial to bring the code in PeerManagerImpl::SendMessages(CNode* pto) into net.cpp, as it uses a significant amount of net_processing objects and references. I will try to find a way to do this.

@hebasto
Copy link
Member

hebasto commented May 16, 2022

@w0xlt

Still working on this?

@w0xlt
Copy link
Contributor Author

w0xlt commented Jun 24, 2022

It doesn't seem trivial to bring the code in PeerManagerImpl::SendMessages(CNode* pto) into net.cpp, as it uses a significant amount of net_processing objects and references. I will try to find a way to do this.

@hebasto I hadn't seen the message before. I will try to solve the issue described in the comment above.

@w0xlt w0xlt force-pushed the cs_tx_inventory_mutex branch from a3b7192 to 7a44768 Compare June 24, 2022 15:24
@w0xlt
Copy link
Contributor Author

w0xlt commented Jun 24, 2022

It doesn't seem trivial to bring the code in PeerManagerImpl::SendMessages(CNode* pto) into net.cpp, as it uses a significant amount of net_processing objects and references. I will try to find a way to do this.

@hebasto 88777c4 does the trick, apparently with no change in behavior..
However, I'm not sure it's an elegant solution due to the amount of parameters.
I'm pushing it anyway so that reviewers can eventually suggest some improvement.

This commit also makes it easier to change RecursiveMutex m_bloom_filter_mutex to Mutex

@achow101
Copy link
Member

Are you still working on this?

@hebasto
Copy link
Member

hebasto commented Oct 17, 2022

After 1066d10, the PR title and description should be updated, no?

@w0xlt w0xlt changed the title p2p: Replace RecursiveMutex cs_tx_inventory with Mutex and rename it p2p: Replace RecursiveMutex m_tx_inventory_mutex with Mutex and rename it Oct 17, 2022
@w0xlt
Copy link
Contributor Author

w0xlt commented Oct 17, 2022

Are you still working on this?

The PR is ready for reviews.

PR title and description should be updated

Done.

m_send_mempool = value;
}

void Relay(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't reviewed this pull request, but this would be the first time that a net processing logic is put in the peers struct, instead of the net processing implementation.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The idea is to prevent external synchronizing (acquiring m_tx_inventory_mutex from outside of the CNode::TxRelay instance).
Perhaps this requires a more complex refactoring.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dergoegge you might have thoughts here?

@DrahtBot
Copy link
Contributor

DrahtBot commented Dec 2, 2022

🐙 This pull request conflicts with the target branch and needs rebase.

@achow101 achow101 closed this Apr 25, 2023
@bitcoin bitcoin locked and limited conversation to collaborators Apr 24, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants