Fix ZMQ-PUB reporting of mempool txes that were initially in stem phase
What changed, and why it matters
This patch fixes a bug in how Monero announces new mempool transactions over its ZeroMQ publish (ZMQ-PUB) interface. Before the fix, transactions that first arrived in a privacy-preserving 'stem' phase were incorrectly announced as if they were ordinary public mempool transactions, potentially leaking information about transaction timing or origin. The fix now only sends the ZMQ notification when the transaction is genuinely being broadcast publicly for the first time.
Apply the patch. Reviewers relying on ZMQ-PUB mempool events for analytics, wallet notifications, or network monitoring should be aware that prior behavior may have over-reported stem-phase transactions. No immediate active exploitation vector is evident, but operators should upgrade to avoid privacy-relevant information leakage.
Security signals we found
Privacy leak in transaction relay metadata
Incorrect ZMQ-PUB event condition for stem-phase transactions
Dandelion++ stem/broadcast relay state confusion
Return-value semantics clarified in API comments
Evidence from the diff
The change replaces tvc.m_added_to_pool with res as the condition for emitting a ZMQ txpool event. add_tx returns true only when the transaction passes verification AND is the first observation in a broadcasted relay method. The previous condition used tvc.m_added_to_pool, which could be true for stem-phase Dandelion transactions that entered the pool but were not yet broadcast. The header comments for both add_tx overloads are updated to clarify the return value semantics.
Changed components
src/cryptonote_core/cryptonote_core.cppsrc/cryptonote_core/tx_pool.hZMQ-PUB mempool transaction notificationsDandelion++ transaction relay logicInspect captured patch +5 / −2
diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp
index 473afa4..5b7da7c 100644
--- a/src/cryptonote_core/cryptonote_core.cpp
+++ b/src/cryptonote_core/cryptonote_core.cpp
@@ -1138,7 +1138,7 @@ namespace cryptonote
const bool res = m_mempool.add_tx(tx, tx_hash, blob, tx_weight, tvc, tx_relay, relayed, version);
// If new incoming tx passed verification and entered the pool, notify ZMQ
- if (!tvc.m_verifivation_failed && tvc.m_added_to_pool && matches_category(tx_relay, relay_category::legacy))
+ if (!tvc.m_verifivation_failed && res && matches_category(tx_relay, relay_category::legacy))
{
m_blockchain_storage.notify_txpool_event({txpool_event{
.tx = tx,
diff --git a/src/cryptonote_core/tx_pool.h b/src/cryptonote_core/tx_pool.h
index 8528e63..178b2af 100644
--- a/src/cryptonote_core/tx_pool.h
+++ b/src/cryptonote_core/tx_pool.h
@@ -116,6 +116,8 @@ namespace cryptonote
* @tx_relay how the transaction was received
* @param tx_weight the transaction's weight
* @param valid_input_verification_id a previously valid verID if non-null
+ * @return True if tx passes verification checks AND is first observation
+ * of tx in `broadcasted` relay method.
*/
bool add_tx(transaction &tx, const crypto::hash &id, const cryptonote::blobdata &blob,
size_t tx_weight, tx_verification_context& tvc, relay_method tx_relay, bool relayed,
@@ -144,7 +146,8 @@ namespace cryptonote
* passes the non-input consensus tests (e.g. for newly received relayed txs), then leave
* "nic_verified_hf_version" as its default value of 0 (there is no v0 fork).
*
- * @return true if the transaction passes validations, otherwise false
+ * @return True if tx passes verification checks AND is first observation
+ * of tx in `broadcasted` relay method.
*/
bool add_tx(transaction &tx, tx_verification_context& tvc, relay_method tx_relay, bool relayed,
uint8_t version, uint8_t nic_verified_hf_version = 0,
Why this scored 41/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.