From d56a450bf5172e2c3f4b9a2786e71268019e1277 Mon Sep 17 00:00:00 2001 From: Ava Chow Date: Mon, 10 Jun 2024 15:40:53 -0400 Subject: [PATCH] gui: Use wallet name for wallet migration rather than WalletModel To prepare for migrating wallets that are not loaded, when migration occurs in the GUI, it should not rely on a WalletModel existing. Co-authored-by: furszy --- src/interfaces/wallet.h | 3 +++ src/qt/askpassphrasedialog.cpp | 8 +++++++- src/qt/askpassphrasedialog.h | 1 + src/qt/bitcoingui.cpp | 2 +- src/qt/walletcontroller.cpp | 22 +++++++++------------- src/qt/walletcontroller.h | 4 +--- src/wallet/interfaces.cpp | 15 +++++++++++++++ src/wallet/walletdb.cpp | 11 +++++++++++ src/wallet/walletdb.h | 3 +++ 9 files changed, 51 insertions(+), 18 deletions(-) diff --git a/src/interfaces/wallet.h b/src/interfaces/wallet.h index fa82e67ab1..df1ced48a7 100644 --- a/src/interfaces/wallet.h +++ b/src/interfaces/wallet.h @@ -342,6 +342,9 @@ public: //! Migrate a wallet virtual util::Result migrateWallet(const std::string& name, const SecureString& passphrase) = 0; + //! Returns true if wallet stores encryption keys + virtual bool isEncrypted(const std::string& wallet_name) = 0; + //! Return available wallets in wallet directory. virtual std::vector> listWalletDir() = 0; diff --git a/src/qt/askpassphrasedialog.cpp b/src/qt/askpassphrasedialog.cpp index 70ed40b2a1..b0bc1f4806 100644 --- a/src/qt/askpassphrasedialog.cpp +++ b/src/qt/askpassphrasedialog.cpp @@ -44,6 +44,7 @@ AskPassphraseDialog::AskPassphraseDialog(Mode _mode, QWidget *parent, SecureStri ui->passEdit1->hide(); setWindowTitle(tr("Encrypt wallet")); break; + case UnlockMigration: case Unlock: // Ask passphrase ui->warningLabel->setText(tr("This operation needs your wallet passphrase to unlock the wallet.")); ui->passLabel2->hide(); @@ -80,7 +81,7 @@ void AskPassphraseDialog::setModel(WalletModel *_model) void AskPassphraseDialog::accept() { SecureString oldpass, newpass1, newpass2; - if (!model && mode != Encrypt) + if (!model && mode != Encrypt && mode != UnlockMigration) return; oldpass.reserve(MAX_PASSPHRASE_SIZE); newpass1.reserve(MAX_PASSPHRASE_SIZE); @@ -181,6 +182,10 @@ void AskPassphraseDialog::accept() QMessageBox::critical(this, tr("Wallet unlock failed"), e.what()); } break; + case UnlockMigration: + Assume(m_passphrase_out)->assign(oldpass); + QDialog::accept(); + break; case ChangePass: if(newpass1 == newpass2) { @@ -224,6 +229,7 @@ void AskPassphraseDialog::textChanged() case Encrypt: // New passphrase x2 acceptable = !ui->passEdit2->text().isEmpty() && !ui->passEdit3->text().isEmpty(); break; + case UnlockMigration: case Unlock: // Old passphrase x1 acceptable = !ui->passEdit1->text().isEmpty(); break; diff --git a/src/qt/askpassphrasedialog.h b/src/qt/askpassphrasedialog.h index 370ea1de7e..c567c29428 100644 --- a/src/qt/askpassphrasedialog.h +++ b/src/qt/askpassphrasedialog.h @@ -26,6 +26,7 @@ public: Encrypt, /**< Ask passphrase twice and encrypt */ Unlock, /**< Ask passphrase and unlock */ ChangePass, /**< Ask old passphrase + new passphrase twice */ + UnlockMigration, /**< Ask passphrase for unlocking during migration */ }; explicit AskPassphraseDialog(Mode mode, QWidget *parent, SecureString* passphrase_out = nullptr); diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp index 189af77a0c..6ea4b68a3d 100644 --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.cpp @@ -458,7 +458,7 @@ void BitcoinGUI::createActions() 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()); + activity->migrate(walletFrame->currentWalletModel()->wallet().getWalletName()); }); connect(m_mask_values_action, &QAction::toggled, this, &BitcoinGUI::setPrivacy); connect(m_mask_values_action, &QAction::toggled, this, &BitcoinGUI::enableHistoryAction); diff --git a/src/qt/walletcontroller.cpp b/src/qt/walletcontroller.cpp index 6f7049e51b..512ea8a1dc 100644 --- a/src/qt/walletcontroller.cpp +++ b/src/qt/walletcontroller.cpp @@ -437,12 +437,12 @@ void RestoreWalletActivity::finish() Q_EMIT finished(); } -void MigrateWalletActivity::migrate(WalletModel* wallet_model) +void MigrateWalletActivity::migrate(const std::string& name) { // 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 %1?").arg(GUIUtil::HtmlEscape(wallet_model->getDisplayName()))); + box.setText(tr("Are you sure you wish to migrate the wallet %1?").arg(GUIUtil::HtmlEscape(GUIUtil::WalletDisplayName(name)))); 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" @@ -453,29 +453,25 @@ void MigrateWalletActivity::migrate(WalletModel* wallet_model) 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(); + if (node().walletLoader().isEncrypted(name)) { + // Get the passphrase for the wallet + AskPassphraseDialog dlg(AskPassphraseDialog::UnlockMigration, m_parent_widget, &passphrase); + if (dlg.exec() == QDialog::Rejected) return; } - // GUI needs to remove the wallet so that it can actually be unloaded by migration - const std::string name = wallet_model->wallet().getWalletName(); showProgressDialog(tr("Migrate Wallet"), tr("Migrating Wallet %1…").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())); + m_success_message = tr("The wallet '%1' was migrated successfully.").arg(GUIUtil::HtmlEscape(GUIUtil::WalletDisplayName(res->wallet->getWalletName()))); if (res->watchonly_wallet_name) { - m_success_message += QChar(' ') + tr("Watchonly scripts have been migrated to a new wallet named '%1'.").arg(GUIUtil::HtmlEscape(res->watchonly_wallet_name.value())); + m_success_message += QChar(' ') + tr("Watchonly scripts have been migrated to a new wallet named '%1'.").arg(GUIUtil::HtmlEscape(GUIUtil::WalletDisplayName(res->watchonly_wallet_name.value()))); } if (res->solvables_wallet_name) { - m_success_message += QChar(' ') + tr("Solvable but not watched scripts have been migrated to a new wallet named '%1'.").arg(GUIUtil::HtmlEscape(res->solvables_wallet_name.value())); + m_success_message += QChar(' ') + tr("Solvable but not watched scripts have been migrated to a new wallet named '%1'.").arg(GUIUtil::HtmlEscape(GUIUtil::WalletDisplayName(res->solvables_wallet_name.value()))); } m_wallet_model = m_wallet_controller->getOrCreateWallet(std::move(res->wallet)); } else { diff --git a/src/qt/walletcontroller.h b/src/qt/walletcontroller.h index bc98dc051b..7902c3b235 100644 --- a/src/qt/walletcontroller.h +++ b/src/qt/walletcontroller.h @@ -66,8 +66,6 @@ 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); @@ -186,7 +184,7 @@ class MigrateWalletActivity : public WalletControllerActivity public: MigrateWalletActivity(WalletController* wallet_controller, QWidget* parent) : WalletControllerActivity(wallet_controller, parent) {} - void migrate(WalletModel* wallet_model); + void migrate(const std::string& path); Q_SIGNALS: void migrated(WalletModel* wallet_model); diff --git a/src/wallet/interfaces.cpp b/src/wallet/interfaces.cpp index 14f05b6871..21e8a0b3bd 100644 --- a/src/wallet/interfaces.cpp +++ b/src/wallet/interfaces.cpp @@ -652,6 +652,21 @@ public: }; return out; } + bool isEncrypted(const std::string& wallet_name) override + { + auto wallets{GetWallets(m_context)}; + auto it = std::find_if(wallets.begin(), wallets.end(), [&](std::shared_ptr w){ return w->GetName() == wallet_name; }); + if (it != wallets.end()) return (*it)->IsCrypted(); + + // Unloaded wallet, read db + DatabaseOptions options; + options.require_existing = true; + DatabaseStatus status; + bilingual_str error; + auto db = MakeWalletDatabase(wallet_name, options, status, error); + if (!db) return false; + return WalletBatch(*db).IsEncrypted(); + } std::string getWalletDir() override { return fs::PathToString(GetWalletDir()); diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp index 61cc9dbc78..18d6e1407e 100644 --- a/src/wallet/walletdb.cpp +++ b/src/wallet/walletdb.cpp @@ -187,6 +187,17 @@ bool WalletBatch::ReadBestBlock(CBlockLocator& locator) return m_batch->Read(DBKeys::BESTBLOCK_NOMERKLE, locator); } +bool WalletBatch::IsEncrypted() +{ + DataStream prefix; + prefix << DBKeys::MASTER_KEY; + if (auto cursor = m_batch->GetNewPrefixCursor(prefix)) { + DataStream k, v; + if (cursor->Next(k, v) == DatabaseCursor::Status::MORE) return true; + } + return false; +} + bool WalletBatch::WriteOrderPosNext(int64_t nOrderPosNext) { return WriteIC(DBKeys::ORDERPOSNEXT, nOrderPosNext); diff --git a/src/wallet/walletdb.h b/src/wallet/walletdb.h index 9474a59660..bffcc87202 100644 --- a/src/wallet/walletdb.h +++ b/src/wallet/walletdb.h @@ -247,6 +247,9 @@ public: bool WriteBestBlock(const CBlockLocator& locator); bool ReadBestBlock(CBlockLocator& locator); + // Returns true if wallet stores encryption keys + bool IsEncrypted(); + bool WriteOrderPosNext(int64_t nOrderPosNext); bool ReadPool(int64_t nPool, CKeyPool& keypool);