From 28fc562f2692af4f37f918d4ae31c4d115e03aee Mon Sep 17 00:00:00 2001 From: Ava Chow Date: Mon, 10 Jun 2024 15:00:21 -0400 Subject: [PATCH 1/5] wallet, interfaces: Include database format in listWalletDir --- src/interfaces/wallet.h | 2 +- src/qt/bitcoingui.cpp | 6 +++--- src/qt/walletcontroller.cpp | 10 +++++----- src/qt/walletcontroller.h | 2 +- src/wallet/db.cpp | 26 +++++++++++++++++--------- src/wallet/db.h | 2 +- src/wallet/interfaces.cpp | 8 ++++---- src/wallet/rpc/wallet.cpp | 2 +- 8 files changed, 33 insertions(+), 25 deletions(-) diff --git a/src/interfaces/wallet.h b/src/interfaces/wallet.h index c573d6aa65198..fa82e67ab1854 100644 --- a/src/interfaces/wallet.h +++ b/src/interfaces/wallet.h @@ -343,7 +343,7 @@ class WalletLoader : public ChainClient virtual util::Result migrateWallet(const std::string& name, const SecureString& passphrase) = 0; //! Return available wallets in wallet directory. - virtual std::vector listWalletDir() = 0; + virtual std::vector> listWalletDir() = 0; //! Return interfaces for accessing wallets (if any). virtual std::vector> getWallets() = 0; diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp index 1f78550a55904..6590147c6f855 100644 --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.cpp @@ -396,15 +396,15 @@ void BitcoinGUI::createActions() connect(openAction, &QAction::triggered, this, &BitcoinGUI::openClicked); connect(m_open_wallet_menu, &QMenu::aboutToShow, [this] { m_open_wallet_menu->clear(); - for (const std::pair& i : m_wallet_controller->listWalletDir()) { - const std::string& path = i.first; + for (const auto& [path, info] : m_wallet_controller->listWalletDir()) { + const auto& [loaded, _] = info; QString name = path.empty() ? QString("["+tr("default wallet")+"]") : QString::fromStdString(path); // An single ampersand in the menu item's text sets a shortcut for this item. // Single & are shown when && is in the string. So replace & with &&. name.replace(QChar('&'), QString("&&")); QAction* action = m_open_wallet_menu->addAction(name); - if (i.second) { + if (loaded) { // This wallet is already loaded action->setEnabled(false); continue; diff --git a/src/qt/walletcontroller.cpp b/src/qt/walletcontroller.cpp index 34b47c90a3b55..7b3d920e56ef2 100644 --- a/src/qt/walletcontroller.cpp +++ b/src/qt/walletcontroller.cpp @@ -65,16 +65,16 @@ WalletController::~WalletController() delete m_activity_worker; } -std::map WalletController::listWalletDir() const +std::map> WalletController::listWalletDir() const { QMutexLocker locker(&m_mutex); - std::map wallets; - for (const std::string& name : m_node.walletLoader().listWalletDir()) { - wallets[name] = false; + std::map> wallets; + for (const auto& [name, format] : m_node.walletLoader().listWalletDir()) { + wallets[name] = std::make_pair(false, format); } for (WalletModel* wallet_model : m_wallets) { auto it = wallets.find(wallet_model->wallet().getWalletName()); - if (it != wallets.end()) it->second = true; + if (it != wallets.end()) it->second.first = true; } return wallets; } diff --git a/src/qt/walletcontroller.h b/src/qt/walletcontroller.h index c595ba998db78..bc98dc051b2c0 100644 --- a/src/qt/walletcontroller.h +++ b/src/qt/walletcontroller.h @@ -61,7 +61,7 @@ class WalletController : public QObject //! Returns all wallet names in the wallet dir mapped to whether the wallet //! is loaded. - std::map listWalletDir() const; + std::map> listWalletDir() const; void closeWallet(WalletModel* wallet_model, QWidget* parent = nullptr); void closeAllWallets(QWidget* parent = nullptr); diff --git a/src/wallet/db.cpp b/src/wallet/db.cpp index 1523b7d6133de..400b9dc44fbcc 100644 --- a/src/wallet/db.cpp +++ b/src/wallet/db.cpp @@ -19,9 +19,9 @@ namespace wallet { bool operator<(BytePrefix a, Span b) { return a.prefix < b.subspan(0, std::min(a.prefix.size(), b.size())); } bool operator<(Span a, BytePrefix b) { return a.subspan(0, std::min(a.size(), b.prefix.size())) < b.prefix; } -std::vector ListDatabases(const fs::path& wallet_dir) +std::vector> ListDatabases(const fs::path& wallet_dir) { - std::vector paths; + std::vector> paths; std::error_code ec; for (auto it = fs::recursive_directory_iterator(wallet_dir, ec); it != fs::recursive_directory_iterator(); it.increment(ec)) { @@ -38,21 +38,29 @@ std::vector ListDatabases(const fs::path& wallet_dir) try { const fs::path path{it->path().lexically_relative(wallet_dir)}; - if (it->status().type() == fs::file_type::directory && - (IsBDBFile(BDBDataFile(it->path())) || IsSQLiteFile(SQLiteDataFile(it->path())))) { - // Found a directory which contains wallet.dat btree file, add it as a wallet. - paths.emplace_back(path); + if (it->status().type() == fs::file_type::directory) { + if (IsBDBFile(BDBDataFile(it->path()))) { + // Found a directory which contains wallet.dat btree file, add it as a wallet with BERKELEY format. + paths.emplace_back(path, "bdb"); + } else if (IsSQLiteFile(SQLiteDataFile(it->path()))) { + // Found a directory which contains wallet.dat sqlite file, add it as a wallet with SQLITE format. + paths.emplace_back(path, "sqlite"); + } } else if (it.depth() == 0 && it->symlink_status().type() == fs::file_type::regular && it->path().extension() != ".bak") { if (it->path().filename() == "wallet.dat") { - // Found top-level wallet.dat btree file, add top level directory "" + // Found top-level wallet.dat file, add top level directory "" // as a wallet. - paths.emplace_back(); + if (IsBDBFile(it->path())) { + paths.emplace_back(fs::path(), "bdb"); + } else if (IsSQLiteFile(it->path())) { + paths.emplace_back(fs::path(), "sqlite"); + } } else if (IsBDBFile(it->path())) { // Found top-level btree file not called wallet.dat. Current bitcoin // software will never create these files but will allow them to be // opened in a shared database environment for backwards compatibility. // Add it to the list of available wallets. - paths.emplace_back(path); + paths.emplace_back(path, "bdb"); } } } catch (const std::exception& e) { diff --git a/src/wallet/db.h b/src/wallet/db.h index 2045d513769d9..049af8dd19e36 100644 --- a/src/wallet/db.h +++ b/src/wallet/db.h @@ -216,7 +216,7 @@ enum class DatabaseStatus { }; /** Recursively list database paths in directory. */ -std::vector ListDatabases(const fs::path& path); +std::vector> ListDatabases(const fs::path& path); void ReadDatabaseArgs(const ArgsManager& args, DatabaseOptions& options); std::unique_ptr MakeDatabase(const fs::path& path, const DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error); diff --git a/src/wallet/interfaces.cpp b/src/wallet/interfaces.cpp index 9fab1b2ee44a3..14f05b687163b 100644 --- a/src/wallet/interfaces.cpp +++ b/src/wallet/interfaces.cpp @@ -656,11 +656,11 @@ class WalletLoaderImpl : public WalletLoader { return fs::PathToString(GetWalletDir()); } - std::vector listWalletDir() override + std::vector> listWalletDir() override { - std::vector paths; - for (auto& path : ListDatabases(GetWalletDir())) { - paths.push_back(fs::PathToString(path)); + std::vector> paths; + for (auto& [path, format] : ListDatabases(GetWalletDir())) { + paths.emplace_back(fs::PathToString(path), format); } return paths; } diff --git a/src/wallet/rpc/wallet.cpp b/src/wallet/rpc/wallet.cpp index ae1c67ef2a280..7a0b0103c0b99 100644 --- a/src/wallet/rpc/wallet.cpp +++ b/src/wallet/rpc/wallet.cpp @@ -169,7 +169,7 @@ static RPCHelpMan listwalletdir() [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { UniValue wallets(UniValue::VARR); - for (const auto& path : ListDatabases(GetWalletDir())) { + for (const auto& [path, _] : ListDatabases(GetWalletDir())) { UniValue wallet(UniValue::VOBJ); wallet.pushKV("name", path.utf8string()); wallets.push_back(std::move(wallet)); From bfba63880fbb1108b73540faeb0620ba24b8cdd0 Mon Sep 17 00:00:00 2001 From: Ava Chow Date: Mon, 10 Jun 2024 15:39:35 -0400 Subject: [PATCH 2/5] gui: Consolidate wallet display name to GUIUtil function Instead of having the code for the wallet display name being copy and pasted, use a GUIUtil function to get that for us. --- src/qt/bitcoingui.cpp | 2 +- src/qt/guiutil.cpp | 9 +++++++++ src/qt/guiutil.h | 3 +++ src/qt/walletcontroller.cpp | 2 +- src/qt/walletmodel.cpp | 3 +-- 5 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp index 6590147c6f855..189af77a0cdc1 100644 --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.cpp @@ -398,7 +398,7 @@ void BitcoinGUI::createActions() m_open_wallet_menu->clear(); for (const auto& [path, info] : m_wallet_controller->listWalletDir()) { const auto& [loaded, _] = info; - QString name = path.empty() ? QString("["+tr("default wallet")+"]") : QString::fromStdString(path); + QString name = GUIUtil::WalletDisplayName(path); // An single ampersand in the menu item's text sets a shortcut for this item. // Single & are shown when && is in the string. So replace & with &&. name.replace(QChar('&'), QString("&&")); diff --git a/src/qt/guiutil.cpp b/src/qt/guiutil.cpp index f04e5c86f8f2d..2369f6b631241 100644 --- a/src/qt/guiutil.cpp +++ b/src/qt/guiutil.cpp @@ -1008,4 +1008,13 @@ void ShowModalDialogAsynchronously(QDialog* dialog) dialog->show(); } +QString WalletDisplayName(const QString& name) +{ + return name.isEmpty() ? "[" + QObject::tr("default wallet") + "]" : name; +} + +QString WalletDisplayName(const std::string& name) +{ + return WalletDisplayName(QString::fromStdString(name)); +} } // namespace GUIUtil diff --git a/src/qt/guiutil.h b/src/qt/guiutil.h index 3e28e545574a4..452519879437e 100644 --- a/src/qt/guiutil.h +++ b/src/qt/guiutil.h @@ -436,6 +436,9 @@ namespace GUIUtil return false; } + QString WalletDisplayName(const std::string& name); + QString WalletDisplayName(const QString& name); + } // namespace GUIUtil #endif // BITCOIN_QT_GUIUTIL_H diff --git a/src/qt/walletcontroller.cpp b/src/qt/walletcontroller.cpp index 7b3d920e56ef2..ca7c00da41497 100644 --- a/src/qt/walletcontroller.cpp +++ b/src/qt/walletcontroller.cpp @@ -343,7 +343,7 @@ void OpenWalletActivity::finish() void OpenWalletActivity::open(const std::string& path) { - QString name = path.empty() ? QString("["+tr("default wallet")+"]") : QString::fromStdString(path); + QString name = GUIUtil::WalletDisplayName(path); showProgressDialog( //: Title of window indicating the progress of opening of a wallet. diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index f8ce068e1251e..0a01c0a45b192 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -594,8 +594,7 @@ QString WalletModel::getWalletName() const QString WalletModel::getDisplayName() const { - const QString name = getWalletName(); - return name.isEmpty() ? "["+tr("default wallet")+"]" : name; + return GUIUtil::WalletDisplayName(getWalletName()); } bool WalletModel::isMultiwallet() const From c3918583dd5fcd9001136da2192e02e092128901 Mon Sep 17 00:00:00 2001 From: furszy Date: Sun, 11 Aug 2024 23:58:37 -0300 Subject: [PATCH 3/5] gui: don't remove wallet manually before migration --- src/qt/walletcontroller.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/qt/walletcontroller.cpp b/src/qt/walletcontroller.cpp index ca7c00da41497..6f7049e51b2eb 100644 --- a/src/qt/walletcontroller.cpp +++ b/src/qt/walletcontroller.cpp @@ -150,9 +150,10 @@ WalletModel* WalletController::getOrCreateWallet(std::unique_ptr(active_dialog) == nullptr) { connect(qApp, &QApplication::focusWindowChanged, wallet_model, [this, wallet_model]() { if (!QApplication::activeModalWidget()) { removeAndDeleteWallet(wallet_model); @@ -463,8 +464,6 @@ void MigrateWalletActivity::migrate(WalletModel* wallet_model) // 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 %1…").arg(GUIUtil::HtmlEscape(name))); QTimer::singleShot(0, worker(), [this, name, passphrase] { From d56a450bf5172e2c3f4b9a2786e71268019e1277 Mon Sep 17 00:00:00 2001 From: Ava Chow Date: Mon, 10 Jun 2024 15:40:53 -0400 Subject: [PATCH 4/5] 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 fa82e67ab1854..df1ced48a7142 100644 --- a/src/interfaces/wallet.h +++ b/src/interfaces/wallet.h @@ -342,6 +342,9 @@ class WalletLoader : public ChainClient //! 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 70ed40b2a18d1..b0bc1f480651a 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 370ea1de7ecdf..c567c2942888a 100644 --- a/src/qt/askpassphrasedialog.h +++ b/src/qt/askpassphrasedialog.h @@ -26,6 +26,7 @@ class AskPassphraseDialog : public QDialog 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 189af77a0cdc1..6ea4b68a3dd73 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 6f7049e51b2eb..512ea8a1dc116 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 bc98dc051b2c0..7902c3b23544a 100644 --- a/src/qt/walletcontroller.h +++ b/src/qt/walletcontroller.h @@ -66,8 +66,6 @@ class WalletController : public QObject 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 14f05b687163b..21e8a0b3bd24c 100644 --- a/src/wallet/interfaces.cpp +++ b/src/wallet/interfaces.cpp @@ -652,6 +652,21 @@ class WalletLoaderImpl : public WalletLoader }; 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 61cc9dbc780ee..18d6e1407e908 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 9474a59660fe5..bffcc87202aa6 100644 --- a/src/wallet/walletdb.h +++ b/src/wallet/walletdb.h @@ -247,6 +247,9 @@ class WalletBatch 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); From 8f2522d242961ceb9e79672aa43e856863a1a6dd Mon Sep 17 00:00:00 2001 From: Ava Chow Date: Mon, 10 Jun 2024 15:14:04 -0400 Subject: [PATCH 5/5] gui: Use menu for wallet migration Once legacy wallets can no longer be loaded, we need to be able to migrate them without loading. Thus we should use a menu that lists the wallets in the wallet directory instead of an action which migrates the currently loaded wallet. --- src/qt/bitcoingui.cpp | 34 ++++++++++++++++++++++++++++------ 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp index 6ea4b68a3dd73..6d66c7473bd05 100644 --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.cpp @@ -360,6 +360,7 @@ void BitcoinGUI::createActions() 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")); + m_migrate_wallet_menu = new QMenu(this); showHelpMessageAction = new QAction(tr("&Command-line options"), this); showHelpMessageAction->setMenuRole(QAction::NoRole); @@ -455,10 +456,31 @@ 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()->wallet().getWalletName()); + connect(m_migrate_wallet_menu, &QMenu::aboutToShow, [this] { + m_migrate_wallet_menu->clear(); + for (const auto& [wallet_name, info] : m_wallet_controller->listWalletDir()) { + const auto& [loaded, format] = info; + + if (format != "bdb") { // Skip already migrated wallets + continue; + } + + QString name = GUIUtil::WalletDisplayName(wallet_name); + // An single ampersand in the menu item's text sets a shortcut for this item. + // Single & are shown when && is in the string. So replace & with &&. + name.replace(QChar('&'), QString("&&")); + QAction* action = m_migrate_wallet_menu->addAction(name); + + connect(action, &QAction::triggered, [this, wallet_name] { + auto activity = new MigrateWalletActivity(m_wallet_controller, this); + connect(activity, &MigrateWalletActivity::migrated, this, &BitcoinGUI::setCurrentWallet); + activity->migrate(wallet_name); + }); + } + if (m_migrate_wallet_menu->isEmpty()) { + QAction* action = m_migrate_wallet_menu->addAction(tr("No wallets available")); + action->setEnabled(false); + } }); connect(m_mask_values_action, &QAction::toggled, this, &BitcoinGUI::setPrivacy); connect(m_mask_values_action, &QAction::toggled, this, &BitcoinGUI::enableHistoryAction); @@ -691,6 +713,8 @@ void BitcoinGUI::setWalletController(WalletController* wallet_controller, bool s m_open_wallet_action->setEnabled(true); m_open_wallet_action->setMenu(m_open_wallet_menu); m_restore_wallet_action->setEnabled(true); + m_migrate_wallet_action->setEnabled(true); + m_migrate_wallet_action->setMenu(m_migrate_wallet_menu); GUIUtil::ExceptionSafeConnect(wallet_controller, &WalletController::walletAdded, this, &BitcoinGUI::addWallet); connect(wallet_controller, &WalletController::walletRemoved, this, &BitcoinGUI::removeWallet); @@ -771,7 +795,6 @@ void BitcoinGUI::setCurrentWallet(WalletModel* wallet_model) } } updateWindowTitle(); - m_migrate_wallet_action->setEnabled(wallet_model->wallet().isLegacy()); } void BitcoinGUI::setCurrentWalletBySelectorIndex(int index) @@ -805,7 +828,6 @@ 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()