Changeset View
Changeset View
Standalone View
Standalone View
test/functional/p2p-acceptblock.py
#!/usr/bin/env python3 | #!/usr/bin/env python3 | ||||
# Copyright (c) 2015-2016 The Bitcoin Core developers | # Copyright (c) 2015-2016 The Bitcoin Core developers | ||||
# Distributed under the MIT software license, see the accompanying | # Distributed under the MIT software license, see the accompanying | ||||
# file COPYING or http://www.opensource.org/licenses/mit-license.php. | # file COPYING or http://www.opensource.org/licenses/mit-license.php. | ||||
from test_framework.mininode import * | from test_framework.mininode import * | ||||
from test_framework.test_framework import BitcoinTestFramework | from test_framework.test_framework import BitcoinTestFramework | ||||
from test_framework.util import * | from test_framework.util import * | ||||
import time | import time | ||||
from test_framework.blocktools import create_block, create_coinbase | from test_framework.blocktools import create_block, create_coinbase, create_transaction | ||||
''' | ''' | ||||
AcceptBlockTest -- test processing of unrequested blocks. | AcceptBlockTest -- test processing of unrequested blocks. | ||||
Since behavior differs when receiving unrequested blocks from whitelisted peers | Setup: two nodes, node0+node1, not connected to each other. Node1 will have | ||||
versus non-whitelisted peers, this tests the behavior of both (effectively two | nMinimumChainWork set to 0x10, so it won't process low-work unrequested blocks. | ||||
separate tests running in parallel). | |||||
Setup: three nodes, node0+node1+node2, not connected to each other. Node0 does not | |||||
whitelist localhost, but node1 does. They will each be on their own chain for | |||||
this test. Node2 will have nMinimumChainWork set to 0x10, so it won't process | |||||
low-work unrequested blocks. | |||||
We have one NodeConn connection to each, test_node, white_node, and min_work_node, | We have one NodeConn connection to node0 called test_node, and one to node1 | ||||
respectively. | called min_work_node. | ||||
The test: | The test: | ||||
1. Generate one block on each node, to leave IBD. | 1. Generate one block on each node, to leave IBD. | ||||
2. Mine a new block on each tip, and deliver to each node from node's peer. | 2. Mine a new block on each tip, and deliver to each node from node's peer. | ||||
The tip should advance for node0 and node1, but node2 should skip processing | The tip should advance for node0, but node1 should skip processing due to | ||||
due to nMinimumChainWork. | nMinimumChainWork. | ||||
Node2 is unused in tests 3-7: | Node1 is unused in tests 3-7: | ||||
3. Mine a block that forks the previous block, and deliver to each node from | 3. Mine a block that forks from the genesis block, and deliver to test_node. | ||||
corresponding peer. | Node0 should not process this block (just accept the header), because it | ||||
Node0 should not process this block (just accept the header), because it is | is unrequested and doesn't have more or equal work to the tip. | ||||
unrequested and doesn't have more work than the tip. | |||||
Node1 should process because this is coming from a whitelisted peer. | |||||
4. Send another block that builds on the forking block. | |||||
Node0 should process this block but be stuck on the shorter chain, because | |||||
it's missing an intermediate block. | |||||
Node1 should reorg to this longer chain. | |||||
4b.Send 288 more blocks on the longer chain. | 4a,b. Send another two blocks that build on the forking block. | ||||
Node0 should process the second block but be stuck on the shorter chain, | |||||
because it's missing an intermediate block. | |||||
4c.Send 288 more blocks on the longer chain (the number of blocks ahead | |||||
we currently store). | |||||
Node0 should process all but the last block (too far ahead in height). | Node0 should process all but the last block (too far ahead in height). | ||||
Send all headers to Node1, and then send the last block in that chain. | |||||
Node1 should accept the block because it's coming from a whitelisted peer. | |||||
5. Send a duplicate of the block in #3 to Node0. | 5. Send a duplicate of the block in #3 to Node0. | ||||
Node0 should not process the block because it is unrequested, and stay on | Node0 should not process the block because it is unrequested, and stay on | ||||
the shorter chain. | the shorter chain. | ||||
6. Send Node0 an inv for the height 3 block produced in #4 above. | 6. Send Node0 an inv for the height 3 block produced in #4 above. | ||||
Node0 should figure out that Node0 has the missing height 2 block and send a | Node0 should figure out that Node0 has the missing height 2 block and send a | ||||
getdata. | getdata. | ||||
7. Send Node0 the missing block again. | 7. Send Node0 the missing block again. | ||||
Node0 should process and the tip should advance. | Node0 should process and the tip should advance. | ||||
8. Test Node2 is able to sync when connected to node0 (which should have sufficient | 8. Create a fork which is invalid at a height longer than the current chain | ||||
work on its chain). | (ie to which the node will try to reorg) but which has headers built on top | ||||
of the invalid block. Check that we get disconnected if we send more headers | |||||
on the chain the node now knows to be invalid. | |||||
9. Test Node1 is able to sync when connected to node0 (which should have sufficient | |||||
work on its chain). | |||||
''' | ''' | ||||
class AcceptBlockTest(BitcoinTestFramework): | class AcceptBlockTest(BitcoinTestFramework): | ||||
def add_options(self, parser): | def add_options(self, parser): | ||||
parser.add_option("--testbinary", dest="testbinary", | parser.add_option("--testbinary", dest="testbinary", | ||||
default=os.getenv("BITCOIND", "bitcoind"), | default=os.getenv("BITCOIND", "bitcoind"), | ||||
help="bitcoind binary to test") | help="bitcoind binary to test") | ||||
def set_test_params(self): | def set_test_params(self): | ||||
self.setup_clean_chain = True | self.setup_clean_chain = True | ||||
self.num_nodes = 3 | self.num_nodes = 2 | ||||
self.extra_args = [[], ["-whitelist=127.0.0.1"], | self.extra_args = [[], ["-minimumchainwork=0x10"]] | ||||
["-minimumchainwork=0x10"]] | |||||
def setup_network(self): | def setup_network(self): | ||||
# Node0 will be used to test behavior of processing unrequested blocks | # Node0 will be used to test behavior of processing unrequested blocks | ||||
# from peers which are not whitelisted, while Node1 will be used for | # from peers which are not whitelisted, while Node1 will be used for | ||||
# the whitelisted case. | # the whitelisted case. | ||||
# Node2 will be used for non-whitelisted peers to test the interaction | # Node2 will be used for non-whitelisted peers to test the interaction | ||||
# with nMinimumChainWork. | # with nMinimumChainWork. | ||||
self.setup_nodes() | self.setup_nodes() | ||||
def run_test(self): | def run_test(self): | ||||
# Setup the p2p connections and start up the network thread. | # Setup the p2p connections and start up the network thread. | ||||
test_node = NodeConnCB() # connects to node0 (not whitelisted) | test_node = NodeConnCB() # connects to node0 | ||||
white_node = NodeConnCB() # connects to node1 (whitelisted) | min_work_node = NodeConnCB() # connects to node1 | ||||
min_work_node = NodeConnCB() # connects to node2 (not whitelisted) | |||||
connections = [] | connections = [] | ||||
connections.append( | connections.append( | ||||
NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], test_node)) | NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], test_node)) | ||||
connections.append( | connections.append(NodeConn('127.0.0.1', p2p_port(1), | ||||
NodeConn('127.0.0.1', p2p_port(1), self.nodes[1], white_node)) | self.nodes[1], min_work_node)) | ||||
connections.append(NodeConn('127.0.0.1', p2p_port(2), | |||||
self.nodes[2], min_work_node)) | |||||
test_node.add_connection(connections[0]) | test_node.add_connection(connections[0]) | ||||
white_node.add_connection(connections[1]) | min_work_node.add_connection(connections[1]) | ||||
min_work_node.add_connection(connections[2]) | |||||
NetworkThread().start() # Start up network handling in another thread | NetworkThread().start() # Start up network handling in another thread | ||||
# Test logic begins here | # Test logic begins here | ||||
test_node.wait_for_verack() | test_node.wait_for_verack() | ||||
white_node.wait_for_verack() | |||||
min_work_node.wait_for_verack() | min_work_node.wait_for_verack() | ||||
# 1. Have nodes mine a block (nodes1/2 leave IBD) | # 1. Have nodes mine a block (leave IBD) | ||||
[n.generate(1) for n in self.nodes] | [n.generate(1) for n in self.nodes] | ||||
tips = [int("0x" + n.getbestblockhash(), 0) for n in self.nodes] | tips = [int("0x" + n.getbestblockhash(), 0) for n in self.nodes] | ||||
# 2. Send one block that builds on each tip. | # 2. Send one block that builds on each tip. | ||||
# This should be accepted by nodes 1/2 | # This should be accepted by node0 | ||||
blocks_h2 = [] # the height 2 blocks on each node's chain | blocks_h2 = [] # the height 2 blocks on each node's chain | ||||
block_time = int(time.time()) + 1 | block_time = int(time.time()) + 1 | ||||
for i in range(3): | for i in range(2): | ||||
blocks_h2.append(create_block( | blocks_h2.append(create_block( | ||||
tips[i], create_coinbase(2), block_time)) | tips[i], create_coinbase(2), block_time)) | ||||
blocks_h2[i].solve() | blocks_h2[i].solve() | ||||
block_time += 1 | block_time += 1 | ||||
test_node.send_message(msg_block(blocks_h2[0])) | test_node.send_message(msg_block(blocks_h2[0])) | ||||
white_node.send_message(msg_block(blocks_h2[1])) | min_work_node.send_message(msg_block(blocks_h2[1])) | ||||
min_work_node.send_message(msg_block(blocks_h2[2])) | |||||
for x in [test_node, white_node, min_work_node]: | for x in [test_node, min_work_node]: | ||||
x.sync_with_ping() | x.sync_with_ping() | ||||
assert_equal(self.nodes[0].getblockcount(), 2) | assert_equal(self.nodes[0].getblockcount(), 2) | ||||
assert_equal(self.nodes[1].getblockcount(), 2) | assert_equal(self.nodes[1].getblockcount(), 1) | ||||
assert_equal(self.nodes[2].getblockcount(), 1) | |||||
self.log.info( | self.log.info( | ||||
"First height 2 block accepted by node0/node1; correctly rejected by node2") | "First height 2 block accepted by node0; correctly rejected by node1") | ||||
# 3. Send another block that builds on the original tip. | |||||
blocks_h2f = [] # Blocks at height 2 that fork off the main chain | |||||
for i in range(2): | |||||
blocks_h2f.append( | |||||
create_block(tips[i], create_coinbase(2), blocks_h2[i].nTime + 1)) | |||||
blocks_h2f[i].solve() | |||||
test_node.send_message(msg_block(blocks_h2f[0])) | |||||
white_node.send_message(msg_block(blocks_h2f[1])) | |||||
for x in [test_node, white_node]: | # 3. Send another block that builds on genesis. | ||||
x.sync_with_ping() | block_h1f = create_block( | ||||
int("0x" + self.nodes[0].getblockhash(0), 0), create_coinbase(1), block_time) | |||||
block_time += 1 | |||||
block_h1f.solve() | |||||
test_node.send_message(msg_block(block_h1f)) | |||||
test_node.sync_with_ping() | |||||
tip_entry_found = False | |||||
for x in self.nodes[0].getchaintips(): | for x in self.nodes[0].getchaintips(): | ||||
if x['hash'] == blocks_h2f[0].hash: | if x['hash'] == block_h1f.hash: | ||||
assert_equal(x['status'], "headers-only") | assert_equal(x['status'], "headers-only") | ||||
tip_entry_found = True | |||||
assert(tip_entry_found) | |||||
assert_raises_rpc_error(-1, "Block not found on disk", | |||||
self.nodes[0].getblock, block_h1f.hash) | |||||
# 4. Send another two block that build on the fork. | |||||
block_h2f = create_block( | |||||
block_h1f.sha256, create_coinbase(2), block_time) | |||||
block_time += 1 | |||||
block_h2f.solve() | |||||
test_node.send_message(msg_block(block_h2f)) | |||||
for x in self.nodes[1].getchaintips(): | test_node.sync_with_ping() | ||||
if x['hash'] == blocks_h2f[1].hash: | # Since the earlier block was not processed by node, the new block | ||||
assert_equal(x['status'], "valid-headers") | |||||
self.log.info( | |||||
"Second height 2 block accepted only from whitelisted peer") | |||||
# 4. Now send another block that builds on the forking chain. | |||||
blocks_h3 = [] | |||||
for i in range(2): | |||||
blocks_h3.append( | |||||
create_block(blocks_h2f[i].sha256, create_coinbase(3), blocks_h2f[i].nTime + 1)) | |||||
blocks_h3[i].solve() | |||||
test_node.send_message(msg_block(blocks_h3[0])) | |||||
white_node.send_message(msg_block(blocks_h3[1])) | |||||
for x in [test_node, white_node]: | |||||
x.sync_with_ping() | |||||
# Since the earlier block was not processed by node0, the new block | |||||
# can't be fully validated. | # can't be fully validated. | ||||
tip_entry_found = False | |||||
for x in self.nodes[0].getchaintips(): | for x in self.nodes[0].getchaintips(): | ||||
if x['hash'] == blocks_h3[0].hash: | if x['hash'] == block_h2f.hash: | ||||
assert_equal(x['status'], "headers-only") | assert_equal(x['status'], "headers-only") | ||||
tip_entry_found = True | |||||
assert(tip_entry_found) | |||||
# But this block should be accepted by node0 since it has more work. | # But this block should be accepted by node since it has equal work. | ||||
self.nodes[0].getblock(blocks_h3[0].hash) | self.nodes[0].getblock(block_h2f.hash) | ||||
self.log.info( | self.log.info("Second height 2 block accepted, but not reorg'ed to") | ||||
"Unrequested more-work block accepted from non-whitelisted peer") | |||||
# 4b. Now send another block that builds on the forking chain. | |||||
# Node1 should have accepted and reorged. | block_h3 = create_block( | ||||
assert_equal(self.nodes[1].getblockcount(), 3) | block_h2f.sha256, create_coinbase(3), block_h2f.nTime+1) | ||||
self.log.info( | block_h3.solve() | ||||
"Successfully reorged to length 3 chain from whitelisted peer") | test_node.send_message(msg_block(block_h3)) | ||||
# 4b. Now mine 288 more blocks and deliver; all should be processed but | test_node.sync_with_ping() | ||||
# the last (height-too-high) on node0. Node1 should process the tip if | # Since the earlier block was not processed by node, the new block | ||||
# we give it the headers chain leading to the tip. | # can't be fully validated. | ||||
tips = blocks_h3 | tip_entry_found = False | ||||
headers_message = msg_headers() | for x in self.nodes[0].getchaintips(): | ||||
all_blocks = [] # node0's blocks | if x['hash'] == block_h3.hash: | ||||
for j in range(2): | assert_equal(x['status'], "headers-only") | ||||
tip_entry_found = True | |||||
assert(tip_entry_found) | |||||
self.nodes[0].getblock(block_h3.hash) | |||||
# But this block should be accepted by node since it has more work. | |||||
self.nodes[0].getblock(block_h3.hash) | |||||
self.log.info("Unrequested more-work block accepted") | |||||
# 4c. Now mine 288 more blocks and deliver; all should be processed but | |||||
# the last (height-too-high) on node (as long as its not missing any headers) | |||||
tip = block_h3 | |||||
all_blocks = [] | |||||
for i in range(288): | for i in range(288): | ||||
next_block = create_block( | next_block = create_block( | ||||
tips[j].sha256, create_coinbase(i + 4), tips[j].nTime + 1) | tip.sha256, create_coinbase(i + 4), tip.nTime+1) | ||||
next_block.solve() | next_block.solve() | ||||
if j == 0: | |||||
test_node.send_message(msg_block(next_block)) | |||||
all_blocks.append(next_block) | all_blocks.append(next_block) | ||||
else: | tip = next_block | ||||
headers_message.headers.append(CBlockHeader(next_block)) | |||||
tips[j] = next_block | # Now send the block at height 5 and check that it wasn't accepted (missing header) | ||||
test_node.send_message(msg_block(all_blocks[1])) | |||||
time.sleep(2) | test_node.sync_with_ping() | ||||
# Blocks 1-287 should be accepted, block 288 should be ignored because | assert_raises_rpc_error(-5, "Block not found", | ||||
# it's too far ahead | self.nodes[0].getblock, all_blocks[1].hash) | ||||
assert_raises_rpc_error(-5, "Block not found", | |||||
self.nodes[0].getblockheader, all_blocks[1].hash) | |||||
# The block at height 5 should be accepted if we provide the missing header, though | |||||
headers_message = msg_headers() | |||||
headers_message.headers.append(CBlockHeader(all_blocks[0])) | |||||
test_node.send_message(headers_message) | |||||
test_node.send_message(msg_block(all_blocks[1])) | |||||
test_node.sync_with_ping() | |||||
self.nodes[0].getblock(all_blocks[1].hash) | |||||
# Now send the blocks in all_blocks | |||||
for i in range(288): | |||||
test_node.send_message(msg_block(all_blocks[i])) | |||||
test_node.sync_with_ping() | |||||
# Blocks 1-287 should be accepted, block 288 should be ignored because it's too far ahead | |||||
for x in all_blocks[:-1]: | for x in all_blocks[:-1]: | ||||
self.nodes[0].getblock(x.hash) | self.nodes[0].getblock(x.hash) | ||||
assert_raises_rpc_error( | assert_raises_rpc_error( | ||||
-1, "Block not found on disk", self.nodes[0].getblock, all_blocks[-1].hash) | -1, "Block not found on disk", self.nodes[0].getblock, all_blocks[-1].hash) | ||||
headers_message.headers.pop() # Ensure the last block is unrequested | |||||
white_node.send_message(headers_message) # Send headers leading to tip | |||||
white_node.send_message(msg_block(tips[1])) # Now deliver the tip | |||||
white_node.sync_with_ping() | |||||
self.nodes[1].getblock(tips[1].hash) | |||||
self.log.info( | |||||
"Unrequested block far ahead of tip accepted from whitelisted peer") | |||||
# 5. Test handling of unrequested block on the node that didn't process | # 5. Test handling of unrequested block on the node that didn't process | ||||
# Should still not be processed (even though it has a child that has more | # Should still not be processed (even though it has a child that has more | ||||
# work). | # work). | ||||
test_node.send_message(msg_block(blocks_h2f[0])) | |||||
# Here, if the sleep is too short, the test could falsely succeed (if the | # The node should have requested the blocks at some point, so | ||||
# node hasn't processed the block by the time the sleep returns, and then | # disconnect/reconnect first | ||||
# the node processes it and incorrectly advances the tip). | connections[0].disconnect_node() | ||||
# But this would be caught later on, when we verify that an inv triggers | test_node.wait_for_disconnect() | ||||
# a getdata request for this block. | |||||
test_node = NodeConnCB() # connects to node (not whitelisted) | |||||
connections[0] = NodeConn( | |||||
'127.0.0.1', p2p_port(0), self.nodes[0], test_node) | |||||
test_node.add_connection(connections[0]) | |||||
test_node.wait_for_verack() | |||||
test_node.send_message(msg_block(block_h1f)) | |||||
test_node.sync_with_ping() | test_node.sync_with_ping() | ||||
assert_equal(self.nodes[0].getblockcount(), 2) | assert_equal(self.nodes[0].getblockcount(), 2) | ||||
self.log.info( | self.log.info( | ||||
"Unrequested block that would complete more-work chain was ignored") | "Unrequested block that would complete more-work chain was ignored") | ||||
# 6. Try to get node to request the missing block. | # 6. Try to get node to request the missing block. | ||||
# Poke the node with an inv for block at height 3 and see if that | # Poke the node with an inv for block at height 3 and see if that | ||||
# triggers a getdata on block 2 (it should if block 2 is missing). | # triggers a getdata on block 2 (it should if block 2 is missing). | ||||
with mininode_lock: | with mininode_lock: | ||||
# Clear state so we can check the getdata request | # Clear state so we can check the getdata request | ||||
test_node.last_message.pop("getdata", None) | test_node.last_message.pop("getdata", None) | ||||
test_node.send_message(msg_inv([CInv(2, blocks_h3[0].sha256)])) | test_node.send_message(msg_inv([CInv(2, block_h3.sha256)])) | ||||
test_node.sync_with_ping() | test_node.sync_with_ping() | ||||
with mininode_lock: | with mininode_lock: | ||||
getdata = test_node.last_message["getdata"] | getdata = test_node.last_message["getdata"] | ||||
# Check that the getdata includes the right block | # Check that the getdata includes the right block | ||||
assert_equal(getdata.inv[0].hash, blocks_h2f[0].sha256) | assert_equal(getdata.inv[0].hash, block_h1f.sha256) | ||||
self.log.info("Inv at tip triggered getdata for unprocessed block") | self.log.info("Inv at tip triggered getdata for unprocessed block") | ||||
# 7. Send the missing block for the third time (now it is requested) | # 7. Send the missing block for the third time (now it is requested) | ||||
test_node.send_message(msg_block(blocks_h2f[0])) | test_node.send_message(msg_block(block_h1f)) | ||||
test_node.sync_with_ping() | test_node.sync_with_ping() | ||||
assert_equal(self.nodes[0].getblockcount(), 290) | assert_equal(self.nodes[0].getblockcount(), 290) | ||||
self.nodes[0].getblock(all_blocks[286].hash) | |||||
assert_equal(self.nodes[0].getbestblockhash(), all_blocks[286].hash) | |||||
assert_raises_rpc_error(-1, "Block not found on disk", | |||||
self.nodes[0].getblock, all_blocks[287].hash) | |||||
self.log.info( | self.log.info( | ||||
"Successfully reorged to longer chain from non-whitelisted peer") | "Successfully reorged to longer chain from non-whitelisted peer") | ||||
[c.disconnect_node() for c in connections] | # 8. Create a chain which is invalid at a height longer than the | ||||
# current chain, but which has more blocks on top of that | |||||
block_289f = create_block( | |||||
all_blocks[284].sha256, create_coinbase(289), all_blocks[284].nTime+1) | |||||
block_289f.solve() | |||||
block_290f = create_block( | |||||
block_289f.sha256, create_coinbase(290), block_289f.nTime+1) | |||||
block_290f.solve() | |||||
block_291 = create_block( | |||||
block_290f.sha256, create_coinbase(291), block_290f.nTime+1) | |||||
# block_291 spends a coinbase below maturity! | |||||
block_291.vtx.append(create_transaction( | |||||
block_290f.vtx[0], 0, b"42", 1)) | |||||
block_291.hashMerkleRoot = block_291.calc_merkle_root() | |||||
block_291.solve() | |||||
block_292 = create_block( | |||||
block_291.sha256, create_coinbase(292), block_291.nTime+1) | |||||
block_292.solve() | |||||
# 8. Connect node2 to node0 and ensure it is able to sync | # Now send all the headers on the chain and enough blocks to trigger reorg | ||||
connect_nodes(self.nodes[0], 2) | headers_message = msg_headers() | ||||
sync_blocks([self.nodes[0], self.nodes[2]]) | headers_message.headers.append(CBlockHeader(block_289f)) | ||||
self.log.info("Successfully synced nodes 2 and 0") | headers_message.headers.append(CBlockHeader(block_290f)) | ||||
headers_message.headers.append(CBlockHeader(block_291)) | |||||
headers_message.headers.append(CBlockHeader(block_292)) | |||||
test_node.send_message(headers_message) | |||||
test_node.sync_with_ping() | |||||
tip_entry_found = False | |||||
for x in self.nodes[0].getchaintips(): | |||||
if x['hash'] == block_292.hash: | |||||
assert_equal(x['status'], "headers-only") | |||||
tip_entry_found = True | |||||
assert(tip_entry_found) | |||||
assert_raises_rpc_error(-1, "Block not found on disk", | |||||
self.nodes[0].getblock, block_292.hash) | |||||
test_node.send_message(msg_block(block_289f)) | |||||
test_node.send_message(msg_block(block_290f)) | |||||
test_node.sync_with_ping() | |||||
self.nodes[0].getblock(block_289f.hash) | |||||
self.nodes[0].getblock(block_290f.hash) | |||||
test_node.send_message(msg_block(block_291)) | |||||
# At this point we've sent an obviously-bogus block, wait for full processing | |||||
# without assuming whether we will be disconnected or not | |||||
try: | |||||
# Only wait a short while so the test doesn't take forever if we do get | |||||
# disconnected | |||||
test_node.sync_with_ping(timeout=1) | |||||
except AssertionError: | |||||
test_node.wait_for_disconnect() | |||||
test_node = NodeConnCB() # connects to node (not whitelisted) | |||||
connections[0] = NodeConn( | |||||
'127.0.0.1', p2p_port(0), self.nodes[0], test_node) | |||||
test_node.add_connection(connections[0]) | |||||
NetworkThread().start() # Start up network handling in another thread | |||||
test_node.wait_for_verack() | |||||
# We should have failed reorg and switched back to 290 (but have block 291) | |||||
assert_equal(self.nodes[0].getblockcount(), 290) | |||||
assert_equal(self.nodes[0].getbestblockhash(), all_blocks[286].hash) | |||||
assert_equal(self.nodes[0].getblock( | |||||
block_291.hash)["confirmations"], -1) | |||||
# Now send a new header on the invalid chain, indicating we're forked off, and expect to get disconnected | |||||
block_293 = create_block( | |||||
block_292.sha256, create_coinbase(293), block_292.nTime+1) | |||||
block_293.solve() | |||||
headers_message = msg_headers() | |||||
headers_message.headers.append(CBlockHeader(block_293)) | |||||
test_node.send_message(headers_message) | |||||
test_node.wait_for_disconnect() | |||||
# 9. Connect node1 to node0 and ensure it is able to sync | |||||
connect_nodes(self.nodes[0], 1) | |||||
sync_blocks([self.nodes[0], self.nodes[1]]) | |||||
self.log.info("Successfully synced nodes 1 and 0") | |||||
[c.disconnect_node() for c in connections] | [c.disconnect_node() for c in connections] | ||||
if __name__ == '__main__': | if __name__ == '__main__': | ||||
AcceptBlockTest().main() | AcceptBlockTest().main() |