2 # Copyright (c) 2014-2016 The Bitcoin Core developers
3 # Distributed under the MIT software license, see the accompanying
4 # file COPYING or http://www.opensource.org/licenses/mit-license.php.
5 """Test behavior of headers messages to announce blocks.
10 - node0 is the node-under-test. We create two p2p connections to it. The
11 first p2p connection is a control and should only ever receive inv's. The
12 second p2p connection tests the headers sending logic.
13 - node1 is used to create reorgs.
18 Sends two getheaders requests with null locator values. First request's hashstop
19 value refers to validated block, while second request's hashstop value refers to
20 a block which hasn't been validated. Verifies only the first request returns
26 Part 1: No headers announcements before "sendheaders"
27 a. node mines a block [expect: inv]
28 send getdata for the block [expect: block]
29 b. node mines another block [expect: inv]
30 send getheaders and getdata [expect: headers, then block]
31 c. node mines another block [expect: inv]
32 peer mines a block, announces with header [expect: getdata]
33 d. node mines another block [expect: inv]
35 Part 2: After "sendheaders", headers announcements should generally work.
36 a. peer sends sendheaders [expect: no response]
37 peer sends getheaders with current tip [expect: no response]
38 b. node mines a block [expect: tip header]
39 c. for N in 1, ..., 10:
40 * for announce-type in {inv, header}
41 - peer mines N blocks, announces with announce-type
42 [ expect: getheaders/getdata or getdata, deliver block(s) ]
43 - node mines a block [ expect: 1 header ]
45 Part 3: Headers announcements stop after large reorg and resume after getheaders or inv from peer.
46 - For response-type in {inv, getheaders}
47 * node mines a 7 block reorg [ expect: headers announcement of 8 blocks ]
48 * node mines an 8-block reorg [ expect: inv at tip ]
49 * peer responds with getblocks/getdata [expect: inv, blocks ]
50 * node mines another block [ expect: inv at tip, peer sends getdata, expect: block ]
51 * node mines another block at tip [ expect: inv ]
52 * peer responds with getheaders with an old hashstop more than 8 blocks back [expect: headers]
53 * peer requests block [ expect: block ]
54 * node mines another block at tip [ expect: inv, peer sends getdata, expect: block ]
55 * peer sends response-type [expect headers if getheaders, getheaders/getdata if mining new block]
56 * node mines 1 block [expect: 1 header, peer responds with getdata]
58 Part 4: Test direct fetch behavior
59 a. Announce 2 old block headers.
60 Expect: no getdata requests.
61 b. Announce 3 new blocks via 1 headers message.
62 Expect: one getdata request for all 3 blocks.
64 c. Announce 1 header that forks off the last two blocks.
66 d. Announce 1 more header that builds on that fork.
67 Expect: one getdata request for two blocks.
68 e. Announce 16 more headers that build on that fork.
69 Expect: getdata request for 14 more blocks.
70 f. Announce 1 more header that builds on that fork.
73 Part 5: Test handling of headers that don't connect.
75 1. Announce a header that doesn't connect.
76 Expect: getheaders message
77 2. Send headers chain.
78 Expect: getdata for the missing blocks, tip update.
79 b. Then send 9 more headers that don't connect.
80 Expect: getheaders message each time.
81 c. Announce a header that does connect.
83 d. Announce 49 headers that don't connect.
84 Expect: getheaders message each time.
85 e. Announce one more that doesn't connect.
88 from test_framework
.blocktools
import create_block
, create_coinbase
89 from test_framework
.mininode
import (
104 from test_framework
.test_framework
import BitcoinTestFramework
105 from test_framework
.util
import (
111 DIRECT_FETCH_RESPONSE_TIME
= 0.05
113 class BaseNode(P2PInterface
):
117 self
.block_announced
= False
118 self
.last_blockhash_announced
= None
120 def send_get_data(self
, block_hashes
):
121 """Request data for a list of block hashes."""
123 for x
in block_hashes
:
124 msg
.inv
.append(CInv(2, x
))
125 self
.send_message(msg
)
127 def send_get_headers(self
, locator
, hashstop
):
128 msg
= msg_getheaders()
129 msg
.locator
.vHave
= locator
130 msg
.hashstop
= hashstop
131 self
.send_message(msg
)
133 def send_block_inv(self
, blockhash
):
135 msg
.inv
= [CInv(2, blockhash
)]
136 self
.send_message(msg
)
138 def send_header_for_blocks(self
, new_blocks
):
139 headers_message
= msg_headers()
140 headers_message
.headers
= [CBlockHeader(b
) for b
in new_blocks
]
141 self
.send_message(headers_message
)
143 def send_getblocks(self
, locator
):
144 getblocks_message
= msg_getblocks()
145 getblocks_message
.locator
.vHave
= locator
146 self
.send_message(getblocks_message
)
148 def wait_for_getdata(self
, hash_list
, timeout
=60):
152 test_function
= lambda: "getdata" in self
.last_message
and [x
.hash for x
in self
.last_message
["getdata"].inv
] == hash_list
153 wait_until(test_function
, timeout
=timeout
, lock
=mininode_lock
)
155 def wait_for_block_announcement(self
, block_hash
, timeout
=60):
156 test_function
= lambda: self
.last_blockhash_announced
== block_hash
157 wait_until(test_function
, timeout
=timeout
, lock
=mininode_lock
)
159 def on_inv(self
, message
):
160 self
.block_announced
= True
161 self
.last_blockhash_announced
= message
.inv
[-1].hash
163 def on_headers(self
, message
):
164 if len(message
.headers
):
165 self
.block_announced
= True
166 message
.headers
[-1].calc_sha256()
167 self
.last_blockhash_announced
= message
.headers
[-1].sha256
169 def clear_last_announcement(self
):
171 self
.block_announced
= False
172 self
.last_message
.pop("inv", None)
173 self
.last_message
.pop("headers", None)
175 def check_last_announcement(self
, headers
=None, inv
=None):
176 """Test whether the last announcement received had the right header or the right inv.
178 inv and headers should be lists of block hashes."""
180 test_function
= lambda: self
.block_announced
181 wait_until(test_function
, timeout
=60, lock
=mininode_lock
)
184 self
.block_announced
= False
187 if "inv" in self
.last_message
:
188 compare_inv
= [x
.hash for x
in self
.last_message
["inv"].inv
]
190 assert_equal(compare_inv
, inv
)
193 if "headers" in self
.last_message
:
194 compare_headers
= [x
.sha256
for x
in self
.last_message
["headers"].headers
]
195 if headers
is not None:
196 assert_equal(compare_headers
, headers
)
198 self
.last_message
.pop("inv", None)
199 self
.last_message
.pop("headers", None)
201 class SendHeadersTest(BitcoinTestFramework
):
202 def set_test_params(self
):
203 self
.setup_clean_chain
= True
206 def mine_blocks(self
, count
):
207 """Mine count blocks and return the new tip."""
209 # Clear out last block announcement from each p2p listener
210 [x
.clear_last_announcement() for x
in self
.nodes
[0].p2ps
]
211 self
.nodes
[0].generate(count
)
212 return int(self
.nodes
[0].getbestblockhash(), 16)
214 def mine_reorg(self
, length
):
215 """Mine a reorg that invalidates length blocks (replacing them with # length+1 blocks).
217 Note: we clear the state of our p2p connections after the
218 to-be-reorged-out blocks are mined, so that we don't break later tests.
219 return the list of block hashes newly mined."""
221 self
.nodes
[0].generate(length
) # make sure all invalidated blocks are node0's
222 sync_blocks(self
.nodes
, wait
=0.1)
223 for x
in self
.nodes
[0].p2ps
:
224 x
.wait_for_block_announcement(int(self
.nodes
[0].getbestblockhash(), 16))
225 x
.clear_last_announcement()
227 tip_height
= self
.nodes
[1].getblockcount()
228 hash_to_invalidate
= self
.nodes
[1].getblockhash(tip_height
- (length
- 1))
229 self
.nodes
[1].invalidateblock(hash_to_invalidate
)
230 all_hashes
= self
.nodes
[1].generate(length
+ 1) # Must be longer than the orig chain
231 sync_blocks(self
.nodes
, wait
=0.1)
232 return [int(x
, 16) for x
in all_hashes
]
235 # Setup the p2p connections and start up the network thread.
236 inv_node
= self
.nodes
[0].add_p2p_connection(BaseNode())
237 # Make sure NODE_NETWORK is not set for test_node, so no block download
238 # will occur outside of direct fetching
239 test_node
= self
.nodes
[0].add_p2p_connection(BaseNode(), services
=NODE_WITNESS
)
241 network_thread_start()
243 # Test logic begins here
244 inv_node
.wait_for_verack()
245 test_node
.wait_for_verack()
247 # Ensure verack's have been processed by our peer
248 inv_node
.sync_with_ping()
249 test_node
.sync_with_ping()
251 self
.test_null_locators(test_node
, inv_node
)
252 self
.test_nonnull_locators(test_node
, inv_node
)
254 def test_null_locators(self
, test_node
, inv_node
):
255 tip
= self
.nodes
[0].getblockheader(self
.nodes
[0].generate(1)[0])
256 tip_hash
= int(tip
["hash"], 16)
258 inv_node
.check_last_announcement(inv
=[tip_hash
], headers
=[])
259 test_node
.check_last_announcement(inv
=[tip_hash
], headers
=[])
261 self
.log
.info("Verify getheaders with null locator and valid hashstop returns headers.")
262 test_node
.clear_last_announcement()
263 test_node
.send_get_headers(locator
=[], hashstop
=tip_hash
)
264 test_node
.check_last_announcement(headers
=[tip_hash
])
266 self
.log
.info("Verify getheaders with null locator and invalid hashstop does not return headers.")
267 block
= create_block(int(tip
["hash"], 16), create_coinbase(tip
["height"] + 1), tip
["mediantime"] + 1)
269 test_node
.send_header_for_blocks([block
])
270 test_node
.clear_last_announcement()
271 test_node
.send_get_headers(locator
=[], hashstop
=int(block
.hash, 16))
272 test_node
.sync_with_ping()
273 assert_equal(test_node
.block_announced
, False)
274 inv_node
.clear_last_announcement()
275 test_node
.send_message(msg_block(block
))
276 inv_node
.check_last_announcement(inv
=[int(block
.hash, 16)], headers
=[])
278 def test_nonnull_locators(self
, test_node
, inv_node
):
279 tip
= int(self
.nodes
[0].getbestblockhash(), 16)
282 # 1. Mine a block; expect inv announcements each time
283 self
.log
.info("Part 1: headers don't start before sendheaders message...")
286 tip
= self
.mine_blocks(1)
287 inv_node
.check_last_announcement(inv
=[tip
], headers
=[])
288 test_node
.check_last_announcement(inv
=[tip
], headers
=[])
289 # Try a few different responses; none should affect next announcement
291 # first request the block
292 test_node
.send_get_data([tip
])
293 test_node
.wait_for_block(tip
)
295 # next try requesting header and block
296 test_node
.send_get_headers(locator
=[old_tip
], hashstop
=tip
)
297 test_node
.send_get_data([tip
])
298 test_node
.wait_for_block(tip
)
299 test_node
.clear_last_announcement() # since we requested headers...
301 # this time announce own block via headers
302 height
= self
.nodes
[0].getblockcount()
303 last_time
= self
.nodes
[0].getblock(self
.nodes
[0].getbestblockhash())['time']
304 block_time
= last_time
+ 1
305 new_block
= create_block(tip
, create_coinbase(height
+ 1), block_time
)
307 test_node
.send_header_for_blocks([new_block
])
308 test_node
.wait_for_getdata([new_block
.sha256
])
309 test_node
.send_message(msg_block(new_block
))
310 test_node
.sync_with_ping() # make sure this block is processed
311 inv_node
.clear_last_announcement()
312 test_node
.clear_last_announcement()
314 self
.log
.info("Part 1: success!")
315 self
.log
.info("Part 2: announce blocks with headers after sendheaders message...")
317 # 2. Send a sendheaders message and test that headers announcements
318 # commence and keep working.
319 test_node
.send_message(msg_sendheaders())
320 prev_tip
= int(self
.nodes
[0].getbestblockhash(), 16)
321 test_node
.send_get_headers(locator
=[prev_tip
], hashstop
=0)
322 test_node
.sync_with_ping()
324 # Now that we've synced headers, headers announcements should work
325 tip
= self
.mine_blocks(1)
326 inv_node
.check_last_announcement(inv
=[tip
], headers
=[])
327 test_node
.check_last_announcement(headers
=[tip
])
329 height
= self
.nodes
[0].getblockcount() + 1
330 block_time
+= 10 # Advance far enough ahead
332 # Mine i blocks, and alternate announcing either via
333 # inv (of tip) or via headers. After each, new blocks
334 # mined by the node should successfully be announced
335 # with block header, even though the blocks are never requested
338 for b
in range(i
+ 1):
339 blocks
.append(create_block(tip
, create_coinbase(height
), block_time
))
341 tip
= blocks
[-1].sha256
346 test_node
.send_block_inv(tip
)
347 test_node
.wait_for_getheaders()
348 # Should have received a getheaders now
349 test_node
.send_header_for_blocks(blocks
)
350 # Test that duplicate inv's won't result in duplicate
351 # getdata requests, or duplicate headers announcements
352 [inv_node
.send_block_inv(x
.sha256
) for x
in blocks
]
353 test_node
.wait_for_getdata([x
.sha256
for x
in blocks
])
354 inv_node
.sync_with_ping()
356 # Announce via headers
357 test_node
.send_header_for_blocks(blocks
)
358 test_node
.wait_for_getdata([x
.sha256
for x
in blocks
])
359 # Test that duplicate headers won't result in duplicate
360 # getdata requests (the check is further down)
361 inv_node
.send_header_for_blocks(blocks
)
362 inv_node
.sync_with_ping()
363 [test_node
.send_message(msg_block(x
)) for x
in blocks
]
364 test_node
.sync_with_ping()
365 inv_node
.sync_with_ping()
366 # This block should not be announced to the inv node (since it also
368 assert "inv" not in inv_node
.last_message
369 assert "headers" not in inv_node
.last_message
370 tip
= self
.mine_blocks(1)
371 inv_node
.check_last_announcement(inv
=[tip
], headers
=[])
372 test_node
.check_last_announcement(headers
=[tip
])
376 self
.log
.info("Part 2: success!")
378 self
.log
.info("Part 3: headers announcements can stop after large reorg, and resume after headers/inv from peer...")
380 # PART 3. Headers announcements can stop after large reorg, and resume after
381 # getheaders or inv from peer.
383 # First try mining a reorg that can propagate with header announcement
384 new_block_hashes
= self
.mine_reorg(length
=7)
385 tip
= new_block_hashes
[-1]
386 inv_node
.check_last_announcement(inv
=[tip
], headers
=[])
387 test_node
.check_last_announcement(headers
=new_block_hashes
)
391 # Mine a too-large reorg, which should be announced with a single inv
392 new_block_hashes
= self
.mine_reorg(length
=8)
393 tip
= new_block_hashes
[-1]
394 inv_node
.check_last_announcement(inv
=[tip
], headers
=[])
395 test_node
.check_last_announcement(inv
=[tip
], headers
=[])
399 fork_point
= self
.nodes
[0].getblock("%02x" % new_block_hashes
[0])["previousblockhash"]
400 fork_point
= int(fork_point
, 16)
402 # Use getblocks/getdata
403 test_node
.send_getblocks(locator
=[fork_point
])
404 test_node
.check_last_announcement(inv
=new_block_hashes
, headers
=[])
405 test_node
.send_get_data(new_block_hashes
)
406 test_node
.wait_for_block(new_block_hashes
[-1])
409 # Mine another block, still should get only an inv
410 tip
= self
.mine_blocks(1)
411 inv_node
.check_last_announcement(inv
=[tip
], headers
=[])
412 test_node
.check_last_announcement(inv
=[tip
], headers
=[])
414 # Just get the data -- shouldn't cause headers announcements to resume
415 test_node
.send_get_data([tip
])
416 test_node
.wait_for_block(tip
)
418 # Send a getheaders message that shouldn't trigger headers announcements
419 # to resume (best header sent will be too old)
420 test_node
.send_get_headers(locator
=[fork_point
], hashstop
=new_block_hashes
[1])
421 test_node
.send_get_data([tip
])
422 test_node
.wait_for_block(tip
)
424 test_node
.send_get_data([tip
])
425 test_node
.wait_for_block(tip
)
426 # This time, try sending either a getheaders to trigger resumption
427 # of headers announcements, or mine a new block and inv it, also
428 # triggering resumption of headers announcements.
430 test_node
.send_get_headers(locator
=[tip
], hashstop
=0)
431 test_node
.sync_with_ping()
433 test_node
.send_block_inv(tip
)
434 test_node
.sync_with_ping()
435 # New blocks should now be announced with header
436 tip
= self
.mine_blocks(1)
437 inv_node
.check_last_announcement(inv
=[tip
], headers
=[])
438 test_node
.check_last_announcement(headers
=[tip
])
440 self
.log
.info("Part 3: success!")
442 self
.log
.info("Part 4: Testing direct fetch behavior...")
443 tip
= self
.mine_blocks(1)
444 height
= self
.nodes
[0].getblockcount() + 1
445 last_time
= self
.nodes
[0].getblock(self
.nodes
[0].getbestblockhash())['time']
446 block_time
= last_time
+ 1
448 # Create 2 blocks. Send the blocks, then send the headers.
451 blocks
.append(create_block(tip
, create_coinbase(height
), block_time
))
453 tip
= blocks
[-1].sha256
456 inv_node
.send_message(msg_block(blocks
[-1]))
458 inv_node
.sync_with_ping() # Make sure blocks are processed
459 test_node
.last_message
.pop("getdata", None)
460 test_node
.send_header_for_blocks(blocks
)
461 test_node
.sync_with_ping()
462 # should not have received any getdata messages
464 assert "getdata" not in test_node
.last_message
466 # This time, direct fetch should work
469 blocks
.append(create_block(tip
, create_coinbase(height
), block_time
))
471 tip
= blocks
[-1].sha256
475 test_node
.send_header_for_blocks(blocks
)
476 test_node
.sync_with_ping()
477 test_node
.wait_for_getdata([x
.sha256
for x
in blocks
], timeout
=DIRECT_FETCH_RESPONSE_TIME
)
479 [test_node
.send_message(msg_block(x
)) for x
in blocks
]
481 test_node
.sync_with_ping()
483 # Now announce a header that forks the last two blocks
484 tip
= blocks
[0].sha256
488 # Create extra blocks for later
490 blocks
.append(create_block(tip
, create_coinbase(height
), block_time
))
492 tip
= blocks
[-1].sha256
496 # Announcing one block on fork should not trigger direct fetch
497 # (less work than tip)
498 test_node
.last_message
.pop("getdata", None)
499 test_node
.send_header_for_blocks(blocks
[0:1])
500 test_node
.sync_with_ping()
502 assert "getdata" not in test_node
.last_message
504 # Announcing one more block on fork should trigger direct fetch for
505 # both blocks (same work as tip)
506 test_node
.send_header_for_blocks(blocks
[1:2])
507 test_node
.sync_with_ping()
508 test_node
.wait_for_getdata([x
.sha256
for x
in blocks
[0:2]], timeout
=DIRECT_FETCH_RESPONSE_TIME
)
510 # Announcing 16 more headers should trigger direct fetch for 14 more
512 test_node
.send_header_for_blocks(blocks
[2:18])
513 test_node
.sync_with_ping()
514 test_node
.wait_for_getdata([x
.sha256
for x
in blocks
[2:16]], timeout
=DIRECT_FETCH_RESPONSE_TIME
)
516 # Announcing 1 more header should not trigger any response
517 test_node
.last_message
.pop("getdata", None)
518 test_node
.send_header_for_blocks(blocks
[18:19])
519 test_node
.sync_with_ping()
521 assert "getdata" not in test_node
.last_message
523 self
.log
.info("Part 4: success!")
525 # Now deliver all those blocks we announced.
526 [test_node
.send_message(msg_block(x
)) for x
in blocks
]
528 self
.log
.info("Part 5: Testing handling of unconnecting headers")
529 # First we test that receipt of an unconnecting header doesn't prevent
532 test_node
.last_message
.pop("getdata", None)
534 # Create two more blocks.
536 blocks
.append(create_block(tip
, create_coinbase(height
), block_time
))
538 tip
= blocks
[-1].sha256
541 # Send the header of the second block -> this won't connect.
543 test_node
.last_message
.pop("getheaders", None)
544 test_node
.send_header_for_blocks([blocks
[1]])
545 test_node
.wait_for_getheaders()
546 test_node
.send_header_for_blocks(blocks
)
547 test_node
.wait_for_getdata([x
.sha256
for x
in blocks
])
548 [test_node
.send_message(msg_block(x
)) for x
in blocks
]
549 test_node
.sync_with_ping()
550 assert_equal(int(self
.nodes
[0].getbestblockhash(), 16), blocks
[1].sha256
)
553 # Now we test that if we repeatedly don't send connecting headers, we
554 # don't go into an infinite loop trying to get them to connect.
555 MAX_UNCONNECTING_HEADERS
= 10
556 for j
in range(MAX_UNCONNECTING_HEADERS
+ 1):
557 blocks
.append(create_block(tip
, create_coinbase(height
), block_time
))
559 tip
= blocks
[-1].sha256
563 for i
in range(1, MAX_UNCONNECTING_HEADERS
):
564 # Send a header that doesn't connect, check that we get a getheaders.
566 test_node
.last_message
.pop("getheaders", None)
567 test_node
.send_header_for_blocks([blocks
[i
]])
568 test_node
.wait_for_getheaders()
570 # Next header will connect, should re-set our count:
571 test_node
.send_header_for_blocks([blocks
[0]])
573 # Remove the first two entries (blocks[1] would connect):
576 # Now try to see how many unconnecting headers we can send
577 # before we get disconnected. Should be 5*MAX_UNCONNECTING_HEADERS
578 for i
in range(5 * MAX_UNCONNECTING_HEADERS
- 1):
579 # Send a header that doesn't connect, check that we get a getheaders.
581 test_node
.last_message
.pop("getheaders", None)
582 test_node
.send_header_for_blocks([blocks
[i
% len(blocks
)]])
583 test_node
.wait_for_getheaders()
585 # Eventually this stops working.
586 test_node
.send_header_for_blocks([blocks
[-1]])
588 # Should get disconnected
589 test_node
.wait_for_disconnect()
591 self
.log
.info("Part 5: success!")
593 # Finally, check that the inv node never received a getdata request,
594 # throughout the test
595 assert "getdata" not in inv_node
.last_message
597 if __name__
== '__main__':
598 SendHeadersTest().main()