Fixes #33715
zmq: Log bind error at Error level, abort startup on init error #33727
pull isrod wants to merge 1 commits into bitcoin:master from isrod:2025-10-28-fix-33715 changing 7 files +64 −30-
isrod commented at 6:59 PM on October 28, 2025: none
-
DrahtBot commented at 6:59 PM on October 28, 2025: contributor
<!--e57a25ab6845829454e8d69fc972939a-->
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.
<!--006a51241073e994b41acfe9ec718e94-->
Code Coverage & Benchmarks
For details see: https://corecheck.dev/bitcoin/bitcoin/pulls/33727.
<!--021abf342d371248e50ceaed478a90ca-->
Reviews
See the guideline for information on the review process.
Type Reviewers Concept ACK stickies-v, pinheadmz If your review is incorrectly listed, please copy-paste <code><!--meta-tag:bot-skip--></code> into the comment that the bot should ignore.
<!--174a7506f384e20aa4161008e828411d-->
Conflicts
Reviewers, this pull request conflicts with the following ones:
- #34806 (refactor: logging: Various API improvements by ajtowns)
If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first.
<!--5faf32d7da4f0f540f40219e4f7537a3-->
LLM Linter (✨ experimental)
Possible places where named args for integral literals may be used (e.g.
func(x, /*named_arg=*/0)in C++, andfunc(x, named_arg=0)in Python):- [zmq_msg_send(&msg, sock, data ? ZMQ_SNDMORE : 0)] in src/zmq/zmqpublishnotifier.cpp
<sup>2026-03-18 12:01:38</sup>
- A-Manning force-pushed on Oct 28, 2025
-
in src/zmq/zmqnotificationinterface.h:30 in 759725ffed outdated
25 | @@ -26,7 +26,8 @@ class CZMQNotificationInterface final : public CValidationInterface 26 | 27 | std::list<const CZMQAbstractNotifier*> GetActiveNotifiers() const; 28 | 29 | - static std::unique_ptr<CZMQNotificationInterface> Create(std::function<bool(std::vector<std::byte>&, const CBlockIndex&)> get_block_by_index); 30 | + // nullopt if initialization fails 31 | + static std::optional<std::unique_ptr<CZMQNotificationInterface>> Create(std::function<bool(std::vector<std::byte>&, const CBlockIndex&)> get_block_by_index);
maflcko commented at 9:31 PM on October 28, 2025:pointers are nullable, so why wrap twice?
A-Manning commented at 10:34 PM on October 28, 2025:nullptris already used to indicate that no ZMQ interface was initialized. This is not the same as failing to initialize a ZMQ interface.
stickies-v commented at 12:20 PM on October 31, 2025:I also don't like this double wrapping, it's error prone. It seems like this is currently necessary because
Createis actually more like aMaybeCreate, with actual creation depending ongArgs. A more robust approach imo would be to takegArgsout ofCreate, let the caller decide if we actually want to create aCZMQNotificationInterfaceand pass the necessary parameters. With that change,Createshould always return a value, so we can keepnullptrfor the failure case?One approach could be to carve out a
GetNotifiersfunction:<details> <summary>git diff on 759725ffed</summary>
diff --git a/src/init.cpp b/src/init.cpp index cd3512f3a9..0582d7ed8f 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -1749,15 +1749,18 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) } #ifdef ENABLE_ZMQ - auto zmq_notification_interface = CZMQNotificationInterface::Create( + auto notifiers{CZMQNotificationInterface::GetNotifiers( + gArgs, [&chainman = node.chainman](std::vector<std::byte>& block, const CBlockIndex& index) { assert(chainman); return chainman->m_blockman.ReadRawBlock(block, WITH_LOCK(cs_main, return index.GetBlockPos())); - }); - if (zmq_notification_interface.has_value()) { - g_zmq_notification_interface = std::move(zmq_notification_interface.value()); - } else { - return InitError(Untranslated("Initializing ZMQ interface failed.")); + })}; + if (!notifiers.empty()) { + if (auto zmq_notification_interface{CZMQNotificationInterface::Create(std::move(notifiers))}) { + g_zmq_notification_interface = std::move(zmq_notification_interface); + } else { + return InitError(Untranslated("Initializing ZMQ interface failed.")); + } } if (g_zmq_notification_interface) { diff --git a/src/zmq/zmqnotificationinterface.cpp b/src/zmq/zmqnotificationinterface.cpp index eae377f718..c1e45e33e5 100644 --- a/src/zmq/zmqnotificationinterface.cpp +++ b/src/zmq/zmqnotificationinterface.cpp @@ -40,7 +40,9 @@ std::list<const CZMQAbstractNotifier*> CZMQNotificationInterface::GetActiveNotif return result; } -std::optional<std::unique_ptr<CZMQNotificationInterface>> CZMQNotificationInterface::Create(std::function<bool(std::vector<std::byte>&, const CBlockIndex&)> get_block_by_index) +std::list<std::unique_ptr<CZMQAbstractNotifier>> CZMQNotificationInterface::GetNotifiers( + const ArgsManager& args, + std::function<bool(std::vector<std::byte>&, const CBlockIndex&)> get_block_by_index) { std::map<std::string, CZMQNotifierFactory> factories; factories["pubhashblock"] = CZMQAbstractNotifier::Create<CZMQPublishHashBlockNotifier>; @@ -56,7 +58,7 @@ std::optional<std::unique_ptr<CZMQNotificationInterface>> CZMQNotificationInterf { std::string arg("-zmq" + entry.first); const auto& factory = entry.second; - for (std::string& address : gArgs.GetArgs(arg)) { + for (std::string& address : args.GetArgs(arg)) { // libzmq uses prefix "ipc://" for UNIX domain sockets if (address.starts_with(ADDR_PREFIX_UNIX)) { address.replace(0, ADDR_PREFIX_UNIX.length(), ADDR_PREFIX_IPC); @@ -65,24 +67,26 @@ std::optional<std::unique_ptr<CZMQNotificationInterface>> CZMQNotificationInterf std::unique_ptr<CZMQAbstractNotifier> notifier = factory(); notifier->SetType(entry.first); notifier->SetAddress(address); - notifier->SetOutboundMessageHighWaterMark(static_cast<int>(gArgs.GetIntArg(arg + "hwm", CZMQAbstractNotifier::DEFAULT_ZMQ_SNDHWM))); + notifier->SetOutboundMessageHighWaterMark(static_cast<int>(args.GetIntArg(arg + "hwm", CZMQAbstractNotifier::DEFAULT_ZMQ_SNDHWM))); notifiers.push_back(std::move(notifier)); } } + return notifiers; +} - if (!notifiers.empty()) - { - std::unique_ptr<CZMQNotificationInterface> notificationInterface(new CZMQNotificationInterface()); - notificationInterface->notifiers = std::move(notifiers); +std::unique_ptr<CZMQNotificationInterface> CZMQNotificationInterface::Create( + std::list<std::unique_ptr<CZMQAbstractNotifier>>&& notifiers) +{ + if (notifiers.empty()) return nullptr; - if (notificationInterface->Initialize()) { - return notificationInterface; - } else { - return std::nullopt; - } - } + std::unique_ptr<CZMQNotificationInterface> notificationInterface(new CZMQNotificationInterface()); + notificationInterface->notifiers = std::move(notifiers); - return nullptr; + if (notificationInterface->Initialize()) { + return notificationInterface; + } else { + return nullptr; + } } // Called at startup to conditionally set up ZMQ socket(s) diff --git a/src/zmq/zmqnotificationinterface.h b/src/zmq/zmqnotificationinterface.h index 0abbbef428..f36d633814 100644 --- a/src/zmq/zmqnotificationinterface.h +++ b/src/zmq/zmqnotificationinterface.h @@ -14,6 +14,7 @@ #include <memory> #include <vector> +class ArgsManager; class CBlock; class CBlockIndex; class CZMQAbstractNotifier; @@ -26,8 +27,11 @@ public: std::list<const CZMQAbstractNotifier*> GetActiveNotifiers() const; - // nullopt if initialization fails - static std::optional<std::unique_ptr<CZMQNotificationInterface>> Create(std::function<bool(std::vector<std::byte>&, const CBlockIndex&)> get_block_by_index); + static std::list<std::unique_ptr<CZMQAbstractNotifier>> GetNotifiers( + const ArgsManager& args, + std::function<bool(std::vector<std::byte>&, const CBlockIndex&)> get_block_by_index); + // nullptr if initialization fails + static std::unique_ptr<CZMQNotificationInterface> Create(std::list<std::unique_ptr<CZMQAbstractNotifier>>&& notifiers); protected: bool Initialize();</details>
Edit: adding some unit tests around those functions could also be helpful.
A-Manning commented at 4:43 PM on October 31, 2025:Applied the patch, thanks
in src/zmq/zmqnotificationinterface.cpp:43 in 759725ffed outdated
39 | @@ -40,7 +40,7 @@ std::list<const CZMQAbstractNotifier*> CZMQNotificationInterface::GetActiveNotif 40 | return result; 41 | } 42 | 43 | -std::unique_ptr<CZMQNotificationInterface> CZMQNotificationInterface::Create(std::function<bool(std::vector<std::byte>&, const CBlockIndex&)> get_block_by_index) 44 | +std::optional<std::unique_ptr<CZMQNotificationInterface>> CZMQNotificationInterface::Create(std::function<bool(std::vector<std::byte>&, const CBlockIndex&)> get_block_by_index)
stickies-v commented at 10:11 AM on October 31, 2025:nit: line length (also in .h)
in src/zmq/zmqutil.cpp:18 in 759725ffed outdated
15 | { 16 | - LogDebug(BCLog::ZMQ, "Error: %s, msg: %s\n", str, zmq_strerror(errno)); 17 | + LogPrintLevel(BCLog::ZMQ, level, "Error: %s, msg: %s\n", str, zmq_strerror(errno)); 18 | +} 19 | + 20 | +void zmqErrorDebug(const std::string& str)
stickies-v commented at 10:13 AM on October 31, 2025:ErrorandDebugare different levels, so this name seems confusing to me. Would simplify tozmqErrorandzmqDebug, removing thelevelparameter.Side note: I don't know much about zmq, but it seems like some of these debug messages should actually be warnings, e.g. when block gets failed to read from disk? Can/should probably be done in a separate pull, though.
A-Manning commented at 3:21 PM on October 31, 2025:I agree that
zmqDebug/zmqErrorwould be ideal, howeverzmqErroris currently used to log at theDebuglevel - changing it to log atErrorlevel while keeping the type signature the same seems likely to cause misuse.
stickies-v commented at 11:29 AM on November 3, 2025:This code isn't touched much, the last commit touching
zmqErroris from 2021. I don't think any authors or reviewers would assumezmqErrormeanszmqDebugwithout looking at the underlying code.On further inspection, it seems like none of these messages should be
debugto begin with? They all look likewarningorerrorto me? Finally,LogZmq{Error,Warning}would be a better name.Suggested (untested) diff to address all:
<details> <summary>git diff on 05661ef06b</summary>
diff --git a/src/zmq/zmqnotificationinterface.cpp b/src/zmq/zmqnotificationinterface.cpp index c1e45e33e5..85b8b995b9 100644 --- a/src/zmq/zmqnotificationinterface.cpp +++ b/src/zmq/zmqnotificationinterface.cpp @@ -103,7 +103,7 @@ bool CZMQNotificationInterface::Initialize() if (!pcontext) { - zmqErrorDebug("Unable to initialize context"); + LogZmqError("Unable to initialize context"); return false; } diff --git a/src/zmq/zmqpublishnotifier.cpp b/src/zmq/zmqpublishnotifier.cpp index 665f4a42f8..a7cbf48915 100644 --- a/src/zmq/zmqpublishnotifier.cpp +++ b/src/zmq/zmqpublishnotifier.cpp @@ -59,7 +59,7 @@ static int zmq_send_multipart(void *sock, const void* data, size_t size, ...) int rc = zmq_msg_init_size(&msg, size); if (rc != 0) { - zmqErrorDebug("Unable to initialize ZMQ msg"); + LogZmqWarning("Unable to initialize ZMQ msg"); va_end(args); return -1; } @@ -72,7 +72,7 @@ static int zmq_send_multipart(void *sock, const void* data, size_t size, ...) rc = zmq_msg_send(&msg, sock, data ? ZMQ_SNDMORE : 0); if (rc == -1) { - zmqErrorDebug("Unable to send ZMQ msg"); + LogZmqWarning("Unable to send ZMQ msg"); zmq_msg_close(&msg); va_end(args); return -1; @@ -114,7 +114,7 @@ bool CZMQAbstractPublishNotifier::Initialize(void *pcontext) psocket = zmq_socket(pcontext, ZMQ_PUB); if (!psocket) { - zmqErrorDebug("Failed to create socket"); + LogZmqError("Failed to create socket"); return false; } @@ -123,7 +123,7 @@ bool CZMQAbstractPublishNotifier::Initialize(void *pcontext) int rc = zmq_setsockopt(psocket, ZMQ_SNDHWM, &outbound_message_high_water_mark, sizeof(outbound_message_high_water_mark)); if (rc != 0) { - zmqErrorDebug("Failed to set outbound message high water mark"); + LogZmqError("Failed to set outbound message high water mark"); zmq_close(psocket); return false; } @@ -131,7 +131,7 @@ bool CZMQAbstractPublishNotifier::Initialize(void *pcontext) const int so_keepalive_option {1}; rc = zmq_setsockopt(psocket, ZMQ_TCP_KEEPALIVE, &so_keepalive_option, sizeof(so_keepalive_option)); if (rc != 0) { - zmqErrorDebug("Failed to set SO_KEEPALIVE"); + LogZmqError("Failed to set SO_KEEPALIVE"); zmq_close(psocket); return false; } @@ -140,7 +140,7 @@ bool CZMQAbstractPublishNotifier::Initialize(void *pcontext) const int enable_ipv6 { IsZMQAddressIPV6(address) ? 1 : 0}; rc = zmq_setsockopt(psocket, ZMQ_IPV6, &enable_ipv6, sizeof(enable_ipv6)); if (rc != 0) { - zmqErrorDebug("Failed to set ZMQ_IPV6"); + LogZmqError("Failed to set ZMQ_IPV6"); zmq_close(psocket); return false; } @@ -148,7 +148,7 @@ bool CZMQAbstractPublishNotifier::Initialize(void *pcontext) rc = zmq_bind(psocket, address.c_str()); if (rc != 0) { - zmqError(BCLog::Level::Error, "Failed to bind address"); + LogZmqError("Failed to bind address"); zmq_close(psocket); return false; } @@ -245,7 +245,7 @@ bool CZMQPublishRawBlockNotifier::NotifyBlock(const CBlockIndex *pindex) std::vector<std::byte> block{}; if (!m_get_block_by_index(block, *pindex)) { - zmqErrorDebug("Can't read block from disk"); + LogZmqWarning("Can't read block from disk"); return false; } diff --git a/src/zmq/zmqutil.cpp b/src/zmq/zmqutil.cpp index 86d0b5a2cf..e10817ce19 100644 --- a/src/zmq/zmqutil.cpp +++ b/src/zmq/zmqutil.cpp @@ -8,14 +8,18 @@ #include <zmq.h> #include <cerrno> -#include <string> +#include <string_view> -void zmqError(BCLog::Level level, const std::string& str) +void LogZmqError(std::string_view context) { - LogPrintLevel(BCLog::ZMQ, level, "Error: %s, msg: %s\n", str, zmq_strerror(errno)); + LogPrintLevel(BCLog::ZMQ, BCLog::Level::Error, + "%s, zmq_errno: %d, msg: %s\n", + context, errno, zmq_strerror(errno)); } -void zmqErrorDebug(const std::string& str) +void LogZmqWarning(std::string_view context) { - zmqError(BCLog::Level::Debug, str); + LogPrintLevel(BCLog::ZMQ, BCLog::Level::Warning, + "%s, zmq_errno: %d, msg: %s\n", + context, errno, zmq_strerror(errno)); } diff --git a/src/zmq/zmqutil.h b/src/zmq/zmqutil.h index 595e2c1d31..115254e5f4 100644 --- a/src/zmq/zmqutil.h +++ b/src/zmq/zmqutil.h @@ -8,12 +8,13 @@ #include <logging.h> #include <string> +#include <string_view> -/** Log ZMQ error at the specified level */ -void zmqError(BCLog::Level level, const std::string& str); +/** Log ZMQ failure with Error severity. Use for fatal errors that prevent ZMQ functionality. */ +void LogZmqError(std::string_view context); -/** Log ZMQ error at Debug level */ -void zmqErrorDebug(const std::string& str); +/** Log ZMQ failure with Warning severity. Use for non-fatal issues that may impact functionality. */ +void LogZmqWarning(std::string_view context); /** Prefix for unix domain socket addresses (which are local filesystem paths) */ const std::string ADDR_PREFIX_IPC = "ipc://"; // used by libzmq, example "ipc:///root/path/to/file"</details>
fanquake renamed this:Log ZMQ bind error at Error level, abort startup on ZMQ init error (#33715)
zmq: Log bind error at Error level, abort startup on init error
on Oct 31, 2025DrahtBot added the label RPC/REST/ZMQ on Oct 31, 2025A-Manning force-pushed on Oct 31, 2025in test/functional/interface_zmq.py:188 in 58cfd1cfc0 outdated
184 | @@ -185,8 +185,7 @@ def setup_zmq_test(self, services, *, recv_timeout=60, sync_blocks=True, ipv6=Fa 185 | def test_basic(self, unix = False): 186 | self.log.info(f"Running basic test with {'ipc' if unix else 'tcp'} protocol") 187 | 188 | - # Invalid zmq arguments don't take down the node, see #17185. 189 | - self.restart_node(0, ["-zmqpubrawtx=foo", "-zmqpubhashtx=bar"]) 190 | + self.restart_node(0, [])
stickies-v commented at 12:22 PM on October 31, 2025:Instead of disabling the test, we should probably test that invalid arguments shut down the node, as per the new behaviour?
A-Manning commented at 4:43 PM on October 31, 2025:Done
stickies-v commented at 12:23 PM on October 31, 2025: contributorConcept ACK, the new shutdown behaviour seems much more sane.
A-Manning force-pushed on Oct 31, 2025A-Manning force-pushed on Oct 31, 2025in test/functional/interface_zmq.py:190 in 05661ef06b outdated
184 | @@ -185,8 +185,10 @@ def setup_zmq_test(self, services, *, recv_timeout=60, sync_blocks=True, ipv6=Fa 185 | def test_basic(self, unix = False): 186 | self.log.info(f"Running basic test with {'ipc' if unix else 'tcp'} protocol") 187 | 188 | - # Invalid zmq arguments don't take down the node, see #17185. 189 | - self.restart_node(0, ["-zmqpubrawtx=foo", "-zmqpubhashtx=bar"]) 190 | + # Invalid zmq arguments should cause exit with an error 191 | + self.stop_node(0) 192 | + self.nodes[0].assert_start_raises_init_error(extra_args=["-zmqpubrawtx=foo", "-zmqpubhashtx=bar"])
pinheadmz commented at 3:46 PM on November 3, 2025:Would be nice to assert the
expected_msghere. Also, to more explicitly cover the issue in #33715 I'd expect to see a test where zmq is set to a valid tcp address but one that is already in use (you should be able to attempt a bind to node's p2p or rpc port)pinheadmz commented at 3:46 PM on November 3, 2025: memberconcept ACK
pinheadmz commented at 3:48 PM on November 3, 2025: memberWould be nice to get review from @promag or @laanwj because of the discussion in #17185 and https://github.com/bitcoin/bitcoin/pull/17445
DrahtBot added the label Needs rebase on Dec 12, 2025A-Manning force-pushed on Feb 13, 2026DrahtBot removed the label Needs rebase on Feb 13, 2026A-Manning force-pushed on Feb 13, 2026DrahtBot added the label CI failed on Feb 13, 2026DrahtBot commented at 10:01 AM on February 13, 2026: contributor<!--85328a0da195eb286784d51f73fa0af9-->
🚧 At least one of the CI tasks failed. <sub>Task
Windows-cross to x86_64, msvcrt: https://github.com/bitcoin/bitcoin/actions/runs/21980713763/job/63503214971</sub> <sub>LLM reason (✨ experimental): Compilation failed due to undefined LogPrintLevel in zmqutil.cpp.</sub><details><summary>Hints</summary>
Try to run the tests locally, according to the documentation. However, a CI failure may still happen due to a number of reasons, for example:
Possibly due to a silent merge conflict (the changes in this pull request being incompatible with the current code in the target branch). If so, make sure to rebase on the latest commit of the target branch.
A sanitizer issue, which can only be found by compiling with the sanitizer and running the affected test.
An intermittent issue.
Leave a comment here, if you need help tracking down a confusing failure.
</details>
maflcko commented at 10:59 AM on February 24, 2026: memberMaybe turn into draft while CI is red?
DrahtBot marked this as a draft on Mar 2, 2026maflcko commented at 6:25 AM on March 18, 2026: memberPlease squash your commits according to https://github.com/bitcoin/bitcoin/blob/master/CONTRIBUTING.md#squashing-commits
A-Manning force-pushed on Mar 18, 202617ef1c6b5dLog ZMQ bind error at Error level, abort startup on ZMQ init error
Co-authored-by: Ash Manning <10554686+A-Manning@users.noreply.github.com>
A-Manning force-pushed on Mar 18, 2026DrahtBot removed the label CI failed on Mar 24, 2026
github-metadata-mirror
This is a metadata mirror of the GitHub repository bitcoin/bitcoin. This site is not affiliated with GitHub. Content is generated from a GitHub metadata backup.
generated: 2026-05-03 21:12 UTC
This site is hosted by @0xB10C
More mirrored repositories can be found on mirror.b10c.me