From 2acf773800b793b547450f5ed360d7822bde4215 Mon Sep 17 00:00:00 2001 From: Ava Chow Date: Mon, 26 May 2025 14:43:34 -0700 Subject: [PATCH 1/4] wallet, interfaces, gui: Expose load_after_restore parameter RestoreWallet has a load_after_restore parameter, expose this to callers using it through the wallet interface as well. --- src/interfaces/wallet.h | 2 +- src/qt/walletcontroller.cpp | 2 +- src/wallet/interfaces.cpp | 9 ++++----- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/interfaces/wallet.h b/src/interfaces/wallet.h index 94869aff5af..7385425cca0 100644 --- a/src/interfaces/wallet.h +++ b/src/interfaces/wallet.h @@ -328,7 +328,7 @@ class WalletLoader : public ChainClient virtual std::string getWalletDir() = 0; //! Restore backup wallet - virtual util::Result> restoreWallet(const fs::path& backup_file, const std::string& wallet_name, std::vector& warnings) = 0; + virtual util::Result> restoreWallet(const fs::path& backup_file, const std::string& wallet_name, std::vector& warnings, bool load_after_restore) = 0; //! Migrate a wallet virtual util::Result migrateWallet(const std::string& name, const SecureString& passphrase) = 0; diff --git a/src/qt/walletcontroller.cpp b/src/qt/walletcontroller.cpp index dd093e984a3..e75b92a2701 100644 --- a/src/qt/walletcontroller.cpp +++ b/src/qt/walletcontroller.cpp @@ -409,7 +409,7 @@ void RestoreWalletActivity::restore(const fs::path& backup_file, const std::stri tr("Restoring Wallet %1…").arg(name.toHtmlEscaped())); QTimer::singleShot(0, worker(), [this, backup_file, wallet_name] { - auto wallet{node().walletLoader().restoreWallet(backup_file, wallet_name, m_warning_message)}; + auto wallet{node().walletLoader().restoreWallet(backup_file, wallet_name, m_warning_message, /*load_after_restore=*/true)}; if (wallet) { m_wallet_model = m_wallet_controller->getOrCreateWallet(std::move(*wallet)); diff --git a/src/wallet/interfaces.cpp b/src/wallet/interfaces.cpp index 66b24d1ec2c..15268a1d154 100644 --- a/src/wallet/interfaces.cpp +++ b/src/wallet/interfaces.cpp @@ -608,16 +608,15 @@ class WalletLoaderImpl : public WalletLoader return util::Error{error}; } } - util::Result> restoreWallet(const fs::path& backup_file, const std::string& wallet_name, std::vector& warnings) override + util::Result> restoreWallet(const fs::path& backup_file, const std::string& wallet_name, std::vector& warnings, bool load_after_restore) override { DatabaseStatus status; bilingual_str error; - std::unique_ptr wallet{MakeWallet(m_context, RestoreWallet(m_context, backup_file, wallet_name, /*load_on_start=*/true, status, error, warnings))}; - if (wallet) { - return wallet; - } else { + std::unique_ptr wallet{MakeWallet(m_context, RestoreWallet(m_context, backup_file, wallet_name, /*load_on_start=*/true, status, error, warnings, load_after_restore))}; + if (!error.empty()) { return util::Error{error}; } + return wallet; } util::Result migrateWallet(const std::string& name, const SecureString& passphrase) override { From c13f1fbffb965d32723008615450ced57f0fce6c Mon Sep 17 00:00:00 2001 From: Ava Chow Date: Mon, 26 May 2025 14:46:12 -0700 Subject: [PATCH 2/4] gui: Move actual migration part of migrate() to its own function We will need to use the same migration code in a later commit, so first move it to a separate function. --- src/qt/walletcontroller.cpp | 35 ++++++++++++++++++++--------------- src/qt/walletcontroller.h | 1 + 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/src/qt/walletcontroller.cpp b/src/qt/walletcontroller.cpp index e75b92a2701..8c3d9eeabf0 100644 --- a/src/qt/walletcontroller.cpp +++ b/src/qt/walletcontroller.cpp @@ -439,22 +439,8 @@ void RestoreWalletActivity::finish() Q_EMIT finished(); } -void MigrateWalletActivity::migrate(const std::string& name) +void MigrateWalletActivity::do_migrate(const std::string& name) { - // Warn the user about migration - QMessageBox box(m_parent_widget); - box.setWindowTitle(tr("Migrate wallet")); - box.setText(tr("Are you sure you wish to migrate the wallet %1?").arg(GUIUtil::HtmlEscape(GUIUtil::WalletDisplayName(name)))); - box.setInformativeText(tr("Migrating the wallet will convert this wallet to one or more descriptor wallets. A new wallet backup will need to be made.\n" - "If this wallet contains any watchonly scripts, a new wallet will be created which contains those watchonly scripts.\n" - "If this wallet contains any solvable but not watched scripts, a different and new wallet will be created which contains those scripts.\n\n" - "The migration process will create a backup of the wallet before migrating. This backup file will be named " - "-.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.")); - box.setStandardButtons(QMessageBox::Yes|QMessageBox::Cancel); - box.setDefaultButton(QMessageBox::Yes); - if (box.exec() != QMessageBox::Yes) return; - SecureString passphrase; if (node().walletLoader().isEncrypted(name)) { // Get the passphrase for the wallet @@ -484,6 +470,25 @@ void MigrateWalletActivity::migrate(const std::string& name) }); } +void MigrateWalletActivity::migrate(const std::string& name) +{ + // Warn the user about migration + QMessageBox box(m_parent_widget); + box.setWindowTitle(tr("Migrate wallet")); + box.setText(tr("Are you sure you wish to migrate the wallet %1?").arg(GUIUtil::HtmlEscape(GUIUtil::WalletDisplayName(name)))); + box.setInformativeText(tr("Migrating the wallet will convert this wallet to one or more descriptor wallets. A new wallet backup will need to be made.\n" + "If this wallet contains any watchonly scripts, a new wallet will be created which contains those watchonly scripts.\n" + "If this wallet contains any solvable but not watched scripts, a different and new wallet will be created which contains those scripts.\n\n" + "The migration process will create a backup of the wallet before migrating. This backup file will be named " + "-.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.")); + box.setStandardButtons(QMessageBox::Yes|QMessageBox::Cancel); + box.setDefaultButton(QMessageBox::Yes); + if (box.exec() != QMessageBox::Yes) return; + + do_migrate(name); +} + void MigrateWalletActivity::finish() { if (!m_error_message.empty()) { diff --git a/src/qt/walletcontroller.h b/src/qt/walletcontroller.h index 4d2ba435392..a122c2f7183 100644 --- a/src/qt/walletcontroller.h +++ b/src/qt/walletcontroller.h @@ -195,6 +195,7 @@ class MigrateWalletActivity : public WalletControllerActivity private: QString m_success_message; + void do_migrate(const std::string& name); void finish(); }; From 114b52d605afa5a9f69c23ddb7527c7c749e6bf6 Mon Sep 17 00:00:00 2001 From: Ava Chow Date: Mon, 26 May 2025 14:47:03 -0700 Subject: [PATCH 3/4] gui: Add restore_and_migrate function to restore then migrate a wallet restore_and_migrate first restores a wallet file to the wallets directory in the expected layout, then it performs legacy to descriptor wallet migration on the restored wallet. --- src/qt/walletcontroller.cpp | 39 +++++++++++++++++++++++++++++++++++++ src/qt/walletcontroller.h | 1 + 2 files changed, 40 insertions(+) diff --git a/src/qt/walletcontroller.cpp b/src/qt/walletcontroller.cpp index 8c3d9eeabf0..0e2550f4489 100644 --- a/src/qt/walletcontroller.cpp +++ b/src/qt/walletcontroller.cpp @@ -489,6 +489,45 @@ void MigrateWalletActivity::migrate(const std::string& name) do_migrate(name); } +void MigrateWalletActivity::restore_and_migrate(const fs::path& path, const std::string& wallet_name) +{ + // Warn the user about migration + QMessageBox box(m_parent_widget); + box.setWindowTitle(tr("Restore and Migrate wallet")); + box.setText(tr("Are you sure you wish to restore the wallet file %1 to %2 and migrate it?").arg(GUIUtil::HtmlEscape(path), GUIUtil::HtmlEscape(GUIUtil::WalletDisplayName(wallet_name)))); + box.setInformativeText(tr("Restoring the wallet will copy the backup file to the wallets directory and place it in the standard " + "wallet directory layout. The original file will not be modified.\n\n" + "Migrating the wallet will convert the restored wallet to one or more descriptor wallets. A new wallet backup will need to be made.\n" + "If this wallet contains any watchonly scripts, a new wallet will be created which contains those watchonly scripts.\n" + "If this wallet contains any solvable but not watched scripts, a different and new wallet will be created which contains those scripts.\n\n" + "The migration process will create a backup of the wallet before migrating. This backup file will be named " + "-.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.")); + box.setStandardButtons(QMessageBox::Yes|QMessageBox::Cancel); + box.setDefaultButton(QMessageBox::Yes); + if (box.exec() != QMessageBox::Yes) return; + + showProgressDialog( + //: Title of progress window which is displayed when wallets are being restored. + tr("Restore Wallet"), + /*: Descriptive text of the restore wallets progress window which indicates to + the user that wallets are currently being restored.*/ + tr("Restoring Wallet %1…").arg(GUIUtil::HtmlEscape(GUIUtil::WalletDisplayName(wallet_name)))); + + QTimer::singleShot(0, worker(), [this, path, wallet_name] { + auto res{node().walletLoader().restoreWallet(path, wallet_name, m_warning_message, /*load_after_restore=*/false)}; + + if (!res) { + m_error_message = util::ErrorString(res); + QTimer::singleShot(0, this, &MigrateWalletActivity::finish); + return; + } + QTimer::singleShot(0, this, [this, wallet_name] { + do_migrate(wallet_name); + }); + }); +} + void MigrateWalletActivity::finish() { if (!m_error_message.empty()) { diff --git a/src/qt/walletcontroller.h b/src/qt/walletcontroller.h index a122c2f7183..e24970db328 100644 --- a/src/qt/walletcontroller.h +++ b/src/qt/walletcontroller.h @@ -187,6 +187,7 @@ class MigrateWalletActivity : public WalletControllerActivity public: MigrateWalletActivity(WalletController* wallet_controller, QWidget* parent) : WalletControllerActivity(wallet_controller, parent) {} + void restore_and_migrate(const fs::path& path, const std::string& wallet_name); void migrate(const std::string& path); Q_SIGNALS: From c02e8a376de441e5d0d3512c8496073ad28a4a26 Mon Sep 17 00:00:00 2001 From: Ava Chow Date: Mon, 26 May 2025 14:47:47 -0700 Subject: [PATCH 4/4] gui: Add a menu item to restore then migrate a wallet file Some users will have backups of a legacy wallet which cannot be restored due to being a legacy wallet, and therefore cannot be migrated from the GUI. This menu item allows such users to restore and migrate their wallets in a single action. --- src/qt/bitcoingui.cpp | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp index 9413356b412..6aa8bae0dd5 100644 --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.cpp @@ -485,6 +485,32 @@ void BitcoinGUI::createActions() QAction* action = m_migrate_wallet_menu->addAction(tr("No wallets available")); action->setEnabled(false); } + m_migrate_wallet_menu->addSeparator(); + QAction* restore_migrate_file_action = m_migrate_wallet_menu->addAction(tr("Restore and Migrate Wallet File...")); + restore_migrate_file_action->setEnabled(true); + + connect(restore_migrate_file_action, &QAction::triggered, [this] { + QString name_data_file = tr("Wallet Data"); + QString title_windows = tr("Restore and Migrate Wallet Backup"); + + QString backup_file = GUIUtil::getOpenFileName(this, title_windows, QString(), name_data_file + QLatin1String(" (*.dat)"), nullptr); + if (backup_file.isEmpty()) return; + + bool wallet_name_ok; + /*: Title of pop-up window shown when the user is attempting to + restore a wallet. */ + QString title = tr("Restore and Migrate Wallet"); + //: Label of the input field where the name of the wallet is entered. + QString label = tr("Wallet Name"); + QString wallet_name = QInputDialog::getText(this, title, label, QLineEdit::Normal, "", &wallet_name_ok); + if (!wallet_name_ok || wallet_name.isEmpty()) return; + + auto activity = new MigrateWalletActivity(m_wallet_controller, this); + connect(activity, &MigrateWalletActivity::migrated, this, &BitcoinGUI::setCurrentWallet); + connect(activity, &MigrateWalletActivity::migrated, rpcConsole, &RPCConsole::setCurrentWallet); + auto backup_file_path = fs::PathFromString(backup_file.toStdString()); + activity->restore_and_migrate(backup_file_path, wallet_name.toStdString()); + }); }); connect(m_mask_values_action, &QAction::toggled, this, &BitcoinGUI::setPrivacy); connect(m_mask_values_action, &QAction::toggled, this, &BitcoinGUI::enableHistoryAction);