Bitcoin Core 30.99.0
P2P Digital Currency
zmqpublishnotifier.cpp
Go to the documentation of this file.
1// Copyright (c) 2015-present 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
6
7#include <chain.h>
8#include <crypto/common.h>
9#include <logging.h>
10#include <netaddress.h>
11#include <netbase.h>
13#include <serialize.h>
14#include <streams.h>
15#include <uint256.h>
16#include <util/check.h>
17#include <zmq/zmqutil.h>
18
19#include <zmq.h>
20
21#include <cstdarg>
22#include <cstddef>
23#include <cstdint>
24#include <cstring>
25#include <map>
26#include <optional>
27#include <span>
28#include <string>
29#include <utility>
30#include <vector>
31
32static std::multimap<std::string, CZMQAbstractPublishNotifier*> mapPublishNotifiers;
33
34static const char *MSG_HASHBLOCK = "hashblock";
35static const char *MSG_HASHTX = "hashtx";
36static const char *MSG_RAWBLOCK = "rawblock";
37static const char *MSG_RAWTX = "rawtx";
38static const char *MSG_SEQUENCE = "sequence";
39
40// Internal function to send multipart message
41static int zmq_send_multipart(void *sock, const void* data, size_t size, ...)
42{
43 va_list args;
44 va_start(args, size);
45
46 while (1)
47 {
48 zmq_msg_t msg;
49
50 int rc = zmq_msg_init_size(&msg, size);
51 if (rc != 0)
52 {
53 zmqError("Unable to initialize ZMQ msg");
54 va_end(args);
55 return -1;
56 }
57
58 void *buf = zmq_msg_data(&msg);
59 memcpy(buf, data, size);
60
61 data = va_arg(args, const void*);
62
63 rc = zmq_msg_send(&msg, sock, data ? ZMQ_SNDMORE : 0);
64 if (rc == -1)
65 {
66 zmqError("Unable to send ZMQ msg");
67 zmq_msg_close(&msg);
68 va_end(args);
69 return -1;
70 }
71
72 zmq_msg_close(&msg);
73
74 if (!data)
75 break;
76
77 size = va_arg(args, size_t);
78 }
79 va_end(args);
80 return 0;
81}
82
83static bool IsZMQAddressIPV6(const std::string &zmq_address)
84{
85 const std::string tcp_prefix = "tcp://";
86 const size_t tcp_index = zmq_address.rfind(tcp_prefix);
87 const size_t colon_index = zmq_address.rfind(':');
88 if (tcp_index == 0 && colon_index != std::string::npos) {
89 const std::string ip = zmq_address.substr(tcp_prefix.length(), colon_index - tcp_prefix.length());
90 const std::optional<CNetAddr> addr{LookupHost(ip, false)};
91 if (addr.has_value() && addr.value().IsIPv6()) return true;
92 }
93 return false;
94}
95
97{
99
100 // check if address is being used by other publish notifier
101 std::multimap<std::string, CZMQAbstractPublishNotifier*>::iterator i = mapPublishNotifiers.find(address);
102
103 if (i==mapPublishNotifiers.end())
104 {
105 psocket = zmq_socket(pcontext, ZMQ_PUB);
106 if (!psocket)
107 {
108 zmqError("Failed to create socket");
109 return false;
110 }
111
112 LogDebug(BCLog::ZMQ, "Outbound message high water mark for %s at %s is %d\n", type, address, outbound_message_high_water_mark);
113
114 int rc = zmq_setsockopt(psocket, ZMQ_SNDHWM, &outbound_message_high_water_mark, sizeof(outbound_message_high_water_mark));
115 if (rc != 0)
116 {
117 zmqError("Failed to set outbound message high water mark");
118 zmq_close(psocket);
119 return false;
120 }
121
122 const int so_keepalive_option {1};
123 rc = zmq_setsockopt(psocket, ZMQ_TCP_KEEPALIVE, &so_keepalive_option, sizeof(so_keepalive_option));
124 if (rc != 0) {
125 zmqError("Failed to set SO_KEEPALIVE");
126 zmq_close(psocket);
127 return false;
128 }
129
130 // On some systems (e.g. OpenBSD) the ZMQ_IPV6 must not be enabled, if the address to bind isn't IPv6
131 const int enable_ipv6 { IsZMQAddressIPV6(address) ? 1 : 0};
132 rc = zmq_setsockopt(psocket, ZMQ_IPV6, &enable_ipv6, sizeof(enable_ipv6));
133 if (rc != 0) {
134 zmqError("Failed to set ZMQ_IPV6");
135 zmq_close(psocket);
136 return false;
137 }
138
139 rc = zmq_bind(psocket, address.c_str());
140 if (rc != 0)
141 {
142 zmqError("Failed to bind address");
143 zmq_close(psocket);
144 return false;
145 }
146
147 // register this notifier for the address, so it can be reused for other publish notifier
148 mapPublishNotifiers.insert(std::make_pair(address, this));
149 return true;
150 }
151 else
152 {
153 LogDebug(BCLog::ZMQ, "Reusing socket for address %s\n", address);
154 LogDebug(BCLog::ZMQ, "Outbound message high water mark for %s at %s is %d\n", type, address, outbound_message_high_water_mark);
155
156 psocket = i->second->psocket;
157 mapPublishNotifiers.insert(std::make_pair(address, this));
158
159 return true;
160 }
161}
162
164{
165 // Early return if Initialize was not called
166 if (!psocket) return;
167
168 int count = mapPublishNotifiers.count(address);
169
170 // remove this notifier from the list of publishers using this address
171 typedef std::multimap<std::string, CZMQAbstractPublishNotifier*>::iterator iterator;
172 std::pair<iterator, iterator> iterpair = mapPublishNotifiers.equal_range(address);
173
174 for (iterator it = iterpair.first; it != iterpair.second; ++it)
175 {
176 if (it->second==this)
177 {
178 mapPublishNotifiers.erase(it);
179 break;
180 }
181 }
182
183 if (count == 1)
184 {
185 LogDebug(BCLog::ZMQ, "Close socket at address %s\n", address);
186 int linger = 0;
187 zmq_setsockopt(psocket, ZMQ_LINGER, &linger, sizeof(linger));
188 zmq_close(psocket);
189 }
190
191 psocket = nullptr;
192}
193
194bool CZMQAbstractPublishNotifier::SendZmqMessage(const char *command, const void* data, size_t size)
195{
197
198 /* send three parts, command & data & a LE 4byte sequence number */
199 unsigned char msgseq[sizeof(uint32_t)];
200 WriteLE32(msgseq, nSequence);
201 int rc = zmq_send_multipart(psocket, command, strlen(command), data, size, msgseq, (size_t)sizeof(uint32_t), nullptr);
202 if (rc == -1)
203 return false;
204
205 /* increment memory only sequence number after sending */
206 nSequence++;
207
208 return true;
209}
210
212{
213 uint256 hash = pindex->GetBlockHash();
214 LogDebug(BCLog::ZMQ, "Publish hashblock %s to %s\n", hash.GetHex(), this->address);
215 uint8_t data[32];
216 for (unsigned int i = 0; i < 32; i++) {
217 data[31 - i] = hash.begin()[i];
218 }
219 return SendZmqMessage(MSG_HASHBLOCK, data, 32);
220}
221
223{
224 uint256 hash = transaction.GetHash().ToUint256();
225 LogDebug(BCLog::ZMQ, "Publish hashtx %s to %s\n", hash.GetHex(), this->address);
226 uint8_t data[32];
227 for (unsigned int i = 0; i < 32; i++) {
228 data[31 - i] = hash.begin()[i];
229 }
230 return SendZmqMessage(MSG_HASHTX, data, 32);
231}
232
234{
235 LogDebug(BCLog::ZMQ, "Publish rawblock %s to %s\n", pindex->GetBlockHash().GetHex(), this->address);
236
237 std::vector<std::byte> block{};
238 if (!m_get_block_by_index(block, *pindex)) {
239 zmqError("Can't read block from disk");
240 return false;
241 }
242
243 return SendZmqMessage(MSG_RAWBLOCK, block.data(), block.size());
244}
245
247{
248 uint256 hash = transaction.GetHash().ToUint256();
249 LogDebug(BCLog::ZMQ, "Publish rawtx %s to %s\n", hash.GetHex(), this->address);
250 DataStream ss;
251 ss << TX_WITH_WITNESS(transaction);
252 return SendZmqMessage(MSG_RAWTX, &(*ss.begin()), ss.size());
253}
254
255// Helper function to send a 'sequence' topic message with the following structure:
256// <32-byte hash> | <1-byte label> | <8-byte LE sequence> (optional)
257static bool SendSequenceMsg(CZMQAbstractPublishNotifier& notifier, uint256 hash, char label, std::optional<uint64_t> sequence = {})
258{
259 unsigned char data[sizeof(hash) + sizeof(label) + sizeof(uint64_t)];
260 for (unsigned int i = 0; i < sizeof(hash); ++i) {
261 data[sizeof(hash) - 1 - i] = hash.begin()[i];
262 }
263 data[sizeof(hash)] = label;
264 if (sequence) WriteLE64(data + sizeof(hash) + sizeof(label), *sequence);
265 return notifier.SendZmqMessage(MSG_SEQUENCE, data, sequence ? sizeof(data) : sizeof(hash) + sizeof(label));
266}
267
269{
270 uint256 hash = pindex->GetBlockHash();
271 LogDebug(BCLog::ZMQ, "Publish sequence block connect %s to %s\n", hash.GetHex(), this->address);
272 return SendSequenceMsg(*this, hash, /* Block (C)onnect */ 'C');
273}
274
276{
277 uint256 hash = pindex->GetBlockHash();
278 LogDebug(BCLog::ZMQ, "Publish sequence block disconnect %s to %s\n", hash.GetHex(), this->address);
279 return SendSequenceMsg(*this, hash, /* Block (D)isconnect */ 'D');
280}
281
282bool CZMQPublishSequenceNotifier::NotifyTransactionAcceptance(const CTransaction &transaction, uint64_t mempool_sequence)
283{
284 uint256 hash = transaction.GetHash().ToUint256();
285 LogDebug(BCLog::ZMQ, "Publish hashtx mempool acceptance %s to %s\n", hash.GetHex(), this->address);
286 return SendSequenceMsg(*this, hash, /* Mempool (A)cceptance */ 'A', mempool_sequence);
287}
288
289bool CZMQPublishSequenceNotifier::NotifyTransactionRemoval(const CTransaction &transaction, uint64_t mempool_sequence)
290{
291 uint256 hash = transaction.GetHash().ToUint256();
292 LogDebug(BCLog::ZMQ, "Publish hashtx mempool removal %s to %s\n", hash.GetHex(), this->address);
293 return SendSequenceMsg(*this, hash, /* Mempool (R)emoval */ 'R', mempool_sequence);
294}
const auto command
ArgsManager & args
Definition: bitcoind.cpp:277
The block chain is a tree shaped structure starting with the genesis block at the root,...
Definition: chain.h:95
uint256 GetBlockHash() const
Definition: chain.h:199
The basic transaction that is broadcasted on the network and contained in blocks.
Definition: transaction.h:281
const Txid & GetHash() const LIFETIMEBOUND
Definition: transaction.h:328
bool SendZmqMessage(const char *command, const void *data, size_t size)
uint32_t nSequence
upcounting per message sequence number
bool Initialize(void *pcontext) override
bool NotifyBlock(const CBlockIndex *pindex) override
bool NotifyTransaction(const CTransaction &transaction) override
const std::function< bool(std::vector< std::byte > &, const CBlockIndex &)> m_get_block_by_index
bool NotifyBlock(const CBlockIndex *pindex) override
bool NotifyTransaction(const CTransaction &transaction) override
bool NotifyTransactionAcceptance(const CTransaction &transaction, uint64_t mempool_sequence) override
bool NotifyTransactionRemoval(const CTransaction &transaction, uint64_t mempool_sequence) override
bool NotifyBlockConnect(const CBlockIndex *pindex) override
bool NotifyBlockDisconnect(const CBlockIndex *pindex) override
Double ended buffer combining vector and stream-like interfaces.
Definition: streams.h:130
size_type size() const
Definition: streams.h:164
const_iterator begin() const
Definition: streams.h:160
constexpr unsigned char * begin()
Definition: uint256.h:100
std::string GetHex() const
Definition: uint256.cpp:11
const uint256 & ToUint256() const LIFETIMEBOUND
256-bit opaque blob.
Definition: uint256.h:195
void WriteLE32(B *ptr, uint32_t x)
Definition: common.h:50
void WriteLE64(B *ptr, uint64_t x)
Definition: common.h:57
static CService ip(uint32_t i)
#define LogDebug(category,...)
Definition: logging.h:412
uint64_t sequence
@ ZMQ
Definition: logging.h:92
std::vector< CNetAddr > LookupHost(const std::string &name, unsigned int nMaxSolutions, bool fAllowLookup, DNSLookupFn dns_lookup_function)
Resolve a host string to its corresponding network addresses.
Definition: netbase.cpp:173
static constexpr TransactionSerParams TX_WITH_WITNESS
Definition: transaction.h:180
static int count
assert(!tx.IsCoinBase())
static const char * MSG_HASHBLOCK
static const char * MSG_SEQUENCE
static const char * MSG_RAWBLOCK
static bool SendSequenceMsg(CZMQAbstractPublishNotifier &notifier, uint256 hash, char label, std::optional< uint64_t > sequence={})
static bool IsZMQAddressIPV6(const std::string &zmq_address)
static std::multimap< std::string, CZMQAbstractPublishNotifier * > mapPublishNotifiers
static const char * MSG_RAWTX
static int zmq_send_multipart(void *sock, const void *data, size_t size,...)
static const char * MSG_HASHTX
void zmqError(const std::string &str)
Definition: zmqutil.cpp:15