rpc: report -txsendrate and bucket info via getnetworkinfo
What changed, and why it matters
This commit adds new read-only information fields to Bitcoin Core's getnetworkinfo RPC command. It exposes the configured transaction send rate and internal token-bucket statistics used to pace transaction announcements to peers. There is no code change that modifies behavior, permissions, or network processing; it only reports existing internal state to authorized RPC callers.
No security action required. Treat as a normal observability/monitoring enhancement. Reviewers may optionally consider whether the newly exposed bucket values reveal any node operational details that could aid traffic analysis, but this is informational state already derivable from network behavior.
Security signals we found
New RPC output fields only; no state mutation
No authentication or authorization changes
No network protocol changes
No memory safety or locking pattern regressions visible in diff
Mutex annotation updated to include m_inv_to_send_mutex for GetInfo
Evidence from the diff
The patch extends PeerManagerInfo and getnetworkinfo to surface m_opts.tx_send_rate and InvToSendBucket state (backlog size, count token bucket value, size token bucket value) for inbound and outbound directions. It adds an info() accessor to InvToSendBucket, updates GetInfo() to lock m_inv_to_send_mutex and populate the new fields, and documents/serializes them in src/rpc/net.cpp. No logic governing relay, rate limiting, or authentication is changed.
Changed components
src/net_processing.cppsrc/net_processing.hsrc/rpc/net.cppgetnetworkinfo RPCInspect captured patch +45 / −1
diff --git a/src/net_processing.cpp b/src/net_processing.cpp
index 56c79c2c..11af703c 100644
--- a/src/net_processing.cpp
+++ b/src/net_processing.cpp
@@ -551,6 +551,15 @@ struct InvToSendBucket {
bool count_ok = count_bucket.decrement(1, /*floor=*/count_floor);
return size_ok && count_ok;
}
+
+ PeerManagerInfo::InvBucketInfo info() const
+ {
+ return {
+ .backlog_count = backlog.size(),
+ .count_bucket = count_bucket.value(),
+ .size_bucket = size_bucket.value(),
+ };
+ }
};
class PeerManagerImpl final : public PeerManager
@@ -590,7 +599,7 @@ public:
EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex);
bool GetNodeStateStats(NodeId nodeid, CNodeStateStats& stats) const override EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex);
std::vector<node::TxOrphanage::OrphanInfo> GetOrphanTransactions() override EXCLUSIVE_LOCKS_REQUIRED(!m_tx_download_mutex);
- PeerManagerInfo GetInfo() const override EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex);
+ PeerManagerInfo GetInfo() const override EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex, !m_inv_to_send_mutex);
std::vector<PrivateBroadcast::TxBroadcastInfo> GetPrivateBroadcastInfo() const override EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex);
std::vector<CTransactionRef> AbortPrivateBroadcast(const uint256& id) override EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex);
void SendPings() override EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex);
@@ -1936,10 +1945,14 @@ std::vector<node::TxOrphanage::OrphanInfo> PeerManagerImpl::GetOrphanTransaction
PeerManagerInfo PeerManagerImpl::GetInfo() const
{
+ LOCK(m_inv_to_send_mutex);
return PeerManagerInfo{
.median_outbound_time_offset = m_outbound_time_offsets.Median(),
.ignores_incoming_txs = m_opts.ignore_incoming_txs,
.private_broadcast = m_opts.private_broadcast,
+ .tx_send_rate = m_opts.tx_send_rate,
+ .inbound_bucket = m_inbound_inv_bucket.info(),
+ .outbound_bucket = m_outbound_inv_bucket.info(),
};
}
diff --git a/src/net_processing.h b/src/net_processing.h
index 8e6c4b87..ff0d2baa 100644
--- a/src/net_processing.h
+++ b/src/net_processing.h
@@ -72,9 +72,18 @@ struct CNodeStateStats {
};
struct PeerManagerInfo {
+ struct InvBucketInfo {
+ size_t backlog_count{0};
+ double count_bucket{0};
+ double size_bucket{0};
+ };
+
std::chrono::seconds median_outbound_time_offset{0s};
bool ignores_incoming_txs{false};
bool private_broadcast{DEFAULT_PRIVATE_BROADCAST};
+ unsigned int tx_send_rate{0};
+ InvBucketInfo inbound_bucket;
+ InvBucketInfo outbound_bucket;
};
class PeerManager : public CValidationInterface, public NetEventsInterface
diff --git a/src/rpc/net.cpp b/src/rpc/net.cpp
index ba1080ed..2d20ff16 100644
--- a/src/rpc/net.cpp
+++ b/src/rpc/net.cpp
@@ -659,6 +659,16 @@ static RPCMethod getnetworkinfo()
}},
{RPCResult::Type::BOOL, "localrelay", "true if transaction relay is requested from peers"},
{RPCResult::Type::NUM, "timeoffset", "the time offset"},
+ {RPCResult::Type::NUM, "tx_send_rate", "configured target for maximum number of transactions per second to send to inbound peers"},
+ {RPCResult::Type::OBJ_DYN, "inv_buckets", "", {
+ {RPCResult::Type::OBJ, "inbound/outbound", "connection direction",
+ {
+ {RPCResult::Type::NUM, "backlog", "number of queued txs to announce"},
+ {RPCResult::Type::NUM, "count_tok", "tokens available to be consumed per-transaction"},
+ {RPCResult::Type::NUM, "size_tok", "tokens available to be consumed per-byte"},
+ }
+ }
+ }},
{RPCResult::Type::NUM, "connections", "the total number of connections"},
{RPCResult::Type::NUM, "connections_in", "the number of inbound connections"},
{RPCResult::Type::NUM, "connections_out", "the number of outbound connections"},
@@ -716,6 +726,18 @@ static RPCMethod getnetworkinfo()
auto peerman_info{node.peerman->GetInfo()};
obj.pushKV("localrelay", !peerman_info.ignores_incoming_txs);
obj.pushKV("timeoffset", Ticks<std::chrono::seconds>(peerman_info.median_outbound_time_offset));
+ obj.pushKV("tx_send_rate", peerman_info.tx_send_rate);
+ auto buckjson = [&](const auto& buckinfo) {
+ UniValue b{UniValue::VOBJ};
+ b.pushKV("backlog", buckinfo.backlog_count);
+ b.pushKV("count_tok", buckinfo.count_bucket);
+ b.pushKV("size_tok", buckinfo.size_bucket);
+ return b;
+ };
+ UniValue invbuckets{UniValue::VOBJ};
+ invbuckets.pushKV("inbound", buckjson(peerman_info.inbound_bucket));
+ invbuckets.pushKV("outbound", buckjson(peerman_info.outbound_bucket));
+ obj.pushKV("inv_buckets", invbuckets);
}
if (node.connman) {
obj.pushKV("networkactive", node.connman->GetNetworkActive());
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.