This PR adds a menu item to restore a wallet from a backup file in the GUI. Currently this option exists only in RPC interface.
Motivation: It makes easier for non-technical users to restore backups.
Master | PR |
---|---|
This PR adds a menu item to restore a wallet from a backup file in the GUI. Currently this option exists only in RPC interface.
Motivation: It makes easier for non-technical users to restore backups.
Master | PR |
---|---|
366+ auto wallet_file = wallet_path / "wallet.dat";
367+
368+ fs::copy_file(backup_file, wallet_file, fs::copy_option::fail_if_exists);
369+
370+
371+ return LoadWallet(context, wallet_name, load_on_start, options, status, error, warnings);
nit:
0 auto wallet_file = wallet_path / "wallet.dat";
1 fs::copy_file(backup_file, wallet_file, fs::copy_option::fail_if_exists);
2
3 return LoadWallet(context, wallet_name, load_on_start, options, status, error, warnings);
Concept ACK
I am going to do a full tested review in some time. It would help to add the screenshots of the difference between the master branch and the PR branch in the description.
In the meantime, here’s one nit I caught.
317@@ -318,6 +318,8 @@ class WalletClient : public ChainClient
318 //! Load existing wallet.
319 virtual std::unique_ptr<Wallet> loadWallet(const std::string& name, bilingual_str& error, std::vector<bilingual_str>& warnings) = 0;
320
321+ virtual std::unique_ptr<Wallet> restoreWallet(const std::string& backup_file, const std::string& wallet_name, bilingual_str& error, std::vector<bilingual_str>& warnings) = 0;
0 //! Restore backup wallet
1 virtual std::unique_ptr<Wallet> restoreWallet(const std::string& backup_file, const std::string& wallet_name, bilingual_str& error, std::vector<bilingual_str>& warnings) = 0;
384+ QTimer::singleShot(0, worker(), [this, backup_file, wallet_name] {
385+ std::unique_ptr<interfaces::Wallet> wallet = node().walletClient().restoreWallet(backup_file, wallet_name, m_error_message, m_warning_message);
386+
387+ if (wallet) m_wallet_model = m_wallet_controller->getOrCreateWallet(std::move(wallet));
388+
389+ QTimer::singleShot(0, this, &RestoreWalletActivity::finish);
Standardise spacing:
0 std::unique_ptr<interfaces::Wallet> wallet = node().walletClient().restoreWallet(backup_file, wallet_name, m_error_message, m_warning_message);
1
2 if (wallet) m_wallet_model = m_wallet_controller->getOrCreateWallet(std::move(wallet));
3
4 QTimer::singleShot(0, this, &RestoreWalletActivity::finish);
Suggestion: How about using the auto keyword here? That would make the code simpler and more readable. @hebasto, what do you think about this suggestion?
0 auto wallet = node().walletClient().restoreWallet(backup_file, wallet_name, m_error_message, m_warning_message);
1
2 if (wallet) m_wallet_model = m_wallet_controller->getOrCreateWallet(std::move(wallet));
3
4 QTimer::singleShot(0, this, &RestoreWalletActivity::finish);
Tested Successfully on Ubuntu 20.04
I was able to successfully load the wallet from the wallet.dat file using the menu option introduced in this PR.
Master | PR |
---|---|
Tested that error message is working perfectly using the following patch:
0--- a/src/wallet/wallet.cpp
1+++ b/src/wallet/wallet.cpp
2@@ -357,7 +357,7 @@ std::shared_ptr<CWallet> RestoreWallet(WalletContext& context, const std::string
3 {
4 const fs::path wallet_path = fsbridge::AbsPathJoin(GetWalletDir(), fs::u8path(wallet_name));
5
6- if (fs::exists(wallet_path) || !TryCreateDirectories(wallet_path)) {
7+ if (!fs::exists(wallet_path) || !TryCreateDirectories(wallet_path)) {
8 error = Untranslated(strprintf("Failed to create database path '%s'. Database already exists.", fs::PathToString(wallet_path)));
9 status = DatabaseStatus::FAILED_ALREADY_EXISTS;
10 return nullptr;
Screenshot of error message:
Tested warning message using the following patch
0--- a/src/wallet/wallet.cpp
1+++ b/src/wallet/wallet.cpp
2@@ -85,7 +85,7 @@ static void UpdateWalletSetting(interfaces::Chain& chain,
3 std::vector<bilingual_str>& warnings)
4 {
5 if (!load_on_startup) return;
6- if (load_on_startup.value() && !AddWalletSetting(chain, wallet_name)) {
7+ if (load_on_startup.value() && AddWalletSetting(chain, wallet_name)) {
8 warnings.emplace_back(Untranslated("Wallet load on startup setting could not be updated, so wallet may not be loaded next node startup."));
9 } else if (!load_on_startup.value() && !RemoveWalletSetting(chain, wallet_name)) {
Screenshot of warning message:
There are some minor corrections and suggestions I found that might help improve this PR.
360@@ -360,6 +361,10 @@ void BitcoinGUI::createActions()
361 m_create_wallet_action->setEnabled(false);
362 m_create_wallet_action->setStatusTip(tr("Create a new wallet"));
363
364+ m_restore_wallet_action = new QAction(tr("Restore Wallet…"), this);
365+ m_restore_wallet_action->setEnabled(false);
366+ m_restore_wallet_action->setStatusTip(tr("Restore a wallet from a backup file"));
The strings being introduced need a translator comment. I have written documentation on how to construct these comments here: qml/translator-comments.md
The difference here would be that instead of following the qml style, you’d be looking at how to mark a comment through the qt widgets style.
For a multi-line comment, use /*: ... */
. Let me know if you need help creating the translator comments.
360@@ -360,6 +361,12 @@ void BitcoinGUI::createActions()
361 m_create_wallet_action->setEnabled(false);
362 m_create_wallet_action->setStatusTip(tr("Create a new wallet"));
363
364+ //: A menu item for Restore Wallet
0 //: Name of the menu item that restores wallet from a backup file.
448+ tr("Wallet Data File (*.dat)"), nullptr);
449+ if (backupFile.isEmpty()) return;
450+
451+ bool walletNameOk;
452+ //: Title of the Restore Wallet input dialog (where the wallet name is entered)
453+ //: Restore Wallet input dialog label
Changes since my last review:
The translator comment looks good and helps significantly clarify the meaning and purpose of each string. I have some suggestions for the translator comment, though, that you might consider, to improve upon these comments.
Changes since my last review:
I think this PR is functionally correct now. However, there are some formatting corrections I would like to suggest. (p.s. Sorry, I forgot to check formatting in my earlier reviews of this PR.)
To automatically correct formatting, you can use the following line of code:
0git diff -U0 HEAD~1.. | ./contrib/devtools/clang-format-diff.py -p1 -i -v
The differences I observed after running the above statement:
0diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp
1index f21c5048f..363b618d8 100644
2--- a/src/qt/bitcoingui.cpp
3+++ b/src/qt/bitcoingui.cpp
4@@ -442,10 +442,10 @@ void BitcoinGUI::createActions()
5 });
6 connect(m_restore_wallet_action, &QAction::triggered, [this] {
7 QString backupFile = GUIUtil::getOpenFileName(this,
8- //: The title for Restore Wallet File Windows
9- tr("Load Wallet Backup"), QString(),
10- //: The file extension for Restore Wallet File Windows
11- tr("Wallet Data File (*.dat)"), nullptr);
12+ //: The title for Restore Wallet File Windows
13+ tr("Load Wallet Backup"), QString(),
14+ //: The file extension for Restore Wallet File Windows
15+ tr("Wallet Data File (*.dat)"), nullptr);
16 if (backupFile.isEmpty()) return;
17
18 bool walletNameOk;
19diff --git a/src/qt/walletcontroller.cpp b/src/qt/walletcontroller.cpp
20index 38095ee74..81822d5e5 100644
21--- a/src/qt/walletcontroller.cpp
22+++ b/src/qt/walletcontroller.cpp
23@@ -381,8 +381,7 @@ void RestoreWalletActivity::restore(const std::string& backup_file, const std::s
24 tr("Restore Wallet"),
25 /*: Descriptive text of the restore wallets progress window which indicates to
26 the user that wallets are currently being restored.*/
27- tr("Restoring Wallet <b>%1</b>…").arg(name.toHtmlEscaped())
28- );
29+ tr("Restoring Wallet <b>%1</b>…").arg(name.toHtmlEscaped()));
30
31 QTimer::singleShot(0, worker(), [this, backup_file, wallet_name] {
32 std::unique_ptr<interfaces::Wallet> wallet = node().walletClient().restoreWallet(backup_file, wallet_name, m_error_message, m_warning_message);
33@@ -410,4 +409,3 @@ void RestoreWalletActivity::finish()
34
35 Q_EMIT finished();
36 }
548@@ -549,6 +549,14 @@ class WalletClientImpl : public WalletClient
549 options.require_existing = true;
550 return MakeWallet(m_context, LoadWallet(m_context, name, true /* load_on_start */, options, status, error, warnings));
551 }
552+ std::unique_ptr<Wallet> restoreWallet(const std::string& backup_file, const std::string& wallet_name, bilingual_str& error, std::vector<bilingual_str>& warnings) override
This duplicates new RPC functionality. Please first abstract the RPC code so GUI can share it instead of duplicating it.
Also, I think it would be better to handle error conditions nicer before exposing this to the GUI. For example, we should detect if the wallet is already present and error if so. If an error occurs, we should delete the copy-destination file we made.
d021bf9 modifies the restoredwallet()
RPC to reuse the wallet code and also deletes the wallet folder if the reload fails, as suggested in #471#pullrequestreview-819668069 .
A separate PR (https://github.com/bitcoin/bitcoin/pull/23721) has been opened in the Bitcoin repository to change files that are not GUI related, as suggested in #471 (review)
ACK e82a65423f03d52d30478e065383489e9969621c
reACK 8dd1f21cc014112d0449f7fb98248e2912e8b802
Changes since my last review:
backupFile
-> backup_file
walletClient()
-> walletLoader()
std::string
-> fs::path
. The following function’s code remained unchanged.I successfully compiled the updated PR, created a backup wallet, and restored it in GUI. This goes to show the PR is working perfectly.
p.s.: To check the diff between the older version of this PR and the current one, I used the following command:
0git range-diff 8c0bd871fcf6c5ff5851ccb18a7bc7554a0484b0..e82a65423f03d52d30478e065383489e9969621c 807169e10b4a18324356ed6ee4d69587b96a7c70..8dd1f21cc014112d0449f7fb98248e2912e8b802
Diff:
01: e82a65423 ! 1: 8dd1f21cc gui: Add Wallet Restore in the GUI
1 @@ src/qt/bitcoingui.cpp: void BitcoinGUI::createActions()
2 }
3 });
4 + connect(m_restore_wallet_action, &QAction::triggered, [this] {
5 -+ QString backupFile = GUIUtil::getOpenFileName(this,
6 ++ QString backup_file = GUIUtil::getOpenFileName(this,
7 + //: The title for Restore Wallet File Windows
8 + tr("Load Wallet Backup"), QString(),
9 + //: The file extension for Restore Wallet File Windows
10 + tr("Wallet Data File (*.dat)"), nullptr);
11 -+ if (backupFile.isEmpty()) return;
12 ++ if (backup_file.isEmpty()) return;
13 +
14 + bool walletNameOk;
15 + //: Title of the Restore Wallet input dialog (where the wallet name is entered)
16 @@ src/qt/bitcoingui.cpp: void BitcoinGUI::createActions()
17 +
18 + auto activity = new RestoreWalletActivity(m_wallet_controller, this);
19 + connect(activity, &RestoreWalletActivity::restored, this, &BitcoinGUI::setCurrentWallet);
20 -+ activity->restore(backupFile.toStdString(), walletName.toStdString());
21 ++
22 ++ auto backup_file_path = fs::PathFromString(backup_file.toStdString());
23 ++ activity->restore(backup_file_path, walletName.toStdString());
24 + });
25 connect(m_close_wallet_action, &QAction::triggered, [this] {
26 m_wallet_controller->closeWallet(walletFrame->currentWalletModel(), this);
27 @@ src/qt/walletcontroller.cpp: void LoadWalletsActivity::load()
28 +{
29 +}
30 +
31 -+void RestoreWalletActivity::restore(const std::string& backup_file, const std::string& wallet_name)
32 ++void RestoreWalletActivity::restore(const fs::path& backup_file, const std::string& wallet_name)
33 +{
34 + QString name = QString::fromStdString(wallet_name);
35 +
36 @@ src/qt/walletcontroller.cpp: void LoadWalletsActivity::load()
37 + tr("Restoring Wallet <b>%1</b>…").arg(name.toHtmlEscaped()));
38 +
39 + QTimer::singleShot(0, worker(), [this, backup_file, wallet_name] {
40 -+ std::unique_ptr<interfaces::Wallet> wallet = node().walletClient().restoreWallet(backup_file, wallet_name, m_error_message, m_warning_message);
41 ++ std::unique_ptr<interfaces::Wallet> wallet = node().walletLoader().restoreWallet(backup_file, wallet_name, m_error_message, m_warning_message);
42 +
43 + if (wallet) m_wallet_model = m_wallet_controller->getOrCreateWallet(std::move(wallet));
44 +
45 @@ src/qt/walletcontroller.h: public:
46 +public:
47 + RestoreWalletActivity(WalletController* wallet_controller, QWidget* parent_widget);
48 +
49 -+ void restore(const std::string& backup_file, const std::string& wallet_name);
50 ++ void restore(const fs::path& backup_file, const std::string& wallet_name);
51 +
52 +Q_SIGNALS:
53 + void restored(WalletModel* wallet_model);
Rebased. CI error is unrelated.
CI job has been restarted.
4@@ -5,6 +5,7 @@
5 #ifndef BITCOIN_QT_WALLETCONTROLLER_H
6 #define BITCOIN_QT_WALLETCONTROLLER_H
7
8+#include <fs.h>
To not add the dependency on the header, could replace it for a:
0namespace fs { class path; }
(The cpp already has access to the fs
file)
Tested and code reviewed e681634b
Found the following issue:
The restored
signal is being processed prior to the full execution of addWallet
(walletAdded
signal slot), which causes a mismatch where the GUI shows the frame of the recently loaded wallet without updating the wallet selector (the top right combo box) nor the window title.
It seems to be happening because QT lowers the addWallet
execution priority while is creating the new WalletView
object.
It can be fixed by making the restored
signal connection a Qt::QueuedConnection
type.
In other words, patch:
0diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp
1--- a/src/qt/bitcoingui.cpp (revision 34dff82864965126e35876c02c8158e06d652013)
2+++ b/src/qt/bitcoingui.cpp (date 1654004973424)
3@@ -433,7 +433,7 @@
4 if (!walletNameOk || walletName.isEmpty()) return;
5
6 auto activity = new RestoreWalletActivity(m_wallet_controller, this);
7- connect(activity, &RestoreWalletActivity::restored, this, &BitcoinGUI::setCurrentWallet);
8+ connect(activity, &RestoreWalletActivity::restored, this, &BitcoinGUI::setCurrentWallet, Qt::QueuedConnection);
9
10 auto backup_file_path = fs::PathFromString(backup_file.toStdString());
11 activity->restore(backup_file_path, walletName.toStdString());
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.
No conflicts as of last run.
418@@ -412,6 +419,25 @@ void BitcoinGUI::createActions()
419 action->setEnabled(false);
420 }
421 });
422+ connect(m_restore_wallet_action, &QAction::triggered, [this] {
423+ QString backup_file = GUIUtil::getOpenFileName(this,
424+ //: The title for Restore Wallet File Windows
425+ tr("Load Wallet Backup"), QString(),
426+ //: The file extension for Restore Wallet File Windows
427+ tr("Wallet Data File (*.dat)"), nullptr);
This line and this one https://github.com/bitcoin-core/gui/blob/c99f58a07ecc85291a140b12c71fd2a9870a005e/src/qt/walletview.cpp#L216 must be consistent. Although, have no preference which one is better.
And please separate untranslatable file extension from the translatable string.
425+ tr("Load Wallet Backup"), QString(),
426+ //: The file extension for Restore Wallet File Windows
427+ tr("Wallet Data File (*.dat)"), nullptr);
428+ if (backup_file.isEmpty()) return;
429+
430+ bool walletNameOk;
Please follow our naming convention:
0 bool wallet_name_ok;
427+ tr("Wallet Data File (*.dat)"), nullptr);
428+ if (backup_file.isEmpty()) return;
429+
430+ bool walletNameOk;
431+ //: Title of the Restore Wallet input dialog (where the wallet name is entered)
432+ QString walletName = QInputDialog::getText(this, tr("Restore Name"), tr("Wallet Name:"), QLineEdit::Normal, "", &walletNameOk);
Please follow our naming convention:
0 QString wallet_name = QInputDialog::getText(this, tr("Restore Name"), tr("Wallet Name:"), QLineEdit::Normal, "", &walletNameOk);
413+ }
414+
415+ if (m_wallet_model) Q_EMIT restored(m_wallet_model);
416+
417+ Q_EMIT finished();
418+}
Approach ACK c99f58a07ecc85291a140b12c71fd2a9870a005e.
Suggesting to apply clang-format-diff.py
to your commit(s).
Is it better to combine actions in a menu group:
0--- a/src/qt/bitcoingui.cpp
1+++ b/src/qt/bitcoingui.cpp
2@@ -473,12 +473,13 @@ void BitcoinGUI::createMenuBar()
3 {
4 file->addAction(m_create_wallet_action);
5 file->addAction(m_open_wallet_action);
6- file->addAction(m_restore_wallet_action);
7 file->addAction(m_close_wallet_action);
8 file->addAction(m_close_all_wallets_action);
9 file->addSeparator();
10- file->addAction(openAction);
11 file->addAction(backupWalletAction);
12+ file->addAction(m_restore_wallet_action);
13+ file->addSeparator();
14+ file->addAction(openAction);
15 file->addAction(signMessageAction);
16 file->addAction(verifyMessageAction);
17 file->addAction(m_load_psbt_action);
?
Co-authored-by: Shashwat Vangani <85434418+shaavan@users.noreply.github.com>
Co-authored-by: furszy <matiasfurszyfer@protonmail.com>
Changes since my last review:
walletName
→ wallet_name
walletNameOk
→ wallet_name_ok
Qt::QueuedConnection
, which is explained in this comment #471#pullrequestreview-989631968). On a side note, a very nice catch of bug indeed, @furszy!path.h
at the beginning of walletcontroller.h
to facilitate using fs::path
.I successfully compiled this PR on Ubuntu 22.04
with Qt version 5.15.3
. I was able to test that the backup wallet and restore wallet are visually grouped in the GUI.
Screenshot:
I agree with @jarolrod. Adding release notes would be a good idea!
429+ QString backup_file = GUIUtil::getOpenFileName(this, title_windows, QString(), name_data_file + QLatin1String(" (*.dat)"), nullptr);
430+ if (backup_file.isEmpty()) return;
431+
432+ bool wallet_name_ok;
433+ //: Title of the Restore Wallet input dialog (where the wallet name is entered)
434+ QString wallet_name = QInputDialog::getText(this, tr("Restore Name"), tr("Wallet Name:"), QLineEdit::Normal, "", &wallet_name_ok);
tr()
could not work as expected.