Changeset View
Changeset View
Standalone View
Standalone View
test/functional/p2p-acceptblock.py
Show All 10 Lines | |||||
''' | ''' | ||||
AcceptBlockTest -- test processing of unrequested blocks. | AcceptBlockTest -- test processing of unrequested blocks. | ||||
Since behavior differs when receiving unrequested blocks from whitelisted peers | Since behavior differs when receiving unrequested blocks from whitelisted peers | ||||
versus non-whitelisted peers, this tests the behavior of both (effectively two | versus non-whitelisted peers, this tests the behavior of both (effectively two | ||||
separate tests running in parallel). | separate tests running in parallel). | ||||
Setup: two nodes, node0 and node1, not connected to each other. Node0 does not | 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 | whitelist localhost, but node1 does. They will each be on their own chain for | ||||
this test. | 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 and white_node respectively. | We have one NodeConn connection to each, test_node, white_node, and min_work_node, | ||||
respectively. | |||||
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. | The tip should advance for node0 and node1, but node2 should skip processing | ||||
due to nMinimumChainWork. | |||||
Node2 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 the previous block, and deliver to each node from | ||||
corresponding peer. | corresponding peer. | ||||
Node0 should not process this block (just accept the header), because it is | Node0 should not process this block (just accept the header), because it is | ||||
unrequested and doesn't have more work than the tip. | unrequested and doesn't have more work than the tip. | ||||
Node1 should process because this is coming from a whitelisted peer. | Node1 should process because this is coming from a whitelisted peer. | ||||
4. Send another block that builds on the forking block. | 4. Send another block that builds on the forking block. | ||||
Show All 11 Lines | 5. Send a duplicate of the block in #3 to Node0. | ||||
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 | |||||
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 = 2 | self.num_nodes = 3 | ||||
self.extra_args = [[], ["-whitelist=127.0.0.1"]] | self.extra_args = [[], ["-whitelist=127.0.0.1"], | ||||
["-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 | |||||
# 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 (not whitelisted) | ||||
white_node = NodeConnCB() # connects to node1 (whitelisted) | white_node = NodeConnCB() # connects to node1 (whitelisted) | ||||
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), self.nodes[1], white_node)) | NodeConn('127.0.0.1', p2p_port(1), self.nodes[1], white_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]) | white_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() | white_node.wait_for_verack() | ||||
min_work_node.wait_for_verack() | |||||
# 1. Have both nodes mine a block (leave IBD) | # 1. Have nodes mine a block (nodes1/2 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. | # This should be accepted by nodes 1/2 | ||||
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(2): | for i in range(3): | ||||
blocks_h2.append( | blocks_h2.append(create_block( | ||||
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])) | white_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]: | for x in [test_node, white_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(), 2) | ||||
self.log.info("First height 2 block accepted by both nodes") | assert_equal(self.nodes[2].getblockcount(), 1) | ||||
self.log.info( | |||||
"First height 2 block accepted by node0/node1; correctly rejected by node2") | |||||
# 3. Send another block that builds on the original tip. | # 3. Send another block that builds on the original tip. | ||||
blocks_h2f = [] # Blocks at height 2 that fork off the main chain | blocks_h2f = [] # Blocks at height 2 that fork off the main chain | ||||
for i in range(2): | for i in range(2): | ||||
blocks_h2f.append( | blocks_h2f.append( | ||||
create_block(tips[i], create_coinbase(2), blocks_h2[i].nTime + 1)) | create_block(tips[i], create_coinbase(2), blocks_h2[i].nTime + 1)) | ||||
blocks_h2f[i].solve() | blocks_h2f[i].solve() | ||||
test_node.send_message(msg_block(blocks_h2f[0])) | test_node.send_message(msg_block(blocks_h2f[0])) | ||||
▲ Show 20 Lines • Show All 110 Lines • ▼ Show 20 Lines | def run_test(self): | ||||
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.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] | [c.disconnect_node() for c in connections] | ||||
# 8. Connect node2 to node0 and ensure it is able to sync | |||||
connect_nodes(self.nodes[0], 2) | |||||
sync_blocks([self.nodes[0], self.nodes[2]]) | |||||
self.log.info("Successfully synced nodes 2 and 0") | |||||
[c.disconnect_node() for c in connections] | |||||
if __name__ == '__main__': | if __name__ == '__main__': | ||||
AcceptBlockTest().main() | AcceptBlockTest().main() |