-
Notifications
You must be signed in to change notification settings - Fork 37.8k
wallet: ensure the wallet is unlocked when needed for rescanning #26347
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -243,6 +243,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; | ||
|
@@ -467,6 +468,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; } | ||
|
||
|
@@ -486,6 +488,9 @@ class CWallet final : public WalletStorage, public interfaces::Chain::Notificati | |
|
||
// Used to prevent concurrent calls to walletpassphrase RPC. | ||
Mutex m_unlock_mutex; | ||
// Used to prevent deleting the passphrase from memory when it is still in use. | ||
RecursiveMutex m_relock_mutex; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Using There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
||
bool Unlock(const SecureString& strWalletPassphrase, bool accept_no_keys = false); | ||
bool ChangeWalletPassphrase(const SecureString& strOldWalletPassphrase, const SecureString& strNewWalletPassphrase); | ||
bool EncryptWallet(const SecureString& strWalletPassphrase); | ||
|
@@ -960,12 +965,13 @@ class WalletRescanReserver | |
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 +991,7 @@ class WalletRescanReserver | |
{ | ||
if (m_could_reserve) { | ||
m_wallet.fScanningWallet = false; | ||
m_wallet.m_scanning_with_passphrase = false; | ||
} | ||
} | ||
}; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,6 +14,9 @@ | |
assert_raises_rpc_error, | ||
set_node_times, | ||
) | ||
from test_framework.wallet_util import ( | ||
get_generate_key, | ||
) | ||
|
||
|
||
class TransactionTimeRescanTest(BitcoinTestFramework): | ||
|
@@ -23,6 +26,10 @@ def add_options(self, parser): | |
def set_test_params(self): | ||
self.setup_clean_chain = False | ||
self.num_nodes = 3 | ||
self.extra_args = [["-keypool=400"], | ||
["-keypool=400"], | ||
[] | ||
] | ||
|
||
def skip_test_if_missing_module(self): | ||
self.skip_if_no_wallet() | ||
|
@@ -167,6 +174,38 @@ def run_test(self): | |
assert_raises_rpc_error(-8, "Invalid stop_height", restorewo_wallet.rescanblockchain, 1, -1) | ||
assert_raises_rpc_error(-8, "stop_height must be greater than start_height", restorewo_wallet.rescanblockchain, 20, 10) | ||
|
||
self.log.info("Test `rescanblockchain` fails when wallet is encrypted and locked") | ||
usernode.createwallet(wallet_name="enc_wallet", passphrase="passphrase") | ||
enc_wallet = usernode.get_wallet_rpc("enc_wallet") | ||
assert_raises_rpc_error(-13, "Error: Please enter the wallet passphrase with walletpassphrase first.", enc_wallet.rescanblockchain) | ||
|
||
if not self.options.descriptors: | ||
self.log.info("Test rescanning an encrypted wallet") | ||
hd_seed = get_generate_key().privkey | ||
|
||
usernode.createwallet(wallet_name="temp_wallet", blank=True, descriptors=False) | ||
temp_wallet = usernode.get_wallet_rpc("temp_wallet") | ||
temp_wallet.sethdseed(seed=hd_seed) | ||
|
||
for i in range(399): | ||
temp_wallet.getnewaddress() | ||
|
||
self.generatetoaddress(usernode, COINBASE_MATURITY + 1, temp_wallet.getnewaddress()) | ||
self.generatetoaddress(usernode, COINBASE_MATURITY + 1, temp_wallet.getnewaddress()) | ||
|
||
minernode.createwallet("encrypted_wallet", blank=True, passphrase="passphrase", descriptors=False) | ||
encrypted_wallet = minernode.get_wallet_rpc("encrypted_wallet") | ||
|
||
encrypted_wallet.walletpassphrase("passphrase", 1) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This fails for me when running in valgrind:
Maybe the timeout can be increased to 999999? Also I wonder why you picked a timeout of If you want to confirm that the wallet can't be relocked while the rescan is in progress, wouldn't it be better to call There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Yes, the timeout before
3 was used because it needed to be a number that was large enough so that the scan would begin, but small enough so that the timeout would occur while the rescan is in progress. However, this number can be too large or too small depending on the speed of the machine.
I don't completely understand this suggestion. Do you mean calling
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
No, I mean calling
Without the fix in this pull, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok, I think I understand what you mean now. I have implemented this using multi-threading in #27199. However, for me this no longer fails without the fix. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
For me, nothing fails. Unless I specifically add a manual sleep on the code before the fix: diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp
index eed40f9462..d5b5af0d4c 100644
--- a/src/wallet/wallet.cpp
+++ b/src/wallet/wallet.cpp
@@ -1851,6 +1851,8 @@ CWallet::ScanResult CWallet::ScanForWalletTransactions(const uint256& start_bloc
WalletLogPrintf("Rescan started from block %s... (%s)\n", start_block.ToString(),
fast_rescan_filter ? "fast variant using block filters" : "slow variant inspecting all blocks");
+ UninterruptibleSleep(4s);
+
fAbortRescan = false;
ShowProgress(strprintf("%s " + _("Rescanning…").translated, GetDisplayName()), 0); // show rescan progress in GUI as dialog or on splashscreen, if rescan required on startup (e.g. due to corruption)
uint256 tip_hash = WITH_LOCK(cs_wallet, return GetLastBlockHash()); |
||
encrypted_wallet.sethdseed(seed=hd_seed) | ||
|
||
batch = [] | ||
batch.append(encrypted_wallet.walletpassphrase.get_request("passphrase", 3)) | ||
batch.append(encrypted_wallet.rescanblockchain.get_request()) | ||
|
||
encrypted_wallet.batch(batch) | ||
|
||
assert_equal(encrypted_wallet.getbalance(), temp_wallet.getbalance()) | ||
|
||
if __name__ == '__main__': | ||
TransactionTimeRescanTest().main() |
Uh oh!
There was an error while loading. Please reload this page.