Skip to content

Commit 13b8282

Browse files
author
Jeff Garzik
committed
Merge pull request #6103
2 parents 9733bc9 + 029e278 commit 13b8282

23 files changed

+850
-3
lines changed

configure.ac

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,12 @@ AC_ARG_ENABLE([glibc-back-compat],
137137
[use_glibc_compat=$enableval],
138138
[use_glibc_compat=no])
139139

140+
AC_ARG_ENABLE([zmq],
141+
[AC_HELP_STRING([--disable-zmq],
142+
[Disable ZMQ notifications])],
143+
[use_zmq=$enableval],
144+
[use_zmq=yes])
145+
140146
AC_ARG_WITH([protoc-bindir],[AS_HELP_STRING([--with-protoc-bindir=BIN_DIR],[specify protoc bin path])], [protoc_bin_path=$withval], [])
141147

142148
# Enable debug
@@ -833,6 +839,22 @@ if test x$bitcoin_enable_qt != xno; then
833839
fi
834840
fi
835841

842+
# conditional search for and use libzmq
843+
AC_MSG_CHECKING([whether to build ZMQ support])
844+
if test "x$use_zmq" = "xyes"; then
845+
AC_MSG_RESULT([yes])
846+
PKG_CHECK_MODULES([ZMQ],[libzmq],
847+
[AC_DEFINE([ENABLE_ZMQ],[1],[Define to 1 to enable ZMQ functions])],
848+
[AC_DEFINE([ENABLE_ZMQ],[0],[Define to 1 to enable ZMQ functions])
849+
AC_MSG_WARN([libzmq not found, disabling])
850+
use_zmq=no])
851+
else
852+
AC_MSG_RESULT([no, --disable-zmq used])
853+
AC_DEFINE_UNQUOTED([ENABLE_ZMQ],[0],[Define to 1 to enable ZMQ functions])
854+
fi
855+
856+
AM_CONDITIONAL([ENABLE_ZMQ], [test "x$use_zmq" = "xyes"])
857+
836858
AC_MSG_CHECKING([whether to build test_bitcoin])
837859
if test x$use_tests = xyes; then
838860
AC_MSG_RESULT([yes])

contrib/zmq/zmq_sub.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
#!/usr/bin/env python2
2+
3+
import array
4+
import binascii
5+
import zmq
6+
7+
port = 28332
8+
9+
zmqContext = zmq.Context()
10+
zmqSubSocket = zmqContext.socket(zmq.SUB)
11+
zmqSubSocket.setsockopt(zmq.SUBSCRIBE, "hashblock")
12+
zmqSubSocket.setsockopt(zmq.SUBSCRIBE, "hashtx")
13+
zmqSubSocket.setsockopt(zmq.SUBSCRIBE, "rawblock")
14+
zmqSubSocket.setsockopt(zmq.SUBSCRIBE, "rawtx")
15+
zmqSubSocket.connect("tcp://127.0.0.1:%i" % port)
16+
17+
try:
18+
while True:
19+
msg = zmqSubSocket.recv_multipart()
20+
topic = str(msg[0])
21+
body = msg[1]
22+
23+
if topic == "hashblock":
24+
print "- HASH BLOCK -"
25+
print binascii.hexlify(body)
26+
elif topic == "hashtx":
27+
print '- HASH TX -'
28+
print binascii.hexlify(body)
29+
elif topic == "rawblock":
30+
print "- RAW BLOCK HEADER -"
31+
print binascii.hexlify(body[:80])
32+
elif topic == "rawtx":
33+
print '- RAW TX -'
34+
print binascii.hexlify(body)
35+
36+
except KeyboardInterrupt:
37+
zmqContext.destroy()

depends/packages/packages.mk

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
packages:=boost openssl libevent
2+
packages_darwin:=zeromq
3+
packages_linux:=zeromq
24
native_packages := native_ccache native_comparisontool
35

46
qt_native_packages = native_protobuf

depends/packages/zeromq.mk

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package=zeromq
2+
$(package)_version=4.0.4
3+
$(package)_download_path=http://download.zeromq.org
4+
$(package)_file_name=$(package)-$($(package)_version).tar.gz
5+
$(package)_sha256_hash=1ef71d46e94f33e27dd5a1661ed626cd39be4d2d6967792a275040e34457d399
6+
7+
define $(package)_set_vars
8+
$(package)_config_opts=--without-documentation --disable-shared
9+
$(package)_config_opts_linux=--with-pic
10+
endef
11+
12+
define $(package)_config_cmds
13+
$($(package)_autoconf)
14+
endef
15+
16+
define $(package)_build_cmds
17+
$(MAKE) -C src
18+
endef
19+
20+
define $(package)_stage_cmds
21+
$(MAKE) -C src DESTDIR=$($(package)_staging_dir) install
22+
endef
23+
24+
define $(package)_postprocess_cmds
25+
rm -rf bin share
26+
endef

doc/zmq.md

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
# Block and Transaction Broadcasting With ZeroMQ
2+
3+
[ZeroMQ](http://zeromq.org/) is a lightweight wrapper around TCP
4+
connections, inter-process communications, and shared-memory,
5+
providing various message-oriented semantics such as publish/subcribe,
6+
request/reply, and push/pull.
7+
8+
The Bitcoin Core daemon can be configured to act as a trusted "border
9+
router", implementing the bitcoin wire protocol and relay, making
10+
consensus decisions, maintaining the local blockchain database,
11+
broadcasting locally generated transactions into the network, and
12+
providing a queryable RPC interface to interact on a polled basis for
13+
requesting blockchain related data. However, there exists only a
14+
limited service to notify external software of events like the arrival
15+
of new blocks or transactions.
16+
17+
The ZeroMQ facility implements a notification interface through a
18+
set of specific notifiers. Currently there are notifiers that publish
19+
blocks and transactions. This read-only facility requires only the
20+
connection of a corresponding ZeroMQ subscriber port in receiving
21+
software; it is not authenticated nor is there any two-way protocol
22+
involvement. Therefore, subscribers should validate the received data
23+
since it may be out of date, incomplete or even invalid.
24+
25+
ZeroMQ sockets are self-connecting and self-healing; that is, connects
26+
made between two endpoints will be automatically restored after an
27+
outage, and either end may be freely started or stopped in any order.
28+
29+
Because ZeroMQ is message oriented, subscribers receive transactions
30+
and blocks all-at-once and do not need to implement any sort of
31+
buffering or reassembly.
32+
33+
## Prerequisites
34+
35+
The ZeroMQ feature in Bitcoin Core uses only a very small part of the
36+
ZeroMQ C API, and is thus compatible with any version of ZeroMQ
37+
from 2.1 onward, including all versions in the 3.x and 4.x release
38+
series. Typically, it is packaged by distributions as something like
39+
*libzmq-dev*.
40+
41+
The C++ wrapper for ZeroMQ is *not* needed.
42+
43+
## Enabling
44+
45+
By default, the ZeroMQ port functionality is enabled. Two steps are
46+
required to enable--compiling in the ZeroMQ code, and configuring
47+
runtime operation on the command-line or configuration file.
48+
49+
$ ./configure --enable-zmq (other options)
50+
51+
This will produce a binary that is capable of providing the ZeroMQ
52+
facility, but will not do so until also configured properly.
53+
54+
## Usage
55+
56+
Currently, the following notifications are supported:
57+
58+
-zmqpubhashtx=address
59+
-zmqpubhashblock=address
60+
-zmqpubrawblock=address
61+
-zmqpubrawtx=address
62+
63+
The socket type is PUB and the address must be a valid ZeroMQ
64+
socket address. The same address can be used in more than one notification.
65+
66+
For instance:
67+
68+
$ bitcoind -zmqpubhashtx=tcp://127.0.0.1:28332 -zmqpubrawtx=ipc:///tmp/bitcoind.tx.raw
69+
70+
Each PUB notification has a topic and body, where the header
71+
corresponds to the notification type. For instance, for the notification
72+
`-zmqpubhashtx` the topic is `hashtx` (no null terminator) and the body is the
73+
hexadecimal transaction hash (32 bytes).
74+
75+
These options can also be provided in bitcoin.conf.
76+
77+
ZeroMQ endpoint specifiers for TCP (and others) are documented in the
78+
[ZeroMQ API](http://api.zeromq.org).
79+
80+
Client side, then, the ZeroMQ subscriber socket must have the
81+
ZMQ_SUBSCRIBE option set to one or either of these prefixes (for instance, just `hash`); without
82+
doing so will result in no messages arriving. Please see `contrib/zmq/zmq_sub.py`
83+
for a working example.
84+
85+
## Remarks
86+
87+
From the perspective of bitcoind, the ZeroMQ socket is write-only; PUB
88+
sockets don't even have a read function. Thus, there is no state
89+
introduced into bitcoind directly. Furthermore, no information is
90+
broadcast that wasn't already received from the public P2P network.
91+
92+
No authentication or authorization is done on connecting clients; it
93+
is assumed that the ZeroMQ port is exposed only to trusted entities,
94+
using other means such as firewalling.
95+
96+
Note that when the block chain tip changes, a reorganisation may occur and just
97+
the tip will be notified. It is up to the subscriber to retrieve the chain
98+
from the last known block to the new tip.

qa/pull-tester/rpc-tests.sh

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,10 @@ testScriptsExt=(
5959
'p2p-acceptblock.py'
6060
);
6161

62+
if [ "x$ENABLE_ZMQ" = "x1" ]; then
63+
testScripts=( ${testScripts[@]} 'zmq_test.py' )
64+
fi
65+
6266
extArg="-extended"
6367
passOn=${@#$extArg}
6468

qa/pull-tester/tests-config.sh.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ EXEEXT="@EXEEXT@"
1010
@ENABLE_WALLET_TRUE@ENABLE_WALLET=1
1111
@BUILD_BITCOIN_UTILS_TRUE@ENABLE_UTILS=1
1212
@BUILD_BITCOIND_TRUE@ENABLE_BITCOIND=1
13+
@ENABLE_ZMQ_TRUE@ENABLE_ZMQ=1
1314

1415
REAL_BITCOIND="$BUILDDIR/src/bitcoind${EXEEXT}"
1516
REAL_BITCOINCLI="$BUILDDIR/src/bitcoin-cli${EXEEXT}"

qa/rpc-tests/zmq_test.py

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
#!/usr/bin/env python2
2+
# Copyright (c) 2015 The Bitcoin Core developers
3+
# Distributed under the MIT software license, see the accompanying
4+
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
5+
6+
#
7+
# Test ZMQ interface
8+
#
9+
10+
from test_framework.test_framework import BitcoinTestFramework
11+
from test_framework.util import *
12+
import zmq
13+
import binascii
14+
from test_framework.mininode import hash256
15+
16+
try:
17+
import http.client as httplib
18+
except ImportError:
19+
import httplib
20+
try:
21+
import urllib.parse as urlparse
22+
except ImportError:
23+
import urlparse
24+
25+
class ZMQTest (BitcoinTestFramework):
26+
27+
port = 28332
28+
29+
def setup_nodes(self):
30+
self.zmqContext = zmq.Context()
31+
self.zmqSubSocket = self.zmqContext.socket(zmq.SUB)
32+
self.zmqSubSocket.setsockopt(zmq.SUBSCRIBE, "hashblock")
33+
self.zmqSubSocket.setsockopt(zmq.SUBSCRIBE, "hashtx")
34+
self.zmqSubSocket.connect("tcp://127.0.0.1:%i" % self.port)
35+
# Note: proxies are not used to connect to local nodes
36+
# this is because the proxy to use is based on CService.GetNetwork(), which return NET_UNROUTABLE for localhost
37+
return start_nodes(4, self.options.tmpdir, extra_args=[
38+
['-zmqpubhashtx=tcp://127.0.0.1:'+str(self.port), '-zmqpubhashblock=tcp://127.0.0.1:'+str(self.port)],
39+
[],
40+
[],
41+
[]
42+
])
43+
44+
def run_test(self):
45+
self.sync_all()
46+
47+
genhashes = self.nodes[0].generate(1);
48+
self.sync_all()
49+
50+
print "listen..."
51+
msg = self.zmqSubSocket.recv_multipart()
52+
topic = str(msg[0])
53+
body = msg[1]
54+
55+
msg = self.zmqSubSocket.recv_multipart()
56+
topic = str(msg[0])
57+
body = msg[1]
58+
blkhash = binascii.hexlify(body)
59+
60+
assert_equal(genhashes[0], blkhash) #blockhash from generate must be equal to the hash received over zmq
61+
62+
n = 10
63+
genhashes = self.nodes[1].generate(n);
64+
self.sync_all()
65+
66+
zmqHashes = []
67+
for x in range(0,n*2):
68+
msg = self.zmqSubSocket.recv_multipart()
69+
topic = str(msg[0])
70+
body = msg[1]
71+
if topic == "hashblock":
72+
zmqHashes.append(binascii.hexlify(body))
73+
74+
for x in range(0,n):
75+
assert_equal(genhashes[x], zmqHashes[x]) #blockhash from generate must be equal to the hash received over zmq
76+
77+
#test tx from a second node
78+
hashRPC = self.nodes[1].sendtoaddress(self.nodes[0].getnewaddress(), 1.0)
79+
self.sync_all()
80+
81+
#now we should receive a zmq msg because the tx was broadcastet
82+
msg = self.zmqSubSocket.recv_multipart()
83+
topic = str(msg[0])
84+
body = msg[1]
85+
hashZMQ = ""
86+
if topic == "hashtx":
87+
hashZMQ = binascii.hexlify(body)
88+
89+
assert_equal(hashRPC, hashZMQ) #blockhash from generate must be equal to the hash received over zmq
90+
91+
92+
if __name__ == '__main__':
93+
ZMQTest ().main ()

src/Makefile.am

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,9 @@ if ENABLE_WALLET
4848
BITCOIN_INCLUDES += $(BDB_CPPFLAGS)
4949
EXTRA_LIBRARIES += libbitcoin_wallet.a
5050
endif
51+
if ENABLE_ZMQ
52+
EXTRA_LIBRARIES += libbitcoin_zmq.a
53+
endif
5154

5255
if BUILD_BITCOIN_LIBS
5356
lib_LTLIBRARIES = libbitcoinconsensus.la
@@ -157,7 +160,12 @@ BITCOIN_CORE_H = \
157160
wallet/db.h \
158161
wallet/wallet.h \
159162
wallet/wallet_ismine.h \
160-
wallet/walletdb.h
163+
wallet/walletdb.h \
164+
zmq/zmqabstractnotifier.h \
165+
zmq/zmqconfig.h\
166+
zmq/zmqnotificationinterface.h \
167+
zmq/zmqpublishnotifier.h
168+
161169

162170
obj/build.h: FORCE
163171
@$(MKDIR_P) $(builddir)/obj
@@ -199,6 +207,17 @@ libbitcoin_server_a_SOURCES = \
199207
validationinterface.cpp \
200208
$(BITCOIN_CORE_H)
201209

210+
if ENABLE_ZMQ
211+
LIBBITCOIN_ZMQ=libbitcoin_zmq.a
212+
213+
libbitcoin_zmq_a_CPPFLAGS = $(BITCOIN_INCLUDES)
214+
libbitcoin_zmq_a_SOURCES = \
215+
zmq/zmqabstractnotifier.cpp \
216+
zmq/zmqnotificationinterface.cpp \
217+
zmq/zmqpublishnotifier.cpp
218+
endif
219+
220+
202221
# wallet: shared between bitcoind and bitcoin-qt, but only linked
203222
# when wallet enabled
204223
libbitcoin_wallet_a_CPPFLAGS = $(BITCOIN_INCLUDES)
@@ -320,12 +339,15 @@ bitcoind_LDADD = \
320339
$(LIBMEMENV) \
321340
$(LIBSECP256K1)
322341

342+
if ENABLE_ZMQ
343+
bitcoind_LDADD += $(LIBBITCOIN_ZMQ) $(ZMQ_LIBS)
344+
endif
345+
323346
if ENABLE_WALLET
324347
bitcoind_LDADD += libbitcoin_wallet.a
325348
endif
326349

327350
bitcoind_LDADD += $(BOOST_LIBS) $(BDB_LIBS) $(SSL_LIBS) $(CRYPTO_LIBS) $(MINIUPNPC_LIBS) $(EVENT_PTHREADS_LIBS) $(EVENT_LIBS)
328-
#
329351

330352
# bitcoin-cli binary #
331353
bitcoin_cli_SOURCES = bitcoin-cli.cpp

src/Makefile.qt.include

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -361,6 +361,9 @@ qt_bitcoin_qt_LDADD = qt/libbitcoinqt.a $(LIBBITCOIN_SERVER)
361361
if ENABLE_WALLET
362362
qt_bitcoin_qt_LDADD += $(LIBBITCOIN_WALLET)
363363
endif
364+
if ENABLE_ZMQ
365+
qt_bitcoin_qt_LDADD += $(LIBBITCOIN_ZMQ) $(ZMQ_LIBS)
366+
endif
364367
qt_bitcoin_qt_LDADD += $(LIBBITCOIN_CLI) $(LIBBITCOIN_COMMON) $(LIBBITCOIN_UTIL) $(LIBBITCOIN_CRYPTO) $(LIBBITCOIN_UNIVALUE) $(LIBLEVELDB) $(LIBMEMENV) \
365368
$(BOOST_LIBS) $(QT_LIBS) $(QT_DBUS_LIBS) $(QR_LIBS) $(PROTOBUF_LIBS) $(BDB_LIBS) $(SSL_LIBS) $(CRYPTO_LIBS) $(MINIUPNPC_LIBS) $(LIBSECP256K1) \
366369
$(EVENT_PTHREADS_LIBS) $(EVENT_LIBS)

0 commit comments

Comments
 (0)