Inside Bitcoin
Opening the selected file…
Inside Bitcoin
Opening the selected file…
The code behind the network, with room to understand it.
Release published 2026-10-28 · Snapshot fetched 2026-11-01. Latest stable release at fetch time; development branches and Git history are excluded. Only the selected code window and visible folders load here.
4251throw std::runtime_error(util::ErrorString(spkm_res).original);4252}4253}4255// Add the wallet to settings4256UpdateWalletSetting(*context.chain, wallet_name, load_on_startup, warnings);4257}4258if (data->solvable_descs.size() > 0) {4259wallet.WalletLogPrintf("Making a new watchonly wallet containing the unwatched solvable scripts\n");4261DatabaseStatus status;4262std::vector<bilingual_str> warnings;4263std::string wallet_name = MigrationPrefixName(wallet) + "_solvables";4264std::unique_ptr<WalletDatabase> database = MakeWalletDatabase(wallet_name, options, status, error);4265if (!database) {4266error = strprintf(_("Wallet file creation failed: %s"), error);4267return false;4268}4270data->solvable_wallet = CWallet::CreateNew(empty_context, wallet_name, std::move(database), options.create_flags, /*born_encrypted=*/false, error, warnings);4271if (!data->solvable_wallet) {4272error = _("Error: Failed to create new watchonly wallet");4273return false;4274}4275res.solvables_wallet = data->solvable_wallet;4276LOCK(data->solvable_wallet->cs_wallet);4278// Parse the descriptors and add them to the new wallet4279for (const auto& [desc_str, creation_time] : data->solvable_descs) {4280// Parse the descriptor4281FlatSigningProvider keys;4282std::string parse_err;4283std::vector<std::unique_ptr<Descriptor>> descs = Parse(desc_str, keys, parse_err, /*require_checksum=*/ true);4284// LegacyDataSPKM should not produce invalid, multipath, or ranged watch-only descriptors.4285assert(descs.size() == 1);4286assert(!descs.at(0)->IsRange());4288// Add to the wallet4289WalletDescriptor w_desc(std::move(descs.at(0)), creation_time, 0, 0, 0);4290if (auto spkm_res = data->solvable_wallet->AddWalletDescriptor(w_desc, keys, "", false); !spkm_res) {4291throw std::runtime_error(util::ErrorString(spkm_res).original);4292}4293}4295// Add the wallet to settings4296UpdateWalletSetting(*context.chain, wallet_name, load_on_startup, warnings);4297}4298}4300// Add the descriptors to the wallet, remove the LegacyDataSPKM, and clean up transactions and address book data4301return RunWithinTxn(wallet.GetDatabase(), /*process_desc=*/"apply migration process", [&](WalletBatch& batch) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet){4302if (auto res_migration = wallet.ApplyMigrationData(batch, *data); !res_migration) {4303error = util::ErrorString(res_migration);4304return false;4305}4306wallet.WalletLogPrintf("Wallet migration complete.\n");4307return true;4308});4309}4311util::Result<MigrationResult> MigrateLegacyToDescriptor(const std::string& wallet_name, const SecureString& passphrase, WalletContext& context, bool load_wallet)4312{4313std::vector<bilingual_str> warnings;4314bilingual_str error;4316// The only kind of wallets that could be loaded are descriptor ones, which don't need to be migrated.4317if (auto wallet = GetWallet(context, wallet_name)) {4318assert(wallet->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS));4319return util::Error{_("Error: This wallet is already a descriptor wallet")};4320} else {4321// Check if the wallet is BDB4322const auto& wallet_path = GetWalletPath(wallet_name);4323if (!wallet_path) {4324return util::Error{util::ErrorString(wallet_path)};4325}4326if (!fs::exists(*wallet_path)) {4327return util::Error{_("Error: Wallet does not exist")};4328}4329if (!IsBDBFile(BDBDataFile(*wallet_path))) {4330return util::Error{_("Error: This wallet is already a descriptor wallet")};4331}4332}4334// Load the wallet but only in the context of this function.4335// No signals should be connected nor should anything else be aware of this wallet4336WalletContext empty_context;4337empty_context.args = context.args;4338DatabaseOptions options;4339options.require_existing = true;4340options.require_format = DatabaseFormat::BERKELEY_RO;4341DatabaseStatus status;4342std::unique_ptr<WalletDatabase> database = MakeWalletDatabase(wallet_name, options, status, error);4343if (!database) {4344return util::Error{Untranslated("Wallet file verification failed.") + Untranslated(" ") + error};4345}4347// Make the local wallet4348std::shared_ptr<CWallet> local_wallet = CWallet::LoadExisting(empty_context, wallet_name, std::move(database), error, warnings);4349if (!local_wallet) {4350return util::Error{Untranslated("Wallet loading failed.") + Untranslated(" ") + error};4351}4353return MigrateLegacyToDescriptor(std::move(local_wallet), passphrase, context, load_wallet);4354}4356util::Result<MigrationResult> MigrateLegacyToDescriptor(std::shared_ptr<CWallet> local_wallet, const SecureString& passphrase, WalletContext& context, bool load_wallet)4357{4358MigrationResult res;4359bilingual_str error;4360std::vector<bilingual_str> warnings;4362DatabaseOptions options;4363options.require_existing = true;4364DatabaseStatus status;4366const std::string wallet_name = local_wallet->GetName();4368// Before anything else, check if there is something to migrate.4369if (local_wallet->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)) {4370return util::Error{_("Error: This wallet is already a descriptor wallet")};4371}4373// Make a backup of the DB in the wallet's directory with a unique filename4374// using the wallet name and current timestamp. The backup filename is based4375// on the name of the parent directory containing the wallet data in most4376// cases, but in the case where the wallet name is a path to a data file,4377// the name of the data file is used, and in the case where the wallet name4378// is blank, "default_wallet" is used.4379const std::string backup_prefix = wallet_name.empty() ? MigrationPrefixName(*local_wallet) : [&] {4380// fs::weakly_canonical resolves relative specifiers and remove trailing slashes.4381const auto legacy_wallet_path = fs::weakly_canonical(GetWalletDir() / fs::PathFromString(wallet_name));4382return fs::PathToString(legacy_wallet_path.filename());4383}();4385fs::path backup_filename = fs::PathFromString(strprintf("%s_%d.legacy.bak", backup_prefix, GetTime()));4386fs::path backup_path = fsbridge::AbsPathJoin(GetWalletDir(), backup_filename);4387if (!local_wallet->BackupWallet(fs::PathToString(backup_path))) {4388return util::Error{_("Error: Unable to make a backup of your wallet")};4389}4390res.backup_path = backup_path;4392bool success = false;4394// Unlock the wallet if needed4395if (local_wallet->IsLocked() && !local_wallet->Unlock(passphrase)) {4396if (passphrase.find('\0') == std::string::npos) {4397return util::Error{Untranslated("Error: Wallet decryption failed, the wallet passphrase was not provided or was incorrect.")};4398} else {4399return util::Error{Untranslated("Error: Wallet decryption failed, the wallet passphrase entered was incorrect. "4400"The passphrase contains a null character (ie - a zero byte). "4401"If this passphrase was set with a version of this software prior to 25.0, "4402"please try again with only the characters up to — but not including — "4403"the first null character.")};4404}4405}4407// Indicates whether the current wallet is empty after migration.4408// Notes:4409// When non-empty: the local wallet becomes the main spendable wallet.4410// When empty: The local wallet is excluded from the result, as the4411// user does not expect an empty spendable wallet after4412// migrating only watch-only scripts.4413bool empty_local_wallet = false;4415{4416LOCK(local_wallet->cs_wallet);4417// First change to using SQLite4418if (!local_wallet->MigrateToSQLite(error)) return util::Error{error};4420// Do the migration of keys and scripts for non-empty wallets, and cleanup if it fails4421if (HasLegacyRecords(*local_wallet)) {4422success = DoMigration(*local_wallet, context, error, res, load_wallet);4423// No scripts mean empty wallet after migration4424empty_local_wallet = local_wallet->GetAllScriptPubKeyMans().empty();4425} else {4426// Make sure that descriptors flag is actually set4427local_wallet->SetWalletFlag(WALLET_FLAG_DESCRIPTORS);4428success = true;4429}4430}4432// In case of loading failure, we need to remember the wallet files we have created to remove.4433// A `set` is used as it may be populated with the same wallet directory paths multiple times,4434// both before and after loading. This ensures the set is complete even if one of the wallets4435// fails to load.4436std::set<fs::path> wallet_files_to_remove;4437std::set<fs::path> wallet_empty_dirs_to_remove;4439// Helper to track wallet files and directories for cleanup on failure.4440// Only directories of wallets created during migration (not the main wallet) are tracked.4441auto track_for_cleanup = [&](const CWallet& wallet) {4442const auto files = wallet.GetDatabase().Files();4443wallet_files_to_remove.insert(files.begin(), files.end());4444if (wallet.GetName() != wallet_name) {4445// If this isn’t the main wallet, mark its directory for removal.4446// This applies to the watch-only and solvable wallets.4447// Wallets stored directly as files in the top-level directory4448// (e.g. default unnamed wallets) don’t have a removable parent directory.4449wallet_empty_dirs_to_remove.insert(fs::PathFromString(wallet.GetDatabase().Filename()).parent_path());4450}4451};4454if (success) {4455Assume(!res.wallet); // We will set it here.4456// Check if the local wallet is empty after migration4457if (empty_local_wallet) {4458// This wallet has no records. We can safely remove it.4459std::vector<fs::path> paths_to_remove = local_wallet->GetDatabase().Files();4460local_wallet.reset();4461for (const auto& path_to_remove : paths_to_remove) fs::remove(path_to_remove);4462}4464if (load_wallet) {4465LogInfo("Loading new wallets after migration...\n");4466/** We only override the load_on_startup setting in case the user explicitly said4467* that he does not want to load the wallet, otherwise keep the old wallet configuration */4468} else {4469UpdateWalletSetting(*context.chain, wallet_name, /*load_on_startup=*/false, warnings);4470}4471// Migration successful, if load_wallet is set load all the migrated wallets.4472bool main_wallet_set{false};4473for (std::shared_ptr<CWallet>* wallet_ptr : {&local_wallet, &res.watchonly_wallet, &res.solvables_wallet}) {4474if (success && *wallet_ptr) {4475std::shared_ptr<CWallet>& wallet = *wallet_ptr;4476// Track db path4477track_for_cleanup(*wallet);4478assert(wallet.use_count() == 1);4479std::string wallet_name = wallet->GetName();4480wallet.reset();4481if (load_wallet) {4482wallet = LoadWallet(context, wallet_name, /*load_on_start=*/std::nullopt, options, status, error, warnings);4483if (!wallet) {4484LogError("Failed to load wallet '%s' after migration. Rolling back migration to preserve consistency. "4485"Error cause: %s\n", wallet_name, error.original);4486success = false;4487break;4488}4489}4490// Set the first wallet as the main one.4491// The loop order is intentional and must always start with the local wallet.4492if (!main_wallet_set) {4493res.wallet_name = wallet_name;4494if (load_wallet) res.wallet = std::move(wallet);4495main_wallet_set = true;4496}4497if (wallet_ptr == &res.watchonly_wallet) {4498res.watchonly_wallet_name = wallet_name;4499} else if (wallet_ptr == &res.solvables_wallet) {4500res.solvables_wallet_name = wallet_name;