From 19b3e2e50e84d7d034b1750bc1402e40dc5351d0 Mon Sep 17 00:00:00 2001 From: fanquake Date: Fri, 12 Dec 2025 11:18:20 +0000 Subject: [PATCH 01/16] test: use ModuleNotFoundError in interface_ipc.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Change this so we catch the case where the capnp shared libs have been updated, and can no-longer be loaded by the Python module, resulting in a skipped test, even though pycapnp is installed. i.e: ```bash stderr: Traceback (most recent call last): File "/root/ci_scratch/build/test/functional/interface_ipc.py", line 20, in import capnp # type: ignore[import] # noqa: F401 ^^^^^^^^^^^^ File "/usr/local/lib64/python3.14/site-packages/capnp/__init__.py", line 36, in from .version import version as __version__ File "/usr/local/lib64/python3.14/site-packages/capnp/version.py", line 1, in from .lib.capnp import _CAPNP_VERSION_MAJOR as LIBCAPNP_VERSION_MAJOR # noqa: F401 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ImportError: libcapnpc.so.1.0.1: cannot open shared object file: No such file or directory ``` Failing in this way should make it clear that `pycapnp` needs to be reinstalled/rebuilt. If `pycapnp` is not installed, the test still skips as expected: ```bash Remaining jobs: [interface_ipc.py] 1/1 - interface_ipc.py skipped (capnp module not available.) TEST | STATUS | DURATION interface_ipc.py | ○ Skipped | 0 s ``` Fixes: #34016. Co-authored-by: Ryan Ofsky Github-Pull: #34409 Rebased-From: 905dfdee86d679f8ea31d841bceb77a5724a6b1b --- test/functional/interface_ipc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/functional/interface_ipc.py b/test/functional/interface_ipc.py index 2c9a0022442..89a656f10c5 100755 --- a/test/functional/interface_ipc.py +++ b/test/functional/interface_ipc.py @@ -14,7 +14,7 @@ from test_framework.wallet import MiniWallet # Test may be skipped and not have capnp installed try: import capnp # type: ignore[import] # noqa: F401 -except ImportError: +except ModuleNotFoundError: pass From 48749cf4c7cd39ebb48c9ad81069aba15bd27807 Mon Sep 17 00:00:00 2001 From: Antoine Poinsot Date: Wed, 28 Jan 2026 15:16:49 -0500 Subject: [PATCH 02/16] miniscript: correct and_v() properties and_v() must never be 'd'. This is not a bug fix since this was unreachable in valid Miniscripts: the first sub of an and_v() must be of type V, which conflicts with (i.e. never has) property 'd'. Github-Pull: #34434 Rebased-From: 4fab35cf88c048d2784fe6d71d3f83cc4e420879 --- src/script/miniscript.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/script/miniscript.cpp b/src/script/miniscript.cpp index f04291af1ca..6dc154a8a26 100644 --- a/src/script/miniscript.cpp +++ b/src/script/miniscript.cpp @@ -144,7 +144,7 @@ Type ComputeType(Fragment fragment, Type x, Type y, Type z, const std::vector Date: Wed, 28 Jan 2026 16:50:34 +0000 Subject: [PATCH 03/16] fuzz: Use `__AFL_SHM_ID` for naming test directories Use the AFL++ shared memory ID environment variable to create a deterministic datadir path. This prevents accumulation of stale directories after a fuzz iteration crashes or times out. During long fuzz campaigns, this accumulation has occasionally resulted in running out of disk space. Github-Pull: #34445 Rebased-From: d3e681bc06758fe0686cd96fcfd4a1c4c5af62b4 --- src/test/util/setup_common.cpp | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/test/util/setup_common.cpp b/src/test/util/setup_common.cpp index dec985b2c45..5d839db5bcb 100644 --- a/src/test/util/setup_common.cpp +++ b/src/test/util/setup_common.cpp @@ -161,8 +161,17 @@ BasicTestingSetup::BasicTestingSetup(const ChainType chainType, TestOpts opts) // tests, such as the fuzz tests to run in several processes at the // same time, add a random element to the path. Keep it small enough to // avoid a MAX_PATH violation on Windows. - const auto rand{HexStr(g_rng_temp_path.randbytes(10))}; - m_path_root = fs::temp_directory_path() / TEST_DIR_PATH_ELEMENT / test_name / rand; + // + // When fuzzing with AFL++, use the shared memory ID for a deterministic + // path. This allows for cleanup of leftover directories from timed-out + // or crashed iterations, preventing accumulation of stale datadirs. + if (const char* shm_id = std::getenv("__AFL_SHM_ID"); shm_id && *shm_id) { + m_path_root = fs::temp_directory_path() / TEST_DIR_PATH_ELEMENT / test_name / shm_id; + fs::remove_all(m_path_root); + } else { + const auto rand{HexStr(g_rng_temp_path.randbytes(10))}; + m_path_root = fs::temp_directory_path() / TEST_DIR_PATH_ELEMENT / test_name / rand; + } TryCreateDirectories(m_path_root); } else { // Custom data directory From 7317a0ba1f6561ff0a4d9e1ff5eec2adaf76e085 Mon Sep 17 00:00:00 2001 From: MarcoFalke <*~=`'#}+{/-|&$^_@721217.xyz> Date: Thu, 29 Jan 2026 21:40:10 +0100 Subject: [PATCH 04/16] ci: Always print low ccache hit rate notice Github-Pull: #34453 Rebased-From: fad2876ec330dbb833905d3b2ee5753abc3bc3af --- ci/test/03_test_script.sh | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/ci/test/03_test_script.sh b/ci/test/03_test_script.sh index 05e4d8fd54a..7a1e7f32133 100755 --- a/ci/test/03_test_script.sh +++ b/ci/test/03_test_script.sh @@ -146,11 +146,9 @@ cmake --build "${BASE_BUILD_DIR}" "$MAKEJOBS" --target all $GOAL || ( ) bash -c "${PRINT_CCACHE_STATISTICS}" -if [ "$CI" = "true" ]; then - hit_rate=$(ccache -s | grep "Hits:" | head -1 | sed 's/.*(\(.*\)%).*/\1/') - if [ "${hit_rate%.*}" -lt 75 ]; then - echo "::notice title=low ccache hitrate::Ccache hit-rate in $CONTAINER_NAME was $hit_rate%" - fi +hit_rate=$(ccache --show-stats | grep "Hits:" | head -1 | sed 's/.*(\(.*\)%).*/\1/') +if [ "${hit_rate%.*}" -lt 75 ]; then + echo "::notice title=low ccache hitrate::Ccache hit-rate in $CONTAINER_NAME was $hit_rate%" fi du -sh "${DEPENDS_DIR}"/*/ du -sh "${PREVIOUS_RELEASES_DIR}" From f5d4dc941964dda09ec4695db95a5378b2c8b2d3 Mon Sep 17 00:00:00 2001 From: MarcoFalke <*~=`'#}+{/-|&$^_@721217.xyz> Date: Fri, 30 Jan 2026 13:00:19 +0100 Subject: [PATCH 05/16] ci: [refactor] Allow overwriting check option in run helper Also, use str(e) consistently in all run helpers. This refactor does not change any behavior. This can be reviewed by checking that all instances are exactly identical code now: $ git grep --function-context 'def run(cmd' Github-Pull: #34461 Rebased-From: 2222dadabbbd03be9b4b917583fd51b34857f40c --- .github/ci-test-each-commit-exec.py | 5 +++-- ci/test/02_run_container.py | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/ci-test-each-commit-exec.py b/.github/ci-test-each-commit-exec.py index 3b2eaeeb596..8f02d423ee8 100755 --- a/.github/ci-test-each-commit-exec.py +++ b/.github/ci-test-each-commit-exec.py @@ -10,10 +10,11 @@ import shlex def run(cmd, **kwargs): print("+ " + shlex.join(cmd), flush=True) + kwargs.setdefault("check", True) try: - return subprocess.run(cmd, check=True, **kwargs) + return subprocess.run(cmd, **kwargs) except Exception as e: - sys.exit(e) + sys.exit(str(e)) def main(): diff --git a/ci/test/02_run_container.py b/ci/test/02_run_container.py index 513ecacaca8..e7777b324af 100755 --- a/ci/test/02_run_container.py +++ b/ci/test/02_run_container.py @@ -14,7 +14,7 @@ def run(cmd, **kwargs): try: return subprocess.run(cmd, check=True, **kwargs) except Exception as e: - sys.exit(e) + sys.exit(str(e)) def main(): From 1a757af7ea7455197324773c5e33d76f22053771 Mon Sep 17 00:00:00 2001 From: MarcoFalke <*~=`'#}+{/-|&$^_@721217.xyz> Date: Fri, 30 Jan 2026 13:10:42 +0100 Subject: [PATCH 06/16] ci: Print verbose build error message in test-each-commit Github-Pull: #34461 Rebased-From: bbbb78a4f28fd2378342398ccae60995ae0e08d2 --- .github/ci-test-each-commit-exec.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/ci-test-each-commit-exec.py b/.github/ci-test-each-commit-exec.py index 8f02d423ee8..a6796d7bc60 100755 --- a/.github/ci-test-each-commit-exec.py +++ b/.github/ci-test-each-commit-exec.py @@ -45,7 +45,11 @@ def main(): "-DWITH_USDT=ON", "-DCMAKE_CXX_FLAGS=-Wno-error=unused-member-function", ]) - run(["cmake", "--build", "build", "-j", str(num_procs)]) + + if run(["cmake", "--build", "build", "-j", str(num_procs)], check=False).returncode != 0: + print("Build failure. Verbose build follows.") + run(["cmake", "--build", "build", "-j1", "--verbose"]) + run([ "ctest", "--output-on-failure", From b7a182c7494ef58560853e88a00297c927594453 Mon Sep 17 00:00:00 2001 From: jayvaliya Date: Thu, 5 Feb 2026 14:07:25 +0530 Subject: [PATCH 07/16] doc: fix broken bpftrace installation link The bpftrace project moved from iovisor/bpftrace to bpftrace/bpftraceand removed the separate INSTALL.md file. Installation instructionsare now in the README.md Quick Start section. Github-Pull: #34510 Rebased-From: 42ee31e80c99bdb4d6affdc9dc22a0f3d5da7b59 --- contrib/tracing/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/tracing/README.md b/contrib/tracing/README.md index 252053e7b87..192182e17d6 100644 --- a/contrib/tracing/README.md +++ b/contrib/tracing/README.md @@ -22,7 +22,7 @@ corresponding packages. See [installing bpftrace] and [installing BCC] for more information. For development there exist a [bpftrace Reference Guide], a [BCC Reference Guide], and a [bcc Python Developer Tutorial]. -[installing bpftrace]: https://github.com/iovisor/bpftrace/blob/master/INSTALL.md +[installing bpftrace]: https://github.com/bpftrace/bpftrace/blob/master/README.md#quick-start [installing BCC]: https://github.com/iovisor/bcc/blob/master/INSTALL.md [bpftrace Reference Guide]: https://github.com/iovisor/bpftrace/blob/master/docs/reference_guide.md [BCC Reference Guide]: https://github.com/iovisor/bcc/blob/master/docs/reference_guide.md From 85f8d28fac057fb3458e6478569853d7bdd1167d Mon Sep 17 00:00:00 2001 From: Cory Fields Date: Tue, 10 Feb 2026 20:53:39 +0000 Subject: [PATCH 08/16] build: avoid exporting secp256k1 symbols Take advantage of the new secp256k1 option to avoid visibility attributes on API functions. While most users of a shared libsecp always want API functions exported so that they can actually be linked against, we always build it statically. When that static lib is linked into a (static or shared) libbitcoinkernel, by default its symbols end up exported there as well. As libsecp is an implementation detail of the kernel (and any future Core lib), its symbols should never be exported. Github-Pull: #34554 Rebased-From: 2ccfdb582b646d9bda07f0f13b97cb8c37a452aa --- cmake/secp256k1.cmake | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/cmake/secp256k1.cmake b/cmake/secp256k1.cmake index 5302f516d89..c82f361aba0 100644 --- a/cmake/secp256k1.cmake +++ b/cmake/secp256k1.cmake @@ -9,6 +9,11 @@ function(add_secp256k1 subdir) message("Configuring secp256k1 subtree...") set(BUILD_SHARED_LIBS OFF) set(CMAKE_EXPORT_COMPILE_COMMANDS OFF) + + # Unconditionally prevent secp's symbols from being exported by our libs + set(CMAKE_C_VISIBILITY_PRESET hidden) + set(SECP256K1_ENABLE_API_VISIBILITY_ATTRIBUTES OFF CACHE BOOL "" FORCE) + set(SECP256K1_ENABLE_MODULE_ECDH OFF CACHE BOOL "" FORCE) set(SECP256K1_ENABLE_MODULE_RECOVERY ON CACHE BOOL "" FORCE) set(SECP256K1_ENABLE_MODULE_MUSIG ON CACHE BOOL "" FORCE) From 412725b7a34ecf7cec767686dbb6948646354aae Mon Sep 17 00:00:00 2001 From: ANAVHEOBA Date: Fri, 19 Dec 2025 11:35:00 -0500 Subject: [PATCH 09/16] net: reduce log level for PCP/NAT-PMP NOT_AUTHORIZED failures Users running on home networks with routers that don't support PCP (Port Control Protocol) or NAT-PMP port mapping receive frequent warning-level log messages every few minutes: "pcp: Mapping failed with result NOT_AUTHORIZED (code 2)" This is expected behavior for many consumer routers that have PCP disabled by default, not an actionable error. Add explicit constants for the NOT_AUTHORIZED result code (value 2) for both NAT-PMP and PCP protocols. Log the first NOT_AUTHORIZED failure at warning level for visibility, then downgrade subsequent occurrences to LogDebug to avoid log noise. Other failure types continue to warn unconditionally. Fixes #34114 Co-authored-by: willcl-ark Github-Pull: #34549 Rebased-From: afea2af1391314be0e5caaa0125c884da2405316 --- src/common/pcp.cpp | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/src/common/pcp.cpp b/src/common/pcp.cpp index 6112dbf54c9..7889d211fa2 100644 --- a/src/common/pcp.cpp +++ b/src/common/pcp.cpp @@ -4,6 +4,7 @@ #include +#include #include #include #include @@ -81,6 +82,8 @@ constexpr size_t NATPMP_MAP_RESPONSE_LIFETIME_OFS = 12; constexpr uint8_t NATPMP_RESULT_SUCCESS = 0; //! Result code representing unsupported version. constexpr uint8_t NATPMP_RESULT_UNSUPP_VERSION = 1; +//! Result code representing not authorized (router doesn't support port mapping). +constexpr uint8_t NATPMP_RESULT_NOT_AUTHORIZED = 2; //! Result code representing lack of resources. constexpr uint8_t NATPMP_RESULT_NO_RESOURCES = 4; @@ -144,6 +147,8 @@ constexpr size_t PCP_MAP_EXTERNAL_IP_OFS = 20; //! Result code representing success (RFC6887 7.4), shared with NAT-PMP. constexpr uint8_t PCP_RESULT_SUCCESS = NATPMP_RESULT_SUCCESS; +//! Result code representing not authorized (RFC6887 7.4), shared with NAT-PMP. +constexpr uint8_t PCP_RESULT_NOT_AUTHORIZED = NATPMP_RESULT_NOT_AUTHORIZED; //! Result code representing lack of resources (RFC6887 7.4). constexpr uint8_t PCP_RESULT_NO_RESOURCES = 8; @@ -374,7 +379,16 @@ std::variant NATPMPRequestPortMap(const CNetAddr &g Assume(response.size() >= NATPMP_MAP_RESPONSE_SIZE); uint16_t result_code = ReadBE16(response.data() + NATPMP_RESPONSE_HDR_RESULT_OFS); if (result_code != NATPMP_RESULT_SUCCESS) { - LogPrintLevel(BCLog::NET, BCLog::Level::Warning, "natpmp: Port mapping failed with result %s\n", NATPMPResultString(result_code)); + if (result_code == NATPMP_RESULT_NOT_AUTHORIZED) { + static std::atomic warned{false}; + if (!warned.exchange(true)) { + LogWarning("natpmp: Port mapping failed with result %s\n", NATPMPResultString(result_code)); + } else { + LogDebug(BCLog::NET, "natpmp: Port mapping failed with result %s\n", NATPMPResultString(result_code)); + } + } else { + LogWarning("natpmp: Port mapping failed with result %s\n", NATPMPResultString(result_code)); + } if (result_code == NATPMP_RESULT_NO_RESOURCES) { return MappingError::NO_RESOURCES; } @@ -508,7 +522,16 @@ std::variant PCPRequestPortMap(const PCPMappingNonc uint16_t external_port = ReadBE16(response.data() + PCP_HDR_SIZE + PCP_MAP_EXTERNAL_PORT_OFS); CNetAddr external_addr{PCPUnwrapAddress(response.subspan(PCP_HDR_SIZE + PCP_MAP_EXTERNAL_IP_OFS, ADDR_IPV6_SIZE))}; if (result_code != PCP_RESULT_SUCCESS) { - LogPrintLevel(BCLog::NET, BCLog::Level::Warning, "pcp: Mapping failed with result %s\n", PCPResultString(result_code)); + if (result_code == PCP_RESULT_NOT_AUTHORIZED) { + static std::atomic warned{false}; + if (!warned.exchange(true)) { + LogWarning("pcp: Mapping failed with result %s\n", PCPResultString(result_code)); + } else { + LogDebug(BCLog::NET, "pcp: Mapping failed with result %s\n", PCPResultString(result_code)); + } + } else { + LogWarning("pcp: Mapping failed with result %s\n", PCPResultString(result_code)); + } if (result_code == PCP_RESULT_NO_RESOURCES) { return MappingError::NO_RESOURCES; } From d54d7dfe9ff07b10c9cb206c4a10bf701aebda0c Mon Sep 17 00:00:00 2001 From: SomberNight Date: Wed, 11 Feb 2026 16:16:50 +0000 Subject: [PATCH 10/16] wallet: rpc: manpage: fix example missing `fee_rate` argument The function signature for the `send` RPC is: ``` send [{"address":amount,...},{"data":"hex"},...] ( conf_target "estimate_mode" fee_rate options version ) ``` The last example in the manpage is missing the `fee_rate` arg, but is trying to specify the `options` arg, by index. The parser confuses the intended `options` arg as the missing `fee_rate` arg. See: ``` $ bitcoin-cli -rpcuser=doggman -rpcpassword=donkey -rpcport=18554 -regtest send '{"bcrt1qusm48zmlzwr32csxdw4ar7atw260h22c9ten9l": 0.1}' 1 economical '{"add_to_wallet": false, "inputs": [{"txid":"0b7e1a471dc948b7a6187936b16e6d7d9833629b2f9dd8a392eb89928f63aaad", "vout":0}]}' error code: -8 error message: Cannot specify both conf_target and fee_rate. Please provide either a confirmation target in blocks for automatic fee estimation, or an explicit fee rate. ``` vs ``` $ bitcoin-cli -rpcuser=doggman -rpcpassword=donkey -rpcport=18554 -regtest send '{"bcrt1qusm48zmlzwr32csxdw4ar7atw260h22c9ten9l": 0.1}' 1 economical null '{"add_to_wallet": false, "inputs": [{"txid":"0b7e1a471dc948b7a6187936b16e6d7d9833629b2f9dd8a392eb89928f63aaad", "vout":0}]}' { "psbt": "cHNidP8BAHECAAAAAa2qY4+SieuSo9idL5tiM5h9bW6xNnkYprdIyR1HGn4LAAAAAAD9////AkR2DwQAAAAAFgAUpLDwJu+wFRHLQAgKAb0psk7UVd2AlpgAAAAAABYAFOQ3U4t/E4cVYgZrq9H7q3K0+6lYAAAAAAABAIUCAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/////wQC4wMA/////wLIF6gEAAAAABYAFLMY1zihXrefAA0DA5nld4MCPjkrAAAAAAAAAAAmaiSqIant4vYcP3HR3v0/qZnfo2lTdVxpBol5mWK0i+vYNpdOjPkAAAAAAQEfyBeoBAAAAAAWABSzGNc4oV63nwANAwOZ5XeDAj45KwEIawJHMEQCIElTV4pbUrsPR9qHWcioowVv3QVWHizxwevfD0u/I8YyAiBCY3OzF81PSLM00h4ueQkehYuxDFZu7Jk51iejphKnnwEhA0VKdYVSyBpWoxBwTDOupB58Fi3mEBs+u+OOqEYVd2sZACICA98YLWyH7dBCfXVxe7woiLSTgV1mJN8Zc8KgZ77pVSg+GNBMeT5UAACAAQAAgAAAAIABAAAAbAAAAAAA", "txid": "625b71b314a6ac4f738634e29dc007cd5edc0427c1ae96ab706d06a62910cea2", "hex": "02000000000101adaa638f9289eb92a3d89d2f9b6233987d6d6eb1367918a6b748c91d471a7e0b0000000000fdffffff0244760f0400000000160014a4b0f026efb01511cb40080a01bd29b24ed455dd8096980000000000160014e437538b7f13871562066babd1fbab72b4fba9580247304402204953578a5b52bb0f47da8759c8a8a3056fdd05561e2cf1c1ebdf0f4bbf23c6320220426373b317cd4f48b334d21e2e79091e858bb10c566eec9939d627a3a612a79f012103454a758552c81a56a310704c33aea41e7c162de6101b3ebbe38ea84615776b1900000000", "complete": true } ``` Github-Pull: #34561 Rebased-From: 50cf6838e6aa51e0d712cbc1e13d686253bc8fe0 --- src/wallet/rpc/spend.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wallet/rpc/spend.cpp b/src/wallet/rpc/spend.cpp index d00547a8975..6ed6464f9cd 100644 --- a/src/wallet/rpc/spend.cpp +++ b/src/wallet/rpc/spend.cpp @@ -1297,7 +1297,7 @@ RPCHelpMan send() "Send 0.3 BTC with a fee rate of 25 " + CURRENCY_ATOM + "/vB using named arguments\n" + HelpExampleCli("-named send", "outputs='{\"" + EXAMPLE_ADDRESS[0] + "\": 0.3}' fee_rate=25\n") + "Create a transaction that should confirm the next block, with a specific input, and return result without adding to wallet or broadcasting to the network\n" - + HelpExampleCli("send", "'{\"" + EXAMPLE_ADDRESS[0] + "\": 0.1}' 1 economical '{\"add_to_wallet\": false, \"inputs\": [{\"txid\":\"a08e6907dbbd3d809776dbfc5d82e371b764ed838b5655e72f463568df1aadf0\", \"vout\":1}]}'") + + HelpExampleCli("send", "'{\"" + EXAMPLE_ADDRESS[0] + "\": 0.1}' 1 economical null '{\"add_to_wallet\": false, \"inputs\": [{\"txid\":\"a08e6907dbbd3d809776dbfc5d82e371b764ed838b5655e72f463568df1aadf0\", \"vout\":1}]}'") }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { From 6993b1be781d32a908128a954ad076e8bb5dde3f Mon Sep 17 00:00:00 2001 From: MarcoFalke <*~=`'#}+{/-|&$^_@721217.xyz> Date: Tue, 17 Feb 2026 21:32:17 +0100 Subject: [PATCH 11/16] test: Fix broken --valgrind handling after bitcoin wrapper Prior to this commit, tool_bitcoin.py was failing: ```sh $ ./bld-cmake/test/functional/tool_bitcoin.py --valgrind TestFramework (ERROR): Unexpected exception Traceback (most recent call last): File "./test/functional/test_framework/test_framework.py", line 138, in main self.setup() ~~~~~~~~~~^^ File "./test/functional/test_framework/test_framework.py", line 269, in setup self.setup_network() ~~~~~~~~~~~~~~~~~~^^ File "./test/functional/tool_bitcoin.py", line 38, in setup_network assert all(node.args[:len(node_argv)] == node_argv for node in self.nodes) ~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ AssertionError ``` This commit fixes this issue by running `bitcoin` under valgrind. Also, it comes with other improvements: * Drop the outdated valgrind 3.14 requirement, because there is no distro that ships a version that old anymore. * Drop the VALGRIND_SUPPRESSIONS_FILE env var handling, because it was presumably never used since it was introduced. Also, the use-case seems limited. Review note: The set_cmd_args was ignoring the --valgrind test option. In theory, this could be fixed by refactoring Binaries::node_argv() to be used here. However, for now, just re-implement the node_argv logic in set_cmd_args to prepend the valgrind cmd. Github-Pull: #34608 Rebased-From: fa03fbf7e31f384627230e1bc042f7f29c05ff68 --- .../test_framework/test_framework.py | 33 ++++++++++++++----- test/functional/test_framework/test_node.py | 31 +++++++++++------ test/functional/tool_bitcoin.py | 4 ++- 3 files changed, 48 insertions(+), 20 deletions(-) diff --git a/test/functional/test_framework/test_framework.py b/test/functional/test_framework/test_framework.py index 0b9295cef53..408872de6e6 100755 --- a/test/functional/test_framework/test_framework.py +++ b/test/functional/test_framework/test_framework.py @@ -11,6 +11,7 @@ from datetime import datetime, timezone import json import logging import os +import pathlib import platform import pdb import random @@ -72,9 +73,18 @@ class Binaries: binaries, which takes precedence over the paths above, if specified. This is used by tests calling binaries from previous releases. """ - def __init__(self, paths, bin_dir): + def __init__(self, paths, bin_dir, *, use_valgrind=False): self.paths = paths self.bin_dir = bin_dir + suppressions_file = pathlib.Path(__file__).parents[3] / "contrib" / "valgrind.supp" + self.valgrind_cmd = [ + "valgrind", + f"--suppressions={suppressions_file}", + "--gen-suppressions=all", + "--exit-on-first-error=yes", + "--error-exitcode=1", + "--quiet", + ] if use_valgrind else [] def node_argv(self, **kwargs): "Return argv array that should be used to invoke bitcoind" @@ -102,20 +112,26 @@ class Binaries: return self._argv("chainstate", self.paths.bitcoinchainstate) def _argv(self, command, bin_path, need_ipc=False): - """Return argv array that should be used to invoke the command. It - either uses the bitcoin wrapper executable (if BITCOIN_CMD is set or + """Return argv array that should be used to invoke the command. + + It either uses the bitcoin wrapper executable (if BITCOIN_CMD is set or need_ipc is True), or the direct binary path (bitcoind, etc). When bin_dir is set (by tests calling binaries from previous releases) it - always uses the direct path.""" + always uses the direct path. + + The returned args include valgrind, except when bin_dir is set + (previous releases). Also, valgrind will only apply to the bitcoin + wrapper executable directly, not to the commands that `bitcoin` calls. + """ if self.bin_dir is not None: return [os.path.join(self.bin_dir, os.path.basename(bin_path))] elif self.paths.bitcoin_cmd is not None or need_ipc: # If the current test needs IPC functionality, use the bitcoin # wrapper binary and append -m so it calls multiprocess binaries. bitcoin_cmd = self.paths.bitcoin_cmd or [self.paths.bitcoin_bin] - return bitcoin_cmd + (["-m"] if need_ipc else []) + [command] + return self.valgrind_cmd + bitcoin_cmd + (["-m"] if need_ipc else []) + [command] else: - return [bin_path] + return self.valgrind_cmd + [bin_path] class BitcoinTestMetaClass(type): @@ -247,7 +263,7 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): parser.add_argument("--perf", dest="perf", default=False, action="store_true", help="profile running nodes with perf for the duration of the test") parser.add_argument("--valgrind", dest="valgrind", default=False, action="store_true", - help="run nodes under the valgrind memory error detector: expect at least a ~10x slowdown. valgrind 3.14 or later required. Does not apply to previous release binaries.") + help="Run binaries under the valgrind memory error detector: Expect at least a ~10x slowdown. Does not apply to previous release binaries or binaries called from the bitcoin wrapper executable.") parser.add_argument("--randomseed", type=int, help="set a random seed for deterministically reproducing a previous test run") parser.add_argument("--timeout-factor", dest="timeout_factor", type=float, help="adjust test timeouts by a factor. Setting it to 0 disables all timeouts") @@ -305,7 +321,7 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): return paths def get_binaries(self, bin_dir=None): - return Binaries(self.binary_paths, bin_dir) + return Binaries(self.binary_paths, bin_dir, use_valgrind=self.options.valgrind) def setup(self): """Call this method to start up the test framework object with options set.""" @@ -578,7 +594,6 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): extra_args=args, use_cli=self.options.usecli, start_perf=self.options.perf, - use_valgrind=self.options.valgrind, v2transport=self.options.v2transport, uses_wallet=self.uses_wallet, ) diff --git a/test/functional/test_framework/test_node.py b/test/functional/test_framework/test_node.py index 3b55b915efd..74c95da4c12 100755 --- a/test/functional/test_framework/test_node.py +++ b/test/functional/test_framework/test_node.py @@ -91,7 +91,27 @@ class TestNode(): To make things easier for the test writer, any unrecognised messages will be dispatched to the RPC connection.""" - def __init__(self, i, datadir_path, *, chain, rpchost, timewait, timeout_factor, binaries, coverage_dir, cwd, extra_conf=None, extra_args=None, use_cli=False, start_perf=False, use_valgrind=False, version=None, v2transport=False, uses_wallet=False, ipcbind=False): + def __init__( + self, + i, + datadir_path, + *, + chain, + rpchost, + timewait, + timeout_factor, + binaries, + coverage_dir, + cwd, + extra_conf=None, + extra_args=None, + use_cli=False, + start_perf=False, + version=None, + v2transport=False, + uses_wallet=False, + ipcbind=False, + ): """ Kwargs: start_perf (bool): If True, begin profiling the node with `perf` as soon as @@ -146,15 +166,6 @@ class TestNode(): self.ipc_socket_path = self.ipc_tmp_dir / "node.sock" self.args.append(f"-ipcbind=unix:{self.ipc_socket_path}") - # Use valgrind, expect for previous release binaries - if use_valgrind and version is None: - default_suppressions_file = Path(__file__).parents[3] / "contrib" / "valgrind.supp" - suppressions_file = os.getenv("VALGRIND_SUPPRESSIONS_FILE", - default_suppressions_file) - self.args = ["valgrind", "--suppressions={}".format(suppressions_file), - "--gen-suppressions=all", "--exit-on-first-error=yes", - "--error-exitcode=1", "--quiet"] + self.args - if self.version_is_at_least(190000): self.args.append("-logthreadnames") if self.version_is_at_least(219900): diff --git a/test/functional/tool_bitcoin.py b/test/functional/tool_bitcoin.py index 1112e02e090..d59c75dee53 100755 --- a/test/functional/tool_bitcoin.py +++ b/test/functional/tool_bitcoin.py @@ -39,7 +39,9 @@ class ToolBitcoinTest(BitcoinTestFramework): def set_cmd_args(self, node, args): """Set up node so it will be started through bitcoin wrapper command with specified arguments.""" - node.args = [self.binary_paths.bitcoin_bin] + args + ["node"] + self.node_options[node.index] + # Manually construct the `bitcoin node` command, similar to Binaries::node_argv() + bitcoin_cmd = node.binaries.valgrind_cmd + [node.binaries.paths.bitcoin_bin] + node.args = bitcoin_cmd + args + ["node"] + self.node_options[node.index] def test_args(self, cmd_args, node_args, expect_exe=None, expect_error=None): node = self.nodes[0] From dd8924909a5bbc4698624b8e0f3b7c083e2e54a0 Mon Sep 17 00:00:00 2001 From: MarcoFalke <*~=`'#}+{/-|&$^_@721217.xyz> Date: Wed, 18 Feb 2026 08:43:14 +0100 Subject: [PATCH 12/16] test: Remove redundant warning about missing binaries The error was added in commit 1ea7e45a1f445d32a2b690d52befb2e63418653b, because there was an additional confusing `AssertionError: [node 0] Error: no RPC connection` instead of just a single `FileNotFoundError: [Errno 2] No such file or directory`. This is no longer needed on current master. Also, the test is incomplete, because it was just checking bitcoind and bitcoin-cli, not any other missing binaries. Also, after the previous commit, it would not work in combination with --valgrind. Instead of trying to make it complete, and work in all combinations, just remove it, because the already existing error will be clear in any case. This can be tested via: ```sh ./test/get_previous_releases.py mv releases releases_backup # Confirm the test is skipped due to missing releases ./bld-cmake/test/functional/wallet_migration.py # Confirm the test fails due to missing releases ./bld-cmake/test/functional/wallet_migration.py --previous-releases mv releases_backup releases mv ./releases/v28.2 ./releases/v28.2_backup # Confirm the test fails with a single FileNotFoundError ./bld-cmake/test/functional/wallet_migration.py mv ./releases/v28.2_backup ./releases/v28.2 # Confirm the test runs and passes ./bld-cmake/test/functional/wallet_migration.py rm ./bld-cmake/bin/bitcoind # Confirm the test fails with a single "No such file or directory", # testing with and without --valgrind ./bld-cmake/test/functional/wallet_migration.py ./bld-cmake/test/functional/wallet_migration.py --valgrind ``` Github-Pull: #34608 Rebased-From: fa29fb72cb21bec798ddae49b842c78ac84a5a3b --- .../test_framework/test_framework.py | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/test/functional/test_framework/test_framework.py b/test/functional/test_framework/test_framework.py index 408872de6e6..7d7736388b4 100755 --- a/test/functional/test_framework/test_framework.py +++ b/test/functional/test_framework/test_framework.py @@ -250,7 +250,7 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): help="The seed to use for assigning port numbers (default: current process id)") parser.add_argument("--previous-releases", dest="prev_releases", action="store_true", default=os.path.isdir(previous_releases_path) and bool(os.listdir(previous_releases_path)), - help="Force test of previous releases (default: %(default)s)") + help="Force test of previous releases (default: %(default)s). Previous releases binaries can be downloaded via `test/get_previous_releases.py`.") parser.add_argument("--coveragedir", dest="coveragedir", help="Write tested RPC commands into this directory") parser.add_argument("--configfile", dest="configfile", @@ -559,18 +559,6 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): bin_dirs = [] for v in versions: bin_dir = bin_dir_from_version(v) - - # Fail test if any of the needed release binaries is missing - for bin_path in (argv[0] for binaries in (self.get_binaries(bin_dir),) - for argv in (binaries.node_argv(), binaries.rpc_argv())): - - if shutil.which(bin_path) is None: - self.log.error(f"Binary not found: {bin_path}") - if v is None: - raise AssertionError("At least one binary is missing, did you compile?") - raise AssertionError("At least one release binary is missing. " - "Previous releases binaries can be downloaded via `test/get_previous_releases.py`.") - bin_dirs.append(bin_dir) extra_init = [{}] * num_nodes if self.extra_init is None else self.extra_init # type: ignore[var-annotated] @@ -1064,8 +1052,8 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): """Checks whether previous releases are present and enabled.""" if not os.path.isdir(self.options.previous_releases_path): if self.options.prev_releases: - raise AssertionError("Force test of previous releases but releases missing: {}".format( - self.options.previous_releases_path)) + raise AssertionError(f"Force test of previous releases but releases missing: {self.options.previous_releases_path}\n" + "Previous releases binaries can be downloaded via `test/get_previous_releases.py`.") return self.options.prev_releases def skip_if_no_external_signer(self): From dca7700269bd4be3d6688fae0522a37fc6c9b01e Mon Sep 17 00:00:00 2001 From: MarcoFalke <*~=`'#}+{/-|&$^_@721217.xyz> Date: Wed, 18 Feb 2026 15:25:09 +0100 Subject: [PATCH 13/16] test: valgrind --trace-children=yes for bitcoin wrapper Github-Pull: #34608 Rebased-From: fa5d478853a88ce7d7095b6abfe8112390298b65 --- test/functional/test_framework/test_framework.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/functional/test_framework/test_framework.py b/test/functional/test_framework/test_framework.py index 7d7736388b4..eb8baf97853 100755 --- a/test/functional/test_framework/test_framework.py +++ b/test/functional/test_framework/test_framework.py @@ -81,6 +81,7 @@ class Binaries: "valgrind", f"--suppressions={suppressions_file}", "--gen-suppressions=all", + "--trace-children=yes", # Needed for 'bitcoin' wrapper "--exit-on-first-error=yes", "--error-exitcode=1", "--quiet", @@ -263,7 +264,7 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): parser.add_argument("--perf", dest="perf", default=False, action="store_true", help="profile running nodes with perf for the duration of the test") parser.add_argument("--valgrind", dest="valgrind", default=False, action="store_true", - help="Run binaries under the valgrind memory error detector: Expect at least a ~10x slowdown. Does not apply to previous release binaries or binaries called from the bitcoin wrapper executable.") + help="Run binaries under the valgrind memory error detector: Expect at least a ~10x slowdown. Does not apply to previous release binaries.") parser.add_argument("--randomseed", type=int, help="set a random seed for deterministically reproducing a previous test run") parser.add_argument("--timeout-factor", dest="timeout_factor", type=float, help="adjust test timeouts by a factor. Setting it to 0 disables all timeouts") From 2d035b1d4f277ab91a773d40d9ad7db135b9638a Mon Sep 17 00:00:00 2001 From: fanquake Date: Tue, 9 Dec 2025 11:31:16 +0000 Subject: [PATCH 14/16] guix: use a temporary file over sponge Remove sponge (moreutils). Github-Pull: #34627 Rebased-From: c86bce597a3c4d45f7c2b6149197d8303bda0e87 --- contrib/guix/libexec/build.sh | 8 +++++--- contrib/guix/libexec/codesign.sh | 4 +++- contrib/guix/manifest.scm | 31 ------------------------------- 3 files changed, 8 insertions(+), 35 deletions(-) diff --git a/contrib/guix/libexec/build.sh b/contrib/guix/libexec/build.sh index ebfafb75baa..cfaa0bf70ba 100755 --- a/contrib/guix/libexec/build.sh +++ b/contrib/guix/libexec/build.sh @@ -393,12 +393,14 @@ mv --no-target-directory "$OUTDIR" "$ACTUAL_OUTDIR" \ || ( rm -rf "$ACTUAL_OUTDIR" && exit 1 ) ( + tmp="$(mktemp)" cd /outdir-base { echo "$GIT_ARCHIVE" find "$ACTUAL_OUTDIR" -type f } | xargs realpath --relative-base="$PWD" \ - | xargs sha256sum \ - | sort -k2 \ - | sponge "$ACTUAL_OUTDIR"/SHA256SUMS.part + | xargs sha256sum \ + | sort -k2 \ + > "$tmp"; + mv "$tmp" "$ACTUAL_OUTDIR"/SHA256SUMS.part ) diff --git a/contrib/guix/libexec/codesign.sh b/contrib/guix/libexec/codesign.sh index fe86065350e..43db9159946 100755 --- a/contrib/guix/libexec/codesign.sh +++ b/contrib/guix/libexec/codesign.sh @@ -141,6 +141,7 @@ mv --no-target-directory "$OUTDIR" "$ACTUAL_OUTDIR" \ || ( rm -rf "$ACTUAL_OUTDIR" && exit 1 ) ( + tmp="$(mktemp)" cd /outdir-base { echo "$CODESIGNING_TARBALL" @@ -149,5 +150,6 @@ mv --no-target-directory "$OUTDIR" "$ACTUAL_OUTDIR" \ } | xargs realpath --relative-base="$PWD" \ | xargs sha256sum \ | sort -k2 \ - | sponge "$ACTUAL_OUTDIR"/SHA256SUMS.part + > "$tmp"; + mv "$tmp" "$ACTUAL_OUTDIR"/SHA256SUMS.part ) diff --git a/contrib/guix/manifest.scm b/contrib/guix/manifest.scm index 59a6366984a..9895b066f2f 100644 --- a/contrib/guix/manifest.scm +++ b/contrib/guix/manifest.scm @@ -504,36 +504,6 @@ inspecting signatures in Mach-O binaries.") (("^install-others =.*$") (string-append "install-others = " out "/etc/rpc\n"))))))))))))) -;; The sponge tool from moreutils. -(define-public sponge - (package - (name "sponge") - (version "0.69") - (source (origin - (method url-fetch) - (uri (string-append - "https://git.joeyh.name/index.cgi/moreutils.git/snapshot/ - moreutils-" version ".tar.gz")) - (file-name (string-append "moreutils-" version ".tar.gz")) - (sha256 - (base32 - "1l859qnzccslvxlh5ghn863bkq2vgmqgnik6jr21b9kc6ljmsy8g")))) - (build-system gnu-build-system) - (arguments - (list #:phases - #~(modify-phases %standard-phases - (delete 'configure) - (replace 'install - (lambda* (#:key outputs #:allow-other-keys) - (let ((bin (string-append (assoc-ref outputs "out") "/bin"))) - (install-file "sponge" bin))))) - #:make-flags - #~(list "sponge" (string-append "CC=" #$(cc-for-target))))) - (home-page "https://joeyh.name/code/moreutils/") - (synopsis "Miscellaneous general-purpose command-line tools") - (description "Just sponge") - (license license:gpl2+))) - (packages->manifest (append (list ;; The Basics @@ -548,7 +518,6 @@ inspecting signatures in Mach-O binaries.") patch gawk sed - sponge ;; Compression and archiving tar gzip From 0202ae341f8d23f3dfa1054f86783162d53b925b Mon Sep 17 00:00:00 2001 From: MarcoFalke <*~=`'#}+{/-|&$^_@721217.xyz> Date: Wed, 25 Feb 2026 19:51:24 +0100 Subject: [PATCH 15/16] doc: Update Guix install for Debian/Ubuntu Fixes https://github.com/bitcoin/bitcoin/issues/33982 Co-authored-by: Purple Ninja <129023353+ToRyVand@users.noreply.github.com> Github-Pull: #34671 Rebased-From: faa70ca7642bd653cbd2e544c17fa58d2672afa4 --- contrib/guix/INSTALL.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/contrib/guix/INSTALL.md b/contrib/guix/INSTALL.md index f9a79f66349..93fcdf485b4 100644 --- a/contrib/guix/INSTALL.md +++ b/contrib/guix/INSTALL.md @@ -71,13 +71,13 @@ https://repology.org/project/guix/versions ### Debian / Ubuntu -Guix is available as a distribution package in [Debian -](https://packages.debian.org/search?keywords=guix) and [Ubuntu -](https://packages.ubuntu.com/search?keywords=guix). +Currently, the `guix` package is no longer present in recent Debian or Ubuntu +repositories. Any other installation option mentioned in this document may be +used. -To install: +If you previously installed `guix` via `apt`, you can remove it with: ```sh -sudo apt install guix +sudo apt purge guix ``` ### Arch Linux From fdaf6565a974fb65a290618b0f3b0f5268481795 Mon Sep 17 00:00:00 2001 From: fanquake Date: Fri, 30 Jan 2026 11:50:39 +0000 Subject: [PATCH 16/16] doc: update release notes for v30.x --- doc/release-notes.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/doc/release-notes.md b/doc/release-notes.md index e8ce2e59f29..f5d7d3976db 100644 --- a/doc/release-notes.md +++ b/doc/release-notes.md @@ -44,44 +44,70 @@ Notable changes - #34358 wallet: fix removeprunedfunds bug with conflicting transactions +### Net + +- #34549 net: reduce log level for PCP/NAT-PMP NOT_AUTHORIZED failures + ### PSBT - #34272 psbt: Fix PSBTInputSignedAndVerified bounds assert +### Miniscript + +- #34434 miniscript: correct and_v() properties + ### Build - #34281 build: Temporarily remove confusing and brittle -fdebug-prefix-map +- #34554 build: avoid exporting secp256k1 symbols +- #34627 guix: use a temporary file over sponge, drop moreutils ### Test - #34185 test: fix feature_pruning when built without wallet - #34282 qa: Fix Windows logging bug - #34390 test: allow overriding tar in get_previous_releases.py +- #34409 test: use ModuleNotFoundError in interface_ipc.py +- #34445 fuzz: Use AFL_SHM_ID for naming test directories +- #34608 test: Fix broken --valgrind handling after bitcoin wrapper ### Doc - #34252 doc: add 433 (Pay to Anchor) to bips.md - #34413 doc: Remove outdated -fdebug-prefix-map section in dev notes +- #34510 doc: fix broken bpftrace installation link +- #34561 wallet: rpc: manpage: fix example missing `fee_rate` argument +- #34671 doc: Update Guix install for Debian/Ubuntu ### CI - #32513 ci: remove 3rd party js from windows dll gha job - #34344 ci: update GitHub Actions versions +- #34453 ci: Always print low ccache hit rate notice +- #34461 ci: Print verbose build error message in test-each-commit Credits ======= Thanks to everyone who directly contributed to this release: +- ANAVHEOBA - brunoerg +- darosior - fanquake - Hennadii Stepanov +- jayvaliya - Lőrinc - m3dwards +- marcofleon - MarcoFalke - mzumsande - Padraic Slattery - Sebastian Falbesoner +- SomberNight +- theuni +- ToRyVand +- willcl-ark As well as to everyone that helped with translations on [Transifex](https://explore.transifex.com/bitcoin/bitcoin/).