rpc: use private broadcast from sendrawtransaction RPC if -privatebroadcast is ON
What changed, and why it matters
This commit updates the sendrawtransaction RPC command so that, when the optional -privatebroadcast setting is enabled, it broadcasts raw transactions over privacy-preserving networks (Tor/I2P) instead of sending them to all connected peers. It also adds a check that at least one of those networks is reachable, and updates the command's help text. There is no direct evidence in the commit of a security vulnerability being fixed; it appears to be a privacy feature enhancement.
No immediate security action required. Treat as a normal privacy feature commit. Reviewers may want to confirm that the new reachability check cannot be bypassed and that the private broadcast path handles failures gracefully.
Security signals we found
Privacy behavior change: sendrawtransaction now routes through Tor/I2P when -privatebroadcast is enabled
New runtime precondition check requiring reachable Tor or I2P networks before private broadcast
Help text updated to warn about privacy implications of unconditional broadcast
Evidence from the diff
The change modifies src/rpc/mempool.cpp. It imports common/args.h and netbase.h, reads the -privatebroadcast boolean flag, validates that either NET_ONION or NET_I2P is reachable when private broadcast is requested, and selects between node::TxBroadcast::NO_MEMPOOL_PRIVATE_BROADCAST and node::TxBroadcast::MEMPOOL_AND_BROADCAST_TO_ALL accordingly. The help text is rewritten to describe both modes. No bug, crash, or exploit mechanism is visible in the diff.
Changed components
src/rpc/mempool.cppsendrawtransaction RPCnode::TxBroadcast dispatchInspect captured patch +29 / −5
diff --git a/src/rpc/mempool.cpp b/src/rpc/mempool.cpp
index a9a67834..e5dbe825 100644
--- a/src/rpc/mempool.cpp
+++ b/src/rpc/mempool.cpp
@@ -8,10 +8,12 @@
#include <node/mempool_persist.h>
#include <chainparams.h>
+#include <common/args.h>
#include <consensus/validation.h>
#include <core_io.h>
#include <kernel/mempool_entry.h>
#include <net_processing.h>
+#include <netbase.h>
#include <node/mempool_persist_args.h>
#include <node/types.h>
#include <policy/rbf.h>
@@ -44,11 +46,21 @@ static RPCHelpMan sendrawtransaction()
{
return RPCHelpMan{
"sendrawtransaction",
- "Submit a raw transaction (serialized, hex-encoded) to local node and network.\n"
- "\nThe transaction will be sent unconditionally to all peers, so using sendrawtransaction\n"
- "for manual rebroadcast may degrade privacy by leaking the transaction's origin, as\n"
- "nodes will normally not rebroadcast non-wallet transactions already in their mempool.\n"
+ "Submit a raw transaction (serialized, hex-encoded) to the network.\n"
+
+ "\nIf -privatebroadcast is disabled, then the transaction will be put into the\n"
+ "local mempool of the node and will be sent unconditionally to all currently\n"
+ "connected peers, so using sendrawtransaction for manual rebroadcast will degrade\n"
+ "privacy by leaking the transaction's origin, as nodes will normally not\n"
+ "rebroadcast non-wallet transactions already in their mempool.\n"
+
+ "\nIf -privatebroadcast is enabled, then the transaction will be sent only via\n"
+ "dedicated, short-lived connections to Tor or I2P peers or IPv4/IPv6 peers\n"
+ "via the Tor network. This conceals the transaction's origin. The transaction\n"
+ "will only enter the local mempool when it is received back from the network.\n"
+
"\nA specific exception, RPC_TRANSACTION_ALREADY_IN_UTXO_SET, may throw if the transaction cannot be added to the mempool.\n"
+
"\nRelated RPCs: createrawtransaction, signrawtransactionwithkey\n",
{
{"hexstring", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The hex string of the raw transaction"},
@@ -98,11 +110,23 @@ static RPCHelpMan sendrawtransaction()
std::string err_string;
AssertLockNotHeld(cs_main);
NodeContext& node = EnsureAnyNodeContext(request.context);
+ const bool private_broadcast_enabled{gArgs.GetBoolArg("-privatebroadcast", DEFAULT_PRIVATE_BROADCAST)};
+ if (private_broadcast_enabled &&
+ !g_reachable_nets.Contains(NET_ONION) &&
+ !g_reachable_nets.Contains(NET_I2P)) {
+ throw JSONRPCError(RPC_MISC_ERROR,
+ "-privatebroadcast is enabled, but none of the Tor or I2P networks is "
+ "reachable. Maybe the location of the Tor proxy couldn't be retrieved "
+ "from the Tor daemon at startup. Check whether the Tor daemon is running "
+ "and that -torcontrol, -torpassword and -i2psam are configured properly.");
+ }
+ const auto method = private_broadcast_enabled ? node::TxBroadcast::NO_MEMPOOL_PRIVATE_BROADCAST
+ : node::TxBroadcast::MEMPOOL_AND_BROADCAST_TO_ALL;
const TransactionError err = BroadcastTransaction(node,
tx,
err_string,
max_raw_tx_fee,
- node::TxBroadcast::MEMPOOL_AND_BROADCAST_TO_ALL,
+ method,
/*wait_callback=*/true);
if (TransactionError::OK != err) {
throw JSONRPCTransactionError(err, err_string);
Why this scored 19/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.