Merge bitcoin/bitcoin#34620: [28.x] backports and 28.4rc2

44e6dda745aa1e6cbb80ee1a48206dc924453170 doc: update manpages for v28.4rc2 (Ava Chow)
46ff3698f800201cf1e9fa8c18d8dfbeea00a3bc doc: Update release notes for v28.4rc2 (Ava Chow)
6cf3d8226dee8fd4ae7061b1ef0507631383070f build: bump version to v28.4rc2 (Ava Chow)
931f12dcf79d090e88bde3e671aae53cc1e7c86f doc: Update Guix install for Debian/Ubuntu (MarcoFalke)
242b9ae3f3e65419ac29caa53da6abfdc142730f QA: tool_wallet: Check that db.log is deleted with a lone legacy wallet, but not with a shared db environment (Luke Dashjr)
c5d9f75c4b4a9f43e1f47f70367741c62da557b8 Bugfix: Wallet/Migration: Move backup into wallet directory when migrating from non-directory (Luke Dashjr)
6494072295154fdf3e896e892f0051afd3d1e677 Wallet/Migration: Skip moving the backup file back and forth for no reason (Luke Dashjr)
619480bd785e28b3fa251c629c7527e49edf53e6 Wallet/Migration: If loading the new watchonly or solvables wallet fails, log the correct wallet name in error message (Luke Dashjr)
29abedc97b9ca9e4ff4b9abb47ab90ea8586b327 Wallet/bdb: Safely and correctly list files only used by the single wallet (Luke Dashjr)

Pull request description:

  Backports:

  * #34370
  * #34671

  And the rc2 release process things

ACKs for top commit:
  willcl-ark:
    ACK 44e6dda745aa1e6cbb80ee1a48206dc924453170

Tree-SHA512: beb478a057b14c3ad9f1ce049f304fa1a5ff948c3492efbd39c51b9bb73a695a5382292513ee53f9ee64ecbbe6370cbbf2781bee01cf3dc262623be4110eacd8
This commit is contained in:
merge-script 2026-03-04 11:28:31 +00:00
commit ed80bb21e4
No known key found for this signature in database
GPG Key ID: 2EEB9F5CC09526C1
13 changed files with 105 additions and 53 deletions

View File

@ -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])

View File

@ -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

View File

@ -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\,<command> \/\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 <command> 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
\-?

View File

@ -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

View File

@ -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\,<hex-tx> \/\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
\-?

View File

@ -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
\-?

View File

@ -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.

View File

@ -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
\-?

View File

@ -1,6 +1,6 @@
Bitcoin Core version 28.4rc1 is now available from:
Bitcoin Core version 28.4rc2 is now available from:
<https://bitcoincore.org/bin/bitcoin-core-28.4/test.rc1/>
<https://bitcoincore.org/bin/bitcoin-core-28.4/test.rc2/>
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

View File

@ -16,6 +16,7 @@
#include <util/strencodings.h>
#include <util/translation.h>
#include <set>
#include <stdint.h>
#include <db_cxx.h>
@ -340,6 +341,53 @@ bool BerkeleyDatabase::Verify(bilingual_str& errorStr)
return true;
}
std::vector<fs::path> BerkeleyDatabase::Files()
{
std::vector<fs::path> files;
// If the wallet is the *only* file, clean up the entire BDB environment
constexpr auto build_files_list = [](std::vector<fs::path>& files, const std::shared_ptr<BerkeleyEnvironment>& 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<fs::path> 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);

View File

@ -132,20 +132,7 @@ public:
/** Return path to main database filename */
std::string Filename() override { return fs::PathToString(env->Directory() / m_filename); }
std::vector<fs::path> Files() override
{
std::vector<fs::path> 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<fs::path> Files() override;
std::string Format() override { return "bdb"; }
/**

View File

@ -4474,7 +4474,7 @@ util::Result<MigrationResult> 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<MigrationResult> 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<MigrationResult> 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<std::shared_ptr<CWallet>> created_wallets;
@ -4619,16 +4622,15 @@ util::Result<MigrationResult> MigrateLegacyToDescriptor(const std::string& walle
}
// Restore the backup
DatabaseStatus status;
std::vector<bilingual_str> 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;

View File

@ -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")