rpc/net: report per-peer last_inv_sequence
What changed, and why it matters
This commit adds a new diagnostic field, last_inv_sequence, to the getpeerinfo RPC output and makes a small internal lock change so the value can be read safely. It is essentially an observability/debugging improvement for node operators and does not change network behavior or transaction relay rules. There is no indication it fixes an active security bug.
No security action required. Treat as a routine observability enhancement. Reviewers may verify the new lock annotation is consistent with existing m_tx_inventory_mutex usage.
Security signals we found
New RPC field exposes previously internal peer state
Lock annotation narrowed from global message-processing mutex to per-peer transaction-inventory mutex
No change to transaction relay, mempool, or consensus rules
Evidence from the diff
The patch exposes Peer::TxRelay::m_last_inv_sequence through CNodeStateStats and the getpeerinfo RPC as last_inv_sequence. To support safe concurrent reads it moves the lock annotation for m_last_inv_sequence from NetEventsInterface::g_msgproc_mutex to the finer-grained m_tx_inventory_mutex, and updates FindTxForGetData to take that mutex when reading the sequence. A functional test is updated to expect the new field. No consensus, mempool policy, or P2P protocol logic is altered.
Changed components
src/net_processing.cppsrc/net_processing.hsrc/rpc/net.cpptest/functional/rpc_net.pyInspect captured patch +11 / −5
diff --git a/src/net_processing.cpp b/src/net_processing.cpp
index 373169c0..ce4694d7 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<CAmount> 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<bool>& 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 ee12bd08..6eb4a5e1 100644
--- a/src/net_processing.h
+++ b/src/net_processing.h
@@ -55,6 +55,7 @@ struct CNodeStateStats {
std::vector<int> 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 e5b9880a..ba74283d 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 f52d59d7..aeaf20c2 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(),
Why this scored 18/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.