gui: Adds option to not load the wallet after migration
What changed, and why it matters
This commit adds a user-facing checkbox in the Bitcoin Core desktop wallet that lets someone choose whether to automatically load a wallet after migrating it from an older format. Previously the wallet was always loaded. The change is a usability improvement, not a security fix, and does not introduce any obvious vulnerability.
No security action required; treat as routine feature review. If auditing, verify the downstream `MigrateLegacyToDescriptor` correctly handles `load_wallet=false` without leaving state inconsistent.
Security signals we found
No security-relevant signal: change is a feature/usability addition
No memory safety, cryptographic, consensus, or network changes
No privilege boundary crossed
No bug fix or vulnerability remediation visible in diff
Evidence from the diff
The patch threads a new boolean load_wallet parameter through the GUI wallet migration path: MigrateWalletActivity::do_migrate, the interfaces::WalletLoader::migrateWallet virtual method, and its implementation in wallet/interfaces.cpp. When unchecked, the migrated wallet descriptor is created but getOrCreateWallet is not called, and a message tells the user they can open it later. The default remains checked (load after migration).
Changed components
src/qt/walletcontroller.cppsrc/qt/walletcontroller.hsrc/interfaces/wallet.hsrc/wallet/interfaces.cppInspect captured patch +20 / −10
diff --git a/src/interfaces/wallet.h b/src/interfaces/wallet.h
index 326361aa..7955820b 100644
--- a/src/interfaces/wallet.h
+++ b/src/interfaces/wallet.h
@@ -323,7 +323,7 @@ public:
virtual util::Result<std::unique_ptr<Wallet>> restoreWallet(const fs::path& backup_file, const std::string& wallet_name, std::vector<bilingual_str>& warnings, bool load_after_restore) = 0;
//! Migrate a wallet
- virtual util::Result<WalletMigrationResult> migrateWallet(const std::string& name, const SecureString& passphrase) = 0;
+ virtual util::Result<WalletMigrationResult> migrateWallet(const std::string& name, const SecureString& passphrase, bool load_wallet) = 0;
//! Returns true if wallet stores encryption keys
virtual bool isEncrypted(const std::string& wallet_name) = 0;
diff --git a/src/qt/walletcontroller.cpp b/src/qt/walletcontroller.cpp
index 1d6153d4..e3cb713c 100644
--- a/src/qt/walletcontroller.cpp
+++ b/src/qt/walletcontroller.cpp
@@ -23,6 +23,7 @@
#include <chrono>
#include <QApplication>
+#include <QCheckBox>
#include <QMessageBox>
#include <QMetaObject>
#include <QMutexLocker>
@@ -439,7 +440,7 @@ void RestoreWalletActivity::finish()
Q_EMIT finished();
}
-void MigrateWalletActivity::do_migrate(const std::string& name)
+void MigrateWalletActivity::do_migrate(const std::string& name, bool load_wallet)
{
SecureString passphrase;
if (node().walletLoader().isEncrypted(name)) {
@@ -450,8 +451,8 @@ void MigrateWalletActivity::do_migrate(const std::string& name)
showProgressDialog(tr("Migrate Wallet"), tr("Migrating Wallet <b>%1</b>…").arg(GUIUtil::HtmlEscape(name)));
- QTimer::singleShot(0, worker(), [this, name, passphrase] {
- auto res{node().walletLoader().migrateWallet(name, passphrase)};
+ QTimer::singleShot(0, worker(), [this, name, passphrase, load_wallet] {
+ auto res{node().walletLoader().migrateWallet(name, passphrase, load_wallet)};
if (res) {
m_success_message = tr("The wallet '%1' was migrated successfully.").arg(GUIUtil::HtmlEscape(GUIUtil::WalletDisplayName(name)));
@@ -461,7 +462,12 @@ void MigrateWalletActivity::do_migrate(const std::string& name)
if (res->solvables_wallet_name) {
m_success_message += QChar(' ') + tr("Solvable but not watched scripts have been migrated to a new wallet named '%1'.").arg(GUIUtil::HtmlEscape(GUIUtil::WalletDisplayName(res->solvables_wallet_name.value())));
}
- m_wallet_model = m_wallet_controller->getOrCreateWallet(std::move(res->wallet));
+ if (load_wallet) {
+ assert(res->wallet);
+ m_wallet_model = m_wallet_controller->getOrCreateWallet(std::move(res->wallet));
+ } else {
+ m_success_message += QChar(' ') + tr("The wallet was not loaded after migration. You can open it from the \"File > Open wallet\" menu.");
+ }
} else {
m_error_message = util::ErrorString(res);
}
@@ -482,11 +488,15 @@ void MigrateWalletActivity::migrate(const std::string& name)
"The migration process will create a backup of the wallet before migrating. This backup file will be named "
"<wallet name>-<timestamp>.legacy.bak and can be found in the directory for this wallet. In the event of "
"an incorrect migration, the backup can be restored with the \"Restore Wallet\" functionality."));
+ auto* load_wallet_checkbox = new QCheckBox(tr("Load wallet after migration"), &box);
+ load_wallet_checkbox->setToolTip(tr("If the node is pruned and the wallet was created before the pruned height, the migration process may fail trying to load the migrated wallet."));
+ load_wallet_checkbox->setChecked(true);
+ box.setCheckBox(load_wallet_checkbox);
box.setStandardButtons(QMessageBox::Yes|QMessageBox::Cancel);
box.setDefaultButton(QMessageBox::Yes);
if (box.exec() != QMessageBox::Yes) return;
- do_migrate(name);
+ do_migrate(name, load_wallet_checkbox->isChecked());
}
void MigrateWalletActivity::restore_and_migrate(const fs::path& path, const std::string& wallet_name)
@@ -523,7 +533,7 @@ void MigrateWalletActivity::restore_and_migrate(const fs::path& path, const std:
return;
}
QTimer::singleShot(0, this, [this, wallet_name] {
- do_migrate(wallet_name);
+ do_migrate(wallet_name, /*load_wallet=*/true);
});
});
}
diff --git a/src/qt/walletcontroller.h b/src/qt/walletcontroller.h
index 2fb9ccf1..6f807333 100644
--- a/src/qt/walletcontroller.h
+++ b/src/qt/walletcontroller.h
@@ -196,7 +196,7 @@ Q_SIGNALS:
private:
QString m_success_message;
- void do_migrate(const std::string& name);
+ void do_migrate(const std::string& name, bool load_wallet);
void finish();
};
diff --git a/src/wallet/interfaces.cpp b/src/wallet/interfaces.cpp
index 9f9876da..bed9be0a 100644
--- a/src/wallet/interfaces.cpp
+++ b/src/wallet/interfaces.cpp
@@ -601,9 +601,9 @@ public:
}
return wallet;
}
- util::Result<WalletMigrationResult> migrateWallet(const std::string& name, const SecureString& passphrase) override
+ util::Result<WalletMigrationResult> migrateWallet(const std::string& name, const SecureString& passphrase, bool load_wallet) override
{
- auto res = wallet::MigrateLegacyToDescriptor(name, passphrase, m_context);
+ auto res = wallet::MigrateLegacyToDescriptor(name, passphrase, m_context, load_wallet);
if (!res) return util::Error{util::ErrorString(res)};
WalletMigrationResult out{
.wallet = MakeWallet(m_context, res->wallet),
Why this scored 19/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.