Skip to content
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

locks: introduce mutex for tx download, flush rejection filters on UpdatedBlockTip #30111

Open
wants to merge 7 commits into
base: master
Choose a base branch
from

Conversation

glozow
Copy link
Member

@glozow glozow commented May 15, 2024

See #27463 for full project tracking.

This contains the first 4 commits of #30110, which require some thinking about thread safety in review.

  • Introduce a new m_tx_download_mutex which guards the transaction download data structures including m_txrequest, the rolling bloom filters, and m_orphanage. Later this should become the mutex guarding TxDownloadManager.
    • m_txrequest doesn't need to be guarded using cs_main anymore
    • m_recent_confirmed_transactions doesn't need its own lock anymore
    • m_orphanage doesn't need its own lock anymore
  • Adds a new ValidationInterface event, UpdatedBlockTipSync, which is a synchronous version of UpdatedBlockTip.
  • Flush m_recent_rejects and m_recent_rejects_reconsiderable on UpdatedBlockTipSync just once instead of every time AlreadyHaveTx is called. This should speed up calls to that function and removes the need to lock cs_main every time it is called.

@glozow glozow added the P2P label May 15, 2024
@DrahtBot
Copy link
Contributor

DrahtBot commented May 15, 2024

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

Code Coverage

For detailed information about the code coverage, see the test coverage report.

Reviews

See the guideline for information on the review process.

Type Reviewers
ACK instagibbs

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:

  • #30214 (refactor: Improve assumeutxo state representation by ryanofsky)
  • #30186 (fuzz: increase txorphan harness stability by marcofleon)
  • #30116 (p2p: Fill reconciliation sets (Erlay) attempt 2 by sr-gi)
  • #30110 (refactor: TxDownloadManager by glozow)
  • #29415 (Broadcast own transactions only via short-lived Tor or I2P connections by vasild)
  • #27052 (test: rpc: add last block announcement time to getpeerinfo result by LarryRuane)

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.

@glozow glozow mentioned this pull request May 15, 2024
56 tasks
src/net_processing.cpp Outdated Show resolved Hide resolved
}
} // release MempoolMutex
if (m_chainman.m_options.signals) {
m_chainman.m_options.signals->UpdatedBlockTipSync(pindexNewTip);
Copy link
Member

@hebasto hebasto May 15, 2024

Choose a reason for hiding this comment

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

This call invokes logging, i.e. I/O operations, while holding the ::cs_main and m_mempool->cs mutexes. It is considered as a suboptimal approach from the performance point of view. Could it be avoided?

Copy link
Member Author

Choose a reason for hiding this comment

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

I don't think it's true that m_mempool->cs is still held here (see line above)?

Copy link
Member

Choose a reason for hiding this comment

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

I don't think it's true that m_mempool->cs is still held here (see line above)?

You're right. I was confused by indentation. My apologies.

Copy link
Member Author

Choose a reason for hiding this comment

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

As for cs_main, it seems necessary for the chain tip to be what we think it is when the callback happens. IIUC the logging only happens in debug?

src/net_processing.cpp Outdated Show resolved Hide resolved
Copy link
Contributor

@ajtowns ajtowns left a comment

Choose a reason for hiding this comment

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

I'm not sure how to be confident that the UpdateBlockTipSync / hashRecentRejectsChainTip would be correct and didn't miss an edge case. Rest looks good to me.

src/net_processing.cpp Show resolved Hide resolved
src/net_processing.cpp Outdated Show resolved Hide resolved
@@ -183,6 +183,12 @@ void ValidationSignals::UpdatedBlockTip(const CBlockIndex *pindexNew, const CBlo
fInitialDownload);
}

void ValidationSignals::UpdatedBlockTipSync(const CBlockIndex *pindexNew)
Copy link
Contributor

Choose a reason for hiding this comment

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

Doesn't this also need to be called from InvalidateBlock(), potentially each time the tip is updated?

Copy link
Member

Choose a reason for hiding this comment

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

@glozow
Copy link
Member Author

glozow commented May 16, 2024

I'm not sure how to be confident that the UpdateBlockTipSync / hashRecentRejectsChainTip would be correct and didn't miss an edge case.

I (locally) split the commit into (1) update on validation interface callback and asserting that hashRecentRejectsChainTip is equal to the chain tip whenever we call AlreadyHaveTx + (2) removing hashRecentRejectsChainTip. I think if we see that everything runs fine with (1) it's probably correct.

I've (locally) hit a bug though, so will figure out what's wrong and then push.

@ajtowns
Copy link
Contributor

ajtowns commented May 16, 2024

I'm not sure how to be confident that the UpdateBlockTipSync / hashRecentRejectsChainTip would be correct and didn't miss an edge case.

I (locally) split the commit into (1) update on validation interface callback and asserting that hashRecentRejectsChainTip is equal to the chain tip whenever we call AlreadyHaveTx + (2) removing hashRecentRejectsChainTip. I think if we see that everything runs fine with (1) it's probably correct.

I've (locally) hit a bug though, so will figure out what's wrong and then push.

FWIW I tried something similar, and got an assertion failure in one of the mempool functional tests (maybe mempool_reorg, and thus due to an invalidateblock call?), but it wasn't reliable, it only occurred when I ran many tests in parallel, not when I reran that test on its own. The bug will only trigger if the tip is updated via some other path and AlreadyHave is called before the tip is further updated via the expected path, so (1) doesn't seem like a terribly reliable check to me.

@glozow
Copy link
Member Author

glozow commented May 16, 2024

FWIW I tried something similar, and got an assertion failure in one of the mempool functional tests (maybe mempool_reorg, and thus due to an invalidateblock call?)

Was it mempool_packages.py maybe? Mine tripped there on invalidateblock when I was adding UpdatedBlockTip to InvalidateBlock, and it was a lock ordering problem. My hierarchy is cs_main -> tx_download_mutex -> mempool.cs.

Hm. The alternative is to hold cs_main and tell txdownloadman the current chain tip pretty much all the time...

@ajtowns
Copy link
Contributor

ajtowns commented May 16, 2024

FWIW I tried something similar, and got an assertion failure in one of the mempool functional tests (maybe mempool_reorg, and thus due to an invalidateblock call?)

Was it mempool_packages.py maybe? Mine tripped there on invalidateblock when I was adding UpdatedBlockTip to InvalidateBlock, and it was a lock ordering problem. My hierarchy is cs_main -> tx_download_mutex -> mempool.cs.

Ah, yes, I think it was.

@instagibbs
Copy link
Member

thanks for splitting this up, next on my review docket

@glozow
Copy link
Member Author

glozow commented May 20, 2024

Rebased for #29817

Copy link
Member

@instagibbs instagibbs left a comment

Choose a reason for hiding this comment

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

looking pretty straightforward but I'm not an expert in the current locking setup

will give another pass in a bit

src/net_processing.cpp Outdated Show resolved Hide resolved
@@ -183,6 +183,12 @@ void ValidationSignals::UpdatedBlockTip(const CBlockIndex *pindexNew, const CBlo
fInitialDownload);
}

void ValidationSignals::UpdatedBlockTipSync(const CBlockIndex *pindexNew)
Copy link
Member

Choose a reason for hiding this comment

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

@@ -493,9 +493,9 @@ class PeerManagerImpl final : public PeerManager

/** Overridden from CValidationInterface. */
void BlockConnected(ChainstateRole role, const std::shared_ptr<const CBlock>& pblock, const CBlockIndex* pindexConnected) override
EXCLUSIVE_LOCKS_REQUIRED(!m_recent_confirmed_transactions_mutex);
EXCLUSIVE_LOCKS_REQUIRED(!m_recent_confirmed_transactions_mutex, !m_tx_download_mutex);
Copy link
Member

Choose a reason for hiding this comment

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

not a locking expert but would LOCKS_EXCLUDED be easier to read?

Copy link
Member Author

Choose a reason for hiding this comment

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

Oh, I didn't know you could do both. Happy to change if people want, but generally prefer to follow convention of the existing annotations.

Copy link
Contributor

@ajtowns ajtowns May 23, 2024

Choose a reason for hiding this comment

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

EXCLUSIVE_LOCKS_REQUIRED(!foo) is a stronger assertion; it says that if the caller can see the mutex, it also has to have the same assertion. LOCKS_EXCLUDED(foo) just says you can't LOCK() or have EXCLUSIVE_LOCKS_REQUIRED(foo) -- so there's nothing preventing the caller's caller from having taken the lock.

Copy link
Member Author

Choose a reason for hiding this comment

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

Ah that makes sense. I will leave this as is.

Copy link
Member

Choose a reason for hiding this comment

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

ok, makes sense why I only see it used in cs_main contexts, since it's a global mutex. thanks

src/test/orphanage_tests.cpp Show resolved Hide resolved
src/txorphanage.h Outdated Show resolved Hide resolved
We need to synchronize between various tx download structures.
TxRequest does not inherently need cs_main for synchronization, and it's
not appropriate to lock all of the tx download logic under cs_main.
This is a synchronous version of UpdatedBlockTip.

It allows clients to respond to a new block immediately after it is
connected. The synchronicity is important for things like
m_recent_rejects, in which a transaction's validity can change (rejected
vs accepted) when this event is processed (e.g. it has a timelock
condition that has just been met). This is distinct from something like
m_recent_confirmed_transactions in which the validation outcome is the
same (valid vs already-have).
Resetting m_recent_rejects once per block is more efficient than
comparing hashRecentRejectsChainTip with the chain tip every time we
call AlreadyHaveTx. We keep hashRecentRejectsChainTip for now to assert
that updates happen correctly; it is removed in the next commit.
This also means AlreadyHaveTx no longer needs cs_main held.
The TxOrphanage is now guarded externally by m_tx_download_mutex.
@instagibbs
Copy link
Member

suggested changes were done, reviewed via git range-diff master 7c3fb97 ef8de26

Copy link
Member

@instagibbs instagibbs left a comment

Choose a reason for hiding this comment

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

utACK ef8de26

@@ -779,7 +779,10 @@ class PeerManagerImpl final : public PeerManager
BanMan* const m_banman;
ChainstateManager& m_chainman;
CTxMemPool& m_mempool;
TxRequestTracker m_txrequest GUARDED_BY(::cs_main);

/** Protects tx download including TxRequestTracker, rejection filters, and TxOrphanage. */
Copy link
Member

Choose a reason for hiding this comment

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

non-blocking nit: commit message still referencing TxRequest

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants