Changeset View
Changeset View
Standalone View
Standalone View
test/functional/p2p_sendheaders.py
Show First 20 Lines • Show All 135 Lines • ▼ Show 20 Lines | def send_header_for_blocks(self, new_blocks): | ||||
self.send_message(headers_message) | self.send_message(headers_message) | ||||
def send_getblocks(self, locator): | def send_getblocks(self, locator): | ||||
getblocks_message = msg_getblocks() | getblocks_message = msg_getblocks() | ||||
getblocks_message.locator.vHave = locator | getblocks_message.locator.vHave = locator | ||||
self.send_message(getblocks_message) | self.send_message(getblocks_message) | ||||
def wait_for_block_announcement(self, block_hash, timeout=60): | def wait_for_block_announcement(self, block_hash, timeout=60): | ||||
def test_function(): return self.last_blockhash_announced == block_hash | def test_function(): | ||||
return self.last_blockhash_announced == block_hash | |||||
self.wait_until(test_function, timeout=timeout) | self.wait_until(test_function, timeout=timeout) | ||||
def on_inv(self, message): | def on_inv(self, message): | ||||
self.block_announced = True | self.block_announced = True | ||||
self.last_blockhash_announced = message.inv[-1].hash | self.last_blockhash_announced = message.inv[-1].hash | ||||
def on_headers(self, message): | def on_headers(self, message): | ||||
if len(message.headers): | if len(message.headers): | ||||
Show All 9 Lines | def clear_block_announcements(self): | ||||
with p2p_lock: | with p2p_lock: | ||||
self.block_announced = False | self.block_announced = False | ||||
self.last_message.pop("inv", None) | self.last_message.pop("inv", None) | ||||
self.last_message.pop("headers", None) | self.last_message.pop("headers", None) | ||||
self.recent_headers_announced = [] | self.recent_headers_announced = [] | ||||
def check_last_headers_announcement(self, headers): | def check_last_headers_announcement(self, headers): | ||||
"""Test whether the last headers announcements received are right. | """Test whether the last headers announcements received are right. | ||||
Headers may be announced across more than one message.""" | Headers may be announced across more than one message.""" | ||||
def test_function(): return (len(self.recent_headers_announced) >= len(headers)) | def test_function(): | ||||
return len(self.recent_headers_announced) >= len(headers) | |||||
self.wait_until(test_function) | self.wait_until(test_function) | ||||
with p2p_lock: | with p2p_lock: | ||||
assert_equal(self.recent_headers_announced, headers) | assert_equal(self.recent_headers_announced, headers) | ||||
self.block_announced = False | self.block_announced = False | ||||
self.last_message.pop("headers", None) | self.last_message.pop("headers", None) | ||||
self.recent_headers_announced = [] | self.recent_headers_announced = [] | ||||
def check_last_inv_announcement(self, inv): | def check_last_inv_announcement(self, inv): | ||||
"""Test whether the last announcement received had the right inv. | """Test whether the last announcement received had the right inv. | ||||
inv should be a list of block hashes.""" | inv should be a list of block hashes.""" | ||||
def test_function(): return self.block_announced | def test_function(): | ||||
return self.block_announced | |||||
self.wait_until(test_function) | self.wait_until(test_function) | ||||
with p2p_lock: | with p2p_lock: | ||||
compare_inv = [] | compare_inv = [] | ||||
if "inv" in self.last_message: | if "inv" in self.last_message: | ||||
compare_inv = [x.hash for x in self.last_message["inv"].inv] | compare_inv = [x.hash for x in self.last_message["inv"].inv] | ||||
assert_equal(compare_inv, inv) | assert_equal(compare_inv, inv) | ||||
self.block_announced = False | self.block_announced = False | ||||
self.last_message.pop("inv", None) | self.last_message.pop("inv", None) | ||||
class SendHeadersTest(BitcoinTestFramework): | class SendHeadersTest(BitcoinTestFramework): | ||||
def set_test_params(self): | def set_test_params(self): | ||||
self.setup_clean_chain = True | self.setup_clean_chain = True | ||||
self.num_nodes = 2 | self.num_nodes = 2 | ||||
self.extra_args = [["-noparkdeepreorg"], ["-noparkdeepreorg"]] | self.extra_args = [["-noparkdeepreorg"], ["-noparkdeepreorg"]] | ||||
def mine_blocks(self, count): | def mine_blocks(self, count): | ||||
"""Mine count blocks and return the new tip.""" | """Mine count blocks and return the new tip.""" | ||||
# Clear out block announcements from each p2p listener | # Clear out block announcements from each p2p listener | ||||
[x.clear_block_announcements() for x in self.nodes[0].p2ps] | [x.clear_block_announcements() for x in self.nodes[0].p2ps] | ||||
self.generatetoaddress(self.nodes[0], | self.generatetoaddress( | ||||
count, self.nodes[0].get_deterministic_priv_key().address) | self.nodes[0], count, self.nodes[0].get_deterministic_priv_key().address | ||||
) | |||||
return int(self.nodes[0].getbestblockhash(), 16) | return int(self.nodes[0].getbestblockhash(), 16) | ||||
def mine_reorg(self, length): | def mine_reorg(self, length): | ||||
"""Mine a reorg that invalidates length blocks (replacing them with # length+1 blocks). | """Mine a reorg that invalidates length blocks (replacing them with # length+1 blocks). | ||||
Note: we clear the state of our p2p connections after the | Note: we clear the state of our p2p connections after the | ||||
to-be-reorged-out blocks are mined, so that we don't break later tests. | to-be-reorged-out blocks are mined, so that we don't break later tests. | ||||
return the list of block hashes newly mined.""" | return the list of block hashes newly mined.""" | ||||
# make sure all invalidated blocks are node0's | # make sure all invalidated blocks are node0's | ||||
self.generatetoaddress(self.nodes[0], | self.generatetoaddress( | ||||
length, self.nodes[0].get_deterministic_priv_key().address) | self.nodes[0], length, self.nodes[0].get_deterministic_priv_key().address | ||||
) | |||||
for x in self.nodes[0].p2ps: | for x in self.nodes[0].p2ps: | ||||
x.wait_for_block_announcement( | x.wait_for_block_announcement(int(self.nodes[0].getbestblockhash(), 16)) | ||||
int(self.nodes[0].getbestblockhash(), 16)) | |||||
x.clear_block_announcements() | x.clear_block_announcements() | ||||
tip_height = self.nodes[1].getblockcount() | tip_height = self.nodes[1].getblockcount() | ||||
hash_to_invalidate = self.nodes[1].getblockhash( | hash_to_invalidate = self.nodes[1].getblockhash(tip_height - (length - 1)) | ||||
tip_height - (length - 1)) | |||||
self.nodes[1].invalidateblock(hash_to_invalidate) | self.nodes[1].invalidateblock(hash_to_invalidate) | ||||
# Must be longer than the orig chain | # Must be longer than the orig chain | ||||
all_hashes = self.generatetoaddress(self.nodes[1], | all_hashes = self.generatetoaddress( | ||||
length + 1, self.nodes[1].get_deterministic_priv_key().address) | self.nodes[1], | ||||
length + 1, | |||||
self.nodes[1].get_deterministic_priv_key().address, | |||||
) | |||||
return [int(x, 16) for x in all_hashes] | return [int(x, 16) for x in all_hashes] | ||||
def run_test(self): | def run_test(self): | ||||
# Setup the p2p connections | # Setup the p2p connections | ||||
inv_node = self.nodes[0].add_p2p_connection(BaseNode()) | inv_node = self.nodes[0].add_p2p_connection(BaseNode()) | ||||
# Make sure NODE_NETWORK is not set for test_node, so no block download | # Make sure NODE_NETWORK is not set for test_node, so no block download | ||||
# will occur outside of direct fetching | # will occur outside of direct fetching | ||||
test_node = self.nodes[0].add_p2p_connection(BaseNode(), services=0) | test_node = self.nodes[0].add_p2p_connection(BaseNode(), services=0) | ||||
self.test_null_locators(test_node, inv_node) | self.test_null_locators(test_node, inv_node) | ||||
self.test_nonnull_locators(test_node, inv_node) | self.test_nonnull_locators(test_node, inv_node) | ||||
def test_null_locators(self, test_node, inv_node): | def test_null_locators(self, test_node, inv_node): | ||||
tip = self.nodes[0].getblockheader(self.generatetoaddress(self.nodes[0], | tip = self.nodes[0].getblockheader( | ||||
1, self.nodes[0].get_deterministic_priv_key().address)[0]) | self.generatetoaddress( | ||||
self.nodes[0], 1, self.nodes[0].get_deterministic_priv_key().address | |||||
)[0] | |||||
) | |||||
tip_hash = int(tip["hash"], 16) | tip_hash = int(tip["hash"], 16) | ||||
inv_node.check_last_inv_announcement(inv=[tip_hash]) | inv_node.check_last_inv_announcement(inv=[tip_hash]) | ||||
test_node.check_last_inv_announcement(inv=[tip_hash]) | test_node.check_last_inv_announcement(inv=[tip_hash]) | ||||
self.log.info( | self.log.info( | ||||
"Verify getheaders with null locator and valid hashstop returns headers.") | "Verify getheaders with null locator and valid hashstop returns headers." | ||||
) | |||||
test_node.clear_block_announcements() | test_node.clear_block_announcements() | ||||
test_node.send_get_headers(locator=[], hashstop=tip_hash) | test_node.send_get_headers(locator=[], hashstop=tip_hash) | ||||
test_node.check_last_headers_announcement(headers=[tip_hash]) | test_node.check_last_headers_announcement(headers=[tip_hash]) | ||||
self.log.info( | self.log.info( | ||||
"Verify getheaders with null locator and invalid hashstop does not return headers.") | "Verify getheaders with null locator and invalid hashstop does not return" | ||||
block = create_block(int(tip["hash"], 16), create_coinbase( | " headers." | ||||
tip["height"] + 1), tip["mediantime"] + 1) | ) | ||||
block = create_block( | |||||
int(tip["hash"], 16), | |||||
create_coinbase(tip["height"] + 1), | |||||
tip["mediantime"] + 1, | |||||
) | |||||
block.solve() | block.solve() | ||||
test_node.send_header_for_blocks([block]) | test_node.send_header_for_blocks([block]) | ||||
test_node.clear_block_announcements() | test_node.clear_block_announcements() | ||||
test_node.send_get_headers(locator=[], hashstop=int(block.hash, 16)) | test_node.send_get_headers(locator=[], hashstop=int(block.hash, 16)) | ||||
test_node.sync_with_ping() | test_node.sync_with_ping() | ||||
assert_equal(test_node.block_announced, False) | assert_equal(test_node.block_announced, False) | ||||
inv_node.clear_block_announcements() | inv_node.clear_block_announcements() | ||||
test_node.send_message(msg_block(block)) | test_node.send_message(msg_block(block)) | ||||
inv_node.check_last_inv_announcement(inv=[int(block.hash, 16)]) | inv_node.check_last_inv_announcement(inv=[int(block.hash, 16)]) | ||||
def test_nonnull_locators(self, test_node, inv_node): | def test_nonnull_locators(self, test_node, inv_node): | ||||
tip = int(self.nodes[0].getbestblockhash(), 16) | tip = int(self.nodes[0].getbestblockhash(), 16) | ||||
# PART 1 | # PART 1 | ||||
# 1. Mine a block; expect inv announcements each time | # 1. Mine a block; expect inv announcements each time | ||||
self.log.info( | self.log.info("Part 1: headers don't start before sendheaders message...") | ||||
"Part 1: headers don't start before sendheaders message...") | |||||
for i in range(4): | for i in range(4): | ||||
self.log.debug(f"Part 1.{i}: starting...") | self.log.debug(f"Part 1.{i}: starting...") | ||||
old_tip = tip | old_tip = tip | ||||
tip = self.mine_blocks(1) | tip = self.mine_blocks(1) | ||||
inv_node.check_last_inv_announcement(inv=[tip]) | inv_node.check_last_inv_announcement(inv=[tip]) | ||||
test_node.check_last_inv_announcement(inv=[tip]) | test_node.check_last_inv_announcement(inv=[tip]) | ||||
# Try a few different responses; none should affect next | # Try a few different responses; none should affect next | ||||
# announcement | # announcement | ||||
if i == 0: | if i == 0: | ||||
# first request the block | # first request the block | ||||
test_node.send_get_data([tip]) | test_node.send_get_data([tip]) | ||||
test_node.wait_for_block(tip) | test_node.wait_for_block(tip) | ||||
elif i == 1: | elif i == 1: | ||||
# next try requesting header and block | # next try requesting header and block | ||||
test_node.send_get_headers(locator=[old_tip], hashstop=tip) | test_node.send_get_headers(locator=[old_tip], hashstop=tip) | ||||
test_node.send_get_data([tip]) | test_node.send_get_data([tip]) | ||||
test_node.wait_for_block(tip) | test_node.wait_for_block(tip) | ||||
# since we requested headers... | # since we requested headers... | ||||
test_node.clear_block_announcements() | test_node.clear_block_announcements() | ||||
elif i == 2: | elif i == 2: | ||||
# this time announce own block via headers | # this time announce own block via headers | ||||
inv_node.clear_block_announcements() | inv_node.clear_block_announcements() | ||||
height = self.nodes[0].getblockcount() | height = self.nodes[0].getblockcount() | ||||
last_time = self.nodes[0].getblock( | last_time = self.nodes[0].getblock(self.nodes[0].getbestblockhash())[ | ||||
self.nodes[0].getbestblockhash())['time'] | "time" | ||||
] | |||||
block_time = last_time + 1 | block_time = last_time + 1 | ||||
new_block = create_block( | new_block = create_block(tip, create_coinbase(height + 1), block_time) | ||||
tip, create_coinbase(height + 1), block_time) | |||||
new_block.solve() | new_block.solve() | ||||
test_node.send_header_for_blocks([new_block]) | test_node.send_header_for_blocks([new_block]) | ||||
test_node.wait_for_getdata([new_block.sha256]) | test_node.wait_for_getdata([new_block.sha256]) | ||||
# make sure this block is processed | # make sure this block is processed | ||||
test_node.send_and_ping(msg_block(new_block)) | test_node.send_and_ping(msg_block(new_block)) | ||||
inv_node.wait_until(lambda: inv_node.block_announced) | inv_node.wait_until(lambda: inv_node.block_announced) | ||||
inv_node.clear_block_announcements() | inv_node.clear_block_announcements() | ||||
test_node.clear_block_announcements() | test_node.clear_block_announcements() | ||||
self.log.info("Part 1: success!") | self.log.info("Part 1: success!") | ||||
self.log.info( | self.log.info( | ||||
"Part 2: announce blocks with headers after sendheaders message...") | "Part 2: announce blocks with headers after sendheaders message..." | ||||
) | |||||
# PART 2 | # PART 2 | ||||
# 2. Send a sendheaders message and test that headers announcements | # 2. Send a sendheaders message and test that headers announcements | ||||
# commence and keep working. | # commence and keep working. | ||||
test_node.send_message(msg_sendheaders()) | test_node.send_message(msg_sendheaders()) | ||||
prev_tip = int(self.nodes[0].getbestblockhash(), 16) | prev_tip = int(self.nodes[0].getbestblockhash(), 16) | ||||
test_node.send_get_headers(locator=[prev_tip], hashstop=0) | test_node.send_get_headers(locator=[prev_tip], hashstop=0) | ||||
test_node.sync_with_ping() | test_node.sync_with_ping() | ||||
Show All 9 Lines | def test_nonnull_locators(self, test_node, inv_node): | ||||
# Mine i blocks, and alternate announcing either via | # Mine i blocks, and alternate announcing either via | ||||
# inv (of tip) or via headers. After each, new blocks | # inv (of tip) or via headers. After each, new blocks | ||||
# mined by the node should successfully be announced | # mined by the node should successfully be announced | ||||
# with block header, even though the blocks are never requested | # with block header, even though the blocks are never requested | ||||
for j in range(2): | for j in range(2): | ||||
self.log.debug(f"Part 2.{i}.{j}: starting...") | self.log.debug(f"Part 2.{i}.{j}: starting...") | ||||
blocks = [] | blocks = [] | ||||
for _ in range(i + 1): | for _ in range(i + 1): | ||||
blocks.append(create_block( | blocks.append( | ||||
tip, create_coinbase(height), block_time)) | create_block(tip, create_coinbase(height), block_time) | ||||
) | |||||
blocks[-1].solve() | blocks[-1].solve() | ||||
tip = blocks[-1].sha256 | tip = blocks[-1].sha256 | ||||
block_time += 1 | block_time += 1 | ||||
height += 1 | height += 1 | ||||
if j == 0: | if j == 0: | ||||
# Announce via inv | # Announce via inv | ||||
test_node.send_block_inv(tip) | test_node.send_block_inv(tip) | ||||
test_node.wait_for_getheaders() | test_node.wait_for_getheaders() | ||||
Show All 23 Lines | def test_nonnull_locators(self, test_node, inv_node): | ||||
inv_node.check_last_inv_announcement(inv=[tip]) | inv_node.check_last_inv_announcement(inv=[tip]) | ||||
test_node.check_last_headers_announcement(headers=[tip]) | test_node.check_last_headers_announcement(headers=[tip]) | ||||
height += 1 | height += 1 | ||||
block_time += 1 | block_time += 1 | ||||
self.log.info("Part 2: success!") | self.log.info("Part 2: success!") | ||||
self.log.info( | self.log.info( | ||||
"Part 3: headers announcements can stop after large reorg, and resume after headers/inv from peer...") | "Part 3: headers announcements can stop after large reorg, and resume after" | ||||
" headers/inv from peer..." | |||||
) | |||||
# PART 3. Headers announcements can stop after large reorg, and resume after | # PART 3. Headers announcements can stop after large reorg, and resume after | ||||
# getheaders or inv from peer. | # getheaders or inv from peer. | ||||
for j in range(2): | for j in range(2): | ||||
self.log.debug(f"Part 3.{j}: starting...") | self.log.debug(f"Part 3.{j}: starting...") | ||||
# First try mining a reorg that can propagate with header | # First try mining a reorg that can propagate with header | ||||
# announcement | # announcement | ||||
new_block_hashes = self.mine_reorg(length=7) | new_block_hashes = self.mine_reorg(length=7) | ||||
tip = new_block_hashes[-1] | tip = new_block_hashes[-1] | ||||
inv_node.check_last_inv_announcement(inv=[tip]) | inv_node.check_last_inv_announcement(inv=[tip]) | ||||
test_node.check_last_headers_announcement(headers=new_block_hashes) | test_node.check_last_headers_announcement(headers=new_block_hashes) | ||||
block_time += 8 | block_time += 8 | ||||
# Mine a too-large reorg, which should be announced with a single | # Mine a too-large reorg, which should be announced with a single | ||||
# inv | # inv | ||||
new_block_hashes = self.mine_reorg(length=8) | new_block_hashes = self.mine_reorg(length=8) | ||||
tip = new_block_hashes[-1] | tip = new_block_hashes[-1] | ||||
inv_node.check_last_inv_announcement(inv=[tip]) | inv_node.check_last_inv_announcement(inv=[tip]) | ||||
test_node.check_last_inv_announcement(inv=[tip]) | test_node.check_last_inv_announcement(inv=[tip]) | ||||
block_time += 9 | block_time += 9 | ||||
fork_point = self.nodes[0].getblock( | fork_point = self.nodes[0].getblock(uint256_hex(new_block_hashes[0]))[ | ||||
uint256_hex(new_block_hashes[0]))["previousblockhash"] | "previousblockhash" | ||||
] | |||||
fork_point = int(fork_point, 16) | fork_point = int(fork_point, 16) | ||||
# Use getblocks/getdata | # Use getblocks/getdata | ||||
test_node.send_getblocks(locator=[fork_point]) | test_node.send_getblocks(locator=[fork_point]) | ||||
test_node.check_last_inv_announcement(inv=new_block_hashes) | test_node.check_last_inv_announcement(inv=new_block_hashes) | ||||
test_node.send_get_data(new_block_hashes) | test_node.send_get_data(new_block_hashes) | ||||
test_node.wait_for_block(new_block_hashes[-1]) | test_node.wait_for_block(new_block_hashes[-1]) | ||||
for i in range(3): | for i in range(3): | ||||
self.log.debug(f"Part 3.{j}.{i}: starting...") | self.log.debug(f"Part 3.{j}.{i}: starting...") | ||||
# Mine another block, still should get only an inv | # Mine another block, still should get only an inv | ||||
tip = self.mine_blocks(1) | tip = self.mine_blocks(1) | ||||
inv_node.check_last_inv_announcement(inv=[tip]) | inv_node.check_last_inv_announcement(inv=[tip]) | ||||
test_node.check_last_inv_announcement(inv=[tip]) | test_node.check_last_inv_announcement(inv=[tip]) | ||||
if i == 0: | if i == 0: | ||||
# Just get the data -- shouldn't cause headers | # Just get the data -- shouldn't cause headers | ||||
# announcements to resume | # announcements to resume | ||||
test_node.send_get_data([tip]) | test_node.send_get_data([tip]) | ||||
test_node.wait_for_block(tip) | test_node.wait_for_block(tip) | ||||
elif i == 1: | elif i == 1: | ||||
# Send a getheaders message that shouldn't trigger headers announcements | # Send a getheaders message that shouldn't trigger headers announcements | ||||
# to resume (best header sent will be too old) | # to resume (best header sent will be too old) | ||||
test_node.send_get_headers( | test_node.send_get_headers( | ||||
locator=[fork_point], hashstop=new_block_hashes[1]) | locator=[fork_point], hashstop=new_block_hashes[1] | ||||
) | |||||
test_node.send_get_data([tip]) | test_node.send_get_data([tip]) | ||||
test_node.wait_for_block(tip) | test_node.wait_for_block(tip) | ||||
elif i == 2: | elif i == 2: | ||||
# This time, try sending either a getheaders to trigger resumption | # This time, try sending either a getheaders to trigger resumption | ||||
# of headers announcements, or mine a new block and inv it, also | # of headers announcements, or mine a new block and inv it, also | ||||
# triggering resumption of headers announcements. | # triggering resumption of headers announcements. | ||||
test_node.send_get_data([tip]) | test_node.send_get_data([tip]) | ||||
test_node.wait_for_block(tip) | test_node.wait_for_block(tip) | ||||
if j == 0: | if j == 0: | ||||
test_node.send_get_headers(locator=[tip], hashstop=0) | test_node.send_get_headers(locator=[tip], hashstop=0) | ||||
test_node.sync_with_ping() | test_node.sync_with_ping() | ||||
else: | else: | ||||
test_node.send_block_inv(tip) | test_node.send_block_inv(tip) | ||||
test_node.sync_with_ping() | test_node.sync_with_ping() | ||||
# New blocks should now be announced with header | # New blocks should now be announced with header | ||||
tip = self.mine_blocks(1) | tip = self.mine_blocks(1) | ||||
inv_node.check_last_inv_announcement(inv=[tip]) | inv_node.check_last_inv_announcement(inv=[tip]) | ||||
test_node.check_last_headers_announcement(headers=[tip]) | test_node.check_last_headers_announcement(headers=[tip]) | ||||
self.log.info("Part 3: success!") | self.log.info("Part 3: success!") | ||||
self.log.info("Part 4: Testing direct fetch behavior...") | self.log.info("Part 4: Testing direct fetch behavior...") | ||||
tip = self.mine_blocks(1) | tip = self.mine_blocks(1) | ||||
height = self.nodes[0].getblockcount() + 1 | height = self.nodes[0].getblockcount() + 1 | ||||
last_time = self.nodes[0].getblock( | last_time = self.nodes[0].getblock(self.nodes[0].getbestblockhash())["time"] | ||||
self.nodes[0].getbestblockhash())['time'] | |||||
block_time = last_time + 1 | block_time = last_time + 1 | ||||
# Create 2 blocks. Send the blocks, then send the headers. | # Create 2 blocks. Send the blocks, then send the headers. | ||||
blocks = [] | blocks = [] | ||||
for _ in range(2): | for _ in range(2): | ||||
blocks.append(create_block( | blocks.append(create_block(tip, create_coinbase(height), block_time)) | ||||
tip, create_coinbase(height), block_time)) | |||||
blocks[-1].solve() | blocks[-1].solve() | ||||
tip = blocks[-1].sha256 | tip = blocks[-1].sha256 | ||||
block_time += 1 | block_time += 1 | ||||
height += 1 | height += 1 | ||||
inv_node.send_message(msg_block(blocks[-1])) | inv_node.send_message(msg_block(blocks[-1])) | ||||
inv_node.sync_with_ping() # Make sure blocks are processed | inv_node.sync_with_ping() # Make sure blocks are processed | ||||
test_node.last_message.pop("getdata", None) | test_node.last_message.pop("getdata", None) | ||||
test_node.send_header_for_blocks(blocks) | test_node.send_header_for_blocks(blocks) | ||||
test_node.sync_with_ping() | test_node.sync_with_ping() | ||||
# should not have received any getdata messages | # should not have received any getdata messages | ||||
with p2p_lock: | with p2p_lock: | ||||
assert "getdata" not in test_node.last_message | assert "getdata" not in test_node.last_message | ||||
# This time, direct fetch should work | # This time, direct fetch should work | ||||
blocks = [] | blocks = [] | ||||
for _ in range(3): | for _ in range(3): | ||||
blocks.append(create_block( | blocks.append(create_block(tip, create_coinbase(height), block_time)) | ||||
tip, create_coinbase(height), block_time)) | |||||
blocks[-1].solve() | blocks[-1].solve() | ||||
tip = blocks[-1].sha256 | tip = blocks[-1].sha256 | ||||
block_time += 1 | block_time += 1 | ||||
height += 1 | height += 1 | ||||
test_node.send_header_for_blocks(blocks) | test_node.send_header_for_blocks(blocks) | ||||
test_node.sync_with_ping() | test_node.sync_with_ping() | ||||
test_node.wait_for_getdata( | test_node.wait_for_getdata( | ||||
[x.sha256 for x in blocks], timeout=DIRECT_FETCH_RESPONSE_TIME) | [x.sha256 for x in blocks], timeout=DIRECT_FETCH_RESPONSE_TIME | ||||
) | |||||
[test_node.send_message(msg_block(x)) for x in blocks] | [test_node.send_message(msg_block(x)) for x in blocks] | ||||
test_node.sync_with_ping() | test_node.sync_with_ping() | ||||
# Now announce a header that forks the last two blocks | # Now announce a header that forks the last two blocks | ||||
tip = blocks[0].sha256 | tip = blocks[0].sha256 | ||||
height -= 2 | height -= 2 | ||||
blocks = [] | blocks = [] | ||||
# Create extra blocks for later | # Create extra blocks for later | ||||
for _ in range(20): | for _ in range(20): | ||||
blocks.append(create_block( | blocks.append(create_block(tip, create_coinbase(height), block_time)) | ||||
tip, create_coinbase(height), block_time)) | |||||
blocks[-1].solve() | blocks[-1].solve() | ||||
tip = blocks[-1].sha256 | tip = blocks[-1].sha256 | ||||
block_time += 1 | block_time += 1 | ||||
height += 1 | height += 1 | ||||
# Announcing one block on fork should not trigger direct fetch | # Announcing one block on fork should not trigger direct fetch | ||||
# (less work than tip) | # (less work than tip) | ||||
test_node.last_message.pop("getdata", None) | test_node.last_message.pop("getdata", None) | ||||
test_node.send_header_for_blocks(blocks[0:1]) | test_node.send_header_for_blocks(blocks[0:1]) | ||||
test_node.sync_with_ping() | test_node.sync_with_ping() | ||||
with p2p_lock: | with p2p_lock: | ||||
assert "getdata" not in test_node.last_message | assert "getdata" not in test_node.last_message | ||||
# Announcing one more block on fork should trigger direct fetch for | # Announcing one more block on fork should trigger direct fetch for | ||||
# both blocks (same work as tip) | # both blocks (same work as tip) | ||||
test_node.send_header_for_blocks(blocks[1:2]) | test_node.send_header_for_blocks(blocks[1:2]) | ||||
test_node.sync_with_ping() | test_node.sync_with_ping() | ||||
test_node.wait_for_getdata( | test_node.wait_for_getdata( | ||||
[x.sha256 for x in blocks[0:2]], timeout=DIRECT_FETCH_RESPONSE_TIME) | [x.sha256 for x in blocks[0:2]], timeout=DIRECT_FETCH_RESPONSE_TIME | ||||
) | |||||
# Announcing 16 more headers should trigger direct fetch for 14 more | # Announcing 16 more headers should trigger direct fetch for 14 more | ||||
# blocks | # blocks | ||||
test_node.send_header_for_blocks(blocks[2:18]) | test_node.send_header_for_blocks(blocks[2:18]) | ||||
test_node.sync_with_ping() | test_node.sync_with_ping() | ||||
test_node.wait_for_getdata( | test_node.wait_for_getdata( | ||||
[x.sha256 for x in blocks[2:16]], timeout=DIRECT_FETCH_RESPONSE_TIME) | [x.sha256 for x in blocks[2:16]], timeout=DIRECT_FETCH_RESPONSE_TIME | ||||
) | |||||
# Announcing 1 more header should not trigger any response | # Announcing 1 more header should not trigger any response | ||||
test_node.last_message.pop("getdata", None) | test_node.last_message.pop("getdata", None) | ||||
test_node.send_header_for_blocks(blocks[18:19]) | test_node.send_header_for_blocks(blocks[18:19]) | ||||
test_node.sync_with_ping() | test_node.sync_with_ping() | ||||
with p2p_lock: | with p2p_lock: | ||||
assert "getdata" not in test_node.last_message | assert "getdata" not in test_node.last_message | ||||
self.log.info("Part 4: success!") | self.log.info("Part 4: success!") | ||||
# Now deliver all those blocks we announced. | # Now deliver all those blocks we announced. | ||||
[test_node.send_message(msg_block(x)) for x in blocks] | [test_node.send_message(msg_block(x)) for x in blocks] | ||||
self.log.info("Part 5: Testing handling of unconnecting headers") | self.log.info("Part 5: Testing handling of unconnecting headers") | ||||
# First we test that receipt of an unconnecting header doesn't prevent | # First we test that receipt of an unconnecting header doesn't prevent | ||||
# chain sync. | # chain sync. | ||||
for i in range(10): | for i in range(10): | ||||
self.log.debug(f"Part 5.{i}: starting...") | self.log.debug(f"Part 5.{i}: starting...") | ||||
test_node.last_message.pop("getdata", None) | test_node.last_message.pop("getdata", None) | ||||
blocks = [] | blocks = [] | ||||
# Create two more blocks. | # Create two more blocks. | ||||
for _ in range(2): | for _ in range(2): | ||||
blocks.append(create_block( | blocks.append(create_block(tip, create_coinbase(height), block_time)) | ||||
tip, create_coinbase(height), block_time)) | |||||
blocks[-1].solve() | blocks[-1].solve() | ||||
tip = blocks[-1].sha256 | tip = blocks[-1].sha256 | ||||
block_time += 1 | block_time += 1 | ||||
height += 1 | height += 1 | ||||
# Send the header of the second block -> this won't connect. | # Send the header of the second block -> this won't connect. | ||||
with p2p_lock: | with p2p_lock: | ||||
test_node.last_message.pop("getheaders", None) | test_node.last_message.pop("getheaders", None) | ||||
test_node.send_header_for_blocks([blocks[1]]) | test_node.send_header_for_blocks([blocks[1]]) | ||||
test_node.wait_for_getheaders() | test_node.wait_for_getheaders() | ||||
test_node.send_header_for_blocks(blocks) | test_node.send_header_for_blocks(blocks) | ||||
test_node.wait_for_getdata([x.sha256 for x in 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.send_message(msg_block(x)) for x in blocks] | ||||
test_node.sync_with_ping() | test_node.sync_with_ping() | ||||
assert_equal( | assert_equal(int(self.nodes[0].getbestblockhash(), 16), blocks[1].sha256) | ||||
int(self.nodes[0].getbestblockhash(), 16), blocks[1].sha256) | |||||
blocks = [] | blocks = [] | ||||
# Now we test that if we repeatedly don't send connecting headers, we | # Now we test that if we repeatedly don't send connecting headers, we | ||||
# don't go into an infinite loop trying to get them to connect. | # don't go into an infinite loop trying to get them to connect. | ||||
MAX_UNCONNECTING_HEADERS = 10 | MAX_UNCONNECTING_HEADERS = 10 | ||||
for _ in range(MAX_UNCONNECTING_HEADERS + 1): | for _ in range(MAX_UNCONNECTING_HEADERS + 1): | ||||
blocks.append(create_block( | blocks.append(create_block(tip, create_coinbase(height), block_time)) | ||||
tip, create_coinbase(height), block_time)) | |||||
blocks[-1].solve() | blocks[-1].solve() | ||||
tip = blocks[-1].sha256 | tip = blocks[-1].sha256 | ||||
block_time += 1 | block_time += 1 | ||||
height += 1 | height += 1 | ||||
for i in range(1, MAX_UNCONNECTING_HEADERS): | for i in range(1, MAX_UNCONNECTING_HEADERS): | ||||
# Send a header that doesn't connect, check that we get a | # Send a header that doesn't connect, check that we get a | ||||
# getheaders. | # getheaders. | ||||
Show All 26 Lines | def test_nonnull_locators(self, test_node, inv_node): | ||||
self.log.info("Part 5: success!") | self.log.info("Part 5: success!") | ||||
# Finally, check that the inv node never received a getdata request, | # Finally, check that the inv node never received a getdata request, | ||||
# throughout the test | # throughout the test | ||||
assert "getdata" not in inv_node.last_message | assert "getdata" not in inv_node.last_message | ||||
if __name__ == '__main__': | if __name__ == "__main__": | ||||
SendHeadersTest().main() | SendHeadersTest().main() |