If this is changed to:
- # Create listener with default P2PInterface
- listener = P2PInterface()
- actual_to_addr, actual_to_port = self._create_auto_outbound_listener(listener, i)
+ if self.decide_auto_outbound_where_to:
+ actual_to_addr, actual_to_port, listener = self.decide_auto_outbound_where_to(i)
+ else:
+ # Create listener with default P2PInterface
+ listener = P2PInterface()
+ actual_to_addr, actual_to_port = self._create_auto_outbound_listener(listener, i)
self.log.debug(f"Instructing the SOCKS5 proxy to redirect connection i={i} to "
- f"{format_addr_port(actual_to_addr, actual_to_port)} (a Python node)")
+ f"{format_addr_port(actual_to_addr, actual_to_port)}")
then it is possible to override from the test:
- the decision where to redirect and
- the listener creation (in case something other than
P2PInterface is needed, e.g. P2PDataStore)
With that change, the private broadcast test can take the following simplification (28 insertions(+), 78 deletions(-)):
<details>
<summary>[patch] make use of this in p2p_private_broadcast.py</summary>
diff --git i/test/functional/p2p_private_broadcast.py w/test/functional/p2p_private_broadcast.py
index 4e97da666a..697d44c299 100755
--- i/test/functional/p2p_private_broadcast.py
+++ w/test/functional/p2p_private_broadcast.py
@@ -162,95 +162,45 @@ ADDRMAN_ADDRESSES = [
"[fc00::20]",
]
class P2PPrivateBroadcast(BitcoinTestFramework):
def set_test_params(self):
- self.disable_autoconnect = False
+ self.auto_outbound_mode = True
self.num_nodes = 2
def setup_nodes(self):
- # Start a SOCKS5 proxy server.
- socks5_server_config = Socks5Configuration()
- # self.nodes[0] listens on p2p_port(0),
- # self.nodes[1] listens on p2p_port(1),
- # thus we tell the SOCKS5 server to listen on p2p_port(self.num_nodes) (self.num_nodes is 2)
- socks5_server_config.addr = ("127.0.0.1", p2p_port(self.num_nodes))
- socks5_server_config.unauth = True
- socks5_server_config.auth = True
-
- self.socks5_server = Socks5Server(socks5_server_config)
- self.socks5_server.start()
-
- # Tor ports are the highest among p2p/rpc/tor, so this should be the first available port.
- ports_base = tor_port(MAX_NODES) + 1
-
- self.destinations = []
-
- self.destinations_lock = threading.Lock()
-
- def destinations_factory(requested_to_addr, requested_to_port):
- with self.destinations_lock:
- i = len(self.destinations)
- actual_to_addr = ""
- actual_to_port = 0
- listener = None
- if i == NUM_INITIAL_CONNECTIONS:
- # Instruct the SOCKS5 server to redirect the first private
- # broadcast connection from nodes[0] to nodes[1]
- actual_to_addr = "127.0.0.1" # nodes[1] listen address
- actual_to_port = tor_port(1) # nodes[1] listen port for Tor
- self.log.debug(f"Instructing the SOCKS5 proxy to redirect connection i={i} to "
- f"{format_addr_port(actual_to_addr, actual_to_port)} (nodes[1])")
- else:
- # Create a Python P2P listening node and instruct the SOCKS5 proxy to
- # redirect the connection to it. The first outbound connection is used
- # later to serve GETDATA, thus make it P2PDataStore().
- listener = P2PDataStore() if i == 0 else P2PInterface()
- listener.peer_connect_helper(dstaddr="0.0.0.0", dstport=0, net=self.chain, timeout_factor=self.options.timeout_factor)
- listener.peer_connect_send_version(services=P2P_SERVICES)
-
- def on_listen_done(addr, port):
- nonlocal actual_to_addr
- nonlocal actual_to_port
- actual_to_addr = addr
- actual_to_port = port
-
- self.network_thread.listen(
- addr="127.0.0.1",
- port=ports_base + i,
- p2p=listener,
- callback=on_listen_done)
- # Wait until the callback has been called.
- self.wait_until(lambda: actual_to_port != 0)
- self.log.debug(f"Instructing the SOCKS5 proxy to redirect connection i={i} to "
- f"{format_addr_port(actual_to_addr, actual_to_port)} (a Python node)")
-
- self.destinations.append({
- "requested_to": format_addr_port(requested_to_addr, requested_to_port),
- "node": listener,
- })
- assert_equal(len(self.destinations), i + 1)
-
- return {
- "actual_to_addr": actual_to_addr,
- "actual_to_port": actual_to_port,
- }
-
- self.socks5_server.conf.destinations_factory = destinations_factory
+
+ def decide_auto_outbound_where_to(connection_index):
+ actual_to_addr = ""
+ actual_to_port = 0
+ listener = None
+ if connection_index == NUM_INITIAL_CONNECTIONS:
+ # Instruct the SOCKS5 server to redirect the first private
+ # broadcast connection from nodes[0] to nodes[1]
+ actual_to_addr = "127.0.0.1" # nodes[1] listen address
+ actual_to_port = tor_port(1) # nodes[1] listen port for Tor
+ else:
+ # Create a Python P2P listening node and instruct the SOCKS5 proxy to
+ # redirect the connection to it. The first outbound connection is used
+ # later to serve GETDATA, thus make it P2PDataStore().
+ listener = P2PDataStore() if connection_index == 0 else P2PInterface()
+ actual_to_addr, actual_to_port = self._create_auto_outbound_listener(listener, connection_index)
+ return actual_to_addr, actual_to_port, listener
+
+ self.decide_auto_outbound_where_to = decide_auto_outbound_where_to
self.extra_args = [
[
# Needed to be able to add CJDNS addresses to addrman (otherwise they are unroutable).
"-cjdnsreachable",
# Connecting, sending garbage, being disconnected messes up with this test's
# check_broadcasts() which waits for a particular Python node to receive a connection.
"-v2transport=0",
"-test=addrman",
"-privatebroadcast",
- f"-proxy={socks5_server_config.addr[0]}:{socks5_server_config.addr[1]}",
# To increase coverage, make it think that the I2P network is reachable so that it
# selects such addresses as well. Pick a proxy address where nobody is listening
# and connection attempts fail quickly.
"-i2psam=127.0.0.1:1",
],
[
@@ -268,14 +218,14 @@ class P2PPrivateBroadcast(BitcoinTestFramework):
i = skip_destinations - 1
while broadcasts_done < broadcasts_to_expect:
i += 1
self.log.debug(f"{label}: waiting for outbound connection i={i}")
# At this point the connection may not yet have been established (A),
# may be active (B), or may have already been closed (C).
- self.wait_until(lambda: len(self.destinations) > i)
- dest = self.destinations[i]
+ self.wait_until(lambda: len(self.auto_outbound_destinations) > i)
+ dest = self.auto_outbound_destinations[i]
peer = dest["node"]
peer.wait_until(lambda: peer.message_count["version"] == 1, check_connected=False)
# Now it is either (B) or (C).
if peer.last_message["version"].nServices != 0:
self.log.debug(f"{label}: outbound connection i={i} to {dest['requested_to']} not a private broadcast, ignoring it (maybe feeler or extra block only)")
continue
@@ -313,13 +263,13 @@ class P2PPrivateBroadcast(BitcoinTestFramework):
# Fill tx_originator's addrman.
for addr in ADDRMAN_ADDRESSES:
res = tx_originator.addpeeraddress(address=addr, port=8333, tried=False)
if not res["success"]:
self.log.debug(f"Could not add {addr} to tx_originator's addrman (collision?)")
- self.wait_until(lambda: len(self.destinations) == NUM_INITIAL_CONNECTIONS)
+ self.wait_until(lambda: len(self.auto_outbound_destinations) == NUM_INITIAL_CONNECTIONS)
# The next opened connection by tx_originator should be "private broadcast"
# for sending the transaction. The SOCKS5 proxy should redirect it to tx_receiver.
txs = wallet.create_self_transfer_chain(chain_length=3)
self.log.info(f"Created txid={txs[0]['txid']}: for basic test")
@@ -353,31 +303,31 @@ class P2PPrivateBroadcast(BitcoinTestFramework):
assert_equal(len(tx_originator.getrawmempool()), 0)
wtxid_int = int(txs[0]["wtxid"], 16)
inv = CInv(MSG_WTX, wtxid_int)
self.log.info("Sending INV and waiting for GETDATA from node")
- tx_returner = self.destinations[0]["node"] # Will return the transaction back to the originator.
+ tx_returner = self.auto_outbound_destinations[0]["node"] # Will return the transaction back to the originator.
tx_returner.tx_store[wtxid_int] = txs[0]["tx"]
assert "getdata" not in tx_returner.last_message
received_back_msg = f"Received our privately broadcast transaction (txid={txs[0]['txid']}) from the network"
with tx_originator.assert_debug_log(expected_msgs=[received_back_msg]):
tx_returner.send_without_ping(msg_inv([inv]))
tx_returner.wait_until(lambda: "getdata" in tx_returner.last_message)
self.wait_until(lambda: len(tx_originator.getrawmempool()) > 0)
self.log.info("Waiting for normal broadcast to another peer")
- self.destinations[1]["node"].wait_for_inv([inv])
+ self.auto_outbound_destinations[1]["node"].wait_for_inv([inv])
self.log.info("Sending a transaction that is already in the mempool")
- skip_destinations = len(self.destinations)
+ skip_destinations = len(self.auto_outbound_destinations)
tx_originator.sendrawtransaction(hexstring=txs[0]["hex"], maxfeerate=0)
self.check_broadcasts("Broadcast of mempool transaction", txs[0], NUM_PRIVATE_BROADCAST_PER_TX, skip_destinations)
self.log.info("Sending a transaction with a dependency in the mempool")
- skip_destinations = len(self.destinations)
+ skip_destinations = len(self.auto_outbound_destinations)
tx_originator.sendrawtransaction(hexstring=txs[1]["hex"], maxfeerate=0.1)
self.check_broadcasts("Dependency in mempool", txs[1], NUM_PRIVATE_BROADCAST_PER_TX, skip_destinations)
self.log.info("Sending a transaction with a dependency not in the mempool (should be rejected)")
assert_equal(len(tx_originator.getrawmempool()), 1)
assert_raises_rpc_error(-25, "bad-txns-inputs-missingorspent",
@@ -388,13 +338,13 @@ class P2PPrivateBroadcast(BitcoinTestFramework):
# Since txs[1] has not been received back by tx_originator,
# it should be re-broadcast after a while. Advance tx_originator's clock
# to trigger a re-broadcast. Should be more than the maximum returned by
# NextTxBroadcast() in net_processing.cpp.
self.log.info("Checking that rebroadcast works")
delta = 20 * 60 # 20min
- skip_destinations = len(self.destinations)
+ skip_destinations = len(self.auto_outbound_destinations)
rebroadcast_msg = f"Reattempting broadcast of stale txid={txs[1]['txid']}"
with tx_originator.busy_wait_for_debug_log(expected_msgs=[rebroadcast_msg.encode()]):
tx_originator.setmocktime(int(time.time()) + delta)
tx_originator.mockscheduler(delta)
self.check_broadcasts("Rebroadcast", txs[1], 1, skip_destinations)
tx_originator.setmocktime(0) # Let the clock tick again (it will go backwards due to this).
</details>