Esempio n. 1
0
class PBVSameBlock(BitcoinTestFramework):

    def set_test_params(self):
        self.setup_clean_chain = True
        self.num_nodes = 1
        self.chain = ChainManager()
        self.extra_args = [["-whitelist=127.0.0.1"]]

    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)

        node1 = NodeConnCB()
        connection = NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], node1)
        node1.add_connection(connection)

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

        # send one to get out of IBD state
        self.chain.set_genesis_hash(int(self.nodes[0].getbestblockhash(), 16))
        block = self.chain.next_block(block_count)
        block_count += 1
        node0.send_message(msg_block(block))

        self.nodes[0].waitforblockheight(1)

        block = self.chain.next_block(block_count)

        # set block validating status to wait after validation
        self.nodes[0].waitaftervalidatingblock(block.hash, "add")

        # make sure block hashes are in waiting list
        wait_for_waiting_blocks({block.hash}, self.nodes[0], self.log)

        node0.send_message(msg_block(block))
        node1.send_message(msg_block(block))

        # make sure we started validating blocks.
        # One is validating the other is ignored.
        wait_for_validating_blocks({block.hash}, self.nodes[0], self.log)

        # wait for the log of the ignored block.
        wait_until(lambda: check_for_log_msg(self, block.hash + " will not be considered by the current", "/node0"))

        # remove block validating status to finish validation
        self.nodes[0].waitaftervalidatingblock(block.hash, "remove")

        # wait till validation of block finishes
        node0.sync_with_ping()

        self.nodes[0].waitforblockheight(2)
        assert_equal(block.hash, self.nodes[0].getbestblockhash())
class PBVSameBlock(BitcoinTestFramework):
    def set_test_params(self):
        self.setup_clean_chain = True
        self.num_nodes = 1
        self.chain = ChainManager()
        self.extra_args = [["-whitelist=127.0.0.1"]]

    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()

        # send one to get out of IBD state
        self.chain.set_genesis_hash(int(self.nodes[0].getbestblockhash(), 16))

        ancestor_block_hash = self.nodes[0].getbestblockhash()

        parent_block = self.chain.next_block(block_count)
        block_count += 1

        headers_message = msg_headers()
        headers_message.headers = [CBlockHeader(parent_block)]
        connection.cb.send_message(headers_message)

        child_block = self.chain.next_block(block_count)
        node0.send_message(msg_block(child_block))

        # wait till validation of block finishes
        node0.sync_with_ping()

        assert_equal(ancestor_block_hash, self.nodes[0].getbestblockhash())

        self.stop_node(0)
        self.start_node(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()

        assert_equal(ancestor_block_hash, self.nodes[0].getbestblockhash())

        node0.send_message(msg_block(parent_block))

        # wait till validation of block finishes
        node0.sync_with_ping()

        assert_equal(child_block.hash, self.nodes[0].getbestblockhash())
class WaitAfterValidation(BitcoinTestFramework):
    def set_test_params(self):
        self.setup_clean_chain = True
        self.num_nodes = 1
        self.chain = ChainManager()
        self.extra_args = [["-whitelist=127.0.0.1"]]

    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()
        node0.wait_for_verack()

        self.chain.set_genesis_hash(int(self.nodes[0].getbestblockhash(), 16))
        block = self.chain.next_block(block_count)
        block_count += 1
        node0.send_message(msg_block(block))

        block = self.chain.next_block(block_count)
        self.log.info(f"block hash: {block.hash}")
        self.nodes[0].waitaftervalidatingblock(block.hash, "add")

        # make sure block hash is in waiting list
        wait_for_waiting_blocks({block.hash}, self.nodes[0], self.log)

        self.log.info("sending block")
        node0.send_message(msg_block(block))

        # make sure we started validating block
        wait_for_validating_blocks({block.hash}, self.nodes[0], self.log)

        # sleep a bit and check that in the meantime validation hasn't proceeded
        time.sleep(1)
        assert (block.hash != self.nodes[0].getbestblockhash())

        # after validating the block we release its waiting status
        self.nodes[0].waitaftervalidatingblock(block.hash, "remove")

        # wait till validation of block or blocks finishes
        node0.sync_with_ping()

        assert_equal(block.hash, self.nodes[0].getbestblockhash())
Esempio n. 4
0
class PBVProcessingOrder(BitcoinTestFramework):
    def set_test_params(self):
        self.setup_clean_chain = True
        self.num_nodes = 1
        self.chain = ChainManager()
        self.extra_args = [["-whitelist=127.0.0.1"]]

    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))
        block = self.chain.next_block(block_count)
        block_count += 1
        self.chain.save_spendable_output()
        node0.send_message(msg_block(block))

        for i in range(100):
            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(100):
            out.append(self.chain.get_spendable_output())

        self.log.info("waiting for block height 101 via rpc")
        self.nodes[0].waitforblockheight(101)

        # wait till validation of block or blocks finishes
        node0.sync_with_ping()

        block1 = self.chain.next_block(block_count, spend=out[0], extra_txns=8)
        block_count += 1
        # send block but block him at validation point
        self.nodes[0].waitaftervalidatingblock(block1.hash, "add")
        node0.send_message(msg_block(block1))
        self.log.info(f"block1 hash: {block1.hash}")

        # make sure block hash is in waiting list
        wait_for_waiting_blocks({block1.hash}, self.nodes[0], self.log)

        # send child block
        block2 = self.chain.next_block(block_count,
                                       spend=out[1],
                                       extra_txns=10)
        block_count += 1
        node0.send_message(msg_block(block2))
        self.log.info(f"block2 hash: {block2.hash}")

        def wait_for_log():
            line_text = block2.hash + " will not be considered by the current"
            for line in open(
                    glob.glob(self.options.tmpdir + "/node0" +
                              "/regtest/bitcoind.log")[0]):
                if line_text in line:
                    self.log.info("Found line: %s", line)
                    return True
            return False

        wait_until(wait_for_log)

        self.nodes[0].waitaftervalidatingblock(block1.hash, "remove")

        # wait till validation of block or blocks finishes
        node0.sync_with_ping()

        # block that arrived last on competing chain should be active
        assert_equal(block2.hash, self.nodes[0].getbestblockhash())
class PBVPreciousBlock(BitcoinTestFramework):

    def set_test_params(self):
        self.setup_clean_chain = True
        self.num_nodes = 1
        self.chain = ChainManager()
        self.extra_args = [["-whitelist=127.0.0.1"]]

    def run_test(self):
        block_count = 0

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

        node1 = NodeConnCB()
        connection = NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], node1)
        node1.add_connection(connection)

        node2 = NodeConnCB()
        connection = NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], node2)
        node2.add_connection(connection)

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

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

        _, out, block_count = prepare_init_chain(self.chain, 101, 100, block_0=False, start_block=0, node=node0)

        self.log.info("waiting for block height 101 via rpc")
        self.nodes[0].waitforblockheight(101)

        tip_block_num = block_count - 1

        block2_hard = self.chain.next_block(block_count, spend=out[0], extra_txns=8)
        block_count += 1
        self.chain.set_tip(tip_block_num)

        block3_easier = self.chain.next_block(block_count, spend=out[0], extra_txns=2)
        block_count += 1
        self.chain.set_tip(tip_block_num)

        block4_hard = self.chain.next_block(block_count, spend=out[0], extra_txns=10)
        block_count += 1

        # send two "hard" blocks, with waitaftervalidatingblock we artificially
        # extend validation time.
        self.log.info(f"hard block2 hash: {block2_hard.hash}")
        self.nodes[0].waitaftervalidatingblock(block2_hard.hash, "add")
        self.log.info(f"hard block4 hash: {block4_hard.hash}")
        self.nodes[0].waitaftervalidatingblock(block4_hard.hash, "add")
        # make sure block hashes are in waiting list
        wait_for_waiting_blocks({block2_hard.hash, block4_hard.hash}, self.nodes[0], self.log)

        node0.send_message(msg_block(block2_hard))
        node1.send_message(msg_block(block4_hard))
 
        # make sure we started validating blocks
        wait_for_validating_blocks({block2_hard.hash, block4_hard.hash}, self.nodes[0], self.log)

        self.log.info(f"easier hash: {block3_easier.hash}")
        node2.send_message(msg_block(block3_easier))

        self.nodes[0].waitforblockheight(102)
        assert_equal(block3_easier.hash, self.nodes[0].getbestblockhash())

        # now we can remove waiting status from blocks and finish their validation
        self.nodes[0].waitaftervalidatingblock(block2_hard.hash, "remove")
        self.nodes[0].waitaftervalidatingblock(block4_hard.hash, "remove")

        # wait till validation of block or blocks finishes
        node0.sync_with_ping()

        # now we want our precious block to be one of the harder blocks (block4_hard)
        self.nodes[0].preciousblock(block4_hard.hash)
        assert_equal(block4_hard.hash, self.nodes[0].getbestblockhash())
Esempio n. 6
0
class PBVFirstValidActive(BitcoinTestFramework):
    def set_test_params(self):
        self.setup_clean_chain = True
        self.num_nodes = 1
        self.chain = ChainManager()
        self.extra_args = [["-whitelist=127.0.0.1"]]

    def run_test(self):
        block_count = 0

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

        node1 = NodeConnCB()
        connection = NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], node1)
        node1.add_connection(connection)

        node2 = NodeConnCB()
        connection = NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], node2)
        node2.add_connection(connection)

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

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

        _, out, block_count = prepare_init_chain(self.chain,
                                                 101,
                                                 100,
                                                 block_0=False,
                                                 start_block=0,
                                                 node=node0)

        self.log.info("waiting for block height 101 via rpc")
        self.nodes[0].waitforblockheight(101)

        tip_block_num = block_count - 1

        block2_hard = self.chain.next_block(block_count,
                                            spend=out[0],
                                            extra_txns=8)
        block_count += 1

        self.chain.set_tip(tip_block_num)

        block3_easier = self.chain.next_block(block_count,
                                              spend=out[0],
                                              extra_txns=2)
        easier_block_num = block_count
        block_count += 1

        self.chain.set_tip(tip_block_num)

        block4_hard = self.chain.next_block(block_count,
                                            spend=out[0],
                                            extra_txns=10)
        block_count += 1

        # make child block of easier block
        self.chain.set_tip(easier_block_num)
        block5 = self.chain.next_block(block_count)
        block5_num = block_count
        block_count += 1

        # send two "hard" blocks, with waitaftervalidatingblock we artificially
        # extend validation time.
        self.log.info(f"hard block2 hash: {block2_hard.hash}")
        self.nodes[0].waitaftervalidatingblock(block2_hard.hash, "add")
        self.log.info(f"hard block4 hash: {block4_hard.hash}")
        self.nodes[0].waitaftervalidatingblock(block4_hard.hash, "add")

        # make sure block hashes are in waiting list
        wait_for_waiting_blocks({block2_hard.hash, block4_hard.hash},
                                self.nodes[0], self.log)

        # send blocks via different p2p connection
        node0.send_message(msg_block(block2_hard))
        node1.send_message(msg_block(block4_hard))

        # make sure we started validating blocks
        wait_for_validating_blocks({block2_hard.hash, block4_hard.hash},
                                   self.nodes[0], self.log)

        # send easier block through different p2p connection too
        node2.send_message(msg_block(block3_easier))
        self.log.info(f"easier block hash: {block3_easier.hash}")
        self.nodes[0].waitforblockheight(102)
        assert_equal(block3_easier.hash, self.nodes[0].getbestblockhash())

        # child block of block3_easier
        self.log.info(f"child block hash: {block5.hash}")
        self.nodes[0].waitaftervalidatingblock(block5.hash, "add")

        # make sure child block is in waiting list and then send it
        wait_for_not_validating_blocks({block5.hash}, self.nodes[0], self.log)
        node2.send_message(msg_block(block5))

        # make sure we started validating child block
        wait_for_validating_blocks({block5.hash}, self.nodes[0], self.log)

        # finish validation on block2_hard
        self.nodes[0].waitaftervalidatingblock(block2_hard.hash, "remove")
        wait_for_not_validating_blocks({block2_hard.hash}, self.nodes[0],
                                       self.log)

        # finish validation on child block
        self.nodes[0].waitaftervalidatingblock(block5.hash, "remove")
        wait_for_not_validating_blocks({block5.hash}, self.nodes[0], self.log)

        # block5 should be active at this point
        assert_equal(block5.hash, self.nodes[0].getbestblockhash())

        # finish validation on block4_hard
        self.nodes[0].waitaftervalidatingblock(block4_hard.hash, "remove")
        wait_for_not_validating_blocks({block4_hard.hash}, self.nodes[0],
                                       self.log)

        # block5 should still be active at this point
        assert_equal(block5.hash, self.nodes[0].getbestblockhash())

        # Make three siblings and send them via same p2p connection.
        block6_hard = self.chain.next_block(block_count,
                                            spend=out[1],
                                            extra_txns=8)
        block_count += 1

        self.chain.set_tip(block5_num)

        block7_easier = self.chain.next_block(block_count,
                                              spend=out[1],
                                              extra_txns=2)
        block_count += 1

        self.chain.set_tip(block5_num)

        block8_hard = self.chain.next_block(block_count,
                                            spend=out[1],
                                            extra_txns=10)
        block_count += 1

        self.log.info(f"hard block6 hash: {block6_hard.hash}")
        self.nodes[0].waitaftervalidatingblock(block6_hard.hash, "add")
        self.log.info(f"hard block8 hash: {block8_hard.hash}")
        self.nodes[0].waitaftervalidatingblock(block8_hard.hash, "add")
        # make sure block hashes are in waiting list
        wait_for_waiting_blocks({block6_hard.hash, block8_hard.hash},
                                self.nodes[0], self.log)

        # sending blocks via same p2p connection
        node0.send_message(msg_block(block6_hard))
        node0.send_message(msg_block(block8_hard))

        # make sure we started validating blocks
        wait_for_validating_blocks({block6_hard.hash, block8_hard.hash},
                                   self.nodes[0], self.log)

        # send easier block through same p2p connection too
        node0.send_message(msg_block(block7_easier))

        self.nodes[0].waitforblockheight(104)
        assert_equal(block7_easier.hash, self.nodes[0].getbestblockhash())

        # now we can remove waiting status from blocks and finish their validation
        self.nodes[0].waitaftervalidatingblock(block6_hard.hash, "remove")
        self.nodes[0].waitaftervalidatingblock(block8_hard.hash, "remove")

        # wait till validation of block or blocks finishes
        node0.sync_with_ping()

        # easier block should still be on tip
        assert_equal(block7_easier.hash, self.nodes[0].getbestblockhash())
Esempio n. 7
0
class BSVCheckTTORViolation(BitcoinTestFramework):
    def set_test_params(self):
        self.setup_clean_chain = True
        self.num_nodes = 1
        self.extra_args = [["-whitelist=127.0.0.1"]]
        self.chain = ChainManager()

    # generating transactions in order so first transaction's output will be input for second transaction
    def get_chained_transactions(self, spend, num_of_transactions):
        money_to_spend = 5000000000
        txns = []
        for _ in range(0, num_of_transactions):
            money_to_spend = money_to_spend - 1  # one satoshi to fee
            tx2 = create_transaction(spend.tx, spend.n, b"", money_to_spend,
                                     CScript([OP_TRUE]))
            txns.append(tx2)

            money_to_spend = money_to_spend - 1
            tx3 = create_transaction(tx2,
                                     0,
                                     b"",
                                     money_to_spend,
                                     scriptPubKey=CScript([OP_TRUE]))
            txns.append(tx3)

            spend = PreviousSpendableOutput(tx3, 0)
        return txns

    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)
Esempio n. 8
0
class PBVReorg(BitcoinTestFramework):

    def set_test_params(self):
        self.setup_clean_chain = True
        self.num_nodes = 1
        self.chain = ChainManager()
        self.extra_args = [["-whitelist=127.0.0.1"]]

    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)

        node1 = NodeConnCB()
        connection = NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], node1)
        node1.add_connection(connection)

        node2 = NodeConnCB()
        connection = NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], node2)
        node2.add_connection(connection)

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

        self.chain.set_genesis_hash(int(self.nodes[0].getbestblockhash(), 16))
        block = self.chain.next_block(block_count)
        block_count += 1
        self.chain.save_spendable_output()
        node0.send_message(msg_block(block))

        for i in range(100):
            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(100):
            out.append(self.chain.get_spendable_output())

        self.log.info("waiting for block height 101 via rpc")
        self.nodes[0].waitforblockheight(101)

        tip_block_num = block_count-1

        # left branch
        block2 = self.chain.next_block(block_count, spend=out[0], extra_txns=8)
        block2_num = block_count
        block_count += 1
        node0.send_message(msg_block(block2))
        self.log.info(f"block2 hash: {block2.hash}")

        self.nodes[0].waitforblockheight(102)

        # send blocks 3,4 for parallel validation on left branch
        block3 = self.chain.next_block(block_count, spend=out[1], extra_txns=10)
        block_count += 1

        self.chain.set_tip(block2_num)

        block4 = self.chain.next_block(block_count, spend=out[1], extra_txns=8)
        block_count += 1

        # send two "hard" blocks, with waitaftervalidatingblock we artificially
        # extend validation time.
        self.log.info(f"block3 hash: {block3.hash}")
        self.nodes[0].waitaftervalidatingblock(block3.hash, "add")
        self.log.info(f"block4 hash: {block4.hash}")
        self.nodes[0].waitaftervalidatingblock(block4.hash, "add")
        # make sure block hashes are in waiting list
        wait_for_waiting_blocks({block3.hash, block4.hash}, self.nodes[0], self.log)

        node0.send_message(msg_block(block3))
        node2.send_message(msg_block(block4))

        # make sure we started validating blocks
        wait_for_validating_blocks({block3.hash, block4.hash}, self.nodes[0], self.log)

        # right branch
        self.chain.set_tip(tip_block_num)
        block5 = self.chain.next_block(block_count, spend=out[0], extra_txns=10)
        block_count += 1
        node1.send_message(msg_block(block5))
        self.log.info(f"block5 hash: {block5.hash}")

        # and two blocks to extend second branch to cause reorg
        # - they must be sent from the same node as otherwise they will be
        #   rejected with "prev block not found"
        block6 = self.chain.next_block(block_count)
        node1.send_message(msg_block(block6))
        self.log.info(f"block6 hash: {block6.hash}")
        block_count += 1

        block7 = self.chain.next_block(block_count)
        node1.send_message(msg_block(block7))
        self.log.info(f"block7 hash: {block7.hash}")
        block_count += 1

        self.nodes[0].waitforblockheight(104)
        assert_equal(block7.hash, self.nodes[0].getbestblockhash())

        self.log.info("releasing wait status on parallel blocks to finish their validation")
        self.nodes[0].waitaftervalidatingblock(block3.hash, "remove")
        self.nodes[0].waitaftervalidatingblock(block4.hash, "remove")

        # wait till validation of block or blocks finishes
        node0.sync_with_ping()

        # block that arrived last on competing chain should be active
        assert_equal(block7.hash, self.nodes[0].getbestblockhash())
class FrozenTXOTransactionMining(BitcoinTestFramework):

    def set_test_params(self):
        self.setup_clean_chain = True
        self.num_nodes = 2
        self.chain = ChainManager()
        self.extra_args = [["-disablesafemode=1", "-minrelaytxfee=0", "-limitfreerelay=999999", "-blockmintxfee=0", "-blockassembler=legacy"],
                           ["-disablesafemode=1", "-minrelaytxfee=0", "-limitfreerelay=999999", "-blockmintxfee=0", "-blockassembler=journaling"]]
        self.block_count = 0

    def init_(self, nodes_count):
        nodes = []

        for no in range(0, nodes_count):
            # Create a P2P connections
            node = NodeConnCB()
            connection = NodeConn('127.0.0.1', p2p_port(no), self.nodes[no], node)
            node.add_connection(connection)
            nodes.append(node)

        NetworkThread().start()

        for no in range(0, nodes_count):
            # wait_for_verack ensures that the P2P connection is fully up.
            nodes[no].wait_for_verack()

        self.init_chain_(nodes[0], nodes_count)

        return nodes

    def init_chain_(self, leading_node, nodes_count):
        self.chain.set_genesis_hash(int(self.nodes[0].getbestblockhash(), 16))
        block = self.chain.next_block(self.block_count)
        self.block_count += 1
        self.chain.save_spendable_output()
        leading_node.send_message(msg_block(block))

        for i in range(100):
            block = self.chain.next_block(self.block_count)
            self.block_count += 1
            self.chain.save_spendable_output()
            leading_node.send_message(msg_block(block))

        self.log.info("Waiting for block height 101 via rpc")

        for no in range(0, nodes_count):
            self.nodes[no].waitforblockheight(101)

    def mine_and_send_block_(self, tx, node):
        block = self.chain.next_block(self.block_count)

        self.chain.update_block(self.block_count, [tx])
        node.send_and_ping(msg_block(block))
        self.block_count += 1

        self.log.debug(f"attempted mining block: {block.hash}")

        assert_equal(block.hash, self.nodes[0].getbestblockhash())

    def create_tx_(self, tx_out, unlock, lock):
        unlock_script = b'' if callable(unlock) else unlock
        tx = create_transaction(tx_out.tx, tx_out.n, unlock_script, 1, lock)

        if callable(unlock):
            tx.vin[0].scriptSig = unlock(tx, tx_out.tx)
            tx.calc_sha256()

        return tx

    def check_log(self, node, line_text):
        for line in open(glob.glob(node.datadir + "/regtest/bitcoind.log")[0]):
            if re.search(line_text, line) is not None:
                self.log.debug("Found line in bitcoind.log: %s", line.strip())
                return True
        return False

    def run_test(self):

        (node0, node1) = self.init_(2)

        out = self.chain.get_spendable_output()

        freeze_tx = self.create_tx_(out, b'', CScript([OP_TRUE]))
        self.log.info(f"Mining block with transaction {freeze_tx.hash} whose output will be frozen later")
        self.mine_and_send_block_(freeze_tx, node0)

        spend_frozen_tx = self.create_tx_(PreviousSpendableOutput(freeze_tx, 0), b'', CScript([OP_TRUE]))
        self.log.info(f"Sending transaction {spend_frozen_tx.hash} spending TXO {freeze_tx.hash},0")
        node0.send_and_ping(msg_tx(spend_frozen_tx))

        spend_frozen_tx2 = self.create_tx_(PreviousSpendableOutput(spend_frozen_tx, 0), b'', CScript([OP_TRUE]))
        self.log.info(f"Sending transaction {spend_frozen_tx2.hash} spending TXO {spend_frozen_tx.hash},0")
        node0.send_and_ping(msg_tx(spend_frozen_tx2))

        sync_mempools(self.nodes)

        self.log.info("Checking that transactions were accepted on both nodes")
        for no in range(0, 2):
            mp = self.nodes[no].getrawmempool()
            assert_equal(len(mp), 2)
            assert(spend_frozen_tx.hash in mp and spend_frozen_tx2.hash in mp)

            template_txns = self.nodes[no].getblocktemplate()["transactions"]
            assert_equal(len(template_txns), 2)
            bt = [template_txns[0]['txid'], template_txns[1]['txid']]
            assert(spend_frozen_tx.hash in mp and spend_frozen_tx2.hash in bt)

        current_height = self.nodes[0].getblockcount()
        self.log.info(f"Current height: {current_height}")

        enforce_height = current_height + 2
        self.log.info(f"Freezing TXO {freeze_tx.hash},0 on consensus blacklist at height {enforce_height} on both nodes")
        for no in range(0, 2):
            self.nodes[no].addToConsensusBlacklist({
                "funds": [
                {
                    "txOut" : {
                        "txId" : freeze_tx.hash,
                        "vout" : 0
                    },
                    "enforceAtHeight": [{"start": enforce_height}],
                    "policyExpiresWithConsensus": False
                }]
            });

        self.log.info("Checking that both transactions were removed from mempool and block template on both nodes")
        for no in range(0, 2):
            assert_equal(self.nodes[no].getrawmempool(), [])
            assert_equal(self.nodes[no].getblocktemplate()["transactions"], [])

        enforce_stop_height = enforce_height + 1
        self.log.info(f"Unfreezing TXO {freeze_tx.hash},0 from consensus and policy blacklists at height {enforce_stop_height} on both nodes")
        for no in range(0, 2):
            self.nodes[no].addToConsensusBlacklist({
                "funds": [
                {
                    "txOut" : {
                        "txId" : freeze_tx.hash,
                        "vout" : 0
                    },
                    "enforceAtHeight": [{"start": enforce_height, "stop": enforce_stop_height}],
                    "policyExpiresWithConsensus": True
                }]
            });

        self.log.info(f"Generating blocks so that mempool reaches height {enforce_stop_height+1}")
        while self.nodes[0].getblockcount() < enforce_stop_height:
            self.nodes[0].generate(1)
        sync_blocks(self.nodes)

        spend_unfrozen_tx3 = self.create_tx_(PreviousSpendableOutput(freeze_tx, 0), b'', CScript([OP_NOP, OP_TRUE]))
        self.log.info(f"Sending transaction {spend_unfrozen_tx3.hash} spending now unfrozen TXO {freeze_tx.hash},0")
        node0.send_and_ping(msg_tx(spend_unfrozen_tx3))

        spend_unfrozen_tx4 = self.create_tx_(PreviousSpendableOutput(spend_unfrozen_tx3, 0), b'', CScript([OP_NOP, OP_TRUE]))
        self.log.info(f"Sending transaction {spend_unfrozen_tx4.hash} spending TXO {spend_unfrozen_tx3.hash},0")
        node0.send_and_ping(msg_tx(spend_unfrozen_tx4))

        sync_mempools(self.nodes)

        self.log.info("Checking that transactions were accepted on both nodes")
        for no in range(0, 2):
            mp = self.nodes[no].getrawmempool()
            assert_equal(len(mp), 2)
            assert(spend_unfrozen_tx3.hash in mp and spend_unfrozen_tx4.hash in mp)

            template_txns = self.nodes[no].getblocktemplate()["transactions"]
            assert_equal(len(template_txns), 2)
            bt = [template_txns[0]['txid'], template_txns[1]['txid']]
            assert(spend_unfrozen_tx3.hash in mp and spend_unfrozen_tx4.hash in bt)

        self.log.info("Invalidating chain tip on both nodes to force reorg back one block")
        for no in range(0, 2):
            self.nodes[no].invalidateblock( self.nodes[no].getbestblockhash() )
            assert(self.nodes[no].getblockcount() == enforce_height)

        mempool_scan_check_log_string = "Removing any transactions that spend TXOs, which were previously not considered policy frozen"
        self.log.info("Checking that transactions are still in mempool on both nodes")
        for no in range(0, 2):
            mp = self.nodes[no].getrawmempool()
            assert_equal(len(mp), 2)
            assert(spend_unfrozen_tx3.hash in mp and spend_unfrozen_tx4.hash in mp)

            template_txns = self.nodes[no].getblocktemplate()["transactions"]
            assert_equal(len(template_txns), 2)
            bt = [template_txns[0]['txid'], template_txns[1]['txid']]
            assert(spend_unfrozen_tx3.hash in mp and spend_unfrozen_tx4.hash in bt)

            # bitcoind sould not unnecessarily scan whole mempool to find transactions that spend TXOs, which could become frozen again.
            assert( not self.check_log(self.nodes[no], mempool_scan_check_log_string) )

        self.log.info("Invalidating chain tip on both nodes to force reorg back to height where TXO is still frozen")
        for no in range(0, 2):
            self.nodes[no].invalidateblock( self.nodes[no].getbestblockhash() )
            assert(self.nodes[no].getblockcount() == enforce_height - 1)

        self.log.info("Checking that both transactions were removed from mempool and block template on both nodes")
        for no in range(0, 2):
            assert_equal(self.nodes[no].getrawmempool(), [])
            assert_equal(self.nodes[no].getblocktemplate()["transactions"], [])

            # bitcoind now should scan whole mempool.
            assert( self.check_log(self.nodes[no], mempool_scan_check_log_string) )

        self.log.info("Unfreezing all frozen outputs on both nodes")
        for no in range(0, 2):
            self.nodes[no].invalidateblock( self.nodes[no].getbestblockhash() )
            result = self.nodes[no].clearBlacklists({ "removeAllEntries" : True })
            assert_equal(result["numRemovedEntries"], 1)

        self.log.info(f"Freezing TXO {freeze_tx.hash},0 on policy blacklist on node0 (but not on node1)")
        result = self.nodes[0].addToPolicyBlacklist({
            "funds": [
            {
                "txOut" : {
                    "txId" : freeze_tx.hash,
                    "vout" : 0
                }
            }]
        });
        assert_equal(result["notProcessed"], [])

        spend_frozen_tx1a = self.create_tx_(PreviousSpendableOutput(freeze_tx, 0), b'', CScript([OP_NOP, OP_NOP, OP_TRUE]))
        self.log.info(f"Sending transaction {spend_frozen_tx1a.hash} spending not frozen TXO {freeze_tx.hash},0 to node1")
        node1.send_and_ping(msg_tx(spend_frozen_tx1a))

        spend_frozen_tx2a = self.create_tx_(PreviousSpendableOutput(spend_frozen_tx1a, 0), b'', CScript([OP_NOP, OP_NOP, OP_TRUE]))
        self.log.info(f"Sending transaction {spend_frozen_tx2a.hash} spending TXO {spend_frozen_tx1a.hash},0 to node1")
        node1.send_and_ping(msg_tx(spend_frozen_tx2a))

        self.log.info("Checking that transactions were accepted on node1")
        mp = self.nodes[1].getrawmempool()
        assert_equal(len(mp), 2)
        assert(spend_frozen_tx1a.hash in mp and spend_frozen_tx2a.hash in mp)
        time.sleep(6) # need to wait >5s for the block assembler to create new block
        template_txns = self.nodes[1].getblocktemplate()["transactions"]
        assert_equal(len(template_txns), 2)
        bt = [template_txns[0]['txid'], template_txns[1]['txid']]
        assert(spend_frozen_tx1a.hash in mp and spend_frozen_tx2a.hash in bt)
        
        self.log.info("Checking that transactions are not present in mempool on node0")
        assert_equal(self.nodes[0].getrawmempool(), [])

        self.log.info("Generate block that contains both transactions on node1")
        height_before_block_spending_policy_frozen_txo = self.nodes[1].getblockcount()
        hash_block_spending_policy_frozen_txo = self.nodes[1].generate(1)[0]
        sync_blocks(self.nodes)
        for no in range(0, 2):
            assert(self.nodes[no].getblockcount() == height_before_block_spending_policy_frozen_txo + 1)
            assert_equal(self.nodes[no].getrawmempool(), [])
            assert_equal(self.nodes[no].getblocktemplate()["transactions"], [])

        self.log.info("Invalidating chain tip on both nodes to force reorg back one block")
        for no in range(0, 2):
            self.nodes[no].invalidateblock( hash_block_spending_policy_frozen_txo )
            assert(self.nodes[no].getblockcount() == height_before_block_spending_policy_frozen_txo)

        self.log.info("Checking that transactions are not present in mempool on node0")
        assert_equal(self.nodes[0].getrawmempool(), [])
        assert_equal(self.nodes[0].getblocktemplate()["transactions"], [])

        self.log.info("Checking that transactions were put back to mempool on node1")
        mp = self.nodes[1].getrawmempool()
        assert_equal(len(mp), 2)
        assert(spend_frozen_tx1a.hash in mp and spend_frozen_tx2a.hash in mp)
        template_txns = self.nodes[1].getblocktemplate()["transactions"]
        assert_equal(len(template_txns), 2)
        bt = [template_txns[0]['txid'], template_txns[1]['txid']]
        assert(spend_frozen_tx1a.hash in mp and spend_frozen_tx2a.hash in bt)
class PBVReorg(BitcoinTestFramework):
    def set_test_params(self):
        self.setup_clean_chain = True
        self.num_nodes = 1
        self.chain = ChainManager()
        self.extra_args = [["-whitelist=127.0.0.1", "-relaypriority=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)

        node1 = NodeConnCB()
        connection = NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], node1)
        node1.add_connection(connection)

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

        self.chain.set_genesis_hash(int(self.nodes[0].getbestblockhash(), 16))
        block = self.chain.next_block(block_count)
        block_count += 1
        self.chain.save_spendable_output()
        node0.send_message(msg_block(block))

        num_blocks = 150
        for i in range(num_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(num_blocks):
            out.append(self.chain.get_spendable_output())

        self.log.info("waiting for block height 151 via rpc")
        self.nodes[0].waitforblockheight(num_blocks + 1)

        tip_block_num = block_count - 1

        # left branch
        block2 = self.chain.next_block(block_count,
                                       spend=out[0:9],
                                       extra_txns=8)
        block_count += 1
        node0.send_message(msg_block(block2))
        self.log.info(f"block2 hash: {block2.hash}")

        self.nodes[0].waitforblockheight(num_blocks + 2)

        # send blocks 3,4 for parallel validation on left branch
        self.chain.set_tip(tip_block_num)
        block3 = self.chain.next_block(block_count,
                                       spend=out[10:19],
                                       extra_txns=10)
        block_count += 1

        block4 = self.chain.next_block(block_count,
                                       spend=out[20:29],
                                       extra_txns=8)
        block_count += 1

        # send two "hard" blocks, with waitaftervalidatingblock we artificially
        # extend validation time.
        self.log.info(f"block3 hash: {block3.hash}")
        self.log.info(f"block4 hash: {block4.hash}")
        self.nodes[0].waitaftervalidatingblock(block4.hash, "add")

        # make sure block hashes are in waiting list
        wait_for_waiting_blocks({block4.hash}, self.nodes[0], self.log)

        node1.send_message(msg_block(block3))
        node1.send_message(msg_block(block4))

        # make sure we started validating blocks
        wait_for_validating_blocks({block4.hash}, self.nodes[0], self.log)

        # right branch
        self.chain.set_tip(tip_block_num)
        block5 = self.chain.next_block(block_count)
        # Add some txns from block2 & block3 to block5, just to check that they get
        # filtered from the mempool and not re-added
        block5_duplicated_txns = block3.vtx[1:3] + block2.vtx[1:3]
        self.chain.update_block(block_count, block5_duplicated_txns)
        block_count += 1
        node0.send_message(msg_block(block5))
        self.log.info(f"block5 hash: {block5.hash}")

        # and two blocks to extend second branch to cause reorg
        # - they must be sent from the same node as otherwise they will be
        #   rejected with "prev block not found" as we don't wait for the first
        #   block to arrive so there is a race condition which block is seen
        #   first when using multiple connections
        block6 = self.chain.next_block(block_count)
        node0.send_message(msg_block(block6))
        self.log.info(f"block6 hash: {block6.hash}")
        block_count += 1
        block7 = self.chain.next_block(block_count)
        node0.send_message(msg_block(block7))
        self.log.info(f"block7 hash: {block7.hash}")
        block_count += 1

        self.nodes[0].waitforblockheight(num_blocks + 4)
        assert_equal(block7.hash, self.nodes[0].getbestblockhash())

        self.log.info(
            "releasing wait status on parallel blocks to finish their validation"
        )
        self.nodes[0].waitaftervalidatingblock(block4.hash, "remove")

        # wait till validation of block or blocks finishes
        node0.sync_with_ping()

        # block that arrived last on competing chain should be active
        assert_equal(block7.hash, self.nodes[0].getbestblockhash())

        # make sure that transactions from block2 and 3 (except coinbase, and those also
        # in block 5) are in mempool
        not_expected_in_mempool = set()
        for txn in block5_duplicated_txns:
            not_expected_in_mempool.add(txn.hash)
        expected_in_mempool = set()
        for txn in block2.vtx[1:] + block3.vtx[1:]:
            expected_in_mempool.add(txn.hash)
        expected_in_mempool = expected_in_mempool.difference(
            not_expected_in_mempool)

        mempool = self.nodes[0].getrawmempool()
        assert_equal(expected_in_mempool, set(mempool))
Esempio n. 11
0
class FrozenTXOTransactionFreeze(BitcoinTestFramework):

    def set_test_params(self):
        self.setup_clean_chain = True
        self.num_nodes = 1
        self.chain = ChainManager()
        self.extra_args = [["-whitelist=127.0.0.1", "-minrelaytxfee=0", "-limitfreerelay=999999"]]
        self.block_count = 0

    def _init(self):
        node_no = 0

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

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

        self.chain.set_genesis_hash(int(self.nodes[node_no].getbestblockhash(), 16))
        block = self.chain.next_block(self.block_count)
        self.block_count += 1
        self.chain.save_spendable_output()
        node.send_message(msg_block(block))

        for i in range(100):
            block = self.chain.next_block(self.block_count)
            self.block_count += 1
            self.chain.save_spendable_output()
            node.send_message(msg_block(block))

        self.log.info("Waiting for block height 101 via rpc")
        self.nodes[node_no].waitforblockheight(101)

        return node

    def _create_tx(self, tx_out, unlock, lock):
        unlock_script = b'' if callable(unlock) else unlock
        tx = create_transaction(tx_out.tx, tx_out.n, unlock_script, 1, lock)

        if callable(unlock):
            tx.vin[0].scriptSig = unlock(tx, tx_out.tx)
            tx.calc_sha256()

        return tx

    def _mine_and_send_block(self, tx, node, expect_reject = False):
        block = self.chain.next_block(self.block_count)

        self.chain.update_block(self.block_count, [tx] if tx else [])

        self.log.debug(f"attempting mining block: {block.hash}")

        node.send_block(block, expect_reject)
        self.block_count += 1

    def _remove_last_block(self):
        # remove last block from chain manager
        del self.chain.block_heights[self.chain.blocks[self.block_count-1].sha256]
        del self.chain.blocks[self.block_count-1]
        self.block_count -= 1
        self.chain.set_tip(self.block_count-1)


    def _test_policy_freeze(self, spendable_out, node):
        self.log.info("*** Performing policy freeze checks")

        freeze_tx = self._create_tx(spendable_out, b'', CScript([OP_TRUE]))
        self.log.info(f"Mining block with transaction {freeze_tx.hash} whose output will be frozen later")
        self._mine_and_send_block(freeze_tx, node)

        self.log.info(f"Freezing TXO {freeze_tx.hash},0 on policy blacklist")
        result = node.rpc.addToPolicyBlacklist({
            "funds": [
            {
                "txOut" : {
                    "txId" : freeze_tx.hash,
                    "vout" : 0
                }
            }]
        });
        assert_equal(result["notProcessed"], [])

        spend_frozen_tx = self._create_tx(PreviousSpendableOutput(freeze_tx, 0), b'', CScript([OP_TRUE]))
        self.log.info(f"Sending transaction spending frozen TXO {freeze_tx.hash},0 and checking that it is rejected")
        # must not be accepted as parent transaction is frozen
        node.send_tx(spend_frozen_tx, True)
        assert_equal(node.rpc.getrawmempool(), [])
        assert(node.check_frozen_tx_log(spend_frozen_tx.hash));
        assert(node.check_log("Transaction was rejected because it tried to spend a frozen transaction output.*"+spend_frozen_tx.hash));

        self.log.info(f"Mining block with transaction {spend_frozen_tx.hash} spending frozen TXO {freeze_tx.hash},0 and checking that is accepted")
        self._mine_and_send_block(spend_frozen_tx, node)
        # block is still accepted as consensus freeze is not in effect
        assert_equal(node.rpc.getbestblockhash(), self.chain.tip.hash)

        spend_frozen_tx2 = self._create_tx(PreviousSpendableOutput(spend_frozen_tx, 0), b'', CScript([OP_TRUE]))
        self.log.info(f"Sending transaction {spend_frozen_tx2.hash} spending TXO {spend_frozen_tx.hash},0 that is not yet frozen")
        node.send_tx(spend_frozen_tx2)
        assert_equal(node.rpc.getrawmempool(), [spend_frozen_tx2.hash])

        self.log.info(f"Freezing TXO {spend_frozen_tx.hash},0 on policy blacklist")
        result = node.rpc.addToPolicyBlacklist({
            "funds": [
            {
                "txOut" : {
                    "txId" : spend_frozen_tx.hash,
                    "vout" : 0
                }
            }]
        });
        assert_equal(result["notProcessed"], [])

        self.log.info(f"Checking that transaction {spend_frozen_tx2.hash} is removed from mempool")
        assert_equal(node.rpc.getrawmempool(), [])
        assert(node.check_frozen_tx_log(spend_frozen_tx2.hash));
        assert(node.check_log("Transaction was rejected because it tried to spend a frozen transaction output.*"+spend_frozen_tx2.hash));

        self.log.info(f"Unfreezing TXO {spend_frozen_tx.hash},0 from policy blacklist")
        result = node.rpc.removeFromPolicyBlacklist({
            "funds": [
            {
                "txOut" : {
                    "txId" : spend_frozen_tx.hash,
                    "vout" : 0
                }
            }]
        });
        assert_equal(result["notProcessed"], [])

        self.log.info(f"Sending transaction {spend_frozen_tx2.hash} again and checking that it is accepted")
        node.send_tx(spend_frozen_tx2)
        assert_equal(node.rpc.getrawmempool(), [spend_frozen_tx2.hash])

        self.log.info(f"Checking that transaction {spend_frozen_tx2.hash} is removed from mempool if TXO is re-frozen")
        result = node.rpc.addToPolicyBlacklist({
            "funds": [
            {
                "txOut" : {
                    "txId" : spend_frozen_tx.hash,
                    "vout" : 0
                }
            }]
        });
        assert_equal(result["notProcessed"], [])
        assert_equal(node.rpc.getrawmempool(), [])


    def _test_consensus_freeze(self, spendable_out, node):
        self.log.info("*** Performing consensus freeze checks")

        # Helper to send tx and check it is rejected because of frozen inputs
        def SendTxAndCheckRejected(tx):
            self.log.info(f"Sending transaction {tx.hash} spending TXO {tx.vin[0].prevout.hash:064x},{tx.vin[0].prevout.n} and checking that it is rejected")
            node.send_tx(tx, True)
            assert_equal(node.rpc.getrawmempool(), [])
            assert(node.check_frozen_tx_log(tx.hash));
            assert(node.check_log("Transaction was rejected because it tried to spend a frozen transaction output.*"+tx.hash));

        # Helper to send tx and check it is accepted
        def SendTxAndCheckAccepted(tx):
            self.log.info(f"Sending transaction {tx.hash} spending TXO {tx.vin[0].prevout.hash:064x},{tx.vin[0].prevout.n} and checking that it is accepted")
            node.send_tx(tx)
            assert_equal(node.rpc.getrawmempool(), [tx.hash])

        # Helper to mine block with tx and check it is rejected because of frozen inputs
        def MineAndCheckRejected(tx):
            self.log.info(f"Mining block with transaction {tx.hash} spending TXO {tx.vin[0].prevout.hash:064x},{tx.vin[0].prevout.n} and checking that it is rejected")
            old_tip = self.chain.tip
            self._mine_and_send_block(tx, node, True)
            assert_equal(node.rpc.getbestblockhash(), old_tip.hash)
            assert(node.check_frozen_tx_log(self.chain.tip.hash));
            assert(node.check_log("Block was rejected because it included a transaction, which tried to spend a frozen transaction output.*"+self.chain.tip.hash));
            self._remove_last_block()

        # Helper to mine block with tx and check it is accepted
        def MineAndCheckAccepted(tx):
            self.log.info(f"Mining block with transaction {tx.hash} spending TXO {tx.vin[0].prevout.hash:064x},{tx.vin[0].prevout.n} and checking that is accepted")
            self._mine_and_send_block(tx, node)
            assert_equal(node.rpc.getbestblockhash(), self.chain.tip.hash)

        def MineEmptyBlock():
            self.log.info(f"Mining block with no transactions to increase height")
            self._mine_and_send_block(None, node)
            assert_equal(node.rpc.getbestblockhash(), self.chain.tip.hash)

        freeze_tx = self._create_tx(spendable_out, b'', CScript([OP_TRUE]))
        self.log.info(f"Mining block with transaction {freeze_tx.hash} whose output will be frozen later")
        self._mine_and_send_block(freeze_tx, node)

        self.log.info(f"Freezing TXO {freeze_tx.hash},0 on consensus blacklist")
        result=node.rpc.addToConsensusBlacklist({
            "funds": [
            {
                "txOut" : {
                    "txId" : freeze_tx.hash,
                    "vout" : 0
                },
                "enforceAtHeight": [{"start": 0}],
                "policyExpiresWithConsensus": False
            }]
        });
        assert_equal(result["notProcessed"], [])

        # must not be accepted as parent transaction is frozen
        spend_frozen_tx = self._create_tx(PreviousSpendableOutput(freeze_tx, 0), b'', CScript([OP_TRUE]))
        SendTxAndCheckRejected(spend_frozen_tx)

        # block is rejected as consensus freeze is in effect
        MineAndCheckRejected(spend_frozen_tx)

        current_height = node.rpc.getblockcount()
        self.log.info(f"Current height: {current_height}")
        enforce_height = current_height + 2
        self.log.info(f"Freezing TXO {freeze_tx.hash},0 on consensus blacklist at height {enforce_height}")
        result=node.rpc.addToConsensusBlacklist({
            "funds": [
            {
                "txOut" : {
                    "txId" : freeze_tx.hash,
                    "vout" : 0
                },
                "enforceAtHeight": [{"start": enforce_height}],
                "policyExpiresWithConsensus": False
            }]
        });
        assert_equal(result["notProcessed"], [])

        # must not be accepted even if consensus blacklist is not yet enforced
        spend_frozen_tx2 = self._create_tx(PreviousSpendableOutput(freeze_tx, 0), b'', CScript([OP_TRUE, OP_NOP]))
        SendTxAndCheckRejected(spend_frozen_tx2)

        # block is accepted as consensus freeze is not yet enforced at this height
        MineAndCheckAccepted(spend_frozen_tx2)

        self.log.info(f"Freezing TXO {spend_frozen_tx2.hash},0 on consensus blacklist at height {enforce_height}")
        result=node.rpc.addToConsensusBlacklist({
            "funds": [
            {
                "txOut" : {
                    "txId" : spend_frozen_tx2.hash,
                    "vout" : 0
                },
                "enforceAtHeight": [{"start": enforce_height}],
                "policyExpiresWithConsensus": False
            }]
        });
        assert_equal(result["notProcessed"], [])

        # block is rejected as consensus freeze is enforced at this height
        spend_frozen_tx3 = self._create_tx(PreviousSpendableOutput(spend_frozen_tx2, 0), b'', CScript([OP_TRUE]))
        MineAndCheckRejected(spend_frozen_tx3)

        self.log.info(f"Unfreezing TXO {spend_frozen_tx2.hash},0 from consensus blacklist at height {enforce_height+2}")
        result=node.rpc.addToConsensusBlacklist({
            "funds": [
            {
                "txOut" : {
                    "txId" : spend_frozen_tx2.hash,
                    "vout" : 0
                },
                "enforceAtHeight": [{"start": enforce_height, "stop": enforce_height+2}],
                "policyExpiresWithConsensus": False
            }]
        });
        assert_equal(result["notProcessed"], [])

        MineEmptyBlock()

        # block is rejected as consensus freeze is still enforced at this height
        spend_frozen_tx3_1 = self._create_tx(PreviousSpendableOutput(spend_frozen_tx2, 0), b'', CScript([OP_TRUE, OP_NOP]))
        MineAndCheckRejected(spend_frozen_tx3_1)

        MineEmptyBlock()

        # must not be accepted because policy blacklist enforcement does not expire with consensus
        spend_frozen_tx3_2 = self._create_tx(PreviousSpendableOutput(spend_frozen_tx2, 0), b'', CScript([OP_TRUE, OP_NOP, OP_NOP]))
        SendTxAndCheckRejected(spend_frozen_tx3_2)

        self.log.info(f"Unfreezing TXO {spend_frozen_tx2.hash},0 from consensus and policy blacklist at height {enforce_height+2}")
        result=node.rpc.addToConsensusBlacklist({
            "funds": [
            {
                "txOut" : {
                    "txId" : spend_frozen_tx2.hash,
                    "vout" : 0
                },
                "enforceAtHeight": [{"start": enforce_height, "stop": enforce_height+2}],
                "policyExpiresWithConsensus": True
            }]
        });
        assert_equal(result["notProcessed"], [])

        # must be accepted because policy blacklist enforcement expires with consensus at this height
        spend_frozen_tx3_3 = self._create_tx(PreviousSpendableOutput(spend_frozen_tx2, 0), b'', CScript([OP_TRUE, OP_NOP, OP_NOP, OP_NOP]))
        SendTxAndCheckAccepted(spend_frozen_tx3_3)

        # block is accepted as consensus freeze is not enforced anymore at this height
        spend_frozen_tx3_4 = self._create_tx(PreviousSpendableOutput(spend_frozen_tx2, 0), b'', CScript([OP_TRUE, OP_NOP, OP_NOP, OP_NOP, OP_NOP]))
        MineAndCheckAccepted(spend_frozen_tx3_4)


        self.log.info("*** Performing consensus freeze checks with several block height enforcement intervals")

        # Helper to freeze output 0 of given tx on heights [h+1,h+3), [h+5,h+7), where h is current block height
        # and return a tx that spends that output.
        def FreezeTXO0(tx):
            h = node.rpc.getblockcount()
            self.log.info(f"Current height: {h}")

            self.log.info(f"Freezing TXO {tx.hash},0 on consensus blacklist at heights [{h+1}, {h+3}), [{h+5}, {h+7})")
            result=node.rpc.addToConsensusBlacklist({
                "funds": [
                {
                    "txOut" : {
                        "txId" : tx.hash,
                        "vout" : 0
                    },
                    "enforceAtHeight": [{"start": h+1, "stop": h+3}, {"start": h+5, "stop": h+7}],
                    "policyExpiresWithConsensus": False
                }]
            });
            assert_equal(result["notProcessed"], [])
            tx2=self._create_tx(PreviousSpendableOutput(tx, 0), b'', CScript([OP_TRUE]))
            self.log.info(f"Creating transaction {tx2.hash} spending TXO {tx.vin[0].prevout.hash:064x},{tx.vin[0].prevout.n}")
            return tx2

        tx = spend_frozen_tx3_4

        # Check first interval
        tx = FreezeTXO0(tx)
        MineAndCheckRejected(tx) # block is rejected in first interval
        MineEmptyBlock()
        MineAndCheckRejected(tx)
        MineEmptyBlock()
        SendTxAndCheckRejected(tx) # tx is rejected because policy freeze also applies in gaps between enforcement intervals
        MineAndCheckAccepted(tx) # block is accepted as consensus freeze is not enforced in a gap between enforcement intervals

        # Same as above, but check the second block in a gap between enforcement intervals
        tx=FreezeTXO0(tx)
        MineAndCheckRejected(tx)
        MineEmptyBlock()
        MineAndCheckRejected(tx)
        MineEmptyBlock()
        MineEmptyBlock()
        SendTxAndCheckRejected(tx)
        MineAndCheckAccepted(tx)

        # Check second interval
        tx=FreezeTXO0(tx)
        MineAndCheckRejected(tx)
        MineEmptyBlock()
        MineAndCheckRejected(tx)
        MineEmptyBlock()
        MineEmptyBlock()
        MineEmptyBlock()
        MineAndCheckRejected(tx) # block is rejected in second interval
        MineEmptyBlock()
        MineAndCheckRejected(tx)
        MineEmptyBlock()
        SendTxAndCheckRejected(tx) # tx is rejected because policy freeze also applies after enforcement intervals if policyExpiresWithConsensus=false
        MineAndCheckAccepted(tx) # block is accepted after the last interval


    def run_test(self):

        node = self._init()

        out_policy_freeze_txo_p2p = self.chain.get_spendable_output()
        out_consensus_freeze_txo_p2p = self.chain.get_spendable_output()

        out_policy_freeze_txo_rpc = self.chain.get_spendable_output()
        out_consensus_freeze_txo_rpc = self.chain.get_spendable_output()

        # p2p send test
        p2p_send_node = P2P_send_node(self.options.tmpdir, self.log, 0, node, self.nodes[0])
        self._test_policy_freeze(out_policy_freeze_txo_p2p, p2p_send_node)
        self._test_consensus_freeze(out_consensus_freeze_txo_p2p, p2p_send_node)

        # rpc send test
        rpc_send_node = RPC_send_node(self.options.tmpdir, self.log, 0, node, self.nodes[0])
        self._test_policy_freeze(out_policy_freeze_txo_rpc, rpc_send_node)
        self._test_consensus_freeze(out_consensus_freeze_txo_rpc, rpc_send_node)
Esempio n. 12
0
class FrozenTXOReindex(BitcoinTestFramework):
    def set_test_params(self):
        self.setup_clean_chain = True
        self.num_nodes = 1
        self.chain = ChainManager()
        self.block_count = 0

    def _init(self):
        node_no = 0

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

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

        self.chain.set_genesis_hash(
            int(self.nodes[node_no].getbestblockhash(), 16))
        block = self.chain.next_block(self.block_count)
        self.block_count += 1
        self.chain.save_spendable_output()
        node.send_message(msg_block(block))

        for i in range(100):
            block = self.chain.next_block(self.block_count)
            self.block_count += 1
            self.chain.save_spendable_output()
            node.send_message(msg_block(block))

        self.log.info("Waiting for block height 101 via rpc")
        self.nodes[node_no].waitforblockheight(101)

        return node

    def _create_tx(self, tx_out, unlock, lock):
        unlock_script = b'' if callable(unlock) else unlock

        return create_transaction(tx_out.tx, tx_out.n, unlock_script, 1, lock)

    def _mine_and_send_block(self, tx, node, expect_reject=False):
        block = self.chain.next_block(self.block_count)

        self.chain.update_block(self.block_count, [tx] if tx else [])

        self.log.debug(f"attempting mining block: {block.hash}")

        node.send_block(block, expect_reject)
        self.block_count += 1

        return block.hash

    def _remove_last_block(self):
        # remove last block from chain manager
        del self.chain.block_heights[self.chain.blocks[self.block_count -
                                                       1].sha256]
        del self.chain.blocks[self.block_count - 1]
        self.block_count -= 1
        self.chain.set_tip(self.block_count - 1)

    def _mine_and_check_rejected(self, tx, node):
        self.log.info(
            f"Mining block with transaction {tx.hash} spending TXO {tx.vin[0].prevout.hash:064x},{tx.vin[0].prevout.n} and checking that it is rejected"
        )
        old_tip = self.chain.tip
        rejected_block_hash = self._mine_and_send_block(tx, node, True)
        assert_equal(node.rpc.getbestblockhash(), old_tip.hash)
        assert (node.check_frozen_tx_log(self.chain.tip.hash))
        assert (node.check_log(
            "Block was rejected because it included a transaction, which tried to spend a frozen transaction output.*"
            + self.chain.tip.hash))

        # remove rejected block from test node - the only remaining copy after this point is on remote node disk
        self._remove_last_block()

        return rejected_block_hash

    def _create_policy_freeze_block(self, spendable_out, node):
        freeze_tx = self._create_tx(spendable_out, b'', CScript([OP_TRUE]))
        self.log.info(
            f"Mining block with transaction {freeze_tx.hash} whose output will be frozen later"
        )
        self._mine_and_send_block(freeze_tx, node)

        self.log.info(f"Freezing TXO {freeze_tx.hash},0 on policy blacklist")
        result = node.rpc.addToPolicyBlacklist(
            {"funds": [{
                "txOut": {
                    "txId": freeze_tx.hash,
                    "vout": 0
                }
            }]})
        assert_equal(result["notProcessed"], [])

        spend_frozen_tx = self._create_tx(
            PreviousSpendableOutput(freeze_tx, 0), b'', CScript([OP_TRUE]))
        self.log.info(
            f"Mining block with transaction {spend_frozen_tx.hash} spending frozen TXO {freeze_tx.hash},0 and checking that is accepted"
        )
        self._mine_and_send_block(spend_frozen_tx, node)
        # block is accepted as consensus freeze is not in effect
        assert_equal(node.rpc.getbestblockhash(), self.chain.tip.hash)

    def _create_consensus_freeze_block(self, spendable_out, node):
        freeze_tx = self._create_tx(spendable_out, b'', CScript([OP_TRUE]))
        self.log.info(
            f"Mining block with transaction {freeze_tx.hash} whose output will be frozen later"
        )
        self._mine_and_send_block(freeze_tx, node)

        self.log.info(
            f"Freezing TXO {freeze_tx.hash},0 on consensus blacklist")
        result = node.rpc.addToConsensusBlacklist({
            "funds": [{
                "txOut": {
                    "txId": freeze_tx.hash,
                    "vout": 0
                },
                "enforceAtHeight": [{
                    "start": 0
                }],
                "policyExpiresWithConsensus": False
            }]
        })
        assert_equal(result["notProcessed"], [])

        spend_frozen_tx = self._create_tx(
            PreviousSpendableOutput(freeze_tx, 0), b'', CScript([OP_TRUE]))

        # block is rejected as consensus freeze is in effect
        rejected_block_hash = self._mine_and_check_rejected(
            spend_frozen_tx, node)

        return (freeze_tx.hash, rejected_block_hash)

    def run_test(self):

        node = self._init()

        out_policy_freeze_txo = self.chain.get_spendable_output()
        out_consensus_freeze_txo = self.chain.get_spendable_output()

        send_node = Send_node(self.options.tmpdir, self.log, 0, node,
                              self.nodes[0])
        self._create_policy_freeze_block(out_policy_freeze_txo, send_node)
        [freeze_tx_hash, rejected_block_hash
         ] = self._create_consensus_freeze_block(out_consensus_freeze_txo,
                                                 send_node)
        node_chain_info = send_node.rpc.getblockchaininfo()
        old_tip_hash = node_chain_info['bestblockhash']
        old_tip_height = node_chain_info['blocks']

        assert (rejected_block_hash != old_tip_hash)

        # Make sure that we get to the same height:
        # best block with transactions policy frozen - should get to this point
        # best block with transactions consensus frozen - should not get to this block
        self.stop_node(0)
        self.start_node(0, extra_args=["-reindex=1"])

        send_node.rpc.waitforblockheight(old_tip_height)

        assert_equal(send_node.rpc.getbestblockhash(), old_tip_hash)

        # Unfreeze and reconsider block to show that the block was still stored on disk
        result = self.nodes[0].clearBlacklists({"removeAllEntries": True})
        assert_equal(result["numRemovedEntries"], 2)

        self.stop_node(0)
        self.start_node(0, extra_args=["-reindex=1"])

        send_node.rpc.waitforblockheight(old_tip_height + 1)

        self.log.info(send_node.rpc.getblockchaininfo())
        assert_equal(send_node.rpc.getbestblockhash(), rejected_block_hash)
class GetBlockTemplateRPCTest(BitcoinTestFramework):
    def set_test_params(self):
        self.num_nodes = 1
        self.setup_clean_chain = True
        self.chain = ChainManager()

    def check_mempool(self, rpc, should_be_in_mempool):
        wait_until(lambda: {t.hash
                            for t in should_be_in_mempool}.issubset(
                                set(rpc.getrawmempool())))

    def createLargeTransaction(self, size, depends):
        tx = CTransaction()
        for depend in depends:
            tx.vin.append(CTxIn(COutPoint(depend.sha256, 0), b''))
        tx.vout.append(CTxOut(int(100), CScript([OP_RETURN, b"a" * size])))
        tx.rehash()
        return tx

    def create_bad_block(self, template):
        coinbase_tx = create_coinbase(height=int(template["height"]) + 1)
        coinbase_tx.vin[0].nSequence = 2**32 - 2
        coinbase_tx.rehash()
        block = CBlock()
        block.nVersion = template["version"]
        block.nTime = template["curtime"]
        block.nBits = int(template["bits"], 16)
        block.nNonce = 0
        block.vtx = [coinbase_tx]

        # Make this block incorrect.
        block.hashPrevBlock = 123
        block.hashMerkleRoot = block.calc_merkle_root()

        return block

    def checkBlockTemplate(self, template, txs, dependingTx):

        assert 'capabilities' in template
        assert_equal('proposal', template['capabilities'][0])

        assert 'version' in template
        assert 'previousblockhash' in template
        assert_equal(self.nodes[0].getbestblockhash(),
                     template['previousblockhash'])

        assert 'transactions' in template

        # check if hex data was parsed correctly
        txs_data = [tx['data'] for tx in template['transactions']]
        assert (ToHex(dependingTx) in txs_data)
        for tx in txs:
            assert (ToHex(tx) in txs_data)

        # check dependencies
        depending_indices = []
        depending_txs_hash = [tx.hash for tx in txs]
        for i in range(len(template['transactions'])):
            if template['transactions'][i]['hash'] in depending_txs_hash:
                depending_indices.append(i + 1)

        for tmpl_tx in template['transactions']:
            if tmpl_tx['hash'] == dependingTx.hash:
                assert_equal(2, len(tmpl_tx['depends']))
                assert_equal(set(tmpl_tx['depends']), set(depending_indices))
                break

        assert 'coinbaseaux' in template
        assert 'coinbasevalue' in template
        assert 'longpollid' in template
        assert 'target' in template
        assert 'mintime' in template
        assert 'mutable' in template
        assert 'noncerange' in template
        assert 'sizelimit' in template
        assert 'curtime' in template
        assert 'bits' in template
        assert 'height' in template
        assert_equal(
            self.nodes[0].getblock(self.nodes[0].getbestblockhash())['height']
            + 1, template['height'])

    def run_test(self):

        self.stop_node(0)
        with self.run_node_with_connections("test getblocktemplate RPC call",
                                            0, ["-blockmintxfee=0.0000001"],
                                            1) as connections:

            connection = connections[0]

            # Preparation.
            self.chain.set_genesis_hash(
                int(self.nodes[0].getbestblockhash(), 16))
            starting_blocks = 101
            block_count = 0
            for i in range(starting_blocks):
                block = self.chain.next_block(block_count)
                block_count += 1
                self.chain.save_spendable_output()
                connection.cb.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)

            # Create and send 2 transactions.
            transactions = []
            for i in range(2):
                tx = create_transaction(out[i].tx, out[i].n, b"", 100000,
                                        CScript([OP_TRUE]))
                connection.cb.send_message(msg_tx(tx))
                transactions.append(tx)
            self.check_mempool(self.nodes[0], transactions)

            # Create large transaction that depends on previous two transactions.
            # If transaction pubkey contains 1/2 of BUFFER_SIZE_HttpTextWriter of data, it means that final result will for sure be chunked.
            largeTx = self.createLargeTransaction(
                int(BUFFER_SIZE_HttpTextWriter / 2), transactions)
            connection.cb.send_message(msg_tx(largeTx))
            self.check_mempool(self.nodes[0], [largeTx])

            # Check getblocktemplate response.
            template = self.nodes[0].getblocktemplate()
            self.checkBlockTemplate(template, transactions, largeTx)

            # Check getblocktemplate with invalid reponse
            block = self.create_bad_block(template)
            rsp = self.nodes[0].getblocktemplate({
                'data': b2x(block.serialize()),
                'mode': 'proposal'
            })
            assert_equal(rsp, "inconclusive-not-best-prevblk")

            assert_raises_rpc_error(-22, "Block decode failed",
                                    self.nodes[0].getblocktemplate, {
                                        'data': b2x(block.serialize()[:-1]),
                                        'mode': 'proposal'
                                    })

            # Test getblocktemplate in a batch
            batch = self.nodes[0].batch([
                self.nodes[0].getblockbyheight.get_request(100),
                self.nodes[0].getblocktemplate.get_request(),
                self.nodes[0].getblockcount.get_request(),
                self.nodes[0].undefinedmethod.get_request()
            ])

            assert_equal(batch[0]["error"], None)
            assert_equal(batch[1]["error"], None)
            assert_equal(batch[1]["result"], template)
            assert_equal(batch[2]["error"], None)
            assert_equal(batch[3]["error"]["message"], "Method not found")
            assert_equal(batch[3]["result"], None)
class GetRawMempoolTest(BitcoinTestFramework):
    FORMAT_SEPARATOR = "."

    def set_test_params(self):
        self.num_nodes = 1
        self.setup_clean_chain = True
        self.extra_args = [['-genesisactivationheight=1']]
        self.chain = ChainManager()

    def check_mempool(self, rpc, should_be_in_mempool):
        wait_until(lambda: {t.hash for t in should_be_in_mempool}.issubset(set(rpc.getrawmempool())), timeout=6000)

    def createLargeTransaction(self, size, depends):
        tx = CTransaction()
        for depend in depends:
            tx.vin.append(CTxIn(COutPoint(depend.sha256, 0), b''))
        tx.vout.append(CTxOut(int(100), CScript([OP_RETURN,  b"a" * size])))
        tx.rehash()
        return tx

    def check_fieldsMempoolEntry(self, mempoolEntry):
        assert 'size' in mempoolEntry
        assert 'fee' in mempoolEntry
        assert 'modifiedfee' in mempoolEntry
        assert 'time' in mempoolEntry
        assert 'height' in mempoolEntry
        assert_equal(self.nodes[0].getblock(self.nodes[0].getbestblockhash())['height'], mempoolEntry['height'])
        assert 'depends' in mempoolEntry

    def check_getRawMempool(self, mempool, transactions):
        for transaction in transactions:
            assert transaction.hash in mempool
            self.check_fieldsMempoolEntry(mempool[transaction.hash])

    def run_test(self):

        url = urllib.parse.urlparse(self.nodes[0].url)

        self.stop_node(0)
        with self.run_node_with_connections("test getrawMempool RPC call", 0, [], 1) as connections:

            connection = connections[0]

            # Preparation.
            self.chain.set_genesis_hash(int(self.nodes[0].getbestblockhash(), 16))
            starting_blocks = 101
            block_count = 0
            for i in range(starting_blocks):
                block = self.chain.next_block(block_count)
                block_count += 1
                self.chain.save_spendable_output()
                connection.cb.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)

            # Create and send 2 transactions.
            transactions = []
            for i in range(2):
                tx = create_transaction(out[i].tx, out[i].n, b"", 100000, CScript([OP_TRUE]))
                connection.cb.send_message(msg_tx(tx))
                transactions.append(tx)
            self.check_mempool(self.nodes[0], transactions)

            # Create large transaction that depends on previous two transactions.
            txSize = 1000
            largeTx = self.createLargeTransaction(txSize, transactions)
            connection.cb.send_message(msg_tx(largeTx))
            self.check_mempool(self.nodes[0], [largeTx])

            # getrawmempool, verbosity = False
            assert_equal(set(self.nodes[0].getrawmempool()), set([tx.hash for tx in transactions] + [largeTx.hash]))

            # getrawmempool, verbosity = True
            mempool = self.nodes[0].getrawmempool(True)
            self.check_getRawMempool(mempool, transactions + [largeTx])
            assert_equal(mempool[transactions[0].hash]["depends"], [])
            assert_equal(mempool[largeTx.hash]["depends"], [tx.hash for tx in transactions])
            assert_equal(mempool[largeTx.hash]["size"] > txSize, True)

            # /rest/mempool/contents REST call
            json_string = http_get_call(
            url.hostname, url.port, '/rest/mempool/contents' + self.FORMAT_SEPARATOR + 'json')
            json_obj = json.loads(json_string)
            self.check_getRawMempool(json_obj, transactions + [largeTx])
            assert_equal(mempool[transactions[0].hash]["depends"], [])
            assert_equal(mempool[largeTx.hash]["depends"], [tx.hash for tx in transactions])
            assert_equal(mempool[largeTx.hash]["size"] > txSize, True)

            # getmempooldescendants, verbosity = False
            assert_equal(self.nodes[0].getmempooldescendants(transactions[0].hash), [largeTx.hash])
            # getmempooldescendants, verbosity = True
            mempoolDescendants = self.nodes[0].getmempooldescendants(transactions[0].hash, True)
            self.check_getRawMempool(mempoolDescendants, [largeTx])
            assert_equal(mempool[largeTx.hash], mempoolDescendants[largeTx.hash])

            # getmempoolancestors, verbosity = False
            assert_equal(set(self.nodes[0].getmempoolancestors(largeTx.hash)), set([tx.hash for tx in transactions]))
            # getmempoolancestors, verbosity = True
            mempoolAncestors = self.nodes[0].getmempoolancestors(largeTx.hash, True)
            self.check_getRawMempool(mempoolAncestors, transactions)
            assert_equal(mempool[transactions[0].hash], mempoolAncestors[transactions[0].hash])

            # getmempoolentry
            largeTxEntry = self.nodes[0].getmempoolentry(largeTx.hash)
            self.check_fieldsMempoolEntry(largeTxEntry)
            assert_equal(mempool[largeTx.hash], largeTxEntry)

            # Test those calls in a batch
            batch = self.nodes[0].batch([
                self.nodes[0].getrawmempool.get_request(True),
                self.nodes[0].getrawmempool.get_request(),
                self.nodes[0].getmempooldescendants.get_request(transactions[0].hash, True),
                self.nodes[0].getmempoolancestors.get_request(largeTx.hash),
                self.nodes[0].getmempoolentry.get_request(largeTx.hash)])

            assert_equal(batch[0]["error"], None)
            assert_equal(batch[0]["result"], mempool)
            assert_equal(batch[1]["error"], None)
            assert_equal(batch[2]["result"][largeTx.hash], mempool[largeTx.hash])
            assert_equal(batch[2]["error"], None)
            assert_equal(batch[3]["error"], None)
            assert_equal(batch[4]["error"], None)
Esempio n. 15
0
class PBVSubmitBlock(BitcoinTestFramework):
    def set_test_params(self):
        self.setup_clean_chain = True
        self.num_nodes = 1
        self.chain = ChainManager()
        self.extra_args = [["-whitelist=127.0.0.1"]]

    def run_test(self):
        block_count = 0

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

        node1 = NodeConnCB()
        connection1 = NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], node1)
        node1.add_connection(connection1)

        # *** Prepare node connection for early announcements testing
        node2 = NodeConnCB()
        node2.add_connection(
            NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], node2))

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

        # *** Activate early announcement functionality for this connection
        #     After this point the early announcements are not received yet -
        #     we still need to set latest announced block (CNode::pindexBestKnownBlock)
        #     which is set for e.g. by calling best headers message with locator
        #     set to non-null
        node2.wait_for_verack()
        node2.send_message(msg_sendcmpct(announce=True))

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

        _, outs, block_count = prepare_init_chain(self.chain,
                                                  101,
                                                  1,
                                                  block_0=False,
                                                  start_block=0,
                                                  node=node0)
        out = outs[0]

        self.log.info("waiting for block height 101 via rpc")
        self.nodes[0].waitforblockheight(101)

        tip_block_num = block_count - 1

        # adding extra transactions to get different block hashes
        block2_hard = self.chain.next_block(block_count,
                                            spend=out,
                                            extra_txns=8)
        block_count += 1

        self.chain.set_tip(tip_block_num)

        block3_easier = self.chain.next_block(block_count,
                                              spend=out,
                                              extra_txns=2)
        block_count += 1

        self.chain.set_tip(tip_block_num)

        block4_hard = self.chain.next_block(block_count,
                                            spend=out,
                                            extra_txns=10)
        block_count += 1

        # send three "hard" blocks, with waitaftervalidatingblock we artificially
        # extend validation time.
        self.log.info(f"hard block2 hash: {block2_hard.hash}")
        self.nodes[0].waitaftervalidatingblock(block2_hard.hash, "add")
        self.log.info(f"hard block4 hash: {block4_hard.hash}")
        self.nodes[0].waitaftervalidatingblock(block4_hard.hash, "add")

        # make sure block hashes are in waiting list
        wait_for_waiting_blocks({block2_hard.hash, block4_hard.hash},
                                self.nodes[0], self.log)

        # *** Complete early announcement setup by sending getheaders message
        #     with a non-null locator (pointing to the last block that we know
        #     of on python side - we claim that we know of all the blocks that
        #     bitcoind node knows of)
        #
        #     We also set on_cmpctblock handler as early announced blocks are
        #     announced via compact block messages instead of inv messages
        node2.send_and_ping(
            msg_getheaders(
                locator_have=[int(self.nodes[0].getbestblockhash(), 16)]))
        receivedAnnouncement = False
        waiting_for_announcement_block_hash = block2_hard.sha256

        def on_cmpctblock(conn, message):
            nonlocal receivedAnnouncement
            message.header_and_shortids.header.calc_sha256()
            if message.header_and_shortids.header.sha256 == waiting_for_announcement_block_hash:
                receivedAnnouncement = True

        node2.on_cmpctblock = on_cmpctblock

        # send one block via p2p and one via rpc
        node0.send_message(msg_block(block2_hard))

        # *** make sure that we receive announcement of the block before it has
        #     been validated
        wait_until(lambda: receivedAnnouncement)

        # making rpc call submitblock in a separate thread because waitaftervalidation is blocking
        # the return of submitblock
        submitblock_thread = threading.Thread(target=self.nodes[0].submitblock,
                                              args=(ToHex(block4_hard), ))
        submitblock_thread.start()

        # because self.nodes[0] rpc is blocked we use another rpc client
        rpc_client = get_rpc_proxy(rpc_url(
            get_datadir_path(self.options.tmpdir, 0), 0),
                                   0,
                                   coveragedir=self.options.coveragedir)

        wait_for_validating_blocks({block2_hard.hash, block4_hard.hash},
                                   rpc_client, self.log)

        # *** prepare to intercept block3_easier announcement - it will not be
        #     announced before validation is complete as early announcement is
        #     limited to announcing one block per height (siblings are ignored)
        #     but after validation is complete we should still get the announcing
        #     compact block message
        receivedAnnouncement = False
        waiting_for_announcement_block_hash = block3_easier.sha256

        self.log.info(f"easy block3 hash: {block3_easier.hash}")
        node1.send_message(msg_block(block3_easier))

        # *** Make sure that we receive compact block announcement of the block
        #     after the validation is complete even though it was not the first
        #     block that was received by bitcoind node.
        #
        #     Also make sure that we receive inv announcement of the block after
        #     the validation is complete by the nodes that are not using early
        #     announcement functionality.
        wait_until(lambda: receivedAnnouncement)
        node0.wait_for_inv([CInv(CInv.BLOCK, block3_easier.sha256)])
        # node 1 was the sender but receives inv for block non the less
        # (with early announcement that's not the case - sender does not receive the announcement)
        node1.wait_for_inv([CInv(CInv.BLOCK, block3_easier.sha256)])

        rpc_client.waitforblockheight(102)
        assert_equal(block3_easier.hash, rpc_client.getbestblockhash())

        # now we can remove waiting status from blocks and finish their validation
        rpc_client.waitaftervalidatingblock(block2_hard.hash, "remove")
        rpc_client.waitaftervalidatingblock(block4_hard.hash, "remove")
        submitblock_thread.join()

        # wait till validation of block or blocks finishes
        node0.sync_with_ping()

        # easier block should still be on tip
        assert_equal(block3_easier.hash, self.nodes[0].getbestblockhash())
Esempio n. 16
0
class PBVWithSigOps(BitcoinTestFramework):

    def set_test_params(self):
        self.setup_clean_chain = True
        self.num_nodes = 1
        self.extra_args = [["-whitelist=127.0.0.1"]]
        self.coinbase_key = CECKey()
        self.coinbase_key.set_secretbytes(b"horsebattery")
        self.coinbase_pubkey = self.coinbase_key.get_pubkey()
        self.chain = ChainManager()

    def sign_expensive_tx(self, tx, spend_tx, n, sigChecks):
        sighash = SignatureHashForkId(
            spend_tx.vout[n].scriptPubKey, tx, 0, SIGHASH_ALL | SIGHASH_FORKID, spend_tx.vout[n].nValue)

        tx.vin[0].scriptSig = CScript(
            [self.coinbase_key.sign(sighash) + bytes(bytearray([SIGHASH_ALL | SIGHASH_FORKID])),
             self.coinbase_pubkey] * sigChecks
            + [self.coinbase_key.sign(sighash) + bytes(bytearray([SIGHASH_ALL | SIGHASH_FORKID])),
               self.coinbase_pubkey])

    def get_hard_transactions(self, spend, money_to_spend, num_of_transactions, num_of_sig_checks, expensive_script):
        txns = []
        for _ in range(0, num_of_transactions):
            money_to_spend = money_to_spend - 1  # one satoshi to fee
            tx2 = create_transaction(spend.tx, spend.n, b"", money_to_spend, CScript(expensive_script))
            sign_tx(tx2, spend.tx, spend.n, self.coinbase_key)
            tx2.rehash()
            txns.append(tx2)

            money_to_spend = money_to_spend - 1
            tx3 = create_transaction(tx2, 0, b"", money_to_spend, scriptPubKey=CScript([OP_TRUE]))
            self.sign_expensive_tx(tx3, tx2, 0, num_of_sig_checks)
            tx3.rehash()
            txns.append(tx3)

            spend = PreviousSpendableOutput(tx3, 0)
        return txns

    def run_test(self):
        block_count = 0

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

        network_thread = NetworkThread()
        network_thread.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))

        _, out, block_count = prepare_init_chain(self.chain, 101, 100, block_0=False, start_block=0, node=node0)

        self.log.info("waiting for block height 101 via rpc")
        self.nodes[0].waitforblockheight(101)

        block1_num = block_count - 1

        # num of sig operations in one transaction
        num_of_sig_checks = 70

        expensive_scriptPubKey = [OP_DUP, OP_HASH160, hash160(self.coinbase_pubkey),
                                  OP_EQUALVERIFY, OP_CHECKSIG, OP_DROP] * num_of_sig_checks + [OP_DUP, OP_HASH160,
                                                                                               hash160(
                                                                                                   self.coinbase_pubkey),
                                                                                               OP_EQUALVERIFY,
                                                                                               OP_CHECKSIG]

        money_to_spend = 5000000000
        spend = out[0]

        block2_hard = self.chain.next_block(block_count)

        # creates 4000 hard transaction and 4000 transaction to spend them. It will be 8k transactions in total
        add_txns = self.get_hard_transactions(spend, money_to_spend=money_to_spend, num_of_transactions=4000,
                                              num_of_sig_checks=num_of_sig_checks,
                                              expensive_script=expensive_scriptPubKey)
        self.chain.update_block(block_count, add_txns)
        block_count += 1
        self.log.info(f"block2_hard hash: {block2_hard.hash}")

        self.chain.set_tip(block1_num)
        block3_easier = self.chain.next_block(block_count)
        add_txns = self.get_hard_transactions(spend, money_to_spend=money_to_spend, num_of_transactions=1000,
                                              num_of_sig_checks=num_of_sig_checks,
                                              expensive_script=expensive_scriptPubKey)
        self.chain.update_block(block_count, add_txns)
        self.log.info(f"block3_easier hash: {block3_easier.hash}")

        node0.send_message(msg_block(block2_hard))
        node0.send_message(msg_block(block3_easier))

        def wait_for_log():
            text_activation = f"Block {block2_hard.hash} was not activated as best"
            text_block2 = "Verify 8000 txins"
            text_block3 = "Verify 2000 txins"
            results = 0
            for line in open(glob.glob(self.options.tmpdir + "/node0" + "/regtest/bitcoind.log")[0]):
                if text_activation in line:
                    results += 1
                elif text_block2 in line:
                    results += 1
                elif text_block3 in line:
                    results += 1
            return True if results == 3 else False

        # wait that everything is written to the log
        # try accounting for slower machines by having a large timeout
        wait_until(wait_for_log, timeout=120)

        text_activation = f"Block {block2_hard.hash} was not activated as best"
        text_block2 = "Verify 8000 txins"
        text_block3 = "Verify 2000 txins"
        for line in open(glob.glob(self.options.tmpdir + "/node0" + "/regtest/bitcoind.log")[0]):
            if text_activation in line:
                self.log.info(f"block2_hard was not activated as block3_easy won the validation race")
            elif text_block2 in line:
                line = line.split()
                self.log.info(f"block2_hard took {line[len(line) - 1]} to verify")
            elif text_block3 in line:
                line = line.split()
                self.log.info(f"block3_easy took {line[len(line)-1]} to verify")

        assert_equal(block3_easier.hash, self.nodes[0].getbestblockhash())
        node0.connection.close()
Esempio n. 17
0
class PBVWithSigOps(BitcoinTestFramework):
    def set_test_params(self):
        self.setup_clean_chain = True
        self.num_nodes = 1
        self.extra_args = [["-whitelist=127.0.0.1"]]
        self.coinbase_key = CECKey()
        self.coinbase_key.set_secretbytes(b"horsebattery")
        self.coinbase_pubkey = self.coinbase_key.get_pubkey()
        self.chain = ChainManager()

    def sign_tx(self, tx, spend_tx, n):
        scriptPubKey = bytearray(spend_tx.vout[n].scriptPubKey)
        if (scriptPubKey[0] == OP_TRUE):  # an anyone-can-spend
            tx.vin[0].scriptSig = CScript()
            return
        sighash = SignatureHashForkId(spend_tx.vout[n].scriptPubKey, tx, 0,
                                      SIGHASH_ALL | SIGHASH_FORKID,
                                      spend_tx.vout[n].nValue)
        tx.vin[0].scriptSig = CScript([
            self.coinbase_key.sign(sighash) +
            bytes(bytearray([SIGHASH_ALL | SIGHASH_FORKID]))
        ])

    def sign_expensive_tx(self, tx, spend_tx, n, sigChecks):
        sighash = SignatureHashForkId(spend_tx.vout[n].scriptPubKey, tx, 0,
                                      SIGHASH_ALL | SIGHASH_FORKID,
                                      spend_tx.vout[n].nValue)

        tx.vin[0].scriptSig = CScript([
            self.coinbase_key.sign(sighash) +
            bytes(bytearray([SIGHASH_ALL
                             | SIGHASH_FORKID])), self.coinbase_pubkey
        ] * sigChecks + [
            self.coinbase_key.sign(sighash) +
            bytes(bytearray([SIGHASH_ALL
                             | SIGHASH_FORKID])), self.coinbase_pubkey
        ])

    def next_block(self,
                   number,
                   spend=None,
                   additional_coinbase_value=0,
                   script=CScript([OP_TRUE])):
        if self.chain.tip == None:
            base_block_hash = self.chain._genesis_hash
            block_time = int(time.time()) + 1
        else:
            base_block_hash = self.chain.tip.sha256
            block_time = self.chain.tip.nTime + 1
        # First create the coinbase
        height = self.chain.block_heights[base_block_hash] + 1
        coinbase = create_coinbase(height, self.coinbase_pubkey)
        coinbase.vout[0].nValue += additional_coinbase_value
        coinbase.rehash()
        if spend == None:
            block = create_block(base_block_hash, coinbase, block_time)
        else:
            # All but one satoshi for each txn to fees
            for s in spend:
                coinbase.vout[0].nValue += s.tx.vout[s.n].nValue - 1
                coinbase.rehash()
            block = create_block(base_block_hash, coinbase, block_time)
            # Add as many txns as required
            for s in spend:
                # Spend 1 satoshi
                tx = create_transaction(s.tx, s.n, b"", 1, script)
                self.sign_tx(tx, s.tx, s.n)
                self.chain.add_transactions_to_block(block, [tx])
                block.hashMerkleRoot = block.calc_merkle_root()
        # Do PoW, which is very inexpensive on regnet
        block.solve()
        self.chain.tip = block
        self.chain.block_heights[block.sha256] = height
        assert number not in self.chain.blocks
        self.chain.blocks[number] = block
        return block

    def get_hard_transactions(self, spend, money_to_spend, num_of_transactions,
                              num_of_sig_checks, expensive_script):
        txns = []
        for _ in range(0, num_of_transactions):
            money_to_spend = money_to_spend - 1  # one satoshi to fee
            tx2 = create_transaction(spend.tx, spend.n, b"", money_to_spend,
                                     CScript(expensive_script))
            self.sign_tx(tx2, spend.tx, spend.n)
            tx2.rehash()
            txns.append(tx2)

            money_to_spend = money_to_spend - 1
            tx3 = create_transaction(tx2,
                                     0,
                                     b"",
                                     money_to_spend,
                                     scriptPubKey=CScript([OP_TRUE]))
            self.sign_expensive_tx(tx3, tx2, 0, num_of_sig_checks)
            tx3.rehash()
            txns.append(tx3)

            spend = PreviousSpendableOutput(tx3, 0)
        return txns

    def run_test(self):
        block_count = 0

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

        network_thread = NetworkThread()
        network_thread.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))
        block = self.chain.next_block(block_count)
        block_count += 1
        self.chain.save_spendable_output()
        node0.send_message(msg_block(block))

        for i in range(100):
            block = self.next_block(block_count)
            block_count += 1
            self.chain.save_spendable_output()
            node0.send_message(msg_block(block))

        out = []
        for i in range(100):
            out.append(self.chain.get_spendable_output())

        self.log.info("waiting for block height 101 via rpc")
        self.nodes[0].waitforblockheight(101)

        block1_num = block_count - 1

        # num of sig operations in one transaction
        num_of_sig_checks = 70

        expensive_scriptPubKey = [
            OP_DUP, OP_HASH160,
            hash160(self.coinbase_pubkey), OP_EQUALVERIFY, OP_CHECKSIG, OP_DROP
        ] * num_of_sig_checks + [
            OP_DUP, OP_HASH160,
            hash160(self.coinbase_pubkey), OP_EQUALVERIFY, OP_CHECKSIG
        ]

        money_to_spend = 5000000000
        spend = out[0]

        block2_hard = self.next_block(block_count)

        # creates 4000 hard transaction and 4000 transaction to spend them. It will be 8k transactions in total
        add_txns = self.get_hard_transactions(
            spend,
            money_to_spend=money_to_spend,
            num_of_transactions=4000,
            num_of_sig_checks=num_of_sig_checks,
            expensive_script=expensive_scriptPubKey)
        self.chain.update_block(block_count, add_txns)
        block_count += 1
        self.log.info(f"block2_hard hash: {block2_hard.hash}")

        self.chain.set_tip(block1_num)
        block3_easier = self.next_block(block_count)
        add_txns = self.get_hard_transactions(
            spend,
            money_to_spend=money_to_spend,
            num_of_transactions=1000,
            num_of_sig_checks=num_of_sig_checks,
            expensive_script=expensive_scriptPubKey)
        self.chain.update_block(block_count, add_txns)
        self.log.info(f"block3_easier hash: {block3_easier.hash}")

        node0.send_message(msg_block(block2_hard))
        node0.send_message(msg_block(block3_easier))

        def wait_for_log():
            text_activation = f"Block {block2_hard.hash} was not activated as best"
            text_block2 = "Verify 8000 txins"
            text_block3 = "Verify 2000 txins"
            results = 0
            for line in open(
                    glob.glob(self.options.tmpdir + "/node0" +
                              "/regtest/bitcoind.log")[0]):
                if text_activation in line:
                    results += 1
                elif text_block2 in line:
                    results += 1
                elif text_block3 in line:
                    results += 1
            return True if results == 3 else False

        # wait that everything is written to the log
        # try accounting for slower machines by having a large timeout
        wait_until(wait_for_log, timeout=120)

        text_activation = f"Block {block2_hard.hash} was not activated as best"
        text_block2 = "Verify 8000 txins"
        text_block3 = "Verify 2000 txins"
        for line in open(
                glob.glob(self.options.tmpdir + "/node0" +
                          "/regtest/bitcoind.log")[0]):
            if text_activation in line:
                self.log.info(
                    f"block2_hard was not activated as block3_easy won the validation race"
                )
            elif text_block2 in line:
                line = line.split()
                self.log.info(
                    f"block2_hard took {line[len(line) - 1]} to verify")
            elif text_block3 in line:
                line = line.split()
                self.log.info(
                    f"block3_easy took {line[len(line)-1]} to verify")

        assert_equal(block3_easier.hash, self.nodes[0].getbestblockhash())
        node0.connection.close()
Esempio n. 18
0
class PBVSubmitMiningSolution(BitcoinTestFramework):

    def set_test_params(self):
        self.setup_clean_chain = True
        self.num_nodes = 1
        self.chain = ChainManager()
        self.extra_args = [["-whitelist=127.0.0.1"]]

    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)

        node1 = NodeConnCB()
        connection = NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], node1)
        node1.add_connection(connection)

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

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

        _, outs, block_count = prepare_init_chain(self.chain, 101, 1, block_0=False, start_block=0, node=node0)
        out = outs[0]

        self.log.info("waiting for block height 101 via rpc")
        self.nodes[0].waitforblockheight(101)

        tip_block_num = block_count - 1

        # adding extra transactions to get different block hashes
        block2_hard = self.chain.next_block(block_count, spend=out, extra_txns=8)
        block_count += 1

        self.chain.set_tip(tip_block_num)

        block3_easier = self.chain.next_block(block_count, spend=out, extra_txns=2)
        block_count += 1

        mining_candidate = self.nodes[0].getminingcandidate()
        block4_hard = self.chain.next_block(block_count)
        block4_hard.hashPrevBlock = int(mining_candidate["prevhash"], 16)
        block4_hard.nTime = mining_candidate["time"]
        block4_hard.nVersion = mining_candidate["version"]
        block4_hard.solve()

        mining_solution = {"id": mining_candidate["id"],
                           "nonce": block4_hard.nNonce,
                           "coinbase": ToHex(block4_hard.vtx[0]),
                           "time": mining_candidate["time"],
                           "version": mining_candidate["version"]}

        # send three "hard" blocks, with waitaftervalidatingblock we artificially
        # extend validation time.
        self.log.info(f"hard block2 hash: {block2_hard.hash}")
        self.nodes[0].waitaftervalidatingblock(block2_hard.hash, "add")
        self.log.info(f"hard block4 hash: {block4_hard.hash}")
        self.nodes[0].waitaftervalidatingblock(block4_hard.hash, "add")

        # make sure block hashes are in waiting list
        wait_for_waiting_blocks({block2_hard.hash, block4_hard.hash}, self.nodes[0], self.log)

        # send one block via p2p and one via rpc
        node0.send_message(msg_block(block2_hard))

        # making rpc call submitminingsolution in a separate thread because waitaftervalidation is blocking
        # the return of submitminingsolution
        submitminingsolution_thread = threading.Thread(target=self.nodes[0].submitminingsolution, args=(mining_solution,))
        submitminingsolution_thread.start()

        # because self.nodes[0] rpc is blocked we use another rpc client
        rpc_client = get_rpc_proxy(rpc_url(get_datadir_path(self.options.tmpdir, 0), 0), 0,
                                   coveragedir=self.options.coveragedir)

        wait_for_validating_blocks({block2_hard.hash, block4_hard.hash}, rpc_client, self.log)

        self.log.info(f"easy block3 hash: {block3_easier.hash}")
        node1.send_message(msg_block(block3_easier))

        rpc_client.waitforblockheight(102)
        assert_equal(block3_easier.hash, rpc_client.getbestblockhash())

        # now we can remove waiting status from blocks and finish their validation
        rpc_client.waitaftervalidatingblock(block2_hard.hash, "remove")
        rpc_client.waitaftervalidatingblock(block4_hard.hash, "remove")
        submitminingsolution_thread.join()

        # wait till validation of block or blocks finishes
        node0.sync_with_ping()

        # easier block should still be on tip
        assert_equal(block3_easier.hash, self.nodes[0].getbestblockhash())
class PBVCallGetDataBeforeBlockIsValidated(BitcoinTestFramework):

    def set_test_params(self):
        self.setup_clean_chain = True
        self.num_nodes = 1
        self.chain = ChainManager()
        self.extra_args = [["-whitelist=127.0.0.1"]]

    def run_test(self):
        block_count = 0

        # Create a P2P connection
        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()

        # send one to get out of IBD state
        self.chain.set_genesis_hash(int(self.nodes[0].getbestblockhash(), 16))
        block = self.chain.next_block(block_count)
        block_count += 1
        node0.send_message(msg_block(block))

        self.nodes[0].waitforblockheight(1)

        block_count = self.__test_getdata(node0, block_count)
        block_count = self.__test_getblocks(node0, block_count)

    def __test_getdata(self, node0, block_count):
        block = self.chain.next_block(block_count)
        block_count += 1
        self.log.info(f"block hash: {block.hash}")

        self.__send_blocking_validation_block(block, node0)

        receivedBlock = False

        def on_block(conn, message):
            nonlocal receivedBlock
            message.block.calc_sha256()
            if message.block.sha256 == block.sha256:
                receivedBlock = True
        node0.on_block = on_block
        node0.send_message(msg_getdata([CInv(CInv.BLOCK, int(block.hash, 16))]))

        wait_until(lambda: check_for_log_msg(self, block.hash + " is still waiting as a candidate", "/node0"))

        # remove block validating status to finish validation
        self.nodes[0].waitaftervalidatingblock(block.hash, "remove")

        def wait_for_getdata_reply():
            return receivedBlock
        wait_until(wait_for_getdata_reply)

        return block_count

    def __test_getblocks(self, node0, block_count):
        block1 = self.chain.next_block(block_count)
        block_count += 1
        self.log.info(f"block1 hash: {block1.hash}")

        self.__send_blocking_validation_block(block1, node0)

        receivedBlock = False

        def on_block(conn, message):
            nonlocal receivedBlock
            message.block.calc_sha256()
            if message.block.sha256 == block1.sha256:
                receivedBlock = True
        node0.on_block = on_block
        node0.send_message(msg_getblocks())

        block2 = self.chain.next_block(block_count)
        block_count += 1
        self.log.info(f"block2 hash: {block2.hash}")

        self.__send_blocking_validation_waiting_block(block2, node0)

        wait_until(lambda: check_for_log_msg(self, "Blocks that were received before getblocks message", "/node0"))

        # remove block validating status to finish validation
        self.nodes[0].waitaftervalidatingblock(block1.hash, "remove")

        def wait_for_getblocks_reply():
            return receivedBlock
        wait_until(wait_for_getblocks_reply)

        # remove block validating status to finish validation
        self.nodes[0].waitaftervalidatingblock(block2.hash, "remove")

        # wait till validation of block finishes
        node0.sync_with_ping()

        return block_count

    def __send_blocking_validation_waiting_block(self, block, node0):
        # set block validating status to wait after validation
        self.nodes[0].waitaftervalidatingblock(block.hash, "add")

        # make sure block hash is in the waiting list
        wait_for_waiting_blocks({block.hash}, self.nodes[0], self.log)

        node0.send_message(msg_block(block))

    def __send_blocking_validation_block(self, block, node0):
        self.__send_blocking_validation_waiting_block(block, node0)
        wait_for_validating_blocks({block.hash}, self.nodes[0], self.log)
Esempio n. 20
0
class PBVInvalidate(BitcoinTestFramework):
    def set_test_params(self):
        self.setup_clean_chain = True
        self.num_nodes = 1
        self.chain = ChainManager()
        self.extra_args = [["-whitelist=127.0.0.1"]]

    def run_test(self):

        block_count = 0

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

        node1 = NodeConnCB()
        connection = NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], node1)
        node1.add_connection(connection)

        node2 = NodeConnCB()
        connection = NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], node2)
        node2.add_connection(connection)

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

        self.log.info("Sending blocks to get spendable output")
        self.chain.set_genesis_hash(int(self.nodes[0].getbestblockhash(), 16))
        block = self.chain.next_block(block_count)
        block_count += 1
        self.chain.save_spendable_output()
        node0.send_message(msg_block(block))

        for i in range(100):
            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(100):
            out.append(self.chain.get_spendable_output())

        self.log.info("waiting for block height 101 via rpc")
        self.nodes[0].waitforblockheight(101)

        tip_block_num = block_count - 1

        block2 = self.chain.next_block(block_count, spend=out[0], extra_txns=1)
        block2_count = block_count
        block_count += 1
        self.log.info(f"blockA hash: {block2.hash}")
        node0.send_message(msg_block(block2))
        self.nodes[0].waitforblockheight(102)

        block3_hard = self.chain.next_block(block_count,
                                            spend=out[1],
                                            extra_txns=8)
        block_count += 1
        self.chain.set_tip(block2_count)

        block4_easier = self.chain.next_block(block_count,
                                              spend=out[1],
                                              extra_txns=2)
        block_count += 1
        self.chain.set_tip(block2_count)

        block5_hard = self.chain.next_block(block_count,
                                            spend=out[1],
                                            extra_txns=10)
        block_count += 1

        # send two "hard" blocks, with waitaftervalidatingblock we artificially
        # extend validation time.
        self.log.info(f"hard block3 hash: {block3_hard.hash}")
        self.nodes[0].waitaftervalidatingblock(block3_hard.hash, "add")
        self.log.info(f"hard block5 hash: {block5_hard.hash}")
        self.nodes[0].waitaftervalidatingblock(block5_hard.hash, "add")
        # make sure block hashes are in waiting list
        wait_for_waiting_blocks({block3_hard.hash, block5_hard.hash},
                                self.nodes[0], self.log)

        self.log.info(
            "Sending blocks 3,4,5 on branch 2 for parallel validation")
        node0.send_message(msg_block(block3_hard))
        node2.send_message(msg_block(block5_hard))

        # make sure we started validating blocks
        wait_for_validating_blocks({block3_hard.hash, block5_hard.hash},
                                   self.nodes[0], self.log)

        self.log.info(f"easier hash: {block4_easier.hash}")
        node1.send_message(msg_block(block4_easier))
        self.nodes[0].waitforblockheight(103)

        # Because 4 is easy to validate it will be validated first and set as active tip
        assert_equal(block4_easier.hash, self.nodes[0].getbestblockhash())

        # now we can remove waiting status from blocks and finish their validation
        self.nodes[0].waitaftervalidatingblock(block3_hard.hash, "remove")
        self.nodes[0].waitaftervalidatingblock(block5_hard.hash, "remove")

        # wait till validation of block or blocks finishes
        node0.sync_with_ping()

        # easier block should still be on tip
        assert_equal(block4_easier.hash, self.nodes[0].getbestblockhash())

        self.log.info("Sending blocks 6,7,8 on competing chain to cause reorg")
        self.chain.set_tip(tip_block_num)

        block6 = self.chain.next_block(block_count, spend=out[0], extra_txns=2)
        block_count += 1
        self.log.info(f"block6 hash: {block6.hash}")
        node0.send_message(msg_block(block6))

        block7 = self.chain.next_block(block_count)
        block_count += 1
        self.log.info(f"block7: {block7.hash}")
        node0.send_message(msg_block(block7))

        # send one to cause reorg this should be active
        block8 = self.chain.next_block(block_count)
        block_count += 1
        self.log.info(f"block8: {block8.hash}")
        node0.send_message(msg_block(block8))

        self.nodes[0].waitforblockheight(104)
        assert_equal(block8.hash, self.nodes[0].getbestblockhash())

        self.log.info(
            "Invalidating block7 on competing chain to reorg to first branch again"
        )
        self.log.info(f"invalidating hash {block7.hash}")
        self.nodes[0].invalidateblock(block7.hash)

        #after invalidating, active block should be the one first validated on first branch
        assert_equal(block4_easier.hash, self.nodes[0].getbestblockhash())
class PBVProcessingOrder(BitcoinTestFramework):
    def set_test_params(self):
        self.setup_clean_chain = True
        self.num_nodes = 1
        self.chain = ChainManager()
        self.extra_args = [["-whitelist=127.0.0.1"]]

    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))

        _, out, block_count = prepare_init_chain(self.chain,
                                                 101,
                                                 100,
                                                 start_block=0,
                                                 block_0=False,
                                                 node=node0)

        self.log.info("waiting for block height 101 via rpc")
        self.nodes[0].waitforblockheight(101)

        # wait till validation of block or blocks finishes
        node0.sync_with_ping()

        block1 = self.chain.next_block(block_count, spend=out[0], extra_txns=8)
        block_count += 1
        # send block but block him at validation point
        self.nodes[0].waitaftervalidatingblock(block1.hash, "add")
        node0.send_message(msg_block(block1))
        self.log.info(f"block1 hash: {block1.hash}")

        # make sure block hash is in waiting list
        wait_for_waiting_blocks({block1.hash}, self.nodes[0], self.log)

        # send child block
        block2 = self.chain.next_block(block_count,
                                       spend=out[1],
                                       extra_txns=10)
        block_count += 1
        node0.send_message(msg_block(block2))
        self.log.info(f"block2 hash: {block2.hash}")

        wait_until(lambda: check_for_log_msg(
            self, block2.hash + " will not be considered by the current",
            "/node0"))

        self.nodes[0].waitaftervalidatingblock(block1.hash, "remove")

        # wait till validation of block or blocks finishes
        node0.sync_with_ping()

        # block that arrived last on competing chain should be active
        assert_equal(block2.hash, self.nodes[0].getbestblockhash())
Esempio n. 22
0
class PBVTerminate(BitcoinTestFramework):
    def set_test_params(self):
        self.setup_clean_chain = True
        self.num_nodes = 1
        self.chain = ChainManager()
        self.extra_args = [[
            "-whitelist=127.0.0.1", "-maxparallelblocks=3",
            "-maxparallelblocksperpeer=3"
        ]]

    def run_test(self):
        block_count = 0

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

        node1 = NodeConnCB()
        connection = NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], node1)
        node1.add_connection(connection)

        node2 = NodeConnCB()
        connection = NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], node2)
        node2.add_connection(connection)

        node3 = NodeConnCB()
        connection = NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], node3)
        node3.add_connection(connection)

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

        self.chain.set_genesis_hash(int(self.nodes[0].getbestblockhash(), 16))
        block = self.chain.next_block(block_count)
        block_count += 1
        self.chain.save_spendable_output()
        node0.send_message(msg_block(block))

        for i in range(100):
            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(100):
            out.append(self.chain.get_spendable_output())

        self.log.info("waiting for block height 101 via rpc")
        self.nodes[0].waitforblockheight(101)

        tip_block_num = block_count - 1

        block2 = self.chain.next_block(block_count, spend=out[0], extra_txns=8)
        block_count += 1

        self.chain.set_tip(tip_block_num)

        block3 = self.chain.next_block(block_count,
                                       spend=out[0],
                                       extra_txns=10)
        block_count += 1

        self.chain.set_tip(tip_block_num)

        block4 = self.chain.next_block(block_count,
                                       spend=out[0],
                                       extra_txns=12)
        block_count += 1

        self.chain.set_tip(tip_block_num)

        block5 = self.chain.next_block(block_count,
                                       spend=out[0],
                                       extra_txns=14)
        block5_num = block_count
        block_count += 1

        block6 = self.chain.next_block(block_count, spend=out[1], extra_txns=8)
        block_count += 1

        self.chain.set_tip(block5_num)

        block7 = self.chain.next_block(block_count,
                                       spend=out[1],
                                       extra_txns=10)

        self.log.info(f"block2 hash: {block2.hash}")
        self.nodes[0].waitaftervalidatingblock(block2.hash, "add")
        self.log.info(f"block3 hash: {block3.hash}")
        self.nodes[0].waitaftervalidatingblock(block3.hash, "add")
        self.log.info(f"block4 hash: {block4.hash}")
        self.nodes[0].waitaftervalidatingblock(block4.hash, "add")

        # make sure block hashes are in waiting list
        wait_for_waiting_blocks({block2.hash, block3.hash, block4.hash},
                                self.nodes[0], self.log)

        node0.send_message(msg_block(block2))
        # make sure we started validating block2 first as we expect this one to
        # be terminated later on in the test before its validation is complete
        # (algorithm for premature termination selects based on block height and
        # and validation duration - those that are in validation with smaller
        # height and longer are terminated first)
        wait_for_validating_blocks({block2.hash}, self.nodes[0], self.log)

        node1.send_message(msg_block(block3))
        node2.send_message(msg_block(block4))
        # make sure we started validating blocks
        wait_for_validating_blocks({block2.hash, block3.hash, block4.hash},
                                   self.nodes[0], self.log)

        node3.send_message(msg_block(block5))
        self.log.info(f"block5 hash: {block5.hash}")

        # check log file for logging about which block validation was terminated
        termination_log_found = False
        for line in open(
                glob.glob(self.options.tmpdir + "/node0" +
                          "/regtest/bitcoind.log")[0]):
            if f"Block {block2.hash} will not be considered by the current tip activation as the maximum parallel block" in line:
                termination_log_found = True
                self.log.info("Found line: %s", line.strip())
                break

        self.log.info(f"block6 hash: {block6.hash}")
        self.nodes[0].waitaftervalidatingblock(block6.hash, "add")
        self.log.info(f"block7 hash: {block7.hash}")
        self.nodes[0].waitaftervalidatingblock(block7.hash, "add")

        wait_for_waiting_blocks({block6.hash, block7.hash}, self.nodes[0],
                                self.log)

        node3.send_message(msg_block(block6))
        wait_for_validating_blocks({block6.hash}, self.nodes[0], self.log)

        node3.send_message(msg_block(block7))
        wait_for_validating_blocks({block7.hash}, self.nodes[0], self.log)

        self.nodes[0].waitaftervalidatingblock(block2.hash, "remove")
        # block2 should be canceled.
        wait_for_not_validating_blocks({block2.hash}, self.nodes[0], self.log)

        self.log.info("removing wait status from block7")
        self.nodes[0].waitaftervalidatingblock(block7.hash, "remove")

        # finish block7 validation
        wait_for_not_validating_blocks({block7.hash}, self.nodes[0], self.log)

        # remove wait status from block to finish its validations so the test exits properly
        self.nodes[0].waitaftervalidatingblock(block3.hash, "remove")
        self.nodes[0].waitaftervalidatingblock(block4.hash, "remove")
        self.nodes[0].waitaftervalidatingblock(block6.hash, "remove")

        # wait till validation of block or blocks finishes
        node0.sync_with_ping()

        # block7 should be active in the end
        assert_equal(block7.hash, self.nodes[0].getbestblockhash())

        # check log file for logging about which block validation was terminated
        termination_log_found = False
        for line in open(
                glob.glob(self.options.tmpdir + "/node0" +
                          "/regtest/bitcoind.log")[0]):
            if f"Block {block2.hash} validation was terminated before completion." in line:
                termination_log_found = True
                self.log.info("Found line: %s", line.strip())
                break

        assert_equal(termination_log_found, True)
class PBVWaitAfterValidation(BitcoinTestFramework):

    def set_test_params(self):
        self.setup_clean_chain = True
        self.num_nodes = 1
        self.chain = ChainManager()
        self.extra_args = [["-whitelist=127.0.0.1"]]

    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)

        node1 = NodeConnCB()
        connection = NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], node1)
        node1.add_connection(connection)

        node2 = NodeConnCB()
        connection = NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], node2)
        node2.add_connection(connection)

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

        self.chain.set_genesis_hash(int(self.nodes[0].getbestblockhash(), 16))
        block = self.chain.next_block(block_count)
        block_count += 1
        self.chain.save_spendable_output()
        node0.send_message(msg_block(block))

        for i in range(100):
            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(100):
            out.append(self.chain.get_spendable_output())

        self.log.info("waiting for block height 101 via rpc")
        self.nodes[0].waitforblockheight(101)

        tip_block_num = block_count - 1

        # adding extra transactions to get different block hashes
        block2_hard = self.chain.next_block(block_count, spend=out[0], extra_txns=8)
        block_count += 1

        self.chain.set_tip(tip_block_num)

        block3_easier = self.chain.next_block(block_count, spend=out[0], extra_txns=2)
        block_count += 1

        self.chain.set_tip(tip_block_num)

        block4_hard = self.chain.next_block(block_count, spend=out[0], extra_txns=10)
        block_count += 1

        # send two "hard" blocks, with waitaftervalidatingblock we artificially
        # extend validation time.
        self.log.info(f"hard block2 hash: {block2_hard.hash}")
        self.nodes[0].waitaftervalidatingblock(block2_hard.hash, "add")
        self.log.info(f"hard block4 hash: {block4_hard.hash}")
        self.nodes[0].waitaftervalidatingblock(block4_hard.hash, "add")
        # make sure block hashes are in waiting list
        wait_for_waiting_blocks({block2_hard.hash, block4_hard.hash}, self.nodes[0], self.log)

        node0.send_message(msg_block(block2_hard))
        node1.send_message(msg_block(block4_hard))

        # make sure we started validating blocks
        wait_for_validating_blocks({block2_hard.hash, block4_hard.hash}, self.nodes[0], self.log)

        self.log.info(f"easier block3 hash: {block3_easier.hash}")
        node2.send_message(msg_block(block3_easier))

        self.nodes[0].waitforblockheight(102)
        assert_equal(block3_easier.hash, self.nodes[0].getbestblockhash())

        # now we can remove waiting status from blocks and finish their validation
        self.nodes[0].waitaftervalidatingblock(block2_hard.hash, "remove")
        self.nodes[0].waitaftervalidatingblock(block4_hard.hash, "remove")

        # wait till validation of block or blocks finishes
        node0.sync_with_ping()

        # easier block should still be on tip
        assert_equal(block3_easier.hash, self.nodes[0].getbestblockhash())
class PBVReorgShutdown(BitcoinTestFramework):
    def set_test_params(self):
        self.setup_clean_chain = True
        self.num_nodes = 1
        self.chain = ChainManager()
        self.extra_args = [["-whitelist=127.0.0.1"]]

    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)

        node1 = NodeConnCB()
        connection = NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], node1)
        node1.add_connection(connection)

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

        self.chain.set_genesis_hash(int(self.nodes[0].getbestblockhash(), 16))
        block = self.chain.next_block(block_count)
        block_count += 1
        self.chain.save_spendable_output()
        node0.send_message(msg_block(block))

        for i in range(100):
            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(100):
            out.append(self.chain.get_spendable_output())

        self.log.info("waiting for block height 101 via rpc")
        self.nodes[0].waitforblockheight(101)

        tip_block_num = block_count - 1

        # left branch
        block2 = self.chain.next_block(block_count, spend=out[0], extra_txns=8)
        block2_num = block_count
        block_count += 1
        node0.send_message(msg_block(block2))
        self.log.info(f"block2 hash: {block2.hash}")

        self.nodes[0].waitforblockheight(102)

        # send blocks 3,4 to force a reorg
        self.chain.set_tip(tip_block_num)
        block3 = self.chain.next_block(block_count,
                                       spend=out[1],
                                       extra_txns=10)
        block_count += 1
        block4 = self.chain.next_block(block_count, spend=out[1], extra_txns=8)
        block_count += 1

        self.log.info(f"block3 hash: {block3.hash}")
        self.nodes[0].waitaftervalidatingblock(block3.hash, "add")

        # make sure block hash is in waiting list
        wait_for_waiting_blocks({block3.hash}, self.nodes[0], self.log)

        node0.send_message(msg_block(block3))
        node0.send_message(msg_block(block4))

        # make sure we started validating blocks
        wait_for_validating_blocks({block3.hash}, self.nodes[0], self.log)

        self.stop_node(0)