diff options
author | Hennadii Stepanov <32963518+hebasto@users.noreply.github.com> | 2023-09-20 22:25:37 +0100 |
---|---|---|
committer | Hennadii Stepanov <32963518+hebasto@users.noreply.github.com> | 2023-09-20 22:32:31 +0100 |
commit | 1d4846a8443be901b8a5deb0e357481af22838d0 (patch) | |
tree | 97798d559e47f142411b5c2d95d7b1a0ee4168e4 | |
parent | 8247a8db6963d2116dc4697a3217d736c197f91d (diff) | |
parent | 48aae2cffeb91add75a70ac4d5075c38054452fa (diff) | |
download | bitcoin-1d4846a8443be901b8a5deb0e357481af22838d0.tar.xz |
Merge bitcoin-core/gui#738: Add menu option to migrate a wallet
48aae2cffeb91add75a70ac4d5075c38054452fa gui: Add File > Migrate Wallet (Andrew Chow)
577be889cd52fc2d896a5f39c66bc2cadb8622e4 gui: Optionally return passphrase after unlocking (Andrew Chow)
5b3a85b4c6ffd1f29a917d4c1af4bff6c0ea2ef5 interfaces, wallet: Expose migrate wallet (Andrew Chow)
Pull request description:
GUI users need to be able to migrate wallets without going to the RPC console.
ACKs for top commit:
jarolrod:
ACK 48aae2cffeb91add75a70ac4d5075c38054452fa
pablomartin4btc:
tACK 48aae2cffeb91add75a70ac4d5075c38054452fa
hebasto:
ACK 48aae2cffeb91add75a70ac4d5075c38054452fa
Tree-SHA512: 2d02b1e85e7d6cfbf503f417f150cdaa0c63822942e9a6fe28c0ad3e7f40a957bb01a375c909a60432dc600e84574881aa446c7ec983b56f0bb23f07ef15de54
-rw-r--r-- | src/interfaces/wallet.h | 13 | ||||
-rw-r--r-- | src/qt/askpassphrasedialog.cpp | 3 | ||||
-rw-r--r-- | src/qt/bitcoingui.cpp | 12 | ||||
-rw-r--r-- | src/qt/bitcoingui.h | 2 | ||||
-rw-r--r-- | src/qt/walletcontroller.cpp | 64 | ||||
-rw-r--r-- | src/qt/walletcontroller.h | 22 | ||||
-rw-r--r-- | src/wallet/interfaces.cpp | 13 | ||||
-rw-r--r-- | src/wallet/wallet.cpp | 2 | ||||
-rw-r--r-- | src/wallet/wallet.h | 1 |
9 files changed, 131 insertions, 1 deletions
diff --git a/src/interfaces/wallet.h b/src/interfaces/wallet.h index 9987681367..209516ebcb 100644 --- a/src/interfaces/wallet.h +++ b/src/interfaces/wallet.h @@ -51,6 +51,7 @@ struct WalletBalances; struct WalletTx; struct WalletTxOut; struct WalletTxStatus; +struct WalletMigrationResult; using WalletOrderForm = std::vector<std::pair<std::string, std::string>>; using WalletValueMap = std::map<std::string, std::string>; @@ -333,6 +334,9 @@ public: //! Restore backup wallet virtual util::Result<std::unique_ptr<Wallet>> restoreWallet(const fs::path& backup_file, const std::string& wallet_name, std::vector<bilingual_str>& warnings) = 0; + //! Migrate a wallet + virtual util::Result<WalletMigrationResult> migrateWallet(const std::string& name, const SecureString& passphrase) = 0; + //! Return available wallets in wallet directory. virtual std::vector<std::string> listWalletDir() = 0; @@ -424,6 +428,15 @@ struct WalletTxOut bool is_spent = false; }; +//! Migrated wallet info +struct WalletMigrationResult +{ + std::unique_ptr<Wallet> wallet; + std::optional<std::string> watchonly_wallet_name; + std::optional<std::string> solvables_wallet_name; + fs::path backup_path; +}; + //! Return implementation of Wallet interface. This function is defined in //! dummywallet.cpp and throws if the wallet component is not compiled. std::unique_ptr<Wallet> MakeWallet(wallet::WalletContext& context, const std::shared_ptr<wallet::CWallet>& wallet); diff --git a/src/qt/askpassphrasedialog.cpp b/src/qt/askpassphrasedialog.cpp index 0a96be038b..246dff0069 100644 --- a/src/qt/askpassphrasedialog.cpp +++ b/src/qt/askpassphrasedialog.cpp @@ -167,6 +167,9 @@ void AskPassphraseDialog::accept() "passphrase to avoid this issue in the future.")); } } else { + if (m_passphrase_out) { + m_passphrase_out->assign(oldpass); + } QDialog::accept(); // Success } } catch (const std::runtime_error& e) { diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp index b84cd02bda..2862dddb56 100644 --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.cpp @@ -359,6 +359,10 @@ void BitcoinGUI::createActions() m_close_all_wallets_action = new QAction(tr("Close All Wallets…"), this); m_close_all_wallets_action->setStatusTip(tr("Close all wallets")); + m_migrate_wallet_action = new QAction(tr("Migrate Wallet"), this); + m_migrate_wallet_action->setEnabled(false); + m_migrate_wallet_action->setStatusTip(tr("Migrate a wallet")); + showHelpMessageAction = new QAction(tr("&Command-line options"), this); showHelpMessageAction->setMenuRole(QAction::NoRole); showHelpMessageAction->setStatusTip(tr("Show the %1 help message to get a list with possible Bitcoin command-line options").arg(PACKAGE_NAME)); @@ -459,6 +463,11 @@ void BitcoinGUI::createActions() connect(m_close_all_wallets_action, &QAction::triggered, [this] { m_wallet_controller->closeAllWallets(this); }); + connect(m_migrate_wallet_action, &QAction::triggered, [this] { + auto activity = new MigrateWalletActivity(m_wallet_controller, this); + connect(activity, &MigrateWalletActivity::migrated, this, &BitcoinGUI::setCurrentWallet); + activity->migrate(walletFrame->currentWalletModel()); + }); connect(m_mask_values_action, &QAction::toggled, this, &BitcoinGUI::setPrivacy); connect(m_mask_values_action, &QAction::toggled, this, &BitcoinGUI::enableHistoryAction); } @@ -486,6 +495,7 @@ void BitcoinGUI::createMenuBar() file->addAction(m_open_wallet_action); file->addAction(m_close_wallet_action); file->addAction(m_close_all_wallets_action); + file->addAction(m_migrate_wallet_action); file->addSeparator(); file->addAction(backupWalletAction); file->addAction(m_restore_wallet_action); @@ -770,6 +780,7 @@ void BitcoinGUI::setCurrentWallet(WalletModel* wallet_model) } } updateWindowTitle(); + m_migrate_wallet_action->setEnabled(wallet_model->wallet().isLegacy()); } void BitcoinGUI::setCurrentWalletBySelectorIndex(int index) @@ -803,6 +814,7 @@ void BitcoinGUI::setWalletActionsEnabled(bool enabled) openAction->setEnabled(enabled); m_close_wallet_action->setEnabled(enabled); m_close_all_wallets_action->setEnabled(enabled); + m_migrate_wallet_action->setEnabled(enabled); } void BitcoinGUI::createTrayIcon() diff --git a/src/qt/bitcoingui.h b/src/qt/bitcoingui.h index 4e1f05255a..510561454b 100644 --- a/src/qt/bitcoingui.h +++ b/src/qt/bitcoingui.h @@ -163,6 +163,8 @@ private: QAction* m_wallet_selector_label_action = nullptr; QAction* m_wallet_selector_action = nullptr; QAction* m_mask_values_action{nullptr}; + QAction* m_migrate_wallet_action{nullptr}; + QMenu* m_migrate_wallet_menu{nullptr}; QLabel *m_wallet_selector_label = nullptr; QComboBox* m_wallet_selector = nullptr; diff --git a/src/qt/walletcontroller.cpp b/src/qt/walletcontroller.cpp index 8c8abf0e90..ca2fa2d672 100644 --- a/src/qt/walletcontroller.cpp +++ b/src/qt/walletcontroller.cpp @@ -435,3 +435,67 @@ void RestoreWalletActivity::finish() Q_EMIT finished(); } + +void MigrateWalletActivity::migrate(WalletModel* wallet_model) +{ + // 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 <i>%1</i>?").arg(GUIUtil::HtmlEscape(wallet_model->getDisplayName()))); + 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 " + "<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.")); + box.setStandardButtons(QMessageBox::Yes|QMessageBox::Cancel); + box.setDefaultButton(QMessageBox::Yes); + if (box.exec() != QMessageBox::Yes) return; + + // Get the passphrase if it is encrypted regardless of it is locked or unlocked. We need the passphrase itself. + SecureString passphrase; + WalletModel::EncryptionStatus enc_status = wallet_model->getEncryptionStatus(); + if (enc_status == WalletModel::EncryptionStatus::Locked || enc_status == WalletModel::EncryptionStatus::Unlocked) { + AskPassphraseDialog dlg(AskPassphraseDialog::Unlock, m_parent_widget, &passphrase); + dlg.setModel(wallet_model); + dlg.exec(); + } + + // GUI needs to remove the wallet so that it can actually be unloaded by migration + const std::string name = wallet_model->wallet().getWalletName(); + m_wallet_controller->removeAndDeleteWallet(wallet_model); + + 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)}; + + if (res) { + m_success_message = tr("The wallet '%1' was migrated successfully.").arg(GUIUtil::HtmlEscape(res->wallet->getWalletName())); + if (res->watchonly_wallet_name) { + m_success_message += tr(" Watchonly scripts have been migrated to a new wallet named '%1'.").arg(GUIUtil::HtmlEscape(res->watchonly_wallet_name.value())); + } + if (res->solvables_wallet_name) { + m_success_message += tr(" Solvable but not watched scripts have been migrated to a new wallet named '%1'.").arg(GUIUtil::HtmlEscape(res->solvables_wallet_name.value())); + } + m_wallet_model = m_wallet_controller->getOrCreateWallet(std::move(res->wallet)); + } else { + m_error_message = util::ErrorString(res); + } + + QTimer::singleShot(0, this, &MigrateWalletActivity::finish); + }); +} + +void MigrateWalletActivity::finish() +{ + if (!m_error_message.empty()) { + QMessageBox::critical(m_parent_widget, tr("Migration failed"), QString::fromStdString(m_error_message.translated)); + } else { + QMessageBox::information(m_parent_widget, tr("Migration Successful"), m_success_message); + } + + if (m_wallet_model) Q_EMIT migrated(m_wallet_model); + + Q_EMIT finished(); +} diff --git a/src/qt/walletcontroller.h b/src/qt/walletcontroller.h index 8ec0243890..c595ba998d 100644 --- a/src/qt/walletcontroller.h +++ b/src/qt/walletcontroller.h @@ -40,6 +40,7 @@ class path; class AskPassphraseDialog; class CreateWalletActivity; class CreateWalletDialog; +class MigrateWalletActivity; class OpenWalletActivity; class WalletControllerActivity; @@ -65,6 +66,8 @@ public: void closeWallet(WalletModel* wallet_model, QWidget* parent = nullptr); void closeAllWallets(QWidget* parent = nullptr); + void migrateWallet(WalletModel* wallet_model, QWidget* parent = nullptr); + Q_SIGNALS: void walletAdded(WalletModel* wallet_model); void walletRemoved(WalletModel* wallet_model); @@ -83,6 +86,7 @@ private: std::unique_ptr<interfaces::Handler> m_handler_load_wallet; friend class WalletControllerActivity; + friend class MigrateWalletActivity; }; class WalletControllerActivity : public QObject @@ -175,4 +179,22 @@ private: void finish(); }; +class MigrateWalletActivity : public WalletControllerActivity +{ + Q_OBJECT + +public: + MigrateWalletActivity(WalletController* wallet_controller, QWidget* parent) : WalletControllerActivity(wallet_controller, parent) {} + + void migrate(WalletModel* wallet_model); + +Q_SIGNALS: + void migrated(WalletModel* wallet_model); + +private: + QString m_success_message; + + void finish(); +}; + #endif // BITCOIN_QT_WALLETCONTROLLER_H diff --git a/src/wallet/interfaces.cpp b/src/wallet/interfaces.cpp index 5f2aee6923..089462c05a 100644 --- a/src/wallet/interfaces.cpp +++ b/src/wallet/interfaces.cpp @@ -41,6 +41,7 @@ using interfaces::Wallet; using interfaces::WalletAddress; using interfaces::WalletBalances; using interfaces::WalletLoader; +using interfaces::WalletMigrationResult; using interfaces::WalletOrderForm; using interfaces::WalletTx; using interfaces::WalletTxOut; @@ -630,6 +631,18 @@ public: return util::Error{error}; } } + util::Result<WalletMigrationResult> migrateWallet(const std::string& name, const SecureString& passphrase) override + { + auto res = wallet::MigrateLegacyToDescriptor(name, passphrase, m_context); + if (!res) return util::Error{util::ErrorString(res)}; + WalletMigrationResult out{ + .wallet = MakeWallet(m_context, res->wallet), + .watchonly_wallet_name = res->watchonly_wallet ? std::make_optional(res->watchonly_wallet->GetName()) : std::nullopt, + .solvables_wallet_name = res->solvables_wallet ? std::make_optional(res->solvables_wallet->GetName()) : std::nullopt, + .backup_path = res->backup_path, + }; + return {std::move(out)}; // std::move to work around clang bug + } std::string getWalletDir() override { return fs::PathToString(GetWalletDir()); diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 3622b00c77..2459908419 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -4213,7 +4213,7 @@ util::Result<MigrationResult> MigrateLegacyToDescriptor(const std::string& walle // Migration successful, unload the wallet locally, then reload it. assert(local_wallet.use_count() == 1); local_wallet.reset(); - LoadWallet(context, wallet_name, /*load_on_start=*/std::nullopt, options, status, error, warnings); + res.wallet = LoadWallet(context, wallet_name, /*load_on_start=*/std::nullopt, options, status, error, warnings); res.wallet_name = wallet_name; } else { // Migration failed, cleanup diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 97d06641a7..5adb8b6e27 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -1087,6 +1087,7 @@ bool RemoveWalletSetting(interfaces::Chain& chain, const std::string& wallet_nam struct MigrationResult { std::string wallet_name; + std::shared_ptr<CWallet> wallet; std::shared_ptr<CWallet> watchonly_wallet; std::shared_ptr<CWallet> solvables_wallet; fs::path backup_path; |