Skip to content

Commit 04c90f1

Browse files
committed
Merge bitcoin/bitcoin#27679: ZMQ: Support UNIX domain sockets
21d0e6c doc: release notes for PR 27679 (Matthew Zipkin) 791dea2 test: cover unix sockets in zmq interface (Matthew Zipkin) c87b0a0 zmq: accept unix domain socket address for notifier (Matthew Zipkin) Pull request description: This is a follow-up to bitcoin/bitcoin#27375, allowing ZMQ notifications to be published to a UNIX domain socket. Fortunately, libzmq handles unix sockets already, all we really have to do to support it is allow the format in the actual option. [libzmq](https://libzmq.readthedocs.io/en/latest/zmq_ipc.html) uses the prefix `ipc://` as opposed to `unix:` which is [used by Tor](https://gitlab.torproject.org/tpo/core/tor/-/blob/main/doc/man/tor.1.txt?ref_type=heads#L1475) and now also by [bitcoind](https://github.com/bitcoin/bitcoin/blob/a85e5a7c9ab75209bc88e49be6991ba0a467034e/doc/release-notes-27375.md?plain=1#L5) so we need to switch that internally. As far as I can tell, [LND](https://github.com/lightninglabs/gozmq/blob/d20a764486bf506bc045642e455bc7f0d21b232a/zmq.go#L38) supports `ipc://` and `unix://` (notice the double slashes). With this patch, LND can connect to bitcoind using unix sockets: Example: *bitcoin.conf*: ``` zmqpubrawblock=unix:/tmp/zmqsb zmqpubrawtx=unix:/tmp/zmqst ``` *lnd.conf*: ``` bitcoind.zmqpubrawblock=ipc:///tmp/zmqsb bitcoind.zmqpubrawtx=ipc:///tmp/zmqst ``` ACKs for top commit: laanwj: Code review ACK 21d0e6c tdb3: crACK for 21d0e6c. Changes lgtm. Will follow up with some testing within the next few days as time allows. achow101: ACK 21d0e6c guggero: Tested and code review ACK 21d0e6c Tree-SHA512: ffd50222e80dd029d903e5ddde37b83f72dfec1856a3f7ce49da3b54a45de8daaf80eea1629a30f58559f4b8ded0b29809548c0638cd1c2811b2736ad8b73030
2 parents ba7c67f + 21d0e6c commit 04c90f1

File tree

5 files changed

+50
-21
lines changed

5 files changed

+50
-21
lines changed

doc/release-notes-27679.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
- unix socket paths are now accepted for `-zmqpubrawblock` and `-zmqpubrawtx` with
2+
the format `-zmqpubrawtx=unix:/path/to/file`

src/init.cpp

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1301,30 +1301,33 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
13011301
}
13021302
}
13031303

1304-
for (const std::string port_option : {
1305-
"-i2psam",
1306-
"-onion",
1307-
"-proxy",
1308-
"-rpcbind",
1309-
"-torcontrol",
1310-
"-whitebind",
1311-
"-zmqpubhashblock",
1312-
"-zmqpubhashtx",
1313-
"-zmqpubrawblock",
1314-
"-zmqpubrawtx",
1315-
"-zmqpubsequence",
1304+
for (const auto &port_option : std::vector<std::pair<std::string, bool>>{
1305+
// arg name UNIX socket support
1306+
{"-i2psam", false},
1307+
{"-onion", true},
1308+
{"-proxy", true},
1309+
{"-rpcbind", false},
1310+
{"-torcontrol", false},
1311+
{"-whitebind", false},
1312+
{"-zmqpubhashblock", true},
1313+
{"-zmqpubhashtx", true},
1314+
{"-zmqpubrawblock", true},
1315+
{"-zmqpubrawtx", true},
1316+
{"-zmqpubsequence", true}
13161317
}) {
1317-
for (const std::string& socket_addr : args.GetArgs(port_option)) {
1318+
const std::string arg{port_option.first};
1319+
const bool unix{port_option.second};
1320+
for (const std::string& socket_addr : args.GetArgs(arg)) {
13181321
std::string host_out;
13191322
uint16_t port_out{0};
13201323
if (!SplitHostPort(socket_addr, port_out, host_out)) {
13211324
#if HAVE_SOCKADDR_UN
1322-
// Allow unix domain sockets for -proxy and -onion e.g. unix:/some/file/path
1323-
if ((port_option != "-proxy" && port_option != "-onion") || socket_addr.find(ADDR_PREFIX_UNIX) != 0) {
1324-
return InitError(InvalidPortErrMsg(port_option, socket_addr));
1325+
// Allow unix domain sockets for some options e.g. unix:/some/file/path
1326+
if (!unix || socket_addr.find(ADDR_PREFIX_UNIX) != 0) {
1327+
return InitError(InvalidPortErrMsg(arg, socket_addr));
13251328
}
13261329
#else
1327-
return InitError(InvalidPortErrMsg(port_option, socket_addr));
1330+
return InitError(InvalidPortErrMsg(arg, socket_addr));
13281331
#endif
13291332
}
13301333
}

src/zmq/zmqnotificationinterface.cpp

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include <kernel/chain.h>
99
#include <kernel/mempool_entry.h>
1010
#include <logging.h>
11+
#include <netbase.h>
1112
#include <primitives/block.h>
1213
#include <primitives/transaction.h>
1314
#include <validationinterface.h>
@@ -57,7 +58,12 @@ std::unique_ptr<CZMQNotificationInterface> CZMQNotificationInterface::Create(std
5758
{
5859
std::string arg("-zmq" + entry.first);
5960
const auto& factory = entry.second;
60-
for (const std::string& address : gArgs.GetArgs(arg)) {
61+
for (std::string& address : gArgs.GetArgs(arg)) {
62+
// libzmq uses prefix "ipc://" for UNIX domain sockets
63+
if (address.substr(0, ADDR_PREFIX_UNIX.length()) == ADDR_PREFIX_UNIX) {
64+
address.replace(0, ADDR_PREFIX_UNIX.length(), ADDR_PREFIX_IPC);
65+
}
66+
6167
std::unique_ptr<CZMQAbstractNotifier> notifier = factory();
6268
notifier->SetType(entry.first);
6369
notifier->SetAddress(address);

src/zmq/zmqutil.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,7 @@
99

1010
void zmqError(const std::string& str);
1111

12+
/** Prefix for unix domain socket addresses (which are local filesystem paths) */
13+
const std::string ADDR_PREFIX_IPC = "ipc://"; // used by libzmq, example "ipc:///root/path/to/file"
14+
1215
#endif // BITCOIN_ZMQ_ZMQUTIL_H

test/functional/interface_zmq.py

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
# Distributed under the MIT software license, see the accompanying
44
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
55
"""Test the ZMQ notification interface."""
6+
import os
67
import struct
8+
import tempfile
79
from time import sleep
810
from io import BytesIO
911

@@ -30,7 +32,7 @@
3032
from test_framework.wallet import (
3133
MiniWallet,
3234
)
33-
from test_framework.netutil import test_ipv6_local
35+
from test_framework.netutil import test_ipv6_local, test_unix_socket
3436

3537

3638
# Test may be skipped and not have zmq installed
@@ -119,6 +121,10 @@ def run_test(self):
119121
self.ctx = zmq.Context()
120122
try:
121123
self.test_basic()
124+
if test_unix_socket():
125+
self.test_basic(unix=True)
126+
else:
127+
self.log.info("Skipping ipc test, because UNIX sockets are not supported.")
122128
self.test_sequence()
123129
self.test_mempool_sync()
124130
self.test_reorg()
@@ -139,7 +145,7 @@ def setup_zmq_test(self, services, *, recv_timeout=60, sync_blocks=True, ipv6=Fa
139145
socket.setsockopt(zmq.IPV6, 1)
140146
subscribers.append(ZMQSubscriber(socket, topic.encode()))
141147

142-
self.restart_node(0, [f"-zmqpub{topic}={address}" for topic, address in services])
148+
self.restart_node(0, [f"-zmqpub{topic}={address.replace('ipc://', 'unix:')}" for topic, address in services])
143149

144150
for i, sub in enumerate(subscribers):
145151
sub.socket.connect(services[i][1])
@@ -176,12 +182,19 @@ def setup_zmq_test(self, services, *, recv_timeout=60, sync_blocks=True, ipv6=Fa
176182

177183
return subscribers
178184

179-
def test_basic(self):
185+
def test_basic(self, unix = False):
186+
self.log.info(f"Running basic test with {'ipc' if unix else 'tcp'} protocol")
180187

181188
# Invalid zmq arguments don't take down the node, see #17185.
182189
self.restart_node(0, ["-zmqpubrawtx=foo", "-zmqpubhashtx=bar"])
183190

184191
address = f"tcp://127.0.0.1:{self.zmq_port_base}"
192+
193+
if unix:
194+
# Use the shortest temp path possible since paths may have as little as 92-char limit
195+
socket_path = tempfile.NamedTemporaryFile().name
196+
address = f"ipc://{socket_path}"
197+
185198
subs = self.setup_zmq_test([(topic, address) for topic in ["hashblock", "hashtx", "rawblock", "rawtx"]])
186199

187200
hashblock = subs[0]
@@ -247,6 +260,8 @@ def test_basic(self):
247260
])
248261

249262
assert_equal(self.nodes[1].getzmqnotifications(), [])
263+
if unix:
264+
os.unlink(socket_path)
250265

251266
def test_reorg(self):
252267

0 commit comments

Comments
 (0)