Skip to content

Commit e6a14b6

Browse files
Jeff GarzikJoão Barbosa
authored andcommitted
Add ZeroMQ support. Notify blocks and transactions via ZeroMQ
Continues Johnathan Corgan's work. Publishing multipart messages Bugfix: Add missing zmq header includes Bugfix: Adjust build system to link ZeroMQ code for Qt binaries
1 parent 1136879 commit e6a14b6

16 files changed

+717
-4
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()

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.

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)

src/Makefile.qttest.include

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ qt_test_test_bitcoin_qt_LDADD = $(LIBBITCOINQT) $(LIBBITCOIN_SERVER)
3030
if ENABLE_WALLET
3131
qt_test_test_bitcoin_qt_LDADD += $(LIBBITCOIN_WALLET)
3232
endif
33+
if ENABLE_ZMQ
34+
qt_test_test_bitcoin_qt_LDADD += $(LIBBITCOIN_ZMQ) $(ZMQ_LIBS)
35+
endif
3336
qt_test_test_bitcoin_qt_LDADD += $(LIBBITCOIN_CLI) $(LIBBITCOIN_COMMON) $(LIBBITCOIN_UTIL) $(LIBBITCOIN_CRYPTO) $(LIBBITCOIN_UNIVALUE) $(LIBLEVELDB) \
3437
$(LIBMEMENV) $(BOOST_LIBS) $(QT_DBUS_LIBS) $(QT_TEST_LIBS) $(QT_LIBS) \
3538
$(QR_LIBS) $(PROTOBUF_LIBS) $(BDB_LIBS) $(SSL_LIBS) $(CRYPTO_LIBS) $(MINIUPNPC_LIBS) $(LIBSECP256K1) \

src/Makefile.test.include

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,10 @@ endif
100100
test_test_bitcoin_LDADD += $(LIBBITCOIN_CONSENSUS) $(BDB_LIBS) $(SSL_LIBS) $(CRYPTO_LIBS) $(MINIUPNPC_LIBS)
101101
test_test_bitcoin_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) -static
102102

103+
if ENABLE_ZMQ
104+
test_test_bitcoin_LDADD += $(ZMQ_LIBS)
105+
endif
106+
103107
nodist_test_test_bitcoin_SOURCES = $(GENERATED_TEST_FILES)
104108

105109
$(BITCOIN_TESTS): $(GENERATED_TEST_FILES)

src/init.cpp

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@
3838
#include "wallet/wallet.h"
3939
#include "wallet/walletdb.h"
4040
#endif
41-
4241
#include <stdint.h>
4342
#include <stdio.h>
4443

@@ -55,13 +54,21 @@
5554
#include <boost/thread.hpp>
5655
#include <openssl/crypto.h>
5756

57+
#if ENABLE_ZMQ
58+
#include "zmq/zmqnotificationinterface.h"
59+
#endif
60+
5861
using namespace std;
5962

6063
#ifdef ENABLE_WALLET
6164
CWallet* pwalletMain = NULL;
6265
#endif
6366
bool fFeeEstimatesInitialized = false;
6467

68+
#if ENABLE_ZMQ
69+
static CZMQNotificationInterface* pzmqNotificationInterface = NULL;
70+
#endif
71+
6572
#ifdef WIN32
6673
// Win32 LevelDB doesn't use filedescriptors, and the ones used for
6774
// accessing block files don't count towards the fd_set size limit
@@ -211,6 +218,16 @@ void Shutdown()
211218
if (pwalletMain)
212219
pwalletMain->Flush(true);
213220
#endif
221+
222+
#if ENABLE_ZMQ
223+
if (pzmqNotificationInterface) {
224+
UnregisterValidationInterface(pzmqNotificationInterface);
225+
pzmqNotificationInterface->Shutdown();
226+
delete pzmqNotificationInterface;
227+
pzmqNotificationInterface = NULL;
228+
}
229+
#endif
230+
214231
#ifndef WIN32
215232
try {
216233
boost::filesystem::remove(GetPidFile());
@@ -375,6 +392,14 @@ std::string HelpMessage(HelpMessageMode mode)
375392
" " + _("(1 = keep tx meta data e.g. account owner and payment request information, 2 = drop tx meta data)"));
376393
#endif
377394

395+
#if ENABLE_ZMQ
396+
strUsage += HelpMessageGroup(_("ZeroMQ notification options:"));
397+
strUsage += HelpMessageOpt("-zmqpubhashblock=<address>", _("Enable publish hash block in <address>"));
398+
strUsage += HelpMessageOpt("-zmqpubhashtransaction=<address>", _("Enable publish hash transaction in <address>"));
399+
strUsage += HelpMessageOpt("-zmqpubrawblock=<address>", _("Enable publish raw block in <address>"));
400+
strUsage += HelpMessageOpt("-zmqpubrawtransaction=<address>", _("Enable publish raw transaction in <address>"));
401+
#endif
402+
378403
strUsage += HelpMessageGroup(_("Debugging/Testing options:"));
379404
if (showDebug)
380405
{
@@ -1125,6 +1150,15 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler)
11251150
BOOST_FOREACH(const std::string& strDest, mapMultiArgs["-seednode"])
11261151
AddOneShot(strDest);
11271152

1153+
#if ENABLE_ZMQ
1154+
pzmqNotificationInterface = CZMQNotificationInterface::CreateWithArguments(mapArgs);
1155+
1156+
if (pzmqNotificationInterface) {
1157+
pzmqNotificationInterface->Initialize();
1158+
RegisterValidationInterface(pzmqNotificationInterface);
1159+
}
1160+
#endif
1161+
11281162
// ********************************************************* Step 7: load block chain
11291163

11301164
fReindex = GetBoolArg("-reindex", false);

src/validationinterface.cpp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@ void UnregisterAllValidationInterfaces() {
4545
g_signals.SetBestChain.disconnect_all_slots();
4646
g_signals.UpdatedTransaction.disconnect_all_slots();
4747
g_signals.SyncTransaction.disconnect_all_slots();
48-
g_signals.UpdatedTransaction.disconnect_all_slots();
4948
g_signals.UpdatedBlockTip.disconnect_all_slots();
5049
}
5150

src/zmq/zmqabstractnotifier.cpp

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// Copyright (c) 2015 The Bitcoin Core developers
2+
// Distributed under the MIT software license, see the accompanying
3+
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4+
5+
#include "zmqabstractnotifier.h"
6+
#include "util.h"
7+
8+
9+
CZMQAbstractNotifier::~CZMQAbstractNotifier()
10+
{
11+
assert(!psocket);
12+
}
13+
14+
bool CZMQAbstractNotifier::NotifyBlock(const uint256 &/*hash*/)
15+
{
16+
return true;
17+
}
18+
19+
bool CZMQAbstractNotifier::NotifyTransaction(const CTransaction &/*transaction*/)
20+
{
21+
return true;
22+
}

0 commit comments

Comments
 (0)