Bitcoin Core  31.0.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>
12 #include <primitives/transaction.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 
32 static std::multimap<std::string, CZMQAbstractPublishNotifier*> mapPublishNotifiers;
33 
34 static const char *MSG_HASHBLOCK = "hashblock";
35 static const char *MSG_HASHTX = "hashtx";
36 static const char *MSG_RAWBLOCK = "rawblock";
37 static const char *MSG_RAWTX = "rawtx";
38 static const char *MSG_SEQUENCE = "sequence";
39 
40 // Internal function to send multipart message
41 static 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 
83 static 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 {
98  assert(!psocket);
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 
194 bool CZMQAbstractPublishNotifier::SendZmqMessage(const char *command, const void* data, size_t size)
195 {
196  assert(psocket);
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)
257 static 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 
282 bool 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 
289 bool 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 }
static const char * MSG_HASHBLOCK
bool NotifyTransaction(const CTransaction &transaction) override
uint32_t nSequence
upcounting per message sequence number
assert(!tx.IsCoinBase())
const std::function< bool(std::vector< std::byte > &, const CBlockIndex &)> m_get_block_by_index
bool NotifyBlockDisconnect(const CBlockIndex *pindex) override
bool SendZmqMessage(const char *command, const void *data, size_t size)
static std::multimap< std::string, CZMQAbstractPublishNotifier * > mapPublishNotifiers
static bool SendSequenceMsg(CZMQAbstractPublishNotifier &notifier, uint256 hash, char label, std::optional< uint64_t > sequence={})
memcpy(result.begin(), stream.data(), stream.size())
static const char * MSG_HASHTX
bool NotifyTransaction(const CTransaction &transaction) override
void WriteLE64(B *ptr, uint64_t x)
Definition: common.h:57
constexpr unsigned char * begin()
Definition: uint256.h:100
bool NotifyBlock(const CBlockIndex *pindex) override
uint256 GetBlockHash() const
Definition: chain.h:198
void WriteLE32(B *ptr, uint32_t x)
Definition: common.h:50
ArgsManager & args
Definition: bitcoind.cpp:277
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
Double ended buffer combining vector and stream-like interfaces.
Definition: streams.h:132
bool NotifyBlock(const CBlockIndex *pindex) override
static int zmq_send_multipart(void *sock, const void *data, size_t size,...)
static CService ip(uint32_t i)
static bool IsZMQAddressIPV6(const std::string &zmq_address)
void zmqError(const std::string &str)
Definition: zmqutil.cpp:15
bool NotifyTransactionAcceptance(const CTransaction &transaction, uint64_t mempool_sequence) override
#define LogDebug(category,...)
Definition: log.h:115
256-bit opaque blob.
Definition: uint256.h:195
static const char * MSG_RAWTX
const auto command
static const char * MSG_RAWBLOCK
The block chain is a tree shaped structure starting with the genesis block at the root...
Definition: chain.h:93
bool NotifyTransactionRemoval(const CTransaction &transaction, uint64_t mempool_sequence) override
std::string GetHex() const
Definition: uint256.cpp:11
static int count
bool NotifyBlockConnect(const CBlockIndex *pindex) override
const uint256 & ToUint256() const LIFETIMEBOUND
The basic transaction that is broadcasted on the network and contained in blocks. ...
Definition: transaction.h:280
uint64_t sequence
const Txid & GetHash() const LIFETIMEBOUND
Definition: transaction.h:328
static constexpr TransactionSerParams TX_WITH_WITNESS
Definition: transaction.h:180
static const char * MSG_SEQUENCE
bool Initialize(void *pcontext) override