Tasks:
- Compile Bitcoin Core
- Run the unit and functional tests
- In
example_test.py
, get node 1 to mine another block, send it to node 2, and check that node 2 received it.
Explanation:
-
Following the logic of
run_test
, I created a new function calledrun_extra_test
to complete this task. -
In this new function, I pass
peer_receiving
from therun_test
function because it was already set to node2. -
Code flow for
run_extra_test
:- set the messenger peer to node1
peer_messaging = self.nodes[1].add_p2p_connection(BaseNode())
- get current chain tip, block time and height
self.tip = int(self.nodes[1].getbestblockhash(), 16) self.block_time = self.nodes[1].getblock(self.nodes[1].getbestblockhash())['time'] + 1 height = self.nodes[1].getblockcount()
- node1 creates new block, solves it, creates new message, and update tip, block time and height
next_block = create_block(self.tip, create_coinbase(height+1), self.block_time) next_block.solve() block_message = msg_block(next_block) peer_messaging.send_message(block_message) self.tip = next_block.sha256 self.block_time += 1 height += 1
- Wait for node2 to reach current tip (height 12), ensure node2 and node1 are connected, sync all blocks between them
self.nodes[2].waitforblockheight(12) self.connect_nodes(2, 1) self.sync_all()
- check that each block was received only once & test that node1 propogates new block to node2
getdata_request = msg_getdata() getdata_request.inv.append(CInv(MSG_BLOCK, self.tip)) peer_receiving.send_message(getdata_request) with p2p_lock: for block in peer_receiving.block_receive_map.values(): assert_equal(block, 1) node1_tip = int(self.nodes[1].getbestblockhash(), 16) node1_tip = int(self.nodes[1].getbestblockhash(), 16) node2_tip = int(self.nodes[2].getbestblockhash(), 16) assert_equal(node1_tip, node2_tip) node1_height = self.nodes[1].getblockcount() node2_height = self.nodes[2].getblockcount() assert_equal(node1_height, node2_height)
- set the messenger peer to node1
-
Full code
def run_test(self):
"""Main test logic"""
# Create P2P connections will wait for a verack to make sure the connection is fully up
peer_messaging = self.nodes[0].add_p2p_connection(BaseNode())
# Generating a block on one of the nodes will get us out of IBD
blocks = [int(self.generate(self.nodes[0], sync_fun=lambda: self.sync_all(self.nodes[0:2]), nblocks=1)[0], 16)]
# Notice above how we called an RPC by calling a method with the same
# name on the node object. Notice also how we used a keyword argument
# to specify a named RPC argument. Neither of those are defined on the
# node object. Instead there's some __getattr__() magic going on under
# the covers to dispatch unrecognised attribute calls to the RPC
# interface.
# Logs are nice. Do plenty of them. They can be used in place of comments for
# breaking the test into sub-sections.
self.log.info("Starting test!")
self.log.info("Calling a custom function")
custom_function()
self.log.info("Calling a custom method")
self.custom_method()
self.log.info("Create some blocks")
self.tip = int(self.nodes[0].getbestblockhash(), 16)
self.block_time = self.nodes[0].getblock(self.nodes[0].getbestblockhash())['time'] + 1
height = self.nodes[0].getblockcount()
for _ in range(10):
# Use the blocktools functionality to manually build a block.
# Calling the generate() rpc is easier, but this allows us to exactly
# control the blocks and transactions.
block = create_block(self.tip, create_coinbase(height+1), self.block_time)
block.solve()
block_message = msg_block(block)
# Send message is used to send a P2P message to the node over our P2PInterface
peer_messaging.send_message(block_message)
self.tip = block.sha256
blocks.append(self.tip)
self.block_time += 1
height += 1
self.log.info("Wait for node1 to reach current tip (height 11) using RPC")
self.nodes[1].waitforblockheight(11)
self.log.info("Connect node2 and node1")
self.connect_nodes(1, 2)
self.log.info("Wait for node2 to receive all the blocks from node1")
self.sync_all()
self.log.info("Add P2P connection to node2")
self.nodes[0].disconnect_p2ps()
peer_receiving = self.nodes[2].add_p2p_connection(BaseNode())
self.log.info("Test that node2 propagates all the blocks to us")
getdata_request = msg_getdata()
for block in blocks:
getdata_request.inv.append(CInv(MSG_BLOCK, block))
peer_receiving.send_message(getdata_request)
# wait_until() will loop until a predicate condition is met. Use it to test properties of the
# P2PInterface objects.
peer_receiving.wait_until(lambda: sorted(blocks) == sorted(list(peer_receiving.block_receive_map.keys())), timeout=5)
self.log.info("Check that each block was received only once")
# The network thread uses a global lock on data access to the P2PConnection objects when sending and receiving
# messages. The test thread should acquire the global lock before accessing any P2PConnection data to avoid locking
# and synchronization issues. Note p2p.wait_until() acquires this global lock internally when testing the predicate.
with p2p_lock:
for block in peer_receiving.block_receive_map.values():
assert_equal(block, 1)
self.run_extra_test(peer_receiving)
def run_extra_test(self, peer_receiving):
self.log.info("Setup node1 as messenger")
peer_messaging = self.nodes[1].add_p2p_connection(BaseNode())
self.log.info("Update chain tip, block time and height")
self.tip = int(self.nodes[1].getbestblockhash(), 16)
self.block_time = self.nodes[1].getblock(self.nodes[1].getbestblockhash())['time'] + 1
height = self.nodes[1].getblockcount()
self.log.info('Node 1 creates new block')
next_block = create_block(self.tip, create_coinbase(height+1), self.block_time)
next_block.solve()
block_message = msg_block(next_block)
peer_messaging.send_message(block_message)
self.tip = next_block.sha256
self.block_time += 1
height += 1
self.log.info("Wait for node1 to reach current tip (height 11) using RPC")
self.nodes[2].waitforblockheight(12)
self.log.info("Reconnect node2 and node1")
self.connect_nodes(2, 1)
self.log.info("Wait for node2 to receive next block from node1")
self.sync_all()
self.log.info("Test that node1 propagates the next block to us")
getdata_request = msg_getdata()
getdata_request.inv.append(CInv(MSG_BLOCK, self.tip))
peer_receiving.send_message(getdata_request)
self.log.info("Check that each block was received only once")
with p2p_lock:
for block in peer_receiving.block_receive_map.values():
assert_equal(block, 1)
node1_tip = int(self.nodes[1].getbestblockhash(), 16)
self.log.info("Assert node1 and node2 have same tip and height")
node1_tip = int(self.nodes[1].getbestblockhash(), 16)
node2_tip = int(self.nodes[2].getbestblockhash(), 16)
assert_equal(node1_tip, node2_tip)
node1_height = self.nodes[1].getblockcount()
node2_height = self.nodes[2].getblockcount()
assert_equal(node1_height, node2_height)