What changed, and why it matters
This commit adds a new read-only RPC command called getprivatebroadcastinfo to Bitcoin Core. It lets users inspect transactions currently being broadcast privately, including their IDs, raw data, and which peers they were sent to and when. There is no code change that modifies how transactions are broadcast or how peers are chosen; it only exposes existing internal state through the RPC interface.
No security action required. Treat as a normal feature review; verify RPC help text and access controls are consistent with other informational RPCs.
Security signals we found
New read-only RPC exposing previously internal private-broadcast state
No authentication, authorization, consensus, or network-behavior changes
No memory safety, input parsing, or resource exhaustion signals in the diff
Fuzzing allow-list updated to include the new command
Evidence from the diff
The patch introduces a new RPC getprivatebroadcastinfo under the rawtransactions namespace. It wires PeerManagerImpl::GetPrivateBroadcastInfo() through net_processing to return PrivateBroadcast::TxBroadcastInfo entries, then formats them as JSON in src/rpc/mempool.cpp. The command is added to the fuzzing allow-list. It is a pure observability/informational addition with no state mutation, no authorization bypass, and no change to P2P protocol behavior.
Changed components
src/net_processing.cppsrc/net_processing.hsrc/rpc/mempool.cppsrc/test/fuzz/rpc.cppInspect captured patch +77 / −0
diff --git a/src/net_processing.cpp b/src/net_processing.cpp
index 1aefbf77..7ab05cd0 100644
--- a/src/net_processing.cpp
+++ b/src/net_processing.cpp
@@ -542,6 +542,7 @@ public:
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);
+ std::vector<PrivateBroadcast::TxBroadcastInfo> GetPrivateBroadcastInfo() const override EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex);
void SendPings() override EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex);
void InitiateTxBroadcastToAll(const Txid& txid, const Wtxid& wtxid) override EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex);
void InitiateTxBroadcastPrivate(const CTransactionRef& tx) override EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex);
@@ -1855,6 +1856,11 @@ PeerManagerInfo PeerManagerImpl::GetInfo() const
};
}
+std::vector<PrivateBroadcast::TxBroadcastInfo> PeerManagerImpl::GetPrivateBroadcastInfo() const
+{
+ return m_tx_for_private_broadcast.GetBroadcastInfo();
+}
+
void PeerManagerImpl::AddToCompactExtraTransactions(const CTransactionRef& tx)
{
if (m_opts.max_extra_txs <= 0)
diff --git a/src/net_processing.h b/src/net_processing.h
index 504e708d..4aac8daa 100644
--- a/src/net_processing.h
+++ b/src/net_processing.h
@@ -9,6 +9,7 @@
#include <consensus/amount.h>
#include <net.h>
#include <node/txorphanage.h>
+#include <private_broadcast.h>
#include <protocol.h>
#include <threadsafety.h>
#include <util/expected.h>
@@ -118,6 +119,9 @@ public:
/** Get peer manager info. */
virtual PeerManagerInfo GetInfo() const = 0;
+ /** Get info about transactions currently being privately broadcast. */
+ virtual std::vector<PrivateBroadcast::TxBroadcastInfo> GetPrivateBroadcastInfo() const = 0;
+
/**
* Initiate a transaction broadcast to eligible peers.
* Queue the witness transaction id to `Peer::TxRelay::m_tx_inventory_to_send`
diff --git a/src/rpc/mempool.cpp b/src/rpc/mempool.cpp
index 66ce1c61..35870368 100644
--- a/src/rpc/mempool.cpp
+++ b/src/rpc/mempool.cpp
@@ -137,6 +137,71 @@ static RPCHelpMan sendrawtransaction()
};
}
+static RPCHelpMan getprivatebroadcastinfo()
+{
+ return RPCHelpMan{
+ "getprivatebroadcastinfo",
+ "Returns information about transactions that are currently being privately broadcast.\n",
+ {},
+ RPCResult{
+ RPCResult::Type::OBJ, "", "",
+ {
+ {RPCResult::Type::ARR, "transactions", "",
+ {
+ {RPCResult::Type::OBJ, "", "",
+ {
+ {RPCResult::Type::STR_HEX, "txid", "The transaction hash in hex"},
+ {RPCResult::Type::STR_HEX, "wtxid", "The transaction witness hash in hex"},
+ {RPCResult::Type::STR_HEX, "hex", "The serialized, hex-encoded transaction data"},
+ {RPCResult::Type::ARR, "peers", "Per-peer send and acknowledgment information for this transaction",
+ {
+ {RPCResult::Type::OBJ, "", "",
+ {
+ {RPCResult::Type::STR, "address", "The address of the peer to which the transaction was sent"},
+ {RPCResult::Type::NUM_TIME, "sent", "The time this transaction was picked for sending to this peer via private broadcast (seconds since epoch)"},
+ {RPCResult::Type::NUM_TIME, "received", /*optional=*/true, "The time this peer acknowledged reception of the transaction (seconds since epoch)"},
+ }},
+ }},
+ }},
+ }},
+ }},
+ RPCExamples{
+ HelpExampleCli("getprivatebroadcastinfo", "")
+ + HelpExampleRpc("getprivatebroadcastinfo", "")
+ },
+ [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
+ {
+ const NodeContext& node{EnsureAnyNodeContext(request.context)};
+ const PeerManager& peerman{EnsurePeerman(node)};
+ const auto txs{peerman.GetPrivateBroadcastInfo()};
+
+ UniValue transactions(UniValue::VARR);
+ for (const auto& tx_info : txs) {
+ UniValue o(UniValue::VOBJ);
+ o.pushKV("txid", tx_info.tx->GetHash().ToString());
+ o.pushKV("wtxid", tx_info.tx->GetWitnessHash().ToString());
+ o.pushKV("hex", EncodeHexTx(*tx_info.tx));
+ UniValue peers(UniValue::VARR);
+ for (const auto& peer : tx_info.peers) {
+ UniValue p(UniValue::VOBJ);
+ p.pushKV("address", peer.address.ToStringAddrPort());
+ p.pushKV("sent", TicksSinceEpoch<std::chrono::seconds>(peer.sent));
+ if (peer.received.has_value()) {
+ p.pushKV("received", TicksSinceEpoch<std::chrono::seconds>(*peer.received));
+ }
+ peers.push_back(std::move(p));
+ }
+ o.pushKV("peers", std::move(peers));
+ transactions.push_back(std::move(o));
+ }
+
+ UniValue ret(UniValue::VOBJ);
+ ret.pushKV("transactions", std::move(transactions));
+ return ret;
+ },
+ };
+}
+
static RPCHelpMan testmempoolaccept()
{
return RPCHelpMan{
@@ -1329,6 +1394,7 @@ void RegisterMempoolRPCCommands(CRPCTable& t)
{
static const CRPCCommand commands[]{
{"rawtransactions", &sendrawtransaction},
+ {"rawtransactions", &getprivatebroadcastinfo},
{"rawtransactions", &testmempoolaccept},
{"blockchain", &getmempoolancestors},
{"blockchain", &getmempooldescendants},
diff --git a/src/test/fuzz/rpc.cpp b/src/test/fuzz/rpc.cpp
index ba052d3c..cfb36afd 100644
--- a/src/test/fuzz/rpc.cpp
+++ b/src/test/fuzz/rpc.cpp
@@ -147,6 +147,7 @@ const std::vector<std::string> RPC_COMMANDS_SAFE_FOR_FUZZING{
"getorphantxs",
"getpeerinfo",
"getprioritisedtransactions",
+ "getprivatebroadcastinfo",
"getrawaddrman",
"getrawmempool",
"getrawtransaction",
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.