From adefb51c5437667696cacaf163ea08b39e961358 Mon Sep 17 00:00:00 2001 From: Anthony Towns Date: Sun, 7 May 2023 12:34:40 +1000 Subject: [PATCH 1/3] rpc/net: add per-peer inv_to_send sizes --- src/net_processing.cpp | 2 ++ src/net_processing.h | 1 + src/rpc/net.cpp | 2 ++ test/functional/rpc_net.py | 1 + 4 files changed, 6 insertions(+) diff --git a/src/net_processing.cpp b/src/net_processing.cpp index ec6f55cfdf6..373169c08bd 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -1728,9 +1728,11 @@ bool PeerManagerImpl::GetNodeStateStats(NodeId nodeid, CNodeStateStats& stats) c if (auto tx_relay = peer->GetTxRelay(); tx_relay != nullptr) { stats.m_relay_txs = WITH_LOCK(tx_relay->m_bloom_filter_mutex, return tx_relay->m_relay_txs); stats.m_fee_filter_received = tx_relay->m_fee_filter_received.load(); + stats.m_inv_to_send = WITH_LOCK(tx_relay->m_tx_inventory_mutex, return tx_relay->m_tx_inventory_to_send.size()); } else { stats.m_relay_txs = false; stats.m_fee_filter_received = 0; + stats.m_inv_to_send = 0; } stats.m_ping_wait = ping_wait; diff --git a/src/net_processing.h b/src/net_processing.h index 8c140d98ad6..ee12bd08d34 100644 --- a/src/net_processing.h +++ b/src/net_processing.h @@ -54,6 +54,7 @@ struct CNodeStateStats { std::chrono::microseconds m_ping_wait; std::vector vHeightInFlight; bool m_relay_txs; + int m_inv_to_send = 0; CAmount m_fee_filter_received; uint64_t m_addr_processed = 0; uint64_t m_addr_rate_limited = 0; diff --git a/src/rpc/net.cpp b/src/rpc/net.cpp index fbb70d72161..e5b9880ad96 100644 --- a/src/rpc/net.cpp +++ b/src/rpc/net.cpp @@ -142,6 +142,7 @@ static RPCHelpMan getpeerinfo() {RPCResult::Type::STR, "SERVICE_NAME", "the service name if it is recognised"} }}, {RPCResult::Type::BOOL, "relaytxes", "Whether we relay transactions to this peer"}, + {RPCResult::Type::NUM, "inv_to_send", "How many txs we have queued to announce to this peer"}, {RPCResult::Type::NUM_TIME, "lastsend", "The " + UNIX_EPOCH_TIME + " of the last send"}, {RPCResult::Type::NUM_TIME, "lastrecv", "The " + UNIX_EPOCH_TIME + " of the last receive"}, {RPCResult::Type::NUM_TIME, "last_transaction", "The " + UNIX_EPOCH_TIME + " of the last valid transaction received from this peer"}, @@ -238,6 +239,7 @@ static RPCHelpMan getpeerinfo() obj.pushKV("services", strprintf("%016x", services)); obj.pushKV("servicesnames", GetServicesNames(services)); obj.pushKV("relaytxes", statestats.m_relay_txs); + obj.pushKV("inv_to_send", statestats.m_inv_to_send); obj.pushKV("lastsend", count_seconds(stats.m_last_send)); obj.pushKV("lastrecv", count_seconds(stats.m_last_recv)); obj.pushKV("last_transaction", count_seconds(stats.m_last_tx_time)); diff --git a/test/functional/rpc_net.py b/test/functional/rpc_net.py index 41ecbbed22d..f52d59d7b3e 100755 --- a/test/functional/rpc_net.py +++ b/test/functional/rpc_net.py @@ -166,6 +166,7 @@ class NetTest(BitcoinTestFramework): "permissions": [], "presynced_headers": -1, "relaytxes": False, + "inv_to_send": 0, "services": "0000000000000000", "servicesnames": [], "session_id": "" if not self.options.v2transport else no_version_peer.v2_state.peer['session_id'].hex(), From 77b2ebb811824899f56976f8e3113914706edc97 Mon Sep 17 00:00:00 2001 From: Anthony Towns Date: Wed, 17 Sep 2025 12:17:41 +1000 Subject: [PATCH 2/3] rpc/net: report per-peer last_inv_sequence --- src/net_processing.cpp | 12 +++++++----- src/net_processing.h | 1 + src/rpc/net.cpp | 2 ++ test/functional/rpc_net.py | 1 + 4 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/net_processing.cpp b/src/net_processing.cpp index 373169c08bd..ce4694d797b 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -312,7 +312,7 @@ struct Peer { std::chrono::microseconds m_next_inv_send_time GUARDED_BY(m_tx_inventory_mutex){0}; /** The mempool sequence num at which we sent the last `inv` message to this peer. * Can relay txs with lower sequence numbers than this (see CTxMempool::info_for_relay). */ - uint64_t m_last_inv_sequence GUARDED_BY(NetEventsInterface::g_msgproc_mutex){1}; + uint64_t m_last_inv_sequence GUARDED_BY(m_tx_inventory_mutex){1}; /** Minimum fee rate with which to filter transaction announcements to this node. See BIP133. */ std::atomic m_fee_filter_received{0}; @@ -942,7 +942,7 @@ private: /** Determine whether or not a peer can request a transaction, and return it (or nullptr if not found or not allowed). */ CTransactionRef FindTxForGetData(const Peer::TxRelay& tx_relay, const GenTxid& gtxid) - EXCLUSIVE_LOCKS_REQUIRED(!m_most_recent_block_mutex, NetEventsInterface::g_msgproc_mutex); + EXCLUSIVE_LOCKS_REQUIRED(!m_most_recent_block_mutex, !tx_relay.m_tx_inventory_mutex); void ProcessGetData(CNode& pfrom, Peer& peer, const std::atomic& interruptMsgProc) EXCLUSIVE_LOCKS_REQUIRED(!m_most_recent_block_mutex, peer.m_getdata_requests_mutex, NetEventsInterface::g_msgproc_mutex) @@ -1728,7 +1728,9 @@ bool PeerManagerImpl::GetNodeStateStats(NodeId nodeid, CNodeStateStats& stats) c if (auto tx_relay = peer->GetTxRelay(); tx_relay != nullptr) { stats.m_relay_txs = WITH_LOCK(tx_relay->m_bloom_filter_mutex, return tx_relay->m_relay_txs); stats.m_fee_filter_received = tx_relay->m_fee_filter_received.load(); - stats.m_inv_to_send = WITH_LOCK(tx_relay->m_tx_inventory_mutex, return tx_relay->m_tx_inventory_to_send.size()); + LOCK(tx_relay->m_tx_inventory_mutex); + stats.m_last_inv_seq = tx_relay->m_last_inv_sequence; + stats.m_inv_to_send = tx_relay->m_tx_inventory_to_send.size(); } else { stats.m_relay_txs = false; stats.m_fee_filter_received = 0; @@ -2364,8 +2366,8 @@ void PeerManagerImpl::ProcessGetBlockData(CNode& pfrom, Peer& peer, const CInv& CTransactionRef PeerManagerImpl::FindTxForGetData(const Peer::TxRelay& tx_relay, const GenTxid& gtxid) { auto txinfo{std::visit( - [&](const auto& id) EXCLUSIVE_LOCKS_REQUIRED(NetEventsInterface::g_msgproc_mutex) { - return m_mempool.info_for_relay(id, tx_relay.m_last_inv_sequence); + [&](const auto& id) { + return m_mempool.info_for_relay(id, WITH_LOCK(tx_relay.m_tx_inventory_mutex, return tx_relay.m_last_inv_sequence)); }, gtxid)}; diff --git a/src/net_processing.h b/src/net_processing.h index ee12bd08d34..6eb4a5e16a2 100644 --- a/src/net_processing.h +++ b/src/net_processing.h @@ -55,6 +55,7 @@ struct CNodeStateStats { std::vector vHeightInFlight; bool m_relay_txs; int m_inv_to_send = 0; + uint64_t m_last_inv_seq{0}; CAmount m_fee_filter_received; uint64_t m_addr_processed = 0; uint64_t m_addr_rate_limited = 0; diff --git a/src/rpc/net.cpp b/src/rpc/net.cpp index e5b9880ad96..ba74283d237 100644 --- a/src/rpc/net.cpp +++ b/src/rpc/net.cpp @@ -142,6 +142,7 @@ static RPCHelpMan getpeerinfo() {RPCResult::Type::STR, "SERVICE_NAME", "the service name if it is recognised"} }}, {RPCResult::Type::BOOL, "relaytxes", "Whether we relay transactions to this peer"}, + {RPCResult::Type::NUM, "last_inv_sequence", "Mempool sequence number of this peer's last INV"}, {RPCResult::Type::NUM, "inv_to_send", "How many txs we have queued to announce to this peer"}, {RPCResult::Type::NUM_TIME, "lastsend", "The " + UNIX_EPOCH_TIME + " of the last send"}, {RPCResult::Type::NUM_TIME, "lastrecv", "The " + UNIX_EPOCH_TIME + " of the last receive"}, @@ -239,6 +240,7 @@ static RPCHelpMan getpeerinfo() obj.pushKV("services", strprintf("%016x", services)); obj.pushKV("servicesnames", GetServicesNames(services)); obj.pushKV("relaytxes", statestats.m_relay_txs); + obj.pushKV("last_inv_sequence", statestats.m_last_inv_seq); obj.pushKV("inv_to_send", statestats.m_inv_to_send); obj.pushKV("lastsend", count_seconds(stats.m_last_send)); obj.pushKV("lastrecv", count_seconds(stats.m_last_recv)); diff --git a/test/functional/rpc_net.py b/test/functional/rpc_net.py index f52d59d7b3e..aeaf20c23d5 100755 --- a/test/functional/rpc_net.py +++ b/test/functional/rpc_net.py @@ -167,6 +167,7 @@ class NetTest(BitcoinTestFramework): "presynced_headers": -1, "relaytxes": False, "inv_to_send": 0, + "last_inv_sequence": 0, "services": "0000000000000000", "servicesnames": [], "session_id": "" if not self.options.v2transport else no_version_peer.v2_state.peer['session_id'].hex(), From 2738b63e025d240618b3c72c28243c3e4d7d9c79 Mon Sep 17 00:00:00 2001 From: Anthony Towns Date: Tue, 23 Sep 2025 14:52:13 +1000 Subject: [PATCH 3/3] test: validate behaviour of getpeerinfo last_inv_sequence and inv_to_send Co-Authored-By: MarcoFalke <*~=`'#}+{/-|&$^_@721217.xyz> --- test/functional/p2p_leak_tx.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/test/functional/p2p_leak_tx.py b/test/functional/p2p_leak_tx.py index a1a00751d12..42e586fca3d 100755 --- a/test/functional/p2p_leak_tx.py +++ b/test/functional/p2p_leak_tx.py @@ -12,6 +12,7 @@ from test_framework.util import ( ) from test_framework.wallet import MiniWallet +import time class P2PNode(P2PDataStore): def on_inv(self, msg): @@ -36,8 +37,24 @@ class P2PLeakTxTest(BitcoinTestFramework): self.log.debug("Generate transaction and block") inbound_peer.last_message.pop("inv", None) + + self.gen_node.setmocktime(int(time.time())) # pause time based activities wtxid = self.miniwallet.send_self_transfer(from_node=self.gen_node)["wtxid"] + rawmp = self.gen_node.getrawmempool(False, True) + pi = self.gen_node.getpeerinfo()[0] + assert_equal(rawmp["mempool_sequence"], 2) # our tx cause mempool activity + assert_equal(pi["last_inv_sequence"], 1) # that is after the last inv + assert_equal(pi["inv_to_send"], 1) # and our tx has been queued + self.gen_node.setmocktime(0) + inbound_peer.wait_until(lambda: "inv" in inbound_peer.last_message and inbound_peer.last_message.get("inv").inv[0].hash == int(wtxid, 16)) + + rawmp = self.gen_node.getrawmempool(False, True) + pi = self.gen_node.getpeerinfo()[0] + assert_equal(rawmp["mempool_sequence"], 2) # no mempool update + assert_equal(pi["last_inv_sequence"], 2) # announced the current mempool + assert_equal(pi["inv_to_send"], 0) # nothing left in the queue + want_tx = msg_getdata(inv=inbound_peer.last_message.get("inv").inv) self.generate(self.gen_node, 1)