From 29abedc97b9ca9e4ff4b9abb47ab90ea8586b327 Mon Sep 17 00:00:00 2001 From: Luke Dashjr Date: Thu, 15 Jan 2026 19:01:19 +0000 Subject: [PATCH 1/9] Wallet/bdb: Safely and correctly list files only used by the single wallet If any other files exist in the directory, we cannot assume the sharable files are exclusively for this wallet. But if they are, this also cleans up other log.* files Github-Pull: #34370 Rebased-From: 7475d134f6a3a6039ab6b9d39706ade47c764aa8 --- src/wallet/bdb.cpp | 48 ++++++++++++++++++++++++++++++++++++++++++++++ src/wallet/bdb.h | 15 +-------------- 2 files changed, 49 insertions(+), 14 deletions(-) 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"; } /** From 619480bd785e28b3fa251c629c7527e49edf53e6 Mon Sep 17 00:00:00 2001 From: Luke Dashjr Date: Thu, 15 Jan 2026 19:03:23 +0000 Subject: [PATCH 2/9] Wallet/Migration: If loading the new watchonly or solvables wallet fails, log the correct wallet name in error message Github-Pull: #34370 Rebased-From: 60f529027c6eacbdc298fab50192f8c60d7082a1 --- src/wallet/wallet.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 1dbb3c7f47d..ec6973bb140 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; From 6494072295154fdf3e896e892f0051afd3d1e677 Mon Sep 17 00:00:00 2001 From: Luke Dashjr Date: Thu, 15 Jan 2026 19:27:23 +0000 Subject: [PATCH 3/9] Wallet/Migration: Skip moving the backup file back and forth for no reason Since we no longer delete the wallet directory, there's no need to vacate it The moving only served to risk errors by crossing filesystem boundaries (which fs::rename can't handle) Github-Pull: 34370 Rebased-From: cef01d0be5223e9d33efc897d7fbe5d0a08692c0 --- src/wallet/wallet.cpp | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index ec6973bb140..4c722b57406 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -4576,9 +4576,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 +4616,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; From c5d9f75c4b4a9f43e1f47f70367741c62da557b8 Mon Sep 17 00:00:00 2001 From: Luke Dashjr Date: Tue, 20 Jan 2026 18:20:14 +0000 Subject: [PATCH 4/9] Bugfix: Wallet/Migration: Move backup into wallet directory when migrating from non-directory While 30.x+ keep backup files in walletdir, 29.x places them in the migrated wallet directory Github-Pull: #34370 Rebased-From: 69a6b9b1152ba0bb3edab6d2a54509fd416b24c8 --- src/wallet/wallet.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 4c722b57406..822967fc866 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -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) { From 242b9ae3f3e65419ac29caa53da6abfdc142730f Mon Sep 17 00:00:00 2001 From: Luke Dashjr Date: Wed, 21 Jan 2026 21:32:40 +0000 Subject: [PATCH 5/9] QA: tool_wallet: Check that db.log is deleted with a lone legacy wallet, but not with a shared db environment Github-Pull: #34370 Rebased-From: 65173944ed60df3b9cffca95932aed8720921478 --- test/functional/tool_wallet.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) 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") From 931f12dcf79d090e88bde3e671aae53cc1e7c86f Mon Sep 17 00:00:00 2001 From: MarcoFalke <*~=`'#}+{/-|&$^_@721217.xyz> Date: Wed, 25 Feb 2026 19:51:24 +0100 Subject: [PATCH 6/9] doc: Update Guix install for Debian/Ubuntu Fixes https://github.com/bitcoin/bitcoin/issues/33982 Co-authored-by: Purple Ninja <129023353+ToRyVand@users.noreply.github.com> Github-Pull: bitcoin/bitcoin#34671 Rebased-From: faa70ca7642bd653cbd2e544c17fa58d2672afa4 --- contrib/guix/INSTALL.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) 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 From 6cf3d8226dee8fd4ae7061b1ef0507631383070f Mon Sep 17 00:00:00 2001 From: Ava Chow Date: Wed, 18 Feb 2026 17:05:47 -0800 Subject: [PATCH 7/9] build: bump version to v28.4rc2 --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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]) From 46ff3698f800201cf1e9fa8c18d8dfbeea00a3bc Mon Sep 17 00:00:00 2001 From: Ava Chow Date: Wed, 18 Feb 2026 17:06:40 -0800 Subject: [PATCH 8/9] doc: Update release notes for v28.4rc2 --- doc/release-notes.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) 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 From 44e6dda745aa1e6cbb80ee1a48206dc924453170 Mon Sep 17 00:00:00 2001 From: Ava Chow Date: Wed, 18 Feb 2026 17:15:32 -0800 Subject: [PATCH 9/9] doc: update manpages for v28.4rc2 --- doc/man/bitcoin-cli.1 | 6 +++--- doc/man/bitcoin-qt.1 | 6 +++--- doc/man/bitcoin-tx.1 | 6 +++--- doc/man/bitcoin-util.1 | 6 +++--- doc/man/bitcoin-wallet.1 | 6 +++--- doc/man/bitcoind.1 | 6 +++--- 6 files changed, 18 insertions(+), 18 deletions(-) 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 \-?