Skip to content

Commit 03042fb

Browse files
committed
Merge bitcoin/bitcoin#29006: test: fix v2 transport intermittent test failure (#29002)
00e0658 test: fix v2 transport intermittent test failure (#29002) (Sebastian Falbesoner) Pull request description: This PR improves the following fragile construct for detection of a new connection to the node under test in `p2p_v2_transport.py`: https://github.com/bitcoin/bitcoin/blob/6d5790956f45e3de5c6c4ee6fda21878b0d1287b/test/functional/p2p_v2_transport.py#L154-L156 Only relying on the number of peers for that suffers from race conditions, as unrelated previous peers could disconnect at anytime in-between. In the test run in #29002, the following happens: - `getpeerinfo()` is called the first time -> assigned to `num_peers` - **previous peer disconnects**, the node's peer count is now `num_peers - 1` (in most test runs, this happens before the first getpeerinfo call) - new peer connects, the node's peer count is now `num_peers` - the condition that the node's peer count is `num_peers + 1` is never true, test fails Use the more robust approach of watching for an increased highest peer id instead (again using the `getpeerinfo` RPC call), with a newly introduced context manager method `TestNode.wait_for_new_peer()`. Note that for the opposite case of a disconnect, no new method is introduced; this is currently used only once in the test and is also simpler. Still happy to take suggestions for alternative solutions. Fixes #29002. ACKs for top commit: kevkevinpal: Concept ACK [00e0658](bitcoin/bitcoin@00e0658) maflcko: Ok, lgtm ACK 00e0658 stratospher: ACK 00e0658. Tree-SHA512: 0118b87f54ea5e6e080ff44f29d6af6674c757a588534b3add040da435f4359e71bf85bc0a5eb7170f99cc9956e1a03c35cce653d642d31eed41bbed1f94f44f
2 parents 1f352cf + 00e0658 commit 03042fb

File tree

2 files changed

+26
-8
lines changed

2 files changed

+26
-8
lines changed

test/functional/p2p_v2_transport.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -133,9 +133,8 @@ def run_test(self):
133133
V1_PREFIX = MAGIC_BYTES["regtest"] + b"version\x00\x00\x00\x00\x00"
134134
assert_equal(len(V1_PREFIX), 16)
135135
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
136-
num_peers = len(self.nodes[0].getpeerinfo())
137-
s.connect(("127.0.0.1", p2p_port(0)))
138-
self.wait_until(lambda: len(self.nodes[0].getpeerinfo()) == num_peers + 1)
136+
with self.nodes[0].wait_for_new_peer():
137+
s.connect(("127.0.0.1", p2p_port(0)))
139138
s.sendall(V1_PREFIX[:-1])
140139
assert_equal(self.nodes[0].getpeerinfo()[-1]["transport_protocol_type"], "detecting")
141140
s.sendall(bytes([V1_PREFIX[-1]])) # send out last prefix byte
@@ -144,22 +143,23 @@ def run_test(self):
144143
# Check wrong network prefix detection (hits if the next 12 bytes correspond to a v1 version message)
145144
wrong_network_magic_prefix = MAGIC_BYTES["signet"] + V1_PREFIX[4:]
146145
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
147-
s.connect(("127.0.0.1", p2p_port(0)))
146+
with self.nodes[0].wait_for_new_peer():
147+
s.connect(("127.0.0.1", p2p_port(0)))
148148
with self.nodes[0].assert_debug_log(["V2 transport error: V1 peer with wrong MessageStart"]):
149149
s.sendall(wrong_network_magic_prefix + b"somepayload")
150150

151151
# Check detection of missing garbage terminator (hits after fixed amount of data if terminator never matches garbage)
152152
MAX_KEY_GARB_AND_GARBTERM_LEN = 64 + 4095 + 16
153153
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
154-
num_peers = len(self.nodes[0].getpeerinfo())
155-
s.connect(("127.0.0.1", p2p_port(0)))
156-
self.wait_until(lambda: len(self.nodes[0].getpeerinfo()) == num_peers + 1)
154+
with self.nodes[0].wait_for_new_peer():
155+
s.connect(("127.0.0.1", p2p_port(0)))
157156
s.sendall(b'\x00' * (MAX_KEY_GARB_AND_GARBTERM_LEN - 1))
158157
self.wait_until(lambda: self.nodes[0].getpeerinfo()[-1]["bytesrecv"] == MAX_KEY_GARB_AND_GARBTERM_LEN - 1)
159158
with self.nodes[0].assert_debug_log(["V2 transport error: missing garbage terminator"]):
159+
peer_id = self.nodes[0].getpeerinfo()[-1]["id"]
160160
s.sendall(b'\x00') # send out last byte
161161
# should disconnect immediately
162-
self.wait_until(lambda: len(self.nodes[0].getpeerinfo()) == num_peers)
162+
self.wait_until(lambda: not peer_id in [p["id"] for p in self.nodes[0].getpeerinfo()])
163163

164164

165165
if __name__ == '__main__':

test/functional/test_framework/test_node.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -519,6 +519,24 @@ def wait_for_debug_log(self, expected_msgs, timeout=60):
519519
'Expected messages "{}" does not partially match log:\n\n{}\n\n'.format(
520520
str(expected_msgs), print_log))
521521

522+
@contextlib.contextmanager
523+
def wait_for_new_peer(self, timeout=5):
524+
"""
525+
Wait until the node is connected to at least one new peer. We detect this
526+
by watching for an increased highest peer id, using the `getpeerinfo` RPC call.
527+
Note that the simpler approach of only accounting for the number of peers
528+
suffers from race conditions, as disconnects from unrelated previous peers
529+
could happen anytime in-between.
530+
"""
531+
def get_highest_peer_id():
532+
peer_info = self.getpeerinfo()
533+
return peer_info[-1]["id"] if peer_info else -1
534+
535+
initial_peer_id = get_highest_peer_id()
536+
yield
537+
wait_until_helper_internal(lambda: get_highest_peer_id() > initial_peer_id,
538+
timeout=timeout, timeout_factor=self.timeout_factor)
539+
522540
@contextlib.contextmanager
523541
def profile_with_perf(self, profile_name: str):
524542
"""

0 commit comments

Comments
 (0)