mirror of
https://github.com/Retropex/bitcoin.git
synced 2025-05-13 11:40:42 +02:00
Merge bitcoin-core/gui#738: Add menu option to migrate a wallet
48aae2cffe
gui: Add File > Migrate Wallet (Andrew Chow)577be889cd
gui: Optionally return passphrase after unlocking (Andrew Chow)5b3a85b4c6
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: ACK48aae2cffe
pablomartin4btc: tACK48aae2cffe
hebasto: ACK48aae2cffe
Tree-SHA512: 2d02b1e85e7d6cfbf503f417f150cdaa0c63822942e9a6fe28c0ad3e7f40a957bb01a375c909a60432dc600e84574881aa446c7ec983b56f0bb23f07ef15de54
This commit is contained in:
commit
1d4846a844
@ -51,6 +51,7 @@ struct WalletBalances;
|
|||||||
struct WalletTx;
|
struct WalletTx;
|
||||||
struct WalletTxOut;
|
struct WalletTxOut;
|
||||||
struct WalletTxStatus;
|
struct WalletTxStatus;
|
||||||
|
struct WalletMigrationResult;
|
||||||
|
|
||||||
using WalletOrderForm = std::vector<std::pair<std::string, std::string>>;
|
using WalletOrderForm = std::vector<std::pair<std::string, std::string>>;
|
||||||
using WalletValueMap = std::map<std::string, std::string>;
|
using WalletValueMap = std::map<std::string, std::string>;
|
||||||
@ -333,6 +334,9 @@ public:
|
|||||||
//! Restore backup wallet
|
//! 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;
|
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.
|
//! Return available wallets in wallet directory.
|
||||||
virtual std::vector<std::string> listWalletDir() = 0;
|
virtual std::vector<std::string> listWalletDir() = 0;
|
||||||
|
|
||||||
@ -424,6 +428,15 @@ struct WalletTxOut
|
|||||||
bool is_spent = false;
|
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
|
//! Return implementation of Wallet interface. This function is defined in
|
||||||
//! dummywallet.cpp and throws if the wallet component is not compiled.
|
//! 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);
|
std::unique_ptr<Wallet> MakeWallet(wallet::WalletContext& context, const std::shared_ptr<wallet::CWallet>& wallet);
|
||||||
|
@ -167,6 +167,9 @@ void AskPassphraseDialog::accept()
|
|||||||
"passphrase to avoid this issue in the future."));
|
"passphrase to avoid this issue in the future."));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
if (m_passphrase_out) {
|
||||||
|
m_passphrase_out->assign(oldpass);
|
||||||
|
}
|
||||||
QDialog::accept(); // Success
|
QDialog::accept(); // Success
|
||||||
}
|
}
|
||||||
} catch (const std::runtime_error& e) {
|
} catch (const std::runtime_error& e) {
|
||||||
|
@ -359,6 +359,10 @@ void BitcoinGUI::createActions()
|
|||||||
m_close_all_wallets_action = new QAction(tr("Close All Wallets…"), this);
|
m_close_all_wallets_action = new QAction(tr("Close All Wallets…"), this);
|
||||||
m_close_all_wallets_action->setStatusTip(tr("Close all wallets"));
|
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 = new QAction(tr("&Command-line options"), this);
|
||||||
showHelpMessageAction->setMenuRole(QAction::NoRole);
|
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));
|
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] {
|
connect(m_close_all_wallets_action, &QAction::triggered, [this] {
|
||||||
m_wallet_controller->closeAllWallets(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::setPrivacy);
|
||||||
connect(m_mask_values_action, &QAction::toggled, this, &BitcoinGUI::enableHistoryAction);
|
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_open_wallet_action);
|
||||||
file->addAction(m_close_wallet_action);
|
file->addAction(m_close_wallet_action);
|
||||||
file->addAction(m_close_all_wallets_action);
|
file->addAction(m_close_all_wallets_action);
|
||||||
|
file->addAction(m_migrate_wallet_action);
|
||||||
file->addSeparator();
|
file->addSeparator();
|
||||||
file->addAction(backupWalletAction);
|
file->addAction(backupWalletAction);
|
||||||
file->addAction(m_restore_wallet_action);
|
file->addAction(m_restore_wallet_action);
|
||||||
@ -770,6 +780,7 @@ void BitcoinGUI::setCurrentWallet(WalletModel* wallet_model)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
updateWindowTitle();
|
updateWindowTitle();
|
||||||
|
m_migrate_wallet_action->setEnabled(wallet_model->wallet().isLegacy());
|
||||||
}
|
}
|
||||||
|
|
||||||
void BitcoinGUI::setCurrentWalletBySelectorIndex(int index)
|
void BitcoinGUI::setCurrentWalletBySelectorIndex(int index)
|
||||||
@ -803,6 +814,7 @@ void BitcoinGUI::setWalletActionsEnabled(bool enabled)
|
|||||||
openAction->setEnabled(enabled);
|
openAction->setEnabled(enabled);
|
||||||
m_close_wallet_action->setEnabled(enabled);
|
m_close_wallet_action->setEnabled(enabled);
|
||||||
m_close_all_wallets_action->setEnabled(enabled);
|
m_close_all_wallets_action->setEnabled(enabled);
|
||||||
|
m_migrate_wallet_action->setEnabled(enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
void BitcoinGUI::createTrayIcon()
|
void BitcoinGUI::createTrayIcon()
|
||||||
|
@ -163,6 +163,8 @@ private:
|
|||||||
QAction* m_wallet_selector_label_action = nullptr;
|
QAction* m_wallet_selector_label_action = nullptr;
|
||||||
QAction* m_wallet_selector_action = nullptr;
|
QAction* m_wallet_selector_action = nullptr;
|
||||||
QAction* m_mask_values_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;
|
QLabel *m_wallet_selector_label = nullptr;
|
||||||
QComboBox* m_wallet_selector = nullptr;
|
QComboBox* m_wallet_selector = nullptr;
|
||||||
|
@ -435,3 +435,67 @@ void RestoreWalletActivity::finish()
|
|||||||
|
|
||||||
Q_EMIT finished();
|
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();
|
||||||
|
}
|
||||||
|
@ -40,6 +40,7 @@ class path;
|
|||||||
class AskPassphraseDialog;
|
class AskPassphraseDialog;
|
||||||
class CreateWalletActivity;
|
class CreateWalletActivity;
|
||||||
class CreateWalletDialog;
|
class CreateWalletDialog;
|
||||||
|
class MigrateWalletActivity;
|
||||||
class OpenWalletActivity;
|
class OpenWalletActivity;
|
||||||
class WalletControllerActivity;
|
class WalletControllerActivity;
|
||||||
|
|
||||||
@ -65,6 +66,8 @@ public:
|
|||||||
void closeWallet(WalletModel* wallet_model, QWidget* parent = nullptr);
|
void closeWallet(WalletModel* wallet_model, QWidget* parent = nullptr);
|
||||||
void closeAllWallets(QWidget* parent = nullptr);
|
void closeAllWallets(QWidget* parent = nullptr);
|
||||||
|
|
||||||
|
void migrateWallet(WalletModel* wallet_model, QWidget* parent = nullptr);
|
||||||
|
|
||||||
Q_SIGNALS:
|
Q_SIGNALS:
|
||||||
void walletAdded(WalletModel* wallet_model);
|
void walletAdded(WalletModel* wallet_model);
|
||||||
void walletRemoved(WalletModel* wallet_model);
|
void walletRemoved(WalletModel* wallet_model);
|
||||||
@ -83,6 +86,7 @@ private:
|
|||||||
std::unique_ptr<interfaces::Handler> m_handler_load_wallet;
|
std::unique_ptr<interfaces::Handler> m_handler_load_wallet;
|
||||||
|
|
||||||
friend class WalletControllerActivity;
|
friend class WalletControllerActivity;
|
||||||
|
friend class MigrateWalletActivity;
|
||||||
};
|
};
|
||||||
|
|
||||||
class WalletControllerActivity : public QObject
|
class WalletControllerActivity : public QObject
|
||||||
@ -175,4 +179,22 @@ private:
|
|||||||
void finish();
|
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
|
#endif // BITCOIN_QT_WALLETCONTROLLER_H
|
||||||
|
@ -41,6 +41,7 @@ using interfaces::Wallet;
|
|||||||
using interfaces::WalletAddress;
|
using interfaces::WalletAddress;
|
||||||
using interfaces::WalletBalances;
|
using interfaces::WalletBalances;
|
||||||
using interfaces::WalletLoader;
|
using interfaces::WalletLoader;
|
||||||
|
using interfaces::WalletMigrationResult;
|
||||||
using interfaces::WalletOrderForm;
|
using interfaces::WalletOrderForm;
|
||||||
using interfaces::WalletTx;
|
using interfaces::WalletTx;
|
||||||
using interfaces::WalletTxOut;
|
using interfaces::WalletTxOut;
|
||||||
@ -630,6 +631,18 @@ public:
|
|||||||
return util::Error{error};
|
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
|
std::string getWalletDir() override
|
||||||
{
|
{
|
||||||
return fs::PathToString(GetWalletDir());
|
return fs::PathToString(GetWalletDir());
|
||||||
|
@ -4213,7 +4213,7 @@ util::Result<MigrationResult> MigrateLegacyToDescriptor(const std::string& walle
|
|||||||
// Migration successful, unload the wallet locally, then reload it.
|
// Migration successful, unload the wallet locally, then reload it.
|
||||||
assert(local_wallet.use_count() == 1);
|
assert(local_wallet.use_count() == 1);
|
||||||
local_wallet.reset();
|
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;
|
res.wallet_name = wallet_name;
|
||||||
} else {
|
} else {
|
||||||
// Migration failed, cleanup
|
// Migration failed, cleanup
|
||||||
|
@ -1087,6 +1087,7 @@ bool RemoveWalletSetting(interfaces::Chain& chain, const std::string& wallet_nam
|
|||||||
|
|
||||||
struct MigrationResult {
|
struct MigrationResult {
|
||||||
std::string wallet_name;
|
std::string wallet_name;
|
||||||
|
std::shared_ptr<CWallet> wallet;
|
||||||
std::shared_ptr<CWallet> watchonly_wallet;
|
std::shared_ptr<CWallet> watchonly_wallet;
|
||||||
std::shared_ptr<CWallet> solvables_wallet;
|
std::shared_ptr<CWallet> solvables_wallet;
|
||||||
fs::path backup_path;
|
fs::path backup_path;
|
||||||
|
Loading…
Reference in New Issue
Block a user