mirror of
https://github.com/bitcoin/bitcoin.git
synced 2026-01-22 08:09:19 +01:00
Compare commits
33 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4d7d5f6b79 | ||
|
|
04a996b1a7 | ||
|
|
ed355b8f57 | ||
|
|
6c98d68be1 | ||
|
|
6d86b32e78 | ||
|
|
1dae0027cd | ||
|
|
9e59047a7e | ||
|
|
abb6ae2ec5 | ||
|
|
483d158f53 | ||
|
|
747a863f5b | ||
|
|
cc3cdbe921 | ||
|
|
c4082a45e6 | ||
|
|
185ca0e391 | ||
|
|
bc71372c0e | ||
|
|
bef4b1fdee | ||
|
|
ac940ac2ca | ||
|
|
8e5c02a77f | ||
|
|
ac4d0956cc | ||
|
|
454ac8e7db | ||
|
|
e9f73b8149 | ||
|
|
f22122bc27 | ||
|
|
7568bc3ab0 | ||
|
|
c065bcd2d7 | ||
|
|
6983c7d769 | ||
|
|
8769c718f4 | ||
|
|
ed0774bd08 | ||
|
|
f620dde411 | ||
|
|
b734c4026b | ||
|
|
7ea855fd55 | ||
|
|
dd47caee82 | ||
|
|
2a21824b11 | ||
|
|
57264431ff | ||
|
|
26294d627e |
@@ -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
|
||||
|
||||
4
COPYING
4
COPYING
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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) |
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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/).
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 isn’t 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) don’t 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
|
||||
|
||||
@@ -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__':
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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 = [
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user