Changeset View
Changeset View
Standalone View
Standalone View
test/functional/p2p_sendheaders.py
Show First 20 Lines • Show All 108 Lines • ▼ Show 20 Lines | |||||
DIRECT_FETCH_RESPONSE_TIME = 0.05 | DIRECT_FETCH_RESPONSE_TIME = 0.05 | ||||
class BaseNode(P2PInterface): | class BaseNode(P2PInterface): | ||||
def __init__(self): | def __init__(self): | ||||
super().__init__() | super().__init__() | ||||
self.block_announced = False | self.block_announced = False | ||||
self.last_blockhash_announced = None | self.last_blockhash_announced = None | ||||
self.recent_headers_announced = [] | |||||
def send_get_data(self, block_hashes): | def send_get_data(self, block_hashes): | ||||
"""Request data for a list of block hashes.""" | """Request data for a list of block hashes.""" | ||||
msg = msg_getdata() | msg = msg_getdata() | ||||
for x in block_hashes: | for x in block_hashes: | ||||
msg.inv.append(CInv(2, x)) | msg.inv.append(CInv(2, x)) | ||||
self.send_message(msg) | self.send_message(msg) | ||||
Show All 32 Lines | class BaseNode(P2PInterface): | ||||
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): | ||||
self.block_announced = True | self.block_announced = True | ||||
message.headers[-1].calc_sha256() | for x in message.headers: | ||||
x.calc_sha256() | |||||
# append because headers may be announced over multiple messages. | |||||
self.recent_headers_announced.append(x.sha256) | |||||
self.last_blockhash_announced = message.headers[-1].sha256 | self.last_blockhash_announced = message.headers[-1].sha256 | ||||
def clear_last_announcement(self): | def clear_block_announcements(self): | ||||
with mininode_lock: | with mininode_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 = [] | |||||
def check_last_announcement(self, headers=None, inv=None): | def check_last_headers_announcement(self, headers): | ||||
"""Test whether the last announcement received had the right header or the right inv. | """Test whether the last headers announcements received are right. | ||||
Headers may be announced across more than one message.""" | |||||
inv and headers should be lists of block hashes.""" | def test_function(): return (len(self.recent_headers_announced) >= len(headers)) | ||||
wait_until(test_function, timeout=60, lock=mininode_lock) | |||||
with mininode_lock: | |||||
assert_equal(self.recent_headers_announced, headers) | |||||
self.block_announced = False | |||||
self.last_message.pop("headers", None) | |||||
self.recent_headers_announced = [] | |||||
def check_last_inv_announcement(self, inv): | |||||
"""Test whether the last announcement received had the right inv. | |||||
inv should be a list of block hashes.""" | |||||
def test_function(): return self.block_announced | def test_function(): return self.block_announced | ||||
wait_until(test_function, timeout=60, lock=mininode_lock) | wait_until(test_function, timeout=60, lock=mininode_lock) | ||||
with mininode_lock: | with mininode_lock: | ||||
self.block_announced = False | |||||
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] | ||||
if inv is not None: | |||||
assert_equal(compare_inv, inv) | assert_equal(compare_inv, inv) | ||||
self.block_announced = False | |||||
compare_headers = [] | |||||
if "headers" in self.last_message: | |||||
compare_headers = [ | |||||
x.sha256 for x in self.last_message["headers"].headers] | |||||
if headers is not None: | |||||
assert_equal(compare_headers, headers) | |||||
self.last_message.pop("inv", None) | self.last_message.pop("inv", None) | ||||
self.last_message.pop("headers", 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 last block announcement from each p2p listener | # Clear out block announcements from each p2p listener | ||||
[x.clear_last_announcement() for x in self.nodes[0].p2ps] | [x.clear_block_announcements() for x in self.nodes[0].p2ps] | ||||
self.nodes[0].generate(count) | self.nodes[0].generate(count) | ||||
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.nodes[0].generate(length) | self.nodes[0].generate(length) | ||||
sync_blocks(self.nodes, wait=0.1) | sync_blocks(self.nodes, wait=0.1) | ||||
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_last_announcement() | 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.nodes[1].generate(length + 1) | all_hashes = self.nodes[1].generate(length + 1) | ||||
sync_blocks(self.nodes, wait=0.1) | sync_blocks(self.nodes, wait=0.1) | ||||
Show All 18 Lines | def run_test(self): | ||||
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.nodes[0].generate(1)[0]) | tip = self.nodes[0].getblockheader(self.nodes[0].generate(1)[0]) | ||||
tip_hash = int(tip["hash"], 16) | tip_hash = int(tip["hash"], 16) | ||||
inv_node.check_last_announcement(inv=[tip_hash], headers=[]) | inv_node.check_last_inv_announcement(inv=[tip_hash]) | ||||
test_node.check_last_announcement(inv=[tip_hash], headers=[]) | 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_last_announcement() | 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_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 headers.") | ||||
block = create_block(int(tip["hash"], 16), create_coinbase( | block = create_block(int(tip["hash"], 16), create_coinbase( | ||||
tip["height"] + 1), tip["mediantime"] + 1) | 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_last_announcement() | 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_last_announcement() | inv_node.clear_block_announcements() | ||||
test_node.send_message(msg_block(block)) | test_node.send_message(msg_block(block)) | ||||
inv_node.check_last_announcement(inv=[int(block.hash, 16)], headers=[]) | 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): | ||||
old_tip = tip | old_tip = tip | ||||
tip = self.mine_blocks(1) | tip = self.mine_blocks(1) | ||||
inv_node.check_last_announcement(inv=[tip], headers=[]) | inv_node.check_last_inv_announcement(inv=[tip]) | ||||
test_node.check_last_announcement(inv=[tip], headers=[]) | test_node.check_last_inv_announcement(inv=[tip]) | ||||
# Try a few different responses; none should affect next announcement | # Try a few different responses; none should affect next 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_last_announcement() | 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 | ||||
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())['time'] | self.nodes[0].getbestblockhash())['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]) | ||||
test_node.send_message(msg_block(new_block)) | test_node.send_message(msg_block(new_block)) | ||||
test_node.sync_with_ping() # make sure this block is processed | test_node.sync_with_ping() # make sure this block is processed | ||||
inv_node.clear_last_announcement() | inv_node.clear_block_announcements() | ||||
test_node.clear_last_announcement() | 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() | ||||
# Now that we've synced headers, headers announcements should work | # Now that we've synced headers, headers announcements should work | ||||
tip = self.mine_blocks(1) | tip = self.mine_blocks(1) | ||||
inv_node.check_last_announcement(inv=[tip], headers=[]) | inv_node.check_last_inv_announcement(inv=[tip]) | ||||
test_node.check_last_announcement(headers=[tip]) | test_node.check_last_headers_announcement(headers=[tip]) | ||||
height = self.nodes[0].getblockcount() + 1 | height = self.nodes[0].getblockcount() + 1 | ||||
block_time += 10 # Advance far enough ahead | block_time += 10 # Advance far enough ahead | ||||
for i in range(10): | for i in range(10): | ||||
# 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 | ||||
Show All 28 Lines | def test_nonnull_locators(self, test_node, inv_node): | ||||
[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() | ||||
inv_node.sync_with_ping() | inv_node.sync_with_ping() | ||||
# This block should not be announced to the inv node (since it also | # This block should not be announced to the inv node (since it also | ||||
# broadcast it) | # broadcast it) | ||||
assert "inv" not in inv_node.last_message | assert "inv" not in inv_node.last_message | ||||
assert "headers" not in inv_node.last_message | assert "headers" not in inv_node.last_message | ||||
tip = self.mine_blocks(1) | tip = self.mine_blocks(1) | ||||
inv_node.check_last_announcement(inv=[tip], headers=[]) | inv_node.check_last_inv_announcement(inv=[tip]) | ||||
test_node.check_last_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): | ||||
# First try mining a reorg that can propagate with header announcement | # First try mining a reorg that can propagate with header 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_announcement(inv=[tip], headers=[]) | inv_node.check_last_inv_announcement(inv=[tip]) | ||||
test_node.check_last_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 inv | # Mine a too-large reorg, which should be announced with a single 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_announcement(inv=[tip], headers=[]) | inv_node.check_last_inv_announcement(inv=[tip]) | ||||
test_node.check_last_announcement(inv=[tip], headers=[]) | test_node.check_last_inv_announcement(inv=[tip]) | ||||
block_time += 9 | block_time += 9 | ||||
fork_point = self.nodes[0].getblock("{:02x}".format( | fork_point = self.nodes[0].getblock("{:02x}".format( | ||||
new_block_hashes[0]))["previousblockhash"] | new_block_hashes[0]))["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_announcement(inv=new_block_hashes, headers=[]) | 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): | ||||
# 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_announcement(inv=[tip], headers=[]) | inv_node.check_last_inv_announcement(inv=[tip]) | ||||
test_node.check_last_announcement(inv=[tip], headers=[]) | test_node.check_last_inv_announcement(inv=[tip]) | ||||
if i == 0: | if i == 0: | ||||
self.log.debug( | self.log.debug( | ||||
"Just get the data -- shouldn't cause headers announcements to resume") | "Just get the data -- shouldn't cause headers 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: | ||||
self.log.debug( | self.log.debug( | ||||
"Send a getheaders message that shouldn't trigger headers announcements to resume (best header sent will be too old)") | "Send a getheaders message that shouldn't trigger headers announcements to resume (best header sent will be too old)") | ||||
Show All 9 Lines | def test_nonnull_locators(self, test_node, inv_node): | ||||
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_announcement(inv=[tip], headers=[]) | inv_node.check_last_inv_announcement(inv=[tip]) | ||||
test_node.check_last_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'] | ||||
▲ Show 20 Lines • Show All 163 Lines • Show Last 20 Lines |