コード例 #1
0
 def set_test_params(self):
     self.setup_clean_chain = True
     self.chain = ChainManager()
     self.num_nodes = 1
     self.next_block = 0
     self.headerSize = 24
     self.num_peers = 15
     self.excessiveblocksize = 3 * ONE_MEGABYTE
コード例 #2
0
    def __init__(self, remote_node, node_number):
        super(RunnerNode, self).__init__()
        self.chain = ChainManager()
        self.next_block = 0
        self.remote_node = remote_node
        self.node_number = node_number

        connections = []
        connections.append(
            NodeConn('127.0.0.1', p2p_port(self.node_number), self.remote_node,
                     self))
        self.add_connection(connections[0])
コード例 #3
0
class RunnerNode(NodeConnCB):
    def __init__(self, remote_node, node_number):
        super(RunnerNode, self).__init__()
        self.chain = ChainManager()
        self.next_block = 0
        self.remote_node = remote_node
        self.node_number = node_number

        connections = []
        connections.append(
            NodeConn('127.0.0.1', p2p_port(self.node_number), self.remote_node,
                     self))
        self.add_connection(connections[0])

    def finish_setup_after_network_is_started(self, tmp_dir):
        self.wait_for_verack()

        self.chain.set_genesis_hash(
            int(self.remote_node.getbestblockhash(), 16))

        # Build the blockchain
        self.tip = int(self.remote_node.getbestblockhash(), 16)
        self.block_time = self.remote_node.getblock(
            self.remote_node.getbestblockhash())['time'] + 1

        _, _, self.next_block = prepare_init_chain(self.chain,
                                                   100,
                                                   0,
                                                   block_0=False,
                                                   start_block=0,
                                                   node=self)

        self.sync_with_ping()

        # Create a new block - half of max block file size. Together with above blocks
        # this leaves less than 1MB of free space in the first block file
        self.create_and_send_block(ONE_MEGABYTE)
        assert (len(
            glob.glob(tmp_dir + "/node" + str(self.node_number) +
                      "/regtest/blocks/blk0000*.dat")) == 1
                )  # sanity check that there is still only one file

    def create_and_send_block(self, block_size):
        out = self.chain.get_spendable_output()
        block = self.chain.next_block(self.next_block,
                                      spend=out,
                                      block_size=block_size)
        self.next_block += 1
        self.chain.save_spendable_output()
        self.send_and_ping(msg_block(block))

        return block, self.next_block - 1
コード例 #4
0
class MaxSendQueuesBytesTest(BitcoinTestFramework):
    def set_test_params(self):
        self.setup_clean_chain = True
        self.chain = ChainManager()
        self.num_nodes = 1
        self.next_block = 0
        self.headerSize = 24
        self.num_peers = 15
        self.excessiveblocksize = 3 * ONE_MEGABYTE

    # Request block "block" from all nodes.
    def requestBlocks(self, test_nodes, block):
        REJECT_TOOBUSY = int('0x44', 16)

        numberOfRejectedMsgs = 0
        numberOfReceivedBlocks = 0

        def on_block(conn, message):
            nonlocal numberOfReceivedBlocks
            numberOfReceivedBlocks += 1

        def on_reject(conn, message):
            assert_equal(message.code, REJECT_TOOBUSY)
            nonlocal numberOfRejectedMsgs
            numberOfRejectedMsgs += 1

        getdata_request = msg_getdata([CInv(2, block)])

        for test_node in test_nodes:
            test_node.on_block = on_block
            test_node.on_reject = on_reject
            test_node.send_message(getdata_request)

        # Let bitcoind process and send all the messages.
        for test_node in test_nodes:
            test_node.sync_with_ping()

        return numberOfReceivedBlocks, numberOfRejectedMsgs

    def prepareChain(self):
        node = NodeConnCB()
        connections = []
        connections.append(
            NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], node))
        node.add_connection(connections[0])

        NetworkThread().start()
        node.wait_for_verack()

        # Generate some old blocks
        self.chain.set_genesis_hash(int(self.nodes[0].getbestblockhash(), 16))

        # Create the first block with a coinbase output to our key
        block = self.chain.next_block(self.next_block)
        self.next_block += 1
        self.chain.save_spendable_output()
        node.send_message(msg_block(block))

        # Bury the block 100 deep so the coinbase output is spendable
        for i in range(1, 100):
            block = self.chain.next_block(self.next_block)
            self.next_block += 1
            self.chain.save_spendable_output()
            node.send_message(msg_block(block))

        return node

    def mineBigBlock(self, node):
        block = self.chain.next_block(self.next_block,
                                      spend=self.chain.get_spendable_output(),
                                      block_size=self.excessiveblocksize)
        self.next_block += 1
        self.chain.save_spendable_output()
        node.send_message(msg_block(block))
        node.sync_with_ping()
        createdBlock = self.nodes[0].getbestblockhash()
        logger.info("Big block %s created (%d B)", createdBlock,
                    self.excessiveblocksize)
        createdBlock = int(createdBlock, 16)
        return createdBlock

    def run_test(self):
        @contextlib.contextmanager
        def run_connection(factorMaxSendingBlocksSize, blockSize):
            title = "Send GetData and receive block messages while factorMaxSendingBlockSize is {}.".format(
                factorMaxSendingBlocksSize)
            logger.debug("setup %s", title)

            args = [
                "-excessiveblocksize={}".format(self.excessiveblocksize +
                                                self.headerSize),
                "-blockmaxsize={}".format(self.excessiveblocksize +
                                          self.headerSize),
                "-factorMaxSendQueuesBytes={}".format(
                    factorMaxSendingBlocksSize)
            ]

            self.start_node(0, args)

            test_nodes = []
            for i in range(self.num_peers):
                test_nodes.append(NodeConnCB())

            connections = []
            for test_node in test_nodes:
                connection = NodeConn('127.0.0.1', p2p_port(0), self.nodes[0],
                                      test_node)
                connections.append(connection)
                test_node.add_connection(connection)

            thr = NetworkThread()
            thr.start()
            for test_node in test_nodes:
                test_node.wait_for_verack()

            logger.debug("before %s", title)
            yield test_nodes
            logger.debug("after %s", title)

            for connection in connections:
                connection.close()
            del connections
            thr.join()
            disconnect_nodes(self.nodes[0], 1)
            self.stop_node(0)
            logger.debug("finished %s", title)

        node = self.prepareChain()

        # Mine a big block.
        oldBlock = self.mineBigBlock(node)

        # Mine another big block so that the previous block is not the tip of chain.
        newBlock = self.mineBigBlock(node)

        self.stop_node(0)

        # Scenario 1: Blocks from bitcoind should be sent in parallel as factorMaxSendQueuesBytes=num_peers.
        with run_connection(self.num_peers,
                            self.excessiveblocksize) as test_nodes:
            numberOfReceivedBlocksParallel, numberOfRejectedMsgs = self.requestBlocks(
                test_nodes, oldBlock)
        assert_equal(self.num_peers, numberOfReceivedBlocksParallel)
        assert_equal(0, numberOfRejectedMsgs)

        # Scenario 2: Blocks from bitcoind should not be sent in parallel because factorMaxSendQueuesBytes=1
        # only allows one 3MB to be downloaded at once.
        with run_connection(1, self.excessiveblocksize) as test_nodes:
            numberOfReceivedBlocksSeries, numberOfRejectedMsgs = self.requestBlocks(
                test_nodes, oldBlock)
        # numReceivedBlocksSeries may vary between test runs (based on processing power).
        # But still we expect the processing to be slow enough that with 15 messages at least one will be rejected.
        assert_greater_than(numberOfReceivedBlocksParallel,
                            numberOfReceivedBlocksSeries)
        assert_greater_than(numberOfRejectedMsgs, 0)
        assert_equal(numberOfReceivedBlocksSeries + numberOfRejectedMsgs, 15)
        logger.info(
            "%d blocks received when running with factorMaxSendQueuesBytes=%d.",
            numberOfReceivedBlocksSeries, 1)

        # Scenario 3: Blocks from bitcoind should be sent in parallel, because we are requesting the most recent block.
        with run_connection(1, self.excessiveblocksize) as test_nodes:
            numberOfReceivedBlocksNewBlock, numberOfRejectedMsgs = self.requestBlocks(
                test_nodes, newBlock)
        assert_equal(self.num_peers, numberOfReceivedBlocksNewBlock)
        assert_equal(0, numberOfRejectedMsgs)
コード例 #5
0
    def test_blocktree(self):
        """
            Test soft rejection block status in non-trivial tree of blocks
        """

        # Create a P2P connection to node0 that will be used to send blocks
        self.stop_node(0)
        with self.run_node_with_connections(
                title="test_blocktree",
                node_index=0,
                args=[
                    "-whitelist=127.0.0.1"
                ],  # Need to whilelist localhost, so that node accepts any block
                number_of_connections=1) as connections:
            conn0 = connections[0]

            # Create the following tree of blocks:
            #     genesis
            #        |
            #   1001...1200
            #        |
            #        1    height=201
            #       / \
            #      2   8
            #     /|\   \
            #    3 4 6   9
            #      | |
            #      5 7
            chain = ChainManager()
            genesis_hash = self.nodes[0].getbestblockhash()
            chain.set_genesis_hash(int(genesis_hash, 16))
            _, out, _ = prepare_init_chain(chain,
                                           200,
                                           12,
                                           block_0=False,
                                           start_block=1001,
                                           node=conn0.cb)
            conn0.cb.sync_with_ping()

            # Check that we have created a chain that we wanted
            assert_equal(self.nodes[0].getblockcount(), 200)
            assert_equal(
                self.nodes[0].getblock(chain.blocks[1001].hash)["height"], 1)
            assert_equal(self.nodes[0].getbestblockhash(),
                         chain.blocks[1200].hash)
            assert_equal(
                self.nodes[0].getblock(chain.blocks[1200].hash)["height"], 200)

            def new_blk(idx, prev_idx):
                chain.set_tip(prev_idx)
                b = chain.next_block(
                    idx, spend=out[idx]
                )  # spend output with the same index as block
                # NOTE: We don't really care about spending outputs in this test.
                #       Spending different outputs is only used as a convenient way
                #       to make two blocks different if they have the same parent.
                conn0.cb.send_message(msg_block(b))  # send block to node
                conn0.cb.sync_with_ping(
                )  # wait until node has processed the block
                self.log.debug("Created block: idx=%i prev=%i hash=%s" %
                               (idx, prev_idx, b.hash))

            new_blk(1, 1200)
            new_blk(2, 1)
            new_blk(3, 2)
            new_blk(4, 2)
            new_blk(5, 4)
            new_blk(6, 2)
            new_blk(7, 6)
            new_blk(8, 1)
            new_blk(9, 8)

            # Block 5 should be tip of the active chain (it its highest and was received before 7, which is at the same height)
            assert_equal(self.nodes[0].getbestblockhash(),
                         chain.blocks[5].hash)

            # If 5 is soft rejected, 7 should become best
            self.nodes[0].softrejectblock(chain.blocks[5].hash, 0)
            assert_equal(self.nodes[0].getbestblockhash(),
                         chain.blocks[7].hash)

            # If 7 is also soft rejected, 3 should become best
            self.nodes[0].softrejectblock(chain.blocks[7].hash, 0)
            assert_equal(self.nodes[0].getbestblockhash(),
                         chain.blocks[3].hash)

            # Reset state
            self.nodes[0].acceptblock(chain.blocks[5].hash)
            self.nodes[0].acceptblock(chain.blocks[7].hash)
            assert_equal(self.soft_rej_blocks_hashes(self.nodes[0]), set())
            assert_equal(self.nodes[0].getbestblockhash(),
                         chain.blocks[5].hash)

            # If 2 is soft rejected for next two blocks, 9 should become best
            self.nodes[0].softrejectblock(chain.blocks[2].hash, 2)
            assert_equal(self.nodes[0].getbestblockhash(),
                         chain.blocks[9].hash)

            # If we reconsider 2 to be soft rejected only for next one block, 5 should again become best
            self.nodes[0].acceptblock(chain.blocks[2].hash, 1)
            assert_equal(self.nodes[0].getbestblockhash(),
                         chain.blocks[5].hash)

            # Reset state
            self.nodes[0].acceptblock(chain.blocks[2].hash)
            assert_equal(self.soft_rej_blocks_hashes(self.nodes[0]), set())
            assert_equal(self.nodes[0].getbestblockhash(),
                         chain.blocks[5].hash)

            # If 1 is soft rejected for next 3 blocks, 1200 should become best
            self.nodes[0].softrejectblock(chain.blocks[1].hash, 3)
            assert_equal(self.nodes[0].getbestblockhash(),
                         chain.blocks[1200].hash)

            # If 1 is soft rejected only until next block, 2 is soft rejected for 2 blocks and 9 is soft rejected until next block, 8 should become best
            self.nodes[0].acceptblock(chain.blocks[1].hash, 0)
            self.nodes[0].softrejectblock(chain.blocks[9].hash, 0)
            self.nodes[0].softrejectblock(chain.blocks[2].hash, 2)
            assert_equal(self.nodes[0].getbestblockhash(),
                         chain.blocks[8].hash)

            # If we now receive a new block after 9, it should become best
            new_blk(10, 9)
            assert_equal(self.nodes[0].getbestblockhash(),
                         chain.blocks[10].hash)

            # Reset state, block 5 should still be best
            self.nodes[0].acceptblock(chain.blocks[1].hash)
            self.nodes[0].acceptblock(chain.blocks[9].hash)
            self.nodes[0].acceptblock(chain.blocks[2].hash)
            assert_equal(self.soft_rej_blocks_hashes(self.nodes[0]), set())
            assert_equal(self.nodes[0].getbestblockhash(),
                         chain.blocks[5].hash)

            # Soft rejecting block 1001 for next 202 blocks should have no effect
            self.nodes[0].softrejectblock(chain.blocks[1001].hash, 202)
            assert_equal(self.nodes[0].getbestblockhash(),
                         chain.blocks[5].hash)

            # If block 1001 is soft rejected for next 203 blocks, genesis should become best
            self.nodes[0].softrejectblock(chain.blocks[1001].hash, 203)
            assert_equal(self.nodes[0].getbestblockhash(), genesis_hash)

            # If we now receive a new block after 5, it should become best
            new_blk(11, 5)
            assert_equal(self.nodes[0].getbestblockhash(),
                         chain.blocks[11].hash)

            # Reset state, block 11 should still be best
            self.nodes[0].acceptblock(chain.blocks[1001].hash)
            assert_equal(self.soft_rej_blocks_hashes(self.nodes[0]), set())
            assert_equal(self.nodes[0].getbestblockhash(),
                         chain.blocks[11].hash)
コード例 #6
0
class MaxSendQueuesBytesTest(BitcoinTestFramework):
    def set_test_params(self):
        self.setup_clean_chain = True
        self.chain = ChainManager()
        self.num_nodes = 1
        self.next_block = 0
        self.headerSize = 24
        self.num_peers = 15
        self.excessiveblocksize = 5 * ONE_MEGABYTE

    # Request block "block" from all nodes.
    def requestBlocks(self, test_nodes, block):
        REJECT_TOOBUSY = int('0x44', 16)

        numberOfRejectedMsgs = 0
        numberOfReceivedBlocks = 0

        def on_block(conn, message):
            nonlocal numberOfReceivedBlocks
            numberOfReceivedBlocks += 1

        def on_reject(conn, message):
            assert_equal(message.code, REJECT_TOOBUSY)
            nonlocal numberOfRejectedMsgs
            numberOfRejectedMsgs += 1

        getdata_request = msg_getdata([CInv(2, block)])

        for test_node in test_nodes:
            test_node.cb.on_block = on_block
            test_node.cb.on_reject = on_reject
            test_node.cb.send_message(getdata_request)

        # Let bitcoind process and send all the messages.
        for test_node in test_nodes:
            test_node.cb.sync_with_ping()

        return numberOfReceivedBlocks, numberOfRejectedMsgs

    def prepareChain(self):
        node = NodeConnCB()
        connections = []
        connections.append(
            NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], node))
        node.add_connection(connections[0])

        NetworkThread().start()
        node.wait_for_verack()

        # Generate some old blocks
        self.chain.set_genesis_hash(int(self.nodes[0].getbestblockhash(), 16))

        # Create the first block with a coinbase output to our key
        block = self.chain.next_block(self.next_block)
        self.next_block += 1
        self.chain.save_spendable_output()
        node.send_message(msg_block(block))

        # Bury the block 100 deep so the coinbase output is spendable
        for i in range(1, 100):
            block = self.chain.next_block(self.next_block)
            self.next_block += 1
            self.chain.save_spendable_output()
            node.send_message(msg_block(block))

        return node

    def mineBigBlock(self, node):
        block = self.chain.next_block(self.next_block,
                                      spend=self.chain.get_spendable_output(),
                                      block_size=self.excessiveblocksize)
        self.next_block += 1
        self.chain.save_spendable_output()
        node.send_message(msg_block(block))
        node.sync_with_ping()
        createdBlock = self.nodes[0].getbestblockhash()
        logger.info("Big block %s created (%d B)", createdBlock,
                    self.excessiveblocksize)
        createdBlock = int(createdBlock, 16)
        return createdBlock

    def run_test(self):
        node = self.prepareChain()

        # Mine a big block.
        oldBlock = self.mineBigBlock(node)

        # Mine another big block so that the previous block is not the tip of chain.
        newBlock = self.mineBigBlock(node)

        self.stop_node(0)

        # Scenario 1: Blocks from bitcoind should be sent in parallel as factormaxsendqueuesbytes=num_peers.
        args = [
            "-excessiveblocksize={}".format(self.excessiveblocksize +
                                            self.headerSize),
            "-blockmaxsize={}".format(self.excessiveblocksize +
                                      self.headerSize), '-rpcservertimeout=500'
        ]
        with self.run_node_with_connections(
                "should be sent in parallel as factormaxsendqueuesbytes=num_peers",
                0,
                args + ["-factormaxsendqueuesbytes={}".format(self.num_peers)],
                self.num_peers) as connections:

            start = time.time()
            numberOfReceivedBlocksParallel, numberOfRejectedMsgs = self.requestBlocks(
                connections, oldBlock)
            logger.info("finished requestBlock duration %s s",
                        time.time() - start)
            assert_equal(self.num_peers, numberOfReceivedBlocksParallel)
            assert_equal(0, numberOfRejectedMsgs)

        # Scenario 2: Blocks from bitcoind should not be sent in parallel because factormaxsendqueuesbytes=1
        # only allows one 5MB to be downloaded at once.
        with self.run_node_with_connections(
                "should not be sent in parallel because factormaxsendqueuesbytes=1",
                0, args + ["-factormaxsendqueuesbytes=1"],
                self.num_peers) as connections:

            start = time.time()
            numberOfReceivedBlocksSeries, numberOfRejectedMsgs = self.requestBlocks(
                connections, oldBlock)
            logger.info("finished requestBlock duration %s s",
                        time.time() - start)
            # numReceivedBlocksSeries may vary between test runs (based on processing power).
            # But still we expect the processing to be slow enough that with 15 messages at least one will be rejected.
            logger.info(
                "%d blocks received when running with factormaxsendqueuesbytes=%d.",
                numberOfReceivedBlocksSeries, 1)
            assert_greater_than(numberOfReceivedBlocksParallel,
                                numberOfReceivedBlocksSeries)
            assert_greater_than(numberOfRejectedMsgs, 0)
            assert_equal(numberOfReceivedBlocksSeries + numberOfRejectedMsgs,
                         15)

        # Scenario 3: Blocks from bitcoind should not be sent in parallel if we reached rate limit,
        # because we are requesting the most recent block from non whitelisted peer.
        with self.run_node_with_connections(
                "some blocks should be rejected because non whitelisted peer are requesting most recent block",
                0, args + ["-factormaxsendqueuesbytes=1"],
                self.num_peers) as connections:
            start = time.time()
            numberOfReceivedBlocksNewBlock, numberOfRejectedMsgs = self.requestBlocks(
                connections, newBlock)
            logger.info("finished requestBlock duration %s s",
                        time.time() - start)
            logger.info(
                "%d blocks received when running with factormaxsendqueuesbytes=%d.",
                numberOfReceivedBlocksNewBlock, 1)
            assert_greater_than(self.num_peers, numberOfReceivedBlocksNewBlock)
            assert_greater_than(numberOfRejectedMsgs, 0)

        # Scenario 4: Blocks from bitcoind should be sent in parallel, because there is no limit on whitelisted peers
        # requesting most recent block even if queue is full.
        with self.run_node_with_connections(
                "should be sent in parallel, because we are requesting the most recent block",
                0,
                args + ["-factormaxsendqueuesbytes=1", "-whitelist=127.0.0.1"],
                self.num_peers) as connections:

            start = time.time()
            numberOfReceivedBlocksNewBlock, numberOfRejectedMsgs = self.requestBlocks(
                connections, newBlock)
            logger.info("finished requestBlock duration %s s",
                        time.time() - start)
            assert_equal(self.num_peers, numberOfReceivedBlocksNewBlock)
            assert_equal(0, numberOfRejectedMsgs)