Skip to content

Conversation

@TheBlueMatt
Copy link
Contributor

@TheBlueMatt TheBlueMatt commented Apr 13, 2018

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.

Copy link
Member

@maflcko maflcko left a 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)

*
* 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,
Copy link
Member

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"?

setEntries stage;
stage.insert(it);
RemoveStaged(stage, true, MemPoolRemovalReason::BLOCK);
txn_removed_in_block.push_back(tx); // Use the block's copy
Copy link
Member

@maflcko maflcko Apr 16, 2018

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?

Copy link
Contributor Author

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").

// 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
Copy link
Member

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);
}
Copy link
Member

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?

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);
Copy link
Member

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?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Its used in #11775.

@TheBlueMatt TheBlueMatt force-pushed the 2018-04-mempoolinterface branch 2 times, most recently from e530a76 to e95db39 Compare April 17, 2018 15:26
@maflcko
Copy link
Member

maflcko commented Apr 17, 2018

weak re-utACK e95db395592c763e8c77f619db7a8f5dbaa0a604 (Only changes were a rebase to solve conflicts and three minor comment clarifications)

Copy link
Contributor

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?

Copy link
Member

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.

Copy link
Contributor Author

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.

@sipa
Copy link
Member

sipa commented Apr 26, 2018

I'm confused, this removes the connect trace, but it's only refactoring?

@ajtowns
Copy link
Contributor

ajtowns commented Apr 26, 2018

Still a big set of commits.

It seems like it would have been simpler to do:

+void CTxMemPool::removeRecursive(const CTransaction &origTx, MemPoolRemovalReason reason, std::vector<CTransactionRef>* txn_removed) {
     // Remove transaction from memory pool
     LOCK(cs);
     setEntries setAllRemoves;
     calculateRemoveRecursive(origTx, setAllRemoves);
+    if (txn_removed != nullptr) {
+        for (const txiter& it : setAllRemoves) {
+            txn_removed->push_back(it->GetSharedTx());
+        }
+    }
     RemoveStaged(setAllRemoves, false, reason);
 }

and pass in &txn_removed rather than duplicate the code. That would also avoid the need to split out calculateRemoveRecursive() from what I can see, though maybe another PR needs it? I'm not sure that UpdateMempoolForReorg() shouldn't also be tracking the conflicting txs it removes and issuing a notification anyway?

Otherwise:

  • Pass new block height through MempoolUpdatedForBlock
  • Remove useless scope in AcceptToMemoryPoolWorker
  • Remove boost::signals from txmempool, call GetMainSignals() directly
  • Track the set of transactions removed/conflicted in removeForBlock
  • Split removeRecursive into calculate/remove steps
  • Pass MemPoolRemovalReason out through TransactionRemovedFromMempool

all look fine and straightforward (and easily reviewable/mergeable if they were all there was).

I haven't verified that the comments in

  • Clarify validationinterface notification ordering

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:

  • Add txn_replaced to Added callback, remove lReplaced ATMP arg
  • BlockConnected(vtxConflicted) -> New MempoolInterface callback
  • Add a parallel validation interface for mempool events.

EDIT: In particular, there's a couple of changes in the "Add txn_replaced" commit that deal with (un)registering MempoolInterface that seem like they should be in an earlier commit, possibly the "parallel validation interface for mempool events" one?

I'm not familiar with what ConnectTrace does or the impact of changing that area of code to be able to be confident in the BlockConnected... commit. Having one commit that just adds MempoolUpdatedForBlockConnect followed by one that removes ConnectTrace might be simpler to review? The MempoolUpdateForBlockConnect parts look good at least. Seems like the switch from BasicTestingSetup to TestingSetup for policyestimator_tests.cpp should be in one of the commits in "Move fee estimator thread" PR?

@TheBlueMatt
Copy link
Contributor Author

@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?

Copy link
Contributor

@ryanofsky ryanofsky left a 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)

Copy link
Contributor

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))

Copy link
Member

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.

Copy link
Member

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?

Copy link
Contributor

@jnewbery jnewbery left a 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)

Copy link
Contributor

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?

Copy link
Contributor

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.

Copy link
Contributor

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).

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 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.

Copy link
Contributor

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

Copy link
Contributor

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?

Copy link
Contributor

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

Copy link
Contributor

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).

Copy link
Contributor Author

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).

Copy link
Contributor

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! :-)

Copy link
Contributor

Choose a reason for hiding this comment

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

sure, sounds good

Copy link
Contributor

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

Copy link
Contributor

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).
@TheBlueMatt TheBlueMatt force-pushed the 2018-04-mempoolinterface branch from e95db39 to 9a48d33 Compare May 2, 2018 18:09
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.
@TheBlueMatt TheBlueMatt force-pushed the 2018-04-mempoolinterface branch from 9a48d33 to 53b90df Compare May 2, 2018 18:15
void RegisterMempoolInterface(MempoolInterface* listener);
/** Unregister a listener from mempool */
void UnregisterMempoolInterface(MempoolInterface* listener);
/** Unregister all listeners from core and mempool */
Copy link
Contributor

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

Copy link
Contributor

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

Copy link
Contributor

@jnewbery jnewbery left a 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)
Copy link
Member

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;
Copy link
Member

@laanwj laanwj May 3, 2018

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.

Copy link
Contributor Author

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.

Copy link
Member

@laanwj laanwj May 12, 2018

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.

Copy link
Member

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.

@TheBlueMatt TheBlueMatt changed the title Split validationinterface into paralell validation/mempool interfaces Split validationinterface into parallell validation/mempool interfaces May 10, 2018
@TheBlueMatt TheBlueMatt changed the title Split validationinterface into parallell validation/mempool interfaces Split validationinterface into parallel validation/mempool interfaces May 10, 2018
@jimpo
Copy link
Contributor

jimpo commented May 11, 2018

ACK 53b90df. Tested by running with -debug=mempool, sending to wallet and checking unconfirmed balance, spending from wallet and checking balance. Did not attempt to test conflicts/double spends.

Copy link
Member

@sdaftuar sdaftuar left a 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.

Copy link
Member

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
Copy link
Member

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;
Copy link
Member

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)
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 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) {}
Copy link
Member

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);
}
Copy link
Member

@maflcko maflcko May 17, 2018

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?

@maflcko
Copy link
Member

maflcko commented May 21, 2018

Needs rebase.

@bitcoin bitcoin locked as resolved and limited conversation to collaborators Dec 16, 2021
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.