wallet: keep track of when the passphrase is needed when rescanning

Wallet passphrases are needed to top up the keypool during a
rescan. The following RPCs need the passphrase when rescanning:
    - `importdescriptors`
    - `rescanblockchain`

The following RPCs use the information about whether or not the
passphrase is being used to ensure that full rescans are able to
take place:
    - `walletlock`
    - `encryptwallet`
    - `walletpassphrasechange`
This commit is contained in:
ishaanam 2022-10-18 18:28:26 -04:00
parent 576e16e702
commit 66a86ebabb
4 changed files with 21 additions and 3 deletions

View file

@ -1651,7 +1651,7 @@ RPCHelpMan importdescriptors()
}
WalletRescanReserver reserver(*pwallet);
if (!reserver.reserve()) {
if (!reserver.reserve(/*with_passphrase=*/true)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait.");
}

View file

@ -128,6 +128,10 @@ RPCHelpMan walletpassphrasechange()
throw JSONRPCError(RPC_WALLET_WRONG_ENC_STATE, "Error: running with an unencrypted wallet, but walletpassphrasechange was called.");
}
if (pwallet->IsScanningWithPassphrase()) {
throw JSONRPCError(RPC_WALLET_ERROR, "Error: the wallet is currently being used to rescan the blockchain for related transactions. Please call `abortrescan` before changing the passphrase.");
}
// TODO: get rid of these .c_str() calls by implementing SecureString::operator=(std::string)
// Alternately, find a way to make request.params[0] mlock()'d to begin with.
SecureString strOldWalletPass;
@ -181,6 +185,10 @@ RPCHelpMan walletlock()
throw JSONRPCError(RPC_WALLET_WRONG_ENC_STATE, "Error: running with an unencrypted wallet, but walletlock was called.");
}
if (pwallet->IsScanningWithPassphrase()) {
throw JSONRPCError(RPC_WALLET_ERROR, "Error: the wallet is currently being used to rescan the blockchain for related transactions. Please call `abortrescan` before locking the wallet.");
}
pwallet->Lock();
pwallet->nRelockTime = 0;
@ -229,6 +237,10 @@ RPCHelpMan encryptwallet()
throw JSONRPCError(RPC_WALLET_WRONG_ENC_STATE, "Error: running with an encrypted wallet, but encryptwallet was called.");
}
if (pwallet->IsScanningWithPassphrase()) {
throw JSONRPCError(RPC_WALLET_ERROR, "Error: the wallet is currently being used to rescan the blockchain for related transactions. Please call `abortrescan` before encrypting the wallet.");
}
// TODO: get rid of this .c_str() by implementing SecureString::operator=(std::string)
// Alternately, find a way to make request.params[0] mlock()'d to begin with.
SecureString strWalletPass;

View file

@ -872,15 +872,17 @@ RPCHelpMan rescanblockchain()
wallet.BlockUntilSyncedToCurrentChain();
WalletRescanReserver reserver(*pwallet);
if (!reserver.reserve()) {
if (!reserver.reserve(/*with_passphrase=*/true)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait.");
}
int start_height = 0;
std::optional<int> stop_height;
uint256 start_block;
{
LOCK(pwallet->cs_wallet);
EnsureWalletIsUnlocked(*pwallet);
int tip_height = pwallet->GetLastBlockHeight();
if (!request.params[0].isNull()) {

View file

@ -243,6 +243,7 @@ private:
std::atomic<bool> fAbortRescan{false};
std::atomic<bool> fScanningWallet{false}; // controlled by WalletRescanReserver
std::atomic<bool> m_attaching_chain{false};
std::atomic<bool> m_scanning_with_passphrase{false};
std::atomic<int64_t> m_scanning_start{0};
std::atomic<double> m_scanning_progress{0};
friend class WalletRescanReserver;
@ -467,6 +468,7 @@ public:
void AbortRescan() { fAbortRescan = true; }
bool IsAbortingRescan() const { return fAbortRescan; }
bool IsScanning() const { return fScanningWallet; }
bool IsScanningWithPassphrase() const { return m_scanning_with_passphrase; }
int64_t ScanningDuration() const { return fScanningWallet ? GetTimeMillis() - m_scanning_start : 0; }
double ScanningProgress() const { return fScanningWallet ? (double) m_scanning_progress : 0; }
@ -960,12 +962,13 @@ private:
public:
explicit WalletRescanReserver(CWallet& w) : m_wallet(w) {}
bool reserve()
bool reserve(bool with_passphrase = false)
{
assert(!m_could_reserve);
if (m_wallet.fScanningWallet.exchange(true)) {
return false;
}
m_wallet.m_scanning_with_passphrase.exchange(with_passphrase);
m_wallet.m_scanning_start = GetTimeMillis();
m_wallet.m_scanning_progress = 0;
m_could_reserve = true;
@ -985,6 +988,7 @@ public:
{
if (m_could_reserve) {
m_wallet.fScanningWallet = false;
m_wallet.m_scanning_with_passphrase = false;
}
}
};