Dash Core  0.12.2.1
P2P Digital Currency
sendheaders.py
Go to the documentation of this file.
1 #!/usr/bin/env python2
2 #
3 # Distributed under the MIT/X11 software license, see the accompanying
4 # file COPYING or http://www.opensource.org/licenses/mit-license.php.
5 #
6 
7 from test_framework.mininode import *
8 from test_framework.test_framework import BitcoinTestFramework
9 from test_framework.util import *
10 from test_framework.blocktools import create_block, create_coinbase
11 
12 '''
13 SendHeadersTest -- test behavior of headers messages to announce blocks.
14 
15 Setup:
16 
17 - Two nodes, two p2p connections to node0. One p2p connection should only ever
18  receive inv's (omitted from testing description below, this is our control).
19  Second node is used for creating reorgs.
20 
21 Part 1: No headers announcements before "sendheaders"
22 a. node mines a block [expect: inv]
23  send getdata for the block [expect: block]
24 b. node mines another block [expect: inv]
25  send getheaders and getdata [expect: headers, then block]
26 c. node mines another block [expect: inv]
27  peer mines a block, announces with header [expect: getdata]
28 d. node mines another block [expect: inv]
29 
30 Part 2: After "sendheaders", headers announcements should generally work.
31 a. peer sends sendheaders [expect: no response]
32  peer sends getheaders with current tip [expect: no response]
33 b. node mines a block [expect: tip header]
34 c. for N in 1, ..., 10:
35  * for announce-type in {inv, header}
36  - peer mines N blocks, announces with announce-type
37  [ expect: getheaders/getdata or getdata, deliver block(s) ]
38  - node mines a block [ expect: 1 header ]
39 
40 Part 3: Headers announcements stop after large reorg and resume after getheaders or inv from peer.
41 - For response-type in {inv, getheaders}
42  * node mines a 7 block reorg [ expect: headers announcement of 8 blocks ]
43  * node mines an 8-block reorg [ expect: inv at tip ]
44  * peer responds with getblocks/getdata [expect: inv, blocks ]
45  * node mines another block [ expect: inv at tip, peer sends getdata, expect: block ]
46  * node mines another block at tip [ expect: inv ]
47  * peer responds with getheaders with an old hashstop more than 8 blocks back [expect: headers]
48  * peer requests block [ expect: block ]
49  * node mines another block at tip [ expect: inv, peer sends getdata, expect: block ]
50  * peer sends response-type [expect headers if getheaders, getheaders/getdata if mining new block]
51  * node mines 1 block [expect: 1 header, peer responds with getdata]
52 
53 Part 4: Test direct fetch behavior
54 a. Announce 2 old block headers.
55  Expect: no getdata requests.
56 b. Announce 3 new blocks via 1 headers message.
57  Expect: one getdata request for all 3 blocks.
58  (Send blocks.)
59 c. Announce 1 header that forks off the last two blocks.
60  Expect: no response.
61 d. Announce 1 more header that builds on that fork.
62  Expect: one getdata request for two blocks.
63 e. Announce 16 more headers that build on that fork.
64  Expect: getdata request for 14 more blocks.
65 f. Announce 1 more header that builds on that fork.
66  Expect: no response.
67 '''
68 
70  def __init__(self):
71  NodeConnCB.__init__(self)
72  self.connection = None
73  self.last_inv = None
74  self.last_headers = None
75  self.last_block = None
76  self.ping_counter = 1
77  self.last_pong = msg_pong(0)
78  self.last_getdata = None
79  self.sleep_time = 0.05
80  self.block_announced = False
81 
83  with mininode_lock:
84  self.block_announced = False
85  self.last_inv = None
86  self.last_headers = None
87 
88  def add_connection(self, conn):
89  self.connection = conn
90 
91  # Request data for a list of block hashes
92  def get_data(self, block_hashes):
93  msg = msg_getdata()
94  for x in block_hashes:
95  msg.inv.append(CInv(2, x))
96  self.connection.send_message(msg)
97 
98  def get_headers(self, locator, hashstop):
99  msg = msg_getheaders()
100  msg.locator.vHave = locator
101  msg.hashstop = hashstop
102  self.connection.send_message(msg)
103 
104  def send_block_inv(self, blockhash):
105  msg = msg_inv()
106  msg.inv = [CInv(2, blockhash)]
107  self.connection.send_message(msg)
108 
109  # Wrapper for the NodeConn's send_message function
110  def send_message(self, message):
111  self.connection.send_message(message)
112 
113  def on_inv(self, conn, message):
114  self.last_inv = message
115  self.block_announced = True
116 
117  def on_headers(self, conn, message):
118  self.last_headers = message
119  self.block_announced = True
120 
121  def on_block(self, conn, message):
122  self.last_block = message.block
123  self.last_block.calc_sha256()
124 
125  def on_getdata(self, conn, message):
126  self.last_getdata = message
127 
128  def on_pong(self, conn, message):
129  self.last_pong = message
130 
131  # Test whether the last announcement we received had the
132  # right header or the right inv
133  # inv and headers should be lists of block hashes
134  def check_last_announcement(self, headers=None, inv=None):
135  expect_headers = headers if headers != None else []
136  expect_inv = inv if inv != None else []
137  test_function = lambda: self.block_announced
138  self.sync(test_function)
139  with mininode_lock:
140  self.block_announced = False
141 
142  success = True
143  compare_inv = []
144  if self.last_inv != None:
145  compare_inv = [x.hash for x in self.last_inv.inv]
146  if compare_inv != expect_inv:
147  success = False
148 
149  hash_headers = []
150  if self.last_headers != None:
151  # treat headers as a list of block hashes
152  hash_headers = [ x.sha256 for x in self.last_headers.headers ]
153  if hash_headers != expect_headers:
154  success = False
155 
156  self.last_inv = None
157  self.last_headers = None
158  return success
159 
160  # Syncing helpers
161  def sync(self, test_function, timeout=60):
162  while timeout > 0:
163  with mininode_lock:
164  if test_function():
165  return
166  time.sleep(self.sleep_time)
167  timeout -= self.sleep_time
168  raise AssertionError("Sync failed to complete")
169 
170  def sync_with_ping(self, timeout=60):
171  self.send_message(msg_ping(nonce=self.ping_counter))
172  test_function = lambda: self.last_pong.nonce == self.ping_counter
173  self.sync(test_function, timeout)
174  self.ping_counter += 1
175  return
176 
177  def wait_for_block(self, blockhash, timeout=60):
178  test_function = lambda: self.last_block != None and self.last_block.sha256 == blockhash
179  self.sync(test_function, timeout)
180  return
181 
182  def wait_for_getdata(self, hash_list, timeout=60):
183  if hash_list == []:
184  return
185 
186  test_function = lambda: self.last_getdata != None and [x.hash for x in self.last_getdata.inv] == hash_list
187  self.sync(test_function, timeout)
188  return
189 
190  def send_header_for_blocks(self, new_blocks):
191  headers_message = msg_headers()
192  headers_message.headers = [ CBlockHeader(b) for b in new_blocks ]
193  self.send_message(headers_message)
194 
195  def send_getblocks(self, locator):
196  getblocks_message = msg_getblocks()
197  getblocks_message.locator.vHave = locator
198  self.send_message(getblocks_message)
199 
200 # InvNode: This peer should only ever receive inv's, because it doesn't ever send a
201 # "sendheaders" message.
203  def __init__(self):
204  BaseNode.__init__(self)
205 
206 # TestNode: This peer is the one we use for most of the testing.
208  def __init__(self):
209  BaseNode.__init__(self)
210 
212  def setup_chain(self):
213  initialize_chain_clean(self.options.tmpdir, 2)
214 
215  def setup_network(self):
216  self.nodes = []
217  self.nodes = start_nodes(2, self.options.tmpdir, [["-debug", "-logtimemicros=1"]]*2)
218  connect_nodes(self.nodes[0], 1)
219 
220  # mine count blocks and return the new tip
221  def mine_blocks(self, count):
222  # Clear out last block announcement from each p2p listener
223  [ x.clear_last_announcement() for x in self.p2p_connections ]
224  self.nodes[0].generate(count)
225  return int(self.nodes[0].getbestblockhash(), 16)
226 
227  # mine a reorg that invalidates length blocks (replacing them with
228  # length+1 blocks).
229  # Note: we clear the state of our p2p connections after the
230  # to-be-reorged-out blocks are mined, so that we don't break later tests.
231  # return the list of block hashes newly mined
232  def mine_reorg(self, length):
233  self.nodes[0].generate(length) # make sure all invalidated blocks are node0's
234  sync_blocks(self.nodes, wait=0.1)
235  [x.clear_last_announcement() for x in self.p2p_connections]
236 
237  tip_height = self.nodes[1].getblockcount()
238  hash_to_invalidate = self.nodes[1].getblockhash(tip_height-(length-1))
239  self.nodes[1].invalidateblock(hash_to_invalidate)
240  all_hashes = self.nodes[1].generate(length+1) # Must be longer than the orig chain
241  sync_blocks(self.nodes, wait=0.1)
242  return [int(x, 16) for x in all_hashes]
243 
244  def run_test(self):
245  # Setup the p2p connections and start up the network thread.
246  inv_node = InvNode()
247  test_node = TestNode()
248 
249  self.p2p_connections = [inv_node, test_node]
250 
251  connections = []
252  connections.append(NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], inv_node))
253  # Set nServices to 0 for test_node, so no block download will occur outside of
254  # direct fetching
255  connections.append(NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], test_node, services=0))
256  inv_node.add_connection(connections[0])
257  test_node.add_connection(connections[1])
258 
259  NetworkThread().start() # Start up network handling in another thread
260 
261  # Test logic begins here
262  inv_node.wait_for_verack()
263  test_node.wait_for_verack()
264 
265  tip = int(self.nodes[0].getbestblockhash(), 16)
266 
267  # PART 1
268  # 1. Mine a block; expect inv announcements each time
269  print "Part 1: headers don't start before sendheaders message..."
270  for i in xrange(4):
271  old_tip = tip
272  tip = self.mine_blocks(1)
273  assert_equal(inv_node.check_last_announcement(inv=[tip]), True)
274  assert_equal(test_node.check_last_announcement(inv=[tip]), True)
275  # Try a few different responses; none should affect next announcement
276  if i == 0:
277  # first request the block
278  test_node.get_data([tip])
279  test_node.wait_for_block(tip, timeout=5)
280  elif i == 1:
281  # next try requesting header and block
282  test_node.get_headers(locator=[old_tip], hashstop=tip)
283  test_node.get_data([tip])
284  test_node.wait_for_block(tip)
285  test_node.clear_last_announcement() # since we requested headers...
286  elif i == 2:
287  # this time announce own block via headers
288  height = self.nodes[0].getblockcount()
289  last_time = self.nodes[0].getblock(self.nodes[0].getbestblockhash())['time']
290  block_time = last_time + 1
291  new_block = create_block(tip, create_coinbase(height+1), block_time)
292  new_block.solve()
293  test_node.send_header_for_blocks([new_block])
294  test_node.wait_for_getdata([new_block.sha256], timeout=5)
295  test_node.send_message(msg_block(new_block))
296  test_node.sync_with_ping() # make sure this block is processed
297  inv_node.clear_last_announcement()
298  test_node.clear_last_announcement()
299 
300  print "Part 1: success!"
301  print "Part 2: announce blocks with headers after sendheaders message..."
302  # PART 2
303  # 2. Send a sendheaders message and test that headers announcements
304  # commence and keep working.
305  test_node.send_message(msg_sendheaders())
306  prev_tip = int(self.nodes[0].getbestblockhash(), 16)
307  test_node.get_headers(locator=[prev_tip], hashstop=0L)
308  test_node.sync_with_ping()
309 
310  # Now that we've synced headers, headers announcements should work
311  tip = self.mine_blocks(1)
312  assert_equal(inv_node.check_last_announcement(inv=[tip]), True)
313  assert_equal(test_node.check_last_announcement(headers=[tip]), True)
314 
315  height = self.nodes[0].getblockcount()+1
316  block_time += 10 # Advance far enough ahead
317  for i in xrange(10):
318  # Mine i blocks, and alternate announcing either via
319  # inv (of tip) or via headers. After each, new blocks
320  # mined by the node should successfully be announced
321  # with block header, even though the blocks are never requested
322  for j in xrange(2):
323  blocks = []
324  for b in xrange(i+1):
325  blocks.append(create_block(tip, create_coinbase(height), block_time))
326  blocks[-1].solve()
327  tip = blocks[-1].sha256
328  block_time += 1
329  height += 1
330  if j == 0:
331  # Announce via inv
332  test_node.send_block_inv(tip)
333  test_node.wait_for_getdata([tip], timeout=5)
334  # Test that duplicate inv's won't result in duplicate
335  # getdata requests, or duplicate headers announcements
336  inv_node.send_block_inv(tip)
337  # Should have received a getheaders as well!
338  test_node.send_header_for_blocks(blocks)
339  test_node.wait_for_getdata([x.sha256 for x in blocks[0:-1]], timeout=5)
340  [ inv_node.send_block_inv(x.sha256) for x in blocks[0:-1] ]
341  inv_node.sync_with_ping()
342  else:
343  # Announce via headers
344  test_node.send_header_for_blocks(blocks)
345  test_node.wait_for_getdata([x.sha256 for x in blocks], timeout=5)
346  # Test that duplicate headers won't result in duplicate
347  # getdata requests (the check is further down)
348  inv_node.send_header_for_blocks(blocks)
349  inv_node.sync_with_ping()
350  [ test_node.send_message(msg_block(x)) for x in blocks ]
351  test_node.sync_with_ping()
352  inv_node.sync_with_ping()
353  # This block should not be announced to the inv node (since it also
354  # broadcast it)
355  assert_equal(inv_node.last_inv, None)
356  assert_equal(inv_node.last_headers, None)
357  tip = self.mine_blocks(1)
358  assert_equal(inv_node.check_last_announcement(inv=[tip]), True)
359  assert_equal(test_node.check_last_announcement(headers=[tip]), True)
360  height += 1
361  block_time += 1
362 
363  print "Part 2: success!"
364 
365  print "Part 3: headers announcements can stop after large reorg, and resume after headers/inv from peer..."
366 
367  # PART 3. Headers announcements can stop after large reorg, and resume after
368  # getheaders or inv from peer.
369  for j in xrange(2):
370  # First try mining a reorg that can propagate with header announcement
371  new_block_hashes = self.mine_reorg(length=7)
372  tip = new_block_hashes[-1]
373  assert_equal(inv_node.check_last_announcement(inv=[tip]), True)
374  assert_equal(test_node.check_last_announcement(headers=new_block_hashes), True)
375 
376  block_time += 8
377 
378  # Mine a too-large reorg, which should be announced with a single inv
379  new_block_hashes = self.mine_reorg(length=8)
380  tip = new_block_hashes[-1]
381  assert_equal(inv_node.check_last_announcement(inv=[tip]), True)
382  assert_equal(test_node.check_last_announcement(inv=[tip]), True)
383 
384  block_time += 9
385 
386  fork_point = self.nodes[0].getblock("%02x" % new_block_hashes[0])["previousblockhash"]
387  fork_point = int(fork_point, 16)
388 
389  # Use getblocks/getdata
390  test_node.send_getblocks(locator = [fork_point])
391  assert_equal(test_node.check_last_announcement(inv=new_block_hashes), True)
392  test_node.get_data(new_block_hashes)
393  test_node.wait_for_block(new_block_hashes[-1])
394 
395  for i in xrange(3):
396  # Mine another block, still should get only an inv
397  tip = self.mine_blocks(1)
398  assert_equal(inv_node.check_last_announcement(inv=[tip]), True)
399  assert_equal(test_node.check_last_announcement(inv=[tip]), True)
400  if i == 0:
401  # Just get the data -- shouldn't cause headers announcements to resume
402  test_node.get_data([tip])
403  test_node.wait_for_block(tip)
404  elif i == 1:
405  # Send a getheaders message that shouldn't trigger headers announcements
406  # to resume (best header sent will be too old)
407  test_node.get_headers(locator=[fork_point], hashstop=new_block_hashes[1])
408  test_node.get_data([tip])
409  test_node.wait_for_block(tip)
410  elif i == 2:
411  test_node.get_data([tip])
412  test_node.wait_for_block(tip)
413  # This time, try sending either a getheaders to trigger resumption
414  # of headers announcements, or mine a new block and inv it, also
415  # triggering resumption of headers announcements.
416  if j == 0:
417  test_node.get_headers(locator=[tip], hashstop=0L)
418  test_node.sync_with_ping()
419  else:
420  test_node.send_block_inv(tip)
421  test_node.sync_with_ping()
422  # New blocks should now be announced with header
423  tip = self.mine_blocks(1)
424  assert_equal(inv_node.check_last_announcement(inv=[tip]), True)
425  assert_equal(test_node.check_last_announcement(headers=[tip]), True)
426 
427  print "Part 3: success!"
428 
429  print "Part 4: Testing direct fetch behavior..."
430  tip = self.mine_blocks(1)
431  height = self.nodes[0].getblockcount() + 1
432  last_time = self.nodes[0].getblock(self.nodes[0].getbestblockhash())['time']
433  block_time = last_time + 1
434 
435  # Create 2 blocks. Send the blocks, then send the headers.
436  blocks = []
437  for b in xrange(2):
438  blocks.append(create_block(tip, create_coinbase(height), block_time))
439  blocks[-1].solve()
440  tip = blocks[-1].sha256
441  block_time += 1
442  height += 1
443  inv_node.send_message(msg_block(blocks[-1]))
444 
445  inv_node.sync_with_ping() # Make sure blocks are processed
446  test_node.last_getdata = None
447  test_node.send_header_for_blocks(blocks)
448  test_node.sync_with_ping()
449  # should not have received any getdata messages
450  with mininode_lock:
451  assert_equal(test_node.last_getdata, None)
452 
453  # This time, direct fetch should work
454  blocks = []
455  for b in xrange(3):
456  blocks.append(create_block(tip, create_coinbase(height), block_time))
457  blocks[-1].solve()
458  tip = blocks[-1].sha256
459  block_time += 1
460  height += 1
461 
462  test_node.send_header_for_blocks(blocks)
463  test_node.sync_with_ping()
464  test_node.wait_for_getdata([x.sha256 for x in blocks], timeout=test_node.sleep_time)
465 
466  [ test_node.send_message(msg_block(x)) for x in blocks ]
467 
468  test_node.sync_with_ping()
469 
470  # Now announce a header that forks the last two blocks
471  tip = blocks[0].sha256
472  height -= 1
473  blocks = []
474 
475  # Create extra blocks for later
476  for b in xrange(20):
477  blocks.append(create_block(tip, create_coinbase(height), block_time))
478  blocks[-1].solve()
479  tip = blocks[-1].sha256
480  block_time += 1
481  height += 1
482 
483  # Announcing one block on fork should not trigger direct fetch
484  # (less work than tip)
485  test_node.last_getdata = None
486  test_node.send_header_for_blocks(blocks[0:1])
487  test_node.sync_with_ping()
488  with mininode_lock:
489  assert_equal(test_node.last_getdata, None)
490 
491  # Announcing one more block on fork should trigger direct fetch for
492  # both blocks (same work as tip)
493  test_node.send_header_for_blocks(blocks[1:2])
494  test_node.sync_with_ping()
495  test_node.wait_for_getdata([x.sha256 for x in blocks[0:2]], timeout=test_node.sleep_time)
496 
497  # Announcing 16 more headers should trigger direct fetch for 14 more
498  # blocks
499  test_node.send_header_for_blocks(blocks[2:18])
500  test_node.sync_with_ping()
501  test_node.wait_for_getdata([x.sha256 for x in blocks[2:16]], timeout=test_node.sleep_time)
502 
503  # Announcing 1 more header should not trigger any response
504  test_node.last_getdata = None
505  test_node.send_header_for_blocks(blocks[18:19])
506  test_node.sync_with_ping()
507  with mininode_lock:
508  assert_equal(test_node.last_getdata, None)
509 
510  print "Part 4: success!"
511 
512  # Finally, check that the inv node never received a getdata request,
513  # throughout the test
514  assert_equal(inv_node.last_getdata, None)
515 
516 if __name__ == '__main__':
def create_block(hashprev, coinbase, nTime=None)
Definition: blocktools.py:11
def sync(self, test_function, timeout=60)
Definition: sendheaders.py:161
def send_header_for_blocks(self, new_blocks)
Definition: sendheaders.py:190
def start_nodes(num_nodes, dirname, extra_args=None, rpchost=None, binary=None)
Definition: util.py:305
def send_block_inv(self, blockhash)
Definition: sendheaders.py:104
UniValue getblock(const UniValue &params, bool fHelp)
Definition: blockchain.cpp:483
def on_headers(self, conn, message)
Definition: sendheaders.py:117
def connect_nodes(from_connection, node_num)
Definition: util.py:343
def send_message(self, message)
Definition: sendheaders.py:110
def wait_for_getdata(self, hash_list, timeout=60)
Definition: sendheaders.py:182
UniValue getblockhash(const UniValue &params, bool fHelp)
Definition: blockchain.cpp:311
def create_coinbase(height, pubkey=None)
Definition: blocktools.py:43
UniValue getblockcount(const UniValue &params, bool fHelp)
Definition: blockchain.cpp:131
def initialize_chain_clean(test_dir, num_nodes)
Definition: util.py:252
def get_headers(self, locator, hashstop)
Definition: sendheaders.py:98
def on_pong(self, conn, message)
Definition: sendheaders.py:128
def mine_blocks(self, count)
Definition: sendheaders.py:221
def on_getdata(self, conn, message)
Definition: sendheaders.py:125
UniValue generate(const UniValue &params, bool fHelp)
Definition: mining.cpp:122
def sync_blocks(rpc_connections, wait=1)
Definition: util.py:117
def add_connection(self, conn)
Definition: sendheaders.py:88
def get_data(self, block_hashes)
Definition: sendheaders.py:92
def send_getblocks(self, locator)
Definition: sendheaders.py:195
def clear_last_announcement(self)
Definition: sendheaders.py:82
UniValue getbestblockhash(const UniValue &params, bool fHelp)
Definition: blockchain.cpp:148
def on_block(self, conn, message)
Definition: sendheaders.py:121
def check_last_announcement(self, headers=None, inv=None)
Definition: sendheaders.py:134
def wait_for_block(self, blockhash, timeout=60)
Definition: sendheaders.py:177
def p2p_port(n)
Definition: util.py:93
def assert_equal(thing1, thing2)
Definition: util.py:461
def on_inv(self, conn, message)
Definition: sendheaders.py:113
def mine_reorg(self, length)
Definition: sendheaders.py:232
def sync_with_ping(self, timeout=60)
Definition: sendheaders.py:170