init: make inbound tx relay percentage configurable
What changed, and why it matters
This commit adds a new user-configurable setting called -inboundrelaypercent to Bitcoin Core. It lets node operators choose what percentage of incoming peer connections are allowed to relay transactions, instead of being hard-coded to 50%. The change itself is a feature/configuration tweak, not a fix for an active security bug. It could indirectly affect resource usage or network behavior if set unwisely, but it does not introduce a clear vulnerability.
No immediate security action required. Reviewers may want to confirm that edge cases (e.g., very low -maxconnections combined with high -inboundrelaypercent) behave sensibly and that the clamping prevents negative or >100 values as intended.
Security signals we found
New configuration option affecting P2P connection slot allocation
Input clamped to 0-100 via std::clamp, preventing out-of-range values
No validation, cryptography, or consensus code modified
No privilege escalation, remote code execution, or denial-of-service primitive introduced by the diff
Evidence from the diff
The patch makes the previously hard-coded FULL_RELAY_INBOUND_PCT (50%) into a configurable option, -inboundrelaypercent, with a default of 50% and clamped range 0-100. The value is passed through CConnman::Options to CConnman initialization and used to compute m_max_inbound_full_relay. Functional tests are updated to exercise 0% and 100% values and to replace a large -maxconnections=150 workaround with -inboundrelaypercent=100. No memory-safety, authentication, or consensus changes are present.
Changed components
src/init.cpp argument parsing and node initializationsrc/net.h CConnman::Options and inbound full-relay slot calculationtest/functional/p2p_connection_limits.pytest/functional/p2p_opportunistic_1p1c.pyInspect captured patch +19 / −6
diff --git a/src/init.cpp b/src/init.cpp
index 7f708412..c9363ba9 100644
--- a/src/init.cpp
+++ b/src/init.cpp
@@ -578,11 +578,12 @@ void SetupServerArgs(ArgsManager& argsman, bool can_listen_ipc)
argsman.AddArg("-forcednsseed", strprintf("Always query for peer addresses via DNS lookup (default: %u)", DEFAULT_FORCEDNSSEED), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
argsman.AddArg("-listen", strprintf("Accept connections from outside (default: %u if no -proxy, -connect or -maxconnections=0)", DEFAULT_LISTEN), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
argsman.AddArg("-listenonion", strprintf("Automatically create Tor onion service (default: %d)", DEFAULT_LISTEN_ONION), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
- argsman.AddArg("-maxconnections=<n>", strprintf("Maintain at most <n> automatic connections to peers (default: %u). %u slots of these are reserved for outgoing connections, %u percent of the remaining ones can support transaction relay. "
+ argsman.AddArg("-maxconnections=<n>", strprintf("Maintain at most <n> automatic connections to peers (default: %u). %u slots of these are reserved for outgoing connections. See -inboundrelaypercent for more information about limits applied to transaction relay inbound peers. "
"This limit does not apply to connections manually added via -addnode or the addnode RPC, which have a separate limit of %u. "
"It does not apply to short-lived private broadcast connections either, which have a separate limit of %u.",
- DEFAULT_MAX_PEER_CONNECTIONS, MAX_OUTBOUND_FULL_RELAY_CONNECTIONS + MAX_BLOCK_RELAY_ONLY_CONNECTIONS + MAX_FEELER_CONNECTIONS, static_cast<int>(100 * FULL_RELAY_INBOUND_PCT), MAX_ADDNODE_CONNECTIONS, MAX_PRIVATE_BROADCAST_CONNECTIONS),
+ DEFAULT_MAX_PEER_CONNECTIONS, MAX_OUTBOUND_FULL_RELAY_CONNECTIONS + MAX_BLOCK_RELAY_ONLY_CONNECTIONS + MAX_FEELER_CONNECTIONS, MAX_ADDNODE_CONNECTIONS, MAX_PRIVATE_BROADCAST_CONNECTIONS),
ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
+ argsman.AddArg("-inboundrelaypercent=<n>", strprintf("Permit a maximum percent of inbound connections to relay transactions, to limit memory utilization (0 to 100, default: %u).", DEFAULT_FULL_RELAY_INBOUND_PCT), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
argsman.AddArg("-maxreceivebuffer=<n>", strprintf("Maximum per-connection receive buffer, <n>*1000 bytes (default: %u)", DEFAULT_MAXRECEIVEBUFFER), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
argsman.AddArg("-maxsendbuffer=<n>", strprintf("Maximum per-connection memory usage for the send buffer, <n>*1000 bytes (default: %u)", DEFAULT_MAXSENDBUFFER), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
argsman.AddArg("-maxuploadtarget=<n>", strprintf("Tries to keep outbound traffic under the given target per 24h. Limit does not apply to peers with 'download' permission or blocks created within past week. 0 = no limit (default: %s). Optional suffix units [k|K|m|M|g|G|t|T] (default: M). Lowercase is 1000 base while uppercase is 1024 base", DEFAULT_MAX_UPLOAD_TARGET), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
@@ -2121,6 +2122,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
CConnman::Options connOptions;
connOptions.m_local_services = g_local_services;
connOptions.m_max_automatic_connections = nMaxConnections;
+ connOptions.m_full_relay_inbound_percent = std::clamp<int>(args.GetIntArg("-inboundrelaypercent", DEFAULT_FULL_RELAY_INBOUND_PCT), 0, 100);
connOptions.uiInterface = &uiInterface;
connOptions.m_banman = node.banman.get();
connOptions.m_msgproc = node.peerman.get();
diff --git a/src/net.h b/src/net.h
index fa5768cb..f1814120 100644
--- a/src/net.h
+++ b/src/net.h
@@ -79,8 +79,8 @@ static constexpr size_t MAX_PRIVATE_BROADCAST_CONNECTIONS{64};
static const bool DEFAULT_LISTEN = true;
/** The maximum number of peer connections to maintain. */
static const unsigned int DEFAULT_MAX_PEER_CONNECTIONS{200};
-/** Percentage of inbound connection slots that tx-relaying peers can use */
-static const int FULL_RELAY_INBOUND_PCT{50};
+/** Default percentage of inbound connection slots that tx-relaying peers can use */
+static const int DEFAULT_FULL_RELAY_INBOUND_PCT{50};
/** The default for -maxuploadtarget. 0 = Unlimited */
static const std::string DEFAULT_MAX_UPLOAD_TARGET{"0M"};
/** Default for blocks only*/
@@ -1089,6 +1089,7 @@ public:
{
ServiceFlags m_local_services = NODE_NONE;
int m_max_automatic_connections = DEFAULT_MAX_PEER_CONNECTIONS;
+ int m_full_relay_inbound_percent = DEFAULT_FULL_RELAY_INBOUND_PCT;
CClientUIInterface* uiInterface = nullptr;
NetEventsInterface* m_msgproc = nullptr;
BanMan* m_banman = nullptr;
@@ -1124,7 +1125,7 @@ public:
m_max_outbound_block_relay = std::min(MAX_BLOCK_RELAY_ONLY_CONNECTIONS, m_max_automatic_connections - m_max_outbound_full_relay);
m_max_automatic_outbound = m_max_outbound_full_relay + m_max_outbound_block_relay + m_max_feeler;
m_max_inbound = std::max(0, m_max_automatic_connections - m_max_automatic_outbound);
- m_max_inbound_full_relay = std::max(0, static_cast<int>(FULL_RELAY_INBOUND_PCT / 100.0 * m_max_inbound));
+ m_max_inbound_full_relay = std::max(0, static_cast<int>(connOptions.m_full_relay_inbound_percent / 100.0 * m_max_inbound));
m_use_addrman_outgoing = connOptions.m_use_addrman_outgoing;
m_client_interface = connOptions.uiInterface;
m_banman = connOptions.m_banman;
diff --git a/test/functional/p2p_connection_limits.py b/test/functional/p2p_connection_limits.py
index 20e34cab..338f8f74 100755
--- a/test/functional/p2p_connection_limits.py
+++ b/test/functional/p2p_connection_limits.py
@@ -58,6 +58,16 @@ class P2PConnectionLimits(BitcoinTestFramework):
self.nodes[0].add_p2p_connection(P2PInterface(), send_version=False, wait_for_verack=False, expect_success=False)
self.wait_until(lambda: len(node.getpeerinfo()) == 2)
+ self.log.info('Test different values of inboundrelaypercent')
+ self.restart_node(0, ['-maxconnections=13', '-inboundrelaypercent=0'])
+ with node.assert_debug_log(['failed to find a tx-relaying eviction candidate - connection dropped'], timeout=2):
+ self.nodes[0].add_p2p_connection(P2PInterface(), expect_success=False, wait_for_verack=False)
+
+ self.restart_node(0, ['-maxconnections=13', '-inboundrelaypercent=100'])
+ node.add_p2p_connection(P2PInterface())
+ node.add_p2p_connection(P2PInterface())
+ self.wait_until(lambda: len(node.getpeerinfo()) == 2)
+
if __name__ == '__main__':
P2PConnectionLimits(__file__).main()
diff --git a/test/functional/p2p_opportunistic_1p1c.py b/test/functional/p2p_opportunistic_1p1c.py
index 6841d7c3..156ffd9a 100755
--- a/test/functional/p2p_opportunistic_1p1c.py
+++ b/test/functional/p2p_opportunistic_1p1c.py
@@ -76,7 +76,7 @@ class PackageRelayTest(BitcoinTestFramework):
self.setup_clean_chain = True
self.num_nodes = 1
self.extra_args = [[
- "-maxmempool=5","-maxconnections=150"
+ "-maxmempool=5","-inboundrelaypercent=100"
]]
def create_tx_below_mempoolminfee(self, wallet, utxo_to_spend=None):
Why this scored 20/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.