-
Notifications
You must be signed in to change notification settings - Fork 37.7k
Wallet passive startup #15719
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
Wallet passive startup #15719
Conversation
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers. 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. |
How will |
re: #15719 (comment)
No different than other callback arguments. In #10102, |
@ryanofsky ok thanks, will look into that. |
src/interfaces/chain.cpp
Outdated
std::vector<CTransactionRef> snapshot; | ||
std::packaged_task<std::unique_ptr<Handler>()> task([&] { | ||
if (snapshot_fn) snapshot_fn(std::move(snapshot)); | ||
return MakeUnique<NotificationsHandlerImpl>(*this, notifications); |
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.
Mempool can change after the snapshot but before registering the interface and so those changes will be missed.
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.
re: #15719 (comment)
Mempool can change after the snapshot but before registering the interface and so those changes will be missed.
This isn't true, and the comment below, "Hold locks while scheduling the task so notifications about added and removed transactions after the snapshot arrive after the snapshot" is specifically referring to this case.
Let me know if I should make it more clear, but as long as the locks are held, transactions can't be added / removed by other threads, and as long as CallFunctionInValidationInterfaceQueue is called before transactions are added/removed, the handler will be registered before the queued notifications signal it.
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.
But the queue can be non empty when CallFunctionInValidationInterfaceQueue is called?
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.
re: #15719 (comment)
But the queue can be non empty when CallFunctionInValidationInterfaceQueue is called?
I think you are referring to a different problem now (problem of old notifications being received when they shouldn't be, which is different from the problem of new notifications not being received when they should be). The problem of old notifications being received when they shouldn't be is what happens without this PR in the current code:
bitcoin/src/interfaces/chain.h
Lines 273 to 281 in 2c364fd
//! Synchronously send TransactionAddedToMempool notifications about all | |
//! current mempool transactions to the specified handler and return after | |
//! the last one is sent. These notifications aren't coordinated with async | |
//! notifications sent by handleNotifications, so out of date async | |
//! notifications from handleNotifications can arrive during and after | |
//! synchronous notifications from requestMempoolTransactions. Clients need | |
//! to be prepared to handle this by ignoring notifications about unknown | |
//! removed transactions and already added new transactions. | |
virtual void requestMempoolTransactions(Notifications& notifications) = 0; |
The problem is fixed by this PR by not constructing NotificationsHandlerImpl until after snapshot_fn is called. The comment there "to first send the snapshot before enabling subsequent notifications" is about 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.
AddToProcessQueue
tricked me, what really goes to that queue are notifications.
src/interfaces/chain.cpp
Outdated
std::vector<CTransactionRef> snapshot; | ||
std::packaged_task<std::unique_ptr<Handler>()> task([&] { | ||
if (snapshot_fn) snapshot_fn(std::move(snapshot)); | ||
return MakeUnique<NotificationsHandlerImpl>(*this, notifications); |
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.
AddToProcessQueue
tricked me, what really goes to that queue are notifications.
src/interfaces/chain.cpp
Outdated
|
||
// Snapshot mempool transactions, then schedule the task to execute. | ||
// Hold locks while scheduling the task so notifications about added and | ||
// removed transactions after the snapshot arrive after the snapshot. |
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.
also "arrive after task() is called" right?
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.
re: #15719 (comment)
also "arrive after task() is called" right?
Yes task() is what sends the snapshot, so that's what this is referring to. Replaced "transactions after the snapshot arrive after the snapshot" with "transactions after taking the snapshot arrive after the snapshot callback in task()" to reference it and be more specific.
Should we assert cs_main is held in the relevant validation interface notifications? |
re: #15719 (comment)
Removing locking from notifications is a step toward eventually removing Chain::Lock completely, and having the wallet just asynchronously process notifications without being able to hold on to |
Sorry for not being clear. I mean that @@ -152,6 +152,8 @@ void CMainSignals::UpdatedBlockTip(const CBlockIndex *pindexNew, const CBlockInd
}
void CMainSignals::TransactionAddedToMempool(const CTransactionRef &ptx) {
+ AssertLockHeld(::cs_main);
+ AssertLockHeld(::mempool.cs);
m_internals->m_schedulerClient.AddToProcessQueue([ptx, this] {
m_internals->TransactionAddedToMempool(ptx);
}); |
Concept ACK
Do we know what this does to memory usage if there's a very large mempool (either at startup when starting one or multiple wallets, or when loading a wallet dynamically during runtime)? |
With this PR by itself, a new temporary vector with one pointer per mempool transaction is created when a wallet is loaded. It's something to consider, but probably not likely to cause problems. With this PR combined with #10102 and running Since I did write this PR "Improves performance over IPC" in the description, the increased memory usage might factor into that. But to be sure, the motivation for this PR is mainly to make notifications show up cleanly and in order on the client side. |
src/wallet/wallet.cpp
Outdated
@@ -4213,6 +4215,17 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain, | |||
// Try to top up keypool. No-op if the wallet is locked. | |||
walletInstance->TopUpKeyPool(); | |||
|
|||
// Register with the validation interface. Skip requesting mempool transactions if wallet is empty. |
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 moving the sync-from-mempool function to above the initial rescan breaks the (undocumented) expectation that the wallet is notified of transactions in dependency order. In fact, this was slightly broken by #15652, but this PR makes it worse.
Prior to dynamically-loaded wallets, the wallet would always receive a transaction after it had received all of that transaction's ancestors. That wallet relies on that expectation:
CWallet::IsFromMe()
will only match on a transaction if its parent is already in the wallet.nTimeSmart
should be monotonically increasing for transaction descendency. If transactions arrive in the wrong order then this is no longer true, and they'll be shown out-of-order in the GUI.
Since #10740, a wallet could receive mempool transactions out-of-order (since a newly loaded wallet would not be notified of transactions in the mempool, but could be notified of their descendant transactions when they arrive). #15652 improved the situtation by notifying the wallet of all mempool transactions, but didn't sort them first, so a wallet could still be notified of the mempool txs in the wrong order. This PR potentially makes things worse by notifying the wallet of mempool txs before the blocks which could contain the mempool txs' ancestors.
I think to fix this, we need to:
- move the sync-from-mempool to after the rescan call, while cs_main is still held
- sort the mempool txs by using
mempool.infoAll()
before notifying the wallet.
We should also document that the wallet expects to see transactions in order!
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 also wonder if there could brokenness in the case where a reorg happens during a rescanblockchain
or importmulti
RPC call (these rescans are different because cs_main isn't held the whole time).
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.
re: #15719 (comment)
I think to fix this, we need to:
- move the sync-from-mempool to after the rescan call, while cs_main is still held
- sort the mempool txs by using
mempool.infoAll()
before notifying the wallet.We should also document that the wallet expects to see transactions in order!
Moving sync is done. Rescanning happens before syncing from mempool in the current version of this PR. Sorting mempool isn't done yet. So this will still be as broken as it has been since #15652, but not more broken. There's already a lot going on here, so I think I would want to address this in a different PR. It seems pretty easy to fix independently of this PR, but writing a test case could be a challenge
@ryanofsky Are you still working on this? If no, then please close it. |
Thanks for the reminder. I looked into this again and I think the WIP tag still applies. I had been indecisive about involving rescan in this this change after John pointed out it was necessary, since I was unsure if it was better to incrementally change the rescan code (first synchronize rescan with notifications, then consolidate rescans across wallets https://gist.github.com/ariard/89f9bcc3a7ab9576fc6d15d251032cfa), or to just do both things in a single PR. But now I think it should be possible to synchronize rescan with notifications in a way that doesn't change existing code too much and should also make ariard's proposed change easier. |
532cc1b
to
a075d47
Compare
# | ||
# Uses of std::cout are guaranteed thread safe by the c++ standard | ||
# https://stackoverflow.com/questions/50322790/thread-safety-and-piping-to-streams | ||
race:std::__1::ios_base::width |
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 remove this after #23370
Allow retrieving locator from Chain findBlock methods, used in upcoming commit by the wallet to get the locator for the chain tip on startup.
Prevents log callbacks from deadlocking if they call code containing log statements. In most cases it will not make sense to call logging code from a log hook, because it could trigger and endless chain of log messages. But if a hook is conditioned on the content of messages, like the DebugLogHelper hook, it can make sense and be useful. The new functionality is used in the CreateWalletFromFile test in an upcoming commit to be able to create transactions and blocks at specific points during test execution and check for race conditions.
Make AttachChain just responsible for syncing to the chain, moving error handling and chain assignment to the caller before more syncing functionality is moved in the next commit. Also, start using the AttachChain method in tests instead of trying attach in a more partial way using Chain::handleNotifications.
Move wallet startup code closer to a simple model where the wallet attaches to the chain with a single chain.handleNotifications() call, and just starts passively receiving blocks and mempool notifications from the last update point, instead having to actively rescan blocks and request a mempool snapshot, and deal with the tip changing, and deal with early or stale notifications. Also, stop locking the cs_wallet mutex and registering for validationinterface notifications before the rescan. This was new behavior since 6a72f26 bitcoin#16426 and is not ideal because it stops other wallets and rpcs and the gui from receiving new notifications until after the scan completes. This change is a half-step towards implementing multiwallet parallel scans (bitcoin#11756), since it provides needed locator and birthday timestamp information to the Chain interface, and it rationalizes locking and event ordering in the startup code. The second half of implementing parallel rescans requires moving the ScanForWalletTransactions implementation (which this PR does not touch) from the wallet to the node.
This is just a code simplification and revert of 0440481 from bitcoin#15652. No behavior is changing.
🐙 This pull request conflicts with the target branch and needs rebase. Want to unsubscribe from rebase notifications on this pull request? Just convert this pull request to a "draft". |
Move wallet startup code closer to a simple model where the wallet attaches to the chain with a single chain.handleNotifications() call, and just starts passively receiving blocks and mempool notifications from the last update point, instead having to actively rescan blocks and request a mempool snapshot, and deal with the tip changing, and deal with early or stale notifications.
Also, stop locking the cs_wallet mutex and registering for validationinterface notifications before the rescan. This was new behavior since 6a72f26 #16426 and is not ideal because it stops other wallets and rpcs and the gui from receiving new notifications until after the scan completes.
This change is a half-step towards implementing multiwallet scans (#11756), since it provides needed locator and birthday timestamp information to the Chain interface, and it rationalizes locking and event ordering in the startup code. The second half of implementing parallel rescans requires moving the ScanForWalletTransactions implementation (which this PR does not touch) from the wallet to the node.