Split validationinterface into parallel validation/mempool interfaces#12979
Split validationinterface into parallel validation/mempool interfaces#12979TheBlueMatt wants to merge 9 commits intobitcoin:masterfrom
Conversation
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, |
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.
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.
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.
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.
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); |
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.
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.
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.
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.
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.
@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.
src/validationinterface.h
Outdated
There was a problem hiding this comment.
Should this be BlockDisconnected/BlockConnected?
src/validationinterface.h
Outdated
There was a problem hiding this comment.
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.
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.
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.
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.
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.
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.
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.
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).
src/validation.cpp
Outdated
There was a problem hiding this comment.
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.
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.
s/core and mempool/validation and mempool
src/validationinterface.h
Outdated
There was a problem hiding this comment.
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.
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.
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.
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.
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.
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.
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.
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.
@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.
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.
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.
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.
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.
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.