test: use dynamic port allocation to avoid test conflicts
What changed, and why it matters
This commit only changes Bitcoin Core's internal test framework. It switches test-only SOCKS5 proxy and P2P mock servers to use dynamic port allocation (port=0) so automated tests are less likely to fail with 'address already in use' errors. There is no change to production Bitcoin node code, no user-facing behavior change, and no security vulnerability being fixed.
No security action required. This is a test reliability improvement. Reviewers can treat it as routine test-framework maintenance.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The patch modifies four files under test/functional/ and test/functional/test_framework/. It updates socks5.py and p2p.py to record the actual OS-assigned port after binding to port=0, and updates feature_proxy.py and feature_anchors.py to request port=0 instead of fixed p2p_port(…) values. This eliminates hard-coded port collisions in functional tests but does not alter consensus, networking, wallet, or any production code path.
Changed components
test/functional/feature_anchors.pytest/functional/feature_proxy.pytest/functional/test_framework/p2p.pytest/functional/test_framework/socks5.pyInspect captured patch +16 / −11
diff --git a/test/functional/feature_anchors.py b/test/functional/feature_anchors.py
index d3d24f46..e82f28f4 100755
--- a/test/functional/feature_anchors.py
+++ b/test/functional/feature_anchors.py
@@ -10,7 +10,7 @@ from test_framework.p2p import P2PInterface, P2P_SERVICES
from test_framework.socks5 import Socks5Configuration, Socks5Server
from test_framework.messages import CAddress, hash256
from test_framework.test_framework import BitcoinTestFramework
-from test_framework.util import check_node_connections, assert_equal, p2p_port
+from test_framework.util import check_node_connections, assert_equal
INBOUND_CONNECTIONS = 5
BLOCK_RELAY_CONNECTIONS = 2
@@ -92,7 +92,9 @@ class AnchorsTest(BitcoinTestFramework):
onion_conf = Socks5Configuration()
onion_conf.auth = True
onion_conf.unauth = True
- onion_conf.addr = ('127.0.0.1', p2p_port(self.num_nodes))
+ # Use port=0 for dynamic allocation to avoid conflicts with concurrent
+ # tests or ports in TIME_WAIT state from previous test runs.
+ onion_conf.addr = ('127.0.0.1', 0)
onion_conf.keep_alive = True
onion_proxy = Socks5Server(onion_conf)
onion_proxy.start()
diff --git a/test/functional/feature_proxy.py b/test/functional/feature_proxy.py
index e95d5ed4..fa4e966e 100755
--- a/test/functional/feature_proxy.py
+++ b/test/functional/feature_proxy.py
@@ -46,10 +46,7 @@ import tempfile
from test_framework.socks5 import Socks5Configuration, Socks5Command, Socks5Server, AddressType
from test_framework.test_framework import BitcoinTestFramework
-from test_framework.util import (
- assert_equal,
- p2p_port,
-)
+from test_framework.util import assert_equal
from test_framework.netutil import test_ipv6_local, test_unix_socket
# Networks returned by RPC getpeerinfo.
@@ -74,22 +71,24 @@ class ProxyTest(BitcoinTestFramework):
def setup_nodes(self):
self.have_ipv6 = test_ipv6_local()
self.have_unix_sockets = test_unix_socket()
- # Create two proxies on different ports
+ # Create two proxies on different ports.
+ # Use port=0 to let the OS assign available ports, avoiding
+ # "address already in use" errors from concurrent tests.
# ... one unauthenticated
self.conf1 = Socks5Configuration()
- self.conf1.addr = ('127.0.0.1', p2p_port(self.num_nodes))
+ self.conf1.addr = ('127.0.0.1', 0)
self.conf1.unauth = True
self.conf1.auth = False
# ... one supporting authenticated and unauthenticated (Tor)
self.conf2 = Socks5Configuration()
- self.conf2.addr = ('127.0.0.1', p2p_port(self.num_nodes + 1))
+ self.conf2.addr = ('127.0.0.1', 0)
self.conf2.unauth = True
self.conf2.auth = True
if self.have_ipv6:
# ... one on IPv6 with similar configuration
self.conf3 = Socks5Configuration()
self.conf3.af = socket.AF_INET6
- self.conf3.addr = ('::1', p2p_port(self.num_nodes + 2))
+ self.conf3.addr = ('::1', 0)
self.conf3.unauth = True
self.conf3.auth = True
else:
diff --git a/test/functional/test_framework/p2p.py b/test/functional/test_framework/p2p.py
index 346a7c65..986eaf1e 100755
--- a/test/functional/test_framework/p2p.py
+++ b/test/functional/test_framework/p2p.py
@@ -787,13 +787,14 @@ class NetworkThread(threading.Thread):
cls.protos[(addr, port)] = None
return response
- if (addr, port) not in cls.listeners:
+ if port == 0 or (addr, port) not in cls.listeners:
# When creating a listener on a given (addr, port) we only need to
# do it once. If we want different behaviors for different
# connections, we can accomplish this by providing different
# `proto` functions
listener = await cls.network_event_loop.create_server(peer_protocol, addr, port)
+ port = listener.sockets[0].getsockname()[1]
logger.debug("Listening server on %s:%d should be started" % (addr, port))
cls.listeners[(addr, port)] = listener
diff --git a/test/functional/test_framework/socks5.py b/test/functional/test_framework/socks5.py
index 3fd4d5c8..711734cf 100644
--- a/test/functional/test_framework/socks5.py
+++ b/test/functional/test_framework/socks5.py
@@ -203,6 +203,9 @@ class Socks5Server():
self.s = socket.socket(conf.af)
self.s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.s.bind(conf.addr)
+ # When port=0, the OS assigns an available port. Update conf.addr
+ # to reflect the actual bound address so callers can use it.
+ self.conf.addr = self.s.getsockname()
self.s.listen(5)
self.running = False
self.thread = None
Why this scored 15/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.