-
Notifications
You must be signed in to change notification settings - Fork 1.2k
backport: batch 2 of migratewallet RPC #7277
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
117dab2
087c9be
493f633
56a61c8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -558,3 +558,67 @@ void RescanWalletActivity::finish() | |||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| 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(); | ||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
For encrypted wallets, Useful? React with 👍 / 👎.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. backported code; if there's no relevant backport from bitcoin-core, this comment would be ignored. @thepastaclaw any relevant backport for this fix? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes — the relevant upstream follow-up is bitcoin-core/gui#824, specifically commit d56a450 ( That commit changes the migration passphrase flow to use AskPassphraseDialog dlg(AskPassphraseDialog::UnlockMigration, m_parent_widget, &passphrase);
if (dlg.exec() == QDialog::Rejected) return;So this Codex finding is real upstream-covered behavior, but it is part of the broader “migrate legacy wallets that are not loaded” GUI follow-up rather than a small standalone fix on top of gui#738.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it can't be done right now, because bitcoin-core/gui#824 depends on bitcoin#27790 which seems impossible to be done right now; I am going to prioritize it for batch 3 but it seems as non-trivial yet. For Bitcoin Core the gap between bitcoin-core/gui#824 and bitcoin-core/gui#738 is 2 major versions btw. |
||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| // 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); | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+581
to
+590
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Abort migration when passphrase entry is cancelled. The dialog result is ignored, so canceling still removes the wallet and proceeds with migration using an empty passphrase. Suggested fix 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 (dlg.exec() != QDialog::Accepted) return;
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||
| 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 += QChar(' ') + 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 += 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_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(); | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -55,6 +55,7 @@ using interfaces::Wallet; | |
| using interfaces::WalletAddress; | ||
| using interfaces::WalletBalances; | ||
| using interfaces::WalletLoader; | ||
| using interfaces::WalletMigrationResult; | ||
| using interfaces::WalletOrderForm; | ||
| using interfaces::WalletTx; | ||
| using interfaces::WalletTxOut; | ||
|
|
@@ -936,6 +937,18 @@ class WalletLoaderImpl : public WalletLoader | |
| 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), | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
If the post-migration Useful? React with 👍 / 👎. |
||
| .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 | ||
|
Comment on lines
+944
to
+950
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift Propagate wallet reload failures instead of returning success. In Update 🤖 Prompt for AI Agents |
||
| } | ||
| std::string getWalletDir() override | ||
| { | ||
| return fs::PathToString(GetWalletDir()); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3264,6 +3264,7 @@ std::shared_ptr<CWallet> CWallet::Create(WalletContext& context, const std::stri | |
| for (auto spk_man : walletInstance->GetActiveScriptPubKeyMans()) { | ||
| if (spk_man->HavePrivateKeys()) { | ||
| warnings.push_back(strprintf(_("Warning: Private keys detected in wallet {%s} with disabled private keys"), walletFile)); | ||
| break; | ||
| } | ||
| } | ||
| } | ||
|
|
@@ -4553,16 +4554,19 @@ bool CWallet::MigrateToSQLite(bilingual_str& error) | |
|
|
||
| // Close this database and delete the file | ||
| fs::path db_path = fs::PathFromString(m_database->Filename()); | ||
| fs::path db_dir = db_path.parent_path(); | ||
| m_database->Close(); | ||
| fs::remove(db_path); | ||
|
|
||
| // Generate the path for the location of the migrated wallet | ||
| // Wallets that are plain files rather than wallet directories will be migrated to be wallet directories. | ||
| const fs::path wallet_path = fsbridge::AbsPathJoin(GetWalletDir(), fs::PathFromString(m_name)); | ||
|
|
||
| // Make new DB | ||
| DatabaseOptions opts; | ||
| opts.require_create = true; | ||
| opts.require_format = DatabaseFormat::SQLITE; | ||
| DatabaseStatus db_status; | ||
| std::unique_ptr<WalletDatabase> new_db = MakeDatabase(db_dir, opts, db_status, error); | ||
| std::unique_ptr<WalletDatabase> new_db = MakeDatabase(wallet_path, opts, db_status, error); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When migrating a legacy wallet stored as a top-level BDB file, the backup is created directly in Useful? React with 👍 / 👎. |
||
| assert(new_db); // This is to prevent doing anything further with this wallet. The original file was deleted, but a backup exists. | ||
| m_database.reset(); | ||
| m_database = std::move(new_db); | ||
|
|
@@ -4988,7 +4992,7 @@ util::Result<MigrationResult> MigrateLegacyToDescriptor(const std::string& walle | |
| // Migration successful, unload the wallet locally, then reload it. | ||
| assert(local_wallet.use_count() == 1); | ||
| 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; | ||
|
Comment on lines
4992
to
4996
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔴 Blocking: Failed post-migration reload returns a null wallet as success
source: ['codex', 'coderabbit']
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Correct observation, but this is exactly upstream's state at this point in Upstream fixed precisely this in bitcoin#28609 (d616d30, "There is also Note the window is narrow (the wallet file was just written and verified) and |
||
| } else { | ||
| // Migration failed, cleanup | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.