Skip to content

Commit

Permalink
wallet: keep track of when the passphrase is needed when rescanning
Browse files Browse the repository at this point in the history
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`
  • Loading branch information
ishaanam committed Oct 26, 2022
1 parent deeb70a commit 8c16fdb
Show file tree
Hide file tree
Showing 4 changed files with 21 additions and 3 deletions.
2 changes: 1 addition & 1 deletion src/wallet/rpc/backup.cpp
Expand Up @@ -1653,7 +1653,7 @@ RPCHelpMan importdescriptors()
RPCTypeCheck(main_request.params, {UniValue::VARR, UniValue::VOBJ});

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

Expand Down
12 changes: 12 additions & 0 deletions src/wallet/rpc/encrypt.cpp
Expand Up @@ -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;
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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;
Expand Down
4 changes: 3 additions & 1 deletion src/wallet/rpc/transactions.cpp
Expand Up @@ -866,15 +866,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()) {
Expand Down
6 changes: 5 additions & 1 deletion src/wallet/wallet.h
Expand Up @@ -242,6 +242,7 @@ class CWallet final : public WalletStorage, public interfaces::Chain::Notificati
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;
Expand Down Expand Up @@ -466,6 +467,7 @@ class CWallet final : public WalletStorage, public interfaces::Chain::Notificati
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; }

Expand Down Expand Up @@ -958,12 +960,13 @@ class WalletRescanReserver
public:
explicit WalletRescanReserver(CWallet& w) : m_wallet(w), m_could_reserve(false) {}

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;
Expand All @@ -983,6 +986,7 @@ class WalletRescanReserver
{
if (m_could_reserve) {
m_wallet.fScanningWallet = false;
m_wallet.m_scanning_with_passphrase = false;
}
}
};
Expand Down

0 comments on commit 8c16fdb

Please sign in to comment.