Skip to content

Commit

Permalink
Merge #29736: test: Extends wait_for_getheaders so a specific block h…
Browse files Browse the repository at this point in the history
…ash can be checked

c4f857c test: Extends wait_for_getheaders so a specific block hash can be checked (Sergi Delgado Segura)

Pull request description:

  Fixes #18614

  Previously, `wait_for_getheaders` would check whether a node had received **any** getheaders message. This implied that, if a test needed to check for a specific block hash within a headers message, it had to make sure that it was checking the desired message. This normally involved having to manually clear `last_message`. This method, apart from being too verbose, was error-prone, given an undesired `getheaders` would make tests pass.

  This adds the ability to check for a specific block_hash within the last `getheaders` message.

ACKs for top commit:
  achow101:
    ACK c4f857c
  BrandonOdiwuor:
    crACK c4f857c
  cbergqvist:
    ACK c4f857c
  stratospher:
    tested ACK c4f857c. went through all getheaders messages sent in the tests and checked that it's the one we want.

Tree-SHA512: afc9a31673344dfaaefcf692ec2ab65958c3d4c005f5f3af525e9960f0622d8246d5311e59aba06cfd5c9e0ef9eb90a7fc8e210f030bfbe67b897c061efdeed1
  • Loading branch information
achow101 committed Apr 25, 2024
2 parents 16a6174 + c4f857c commit 3c88eac
Show file tree
Hide file tree
Showing 6 changed files with 28 additions and 31 deletions.
2 changes: 1 addition & 1 deletion test/functional/mining_basic.py
Expand Up @@ -308,7 +308,7 @@ def chain_tip(b_hash, *, status='headers-only', branchlen=1):

# Should ask for the block from a p2p node, if they announce the header as well:
peer = node.add_p2p_connection(P2PDataStore())
peer.wait_for_getheaders(timeout=5) # Drop the first getheaders
peer.wait_for_getheaders(timeout=5, block_hash=block.hashPrevBlock)
peer.send_blocks_and_test(blocks=[block], node=node)
# Must be active now:
assert chain_tip(block.hash, status='active', branchlen=0) in node.getchaintips()
Expand Down
2 changes: 1 addition & 1 deletion test/functional/p2p_compactblocks.py
Expand Up @@ -387,7 +387,7 @@ def test_compactblock_requests(self, test_node):

if announce == "inv":
test_node.send_message(msg_inv([CInv(MSG_BLOCK, block.sha256)]))
test_node.wait_until(lambda: "getheaders" in test_node.last_message, timeout=30)
test_node.wait_for_getheaders(timeout=30)
test_node.send_header_for_blocks([block])
else:
test_node.send_header_for_blocks([block])
Expand Down
15 changes: 5 additions & 10 deletions test/functional/p2p_initial_headers_sync.py
Expand Up @@ -38,9 +38,10 @@ def announce_random_block(self, peers):
def run_test(self):
self.log.info("Adding a peer to node0")
peer1 = self.nodes[0].add_p2p_connection(P2PInterface())
best_block_hash = int(self.nodes[0].getbestblockhash(), 16)

# Wait for peer1 to receive a getheaders
peer1.wait_for_getheaders()
peer1.wait_for_getheaders(block_hash=best_block_hash)
# An empty reply will clear the outstanding getheaders request,
# allowing additional getheaders requests to be sent to this peer in
# the future.
Expand All @@ -60,17 +61,12 @@ def run_test(self):
assert "getheaders" not in peer2.last_message
assert "getheaders" not in peer3.last_message

with p2p_lock:
peer1.last_message.pop("getheaders", None)

self.log.info("Have all peers announce a new block")
self.announce_random_block(all_peers)

self.log.info("Check that peer1 receives a getheaders in response")
peer1.wait_for_getheaders()
peer1.wait_for_getheaders(block_hash=best_block_hash)
peer1.send_message(msg_headers()) # Send empty response, see above
with p2p_lock:
peer1.last_message.pop("getheaders", None)

self.log.info("Check that exactly 1 of {peer2, peer3} received a getheaders in response")
count = 0
Expand All @@ -80,7 +76,6 @@ def run_test(self):
if "getheaders" in p.last_message:
count += 1
peer_receiving_getheaders = p
p.last_message.pop("getheaders", None)
p.send_message(msg_headers()) # Send empty response, see above

assert_equal(count, 1)
Expand All @@ -89,14 +84,14 @@ def run_test(self):
self.announce_random_block(all_peers)

self.log.info("Check that peer1 receives a getheaders in response")
peer1.wait_for_getheaders()
peer1.wait_for_getheaders(block_hash=best_block_hash)

self.log.info("Check that the remaining peer received a getheaders as well")
expected_peer = peer2
if peer2 == peer_receiving_getheaders:
expected_peer = peer3

expected_peer.wait_for_getheaders()
expected_peer.wait_for_getheaders(block_hash=best_block_hash)

self.log.info("Success!")

Expand Down
3 changes: 1 addition & 2 deletions test/functional/p2p_segwit.py
Expand Up @@ -191,14 +191,13 @@ def announce_tx_and_wait_for_getdata(self, tx, success=True, use_wtxid=False):
def announce_block_and_wait_for_getdata(self, block, use_header, timeout=60):
with p2p_lock:
self.last_message.pop("getdata", None)
self.last_message.pop("getheaders", None)
msg = msg_headers()
msg.headers = [CBlockHeader(block)]
if use_header:
self.send_message(msg)
else:
self.send_message(msg_inv(inv=[CInv(MSG_BLOCK, block.sha256)]))
self.wait_for_getheaders(timeout=timeout)
self.wait_for_getheaders(block_hash=block.hashPrevBlock, timeout=timeout)
self.send_message(msg)
self.wait_for_getdata([block.sha256], timeout=timeout)

Expand Down
21 changes: 11 additions & 10 deletions test/functional/p2p_sendheaders.py
Expand Up @@ -311,6 +311,7 @@ def test_nonnull_locators(self, test_node, inv_node):

# Now that we've synced headers, headers announcements should work
tip = self.mine_blocks(1)
expected_hash = tip
inv_node.check_last_inv_announcement(inv=[tip])
test_node.check_last_headers_announcement(headers=[tip])

Expand All @@ -334,7 +335,10 @@ def test_nonnull_locators(self, test_node, inv_node):
if j == 0:
# Announce via inv
test_node.send_block_inv(tip)
test_node.wait_for_getheaders()
if i == 0:
test_node.wait_for_getheaders(block_hash=expected_hash)
else:
assert "getheaders" not in test_node.last_message
# Should have received a getheaders now
test_node.send_header_for_blocks(blocks)
# Test that duplicate inv's won't result in duplicate
Expand Down Expand Up @@ -521,6 +525,7 @@ def test_nonnull_locators(self, test_node, inv_node):
self.log.info("Part 5: Testing handling of unconnecting headers")
# First we test that receipt of an unconnecting header doesn't prevent
# chain sync.
expected_hash = tip
for i in range(10):
self.log.debug("Part 5.{}: starting...".format(i))
test_node.last_message.pop("getdata", None)
Expand All @@ -533,15 +538,14 @@ def test_nonnull_locators(self, test_node, inv_node):
block_time += 1
height += 1
# Send the header of the second block -> this won't connect.
with p2p_lock:
test_node.last_message.pop("getheaders", None)
test_node.send_header_for_blocks([blocks[1]])
test_node.wait_for_getheaders()
test_node.wait_for_getheaders(block_hash=expected_hash)
test_node.send_header_for_blocks(blocks)
test_node.wait_for_getdata([x.sha256 for x in blocks])
[test_node.send_message(msg_block(x)) for x in blocks]
test_node.sync_with_ping()
assert_equal(int(self.nodes[0].getbestblockhash(), 16), blocks[1].sha256)
expected_hash = blocks[1].sha256

blocks = []
# Now we test that if we repeatedly don't send connecting headers, we
Expand All @@ -556,13 +560,12 @@ def test_nonnull_locators(self, test_node, inv_node):

for i in range(1, MAX_NUM_UNCONNECTING_HEADERS_MSGS):
# Send a header that doesn't connect, check that we get a getheaders.
with p2p_lock:
test_node.last_message.pop("getheaders", None)
test_node.send_header_for_blocks([blocks[i]])
test_node.wait_for_getheaders()
test_node.wait_for_getheaders(block_hash=expected_hash)

# Next header will connect, should re-set our count:
test_node.send_header_for_blocks([blocks[0]])
expected_hash = blocks[0].sha256

# Remove the first two entries (blocks[1] would connect):
blocks = blocks[2:]
Expand All @@ -571,10 +574,8 @@ def test_nonnull_locators(self, test_node, inv_node):
# before we get disconnected. Should be 5*MAX_NUM_UNCONNECTING_HEADERS_MSGS
for i in range(5 * MAX_NUM_UNCONNECTING_HEADERS_MSGS - 1):
# Send a header that doesn't connect, check that we get a getheaders.
with p2p_lock:
test_node.last_message.pop("getheaders", None)
test_node.send_header_for_blocks([blocks[i % len(blocks)]])
test_node.wait_for_getheaders()
test_node.wait_for_getheaders(block_hash=expected_hash)

# Eventually this stops working.
test_node.send_header_for_blocks([blocks[-1]])
Expand Down
16 changes: 9 additions & 7 deletions test/functional/test_framework/p2p.py
Expand Up @@ -644,15 +644,17 @@ def test_function():

self.wait_until(test_function, timeout=timeout)

def wait_for_getheaders(self, *, timeout=60):
"""Waits for a getheaders message.
def wait_for_getheaders(self, block_hash=None, *, timeout=60):
"""Waits for a getheaders message containing a specific block hash.
Receiving any getheaders message will satisfy the predicate. the last_message["getheaders"]
value must be explicitly cleared before calling this method, or this will return
immediately with success. TODO: change this method to take a hash value and only
return true if the correct block header has been requested."""
If no block hash is provided, checks whether any getheaders message has been received by the node."""
def test_function():
return self.last_message.get("getheaders")
last_getheaders = self.last_message.pop("getheaders", None)
if block_hash is None:
return last_getheaders
if last_getheaders is None:
return False
return block_hash == last_getheaders.locator.vHave[0]

self.wait_until(test_function, timeout=timeout)

Expand Down

0 comments on commit 3c88eac

Please sign in to comment.