-
Notifications
You must be signed in to change notification settings - Fork 37.7k
p2p: avoid traversing blocks (twice) during IBD #32730
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
p2p: avoid traversing blocks (twice) during IBD #32730
Conversation
|
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers. Code Coverage & BenchmarksFor details see: https://corecheck.dev/bitcoin/bitcoin/pulls/32730. ReviewsSee the guideline for information on the review process. ConflictsReviewers, this pull request conflicts with the following ones:
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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Only had time for adding a few questions and quickly checking the expected effect this will have on IBD.
A flame graph I did this week for master, showing a -reindex until 900k blocks shows this part of the code to be 0.07% of IBD (~20 seconds). It can still make sense to do it, we should avoid doing unnecessary work, just setting the expectations straight.
Left a few notes, the guard makes sense to me, but the part I passionately disagree with is the boolean argument train - is there a simpler way to avoid that either via a different guard or the locally available m_initial_sync_finished (we've been locking before anyway).
| @@ -246,6 +246,9 @@ bool TxOrphanage::HaveTxToReconsider(NodeId peer) | |||
|
|
|||
| void TxOrphanage::EraseForBlock(const CBlock& block) | |||
| { | |||
| // No orphans, nothing to do | |||
| if (m_orphans.empty()) return; | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This makes sense on its own I think - it's similar to another draft PR I had (bitcoin-dev-tools#167). If you think it's related and adds anything, feel free to cherry-pick it here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This makes sense on its own I think - it's similar to another draft PR I had (bitcoin-dev-tools#167). If you think it's related and adds anything, feel free to cherry-pick it here.
That's cool, will check it in the coming days.
First quick glance it seems that if the mempool is empty; we could avoid calling removeForBlock entirely. Which will save us from adding another callback into the validation queue too, which will leave space for processing another block during ibd.
Yet, that was a fast check. Should check who is using the MempoolTransactionsRemovedForBlock event.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To simplify review, could you split it into several commits - one changing the m_initial_sync(_finished) implementation, one adding it next to role == ChainstateRole::BACKGROUND and one with this change - explaining the context (which are a bit muffled for me currently) in each commit message?
I'm also curious if it's possible to get back to being in IBD after we're finished (e.g. after being offline for weeks, i.e. if (chain.Tip()->Time() < Now<NodeSeconds>() - m_options.max_tip_age) {).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Opened it separately in #32827, please resolve this comment
| @@ -246,6 +246,9 @@ bool TxOrphanage::HaveTxToReconsider(NodeId peer) | |||
|
|
|||
| void TxOrphanage::EraseForBlock(const CBlock& block) | |||
| { | |||
| // No orphans, nothing to do | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I find the comment redundant, it states exactly what the code already states clearly, doesn't add any new info
| @@ -1994,7 +1994,7 @@ void PeerManagerImpl::BlockConnected( | |||
|
|
|||
| // The following task can be skipped since we don't maintain a mempool for | |||
| // the ibd/background chainstate. | |||
| if (role == ChainstateRole::BACKGROUND) { | |||
| if (is_ibd || role == ChainstateRole::BACKGROUND) { | |||
| return; | |||
| } | |||
| LOCK(m_tx_download_mutex); | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can safely skip this because we're never requesting single transactions during IBD (i.e. AlreadyHaveTx isn't needed during IBD)?
for (const auto& ptx : pblock->vtx) {
RecentConfirmedTransactionsFilter().insert(ptx->GetHash().ToUint256());
if (ptx->HasWitness()) {
RecentConfirmedTransactionsFilter().insert(ptx->GetWitnessHash().ToUint256());
}
m_txrequest.ForgetTxHash(ptx->GetHash());
m_txrequest.ForgetTxHash(ptx->GetWitnessHash());
}Can we guard this by some other criteria instead of the IBD boolean, like we're doing with EraseForBlock, e.g.
if (!m_txrequest.Size()) return;There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can safely skip this because we're never requesting single transactions during IBD (i.e.
AlreadyHaveTxisn't needed during IBD)?for (const auto& ptx : pblock->vtx) { RecentConfirmedTransactionsFilter().insert(ptx->GetHash().ToUint256()); if (ptx->HasWitness()) { RecentConfirmedTransactionsFilter().insert(ptx->GetWitnessHash().ToUint256()); } m_txrequest.ForgetTxHash(ptx->GetHash()); m_txrequest.ForgetTxHash(ptx->GetWitnessHash()); }Can we guard this by some other criteria instead of the IBD boolean, like we're doing with
EraseForBlock, e.g.if (!m_txrequest.Size()) return;
I thought about it but didn't do it on purpose. The reasoning is that we still need to add the txs into the recently confirmed filters post-IBD even when we don't have any tx requests. In other words, we still need to traverse the block. Could at most skip the last two lines with the emptiness check.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could at most skip the last two lines with the emptiness check
Not sure that helps
src/net_processing.cpp
Outdated
| // The following task can be skipped since we don't maintain a mempool for | ||
| // the ibd/background chainstate. | ||
| if (role == ChainstateRole::BACKGROUND) { | ||
| if (is_ibd || role == ChainstateRole::BACKGROUND) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is the comment (already mentioning IBD) still accurate?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is the comment (already mentioning IBD) still accurate?
The comment was accurate, but the code didn’t behave as described. Now it does.
src/net_processing.cpp
Outdated
| @@ -1975,7 +1975,8 @@ void PeerManagerImpl::ActiveTipChange(const CBlockIndex& new_tip, bool is_ibd) | |||
| void PeerManagerImpl::BlockConnected( | |||
| ChainstateRole role, | |||
| const std::shared_ptr<const CBlock>& pblock, | |||
| const CBlockIndex* pindex) | |||
| const CBlockIndex* pindex, | |||
| bool is_ibd) | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
there should be a better way to do this than passing around a boolean over so many layers. I'll review it properly this week.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
there should be a better way to do this than passing around a boolean over so many layers. I'll review it properly this week.
Did you check the second approach proposed in the PR description?
| @@ -246,6 +246,9 @@ bool TxOrphanage::HaveTxToReconsider(NodeId peer) | |||
|
|
|||
| void TxOrphanage::EraseForBlock(const CBlock& block) | |||
| { | |||
| // No orphans, nothing to do | |||
| if (m_orphans.empty()) return; | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is a common-sense change by itself 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
tiny note: this also skip post-IBD blocks when we don't have any orphans.
--Credits to @mzumsande for pointing it out too--
We could do the same for the tx requests tracker loop but.. that wouldn't change much as we still need to traverse the block for the "recently confirmed" filter anyway.
src/net_processing.cpp
Outdated
| @@ -1993,7 +1994,7 @@ void PeerManagerImpl::BlockConnected( | |||
|
|
|||
| // The following task can be skipped since we don't maintain a mempool for | |||
| // the ibd/background chainstate. | |||
| if (role == ChainstateRole::BACKGROUND) { | |||
| if (is_ibd || role == ChainstateRole::BACKGROUND) { | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't checking role == ChainstateRole::BACKGROUND || m_chainman.IsInitialBlockDownload() be good enough? I realize it is racy, the condition may already be false even when the last few ibd blocks are still being processed, but I don't think that really matters here. We also use it similarly in the BlockChecked method.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's why I asked for !m_initial_sync_finished as well.
If the race is biased towards reenabling it too early (rather than later), I agree that it should be fine.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
furszy reminded me that BlockChecked is run synchronously, so I don't think that comparison makes sense after all. I guess this comment can be resolved.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't checking
role == ChainstateRole::BACKGROUND || m_chainman.IsInitialBlockDownload()be good enough? I realize it is racy, the condition may already be false even when the last few ibd blocks are still being processed, but I don't think that really matters here. We also use it similarly in theBlockCheckedmethod.
The diff between BlockConnected() and BlockChecked() is that the former one runs async while the latter runs synchronously. So the idea was to avoid locking cs_main in a different thread which delays other parts of the node.
I also thought about using the 'm_initial_sync_finished' bool for this, but that's not good because the field is calculated at a different time interval in the stale tip check thread (we could compute it more often but that's a different story).
The last idea that had was the enabled flag inside the TxDownloadManager (check the PR description, you might like this one more).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The last idea that had was the enabled flag inside the TxDownloadManager (check the PR description, you might like this one more).
I saw that but not yet sure until I see it in action. I definitely dislike the current arg passing, so I'd vote for that if we really need this param
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I saw that but not yet sure until I see it in action. I definitely dislike the current arg passing, so I'd vote for that if we really need this param
Ok, I'm a ~0 on this. I think this field is special enough (reason below) to be signaled as a separate arg or it could be wrapped into a struct — similar to how it's done in the interfaces::Chain::Notifications class. With this, I'm not saying to not try the TxDownloadManager enabled flag approach, I just think that the current approach is slightly more useful for future listeners usages.
The only thing I'm somewhat convinced of is that, just like listeners are informed about blocks being downloaded through AssumeUTXO background chain validation, it would be nice if they are aware of IBD blocks — otherwise, sooner or later they will end up re-implementing the same racy/locking isIBD() logic across multiple layers (which could be fine for some cases but it doesn't seem to be generally good).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it would be nice if they are aware of IBD blocks — otherwise [...] they will end up re-implementing
Is there a more reusable way we could implement this?
|
One thing that still irks me a bit is that we are communicating more |
Context: Every time a block is connected, a 'BlockConnected' event is appended to the validation interface queue. This queue is consumed sequentially by a single worker thread. To avoid excessive memory usage, the queue is limited to 10 events at a time, and we stop processing new blocks until the queued events are handled. Issue: Within the 'PeerManager::BlockConnected()' listener, we traverse the block twice inside the transaction download manager — despite not needing to handle orphans or transaction requests during IBD. This redundant work adds unnecessary delay to the initial block download process.
e48bfeb to
8b9d396
Compare
Hmm yeah, we would need to decouple the block download logic from the chain state class first. This reminds me to #27837, which is a small step in that direction.
Check it now. Ended up reimplementing it differently. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would be good to first add proper tests for the touched code in another pull, before refactoring the code for a 0.07% speedup. (The refactor broke the code, as I see it)
| if (!m_initial_sync_finished && CanDirectFetch()) { | ||
| m_connman.StartExtraBlockRelayPeers(); | ||
| m_initial_sync_finished = true; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
m_initial_sync_finished will normally be set to true, so this is dead code now. Surprising that no tests failed ...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like this new patch a lot more, setting IBD is a lot cleaner this way.
Somebody with more experience should validate whether that's correct or not.
My main suggestion is splitting to commits where the messages put the 3 distinct changes in context - and maybe to invert the naming of a the new field which is always negated at use site anyway.
Patch
diff --git a/src/init.cpp b/src/init.cpp
index 88fe531119..80b2fc7d6a 100644
--- a/src/init.cpp
+++ b/src/init.cpp
@@ -1909,7 +1909,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
// ********************************************************* Step 12: start node
int64_t best_block_time{};
- bool initial_sync_finished{false};
+ bool initial_sync{true};
{
LOCK(chainman.GetMutex());
const auto& tip{*Assert(chainman.ActiveTip())};
@@ -1925,10 +1925,10 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
tip_info->header_height = chainman.m_best_header->nHeight;
tip_info->header_time = chainman.m_best_header->GetBlockTime();
}
- initial_sync_finished = !chainman.IsInitialBlockDownload();
+ initial_sync = chainman.IsInitialBlockDownload();
}
LogPrintf("nBestHeight = %d\n", chain_active_height);
- if (node.peerman) node.peerman->SetBestBlock(chain_active_height, std::chrono::seconds{best_block_time}, initial_sync_finished);
+ if (node.peerman) node.peerman->SetBestBlock(chain_active_height, best_block_time, initial_sync);
// Map ports with NAT-PMP
StartMapPort(args.GetBoolArg("-natpmp", DEFAULT_NATPMP));
diff --git a/src/net_processing.cpp b/src/net_processing.cpp
index 97e3e05d4f..89229d6cae 100644
--- a/src/net_processing.cpp
+++ b/src/net_processing.cpp
@@ -511,7 +511,7 @@ public:
EXCLUSIVE_LOCKS_REQUIRED(!m_tx_download_mutex);
void BlockDisconnected(const std::shared_ptr<const CBlock> &block, const CBlockIndex* pindex) override
EXCLUSIVE_LOCKS_REQUIRED(!m_tx_download_mutex);
- void UpdatedBlockTip(const CBlockIndex *pindexNew, const CBlockIndex *pindexFork, bool fInitialDownload) override
+ void UpdatedBlockTip(const CBlockIndex *pindexNew, const CBlockIndex *pindexFork, bool initial_sync) override
EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex);
void BlockChecked(const CBlock& block, const BlockValidationState& state) override
EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex);
@@ -537,11 +537,11 @@ public:
PeerManagerInfo GetInfo() const override EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex);
void SendPings() override EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex);
void RelayTransaction(const uint256& txid, const uint256& wtxid) override EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex);
- void SetBestBlock(int height, std::chrono::seconds time, bool initial_sync_finished) override
+ void SetBestBlock(int height, uint64_t block_time, bool initial_sync) override
{
m_best_height = height;
- m_best_block_time = time;
- m_initial_sync_finished = initial_sync_finished;
+ m_best_block_time = std::chrono::seconds{block_time};
+ m_initial_sync = initial_sync;
};
void UnitTestMisbehaving(NodeId peer_id) override EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex) { Misbehaving(*Assert(GetPeerRef(peer_id)), ""); };
void ProcessMessage(CNode& pfrom, const std::string& msg_type, DataStream& vRecv,
@@ -789,9 +789,9 @@ private:
bool RejectIncomingTxs(const CNode& peer) const;
- /** Whether we've completed initial sync yet, for determining when to turn
+ /** Whether we're still in initial sync, for determining when to turn
* on extra block-relay-only peers. */
- std::atomic<bool> m_initial_sync_finished{false};
+ std::atomic<bool> m_initial_sync{true};
/** Protects m_peer_map. This mutex must not be locked while holding a lock
* on any of the mutexes inside a Peer object. */
@@ -1994,7 +1994,7 @@ void PeerManagerImpl::BlockConnected(
// The following task can be skipped since we don't maintain a mempool for
// the ibd/background chainstate.
- if (!m_initial_sync_finished || role == ChainstateRole::BACKGROUND) {
+ if (m_initial_sync || role == ChainstateRole::BACKGROUND) {
return;
}
LOCK(m_tx_download_mutex);
@@ -2066,12 +2066,12 @@ void PeerManagerImpl::NewPoWValidBlock(const CBlockIndex *pindex, const std::sha
* Update our best height and announce any block hashes which weren't previously
* in m_chainman.ActiveChain() to our peers.
*/
-void PeerManagerImpl::UpdatedBlockTip(const CBlockIndex *pindexNew, const CBlockIndex *pindexFork, bool fInitialDownload)
+void PeerManagerImpl::UpdatedBlockTip(const CBlockIndex *pindexNew, const CBlockIndex *pindexFork, bool initial_sync)
{
- SetBestBlock(pindexNew->nHeight, std::chrono::seconds{pindexNew->GetBlockTime()}, !fInitialDownload);
+ SetBestBlock(pindexNew->nHeight, pindexNew->GetBlockTime(), initial_sync);
// Don't relay inventory during initial block download.
- if (fInitialDownload) return;
+ if (initial_sync) return;
// Find the hashes of all blocks that weren't previously in the best chain.
std::vector<uint256> vHashes;
@@ -5266,8 +5266,9 @@ void PeerManagerImpl::CheckForStaleTipAndEvictPeers()
m_stale_tip_check_time = now + STALE_CHECK_INTERVAL;
}
- if (!m_initial_sync_finished && CanDirectFetch()) {
+ if (m_initial_sync && CanDirectFetch()) {
m_connman.StartExtraBlockRelayPeers();
+ // TODO? m_initial_sync = false;
}
}
diff --git a/src/net_processing.h b/src/net_processing.h
index 5a5980a9da..d7ea6a12a3 100644
--- a/src/net_processing.h
+++ b/src/net_processing.h
@@ -124,8 +124,8 @@ public:
/** Send ping message to all peers */
virtual void SendPings() = 0;
- /** Set the height of the best block and its time (seconds since epoch), and indicate whether historical block sync has completed. */
- virtual void SetBestBlock(int height, std::chrono::seconds time, bool initial_sync_finished) = 0;
+ /** Set the height of the best block, its block time, and whether block sync has completed. */
+ virtual void SetBestBlock(int height, uint64_t block_time, bool initial_sync) = 0;
/* Public for unit testing. */
virtual void UnitTestMisbehaving(NodeId peer_id) = 0;
diff --git a/src/test/peerman_tests.cpp b/src/test/peerman_tests.cpp
index 30d680ffde..0bedae7859 100644
--- a/src/test/peerman_tests.cpp
+++ b/src/test/peerman_tests.cpp
@@ -41,8 +41,7 @@ BOOST_AUTO_TEST_CASE(connections_desirable_service_flags)
// Make peerman aware of the initial best block and verify we accept limited peers when we start close to the tip time.
auto tip = WITH_LOCK(::cs_main, return m_node.chainman->ActiveChain().Tip());
uint64_t tip_block_time = tip->GetBlockTime();
- int tip_block_height = tip->nHeight;
- peerman->SetBestBlock(tip_block_height, std::chrono::seconds{tip_block_time}, /*initial_sync_finished=*/true);
+ peerman->SetBestBlock(tip->nHeight, tip_block_time, /*initial_sync=*/false);
SetMockTime(tip_block_time + 1); // Set node time to tip time
BOOST_CHECK(peerman->GetDesirableServiceFlags(peer_flags) == ServiceFlags(NODE_NETWORK_LIMITED | NODE_WITNESS));
diff --git a/src/txorphanage.cpp b/src/txorphanage.cpp
index ca47bf949f..80d25df047 100644
--- a/src/txorphanage.cpp
+++ b/src/txorphanage.cpp
@@ -246,7 +246,6 @@ bool TxOrphanage::HaveTxToReconsider(NodeId peer)
void TxOrphanage::EraseForBlock(const CBlock& block)
{
- // No orphans, nothing to do
if (m_orphans.empty()) return;
std::vector<Wtxid> vOrphanErase;| @@ -790,7 +791,7 @@ class PeerManagerImpl final : public PeerManager | |||
|
|
|||
| /** Whether we've completed initial sync yet, for determining when to turn | |||
| * on extra block-relay-only peers. */ | |||
| bool m_initial_sync_finished GUARDED_BY(cs_main){false}; | |||
| std::atomic<bool> m_initial_sync_finished{false}; | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 for using an atomic here!
Note: the usages are always inverted now, can we rather have std::atomic<bool> m_initial_sync{true} instead?
| @@ -246,6 +246,9 @@ bool TxOrphanage::HaveTxToReconsider(NodeId peer) | |||
|
|
|||
| void TxOrphanage::EraseForBlock(const CBlock& block) | |||
| { | |||
| // No orphans, nothing to do | |||
| if (m_orphans.empty()) return; | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To simplify review, could you split it into several commits - one changing the m_initial_sync(_finished) implementation, one adding it next to role == ChainstateRole::BACKGROUND and one with this change - explaining the context (which are a bit muffled for me currently) in each commit message?
I'm also curious if it's possible to get back to being in IBD after we're finished (e.g. after being offline for weeks, i.e. if (chain.Tip()->Time() < Now<NodeSeconds>() - m_options.max_tip_age) {).
| @@ -5267,7 +5268,6 @@ void PeerManagerImpl::CheckForStaleTipAndEvictPeers() | |||
|
|
|||
| if (!m_initial_sync_finished && CanDirectFetch()) { | |||
| m_connman.StartExtraBlockRelayPeers(); | |||
| m_initial_sync_finished = true; | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
does this introduce any behavioral change? (i.e. m_initial_sync_finished being set to true at a different lifecycle)
| } | ||
| LogPrintf("nBestHeight = %d\n", chain_active_height); | ||
| if (node.peerman) node.peerman->SetBestBlock(chain_active_height, std::chrono::seconds{best_block_time}); | ||
| if (node.peerman) node.peerman->SetBestBlock(chain_active_height, std::chrono::seconds{best_block_time}, initial_sync_finished); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Q: is the guard only meant for tests or can this happen in prod as well? Given that we only need initial_sync_finished when this is set, was wondering if it would make sense to guard the initial_sync_finished = !chainman.IsInitialBlockDownload() as well - but not sure if that makes it clearer or more cumbersome. Just documenting my questions, no action needed.
| @@ -124,8 +124,8 @@ class PeerManager : public CValidationInterface, public NetEventsInterface | |||
| /** Send ping message to all peers */ | |||
| virtual void SendPings() = 0; | |||
|
|
|||
| /** Set the height of the best block and its time (seconds since epoch). */ | |||
| virtual void SetBestBlock(int height, std::chrono::seconds time) = 0; | |||
| /** Set the height of the best block and its time (seconds since epoch), and indicate whether historical block sync has completed. */ | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
indicatedoesn't hint that we're actually setting it as wellhistoricalmight not be accurate given that we can re-enter IBD mode I think
| @@ -1994,7 +1994,7 @@ void PeerManagerImpl::BlockConnected( | |||
|
|
|||
| // The following task can be skipped since we don't maintain a mempool for | |||
| // the ibd/background chainstate. | |||
| if (role == ChainstateRole::BACKGROUND) { | |||
| if (is_ibd || role == ChainstateRole::BACKGROUND) { | |||
| return; | |||
| } | |||
| LOCK(m_tx_download_mutex); | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could at most skip the last two lines with the emptiness check
Not sure that helps
| @@ -537,10 +537,11 @@ class PeerManagerImpl final : public PeerManager | |||
| PeerManagerInfo GetInfo() const override EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex); | |||
| void SendPings() override EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex); | |||
| void RelayTransaction(const uint256& txid, const uint256& wtxid) override EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex); | |||
| void SetBestBlock(int height, std::chrono::seconds time) override | |||
| void SetBestBlock(int height, std::chrono::seconds time, bool initial_sync_finished) override | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given that we're always calling this with tip->GetBlockTime() and converting it to seconds on the call sites, we might simplify it to accept uint64_t block_time and convert to seconds inside instead. Not sure it's better, just noticed a pattern...
yeah.. This reminded me why I didn’t use the |
|
🐙 This pull request conflicts with the target branch and needs rebase. |
249889b orphanage: avoid vtx iteration when no orphans (furszy) 41ad2be mempool: Avoid expensive loop in `removeForBlock` during IBD (Lőrinc) Pull request description: During Initial Block Download, the mempool is usually empty, but `CTxMemPool::removeForBlock` is still called for every connected block where we: * iterate over every transaction in the block even though none will be found in the empty `mapTx`, always leaving `txs_removed_for_block` empty... * which is pre-allocated regardless with `40 bytes * vtx.size()`, even though it will always remain empty. Similarly to #32730 (comment), this change introduces a minor performance & memory optimization by only executing the loop if any of the affected mempool maps have any contents. The second commit is cherry-picked from there since it's related to this change as well. ACKs for top commit: optout21: ACK 249889b glozow: ACK 249889b ismaelsadeeq: reACK 249889b Tree-SHA512: 80d06ff1515164529cdc3ad21db3041bb5b2a1d4b72ba9e6884cdf40c5f1477fee7479944b8bca32a6f0bf27c4e5501fccd085f6041a2dbb101438629cfb9e4b
Context:
Every time a block is connected, a
BlockConnected()event is appended to the validation interface queue. This queue is consumed sequentially by a single worker thread. To avoid excessive memory usage, the queue is limited to 10 events at a time, and we stop processing new blocks until the queued events are handled.Issue:
Within the
PeerManager::BlockConnected()listener, we traverse the block twice inside the transaction download manager — despite not needing to handle orphans or transaction requests during IBD.Extra info about the changes:
The new arg added to
BlockConnected()in the first commit is primarily meant to avoid lockingcs_mainon thelistener side (to avoid calling
IsInitialBlockDownload()there).Another way of implementing this could be to add a bool field
enabledto theTxDownloadManagerclass that is onlyset to true when we are out of IBD, so that we don't have to include a new arg inside the block connection event.
This is open for discussion. Could try implementing this second approach too.