Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save bnonni/947905c44082991de3d808a279e37ce3 to your computer and use it in GitHub Desktop.
Save bnonni/947905c44082991de3d808a279e37ce3 to your computer and use it in GitHub Desktop.

Chaincode Labs Seminar Application Exercises

Bitcoin Protocol Development

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 called run_extra_test to complete this task.

  • In this new function, I pass peer_receiving from the run_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)
  • 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)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment