示例#1
0
    def run_test(self):
        node0 = NodeConnCB()

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

        NetworkThread().start()
        node0.wait_for_verack()

        # Set node time to 60 days ago
        self.nodes[0].setmocktime(int(time.time()) - 60 * 24 * 60 * 6)

        # Generating a chain of 10 blocks
        block_hashes = self.nodes[0].generate(nblocks=10)

        # Create longer chain starting 2 blocks before current tip
        height = len(block_hashes) - 2
        block_hash = block_hashes[height - 1]
        block_time = self.nodes[0].getblockheader(block_hash)["mediantime"] + 1
        new_blocks = self.build_chain(5, block_hash, height, block_time)

        # Force reorg to a longer chain
        node0.send_message(msg_headers(new_blocks))
        node0.wait_for_getdata()
        for block in new_blocks:
            node0.send_and_ping(msg_block(block))

        # Check that reorg succeeded
        assert_equal(self.nodes[0].getblockcount(), 13)

        stale_hash = int(block_hashes[-1], 16)

        # Check that getdata request for stale block succeeds
        self.send_block_request(stale_hash, node0)
        test_function = lambda: self.last_block_equals(stale_hash, node0)
        wait_until(test_function, timeout=3)

        # Check that getheader request for stale block header succeeds
        self.send_header_request(stale_hash, node0)
        test_function = lambda: self.last_header_equals(stale_hash, node0)
        wait_until(test_function, timeout=3)

        # Longest chain is extended so stale is much older than chain tip
        self.nodes[0].setmocktime(0)
        tip = self.nodes[0].generate(nblocks=1)[0]
        assert_equal(self.nodes[0].getblockcount(), 14)

        # Send getdata & getheaders to refresh last received getheader message
        block_hash = int(tip, 16)
        self.send_block_request(block_hash, node0)
        self.send_header_request(block_hash, node0)
        node0.sync_with_ping()

        # Request for very old stale block should now fail
        self.send_block_request(stale_hash, node0)
        time.sleep(3)
        assert not self.last_block_equals(stale_hash, node0)

        # Request for very old stale block header should now fail
        self.send_header_request(stale_hash, node0)
        time.sleep(3)
        assert not self.last_header_equals(stale_hash, node0)

        # Verify we can fetch very old blocks and headers on the active chain
        block_hash = int(block_hashes[2], 16)
        self.send_block_request(block_hash, node0)
        self.send_header_request(block_hash, node0)
        node0.sync_with_ping()

        self.send_block_request(block_hash, node0)
        test_function = lambda: self.last_block_equals(block_hash, node0)
        wait_until(test_function, timeout=3)

        self.send_header_request(block_hash, node0)
        test_function = lambda: self.last_header_equals(block_hash, node0)
        wait_until(test_function, timeout=3)
示例#2
0
    def run_test(self):
        block_count = 0

        # Create a P2P connections
        node0 = NodeConnCB()
        connection = NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], node0)
        node0.add_connection(connection)

        NetworkThread().start()
        # wait_for_verack ensures that the P2P connection is fully up.
        node0.wait_for_verack()

        self.chain.set_genesis_hash(int(self.nodes[0].getbestblockhash(), 16))

        getDataMessages = []

        def on_getdata(conn, message):
            getDataMessages.append(message)

        node0.on_getdata = on_getdata

        # ***** 1. *****
        # starting_blocks are needed to provide spendable outputs
        starting_blocks = MIN_TTOR_VALIDATION_DISTANCE + 1
        for i in range(starting_blocks):
            block = self.chain.next_block(block_count)
            block_count += 1
            self.chain.save_spendable_output()
            node0.send_message(msg_block(block))
        out = []
        for i in range(starting_blocks):
            out.append(self.chain.get_spendable_output())
        self.nodes[0].waitforblockheight(starting_blocks)

        tip_block_index = block_count - 1

        self.log.info("Block tip height: %d " % block_count)

        # ***** 2. *****
        # branch with blocks that do not violate TTOR
        valid_ttor_branch_height = MIN_TTOR_VALIDATION_DISTANCE + 1
        for i in range(0, valid_ttor_branch_height):
            block = self.chain.next_block(block_count,
                                          spend=out[i],
                                          extra_txns=8)
            block_count += 1
            node0.send_message(msg_block(block))
        chaintip_valid_branch = block
        self.nodes[0].waitforblockheight(starting_blocks +
                                         valid_ttor_branch_height)

        self.log.info("Node's active chain height: %d " %
                      (starting_blocks + valid_ttor_branch_height))

        # ***** 3. *****
        # branch with invalid transaction order that will try to cause a reorg
        self.chain.set_tip(tip_block_index)
        blocks_invalid_ttor = []
        headers_message = msg_headers()
        headers_message.headers = []
        invalid_ttor_branch_height = MIN_TTOR_VALIDATION_DISTANCE + 1
        for i in range(0, invalid_ttor_branch_height):
            spend = out[i]
            block = self.chain.next_block(block_count)
            add_txns = self.get_chained_transactions(spend,
                                                     num_of_transactions=10)

            # change order of transaction that output uses transaction that comes later (makes block violate TTOR)
            temp1 = add_txns[1]
            temp2 = add_txns[2]
            add_txns[1] = temp2
            add_txns[2] = temp1
            self.chain.update_block(block_count, add_txns)
            blocks_invalid_ttor.append(block)
            block_count += 1

            if (i == 0):
                first_block = block
            # wait with sending header for the last block
            if (i != MIN_TTOR_VALIDATION_DISTANCE):
                headers_message.headers.append(CBlockHeader(block))

        self.log.info("Sending %d headers..." % MIN_TTOR_VALIDATION_DISTANCE)

        node0.send_message(headers_message)
        # Wait to make sure we do not receive GETDATA messages yet.
        time.sleep(1)
        # Check that getData is not received until this chain is long at least as the active chain.
        assert_equal(len(getDataMessages), 0)

        self.log.info("Sending 1 more header...")
        # Send HEADERS message for the last block.
        headers_message.headers = [CBlockHeader(block)]
        node0.send_message(headers_message)
        node0.wait_for_getdata()
        self.log.info("Received GETDATA.")
        assert_equal(len(getDataMessages), 1)

        # Send the first block on invalid chain. Chain should be invalidated.
        node0.send_message(msg_block(first_block))

        def wait_to_invalidate_fork():
            chaintips = self.nodes[0].getchaintips()
            if len(chaintips) > 1:
                chaintips_status = [
                    chaintips[0]["status"], chaintips[1]["status"]
                ]
                if "active" in chaintips_status and "invalid" in chaintips_status:
                    active_chain_tip_hash = chaintips[0]["hash"] if chaintips[
                        0]["status"] == "active" else chaintips[1]["hash"]
                    invalid_fork_tip_hash = chaintips[0]["hash"] if chaintips[
                        0]["status"] == "invalid" else chaintips[1]["hash"]
                    assert (active_chain_tip_hash != invalid_fork_tip_hash)

                    for block in blocks_invalid_ttor:
                        if block.hash == invalid_fork_tip_hash:
                            return True
                    return False
                else:
                    return False
            else:
                return False

        wait_until(wait_to_invalidate_fork)

        # chaintip of valid branch should be active
        assert_equal(self.nodes[0].getbestblockhash(),
                     chaintip_valid_branch.hash)

        # check log file that reorg didnt happen
        disconnect_block_log = False
        for line in open(
                glob.glob(self.options.tmpdir + "/node0" +
                          "/regtest/bitcoind.log")[0]):
            if f"Disconnect block" in line:
                disconnect_block_log = True
                self.log.info("Found line: %s", line.strip())
                break

        # we should not find information about disconnecting blocks
        assert_equal(disconnect_block_log, False)

        # check log file that contains information about TTOR violation
        ttor_violation_log = False
        for line in open(
                glob.glob(self.options.tmpdir + "/node0" +
                          "/regtest/bitcoind.log")[0]):
            if f"violates TTOR order" in line:
                ttor_violation_log = True
                self.log.info("Found line: %s", line.strip())
                break

        # we should find information about TTOR being violated
        assert_equal(ttor_violation_log, True)