{
 "number": 35902,
 "repo": "bitcoin/bitcoin",
 "url": "https://github.com/bitcoin/bitcoin/pull/35902",
 "title": "zmq: Log bind error at Error level, abort startup on init error",
 "author": "chriszeng1010",
 "author_association": "FIRST_TIME_CONTRIBUTOR",
 "created_at": "2026-08-05T17:38:58Z",
 "updated_at": "2026-08-30T19:10:17Z",
 "age_days": 42,
 "draft": false,
 "labels": [
  "RPC/REST/ZMQ"
 ],
 "milestone": null,
 "base": "master",
 "head_sha": "311847fffa210b58436df138c11727d18a4c8aa9",
 "head_ref": "zmq-bind-abort",
 "head_repo": "chriszeng1010/bitcoin",
 "head_history": [
  {
   "t": "2026-08-05T18:38:02Z",
   "sha": "d9c4e5fbb6c2fd61b874ff0f7a22706065f07d7d"
  },
  {
   "t": "2026-08-07T16:59:16Z",
   "sha": "cdca6ca48bc4e5add17402a80efa7ab148b69e07"
  },
  {
   "t": "2026-08-11T20:46:33Z",
   "sha": "beeba77775ac9ee161df80c4166c9a31ea6619d3"
  },
  {
   "t": "2026-08-11T21:20:14Z",
   "sha": "97c7f61fb723e42dbe39fd78f0708510d5ac7307"
  },
  {
   "t": "2026-08-17T20:23:22Z",
   "sha": "3f273483218d06cb0222de3defd06bea3c58c042"
  },
  {
   "t": "2026-08-17T21:13:31Z",
   "sha": "194a159afcf01c51afd22f020f8602599fecffc1"
  },
  {
   "t": "2026-08-18T01:10:46Z",
   "sha": "311847fffa210b58436df138c11727d18a4c8aa9"
  }
 ],
 "additions": 113,
 "deletions": 44,
 "changed_files": 9,
 "commit_count": 1,
 "size_bucket": "M",
 "mergeable_state": "clean",
 "bot": {
  "drahtbot": {
   "present": true,
   "reviews": {
    "concept_ack": [
     {
      "login": "pinheadmz",
      "url": "https://github.com/bitcoin/bitcoin/pull/35902#issuecomment-5206864644"
     }
    ],
    "approach_ack": [
     {
      "login": "jeanpablojp",
      "url": "https://github.com/bitcoin/bitcoin/pull/35902#issuecomment-5316718875"
     }
    ]
   },
   "conflicts": []
  }
 },
 "acks_parsed": {
  "pinheadmz": {
   "kind": "concept_ack",
   "hash": null,
   "t": "2026-08-06T15:22:21Z",
   "stale": false
  },
  "jeanpablojp": {
   "kind": "approach_ack",
   "hash": null,
   "t": "2026-08-17T13:29:34Z",
   "stale": false
  }
 },
 "acks_tally": {
  "ack": 0,
  "stale_ack": 0,
  "concept_ack": 1,
  "approach_ack": 1,
  "nack": 0,
  "concept_nack": 0,
  "approach_nack": 0
 },
 "reviews": {
  "approved": 0,
  "changes_requested": 0,
  "distinct_reviewers": [
   "jeanpablojp",
   "pinheadmz",
   "sedited"
  ]
 },
 "signals": {
  "needs_rebase": false,
  "ci_failed": false,
  "mergeable_state": "clean",
  "last_author_activity": "2026-08-30T19:10:17Z",
  "last_reviewer_activity": "2026-08-17T13:29:34Z",
  "last_reviewer": "jeanpablojp",
  "author_silent_days": 17,
  "waiting_on_author_days": 0,
  "days_since_update": 17
 },
 "refs": {
  "mentioned": [
   17185,
   17445,
   33715,
   33727
  ],
  "depends_on": [],
  "fixes": [],
  "linked_issues": [],
  "references": [
   {
    "number": 33727,
    "type": "pull",
    "state": "closed",
    "merged": false,
    "merged_at": null,
    "title": "zmq: Log bind error at Error level, abort startup on init error"
   },
   {
    "number": 33715,
    "type": "issue",
    "state": "open",
    "merged": false,
    "merged_at": null,
    "title": "Failure to bind to ZMQ addresses is swallowed in debug logs"
   },
   {
    "number": 17445,
    "type": "pull",
    "state": "closed",
    "merged": true,
    "merged_at": "2020-01-08",
    "title": "zmq: Fix due to invalid argument and multiple notifiers"
   },
   {
    "number": 17185,
    "type": "issue",
    "state": "closed",
    "merged": false,
    "merged_at": null,
    "title": "invalid zmq argument syntax on windows causes abort"
   }
  ],
  "conflicts": []
 },
 "stack": {
  "shares_commits_with": [],
  "based_on": [],
  "base_for": []
 },
 "review_paths": [
  "test/functional/interface_zmq.py"
 ],
 "body": "## History\n\nThis picks up #33727, which was closed for inactivity\n\n## Problem\n\nCurrently 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.\n\n#33715 hit this when their port was already taken.\n\n## What this PR does\nThe node refuses to start if ZMQ is configured and setup failed. This failure is now logged at error level.\n\nCode path:\nGetNotifiers builds the notifier objects from the config; Create initializes them \u2014 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.\n\n## Why abort?\n#17445 fixed bitcoind crash from bad assert due to ZMQ config (issue #17185).\nIssue #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.\n\n## Testing\n\nbitcoind -regtest -zmqpubhashblock=foo\nResult: exits with code 1, and prints:\n\n```\n[error] Failed to bind address foo for pubhashblock: Invalid argument\nError: Failed to bind address foo for pubhashblock: Invalid argument\n```\n\ninterface_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.\n\nVerified locally on Windows; the full CI matrix is green on my fork.\n\nA node with no -zmqpub* options starts exactly as before \u2014 nothing changes for nodes that don't use ZMQ.",
 "commits": [
  {
   "sha": "311847fffa210b58436df138c11727d18a4c8aa9",
   "date": "2026-08-18T01:00:46Z",
   "message": "Log ZMQ bind error at Error level, abort startup on ZMQ init error\n\nCo-authored-by: Ash Manning <10554686+A-Manning@users.noreply.github.com>\nCo-authored-by: Chris Zeng <chris.dz@gmail.com>"
  }
 ],
 "timeline": [
  {
   "t": "2026-08-05T18:32:14Z",
   "kind": "comment",
   "who": "sedited",
   "assoc": "MEMBER",
   "text": "Can you drop the second commit? We don't typically change these names in separate drive-by commits."
  },
  {
   "t": "2026-08-05T18:38:02Z",
   "kind": "force_push",
   "who": "chriszeng1010",
   "commit": "d9c4e5fbb6c2fd61b874ff0f7a22706065f07d7d"
  },
  {
   "t": "2026-08-05T18:38:24Z",
   "kind": "comment",
   "who": "chriszeng1010",
   "assoc": "NONE",
   "text": "[quoted text omitted]\n\nOkie. Done."
  },
  {
   "t": "2026-08-06T15:22:21Z",
   "kind": "comment",
   "who": "pinheadmz",
   "assoc": "MEMBER",
   "text": "Concept ACK. Built and tested locally with a variety of zmq misconfigurations, permissioned ports, busy ports, etc.\n\nBefore 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?\n\nExample: reason is \"Failed to bind address, msg: Permission denied\"\n```\n--> bcd -regtest -zmqpubhashblock=\"tcp://127.0.0.1:80\" -debug=zmq -printtoconsole=0\nError: Initializing ZMQ interface failed.\n```\n\nExample: reason is \"Failed to bind address, msg: Address already in use\"\n```\n--> bcd -regtest -zmqpubhashblock=\"tcp://127.0.0.1:9050\" -debug=zmq -printtoconsole=0\nError: Initializing ZMQ interface failed.\n```\n\netc..."
  },
  {
   "t": "2026-08-07T16:59:16Z",
   "kind": "force_push",
   "who": "chriszeng1010",
   "commit": "cdca6ca48bc4e5add17402a80efa7ab148b69e07"
  },
  {
   "t": "2026-08-07T17:10:11Z",
   "kind": "comment",
   "who": "chriszeng1010",
   "assoc": "NONE",
   "text": "I refactored the Initialize()'s return type to util::Result<void> and it returns the error code and failure reason.\n\nTested:\n\n$ mkdir -p /tmp/zmqcheck\n./build/bin/bitcoind.exe -regtest -datadir=\"$(cygpath -m /tmp/zmqcheck)\" -zmqpubhashblock=foo -printtoconsole=0\necho \"exit code: $?\"\nError: Failed to bind address foo for pubhashblock: Invalid argument\nexit code: 1"
  },
  {
   "t": "2026-08-07T18:14:05Z",
   "kind": "review_comment",
   "who": "pinheadmz",
   "assoc": "MEMBER",
   "path": "test/functional/interface_zmq.py",
   "commit": "beeba77775ac9ee161df80c4166c9a31ea6619d3",
   "in_reply_to": null,
   "text": "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"
  },
  {
   "t": "2026-08-11T20:46:33Z",
   "kind": "force_push",
   "who": "chriszeng1010",
   "commit": "beeba77775ac9ee161df80c4166c9a31ea6619d3"
  },
  {
   "t": "2026-08-11T20:47:23Z",
   "kind": "comment",
   "who": "chriszeng1010",
   "assoc": "NONE",
   "text": "Latest push should fix broken CI.\nAdded `expected_msg `to existing test cases, plus a secondary case with different notifier type."
  },
  {
   "t": "2026-08-11T20:50:18Z",
   "kind": "review_comment",
   "who": "chriszeng1010",
   "assoc": "NONE",
   "path": "test/functional/interface_zmq.py",
   "commit": "beeba77775ac9ee161df80c4166c9a31ea6619d3",
   "in_reply_to": 3737857625,
   "text": "ack."
  },
  {
   "t": "2026-08-11T21:20:14Z",
   "kind": "force_push",
   "who": "chriszeng1010",
   "commit": "97c7f61fb723e42dbe39fd78f0708510d5ac7307"
  },
  {
   "t": "2026-08-12T15:19:05Z",
   "kind": "review_comment",
   "who": "pinheadmz",
   "assoc": "MEMBER",
   "path": "test/functional/interface_zmq.py",
   "commit": "3f273483218d06cb0222de3defd06bea3c58c042",
   "in_reply_to": null,
   "text": "Why use the regex here? You should be able to assert that the error is exactly this, right?\n\n`Error: Failed to bind address foo for pubrawblock: Invalid argument`\n\nAlso I'd like to see a test covering the other expected errors:\n\n(IP not available locally)\n`Error: Failed to bind address tcp://1.2.3.4:100 for pubrawblock: Can't assign requested address`\n\n(Port in use already by RPC -- will need to use `rpc_port()` in the test framework)\n`Error: Failed to bind address tcp://127.0.0.1:18443 for pubrawblock: Address already in use`"
  },
  {
   "t": "2026-08-17T13:29:34Z",
   "kind": "comment",
   "who": "jeanpablojp",
   "assoc": "CONTRIBUTOR",
   "text": "Approach ACK.\n\nBuilt and ran `interface_zmq.py`, plus some manual broken-config scenarios, and the abort works. Agree with pinheadmz's test coverage requests.\n\nOne small observation is that the PR description is stale relative to the head (Code path and Testing)."
  },
  {
   "t": "2026-08-17T20:23:22Z",
   "kind": "force_push",
   "who": "chriszeng1010",
   "commit": "3f273483218d06cb0222de3defd06bea3c58c042"
  },
  {
   "t": "2026-08-17T21:13:31Z",
   "kind": "force_push",
   "who": "chriszeng1010",
   "commit": "194a159afcf01c51afd22f020f8602599fecffc1"
  },
  {
   "t": "2026-08-17T21:16:21Z",
   "kind": "comment",
   "who": "chriszeng1010",
   "assoc": "NONE",
   "text": "Added new test cases and verified locally on Windows OS. Linux/MacOS are pending CI.\n\nI 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.\n\nAlso, in the test case we point `-zmqpubrawblock` at the node's own RPC port, looked up via `rpc_port()`"
  },
  {
   "t": "2026-08-18T01:10:46Z",
   "kind": "force_push",
   "who": "chriszeng1010",
   "commit": "311847fffa210b58436df138c11727d18a4c8aa9"
  },
  {
   "t": "2026-08-30T19:10:17Z",
   "kind": "comment",
   "who": "chriszeng1010",
   "assoc": "NONE",
   "text": "Can the reviewers please take a look at this PR? Thanks."
  }
 ],
 "labels_log": [
  {
   "t": "2026-08-05T17:39:01Z",
   "action": "labeled",
   "label": "RPC/REST/ZMQ",
   "who": "DrahtBot"
  }
 ],
 "state_log": [],
 "text_chars": 5265,
 "text_tokens_estimate": 1316,
 "changed_paths": [
  "src/init.cpp",
  "src/zmq/zmqabstractnotifier.h",
  "src/zmq/zmqnotificationinterface.cpp",
  "src/zmq/zmqnotificationinterface.h",
  "src/zmq/zmqpublishnotifier.cpp",
  "src/zmq/zmqpublishnotifier.h",
  "src/zmq/zmqutil.cpp",
  "src/zmq/zmqutil.h",
  "test/functional/interface_zmq.py"
 ],
 "files": [
  {
   "path": "src/init.cpp",
   "add": 10,
   "del": 2
  },
  {
   "path": "src/zmq/zmqabstractnotifier.h",
   "add": 3,
   "del": 1
  },
  {
   "path": "src/zmq/zmqnotificationinterface.cpp",
   "add": 25,
   "del": 18
  },
  {
   "path": "src/zmq/zmqnotificationinterface.h",
   "add": 8,
   "del": 2
  },
  {
   "path": "src/zmq/zmqpublishnotifier.cpp",
   "add": 20,
   "del": 16
  },
  {
   "path": "src/zmq/zmqpublishnotifier.h",
   "add": 1,
   "del": 1
  },
  {
   "path": "src/zmq/zmqutil.cpp",
   "add": 1,
   "del": 1
  },
  {
   "path": "src/zmq/zmqutil.h",
   "add": 2,
   "del": 1
  },
  {
   "path": "test/functional/interface_zmq.py",
   "add": 43,
   "del": 2
  }
 ],
 "test_lines": 45,
 "git": {
  "head": "311847fffa210b58436df138c11727d18a4c8aa9",
  "head_matches_backup": true,
  "base": "f72537037d3350e2974efd760eaa7b04f820880c",
  "commits": [
   {
    "sha": "311847fffa",
    "subject": "Log ZMQ bind error at Error level, abort startup on ZMQ init error",
    "files": 9,
    "add": 113,
    "del": 44
   }
  ],
  "patch_truncated": false
 },
 "input_hash": "f9e4d94115e5d8bc",
 "extracted_at": "2026-09-17T16:15:31+00:00"
}