Skip to content

Commit 94aaa5d

Browse files
vasildbrunoergl0rinc
committed
init: introduce a new option to enable/disable private broadcast
Co-authored-by: brunoerg <[email protected]> Co-authored-by: Lőrinc <[email protected]>
1 parent d6ee490 commit 94aaa5d

File tree

3 files changed

+64
-3
lines changed

3 files changed

+64
-3
lines changed

src/init.cpp

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -670,6 +670,15 @@ void SetupServerArgs(ArgsManager& argsman, bool can_listen_ipc)
670670
OptionsCategory::NODE_RELAY);
671671
argsman.AddArg("-minrelaytxfee=<amt>", strprintf("Fees (in %s/kvB) smaller than this are considered zero fee for relaying, mining and transaction creation (default: %s)",
672672
CURRENCY_UNIT, FormatMoney(DEFAULT_MIN_RELAY_TX_FEE)), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY);
673+
argsman.AddArg("-privatebroadcast",
674+
strprintf(
675+
"Broadcast transactions submitted via sendrawtransaction RPC using short-lived "
676+
"connections through the Tor or I2P networks, without putting them in the mempool first. "
677+
"Transactions submitted through the wallet are not affected by this option "
678+
"(default: %u)",
679+
DEFAULT_PRIVATE_BROADCAST),
680+
ArgsManager::ALLOW_ANY,
681+
OptionsCategory::NODE_RELAY);
673682
argsman.AddArg("-whitelistforcerelay", strprintf("Add 'forcerelay' permission to whitelisted peers with default permissions. This will relay transactions even if the transactions were already in the mempool. (default: %d)", DEFAULT_WHITELISTFORCERELAY), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY);
674683
argsman.AddArg("-whitelistrelay", strprintf("Add 'relay' permission to whitelisted peers with default permissions. This will accept relayed transactions even when not relaying transactions (default: %d)", DEFAULT_WHITELISTRELAY), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY);
675684

@@ -1732,13 +1741,13 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
17321741
}
17331742
}
17341743

1744+
const bool listenonion{args.GetBoolArg("-listenonion", DEFAULT_LISTEN_ONION)};
17351745
if (onion_proxy.IsValid()) {
17361746
SetProxy(NET_ONION, onion_proxy);
17371747
} else {
17381748
// If -listenonion is set, then we will (try to) connect to the Tor control port
17391749
// later from the torcontrol thread and may retrieve the onion proxy from there.
1740-
const bool listenonion_disabled{!args.GetBoolArg("-listenonion", DEFAULT_LISTEN_ONION)};
1741-
if (onlynet_used_with_onion && listenonion_disabled) {
1750+
if (onlynet_used_with_onion && !listenonion) {
17421751
return InitError(
17431752
_("Outbound connections restricted to Tor (-onlynet=onion) but the proxy for "
17441753
"reaching the Tor network is not provided: none of -proxy, -onion or "
@@ -2119,7 +2128,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
21192128
connOptions.onion_binds.push_back(onion_service_target);
21202129
}
21212130

2122-
if (args.GetBoolArg("-listenonion", DEFAULT_LISTEN_ONION)) {
2131+
if (listenonion) {
21232132
if (connOptions.onion_binds.size() > 1) {
21242133
InitWarning(strprintf(_("More than one onion bind address is provided. Using %s "
21252134
"for the automatically created Tor onion service."),
@@ -2192,6 +2201,32 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
21922201
conflict->ToStringAddrPort()));
21932202
}
21942203

2204+
if (args.GetBoolArg("-privatebroadcast", DEFAULT_PRIVATE_BROADCAST)) {
2205+
// If -listenonion is set, then NET_ONION may not be reachable now
2206+
// but may become reachable later, thus only error here if it is not
2207+
// reachable and will not become reachable for sure.
2208+
const bool onion_may_become_reachable{listenonion && (!args.IsArgSet("-onlynet") || onlynet_used_with_onion)};
2209+
if (!g_reachable_nets.Contains(NET_I2P) &&
2210+
!g_reachable_nets.Contains(NET_ONION) &&
2211+
!onion_may_become_reachable) {
2212+
return InitError(_("Private broadcast of own transactions requested (-privatebroadcast), "
2213+
"but none of Tor or I2P networks is reachable"));
2214+
}
2215+
if (!connOptions.m_use_addrman_outgoing) {
2216+
return InitError(_("Private broadcast of own transactions requested (-privatebroadcast), "
2217+
"but -connect is also configured. They are incompatible because the "
2218+
"private broadcast needs to open new connections to randomly "
2219+
"chosen Tor or I2P peers. Consider using -maxconnections=0 -addnode=... "
2220+
"instead"));
2221+
}
2222+
if (!proxyRandomize && (g_reachable_nets.Contains(NET_ONION) || onion_may_become_reachable)) {
2223+
InitWarning(_("Private broadcast of own transactions requested (-privatebroadcast) and "
2224+
"-proxyrandomize is disabled. Tor circuits for private broadcast connections "
2225+
"may be correlated to other connections over Tor. For maximum privacy set "
2226+
"-proxyrandomize=1."));
2227+
}
2228+
}
2229+
21952230
if (!node.connman->Start(scheduler, connOptions)) {
21962231
return false;
21972232
}

src/net.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,8 @@ static const std::string DEFAULT_MAX_UPLOAD_TARGET{"0M"};
8383
static const bool DEFAULT_BLOCKSONLY = false;
8484
/** -peertimeout default */
8585
static const int64_t DEFAULT_PEER_CONNECT_TIMEOUT = 60;
86+
/** Default for -privatebroadcast. */
87+
static constexpr bool DEFAULT_PRIVATE_BROADCAST{false};
8688
/** Number of file descriptors required for message capture **/
8789
static const int NUM_FDS_MESSAGE_CAPTURE = 1;
8890
/** Interval for ASMap Health Check **/

test/functional/feature_config_args.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,29 @@ def test_connect_with_seednode(self):
411411
self.restart_node(0, extra_args=[connect_arg, '-dnsseed', '-proxy=localhost:1080'])
412412
self.stop_node(0)
413413

414+
def test_privatebroadcast(self):
415+
self.log.info("Test that an invalid usage of -privatebroadcast throws an init error")
416+
self.stop_node(0)
417+
# -privatebroadcast init error: Tor/I2P not reachable at startup
418+
self.nodes[0].assert_start_raises_init_error(
419+
extra_args=["-privatebroadcast"],
420+
expected_msg=(
421+
"Error: Private broadcast of own transactions requested (-privatebroadcast), "
422+
"but none of Tor or I2P networks is reachable"))
423+
# -privatebroadcast init error: incompatible with -connect
424+
self.nodes[0].assert_start_raises_init_error(
425+
extra_args=["-privatebroadcast", "-connect=127.0.0.1:8333", "-onion=127.0.0.1:9050"],
426+
expected_msg=(
427+
"Error: Private broadcast of own transactions requested (-privatebroadcast), but -connect is also configured. "
428+
"They are incompatible because the private broadcast needs to open new connections to randomly "
429+
"chosen Tor or I2P peers. Consider using -maxconnections=0 -addnode=... instead"))
430+
# Warning case: private broadcast allowed, but -proxyrandomize=0 triggers a privacy warning
431+
self.start_node(0, extra_args=["-privatebroadcast", "-onion=127.0.0.1:9050", "-proxyrandomize=0"])
432+
self.stop_node(0, expected_stderr=(
433+
"Warning: Private broadcast of own transactions requested (-privatebroadcast) and "
434+
"-proxyrandomize is disabled. Tor circuits for private broadcast connections may "
435+
"be correlated to other connections over Tor. For maximum privacy set -proxyrandomize=1."))
436+
414437
def test_ignored_conf(self):
415438
self.log.info('Test error is triggered when the datadir in use contains a bitcoin.conf file that would be ignored '
416439
'because a conflicting -conf file argument is passed.')
@@ -496,6 +519,7 @@ def run_test(self):
496519
self.test_seed_peers()
497520
self.test_networkactive()
498521
self.test_connect_with_seednode()
522+
self.test_privatebroadcast()
499523

500524
self.test_dir_config()
501525
self.test_negated_config()

0 commit comments

Comments
 (0)