Compare commits

..

33 Commits

Author SHA1 Message Date
merge-script
4d7d5f6b79 Merge bitcoin/bitcoin#34229: [30.2] Backports & Final
04a996b1a7 doc: update manual pages for v30.2 (fanquake)
ed355b8f57 build: bump version to v30.2 (fanquake)
6c98d68be1 doc: update release notes for v30.2 (fanquake)
6d86b32e78 guix: Fix `osslsigncode` tests (Hennadii Stepanov)
1dae0027cd wallet: test: Failed migration cleanup (David Gumberg)
9e59047a7e test: migration, avoid backup name mismatch in default_wallet_failure (furszy)

Pull request description:

  Backports:
  * #34221
  * #34226
  * #34227

ACKs for top commit:
  hebasto:
    ACK 04a996b1a7, I have reviewed the code and it looks OK.
  willcl-ark:
    ACK 04a996b1a7
  marcofleon:
    ACK 04a996b1a7

Tree-SHA512: 3389b9b629dcb920186b383353fd386bb757967d224e0267501b5e2083dc1e6cba051df6ef646de05c0e58fd43c9f549b9175eefb77fed1fe9ab7d1648b2d9e7
2026-01-09 14:04:54 +00:00
fanquake
04a996b1a7 doc: update manual pages for v30.2 2026-01-09 11:23:01 +00:00
fanquake
ed355b8f57 build: bump version to v30.2 2026-01-09 11:17:00 +00:00
fanquake
6c98d68be1 doc: update release notes for v30.2 2026-01-09 11:16:32 +00:00
Hennadii Stepanov
6d86b32e78 guix: Fix osslsigncode tests
Github-Pull: #34227
Rebased-From: 194114daf3
2026-01-09 11:15:58 +00:00
David Gumberg
1dae0027cd wallet: test: Failed migration cleanup
Refactor a common way to perform the failed migration test that exists
for default wallets, and add relative-path wallets and absolute-path
wallets.

Github-Pull: #34226
Rebased-From: eeaf28dbe0
2026-01-09 10:11:19 +00:00
furszy
9e59047a7e test: migration, avoid backup name mismatch in default_wallet_failure
The test calls migrate_and_get_rpc(), which sets mock time internally.
The caller caches a mock time value and later relies on it to predict the
backup filename, so setting the mock time again could cause a naming
mismatch.

Fix this by calling the migration RPC directly. Since the test expects the
migration to fail, migrate_and_get_rpc() is unnecessary here.

Github-Pull: #34221
Rebased-From: cbf0bd35bb
2026-01-08 10:21:49 +00:00
merge-script
abb6ae2ec5 Merge bitcoin/bitcoin#34209: [30.x]: Final backports & 30.2rc1
483d158f53 doc: update manual pages for v30.2rc1 (fanquake)
747a863f5b build: bump version to v30.2rc1 (fanquake)
cc3cdbe921 doc: update release notes for v30.2rc1 (fanquake)
c4082a45e6 wallettool: do not use fs::remove_all in createfromdump cleanup (Ava Chow)
185ca0e391 test: coverage for migration failure when last sync is beyond prune height (furszy)
bc71372c0e wallet: migration, fix watch-only and solvables wallets names (furszy)
bef4b1fdee wallet: improve post-migration logging (furszy)
ac940ac2ca test: restorewallet, coverage for existing dirs, unnamed wallet and prune failure (furszy)
8e5c02a77f test: add coverage for unnamed wallet migration failure (furszy)
ac4d0956cc wallet: fix unnamed wallet migration failure (furszy)
454ac8e7db wallet: RestoreWallet failure, erase only what was created (furszy)

Pull request description:

  Backports:
  * #34156
  * #34215

ACKs for top commit:
  darosior:
    ACK 483d158f53
  willcl-ark:
    tACK 483d158f53
  janb84:
    ACK 483d158f53
  w0xlt:
    ACK 483d158f53
  marcofleon:
    lgtm ACK 483d158f53

Tree-SHA512: b43485a8745a663d3a031113a6a266af699b3e70b77f72a0e69ea1986adcaa0ee0d4ce51aacd0fde91611cb4bdba9b6be52e5dca59ff8b12135715b8b1ed5ef2
2026-01-07 15:44:56 +00:00
fanquake
483d158f53 doc: update manual pages for v30.2rc1 2026-01-07 14:34:29 +00:00
fanquake
747a863f5b build: bump version to v30.2rc1 2026-01-07 14:34:28 +00:00
fanquake
cc3cdbe921 doc: update release notes for v30.2rc1 2026-01-07 14:34:24 +00:00
Ava Chow
c4082a45e6 wallettool: do not use fs::remove_all in createfromdump cleanup
Github-Pull: #34215
Rebased-From: f78f6f1dc8
2026-01-07 14:33:39 +00:00
furszy
185ca0e391 test: coverage for migration failure when last sync is beyond prune height
Github-Pull: #34156
Rebased-From: b7c34d08dd
2026-01-07 11:20:07 +00:00
furszy
bc71372c0e wallet: migration, fix watch-only and solvables wallets names
Because the default wallet has no name, the watch-only and solvables
wallets created during migration end up having no name either.

This fixes it by applying the same prefix name we use for the backup
file for an unnamed default wallet.

Before: watch-only wallet named "_watchonly"
After:  watch-only wallet named "default_wallet_watchonly"

Github-Pull: #34156
Rebased-From: 82caa8193a
2026-01-07 11:20:04 +00:00
furszy
bef4b1fdee wallet: improve post-migration logging
Right now, after migration the last message users see is "migration completed",
but the migration isn't actually finished yet. We still need to load the new wallets
to ensure consistency, and if that fails, the migration will be rolled back. This
can be confusing for users.

This change logs the post-migration loading step and if a wallet fails to load and
the migration will be rolled back.

Github-Pull: #34156
Rebased-From: d70b159c42
2026-01-07 11:20:00 +00:00
furszy
ac940ac2ca test: restorewallet, coverage for existing dirs, unnamed wallet and prune failure
The first test verifies that restoring into an existing empty directory
or a directory with no .dat db files succeeds, while restoring into a
dir with a .dat file fails.

The second test covers restoring into the default unnamed wallet
(wallet.dat), which also implicitly exercises the recovery path used
after a failed migration.

The third test covers failure during restore on a prune node. When
the wallet last sync was beyond the pruning height.

Github-Pull: #34156
Rebased-From: f011e0f068
2026-01-07 11:19:57 +00:00
furszy
8e5c02a77f test: add coverage for unnamed wallet migration failure
Verifies that a failed migration of the unnamed (default) wallet
does not erase the main /wallets/ directory, and also that the
backup file exists.

Github-Pull: #34156
Rebased-From: 36093bde63
2026-01-07 11:19:39 +00:00
furszy
ac4d0956cc wallet: fix unnamed wallet migration failure
When migrating any legacy unnamed wallet, a failed migration would
cause the cleanup logic to remove its parent directory. Since this
type of legacy wallet lives directly in the main '/wallets/' folder,
this resulted in unintentionally erasing all wallets, including the
backup file.

To be fully safe, we will no longer call `fs::remove_all`. Instead,
we only erase the individual db files we have created, leaving
everything else intact. The created wallets parent directories are
erased only if they are empty.
As part of this last change, `RestoreWallet` was modified to allow
an existing directory as the destination, since we no longer remove
the original wallet directory (we only remove the files we created
inside it). This also fixes the restore of top-level default wallets
during failures, which were failing due to the directory existence
check that always returns true for the /wallets/ directory.

This bug started after:
f6ee59b6e2
Previously, the `fs::copy_file` call was failing for top-level wallets,
which prevented the `fs::remove_all` call from being reached.

Github-Pull: #34156
Rebased-From: f4c7e28e80
2026-01-07 11:19:35 +00:00
furszy
454ac8e7db wallet: RestoreWallet failure, erase only what was created
Track what RestoreWallet creates so only those files and directories
are removed during a failure and nothing else. Preexisting paths
must be left untouched.

Note:
Using fs::remove_all() instead of fs::remove() in RestoreWallet does
not cause any problems currently, but the change is necessary for the
next commit which extends RestoreWallet to work with existing directories,
which may contain files that must not be deleted.

Github-Pull: #34156
Rebased-From: 4ed0693a3f
2026-01-07 11:18:38 +00:00
merge-script
e9f73b8149 Merge bitcoin/bitcoin#34192: [30.x] Backports
f22122bc27 doc: update release notes for v30.x (fanquake)
7568bc3ab0 test: Avoid hard time.sleep(1) in feature_init.py (MarcoFalke)
c065bcd2d7 init: Signal m_tip_block_cv on Ctrl-C (Ryan Ofsky)
6983c7d769 test: Test SIGTERM handling during waitforblockheight call (Ryan Ofsky)
8769c718f4 doc: Update OpenBSD Build Guide (Hennadii Stepanov)
ed0774bd08 doc: update copyright year (fanquake)
f620dde411 fuzz: doc: remove any mention to address_deserialize_v2 (brunoerg)
b734c4026b guix: reduce allowed exported symbols (fanquake)
7ea855fd55 build: Update minimum required Boost version (Hennadii Stepanov)

Pull request description:

  Backports:
  * #33511
  * #33950
  * #34091
  * #34107
  * #34137
  * #34174
  * #34182

ACKs for top commit:
  willcl-ark:
    ACK f22122bc27
  marcofleon:
    ACK f22122bc27

Tree-SHA512: a4486d331ce8fef07e611188098a965348cdfb081b81e5a2c700e119b1cf3c28cddbeca883ab512a510f6aaa5fc14bf896f70823fa63afcdd0762d9fbda0118b
2026-01-05 17:27:19 +00:00
fanquake
f22122bc27 doc: update release notes for v30.x 2026-01-05 12:09:33 +00:00
MarcoFalke
7568bc3ab0 test: Avoid hard time.sleep(1) in feature_init.py
Github-Pull: #34137
Rebased-From: fa727e3ec9
2026-01-05 12:09:02 +00:00
Ryan Ofsky
c065bcd2d7 init: Signal m_tip_block_cv on Ctrl-C
Signal m_tip_block_cv when Ctrl-C is pressed or SIGTERM is received, the same
way it is currently signalled when the `stop` RPC is called. This lets RPC
calls like `waitforblockheight` and IPC calls like `waitTipChanged` be
interrupted, instead of waiting for their original timeouts and delaying
shutdown.

Historical notes:

- The behavior where `stop` RPC signals `m_tip_block_cv`, but CTRL-C does not,
  has been around since the condition variable was introduced in #30409
  (7eccdaf160).
- The signaling was later moved without changing behavior in #30967
  (5ca28ef28b). This commit moves it again to
  the Interrupt() function, which is probably the place it should have been
  added initially, so it works for Ctrl-C shutdowns as well as `stop`
  shutdowns.
- A Qt shutdown bug calling wait methods was fixed previously in #18452
  (da73f1513a), and this change updates that
  fix to avoid the hang happening again in Qt.

Github-Pull: #33511
Rebased-From: c25a5e670b
2026-01-05 12:07:02 +00:00
Ryan Ofsky
6983c7d769 test: Test SIGTERM handling during waitforblockheight call
Currently when CTRL-C is pressed and there is an active `waitforblockheight`,
or `waitforblock`, or `waitfornewblock` RPC call, or a mining interface
`waitTipChanged` IPC call with a long timeout, the node will not shut down
right away, and will wait for the timeout to be reached before exiting.

This behavior is not ideal and only happens when the node is stopped with
CTRL-C or SIGTERM. When the node is stopped with `bitcoin-cli stop`, the wait
calls are interrupted and the node does shut down right away.

The next commit improves node behavior. This commit just adds test coverage to
simplify the next commit and clarify the change in behavior there.

Github-Pull: #33511
Rebased-From: 6a29f79006
2026-01-05 12:06:57 +00:00
Hennadii Stepanov
8769c718f4 doc: Update OpenBSD Build Guide
Github-Pull: #34182
Rebased-From: 84d8c52662
2026-01-05 11:58:47 +00:00
fanquake
ed0774bd08 doc: update copyright year
Github-Pull: #34174
Rebased-From: b23b901363
2026-01-05 11:41:23 +00:00
brunoerg
f620dde411 fuzz: doc: remove any mention to address_deserialize_v2
Github-Pull: #34091
Rebased-From: caf4843a59
2025-12-31 12:30:48 +00:00
fanquake
b734c4026b guix: reduce allowed exported symbols
Github-Pull: #33950
Rebased-From: 7b90b4f5bb
2025-12-31 12:27:29 +00:00
Hennadii Stepanov
7ea855fd55 build: Update minimum required Boost version
Building with Boost 1.73.0 is broken.

Github-Pull: #34107
Rebased-From: f480c1e717
2025-12-31 12:22:49 +00:00
merge-script
dd47caee82 Merge bitcoin/bitcoin#34092: [30.x] Finalise v30.1
2a21824b11 doc: update manual pages for v30.1 (fanquake)
57264431ff build: bump version to v30.1 (fanquake)
26294d627e doc: update release notes for v30.1 (fanquake)

Pull request description:

  Finalise `v30.1`.

ACKs for top commit:
  waketraindev:
    ACK 2a21824b11
  hebasto:
    ACK 2a21824b11, I have reviewed the code and it looks OK.
  brunoerg:
    code review ACK 2a21824b11
  janb84:
    ACK 2a21824b11
  w0xlt:
    Code Review ACK 2a21824b11

Tree-SHA512: 622d289db49c793fcb7b415d5113495eb824dc87780b082ccd5c43e37d116dca5a606340979a77cad1bfdbfac2c5fd5f18262746ed0e492d9e87bb0d802386ee
2025-12-18 15:54:33 +00:00
fanquake
2a21824b11 doc: update manual pages for v30.1 2025-12-17 15:40:17 +00:00
fanquake
57264431ff build: bump version to v30.1 2025-12-17 15:29:05 +00:00
fanquake
26294d627e doc: update release notes for v30.1 2025-12-17 15:27:46 +00:00
26 changed files with 432 additions and 114 deletions

View File

@@ -28,11 +28,11 @@ get_directory_property(precious_variables CACHE_VARIABLES)
#=============================
set(CLIENT_NAME "Bitcoin Core")
set(CLIENT_VERSION_MAJOR 30)
set(CLIENT_VERSION_MINOR 1)
set(CLIENT_VERSION_MINOR 2)
set(CLIENT_VERSION_BUILD 0)
set(CLIENT_VERSION_RC 1)
set(CLIENT_VERSION_RC 0)
set(CLIENT_VERSION_IS_RELEASE "true")
set(COPYRIGHT_YEAR "2025")
set(COPYRIGHT_YEAR "2026")
# During the enabling of the CXX and CXXOBJ languages, we modify
# CMake's compiler/linker invocation strings by appending the content

View File

@@ -1,7 +1,7 @@
The MIT License (MIT)
Copyright (c) 2009-2025 The Bitcoin Core developers
Copyright (c) 2009-2025 Bitcoin Developers
Copyright (c) 2009-2026 The Bitcoin Core developers
Copyright (c) 2009-2026 Bitcoin Developers
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@@ -29,7 +29,7 @@ function(add_boost_if_needed)
endif()
endif()
find_package(Boost 1.73.0 REQUIRED CONFIG)
find_package(Boost 1.74.0 REQUIRED CONFIG)
mark_as_advanced(Boost_INCLUDE_DIR boost_headers_DIR)
# Workaround for a bug in NetBSD pkgsrc.
# See: https://github.com/NetBSD/pkgsrc/issues/167.

View File

@@ -5,7 +5,7 @@ Upstream-Contact: Satoshi Nakamoto <satoshin@gmx.com>
Source: https://github.com/bitcoin/bitcoin
Files: *
Copyright: 2009-2025, Bitcoin Core Developers
Copyright: 2009-2026, Bitcoin Core Developers
License: Expat
Comment: The Bitcoin Core Developers encompasses all contributors to the
project, listed in the release notes or the git log.

View File

@@ -2,6 +2,7 @@
((gnu packages bash) #:select (bash-minimal))
(gnu packages bison)
((gnu packages certs) #:select (nss-certs))
((gnu packages check) #:select (libfaketime))
((gnu packages cmake) #:select (cmake-minimal))
(gnu packages commencement)
(gnu packages compression)
@@ -209,7 +210,17 @@ and abstract ELF, PE and MachO formats.")
(base32
"1j47vwq4caxfv0xw68kw5yh00qcpbd56d7rq6c483ma3y7s96yyz"))))
(build-system cmake-build-system)
(inputs (list openssl))
(arguments
(list
#:phases
#~(modify-phases %standard-phases
(replace 'check
(lambda* (#:key tests? #:allow-other-keys)
(if tests?
(invoke "faketime" "-f" "@2025-01-01 00:00:00" ;; Tests fail after 2025.
"ctest" "--output-on-failure" "--no-tests=error")
(format #t "test suite not run~%")))))))
(inputs (list libfaketime openssl))
(home-page "https://github.com/mtrojnar/osslsigncode")
(synopsis "Authenticode signing and timestamping tool")
(description "osslsigncode is a small tool that implements part of the

View File

@@ -46,8 +46,7 @@ MAX_VERSIONS = {
# Ignore symbols that are exported as part of every executable
IGNORE_EXPORTS = {
'environ', '_environ', '__environ', '_fini', '_init', 'stdin',
'stdout', 'stderr',
'stdin', 'stdout', 'stderr',
}
# Expected linker-loader names can be found here:

View File

@@ -1,6 +1,6 @@
# OpenBSD Build Guide
**Updated for OpenBSD [7.6](https://www.openbsd.org/76.html)**
**Updated for OpenBSD [7.8](https://www.openbsd.org/78.html)**
This guide describes how to build bitcoind, command-line utilities, and GUI on OpenBSD.
@@ -21,8 +21,11 @@ pkg_add sqlite3
To build Bitcoin Core without the wallet, use `-DENABLE_WALLET=OFF`.
Cap'n Proto is needed for IPC functionality (see [multiprocess.md](multiprocess.md))
and can be built from source: https://capnproto.org/install.html
Cap'n Proto is needed for IPC functionality (see [multiprocess.md](multiprocess.md)):
```bash
pkg_add capnproto
```
Compile with `-DENABLE_IPC=OFF` if you do not need IPC functionality.

View File

@@ -19,7 +19,7 @@ Bitcoin Core requires one of the following compilers.
| Dependency | Releases | Minimum required |
| --- | --- | --- |
| [Boost](../depends/packages/boost.mk) | [link](https://www.boost.org/users/download/) | [1.73.0](https://github.com/bitcoin/bitcoin/pull/29066) |
| [Boost](../depends/packages/boost.mk) | [link](https://www.boost.org/users/download/) | [1.74.0](https://github.com/bitcoin/bitcoin/pull/34107) |
| CMake | [link](https://cmake.org/) | [3.22](https://github.com/bitcoin/bitcoin/pull/30454) |
| [libevent](../depends/packages/libevent.mk) | [link](https://github.com/libevent/libevent/releases) | [2.1.8](https://github.com/bitcoin/bitcoin/pull/24681) |

View File

@@ -79,7 +79,7 @@ of the test. Just make sure to use double-dash to distinguish them from the
fuzzer's own arguments:
```sh
$ FUZZ=address_deserialize_v2 build_fuzz/bin/fuzz -runs=1 fuzz_corpora/address_deserialize_v2 --checkaddrman=5 --printtoconsole=1
$ FUZZ=address_deserialize build_fuzz/bin/fuzz -runs=1 fuzz_corpora/address_deserialize --checkaddrman=5 --printtoconsole=1
```
## Fuzzing corpora

View File

@@ -1,7 +1,7 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3.
.TH BITCOIN-CLI "1" "December 2025" "bitcoin-cli v30.1.0rc1" "User Commands"
.TH BITCOIN-CLI "1" "January 2026" "bitcoin-cli v30.2.0" "User Commands"
.SH NAME
bitcoin-cli \- manual page for bitcoin-cli v30.1.0rc1
bitcoin-cli \- manual page for bitcoin-cli v30.2.0
.SH SYNOPSIS
.B bitcoin-cli
[\fI\,options\/\fR] \fI\,<command> \/\fR[\fI\,params\/\fR]
@@ -15,7 +15,7 @@ bitcoin-cli \- manual page for bitcoin-cli v30.1.0rc1
.B bitcoin-cli
[\fI\,options\/\fR] \fI\,help <command>\/\fR
.SH DESCRIPTION
Bitcoin Core RPC client version v30.1.0rc1
Bitcoin Core RPC client version v30.2.0
.PP
The bitcoin\-cli utility provides a command line interface to interact with a Bitcoin Core RPC server.
.PP
@@ -188,7 +188,7 @@ additional "outonly" (or "o") argument can be passed to see
outbound peers only. Pass "help" (or "h") for detailed help
documentation.
.SH COPYRIGHT
Copyright (C) 2009-2025 The Bitcoin Core developers
Copyright (C) 2009-2026 The Bitcoin Core developers
Please contribute if you find Bitcoin Core useful. Visit
<https://bitcoincore.org/> for further information about the software.

View File

@@ -1,12 +1,12 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3.
.TH BITCOIN-QT "1" "December 2025" "bitcoin-qt v30.1.0rc1" "User Commands"
.TH BITCOIN-QT "1" "January 2026" "bitcoin-qt v30.2.0" "User Commands"
.SH NAME
bitcoin-qt \- manual page for bitcoin-qt v30.1.0rc1
bitcoin-qt \- manual page for bitcoin-qt v30.2.0
.SH SYNOPSIS
.B bitcoin-qt
[\fI\,options\/\fR] [\fI\,URI\/\fR]
.SH DESCRIPTION
Bitcoin Core version v30.1.0rc1
Bitcoin Core version v30.2.0
.PP
The bitcoin\-qt application provides a graphical interface for interacting with Bitcoin Core.
.PP
@@ -839,7 +839,7 @@ Reset all settings changed in the GUI
.IP
Show splash screen on startup (default: 1)
.SH COPYRIGHT
Copyright (C) 2009-2025 The Bitcoin Core developers
Copyright (C) 2009-2026 The Bitcoin Core developers
Please contribute if you find Bitcoin Core useful. Visit
<https://bitcoincore.org/> for further information about the software.

View File

@@ -1,7 +1,7 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3.
.TH BITCOIN-TX "1" "December 2025" "bitcoin-tx v30.1.0rc1" "User Commands"
.TH BITCOIN-TX "1" "January 2026" "bitcoin-tx v30.2.0" "User Commands"
.SH NAME
bitcoin-tx \- manual page for bitcoin-tx v30.1.0rc1
bitcoin-tx \- manual page for bitcoin-tx v30.2.0
.SH SYNOPSIS
.B bitcoin-tx
[\fI\,options\/\fR] \fI\,<hex-tx> \/\fR[\fI\,commands\/\fR]
@@ -9,7 +9,7 @@ bitcoin-tx \- manual page for bitcoin-tx v30.1.0rc1
.B bitcoin-tx
[\fI\,options\/\fR] \fI\,-create \/\fR[\fI\,commands\/\fR]
.SH DESCRIPTION
Bitcoin Core bitcoin\-tx utility version v30.1.0rc1
Bitcoin Core bitcoin\-tx utility version v30.2.0
.PP
The bitcoin\-tx tool is used for creating and modifying bitcoin transactions.
.PP
@@ -146,7 +146,7 @@ set=NAME:JSON\-STRING
.IP
Set register NAME to given JSON\-STRING
.SH COPYRIGHT
Copyright (C) 2009-2025 The Bitcoin Core developers
Copyright (C) 2009-2026 The Bitcoin Core developers
Please contribute if you find Bitcoin Core useful. Visit
<https://bitcoincore.org/> for further information about the software.

View File

@@ -1,7 +1,7 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3.
.TH BITCOIN-UTIL "1" "December 2025" "bitcoin-util v30.1.0rc1" "User Commands"
.TH BITCOIN-UTIL "1" "January 2026" "bitcoin-util v30.2.0" "User Commands"
.SH NAME
bitcoin-util \- manual page for bitcoin-util v30.1.0rc1
bitcoin-util \- manual page for bitcoin-util v30.2.0
.SH SYNOPSIS
.B bitcoin-util
[\fI\,options\/\fR] [\fI\,command\/\fR]
@@ -9,7 +9,7 @@ bitcoin-util \- manual page for bitcoin-util v30.1.0rc1
.B bitcoin-util
[\fI\,options\/\fR] \fI\,grind <hex-block-header>\/\fR
.SH DESCRIPTION
Bitcoin Core bitcoin\-util utility version v30.1.0rc1
Bitcoin Core bitcoin\-util utility version v30.2.0
.PP
The bitcoin\-util tool provides bitcoin related functionality that does not rely on the ability to access a running node. Available [commands] are listed below.
.SH OPTIONS
@@ -65,7 +65,7 @@ grind
.IP
Perform proof of work on hex header string
.SH COPYRIGHT
Copyright (C) 2009-2025 The Bitcoin Core developers
Copyright (C) 2009-2026 The Bitcoin Core developers
Please contribute if you find Bitcoin Core useful. Visit
<https://bitcoincore.org/> for further information about the software.

View File

@@ -1,12 +1,12 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3.
.TH BITCOIN-WALLET "1" "December 2025" "bitcoin-wallet v30.1.0rc1" "User Commands"
.TH BITCOIN-WALLET "1" "January 2026" "bitcoin-wallet v30.2.0" "User Commands"
.SH NAME
bitcoin-wallet \- manual page for bitcoin-wallet v30.1.0rc1
bitcoin-wallet \- manual page for bitcoin-wallet v30.2.0
.SH SYNOPSIS
.B bitcoin-wallet
[\fI\,options\/\fR] \fI\,<command>\/\fR
.SH DESCRIPTION
Bitcoin Core bitcoin\-wallet utility version v30.1.0rc1
Bitcoin Core bitcoin\-wallet utility version v30.2.0
.PP
bitcoin\-wallet is an offline tool for creating and interacting with Bitcoin Core wallet files.
.PP
@@ -100,7 +100,7 @@ info
.IP
Get wallet info
.SH COPYRIGHT
Copyright (C) 2009-2025 The Bitcoin Core developers
Copyright (C) 2009-2026 The Bitcoin Core developers
Please contribute if you find Bitcoin Core useful. Visit
<https://bitcoincore.org/> for further information about the software.

View File

@@ -1,7 +1,7 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3.
.TH BITCOIN "1" "December 2025" "bitcoin v30.1.0rc1" "User Commands"
.TH BITCOIN "1" "January 2026" "bitcoin v30.2.0" "User Commands"
.SH NAME
bitcoin \- manual page for bitcoin v30.1.0rc1
bitcoin \- manual page for bitcoin v30.2.0
.SH SYNOPSIS
.B bitcoin
[\fI\,OPTIONS\/\fR] \fI\,COMMAND\/\fR...
@@ -46,7 +46,7 @@ chainstate [ARGS] Run bitcoin kernel chainstate util, equivalent to running 'bit
test [ARGS] Run unit tests, equivalent to running 'test_bitcoin [ARGS]'.
test\-gui [ARGS] Run GUI unit tests, equivalent to running 'test_bitcoin\-qt [ARGS]'.
.SH COPYRIGHT
Copyright (C) 2009-2025 The Bitcoin Core developers
Copyright (C) 2009-2026 The Bitcoin Core developers
Please contribute if you find Bitcoin Core useful. Visit
<https://bitcoincore.org/> for further information about the software.

View File

@@ -1,12 +1,12 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3.
.TH BITCOIND "1" "December 2025" "bitcoind v30.1.0rc1" "User Commands"
.TH BITCOIND "1" "January 2026" "bitcoind v30.2.0" "User Commands"
.SH NAME
bitcoind \- manual page for bitcoind v30.1.0rc1
bitcoind \- manual page for bitcoind v30.2.0
.SH SYNOPSIS
.B bitcoind
[\fI\,options\/\fR]
.SH DESCRIPTION
Bitcoin Core daemon version v30.1.0rc1 bitcoind
Bitcoin Core daemon version v30.2.0 bitcoind
.PP
The Bitcoin Core daemon (bitcoind) is a headless program that connects to the Bitcoin network to validate and relay transactions and blocks, as well as relaying addresses.
.PP
@@ -817,7 +817,7 @@ subject to empty whitelists.
.IP
Accept command line and JSON\-RPC commands
.SH COPYRIGHT
Copyright (C) 2009-2025 The Bitcoin Core developers
Copyright (C) 2009-2026 The Bitcoin Core developers
Please contribute if you find Bitcoin Core useful. Visit
<https://bitcoincore.org/> for further information about the software.

View File

@@ -1,9 +1,9 @@
v30.x Release Notes
v30.2 Release Notes
===================
Bitcoin Core version v30.1rc1 is now available from:
Bitcoin Core version v30.2 is now available from:
<https://bitcoincore.org/bin/bitcoin-core-30.1/test.rc1/>
<https://bitcoincore.org/bin/bitcoin-core-30.2/>
This release includes new features, various bug fixes and performance
improvements, as well as updated translations.
@@ -42,48 +42,36 @@ Notable changes
### Wallet
- #33528 wallet: don't consider unconfirmed TRUC coins with ancestors
### Build
- #33580 depends: Use `$(package)_file_name` when downloading from the fallback
- #33906 depends: Add patch for Windows11Style plugin
- #32009 contrib: turn off compression of macOS SDK to fix determinism
- #34156 wallet: fix unnamed legacy wallet migration failure
- #34215 wallettool: fix unnamed createfromdump failure walletsdir deletion
- #34221 test: migration, avoid backup name mismatch in default_wallet_failure
### IPC
- #33229 multiprocess: Don't require bitcoin -m argument when IPC options are used
- #33517 multiprocess: Fix high overhead from message logging
- #33519 Update libmultiprocess subtree in 30.x branch
- #33566 miner: fix empty mempool case for waitNext()
- #33676 interfaces: enable cancelling running waitNext calls
- #33511 init: Fix Ctrl-C shutdown hangs during wait calls
### P2P
### Build
- #33723 chainparams: remove dnsseed.bitcoin.dashjr-list-of-p2p-nodes.us
### GUI
- gui#899 qt: Modernize custom filtering
- gui#901 Add createwallet, createwalletdescriptor, and migratewallet to history filter
- #33950 guix: reduce allowed exported symbols
- #34107 build: Update minimum required Boost version
- #34227 guix: Fix osslsigncode tests
### Test
- #33612 test: change log rate limit version gate
- #34137 test: Avoid hard time.sleep(1) in feature_init.py
- #34226 wallet: test: Relative wallet failed migration cleanup
### Fuzz
- #34091 fuzz: doc: remove any mention to address_deserialize_v2
### Doc
- #33630 doc: correct topology requirements in submitpackage helptext
- #33826 scripted-diff: Remove obsolete comment
- #33827 doc: Correct pkgin command usage on NetBSD
- #34182 doc: Update OpenBSD Build Guide
### Misc
- #33508 ci: fix buildx gha cache authentication on forks
- #33558 ci: Use native platform for win-cross task
- #33581 ci: Properly include $FILE_ENV in DEPENDS_HASH
- #33744 ci: Fix lint runner selection (and docker cache)
- #33996 contrib: fix manpage generation
- #34174 doc: update copyright year to 2026
Credits
=======
@@ -91,18 +79,13 @@ Credits
Thanks to everyone who directly contributed to this release:
- Ava Chow
- Cory Fields
- Eugene Siegel
- brunoerg
- davidgumberg
- fanquake
- glozow
- furszy
- Hennadii Stepanov
- ismaelsadeeq
- MarcoFalke
- Ryan Ofsky
- SatsAndSports
- Sjors Provoost
- WakeTrainDev
- willcl-ark
As well as to everyone that helped with translations on
[Transifex](https://explore.transifex.com/bitcoin/bitcoin/).

View File

@@ -215,8 +215,6 @@ void InitContext(NodeContext& node)
node.shutdown_request = [&node] {
assert(node.shutdown_signal);
if (!(*node.shutdown_signal)()) return false;
// Wake any threads that may be waiting for the tip to change.
if (node.notifications) WITH_LOCK(node.notifications->m_tip_block_mutex, node.notifications->m_tip_block_cv.notify_all());
return true;
};
}
@@ -267,6 +265,8 @@ void Interrupt(NodeContext& node)
#if HAVE_SYSTEM
ShutdownNotify(*node.args);
#endif
// Wake any threads that may be waiting for the tip to change.
if (node.notifications) WITH_LOCK(node.notifications->m_tip_block_mutex, node.notifications->m_tip_block_cv.notify_all());
InterruptHTTPServer();
InterruptHTTPRPC();
InterruptRPC();

View File

@@ -132,7 +132,6 @@ public:
}
void appShutdown() override
{
Interrupt(*m_context);
Shutdown(*m_context);
}
void startShutdown() override
@@ -141,12 +140,7 @@ public:
if (!(Assert(ctx.shutdown_request))()) {
LogError("Failed to send shutdown signal\n");
}
// Stop RPC for clean shutdown if any of waitfor* commands is executed.
if (args().GetBoolArg("-server", false)) {
InterruptRPC();
StopRPC();
}
Interrupt(*m_context);
}
bool shutdownRequested() override { return ShutdownRequested(*Assert(m_context)); };
bool isSettingIgnored(const std::string& name) override

View File

@@ -51,7 +51,7 @@ static std::vector<const char*> g_args;
static void SetArgs(int argc, char** argv) {
for (int i = 1; i < argc; ++i) {
// Only take into account arguments that start with `--`. The others are for the fuzz engine:
// `fuzz -runs=1 fuzz_corpora/address_deserialize_v2 --checkaddrman=5`
// `fuzz -runs=1 fuzz_corpora/address_deserialize --checkaddrman=5`
if (strlen(argv[i]) > 2 && argv[i][0] == '-' && argv[i][1] == '-') {
g_args.push_back(argv[i]);
}

View File

@@ -276,11 +276,17 @@ bool CreateFromDump(const ArgsManager& args, const std::string& name, const fs::
dump_file.close();
}
// On failure, gather the paths to remove
std::vector<fs::path> paths_to_remove = wallet->GetDatabase().Files();
if (!name.empty()) paths_to_remove.push_back(wallet_path);
wallet.reset(); // The pointer deleter will close the wallet for us.
// Remove the wallet dir if we have a failure
if (!ret) {
fs::remove_all(wallet_path);
for (const auto& p : paths_to_remove) {
fs::remove(p);
}
}
return ret;

View File

@@ -469,6 +469,8 @@ std::shared_ptr<CWallet> RestoreWallet(WalletContext& context, const fs::path& b
const fs::path wallet_path = fsbridge::AbsPathJoin(GetWalletDir(), fs::u8path(wallet_name));
auto wallet_file = wallet_path / "wallet.dat";
std::shared_ptr<CWallet> wallet;
bool wallet_file_copied = false;
bool created_parent_dir = false;
try {
if (!fs::exists(backup_file)) {
@@ -477,13 +479,34 @@ std::shared_ptr<CWallet> RestoreWallet(WalletContext& context, const fs::path& b
return nullptr;
}
if (fs::exists(wallet_path) || !TryCreateDirectories(wallet_path)) {
error = Untranslated(strprintf("Failed to create database path '%s'. Database already exists.", fs::PathToString(wallet_path)));
status = DatabaseStatus::FAILED_ALREADY_EXISTS;
return nullptr;
// Wallet directories are allowed to exist, but must not contain a .dat file.
// Any existing wallet database is treated as a hard failure to prevent overwriting.
if (fs::exists(wallet_path)) {
// If this is a file, it is the db and we don't want to overwrite it.
if (!fs::is_directory(wallet_path)) {
error = Untranslated(strprintf("Failed to restore wallet. Database file exists '%s'.", fs::PathToString(wallet_path)));
status = DatabaseStatus::FAILED_ALREADY_EXISTS;
return nullptr;
}
// Check we are not going to overwrite an existing db file
if (fs::exists(wallet_file)) {
error = Untranslated(strprintf("Failed to restore wallet. Database file exists in '%s'.", fs::PathToString(wallet_file)));
status = DatabaseStatus::FAILED_ALREADY_EXISTS;
return nullptr;
}
} else {
// The directory doesn't exist, create it
if (!TryCreateDirectories(wallet_path)) {
error = Untranslated(strprintf("Failed to restore database path '%s'.", fs::PathToString(wallet_path)));
status = DatabaseStatus::FAILED_ALREADY_EXISTS;
return nullptr;
}
created_parent_dir = true;
}
fs::copy_file(backup_file, wallet_file, fs::copy_options::none);
wallet_file_copied = true;
if (load_after_restore) {
wallet = LoadWallet(context, wallet_name, load_on_start, options, status, error, warnings);
@@ -496,7 +519,13 @@ std::shared_ptr<CWallet> RestoreWallet(WalletContext& context, const fs::path& b
// Remove created wallet path only when loading fails
if (load_after_restore && !wallet) {
fs::remove_all(wallet_path);
if (wallet_file_copied) fs::remove(wallet_file);
// Clean up the parent directory if we created it during restoration.
// As we have created it, it must be empty after deleting the wallet file.
if (created_parent_dir) {
Assume(fs::is_empty(wallet_path));
fs::remove(wallet_path);
}
}
return wallet;
@@ -4057,6 +4086,15 @@ bool CWallet::CanGrindR() const
return !IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS);
}
// Returns wallet prefix for migration.
// Used to name the backup file and newly created wallets.
// E.g. a watch-only wallet is named "<prefix>_watchonly".
static std::string MigrationPrefixName(CWallet& wallet)
{
const std::string& name{wallet.GetName()};
return name.empty() ? "default_wallet" : name;
}
bool DoMigration(CWallet& wallet, WalletContext& context, bilingual_str& error, MigrationResult& res) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet)
{
AssertLockHeld(wallet.cs_wallet);
@@ -4088,7 +4126,7 @@ bool DoMigration(CWallet& wallet, WalletContext& context, bilingual_str& error,
DatabaseStatus status;
std::vector<bilingual_str> warnings;
std::string wallet_name = wallet.GetName() + "_watchonly";
std::string wallet_name = MigrationPrefixName(wallet) + "_watchonly";
std::unique_ptr<WalletDatabase> database = MakeWalletDatabase(wallet_name, options, status, error);
if (!database) {
error = strprintf(_("Wallet file creation failed: %s"), error);
@@ -4127,7 +4165,7 @@ bool DoMigration(CWallet& wallet, WalletContext& context, bilingual_str& error,
DatabaseStatus status;
std::vector<bilingual_str> warnings;
std::string wallet_name = wallet.GetName() + "_solvables";
std::string wallet_name = MigrationPrefixName(wallet) + "_solvables";
std::unique_ptr<WalletDatabase> database = MakeWalletDatabase(wallet_name, options, status, error);
if (!database) {
error = strprintf(_("Wallet file creation failed: %s"), error);
@@ -4242,7 +4280,7 @@ util::Result<MigrationResult> MigrateLegacyToDescriptor(std::shared_ptr<CWallet>
// cases, but in the case where the wallet name is a path to a data file,
// the name of the data file is used, and in the case where the wallet name
// is blank, "default_wallet" is used.
const std::string backup_prefix = wallet_name.empty() ? "default_wallet" : [&] {
const std::string backup_prefix = wallet_name.empty() ? MigrationPrefixName(*local_wallet) : [&] {
// fs::weakly_canonical resolves relative specifiers and remove trailing slashes.
const auto legacy_wallet_path = fs::weakly_canonical(GetWalletDir() / fs::PathFromString(wallet_name));
return fs::PathToString(legacy_wallet_path.filename());
@@ -4295,11 +4333,28 @@ util::Result<MigrationResult> MigrateLegacyToDescriptor(std::shared_ptr<CWallet>
}
}
// In case of loading failure, we need to remember the wallet dirs to remove.
// In case of loading failure, we need to remember the wallet files we have created to remove.
// A `set` is used as it may be populated with the same wallet directory paths multiple times,
// both before and after loading. This ensures the set is complete even if one of the wallets
// fails to load.
std::set<fs::path> wallet_dirs;
std::set<fs::path> wallet_files_to_remove;
std::set<fs::path> wallet_empty_dirs_to_remove;
// Helper to track wallet files and directories for cleanup on failure.
// Only directories of wallets created during migration (not the main wallet) are tracked.
auto track_for_cleanup = [&](const CWallet& wallet) {
const auto files = wallet.GetDatabase().Files();
wallet_files_to_remove.insert(files.begin(), files.end());
if (wallet.GetName() != wallet_name) {
// If this isnt the main wallet, mark its directory for removal.
// This applies to the watch-only and solvable wallets.
// Wallets stored directly as files in the top-level directory
// (e.g. default unnamed wallets) dont have a removable parent directory.
wallet_empty_dirs_to_remove.insert(fs::PathFromString(wallet.GetDatabase().Filename()).parent_path());
}
};
if (success) {
Assume(!res.wallet); // We will set it here.
// Check if the local wallet is empty after migration
@@ -4307,23 +4362,30 @@ util::Result<MigrationResult> MigrateLegacyToDescriptor(std::shared_ptr<CWallet>
// This wallet has no records. We can safely remove it.
std::vector<fs::path> paths_to_remove = local_wallet->GetDatabase().Files();
local_wallet.reset();
for (const auto& path_to_remove : paths_to_remove) fs::remove_all(path_to_remove);
for (const auto& path_to_remove : paths_to_remove) fs::remove(path_to_remove);
}
LogInfo("Loading new wallets after migration...\n");
// Migration successful, load all the migrated wallets.
for (std::shared_ptr<CWallet>* wallet_ptr : {&local_wallet, &res.watchonly_wallet, &res.solvables_wallet}) {
if (success && *wallet_ptr) {
std::shared_ptr<CWallet>& wallet = *wallet_ptr;
// Save db path and load wallet
wallet_dirs.insert(fs::PathFromString(wallet->GetDatabase().Filename()).parent_path());
// Track db path and load wallet
track_for_cleanup(*wallet);
assert(wallet.use_count() == 1);
std::string wallet_name = wallet->GetName();
wallet.reset();
wallet = LoadWallet(context, wallet_name, /*load_on_start=*/std::nullopt, options, status, error, warnings);
success = (wallet != nullptr);
if (!wallet) {
LogError("Failed to load wallet '%s' after migration. Rolling back migration to preserve consistency. "
"Error cause: %s\n", wallet_name, error.original);
success = false;
break;
}
// When no wallet is set, set the main wallet.
if (success && !res.wallet) {
// Set the first successfully loaded wallet as the main one.
// The loop order is intentional and must always start with the local wallet.
if (!res.wallet) {
res.wallet_name = wallet->GetName();
res.wallet = std::move(wallet);
}
@@ -4338,8 +4400,8 @@ util::Result<MigrationResult> MigrateLegacyToDescriptor(std::shared_ptr<CWallet>
if (res.solvables_wallet) created_wallets.push_back(std::move(res.solvables_wallet));
// Get the directories to remove after unloading
for (std::shared_ptr<CWallet>& w : created_wallets) {
wallet_dirs.emplace(fs::PathFromString(w->GetDatabase().Filename()).parent_path());
for (std::shared_ptr<CWallet>& wallet : created_wallets) {
track_for_cleanup(*wallet);
}
// Unload the wallets
@@ -4358,9 +4420,15 @@ util::Result<MigrationResult> MigrateLegacyToDescriptor(std::shared_ptr<CWallet>
}
}
// Delete the wallet directories
for (const fs::path& dir : wallet_dirs) {
fs::remove_all(dir);
// First, delete the db files we have created throughout this process and nothing else
for (const fs::path& file : wallet_files_to_remove) {
fs::remove(file);
}
// Second, delete the created wallet directories and nothing else. They must be empty at this point.
for (const fs::path& dir : wallet_empty_dirs_to_remove) {
Assume(fs::is_empty(dir));
fs::remove(dir);
}
// Restore the backup

View File

@@ -3,6 +3,7 @@
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Tests related to node initialization."""
from concurrent.futures import ThreadPoolExecutor
from pathlib import Path
import os
import platform
@@ -240,9 +241,59 @@ class InitTest(BitcoinTestFramework):
self.stop_node(0)
assert not custom_pidfile_absolute.exists()
def break_wait_test(self):
"""Test what happens when a break signal is sent during a
waitforblockheight RPC call with a long timeout. Ctrl-Break is sent on
Windows and SIGTERM is sent on other platforms, to trigger the same node
shutdown sequence that would happen if Ctrl-C were pressed in a
terminal. (This can be different than the node shutdown sequence that
happens when the stop RPC is sent.)
The waitforblockheight call should be interrupted and return right away,
and not time out."""
self.log.info("Testing waitforblockheight RPC call followed by break signal")
node = self.nodes[0]
if platform.system() == 'Windows':
# CREATE_NEW_PROCESS_GROUP prevents python test from exiting
# with STATUS_CONTROL_C_EXIT (-1073741510) when break is sent.
self.start_node(node.index, creationflags=subprocess.CREATE_NEW_PROCESS_GROUP)
else:
self.start_node(node.index)
current_height = node.getblock(node.getbestblockhash())['height']
with ThreadPoolExecutor(max_workers=1) as ex:
# Call waitforblockheight with wait timeout longer than RPC timeout,
# so it is possible to distinguish whether it times out or returns
# early. If it times out it will throw an exception, and if it
# returns early it will return the current block height.
self.log.debug(f"Calling waitforblockheight with {self.rpc_timeout} sec RPC timeout")
fut = ex.submit(node.waitforblockheight, height=current_height+1, timeout=self.rpc_timeout*1000*2)
self.wait_until(lambda: any(c["method"] == "waitforblockheight" for c in node.cli.getrpcinfo()["active_commands"]))
self.log.debug(f"Sending break signal to pid {node.process.pid}")
if platform.system() == 'Windows':
# Note: CTRL_C_EVENT should not be sent here because unlike
# CTRL_BREAK_EVENT it can not be targeted at a specific process
# group and may behave unpredictably.
node.process.send_signal(signal.CTRL_BREAK_EVENT)
else:
# Note: signal.SIGINT would work here as well
node.process.send_signal(signal.SIGTERM)
node.process.wait()
result = fut.result()
self.log.debug(f"waitforblockheight returned {result!r}")
assert_equal(result["height"], current_height)
node.wait_until_stopped()
def run_test(self):
self.init_pid_test()
self.init_stress_test()
self.break_wait_test()
if __name__ == '__main__':

View File

@@ -319,6 +319,12 @@ class ToolWalletTest(BitcoinTestFramework):
self.write_dump(dump_data, bad_sum_wallet_dump)
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()
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()
self.log.info('Checking createfromdump with an unnamed wallet')
self.do_tool_createfromdump("", "wallet.dump")
def test_chainless_conflicts(self):
self.log.info("Test wallet tool when wallet contains conflicting transactions")

View File

@@ -39,6 +39,7 @@ from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
assert_raises_rpc_error,
sha256sum_file,
)
@@ -132,10 +133,65 @@ class WalletBackupTest(BitcoinTestFramework):
backup_file = self.nodes[0].datadir_path / 'wallet.bak'
wallet_name = "res0"
wallet_file = node.wallets_path / wallet_name
error_message = "Failed to create database path '{}'. Database already exists.".format(wallet_file)
error_message = "Failed to restore wallet. Database file exists in '{}'.".format(wallet_file / "wallet.dat")
assert_raises_rpc_error(-36, error_message, node.restorewallet, wallet_name, backup_file)
assert wallet_file.exists()
def test_restore_existent_dir(self):
self.log.info("Test restore on an existent empty directory")
node = self.nodes[3]
backup_file = self.nodes[0].datadir_path / 'wallet.bak'
wallet_name = "restored_wallet"
wallet_dir = node.wallets_path / wallet_name
os.mkdir(wallet_dir)
res = node.restorewallet(wallet_name, backup_file)
assert_equal(res['name'], wallet_name)
node.unloadwallet(wallet_name)
self.log.info("Test restore succeeds when the target directory contains non-wallet files")
wallet_file = node.wallets_path / wallet_name / "wallet.dat"
os.remove(wallet_file)
extra_file = node.wallets_path / wallet_name / "not_a_wallet.txt"
extra_file.touch()
res = node.restorewallet(wallet_name, backup_file)
assert_equal(res['name'], wallet_name)
assert extra_file.exists() # extra file was not removed by mistake
node.unloadwallet(wallet_name)
self.log.info("Test restore failure due to existing db file in the destination directory")
original_shasum = sha256sum_file(wallet_file)
error_message = "Failed to restore wallet. Database file exists in '{}'.".format(wallet_dir / "wallet.dat")
assert_raises_rpc_error(-36, error_message, node.restorewallet, wallet_name, backup_file)
# Ensure the wallet file remains untouched
assert wallet_dir.exists()
assert_equal(original_shasum, sha256sum_file(wallet_file))
self.log.info("Test restore succeeds when the .dat file in the destination has a different name")
second_wallet = wallet_dir / "hidden_storage.dat"
os.rename(wallet_dir / "wallet.dat", second_wallet)
original_shasum = sha256sum_file(second_wallet)
res = node.restorewallet(wallet_name, backup_file)
assert_equal(res['name'], wallet_name)
assert (wallet_dir / "hidden_storage.dat").exists()
assert_equal(original_shasum, sha256sum_file(second_wallet))
node.unloadwallet(wallet_name)
# Clean for follow-up tests
os.remove(wallet_file)
def test_restore_into_unnamed_wallet(self):
self.log.info("Test restore into a default unnamed wallet")
# This is also useful to test the migration recovery after failure logic
node = self.nodes[3]
backup_file = self.nodes[0].datadir_path / 'wallet.bak'
wallet_name = ""
res = node.restorewallet(wallet_name, backup_file)
assert_equal(res['name'], "")
assert (node.wallets_path / "wallet.dat").exists()
# Clean for follow-up tests
node.unloadwallet("")
os.remove(node.wallets_path / "wallet.dat")
def test_pruned_wallet_backup(self):
self.log.info("Test loading backup on a pruned node when the backup was created close to the prune height of the restoring node")
node = self.nodes[3]
@@ -155,6 +211,13 @@ class WalletBackupTest(BitcoinTestFramework):
# the backup to load successfully this close to the prune height
node.restorewallet('pruned', node.datadir_path / 'wallet_pruned.bak')
self.log.info("Test restore on a pruned node when the backup was beyond the pruning point")
backup_file = self.nodes[0].datadir_path / 'wallet.bak'
wallet_name = ""
error_message = "Wallet loading failed. Prune: last wallet synchronisation goes beyond pruned data. You need to -reindex (download the whole blockchain again in case of a pruned node)"
assert_raises_rpc_error(-4, error_message, node.restorewallet, wallet_name, backup_file)
assert node.wallets_path.exists() # ensure the wallets dir exists
def run_test(self):
self.log.info("Generating initial blockchain")
self.generate(self.nodes[0], 1)
@@ -219,6 +282,8 @@ class WalletBackupTest(BitcoinTestFramework):
assert_equal(res2_rpc.getbalance(), balance2)
self.restore_wallet_existent_name()
self.test_restore_existent_dir()
self.test_restore_into_unnamed_wallet()
# Backup to source wallet file must fail
sourcePaths = [

View File

@@ -3,6 +3,7 @@
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test Migrating a wallet from legacy to descriptor."""
from pathlib import Path
import os.path
import random
import shutil
@@ -638,6 +639,14 @@ class WalletMigrationTest(BitcoinTestFramework):
assert_equal(bals, wallet.getbalances())
def clear_default_wallet(self, backup_file):
# Test cleanup: Clear unnamed default wallet for subsequent tests
(self.old_node.wallets_path / "wallet.dat").unlink()
(self.master_node.wallets_path / "wallet.dat").unlink(missing_ok=True)
shutil.rmtree(self.master_node.wallets_path / "default_wallet_watchonly", ignore_errors=True)
shutil.rmtree(self.master_node.wallets_path / "default_wallet_solvables", ignore_errors=True)
backup_file.unlink()
def test_default_wallet(self):
self.log.info("Test migration of the wallet named as the empty string")
wallet = self.create_legacy_wallet("")
@@ -654,6 +663,84 @@ class WalletMigrationTest(BitcoinTestFramework):
# migrate_and_get_rpc already checks for backup file existence
assert os.path.basename(res["backup_path"]).startswith("default_wallet")
wallet.unloadwallet()
self.clear_default_wallet(backup_file=Path(res["backup_path"]))
def test_default_wallet_watch_only(self):
self.log.info("Test unnamed (default) watch-only wallet migration")
master_wallet = self.master_node.get_wallet_rpc(self.default_wallet_name)
wallet = self.create_legacy_wallet("", blank=True)
wallet.importaddress(master_wallet.getnewaddress(address_type="legacy"))
res, wallet = self.migrate_and_get_rpc("")
info = wallet.getwalletinfo()
assert_equal(info["descriptors"], True)
assert_equal(info["format"], "sqlite")
assert_equal(info["private_keys_enabled"], False)
assert_equal(info["walletname"], "default_wallet_watchonly")
# Check the default wallet is not available anymore
assert not (self.master_node.wallets_path / "wallet.dat").exists()
wallet.unloadwallet()
self.clear_default_wallet(backup_file=Path(res["backup_path"]))
def test_migration_failure(self, wallet_name):
is_default = wallet_name == ""
wallet_pretty_name = "unnamed (default)" if is_default else f'"{wallet_name}"'
self.log.info(f"Test failure during migration of wallet named: {wallet_pretty_name}")
# Preface, set up legacy wallet and unload it
master_wallet = self.master_node.get_wallet_rpc(self.default_wallet_name)
wallet = self.create_legacy_wallet(wallet_name, blank=True)
wallet.importaddress(master_wallet.getnewaddress(address_type="legacy"))
wallet.unloadwallet()
if os.path.isabs(wallet_name):
old_path = master_path = Path(wallet_name)
else:
old_path = self.old_node.wallets_path / wallet_name
master_path = self.master_node.wallets_path / wallet_name
os.makedirs(master_path, exist_ok=True)
shutil.copyfile(old_path / "wallet.dat", master_path / "wallet.dat")
# This will be the watch-only directory the migration tries to create,
# we make migration fail by placing a wallet.dat file there.
wo_prefix = wallet_name or "default_wallet"
# wo_prefix might have path characters in it, this corresponds with
# DoMigration().
wo_dirname = f"{wo_prefix}_watchonly"
watch_only_dir = self.master_node.wallets_path / wo_dirname
os.mkdir(watch_only_dir)
shutil.copyfile(old_path / "wallet.dat", watch_only_dir / "wallet.dat")
mocked_time = int(time.time())
self.master_node.setmocktime(mocked_time)
assert_raises_rpc_error(-4, "Failed to create database", self.master_node.migratewallet, wallet_name)
self.master_node.setmocktime(0)
# Verify the /wallets/ path exists.
assert self.master_node.wallets_path.exists()
# Verify both wallet paths exist.
assert Path(old_path / "wallet.dat").exists()
assert Path(master_path / "wallet.dat").exists()
backup_prefix = "default_wallet" if is_default else os.path.basename(os.path.abspath(master_path))
backup_path = self.master_node.wallets_path / f"{backup_prefix}_{mocked_time}.legacy.bak"
assert backup_path.exists()
self.assert_is_bdb(wallet_name)
# Cleanup
if is_default:
self.clear_default_wallet(backup_path)
else:
backup_path.unlink()
Path(watch_only_dir / "wallet.dat").unlink()
Path(watch_only_dir).rmdir()
Path(master_path / "wallet.dat").unlink()
Path(old_path / "wallet.dat").unlink(missing_ok=True)
def test_direct_file(self):
self.log.info("Test migration of a wallet that is not in a wallet directory")
wallet = self.create_legacy_wallet("plainfile")
@@ -1521,6 +1608,37 @@ class WalletMigrationTest(BitcoinTestFramework):
self.start_node(self.old_node.index)
self.connect_nodes(1, 0)
def unsynced_wallet_on_pruned_node_fails(self):
self.log.info("Test migration of an unsynced wallet on a pruned node fails gracefully")
wallet = self.create_legacy_wallet("", load_on_startup=False)
last_wallet_synced_block = wallet.getwalletinfo()['lastprocessedblock']['height']
wallet.unloadwallet()
shutil.copyfile(self.old_node.wallets_path / "wallet.dat", self.master_node.wallets_path / "wallet.dat")
# Generate blocks just so the wallet best block is pruned
self.restart_node(0, ["-fastprune", "-prune=1", "-nowallet"])
self.connect_nodes(0, 1)
self.generate(self.master_node, 450, sync_fun=self.no_op)
self.master_node.pruneblockchain(250)
# Ensure next block to sync is unavailable
assert_raises_rpc_error(-1, "Block not available (pruned data)", self.master_node.getblock, self.master_node.getblockhash(last_wallet_synced_block + 1))
# Check migration failure
mocked_time = int(time.time())
self.master_node.setmocktime(mocked_time)
assert_raises_rpc_error(-4, "last wallet synchronisation goes beyond pruned data. You need to -reindex (download the whole blockchain again in case of a pruned node)", self.master_node.migratewallet, wallet_name="")
self.master_node.setmocktime(0)
# Verify the /wallets/ path exists, the wallet is still BDB and the backup file is there.
assert self.master_node.wallets_path.exists()
self.assert_is_bdb("")
backup_path = self.master_node.wallets_path / f"default_wallet_{mocked_time}.legacy.bak"
assert backup_path.exists()
self.clear_default_wallet(backup_path)
def run_test(self):
self.master_node = self.nodes[0]
self.old_node = self.nodes[1]
@@ -1539,7 +1657,18 @@ class WalletMigrationTest(BitcoinTestFramework):
self.test_wallet_with_relative_path()
self.test_wallet_with_path("path/to/mywallet/")
self.test_wallet_with_path("path/that/ends/in/..")
migration_failure_cases = [
"",
"../",
os.path.abspath(self.master_node.datadir_path / "absolute_path"),
"normallynamedwallet"
]
for wallet_name in migration_failure_cases:
self.test_migration_failure(wallet_name=wallet_name)
self.test_default_wallet()
self.test_default_wallet_watch_only()
self.test_direct_file()
self.test_addressbook()
self.test_migrate_raw_p2sh()
@@ -1559,5 +1688,8 @@ class WalletMigrationTest(BitcoinTestFramework):
self.test_solvable_no_privs()
self.test_loading_failure_after_migration()
# Note: After this test the first 250 blocks of 'master_node' are pruned
self.unsynced_wallet_on_pruned_node_fails()
if __name__ == '__main__':
WalletMigrationTest(__file__).main()