zmq: Log bind error at Error level, abort startup on init error #35902

pull chriszeng1010 wants to merge 1 commits into bitcoin:master from chriszeng1010:zmq-bind-abort changing 9 files +113 −44
  1. chriszeng1010 commented at 5:38 PM on August 5, 2026: none

    History

    This picks up #33727, which was closed for inactivity

    Problem

    Currently when a user configures -zmqpub* and the socket can't be bound, the program proceeds normally. The only trace is a debug-level log line that's invisible in a default configuration.

    #33715 hit this when their port was already taken.

    What this PR does

    The node refuses to start if ZMQ is configured and setup failed. This failure is now logged at error level.

    Code path: GetNotifiers builds the notifier objects from the config; Create initializes them — starts the ZMQ engine and binds each socket. When initialization fails, Initialize() returns a util::Result carrying an error that names the notifier, the address and the OS error. Create() propagates it and init.cpp returns InitError with that message. On a node with no -zmqpub* option, the notifier list is empty.

    Why abort?

    #17445 fixed bitcoind crash from bad assert due to ZMQ config (issue #17185). Issue #33715 reports that when ZMQ port was already taken, bitcoind started but user could not see anything with getzmqnotifications. Maintainers at the time agreed it should log louder and abort at startup. Which is what this PR fixes.

    Testing

    bitcoind -regtest -zmqpubhashblock=foo Result: exits with code 1, and prints:

    [error] Failed to bind address foo for pubhashblock: Invalid argument
    Error: Failed to bind address foo for pubhashblock: Invalid argument
    

    interface_zmq.py asserts the exact full error message for five startup-failure cases: invalid address, invalid address on a later notifier after an earlier one bound successfully (partial success must still abort), unreachable IP (EADDRNOTAVAIL), and a port already taken by the node's own RPC server (EADDRINUSE, via rpc_port()). The expected errno text is derived with os.strerror on the platform running the test, since the string after the last ":" differs per platform; on Windows the two strings libzmq hardcodes are pinned instead.

    Verified locally on Windows; the full CI matrix is green on my fork.

    A node with no -zmqpub* options starts exactly as before — nothing changes for nodes that don't use ZMQ.

  2. DrahtBot added the label RPC/REST/ZMQ on Aug 5, 2026
  3. DrahtBot commented at 5:39 PM on August 5, 2026: 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/35902.

    <!--021abf342d371248e50ceaed478a90ca-->

    Reviews

    See the guideline and AI policy for information on the review process.

    Type Reviewers
    Concept ACK pinheadmz
    Approach ACK jeanpablojp

    If your review is incorrectly listed, please copy-paste <code>&lt;!--meta-tag:bot-skip--&gt;</code> into the comment that the bot should ignore.

    <!--5faf32d7da4f0f540f40219e4f7537a3-->

  4. sedited commented at 6:32 PM on August 5, 2026: contributor

    Can you drop the second commit? We don't typically change these names in separate drive-by commits.

  5. chriszeng1010 force-pushed on Aug 5, 2026
  6. chriszeng1010 commented at 6:38 PM on August 5, 2026: none

    Can you drop the second commit? We don't typically change these names in separate drive-by commits.

    Okie. Done.

  7. pinheadmz commented at 3:22 PM on August 6, 2026: member

    Concept ACK. Built and tested locally with a variety of zmq misconfigurations, permissioned ports, busy ports, etc.

    Before I review the code I just noticed that the error reason is not bubbled up to stdout, and I wonder if we can do that?

    Example: reason is "Failed to bind address, msg: Permission denied"

    --> bcd -regtest -zmqpubhashblock="tcp://127.0.0.1:80" -debug=zmq -printtoconsole=0
    Error: Initializing ZMQ interface failed.
    

    Example: reason is "Failed to bind address, msg: Address already in use"

    --> bcd -regtest -zmqpubhashblock="tcp://127.0.0.1:9050" -debug=zmq -printtoconsole=0
    Error: Initializing ZMQ interface failed.
    

    etc...

  8. chriszeng1010 force-pushed on Aug 7, 2026
  9. chriszeng1010 commented at 5:10 PM on August 7, 2026: none

    I refactored the Initialize()'s return type to util::Result<void> and it returns the error code and failure reason.

    Tested:

    $ mkdir -p /tmp/zmqcheck ./build/bin/bitcoind.exe -regtest -datadir="$(cygpath -m /tmp/zmqcheck)" -zmqpubhashblock=foo -printtoconsole=0 echo "exit code: $?" Error: Failed to bind address foo for pubhashblock: Invalid argument exit code: 1

  10. in test/functional/interface_zmq.py:189 in cdca6ca48b outdated
     183 | @@ -184,8 +184,10 @@ def setup_zmq_test(self, services, *, recv_timeout=60, sync_blocks=True, ipv6=Fa
     184 |      def test_basic(self, unix = False):
     185 |          self.log.info(f"Running basic test with {'ipc' if unix else 'tcp'} protocol")
     186 |  
     187 | -        # Invalid zmq arguments don't take down the node, see #17185.
     188 | -        self.restart_node(0, ["-zmqpubrawtx=foo", "-zmqpubhashtx=bar"])
     189 | +        # Invalid zmq arguments should cause exit with an error
     190 | +        self.stop_node(0)
     191 | +        self.nodes[0].assert_start_raises_init_error(extra_args=["-zmqpubrawtx=foo", "-zmqpubhashtx=bar"])
    


    pinheadmz commented at 6:14 PM on August 7, 2026:

    Now that you've got the error messages spitting out, can we cover a few specifically with the functional test? See how other tests add expected_msg to this check


    chriszeng1010 commented at 8:50 PM on August 11, 2026:

    ack.

  11. chriszeng1010 force-pushed on Aug 11, 2026
  12. chriszeng1010 commented at 8:47 PM on August 11, 2026: none

    Latest push should fix broken CI. Added expected_msg to existing test cases, plus a secondary case with different notifier type.

  13. chriszeng1010 force-pushed on Aug 11, 2026
  14. in test/functional/interface_zmq.py:193 in 97c7f61fb7 outdated
     190 | +        # Invalid zmq arguments should cause exit with an error naming the notifier and address
     191 | +        self.stop_node(0)
     192 | +        self.nodes[0].assert_start_raises_init_error(
     193 | +            extra_args=["-zmqpubrawtx=foo", "-zmqpubhashtx=bar"],
     194 | +            expected_msg="Error: Failed to bind address bar for pubhashtx: .+",
     195 | +            match=ErrorMatch.PARTIAL_REGEX,
    


    pinheadmz commented at 3:19 PM on August 12, 2026:

    Why use the regex here? You should be able to assert that the error is exactly this, right?

    Error: Failed to bind address foo for pubrawblock: Invalid argument

    Also I'd like to see a test covering the other expected errors:

    (IP not available locally) Error: Failed to bind address tcp://1.2.3.4:100 for pubrawblock: Can't assign requested address

    (Port in use already by RPC -- will need to use rpc_port() in the test framework) Error: Failed to bind address tcp://127.0.0.1:18443 for pubrawblock: Address already in use

  15. jeanpablojp commented at 1:29 PM on August 17, 2026: contributor

    Approach ACK.

    Built and ran interface_zmq.py, plus some manual broken-config scenarios, and the abort works. Agree with pinheadmz's test coverage requests.

    One small observation is that the PR description is stale relative to the head (Code path and Testing).

  16. chriszeng1010 force-pushed on Aug 17, 2026
  17. chriszeng1010 force-pushed on Aug 17, 2026
  18. chriszeng1010 commented at 9:16 PM on August 17, 2026: none

    Added new test cases and verified locally on Windows OS. Linux/MacOS are pending CI.

    I replaced the Regex with a string check. Since the error string after : comes from zmq_strerror, which isn't stable across platforms. The same failure will print different error messages on different platforms. Ie "Cannot assign requested address" on Linux, "Can't assign requested address" on Mac and "Address not available" on Windows.

    Also, in the test case we point -zmqpubrawblock at the node's own RPC port, looked up via rpc_port()

  19. Log ZMQ bind error at Error level, abort startup on ZMQ init error
    Co-authored-by: Ash Manning <10554686+A-Manning@users.noreply.github.com>
    Co-authored-by: Chris Zeng <chris.dz@gmail.com>
    311847fffa
  20. chriszeng1010 force-pushed on Aug 18, 2026
  21. chriszeng1010 commented at 7:10 PM on August 30, 2026: none

    Can the reviewers please take a look at this PR? Thanks.


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-09-04 07:51 UTC

This site is hosted by @0xB10C
More mirrored repositories can be found on mirror.b10c.me