Dash Core  0.12.2.1
P2P Digital Currency
p2p-acceptblock.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 import time
11 from test_framework.blocktools import create_block, create_coinbase
12 
13 '''
14 AcceptBlockTest -- test processing of unrequested blocks.
15 
16 Since behavior differs when receiving unrequested blocks from whitelisted peers
17 versus non-whitelisted peers, this tests the behavior of both (effectively two
18 separate tests running in parallel).
19 
20 Setup: two nodes, node0 and node1, not connected to each other. Node0 does not
21 whitelist localhost, but node1 does. They will each be on their own chain for
22 this test.
23 
24 We have one NodeConn connection to each, test_node and white_node respectively.
25 
26 The test:
27 1. Generate one block on each node, to leave IBD.
28 
29 2. Mine a new block on each tip, and deliver to each node from node's peer.
30  The tip should advance.
31 
32 3. Mine a block that forks the previous block, and deliver to each node from
33  corresponding peer.
34  Node0 should not process this block (just accept the header), because it is
35  unrequested and doesn't have more work than the tip.
36  Node1 should process because this is coming from a whitelisted peer.
37 
38 4. Send another block that builds on the forking block.
39  Node0 should process this block but be stuck on the shorter chain, because
40  it's missing an intermediate block.
41  Node1 should reorg to this longer chain.
42 
43 4b.Send 288 more blocks on the longer chain.
44  Node0 should process all but the last block (too far ahead in height).
45  Send all headers to Node1, and then send the last block in that chain.
46  Node1 should accept the block because it's coming from a whitelisted peer.
47 
48 5. Send a duplicate of the block in #3 to Node0.
49  Node0 should not process the block because it is unrequested, and stay on
50  the shorter chain.
51 
52 6. Send Node0 an inv for the height 3 block produced in #4 above.
53  Node0 should figure out that Node0 has the missing height 2 block and send a
54  getdata.
55 
56 7. Send Node0 the missing block again.
57  Node0 should process and the tip should advance.
58 '''
59 
60 # TestNode: bare-bones "peer". Used mostly as a conduit for a test to sending
61 # p2p messages to a node, generating the messages in the main testing logic.
63  def __init__(self):
64  NodeConnCB.__init__(self)
65  self.connection = None
66  self.ping_counter = 1
68 
69  def add_connection(self, conn):
70  self.connection = conn
71 
72  # Track the last getdata message we receive (used in the test)
73  def on_getdata(self, conn, message):
74  self.last_getdata = message
75 
76  # Spin until verack message is received from the node.
77  # We use this to signal that our test can begin. This
78  # is called from the testing thread, so it needs to acquire
79  # the global lock.
80  def wait_for_verack(self):
81  while True:
82  with mininode_lock:
83  if self.verack_received:
84  return
85  time.sleep(0.05)
86 
87  # Wrapper for the NodeConn's send_message function
88  def send_message(self, message):
89  self.connection.send_message(message)
90 
91  def on_pong(self, conn, message):
92  self.last_pong = message
93 
94  # Sync up with the node after delivery of a block
95  def sync_with_ping(self, timeout=30):
97  received_pong = False
98  sleep_time = 0.05
99  while not received_pong and timeout > 0:
100  time.sleep(sleep_time)
101  timeout -= sleep_time
102  with mininode_lock:
103  if self.last_pong.nonce == self.ping_counter:
104  received_pong = True
105  self.ping_counter += 1
106  return received_pong
107 
108 
110  def add_options(self, parser):
111  parser.add_option("--testbinary", dest="testbinary",
112  default=os.getenv("DASHD", "dashd"),
113  help="bitcoind binary to test")
114 
115  def setup_chain(self):
116  initialize_chain_clean(self.options.tmpdir, 2)
117 
118  def setup_network(self):
119  # Node0 will be used to test behavior of processing unrequested blocks
120  # from peers which are not whitelisted, while Node1 will be used for
121  # the whitelisted case.
122  self.nodes = []
123  self.nodes.append(start_node(0, self.options.tmpdir, ["-debug"],
124  binary=self.options.testbinary))
125  self.nodes.append(start_node(1, self.options.tmpdir,
126  ["-debug", "-whitelist=127.0.0.1"],
127  binary=self.options.testbinary))
128 
129  def run_test(self):
130  # Setup the p2p connections and start up the network thread.
131  test_node = TestNode() # connects to node0 (not whitelisted)
132  white_node = TestNode() # connects to node1 (whitelisted)
133 
134  connections = []
135  connections.append(NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], test_node))
136  connections.append(NodeConn('127.0.0.1', p2p_port(1), self.nodes[1], white_node))
137  test_node.add_connection(connections[0])
138  white_node.add_connection(connections[1])
139 
140  NetworkThread().start() # Start up network handling in another thread
141 
142  # Test logic begins here
143  test_node.wait_for_verack()
144  white_node.wait_for_verack()
145 
146  # 1. Have both nodes mine a block (leave IBD)
147  [ n.generate(1) for n in self.nodes ]
148  tips = [ int ("0x" + n.getbestblockhash() + "L", 0) for n in self.nodes ]
149 
150  # 2. Send one block that builds on each tip.
151  # This should be accepted.
152  blocks_h2 = [] # the height 2 blocks on each node's chain
153  block_time = int(time.time()) + 1
154  for i in xrange(2):
155  blocks_h2.append(create_block(tips[i], create_coinbase(2), block_time))
156  blocks_h2[i].solve()
157  block_time += 1
158  test_node.send_message(msg_block(blocks_h2[0]))
159  white_node.send_message(msg_block(blocks_h2[1]))
160 
161  [ x.sync_with_ping() for x in [test_node, white_node] ]
162  assert_equal(self.nodes[0].getblockcount(), 2)
163  assert_equal(self.nodes[1].getblockcount(), 2)
164  print "First height 2 block accepted by both nodes"
165 
166  # 3. Send another block that builds on the original tip.
167  blocks_h2f = [] # Blocks at height 2 that fork off the main chain
168  for i in xrange(2):
169  blocks_h2f.append(create_block(tips[i], create_coinbase(2), blocks_h2[i].nTime+1))
170  blocks_h2f[i].solve()
171  test_node.send_message(msg_block(blocks_h2f[0]))
172  white_node.send_message(msg_block(blocks_h2f[1]))
173 
174  [ x.sync_with_ping() for x in [test_node, white_node] ]
175  for x in self.nodes[0].getchaintips():
176  if x['hash'] == blocks_h2f[0].hash:
177  assert_equal(x['status'], "headers-only")
178 
179  for x in self.nodes[1].getchaintips():
180  if x['hash'] == blocks_h2f[1].hash:
181  assert_equal(x['status'], "valid-headers")
182 
183  print "Second height 2 block accepted only from whitelisted peer"
184 
185  # 4. Now send another block that builds on the forking chain.
186  blocks_h3 = []
187  for i in xrange(2):
188  blocks_h3.append(create_block(blocks_h2f[i].sha256, create_coinbase(3), blocks_h2f[i].nTime+1))
189  blocks_h3[i].solve()
190  test_node.send_message(msg_block(blocks_h3[0]))
191  white_node.send_message(msg_block(blocks_h3[1]))
192 
193  [ x.sync_with_ping() for x in [test_node, white_node] ]
194  # Since the earlier block was not processed by node0, the new block
195  # can't be fully validated.
196  for x in self.nodes[0].getchaintips():
197  if x['hash'] == blocks_h3[0].hash:
198  assert_equal(x['status'], "headers-only")
199 
200  # But this block should be accepted by node0 since it has more work.
201  try:
202  self.nodes[0].getblock(blocks_h3[0].hash)
203  print "Unrequested more-work block accepted from non-whitelisted peer"
204  except:
205  raise AssertionError("Unrequested more work block was not processed")
206 
207  # Node1 should have accepted and reorged.
208  assert_equal(self.nodes[1].getblockcount(), 3)
209  print "Successfully reorged to length 3 chain from whitelisted peer"
210 
211  # 4b. Now mine 288 more blocks and deliver; all should be processed but
212  # the last (height-too-high) on node0. Node1 should process the tip if
213  # we give it the headers chain leading to the tip.
214  tips = blocks_h3
215  headers_message = msg_headers()
216  all_blocks = [] # node0's blocks
217  for j in xrange(2):
218  for i in xrange(288):
219  next_block = create_block(tips[j].sha256, create_coinbase(i + 4), tips[j].nTime+1)
220  next_block.solve()
221  if j==0:
222  test_node.send_message(msg_block(next_block))
223  all_blocks.append(next_block)
224  else:
225  headers_message.headers.append(CBlockHeader(next_block))
226  tips[j] = next_block
227 
228  time.sleep(2)
229  for x in all_blocks:
230  try:
231  self.nodes[0].getblock(x.hash)
232  if x == all_blocks[287]:
233  raise AssertionError("Unrequested block too far-ahead should have been ignored")
234  except:
235  if x == all_blocks[287]:
236  print "Unrequested block too far-ahead not processed"
237  else:
238  raise AssertionError("Unrequested block with more work should have been accepted")
239 
240  headers_message.headers.pop() # Ensure the last block is unrequested
241  white_node.send_message(headers_message) # Send headers leading to tip
242  white_node.send_message(msg_block(tips[1])) # Now deliver the tip
243  try:
244  white_node.sync_with_ping()
245  self.nodes[1].getblock(tips[1].hash)
246  print "Unrequested block far ahead of tip accepted from whitelisted peer"
247  except:
248  raise AssertionError("Unrequested block from whitelisted peer not accepted")
249 
250  # 5. Test handling of unrequested block on the node that didn't process
251  # Should still not be processed (even though it has a child that has more
252  # work).
253  test_node.send_message(msg_block(blocks_h2f[0]))
254 
255  # Here, if the sleep is too short, the test could falsely succeed (if the
256  # node hasn't processed the block by the time the sleep returns, and then
257  # the node processes it and incorrectly advances the tip).
258  # But this would be caught later on, when we verify that an inv triggers
259  # a getdata request for this block.
260  test_node.sync_with_ping()
261  assert_equal(self.nodes[0].getblockcount(), 2)
262  print "Unrequested block that would complete more-work chain was ignored"
263 
264  # 6. Try to get node to request the missing block.
265  # Poke the node with an inv for block at height 3 and see if that
266  # triggers a getdata on block 2 (it should if block 2 is missing).
267  with mininode_lock:
268  # Clear state so we can check the getdata request
269  test_node.last_getdata = None
270  test_node.send_message(msg_inv([CInv(2, blocks_h3[0].sha256)]))
271 
272  test_node.sync_with_ping()
273  with mininode_lock:
274  getdata = test_node.last_getdata
275 
276  # Check that the getdata includes the right block
277  assert_equal(getdata.inv[0].hash, blocks_h2f[0].sha256)
278  print "Inv at tip triggered getdata for unprocessed block"
279 
280  # 7. Send the missing block for the third time (now it is requested)
281  test_node.send_message(msg_block(blocks_h2f[0]))
282 
283  test_node.sync_with_ping()
284  assert_equal(self.nodes[0].getblockcount(), 290)
285  print "Successfully reorged to longer chain from non-whitelisted peer"
286 
287  [ c.disconnect_node() for c in connections ]
288 
289 if __name__ == '__main__':
def create_block(hashprev, coinbase, nTime=None)
Definition: blocktools.py:11
def send_message(self, message)
def on_getdata(self, conn, message)
UniValue getblock(const UniValue &params, bool fHelp)
Definition: blockchain.cpp:483
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 sync_with_ping(self, timeout=30)
def start_node(i, dirname, extra_args=None, rpchost=None, timewait=None, binary=None)
Definition: util.py:281
def on_pong(self, conn, message)
def add_connection(self, conn)
def p2p_port(n)
Definition: util.py:93
def assert_equal(thing1, thing2)
Definition: util.py:461