-
Notifications
You must be signed in to change notification settings - Fork 38.8k
Add HD wallet auto-restore functionality #10240
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
4efffdf to
427ea2f
Compare
|
I know there was discussion about this in IRC last week, but I didn't follow closely. Would it be better to review this PR now, or hold off for more changes? |
|
I'm currently implementing the changes we discussed in last weeks RIC meeting. A review makes more sense after the overhaul. |
fa2ed74 to
3a7dbb6
Compare
|
Completely rewrote this PR. Keeping the WIP tag for now. unencrypted walletsFor unencrypted wallets, the key pool will always be topped up. During sync (SyncTransaction), we check for used keypool keys and mark all keys up to the matched key as used. Additionally we topup the keypool to ensure we not fall below the gap limit (20 keys). encrypted wallets in non pruning modeSame as above, but, If we hit the gap limit with an encrypted wallet, we can't topup the keypool. In that case, we just pause the sync (not the node, only the wallet). encrypted wallets in pruned modeSame as above, but, we also stop requesting and connecting blocks. The full node will "pause" in this case until the user did unlock his wallet (== topup, rescan, etc.). This has a little bit of complexity and requires careful reviews. |
7ceb728 to
cada726
Compare
|
All tests are passing now. |
src/wallet/wallet.cpp
Outdated
| { | ||
| // check if we need to flag used keypool keys | ||
| std::set<CKeyID> setKeyPool; | ||
| GetAllReserveKeys(setKeyPool); |
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 guess this is very inefficient and something like #10235 should allow to speed up things.
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.
In commit "Add basic HD wallet restore functionality"
Maybe rename this variable. (It shadows CWallet::setKeyPool, which makes that variable more annoying to grep for.)
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.
Maybe rename this variable. (It shadows CWallet::setKeyPool, which makes that variable more annoying to grep for.)
Done in 2a203b7
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.
In commit "Add basic HD wallet restore functionality"
Maybe avoid BOOST_FOREACH here and below.
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.
Maybe avoid BOOST_FOREACH here and below.
Done in 2a203b7
| if (!walletdb.ReadPool(id, keypool)) | ||
| throw std::runtime_error(std::string(__func__) + ": read failed"); | ||
|
|
||
| if (keypool.vchPubKey.GetID() == keyId) { |
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 "Add basic HD wallet restore functionality"
It seems like this for-loop could be eliminated if you just changed GetAllReserveKeys to return map<CKeyID, int64_t> instead of set<CKeyID>. I think making this change would add less code than is written here.
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.
It would also require to keep the fInternal flags in memory (for the address book internal/external change flagging). This is independently addresses in #10238.
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.
| { | ||
| LOCK(cs_wallet); | ||
| CWalletDB walletdb(*dbw); | ||
| auto it = std::begin(setKeyPool); |
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 "Add basic HD wallet restore functionality"
Should move this declaration closer to where it's actually used way below.
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 move this declaration closer to where it's actually used way below.
(unchanged)
| throw std::runtime_error(std::string(__func__) + ": read failed"); | ||
|
|
||
| // only mark keys on the corresponding chain | ||
| if (keypool.fInternal == foundInternal) { |
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 "Add basic HD wallet restore functionality"
Shouldn't it be an error if this condition is not true?
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.
Shouldn't it be an error if this condition is not true?
(unchanged)
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.
In commit "Make sure we pause immediately if we receive a pause-request"
Would squash this commit into the commit "Add request-halt flag to BlockConnected signal" (along with the other change I suggest moving there) so this is understandable in the correct context.
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.
Would squash this commit into the commit "Add request-halt flag to BlockConnected signal" (along with the other change I suggest moving there) so this is understandable in the correct context.
(unchanged)
| strUsage += HelpMessageOpt("-flushwallet", strprintf("Run a thread to flush wallet periodically (default: %u)", DEFAULT_FLUSHWALLET)); | ||
| strUsage += HelpMessageOpt("-privdb", strprintf("Sets the DB_PRIVATE flag in the wallet db environment (default: %u)", DEFAULT_WALLET_PRIVDB)); | ||
| strUsage += HelpMessageOpt("-walletrejectlongchains", strprintf(_("Wallet will not create transactions that violate mempool chain limits (default: %u)"), DEFAULT_WALLET_REJECT_LONG_CHAINS)); | ||
| strUsage += HelpMessageOpt("-hdignoregaplimit", strprintf(_("Ignores the minimum keypool-size warning for HD restore (default: %u)"), DEFAULT_IGNORE_HD_MIN_KEYPOOL_SIZE)); |
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 "SetSoftArg the keypool always to the minimum gap limit if HD is enabled"
It seems like it would be a lot harder to for someone to understand what this "ignore gap limit" option does than the PAUSE_SYNC_WHEN_KEYPOOL_SMALLER_THAN option I suggested in another comment. Would be curious to know what you think about 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.
The -hdignoregaplimit is for debug purposes only. Our tests run with a keypool size of 1 and therefor would cause troubles running post this PR without a "ignore" option, and I don't think it matter how we "ignore" the gap limit and over a startup argument seems the be the simplest way.
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 -hdignoregaplimit is for debug purposes only
Maybe it's not worth fixing, but even as a developer I think "pause sync when keypool is smaller than" is clearer than "ignore hd gap limit" because one name tells you literally what the option does, while the other refers obliquely to what the option doesn't do.
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 "pause sync when keypool is smaller than" is clearer than "ignore hd gap limit"
👍
nit: place this argument in the correct alphabetic order.
| walletInstance->TopUpKeyPool(); | ||
| } | ||
| if (!walletInstance->CheckKeypoolMinSize()) { | ||
| if (!walletInstance->CheckKeypoolMinSize() && !GetBoolArg("-hdignoregaplimit", DEFAULT_IGNORE_HD_MIN_KEYPOOL_SIZE)) { |
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 "SetSoftArg the keypool always to the minimum gap limit if HD is enabled"
This seems broken. It's still pausing but just no longer warning that the pause took place?
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.
This seems broken. It's still pausing but just no longer warning that the pause took place?
On second look, this seems ok because CWallet::CheckKeypoolMinSize now checks for "-hdignoregaplimit" and disables the pause.
| self.stop_node(1) | ||
| os.remove(self.options.tmpdir + "/node1/regtest/wallet.dat") | ||
| # we need to delete the complete regtest directory | ||
| # otherwise node1 would auto-recover all funds in flag the keypool keys as used |
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 "SetSoftArg the keypool always to the minimum gap limit if HD is enabled"
Is this change meant to be part of this commit? I don't think there's an interaction with hdignoregaplimit here.
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.
Is this change meant to be part of this commit? I don't think there's an interaction with hdignoregaplimit here.
(unchanged)
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 also confused about why wallet-hd.py needs to be updated.
src/wallet/wallet.cpp
Outdated
|
|
||
| // for now, enable block requests and tip updates via the states | ||
| // this will only be sufficient as long as only a single wallet adjusts these stats | ||
| // otherwise the net-/validation-logic needs to poll the state over a signal (or similar) |
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 "Ensure we pause wallet sync when the keypool requires extension"
It should be possible to handle this without any polling or notifications by making fPauseBlockRequests and fPauseTipUpdates counters instead of bools. The counters would need to be incremented whenever fSyncPausedUntilKeypoolExt changed from false to true in any wallet, and decremented when it changed from true to false. Block requesting or tip updating would be paused whenever its respective counter was greater than 0.
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.
Yes. Thats a good idea. Do you think this should be done in this PR or can we tackle this once we have multiple places where the sync may get paused?
Counters may also introduce the risk of unwillingly increasing it twice (while only decrease it once).
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.
Do you think this should be done in this PR or can we tackle this once we have multiple places where the sync may get paused?
Maybe, this PR is already pretty complicated, so I wouldn't want to tack this on in extra commits, but it could be nice if implemented to replace the existing "Add option to pause..." commits.
…re key to respect the gap limit
… BlockConnected signal
11cccad to
4cbc216
Compare
|
Needed rebase. |
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.
Would squash this commit into the commit "Add request-halt flag to BlockConnected signal" (along with the other change I suggest moving there) so this is understandable in the correct context.
(unchanged)
| SyncTransaction(pblock->vtx[i], pindex, i); | ||
| } | ||
| if (fSyncPausedUntilKeypoolExt) { | ||
| requestPause = true; |
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.
As you mention, it would be much simpler to call setBlockRequestsPaused(), but, wouldn't it be a massive layer violation? Communicating through the signals doesn't require that the wallet needs to know what the full node parts is really doing. But maybe im overcomplicating things here.
I don't see why it would be a problem to call setBlockRequestsPaused(true) from CWallet::MarkReserveKeysAsUsed, especially if it isn't a problem to call setBlockRequestsPaused(false) from CWallet::EventuallyRescanAfterKeypoolTopUp which the current code already does. It seems like it would just make pausing simpler, and more consistent with unpausing.
| if (pindexRescan != block) { | ||
| const static std::string pruneError = "Prune: last wallet synchronisation goes beyond pruned data. You need to -reindex (download the whole blockchain again in case of pruned node)"; | ||
| uiInterface.ThreadSafeMessageBox(pruneError, "", CClientUIInterface::MSG_ERROR); | ||
| StartShutdown(); |
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 there is no guarantee that we won't prune bellow the wallets best block,.. this why this shutdown/protection is here.
Wouldn't just staying paused in this case offer as much protection as briefly unpausing and then shutting down?
| { | ||
| LOCK(cs_wallet); | ||
| CWalletDB walletdb(*dbw); | ||
| auto it = std::begin(setKeyPool); |
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 move this declaration closer to where it's actually used way below.
(unchanged)
| if (!walletdb.ReadPool(id, keypool)) | ||
| throw std::runtime_error(std::string(__func__) + ": read failed"); | ||
|
|
||
| if (keypool.vchPubKey.GetID() == keyId) { |
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.
| size_t extKeypoolSize = KeypoolCountExternalKeys(); | ||
| if (IsHDEnabled() && (extKeypoolSize < HD_RESTORE_KEYPOOL_SIZE_MIN || (setKeyPool.size()-extKeypoolSize) < HD_RESTORE_KEYPOOL_SIZE_MIN)) { | ||
| // if the remaining keypool size is below the gap limit, refuse to continue with the sync | ||
| fSyncPausedUntilKeypoolExt = true; |
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.
This pausing behavior seems like it should be optional
The "-hdignoregaplimit" argument added in the next commit does make this optional (commit "SetSoftArg the keypool always to the minimum gap limit"), though I still think a single "pause sync when keypool smaller than" check would be a little cleaner than separate "keypool size min" and "ignore gap limit" checks.
| } | ||
|
|
||
| if (!walletInstance->CheckKeypoolMinSize()) { | ||
| InitWarning(_("Your keypool size is below the required limit for HD rescans. Wallet synchronisation is now paused until you have refilled the keypool.")); |
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.
Warning should suggest how to fix the problem, and also ideally also include more background information (maybe consider using my previous suggestion for the -keypool limit warning).
(unchanged)
| walletInstance->TopUpKeyPool(); | ||
| } | ||
| if (!walletInstance->CheckKeypoolMinSize()) { | ||
| if (!walletInstance->CheckKeypoolMinSize() && !GetBoolArg("-hdignoregaplimit", DEFAULT_IGNORE_HD_MIN_KEYPOOL_SIZE)) { |
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.
This seems broken. It's still pausing but just no longer warning that the pause took place?
On second look, this seems ok because CWallet::CheckKeypoolMinSize now checks for "-hdignoregaplimit" and disables the pause.
src/wallet/wallet.cpp
Outdated
| // HD Restore: Make sure we always have a reasonable keypool size if HD is enabled | ||
| if (walletInstance->IsHDEnabled()) { | ||
| if (walletInstance->IsCrypted()) { | ||
| InitWarning(_("Your are using an encrypted HD wallet. In case you recover a HD wallet, you may miss incomming or outgoing funds.")); |
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 "SetSoftArg the keypool always to the minimum gap limit if HD is enabled"
It might be a good idea to keep the warning about missing funds in the case where IsCrypted && size < KEYPOOL_SIZE_MIN && GetBool("-hdignoregaplimit"). I also had a suggestion about this warning in
#10240 (comment)
src/wallet/wallet.cpp
Outdated
| } | ||
| else { | ||
| if (GetArg("-keypool", DEFAULT_KEYPOOL_SIZE) < HD_RESTORE_KEYPOOL_SIZE_MIN ) { | ||
| InitWarning(_("Your keypool size is below the recommended limit for HD rescans. In case you recover a HD wallet, you may miss incomming or outgoing funds.")); |
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 "Make sure we check the keypool min size during startup"
Why drop this warning? It still seems useful to warn about the "-keypool" being too small even if KeypoolCountExternalKeys() is momentarily returning a high number of keys. (I also had another suggestion about this warning at #10240 (comment))
|
Needs rebase. |
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.
Needs rebase. I've added a lot of mainly nitty comments. The most important thing to address is to make this compatible with multiwallet.
|
|
||
| for (const CTransactionRef& ptx : vtxConflicted) { | ||
| SyncTransaction(ptx); | ||
| void CWallet::BlockConnected(const std::shared_ptr<const CBlock>& pblock, const CBlockIndex *pindex, const std::vector<CTransactionRef>& vtxConflicted, bool &requestPause) { |
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 going to comment on @ryanofsky's other comments regarding commit order (although they all sound sensible). This one should definitely be fixed in commit "Add request-halt flag to BlockConnected signal" since it breaks the build and so breaks git bisect.
| // Make sure pindexBestKnownBlock is up to date, we'll need it. | ||
| ProcessBlockAvailability(nodeid); | ||
|
|
||
| if (isBlockRequestsPaused()) |
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: braces
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 address.
| /** Guess verification progress (as a fraction between 0.0=genesis and 1.0=current tip). */ | ||
| double GuessVerificationProgress(const ChainTxData& data, CBlockIndex* pindex); | ||
|
|
||
| /** |
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: s/will allow to temporary/will temporarily
| bool isBlockRequestsPaused(); | ||
| void setBlockRequestsPaused(bool state); | ||
|
|
||
| /** |
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: s/will temporary/will temporarily
| pwallet->TopUpKeyPool(); | ||
|
|
||
| // give a hint to the wallet in case we have paused sync (we may have fall bellow the hd gap limit) | ||
| // this runs synchronous, at least during the resync, we can be sure the keypool can be topped up |
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:
// Give a hint to the wallet in case we have paused sync (we may have fallen bellow the hd gap limit).
// This runs synchronously, at least during the resync, we can be sure the keypool can be topped up
| def run_test (self): | ||
| tmpdir = self.options.tmpdir | ||
|
|
||
| print("Initialize wallet including backups of unencrypted and encrypted wallet") |
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 use self.log.info() instead of print for logging.
| addr_enc_oldpool = self.nodes[1].getnewaddress() | ||
|
|
||
| # now make sure we retrive an address in the extended pool | ||
| self.nodes[1].walletpassphrase("test", 10) |
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.
Suggest you set the timeout higher, perhaps to 60 seconds. Tests should be robust to bitcoind hanging for several seconds, especially if running in parallel on Travis.
|
|
||
| print("Testing with unencrypted wallet") | ||
| self.stop_node(1) | ||
| shutil.rmtree(tmpdir + "/node1/regtest") |
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 don't do this! It erases the debug.log file. Are you able to be more targeted in what you're erasing?
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 address.
| assert_equal(self.nodes[1].validateaddress(addr)['hdkeypath'], "m/0'/0'/11'") | ||
|
|
||
| rawch = self.nodes[1].getrawchangeaddress() | ||
| txid = self.nodes[0].sendtoaddress(addr, 1) |
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.
txid isn't used. No need to assign it here or below.
| self.stop_node(1) | ||
| os.remove(self.options.tmpdir + "/node1/regtest/wallet.dat") | ||
| # we need to delete the complete regtest directory | ||
| # otherwise node1 would auto-recover all funds in flag the keypool keys as used |
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 also confused about why wallet-hd.py needs to be updated.
|
Will begin code review post rebase. Question about the parameters-- Why 20? that is very small, I am not aware of any reason to not have it be more like-- say-- 10,000. With a large number the pause is hopefully seldom/never hit. Electrum uses a very small number because its a single user wallet and has to send all these addresses for processing to a remote server. In a commercial application getting reordering beyond 20 would be very easy. (e.g hand out 30 keys to different users and only the first one makes a payment). |
|
Agree that 20 is probably way to low. |
sipa
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.
Some comments already.
Overall question: why is any of this conditional on HD being enabled? I think we want to mark user and topup any key seen on the network at any time.
| # Copyright (c) 2017 The Bitcoin Core developers | ||
| # Distributed under the MIT software license, see the accompanying | ||
| # file COPYING or http://www.opensource.org/licenses/mit-license.php. | ||
| """Test Hierarchical Deterministic wallet function.""" |
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 address.
|
|
||
| print("Testing with unencrypted wallet") | ||
| self.stop_node(1) | ||
| shutil.rmtree(tmpdir + "/node1/regtest") |
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 address.
| if (walletInstance->IsCrypted()) { | ||
| InitWarning(_("Your are using an encrypted HD wallet. In case you recover a HD wallet, you may miss incomming or outgoing funds.")); | ||
| } | ||
| else { |
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: else on the same line as }
| // Make sure pindexBestKnownBlock is up to date, we'll need it. | ||
| ProcessBlockAvailability(nodeid); | ||
|
|
||
| if (isBlockRequestsPaused()) |
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 address.
|
Closing in favor of #10830 |
This PR ensures that we always check the keypool during SyncTransaction and, that the lookup window for HD restore is large enough (currently +20 keys).
Once the keypool size falls below the min gap limit (currently 20 keys) and the keypool can't be extended (locked wallet), the wallet then temporary pauses synchronisation.
unencrypted wallets
For unencrypted wallets, the key pool will always be topped up. During sync (SyncTransaction), we check for used keypool keys and mark all keys up to the matched key as used. Additionally we topup the keypool to ensure we not fall below the gap limit (20 keys).
encrypted wallets in non pruning mode
Same as above, but, If we hit the gap limit with an encrypted wallet, we can't topup the keypool. In that case, we just pause the sync (not the node, only the wallet).
Once the user unlocks the wallet over
walletpassphrase, we rescan down to the point where we have stopped previously to ensure we don't miss possible funds.encrypted wallets in pruned mode
Same as above, but, we also stop requesting and connecting blocks. The full node will "pause" in this case until the user did unlock his wallet (== topup, rescan, etc.).
This has a little bit of complexity and requires careful reviews.