-
Notifications
You must be signed in to change notification settings - Fork 38.7k
Split validationinterface into parallel validation/mempool interfaces #12979
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
Conversation
maflcko
left a comment
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.
weak utACK db156ea (Just did a first pass and left some nits on comments)
src/validationinterface.h
Outdated
| * | ||
| * The ordering of BlockDisconnected and TransactionRemovedFromMempool | ||
| * (for transactions removed due to memory constraints or lock time/ | ||
| * coinbase maturity chenges during the disconnection/reorg) is undefined, |
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.
in commit 16e4c4b: Should say "changes"?
src/txmempool.cpp
Outdated
| setEntries stage; | ||
| stage.insert(it); | ||
| RemoveStaged(stage, true, MemPoolRemovalReason::BLOCK); | ||
| txn_removed_in_block.push_back(tx); // Use the block's copy |
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.
in commit ff936ae: I don't understand what this comment means. Mind to elaborate or remove?
Edit: I presume instead of "copy" you mean "witness", since the witness might be different and it is no longer a copy?
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 just added a "as per spec" note that its clarified more in MempoolUpdatedForBlockConnect docs ("The txn_removed_in_block txn are as they appear in the block, and may have different witnesses from the version which was previously in the mempool").
src/txmempool.cpp
Outdated
| // BLOCK and CONFLICT callbacks are generated in removeForBlock | ||
| if (reason != MemPoolRemovalReason::BLOCK && reason != MemPoolRemovalReason::CONFLICT && | ||
| reason != MemPoolRemovalReason::REPLACED) { | ||
| // BLOCK and CONFLICT callbacks are generated in removeForBlock REPLACED |
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.
nit in commit db156ea: Could add a semicolon, dot or new line to indicate the start of a new sentence. (before REPLACED)
|
|
||
| void CZMQNotificationInterface::TransactionAddedToMempool(const CTransactionRef& ptx, const std::vector<CTransactionRef>& txn_replaced) { | ||
| NotifyTransaction(ptx); | ||
| } |
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.
nit in commit 6b4e2a7: Could add a comment to explain why txn_replaced is dropped?
src/txmempool.cpp
Outdated
| ClearPrioritisation(tx->GetHash()); | ||
| } | ||
| GetMainSignals().MempoolUpdatedForBlockConnect(std::move(txn_removed_in_block), std::move(txn_conflicts)); | ||
| GetMainSignals().MempoolUpdatedForBlockConnect(std::move(txn_removed_in_block), std::move(txn_conflicts), nBlockHeight); |
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.
in commit db156ea: You pass the block height, but it doesn't appear to be used anywhere?
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.
Its used in #11775.
e530a76 to
e95db39
Compare
|
weak re-utACK e95db395592c763e8c77f619db7a8f5dbaa0a604 (Only changes were a rebase to solve conflicts and three minor comment clarifications) |
src/wallet/wallet.cpp
Outdated
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 you break this line?
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.
Please no trivial changes to pull request with a ton of commits. It is always a pain to re-ACK, since a diff has to be done for every commit.
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.
Will do when another round of changes comes in after more review, until then agree with Marco, not worth a rebase for it.
|
I'm confused, this removes the connect trace, but it's only refactoring? |
|
Still a big set of commits. It seems like it would have been simpler to do: and pass in Otherwise:
all look fine and straightforward (and easily reviewable/mergeable if they were all there was). I haven't verified that the comments in
are actually correct. For what it's worth, a commit that documented what the ordering ideally would be (after all these commits, eg) and noted any deviations from that as TODOs (which then get removed in subsequent commits as they're fixed) might be/have been better? That leaves these commits I'm not confident about:
EDIT: I'm not familiar with what |
|
@sipa yes. Because the validationinterface/mempoolinterface callbacks happen on a background thread, having an object which queues up callbacks for calling later is just redundant (as no other thread will be able to see an inconsistent state while we hold cs_main). The callbacks themselves should not change in that commit, it just removes a redundant queue. @ajtowns thanks for taking a look. I'm not actually sure what you mean in your suggestion with the patch there, can you clarify a bit further? |
ryanofsky
left a comment
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.
Started review (will update this comment with progress).
- e7d7edc898c31ef37d6d64e61c72979c32253a43 Clarify validationinterface notification ordering (1/10)
- 165d2f1594e56d523797494a20e0b2f7a768244e Add a parallel validation interface for mempool events. (2/10)
- 4305ee98f5c5e558c3b6f229890f0240b12ed5cc Pass MemPoolRemovalReason out through TransactionRemovedFromMempool (3/10)
- 2100e0dca5f786d85f0e74f7515a4797e6d5541a Split removeRecursive into calculate/remove steps (4/10)
- df6c6edc2e8758a6511ba8ee9f66469d9ccc9d95 Track the set of transactions removed/conflicted in removeForBlock (5/10)
- 07418e7d12de22402dfc805d201565f539a47b9b BlockConnected(vtxConflicted) -> New MempoolInterface callback (6/10)
- 9ca7be6992f9230a9a9dcd5f69f9695bf8b9d0b3 Remove boost::signals from txmempool, call GetMainSignals() directly (7/10)
- ab3f927538021a89e38cfa55a312bb8c0a0adb46 Remove useless scope in AcceptToMemoryPoolWorker (8/10)
- 3febd61adb8955d4eee5294ab5554f6613d591ff Add txn_replaced to Added callback, remove lReplaced ATMP arg (9/10)
- e95db395592c763e8c77f619db7a8f5dbaa0a604 Pass new block height through MempoolUpdatedForBlock (10/10)
src/validationinterface.h
Outdated
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.
In commit "Clarify validationinterface notification ordering" (e7d7edc898c31ef37d6d64e61c72979c32253a43)
I couldn't understand what "without forward-progress" means, and would appreciate any clarification. Does it just mean this can be called repeatedly with the same tip? (I asked about this previously in #11856 (comment))
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.
@ryanofsky "without forward-progress" means being at a worse-tip than we were previously at before (less work, or same work but a block that sorts worse than our prior tip, eg we received it second) -- exposing such an intermediate state to listeners is a bug. Note that this would be fixed by #13023.
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.
#13023 was merged so this TODO can be removed?
jnewbery
left a comment
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.
Looks good. I have a few requests to update/add comments.
I think the final commit (Pass new block height through MempoolUpdatedForBlock) should be left out of this PR and put in #11775)
src/validationinterface.h
Outdated
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.
Should this be BlockDisconnected/BlockConnected?
src/validationinterface.h
Outdated
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 don't understand why this is an edge case or needs to be removed? Seems reasonable to remove txs from the mempool without necessarily needing to add other txs.
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.
In fact, I think this comment is basically misleading. TransactionRemovedFromMempool no longer has a corresponding TransactionAddedToMempool event (TransactionAddedToMempool carries its own replaced txs).
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 comment was referencing transactions which were never actually added to mempool but which we tried to add and then removed in the mempool limit pass before we ever generated a TransactionAddedToMempool callback for. Will look into making it more clear.
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.
Ah, I understand now. Suggested wording:
Note that a transaction may fire a TransactionRemovedFromMempool notification without having previously fired a TransactionAddedToMempool notification (for example if it passes all AcceptToMemoryPool checks, but isn't added to the mempool due to a LimitMempoolSize() step). or similar
src/validationinterface.h
Outdated
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'm not sure if 'core' is the right word now that there are validation and mempool interfaces. Would 'chainstate' be better?
src/txmempool.cpp
Outdated
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 think this is still unclear (as per spec is not very meaningful for me). Suggested wording: Use the tx as it appears in the block. See comment for MempoolUpdatedForBlockConnect() for details. or similar
src/validation.cpp
Outdated
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.
Can you add an AssertLockHeld(cs_main); to this function? ConnectTip() is only called by ActivateBestChainStep(), which holds cs_main.
This would make it clearer that MempoolUpdatedForBlockConnect is called immediately before its BlockConnected (ie we can't have two BlockConnected calls racing each other).
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'll let @practicalswift get to that with EXCLUSIVE_LOCKS_REQUIRED additions. Would prefer to not add more AssertLockHelds that are just gonna get converted anyway (also, I think its assumed that everything in CChainState either takes, or requires, and certainly for private stuff requires, cs_main).
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.
@TheBlueMatt I'm standing ready! :-)
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.
sure, sounds good
src/validation.cpp
Outdated
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 can't comment on the exact line above (currently L2361), but can you update:
// Remove conflicting transactions from the mempool.;
to:
// Remove conflicting transactions from the mempool. This will fire the MempoolUpdatedForBlockConnect() notification.
or similar
src/validationinterface.h
Outdated
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.
micronit: this part of the comment was updated in the wrong commit (Remove boost::signals from txmempool, call GetMainSignals() directly instead of BlockConnected(vtxConflicted) -> New MempoolInterface callback)
Because many listeners (eg wallet) will want both types of events to be well-ordered, they are parallel and connected on the backend. However, they are exposed separately to clients to separate the logic (and because, hopefully, eventually, they can be exposed to external clients of Bitcoin Core via libconsensus or similar).
e95db39 to
9a48d33
Compare
This removes the whole ConnectTrace object, which may make it slightly harder to remove the unbounded-memory-during-reorg bug by throwing blocks out of memory and re-loading them from disk later. Comments are added to validationinterface to note where this should likely happen instead of ConnectTrace.
9a48d33 to
53b90df
Compare
| void RegisterMempoolInterface(MempoolInterface* listener); | ||
| /** Unregister a listener from mempool */ | ||
| void UnregisterMempoolInterface(MempoolInterface* listener); | ||
| /** Unregister all listeners from core and mempool */ |
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.
s/core and mempool/validation and mempool
src/validationinterface.h
Outdated
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.
Ah, I understand now. Suggested wording:
Note that a transaction may fire a TransactionRemovedFromMempool notification without having previously fired a TransactionAddedToMempool notification (for example if it passes all AcceptToMemoryPool checks, but isn't added to the mempool due to a LimitMempoolSize() step). or similar
jnewbery
left a comment
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.
one more comment on a comment
| * Note that in some rare cases (eg mempool limiting) a | ||
| * TransactionRemovedFromMempool event may fire with no corresponding | ||
| * TransactionAddedToMempool event for the same transaction. | ||
| * (TODO: remove this edge case) |
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.
Thanks for adding the documentation.
I think it'd be better to open an issue to track the TODOs, instead of putting them in the source code comments (this tends to get out of date very quickly).
|
|
||
| size_t DynamicMemoryUsage() const; | ||
|
|
||
| boost::signals2::signal<void (CTransactionRef)> NotifyEntryAdded; |
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.
What is the rationale to put mempool signals on something else than on the mempool itself (or an object contained in it)?
Naively I'd think it'd be a more appropriate place than on the global validation interface.
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.
CTxMemPool isn't really "the mempool" its more like "the data structure that stores mempool and has some of the logic from validation.cpp's mempool management stuff in it". While it'd be nice to clean that up, I don't think its as cut-and-dry as "belongs elsewhere". More directly, the reason I wanted to do this is it means one less place we have to include boost signals in an included-everywhere .h file, and I'd like to be moving towards not using it even inside the validationinterface for more paralellism as we need it, eventually.
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.
If CTxMempool isn't really "the mempool" that doesn't mean it shouldn't be. I think grouping the functionality for a certain concern together in one module makes sense.
E.g. when trying to understand the code, "Mempool" is definitely a more useful conceptual grouping than "all notification signals".
But I don't feel strongly enough about this to NACK this.
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 think it could make sense either way, with mempool signals living inside and coming from the mempool itself, or completely pulled out and coming from validation.cpp. However it does seem suboptimal to have the call-sites be split, with some coming from validation.cpp and others coming from the mempool.
Is it a design goal to eventually move all the call-sites to validation.cpp? If so then I think this intermediate state is fine for now, as this is still overall an improvement (and it's the existing logic that is already split between ATMP living in validation.cpp, but removeForBlock living in txmempool.cpp), but I would suggest that we add some comments indicating that these invocations of GetMainSignals() should be moved out eventually.
|
ACK 53b90df. Tested by running with |
sdaftuar
left a comment
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.
Looks pretty good. I think it'd be great to also see unit tests that verify the validation interface is living up to its promises, either in this PR or queued up in a follow-on PR.
src/validationinterface.h
Outdated
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.
@ryanofsky "without forward-progress" means being at a worse-tip than we were previously at before (less work, or same work but a block that sorts worse than our prior tip, eg we received it second) -- exposing such an intermediate state to listeners is a bug. Note that this would be fixed by #13023.
| setEntries stage; | ||
| stage.insert(it); | ||
| RemoveStaged(stage, true, MemPoolRemovalReason::BLOCK); | ||
| txn_removed_in_block.push_back(tx); // Use the block's copy as witness may be different |
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 think it would make sense to return both the version removed from the mempool and the version in the block, in the event that a malleated witness is mined.
My thinking is that listeners should either be expected to track everything they care about, in which case we should only return wtxid's to them when referencing something they've already seen, or they should not -- in which case we should give them full transactions everywhere. Right now we're giving full transactions for the added to mempool and removed from mempool cases, so I think we should also give the full transaction in this last edge case, for completeness.
|
|
||
| size_t DynamicMemoryUsage() const; | ||
|
|
||
| boost::signals2::signal<void (CTransactionRef)> NotifyEntryAdded; |
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 think it could make sense either way, with mempool signals living inside and coming from the mempool itself, or completely pulled out and coming from validation.cpp. However it does seem suboptimal to have the call-sites be split, with some coming from validation.cpp and others coming from the mempool.
Is it a design goal to eventually move all the call-sites to validation.cpp? If so then I think this intermediate state is fine for now, as this is still overall an improvement (and it's the existing logic that is already split between ATMP living in validation.cpp, but removeForBlock living in txmempool.cpp), but I would suggest that we add some comments indicating that these invocations of GetMainSignals() should be moved out eventually.
| } | ||
| } | ||
|
|
||
| for (const CTransactionRef& removedTx : lRemovedTxn) |
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 haven't though through the numbers required here to make this a practical DoS, but should we make sure to drain the callback queue before processing too many tx messages to prevent unbounded memory?
| * Called on a background thread. | ||
| */ | ||
| virtual void TransactionAddedToMempool(const CTransactionRef &ptxn) {} | ||
| virtual void TransactionAddedToMempool(const CTransactionRef &ptxn, const std::vector<CTransactionRef>& txn_replaced) {} |
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.
nit: would be nice to add a comment explaining what txn_replaced is.
| void CZMQNotificationInterface::BlockConnected(const std::shared_ptr<const CBlock>& pblock, const CBlockIndex* pindexConnected, const std::vector<CTransactionRef>& vtxConflicted) | ||
| void CZMQNotificationInterface::TransactionAddedToMempool(const CTransactionRef& ptx, const std::vector<CTransactionRef>& txn_replaced) { | ||
| NotifyTransaction(ptx); | ||
| } |
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.
Nit: could add a small comment in this method to explain why txn_replaced can safely be dropped?
|
Needs rebase. |
These are the non-feeestimator-specific refactoring commits from #11775, mostly focusing on splitting the validationinterface into two parlell interfaces.
There were no changes except reordering commits and rebasing on latest master.