aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHennadii Stepanov <32963518+hebasto@users.noreply.github.com>2023-09-20 22:25:37 +0100
committerHennadii Stepanov <32963518+hebasto@users.noreply.github.com>2023-09-20 22:32:31 +0100
commit1d4846a8443be901b8a5deb0e357481af22838d0 (patch)
tree97798d559e47f142411b5c2d95d7b1a0ee4168e4
parent8247a8db6963d2116dc4697a3217d736c197f91d (diff)
parent48aae2cffeb91add75a70ac4d5075c38054452fa (diff)
downloadbitcoin-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.h13
-rw-r--r--src/qt/askpassphrasedialog.cpp3
-rw-r--r--src/qt/bitcoingui.cpp12
-rw-r--r--src/qt/bitcoingui.h2
-rw-r--r--src/qt/walletcontroller.cpp64
-rw-r--r--src/qt/walletcontroller.h22
-rw-r--r--src/wallet/interfaces.cpp13
-rw-r--r--src/wallet/wallet.cpp2
-rw-r--r--src/wallet/wallet.h1
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;