Add test for handling of unconnecting headers

This commit is contained in:
Suhas Daftuar 2016-07-06 21:19:32 -04:00
parent 96fa95361f
commit e91cf4b210

View file

@ -63,6 +63,21 @@ e. Announce 16 more headers that build on that fork.
Expect: getdata request for 14 more blocks. Expect: getdata request for 14 more blocks.
f. Announce 1 more header that builds on that fork. f. Announce 1 more header that builds on that fork.
Expect: no response. Expect: no response.
Part 5: Test handling of headers that don't connect.
a. Repeat 10 times:
1. Announce a header that doesn't connect.
Expect: getheaders message
2. Send headers chain.
Expect: getdata for the missing blocks, tip update.
b. Then send 9 more headers that don't connect.
Expect: getheaders message each time.
c. Announce a header that does connect.
Expect: no response.
d. Announce 49 headers that don't connect.
Expect: getheaders message each time.
e. Announce one more that doesn't connect.
Expect: disconnect.
''' '''
class BaseNode(NodeConnCB): class BaseNode(NodeConnCB):
@ -77,6 +92,8 @@ class BaseNode(NodeConnCB):
self.last_getdata = None self.last_getdata = None
self.sleep_time = 0.05 self.sleep_time = 0.05
self.block_announced = False self.block_announced = False
self.last_getheaders = None
self.disconnected = False
def clear_last_announcement(self): def clear_last_announcement(self):
with mininode_lock: with mininode_lock:
@ -127,6 +144,12 @@ class BaseNode(NodeConnCB):
def on_pong(self, conn, message): def on_pong(self, conn, message):
self.last_pong = message self.last_pong = message
def on_getheaders(self, conn, message):
self.last_getheaders = message
def on_close(self, conn):
self.disconnected = True
# Test whether the last announcement we received had the # Test whether the last announcement we received had the
# right header or the right inv # right header or the right inv
# inv and headers should be lists of block hashes # inv and headers should be lists of block hashes
@ -178,6 +201,11 @@ class BaseNode(NodeConnCB):
self.sync(test_function, timeout) self.sync(test_function, timeout)
return return
def wait_for_getheaders(self, timeout=60):
test_function = lambda: self.last_getheaders != None
self.sync(test_function, timeout)
return
def wait_for_getdata(self, hash_list, timeout=60): def wait_for_getdata(self, hash_list, timeout=60):
if hash_list == []: if hash_list == []:
return return
@ -186,6 +214,11 @@ class BaseNode(NodeConnCB):
self.sync(test_function, timeout) self.sync(test_function, timeout)
return return
def wait_for_disconnect(self, timeout=60):
test_function = lambda: self.disconnected
self.sync(test_function, timeout)
return
def send_header_for_blocks(self, new_blocks): def send_header_for_blocks(self, new_blocks):
headers_message = msg_headers() headers_message = msg_headers()
headers_message.headers = [ CBlockHeader(b) for b in new_blocks ] headers_message.headers = [ CBlockHeader(b) for b in new_blocks ]
@ -510,6 +543,78 @@ class SendHeadersTest(BitcoinTestFramework):
print("Part 4: success!") print("Part 4: success!")
# Now deliver all those blocks we announced.
[ test_node.send_message(msg_block(x)) for x in blocks ]
print("Part 5: Testing handling of unconnecting headers")
# First we test that receipt of an unconnecting header doesn't prevent
# chain sync.
for i in range(10):
test_node.last_getdata = None
blocks = []
# Create two more blocks.
for j in range(2):
blocks.append(create_block(tip, create_coinbase(height), block_time))
blocks[-1].solve()
tip = blocks[-1].sha256
block_time += 1
height += 1
# Send the header of the second block -> this won't connect.
with mininode_lock:
test_node.last_getheaders = None
test_node.send_header_for_blocks([blocks[1]])
test_node.wait_for_getheaders(timeout=1)
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)
blocks = []
# 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.
MAX_UNCONNECTING_HEADERS = 10
for j in range(MAX_UNCONNECTING_HEADERS+1):
blocks.append(create_block(tip, create_coinbase(height), block_time))
blocks[-1].solve()
tip = blocks[-1].sha256
block_time += 1
height += 1
for i in range(1, MAX_UNCONNECTING_HEADERS):
# Send a header that doesn't connect, check that we get a getheaders.
with mininode_lock:
test_node.last_getheaders = None
test_node.send_header_for_blocks([blocks[i]])
test_node.wait_for_getheaders(timeout=1)
# Next header will connect, should re-set our count:
test_node.send_header_for_blocks([blocks[0]])
# Remove the first two entries (blocks[1] would connect):
blocks = blocks[2:]
# Now try to see how many unconnecting headers we can send
# before we get disconnected. Should be 5*MAX_UNCONNECTING_HEADERS
for i in range(5*MAX_UNCONNECTING_HEADERS - 1):
# Send a header that doesn't connect, check that we get a getheaders.
with mininode_lock:
test_node.last_getheaders = None
test_node.send_header_for_blocks([blocks[i%len(blocks)]])
test_node.wait_for_getheaders(timeout=1)
# Eventually this stops working.
with mininode_lock:
self.last_getheaders = None
test_node.send_header_for_blocks([blocks[-1]])
# Should get disconnected
test_node.wait_for_disconnect()
with mininode_lock:
self.last_getheaders = True
print("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_equal(inv_node.last_getdata, None) assert_equal(inv_node.last_getdata, None)