diff --git a/configure.ac b/configure.ac index f9f362ce9b4..3099a3a1003 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ AC_PREREQ([2.69]) define(_CLIENT_VERSION_MAJOR, 28) define(_CLIENT_VERSION_MINOR, 4) define(_CLIENT_VERSION_BUILD, 0) -define(_CLIENT_VERSION_RC, 1) +define(_CLIENT_VERSION_RC, 2) define(_CLIENT_VERSION_IS_RELEASE, true) define(_COPYRIGHT_YEAR, 2026) define(_COPYRIGHT_HOLDERS,[The %s developers]) diff --git a/contrib/guix/INSTALL.md b/contrib/guix/INSTALL.md index 40468c4996e..d04fd6f5fae 100644 --- a/contrib/guix/INSTALL.md +++ b/contrib/guix/INSTALL.md @@ -71,13 +71,13 @@ https://repology.org/project/guix/versions ### Debian / Ubuntu -Guix is available as a distribution package in [Debian -](https://packages.debian.org/search?keywords=guix) and [Ubuntu -](https://packages.ubuntu.com/search?keywords=guix). +Currently, the `guix` package is no longer present in recent Debian or Ubuntu +repositories. Any other installation option mentioned in this document may be +used. -To install: +If you previously installed `guix` via `apt`, you can remove it with: ```sh -sudo apt install guix +sudo apt purge guix ``` ### Arch Linux diff --git a/doc/man/bitcoin-cli.1 b/doc/man/bitcoin-cli.1 index 31ae403dc22..bd3103a3660 100644 --- a/doc/man/bitcoin-cli.1 +++ b/doc/man/bitcoin-cli.1 @@ -1,7 +1,7 @@ .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3. -.TH BITCOIN-CLI "1" "February 2026" "bitcoin-cli v28.4.0rc1" "User Commands" +.TH BITCOIN-CLI "1" "February 2026" "bitcoin-cli v28.4.0rc2" "User Commands" .SH NAME -bitcoin-cli \- manual page for bitcoin-cli v28.4.0rc1 +bitcoin-cli \- manual page for bitcoin-cli v28.4.0rc2 .SH SYNOPSIS .B bitcoin-cli [\fI\,options\/\fR] \fI\, \/\fR[\fI\,params\/\fR] \fI\,Send command to Bitcoin Core\/\fR @@ -15,7 +15,7 @@ bitcoin-cli \- manual page for bitcoin-cli v28.4.0rc1 .B bitcoin-cli [\fI\,options\/\fR] \fI\,help Get help for a command\/\fR .SH DESCRIPTION -Bitcoin Core RPC client version v28.4.0rc1 +Bitcoin Core RPC client version v28.4.0rc2 .SH OPTIONS .HP \-? diff --git a/doc/man/bitcoin-qt.1 b/doc/man/bitcoin-qt.1 index f9f31fa573d..4a13ac617db 100644 --- a/doc/man/bitcoin-qt.1 +++ b/doc/man/bitcoin-qt.1 @@ -1,12 +1,12 @@ .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3. -.TH BITCOIN-QT "1" "February 2026" "bitcoin-qt v28.4.0rc1" "User Commands" +.TH BITCOIN-QT "1" "February 2026" "bitcoin-qt v28.4.0rc2" "User Commands" .SH NAME -bitcoin-qt \- manual page for bitcoin-qt v28.4.0rc1 +bitcoin-qt \- manual page for bitcoin-qt v28.4.0rc2 .SH SYNOPSIS .B bitcoin-qt [\fI\,command-line options\/\fR] [\fI\,URI\/\fR] .SH DESCRIPTION -Bitcoin Core version v28.4.0rc1 +Bitcoin Core version v28.4.0rc2 .PP Optional URI is a Bitcoin address in BIP21 URI format. .SH OPTIONS diff --git a/doc/man/bitcoin-tx.1 b/doc/man/bitcoin-tx.1 index f003f731f4c..006ae349017 100644 --- a/doc/man/bitcoin-tx.1 +++ b/doc/man/bitcoin-tx.1 @@ -1,7 +1,7 @@ .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3. -.TH BITCOIN-TX "1" "February 2026" "bitcoin-tx v28.4.0rc1" "User Commands" +.TH BITCOIN-TX "1" "February 2026" "bitcoin-tx v28.4.0rc2" "User Commands" .SH NAME -bitcoin-tx \- manual page for bitcoin-tx v28.4.0rc1 +bitcoin-tx \- manual page for bitcoin-tx v28.4.0rc2 .SH SYNOPSIS .B bitcoin-tx [\fI\,options\/\fR] \fI\, \/\fR[\fI\,commands\/\fR] \fI\,Update hex-encoded bitcoin transaction\/\fR @@ -9,7 +9,7 @@ bitcoin-tx \- manual page for bitcoin-tx v28.4.0rc1 .B bitcoin-tx [\fI\,options\/\fR] \fI\,-create \/\fR[\fI\,commands\/\fR] \fI\,Create hex-encoded bitcoin transaction\/\fR .SH DESCRIPTION -Bitcoin Core bitcoin\-tx utility version v28.4.0rc1 +Bitcoin Core bitcoin\-tx utility version v28.4.0rc2 .SH OPTIONS .HP \-? diff --git a/doc/man/bitcoin-util.1 b/doc/man/bitcoin-util.1 index d6684e789e3..f3e897768cf 100644 --- a/doc/man/bitcoin-util.1 +++ b/doc/man/bitcoin-util.1 @@ -1,12 +1,12 @@ .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3. -.TH BITCOIN-UTIL "1" "February 2026" "bitcoin-util v28.4.0rc1" "User Commands" +.TH BITCOIN-UTIL "1" "February 2026" "bitcoin-util v28.4.0rc2" "User Commands" .SH NAME -bitcoin-util \- manual page for bitcoin-util v28.4.0rc1 +bitcoin-util \- manual page for bitcoin-util v28.4.0rc2 .SH SYNOPSIS .B bitcoin-util [\fI\,options\/\fR] [\fI\,commands\/\fR] \fI\,Do stuff\/\fR .SH DESCRIPTION -Bitcoin Core bitcoin\-util utility version v28.4.0rc1 +Bitcoin Core bitcoin\-util utility version v28.4.0rc2 .SH OPTIONS .HP \-? diff --git a/doc/man/bitcoin-wallet.1 b/doc/man/bitcoin-wallet.1 index ff0e41cd3f7..b5c61884afa 100644 --- a/doc/man/bitcoin-wallet.1 +++ b/doc/man/bitcoin-wallet.1 @@ -1,9 +1,9 @@ .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3. -.TH BITCOIN-WALLET "1" "February 2026" "bitcoin-wallet v28.4.0rc1" "User Commands" +.TH BITCOIN-WALLET "1" "February 2026" "bitcoin-wallet v28.4.0rc2" "User Commands" .SH NAME -bitcoin-wallet \- manual page for bitcoin-wallet v28.4.0rc1 +bitcoin-wallet \- manual page for bitcoin-wallet v28.4.0rc2 .SH DESCRIPTION -Bitcoin Core bitcoin\-wallet version v28.4.0rc1 +Bitcoin Core bitcoin\-wallet version v28.4.0rc2 .PP bitcoin\-wallet is an offline tool for creating and interacting with Bitcoin Core wallet files. By default bitcoin\-wallet will act on wallets in the default mainnet wallet directory in the datadir. diff --git a/doc/man/bitcoind.1 b/doc/man/bitcoind.1 index d7f9a5c18c0..b85801b027a 100644 --- a/doc/man/bitcoind.1 +++ b/doc/man/bitcoind.1 @@ -1,12 +1,12 @@ .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3. -.TH BITCOIND "1" "February 2026" "bitcoind v28.4.0rc1" "User Commands" +.TH BITCOIND "1" "February 2026" "bitcoind v28.4.0rc2" "User Commands" .SH NAME -bitcoind \- manual page for bitcoind v28.4.0rc1 +bitcoind \- manual page for bitcoind v28.4.0rc2 .SH SYNOPSIS .B bitcoind [\fI\,options\/\fR] \fI\,Start Bitcoin Core\/\fR .SH DESCRIPTION -Bitcoin Core version v28.4.0rc1 +Bitcoin Core version v28.4.0rc2 .SH OPTIONS .HP \-? diff --git a/doc/release-notes.md b/doc/release-notes.md index 297aaa71a47..e7d70ad0fa1 100644 --- a/doc/release-notes.md +++ b/doc/release-notes.md @@ -1,6 +1,6 @@ -Bitcoin Core version 28.4rc1 is now available from: +Bitcoin Core version 28.4rc2 is now available from: - + This release includes various bug fixes and performance improvements, as well as updated translations. @@ -42,6 +42,7 @@ Notable changes - #34156 wallet: fix unnamed legacy wallet migration failure - #34215 wallettool: fix unnamed createfromdump failure walletsdir deletion - #34226 wallet: test: Relative wallet failed migration cleanup +- #34370 Fix #34222 backport bugs ### P2P @@ -71,6 +72,7 @@ Thanks to everyone who directly contributed to this release: - fanquake - furszy - Hennadii Stepanov +- Luke Dashjr - m3dwards - Padraic Slattery - SatsAndSports diff --git a/src/wallet/bdb.cpp b/src/wallet/bdb.cpp index d82d8d45131..dfacf60ab24 100644 --- a/src/wallet/bdb.cpp +++ b/src/wallet/bdb.cpp @@ -16,6 +16,7 @@ #include #include +#include #include #include @@ -340,6 +341,53 @@ bool BerkeleyDatabase::Verify(bilingual_str& errorStr) return true; } +std::vector BerkeleyDatabase::Files() +{ + std::vector files; + // If the wallet is the *only* file, clean up the entire BDB environment + constexpr auto build_files_list = [](std::vector& files, const std::shared_ptr& env, const fs::path& filename) { + if (env->m_databases.size() != 1) return false; + + const auto env_dir = env->Directory(); + const auto db_subdir = env_dir / "database"; + if (fs::exists(db_subdir)) { + if (!fs::is_directory(db_subdir)) return false; + for (const auto& entry : fs::directory_iterator(db_subdir)) { + const auto& path = entry.path().filename(); + if (!fs::PathToString(path).starts_with("log.")) { + return false; + } + files.emplace_back(entry.path()); + } + } + const std::set allowed_paths = { + filename, + "db.log", + ".walletlock", + "database" + }; + for (const auto& entry : fs::directory_iterator(env_dir)) { + const auto& path = entry.path().filename(); + if (allowed_paths.contains(path)) { + files.emplace_back(entry.path()); + } else if (fs::is_directory(entry.path())) { + // Subdirectories can't possibly be using this db env, and is expected if this is a non-directory wallet + // Do not include them in Files, but still allow the env cleanup + } else { + return false; + } + } + return true; + }; + try { + if (build_files_list(files, env, m_filename)) return files; + } catch (...) { + // Give up building the comprehensive file list if any error occurs + } + // Otherwise, it's only really safe to delete the one wallet file + return {env->Directory() / m_filename}; +} + void BerkeleyEnvironment::CheckpointLSN(const std::string& strFile) { dbenv->txn_checkpoint(0, 0, 0); diff --git a/src/wallet/bdb.h b/src/wallet/bdb.h index f27c5e0e0ec..5026610aae5 100644 --- a/src/wallet/bdb.h +++ b/src/wallet/bdb.h @@ -132,20 +132,7 @@ public: /** Return path to main database filename */ std::string Filename() override { return fs::PathToString(env->Directory() / m_filename); } - std::vector Files() override - { - std::vector files; - files.emplace_back(env->Directory() / m_filename); - if (env->m_databases.size() == 1) { - files.emplace_back(env->Directory() / "db.log"); - files.emplace_back(env->Directory() / ".walletlock"); - files.emplace_back(env->Directory() / "database" / "log.0000000001"); - files.emplace_back(env->Directory() / "database"); - // Note that this list is not exhaustive as BDB may create more log files, and possibly other ones too - // However it should be good enough for the only calls to Files() - } - return files; - } + std::vector Files() override; std::string Format() override { return "bdb"; } /** diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 1dbb3c7f47d..822967fc866 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -4474,7 +4474,7 @@ util::Result MigrateLegacyToDescriptor(const std::string& walle to_reload = LoadWallet(context, name, /*load_on_start=*/std::nullopt, options, status, error, warnings); if (!to_reload) { LogError("Failed to load wallet '%s' after migration. Rolling back migration to preserve consistency. " - "Error cause: %s\n", wallet_name, error.original); + "Error cause: %s\n", name, error.original); return false; } return true; @@ -4523,6 +4523,12 @@ util::Result MigrateLegacyToDescriptor(const std::string& walle // First change to using SQLite if (!local_wallet->MigrateToSQLite(error)) return util::Error{error}; + // In case we're migrating from file to directory, move the backup into it + this_wallet_dir = fs::absolute(fs::PathFromString(local_wallet->GetDatabase().Filename())).parent_path(); + backup_path = this_wallet_dir / backup_filename; + fs::rename(res.backup_path, backup_path); + res.backup_path = backup_path; + // Do the migration of keys and scripts for non-blank wallets, and cleanup if it fails success = local_wallet->IsWalletFlagSet(WALLET_FLAG_BLANK_WALLET); if (!success) { @@ -4576,9 +4582,6 @@ util::Result MigrateLegacyToDescriptor(const std::string& walle } if (!success) { // Migration failed, cleanup - // Copy the backup to the actual wallet dir - fs::path temp_backup_location = fsbridge::AbsPathJoin(GetWalletDir(), backup_filename); - fs::rename(backup_path, temp_backup_location); // Make list of wallets to cleanup std::vector> created_wallets; @@ -4619,16 +4622,15 @@ util::Result MigrateLegacyToDescriptor(const std::string& walle } // Restore the backup - DatabaseStatus status; - std::vector warnings; - if (!RestoreWallet(context, temp_backup_location, wallet_name, /*load_on_start=*/std::nullopt, status, error, warnings)) { - error += _("\nUnable to restore backup of wallet."); + // Convert the backup file to the wallet db file by renaming it and moving it into the wallet's directory. + // Reload it into memory if the wallet was previously loaded. + bilingual_str restore_error; + const auto& ptr_wallet = RestoreWallet(context, backup_path, wallet_name, /*load_on_start=*/std::nullopt, status, restore_error, warnings); + if (!restore_error.empty()) { + error += restore_error + _("\nUnable to restore backup of wallet."); return util::Error{error}; } - // Move the backup to the wallet dir - fs::rename(temp_backup_location, backup_path); - return util::Error{error}; } return res; diff --git a/test/functional/tool_wallet.py b/test/functional/tool_wallet.py index 9ef671301f9..948e66a104c 100755 --- a/test/functional/tool_wallet.py +++ b/test/functional/tool_wallet.py @@ -411,17 +411,30 @@ class ToolWalletTest(BitcoinTestFramework): self.assert_raises_tool_error('Error: Checksum is not the correct size', '-wallet=badload', '-dumpfile={}'.format(bad_sum_wallet_dump), 'createfromdump') assert not (self.nodes[0].wallets_path / "badload").is_dir() if not self.options.descriptors: - os.rename(self.nodes[0].wallets_path / "wallet.dat", self.nodes[0].wallets_path / "default.wallet.dat") + os.rename(self.nodes[0].wallets_path / "wallet.dat", self.nodes[0].wallets_path / "../default.wallet.dat") + (self.nodes[0].wallets_path / "db.log").unlink(missing_ok=True) self.assert_raises_tool_error('Error: Checksum is not the correct size', '-wallet=', '-dumpfile={}'.format(bad_sum_wallet_dump), 'createfromdump') assert self.nodes[0].wallets_path.exists() assert not (self.nodes[0].wallets_path / "wallet.dat").exists() + if not self.options.descriptors: + assert not (self.nodes[0].wallets_path / "db.log").exists() self.log.info('Checking createfromdump with an unnamed wallet') self.do_tool_createfromdump("", "wallet.dump") assert (self.nodes[0].wallets_path / "wallet.dat").exists() os.unlink(self.nodes[0].wallets_path / "wallet.dat") if not self.options.descriptors: - os.rename(self.nodes[0].wallets_path / "default.wallet.dat", self.nodes[0].wallets_path / "wallet.dat") + os.rename(self.nodes[0].wallets_path / "../default.wallet.dat", self.nodes[0].wallets_path / "wallet.dat") + + self.log.info('Checking createfromdump with multiple non-directory wallets') + assert not (self.nodes[0].wallets_path / "wallet.dat").is_dir() + assert (self.nodes[0].wallets_path / "db.log").exists() + os.rename(self.nodes[0].wallets_path / "wallet.dat", self.nodes[0].wallets_path / "test.dat") + self.assert_raises_tool_error('Error: Checksum is not the correct size', '-wallet=', '-dumpfile={}'.format(bad_sum_wallet_dump), 'createfromdump') + assert not (self.nodes[0].wallets_path / "wallet.dat").exists() + assert (self.nodes[0].wallets_path / "test.dat").exists() + assert (self.nodes[0].wallets_path / "db.log").exists() + os.rename(self.nodes[0].wallets_path / "test.dat", self.nodes[0].wallets_path / "wallet.dat") def test_chainless_conflicts(self): self.log.info("Test wallet tool when wallet contains conflicting transactions")