Skip to content

Commit 1d4846a

Browse files
committed
Merge bitcoin-core/gui#738: Add menu option to migrate a wallet
48aae2c gui: Add File > Migrate Wallet (Andrew Chow) 577be88 gui: Optionally return passphrase after unlocking (Andrew Chow) 5b3a85b 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 48aae2c pablomartin4btc: tACK 48aae2c hebasto: ACK 48aae2c Tree-SHA512: 2d02b1e85e7d6cfbf503f417f150cdaa0c63822942e9a6fe28c0ad3e7f40a957bb01a375c909a60432dc600e84574881aa446c7ec983b56f0bb23f07ef15de54
2 parents 8247a8d + 48aae2c commit 1d4846a

9 files changed

+131
-1
lines changed

src/interfaces/wallet.h

+13
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ struct WalletBalances;
5151
struct WalletTx;
5252
struct WalletTxOut;
5353
struct WalletTxStatus;
54+
struct WalletMigrationResult;
5455

5556
using WalletOrderForm = std::vector<std::pair<std::string, std::string>>;
5657
using WalletValueMap = std::map<std::string, std::string>;
@@ -333,6 +334,9 @@ class WalletLoader : public ChainClient
333334
//! Restore backup wallet
334335
virtual util::Result<std::unique_ptr<Wallet>> restoreWallet(const fs::path& backup_file, const std::string& wallet_name, std::vector<bilingual_str>& warnings) = 0;
335336

337+
//! Migrate a wallet
338+
virtual util::Result<WalletMigrationResult> migrateWallet(const std::string& name, const SecureString& passphrase) = 0;
339+
336340
//! Return available wallets in wallet directory.
337341
virtual std::vector<std::string> listWalletDir() = 0;
338342

@@ -424,6 +428,15 @@ struct WalletTxOut
424428
bool is_spent = false;
425429
};
426430

431+
//! Migrated wallet info
432+
struct WalletMigrationResult
433+
{
434+
std::unique_ptr<Wallet> wallet;
435+
std::optional<std::string> watchonly_wallet_name;
436+
std::optional<std::string> solvables_wallet_name;
437+
fs::path backup_path;
438+
};
439+
427440
//! Return implementation of Wallet interface. This function is defined in
428441
//! dummywallet.cpp and throws if the wallet component is not compiled.
429442
std::unique_ptr<Wallet> MakeWallet(wallet::WalletContext& context, const std::shared_ptr<wallet::CWallet>& wallet);

src/qt/askpassphrasedialog.cpp

+3
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,9 @@ void AskPassphraseDialog::accept()
167167
"passphrase to avoid this issue in the future."));
168168
}
169169
} else {
170+
if (m_passphrase_out) {
171+
m_passphrase_out->assign(oldpass);
172+
}
170173
QDialog::accept(); // Success
171174
}
172175
} catch (const std::runtime_error& e) {

src/qt/bitcoingui.cpp

+12
Original file line numberDiff line numberDiff line change
@@ -359,6 +359,10 @@ void BitcoinGUI::createActions()
359359
m_close_all_wallets_action = new QAction(tr("Close All Wallets…"), this);
360360
m_close_all_wallets_action->setStatusTip(tr("Close all wallets"));
361361

362+
m_migrate_wallet_action = new QAction(tr("Migrate Wallet"), this);
363+
m_migrate_wallet_action->setEnabled(false);
364+
m_migrate_wallet_action->setStatusTip(tr("Migrate a wallet"));
365+
362366
showHelpMessageAction = new QAction(tr("&Command-line options"), this);
363367
showHelpMessageAction->setMenuRole(QAction::NoRole);
364368
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()
459463
connect(m_close_all_wallets_action, &QAction::triggered, [this] {
460464
m_wallet_controller->closeAllWallets(this);
461465
});
466+
connect(m_migrate_wallet_action, &QAction::triggered, [this] {
467+
auto activity = new MigrateWalletActivity(m_wallet_controller, this);
468+
connect(activity, &MigrateWalletActivity::migrated, this, &BitcoinGUI::setCurrentWallet);
469+
activity->migrate(walletFrame->currentWalletModel());
470+
});
462471
connect(m_mask_values_action, &QAction::toggled, this, &BitcoinGUI::setPrivacy);
463472
connect(m_mask_values_action, &QAction::toggled, this, &BitcoinGUI::enableHistoryAction);
464473
}
@@ -486,6 +495,7 @@ void BitcoinGUI::createMenuBar()
486495
file->addAction(m_open_wallet_action);
487496
file->addAction(m_close_wallet_action);
488497
file->addAction(m_close_all_wallets_action);
498+
file->addAction(m_migrate_wallet_action);
489499
file->addSeparator();
490500
file->addAction(backupWalletAction);
491501
file->addAction(m_restore_wallet_action);
@@ -770,6 +780,7 @@ void BitcoinGUI::setCurrentWallet(WalletModel* wallet_model)
770780
}
771781
}
772782
updateWindowTitle();
783+
m_migrate_wallet_action->setEnabled(wallet_model->wallet().isLegacy());
773784
}
774785

775786
void BitcoinGUI::setCurrentWalletBySelectorIndex(int index)
@@ -803,6 +814,7 @@ void BitcoinGUI::setWalletActionsEnabled(bool enabled)
803814
openAction->setEnabled(enabled);
804815
m_close_wallet_action->setEnabled(enabled);
805816
m_close_all_wallets_action->setEnabled(enabled);
817+
m_migrate_wallet_action->setEnabled(enabled);
806818
}
807819

808820
void BitcoinGUI::createTrayIcon()

src/qt/bitcoingui.h

+2
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,8 @@ class BitcoinGUI : public QMainWindow
163163
QAction* m_wallet_selector_label_action = nullptr;
164164
QAction* m_wallet_selector_action = nullptr;
165165
QAction* m_mask_values_action{nullptr};
166+
QAction* m_migrate_wallet_action{nullptr};
167+
QMenu* m_migrate_wallet_menu{nullptr};
166168

167169
QLabel *m_wallet_selector_label = nullptr;
168170
QComboBox* m_wallet_selector = nullptr;

src/qt/walletcontroller.cpp

+64
Original file line numberDiff line numberDiff line change
@@ -435,3 +435,67 @@ void RestoreWalletActivity::finish()
435435

436436
Q_EMIT finished();
437437
}
438+
439+
void MigrateWalletActivity::migrate(WalletModel* wallet_model)
440+
{
441+
// Warn the user about migration
442+
QMessageBox box(m_parent_widget);
443+
box.setWindowTitle(tr("Migrate wallet"));
444+
box.setText(tr("Are you sure you wish to migrate the wallet <i>%1</i>?").arg(GUIUtil::HtmlEscape(wallet_model->getDisplayName())));
445+
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"
446+
"If this wallet contains any watchonly scripts, a new wallet will be created which contains those watchonly scripts.\n"
447+
"If this wallet contains any solvable but not watched scripts, a different and new wallet will be created which contains those scripts.\n\n"
448+
"The migration process will create a backup of the wallet before migrating. This backup file will be named "
449+
"<wallet name>-<timestamp>.legacy.bak and can be found in the directory for this wallet. In the event of "
450+
"an incorrect migration, the backup can be restored with the \"Restore Wallet\" functionality."));
451+
box.setStandardButtons(QMessageBox::Yes|QMessageBox::Cancel);
452+
box.setDefaultButton(QMessageBox::Yes);
453+
if (box.exec() != QMessageBox::Yes) return;
454+
455+
// Get the passphrase if it is encrypted regardless of it is locked or unlocked. We need the passphrase itself.
456+
SecureString passphrase;
457+
WalletModel::EncryptionStatus enc_status = wallet_model->getEncryptionStatus();
458+
if (enc_status == WalletModel::EncryptionStatus::Locked || enc_status == WalletModel::EncryptionStatus::Unlocked) {
459+
AskPassphraseDialog dlg(AskPassphraseDialog::Unlock, m_parent_widget, &passphrase);
460+
dlg.setModel(wallet_model);
461+
dlg.exec();
462+
}
463+
464+
// GUI needs to remove the wallet so that it can actually be unloaded by migration
465+
const std::string name = wallet_model->wallet().getWalletName();
466+
m_wallet_controller->removeAndDeleteWallet(wallet_model);
467+
468+
showProgressDialog(tr("Migrate Wallet"), tr("Migrating Wallet <b>%1</b>…").arg(GUIUtil::HtmlEscape(name)));
469+
470+
QTimer::singleShot(0, worker(), [this, name, passphrase] {
471+
auto res{node().walletLoader().migrateWallet(name, passphrase)};
472+
473+
if (res) {
474+
m_success_message = tr("The wallet '%1' was migrated successfully.").arg(GUIUtil::HtmlEscape(res->wallet->getWalletName()));
475+
if (res->watchonly_wallet_name) {
476+
m_success_message += tr(" Watchonly scripts have been migrated to a new wallet named '%1'.").arg(GUIUtil::HtmlEscape(res->watchonly_wallet_name.value()));
477+
}
478+
if (res->solvables_wallet_name) {
479+
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()));
480+
}
481+
m_wallet_model = m_wallet_controller->getOrCreateWallet(std::move(res->wallet));
482+
} else {
483+
m_error_message = util::ErrorString(res);
484+
}
485+
486+
QTimer::singleShot(0, this, &MigrateWalletActivity::finish);
487+
});
488+
}
489+
490+
void MigrateWalletActivity::finish()
491+
{
492+
if (!m_error_message.empty()) {
493+
QMessageBox::critical(m_parent_widget, tr("Migration failed"), QString::fromStdString(m_error_message.translated));
494+
} else {
495+
QMessageBox::information(m_parent_widget, tr("Migration Successful"), m_success_message);
496+
}
497+
498+
if (m_wallet_model) Q_EMIT migrated(m_wallet_model);
499+
500+
Q_EMIT finished();
501+
}

src/qt/walletcontroller.h

+22
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ class path;
4040
class AskPassphraseDialog;
4141
class CreateWalletActivity;
4242
class CreateWalletDialog;
43+
class MigrateWalletActivity;
4344
class OpenWalletActivity;
4445
class WalletControllerActivity;
4546

@@ -65,6 +66,8 @@ class WalletController : public QObject
6566
void closeWallet(WalletModel* wallet_model, QWidget* parent = nullptr);
6667
void closeAllWallets(QWidget* parent = nullptr);
6768

69+
void migrateWallet(WalletModel* wallet_model, QWidget* parent = nullptr);
70+
6871
Q_SIGNALS:
6972
void walletAdded(WalletModel* wallet_model);
7073
void walletRemoved(WalletModel* wallet_model);
@@ -83,6 +86,7 @@ class WalletController : public QObject
8386
std::unique_ptr<interfaces::Handler> m_handler_load_wallet;
8487

8588
friend class WalletControllerActivity;
89+
friend class MigrateWalletActivity;
8690
};
8791

8892
class WalletControllerActivity : public QObject
@@ -175,4 +179,22 @@ class RestoreWalletActivity : public WalletControllerActivity
175179
void finish();
176180
};
177181

182+
class MigrateWalletActivity : public WalletControllerActivity
183+
{
184+
Q_OBJECT
185+
186+
public:
187+
MigrateWalletActivity(WalletController* wallet_controller, QWidget* parent) : WalletControllerActivity(wallet_controller, parent) {}
188+
189+
void migrate(WalletModel* wallet_model);
190+
191+
Q_SIGNALS:
192+
void migrated(WalletModel* wallet_model);
193+
194+
private:
195+
QString m_success_message;
196+
197+
void finish();
198+
};
199+
178200
#endif // BITCOIN_QT_WALLETCONTROLLER_H

src/wallet/interfaces.cpp

+13
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ using interfaces::Wallet;
4141
using interfaces::WalletAddress;
4242
using interfaces::WalletBalances;
4343
using interfaces::WalletLoader;
44+
using interfaces::WalletMigrationResult;
4445
using interfaces::WalletOrderForm;
4546
using interfaces::WalletTx;
4647
using interfaces::WalletTxOut;
@@ -630,6 +631,18 @@ class WalletLoaderImpl : public WalletLoader
630631
return util::Error{error};
631632
}
632633
}
634+
util::Result<WalletMigrationResult> migrateWallet(const std::string& name, const SecureString& passphrase) override
635+
{
636+
auto res = wallet::MigrateLegacyToDescriptor(name, passphrase, m_context);
637+
if (!res) return util::Error{util::ErrorString(res)};
638+
WalletMigrationResult out{
639+
.wallet = MakeWallet(m_context, res->wallet),
640+
.watchonly_wallet_name = res->watchonly_wallet ? std::make_optional(res->watchonly_wallet->GetName()) : std::nullopt,
641+
.solvables_wallet_name = res->solvables_wallet ? std::make_optional(res->solvables_wallet->GetName()) : std::nullopt,
642+
.backup_path = res->backup_path,
643+
};
644+
return {std::move(out)}; // std::move to work around clang bug
645+
}
633646
std::string getWalletDir() override
634647
{
635648
return fs::PathToString(GetWalletDir());

src/wallet/wallet.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -4213,7 +4213,7 @@ util::Result<MigrationResult> MigrateLegacyToDescriptor(const std::string& walle
42134213
// Migration successful, unload the wallet locally, then reload it.
42144214
assert(local_wallet.use_count() == 1);
42154215
local_wallet.reset();
4216-
LoadWallet(context, wallet_name, /*load_on_start=*/std::nullopt, options, status, error, warnings);
4216+
res.wallet = LoadWallet(context, wallet_name, /*load_on_start=*/std::nullopt, options, status, error, warnings);
42174217
res.wallet_name = wallet_name;
42184218
} else {
42194219
// Migration failed, cleanup

src/wallet/wallet.h

+1
Original file line numberDiff line numberDiff line change
@@ -1087,6 +1087,7 @@ bool RemoveWalletSetting(interfaces::Chain& chain, const std::string& wallet_nam
10871087

10881088
struct MigrationResult {
10891089
std::string wallet_name;
1090+
std::shared_ptr<CWallet> wallet;
10901091
std::shared_ptr<CWallet> watchonly_wallet;
10911092
std::shared_ptr<CWallet> solvables_wallet;
10921093
fs::path backup_path;

0 commit comments

Comments
 (0)