コード例 #1
0
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)
コード例 #2
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()
コード例 #3
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()
コード例 #4
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", "-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))
コード例 #5
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)
コード例 #6
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)
コード例 #7
0
class FrozenTXOSoftConsensusFreeze(SoftConsensusFreezeBase):
    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", "-softconsensusfreezeduration=4",
            "-disablesafemode=1"
        ]]
        self.block_count = 0

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

        first_frozen_tx = self._create_tx_mine_block_and_freeze_tx(
            node, spendable_out[0])
        second_frozen_tx = self._create_tx_mine_block_and_freeze_tx(
            node, spendable_out[1])

        # block is rejected as consensus freeze is in effect for parent transaction
        first_spend_frozen_tx = self._create_tx(
            PreviousSpendableOutput(first_frozen_tx, 0), b'',
            CScript([OP_TRUE]))
        first_frozen_block = self._mine_and_check_rejected(
            node, first_spend_frozen_tx)

        # block is accepted but ignored since freeze is in place for previous block
        second_spend_frozen_tx = self._create_tx(
            PreviousSpendableOutput(second_frozen_tx, 0), b'',
            CScript([OP_TRUE]))
        second_frozen_block = self._mine_and_send_block(
            second_spend_frozen_tx, node, False, node.rpc.getbestblockhash())

        # both blocks are still frozen
        self._mine_and_send_block(None, node, False,
                                  node.rpc.getbestblockhash())
        self._mine_and_send_block(None, node, False,
                                  node.rpc.getbestblockhash())
        self._mine_and_send_block(None, node, False,
                                  node.rpc.getbestblockhash())

        # first block is unfrozen but since height restriction is not met due
        # to second block being frozen, we remain on the old tip
        self._mine_and_send_block(None, node, False,
                                  node.rpc.getbestblockhash())
        node.reject_check(second_frozen_block)

        # all blocks are unfrozen
        self._mine_and_send_block(None, node)

    def _test_soft_consensus_freeze_on_refreeze(self, spendable_out, node):
        self.log.info(
            "*** Performing soft consensus freeze on refreeze checks")

        first_frozen_tx = self._create_tx_mine_block_and_freeze_tx(
            node, spendable_out)

        tip_height = node.rpc.getblockcount()

        # block is rejected as consensus freeze is in effect for parent transaction
        first_spend_frozen_tx = self._create_tx(
            PreviousSpendableOutput(first_frozen_tx, 0), b'',
            CScript([OP_TRUE]))
        first_frozen_block = self._mine_and_check_rejected(
            node, first_spend_frozen_tx)
        first_frozen_block_height = tip_height + 1

        freeze_for_two_blocks = first_frozen_block_height + 2

        # limit the duration of freeze
        self.log.info(
            f"Freezing TXO {first_frozen_tx.hash} on consensus blacklist until height {freeze_for_two_blocks}"
        )
        result = node.rpc.addToConsensusBlacklist({
            "funds": [{
                "txOut": {
                    "txId": first_frozen_tx.hash,
                    "vout": 0
                },
                "enforceAtHeight": [{
                    "start": 0,
                    "stop": freeze_for_two_blocks
                }],
                "policyExpiresWithConsensus":
                False
            }]
        })
        assert_equal(result["notProcessed"], [])

        # block is expected to still be frozen
        self._mine_and_send_block(None, node, False,
                                  node.rpc.getbestblockhash())
        self._mine_and_send_block(None, node, False,
                                  node.rpc.getbestblockhash())
        self._mine_and_send_block(None, node, False,
                                  node.rpc.getbestblockhash())

        # block is expected to still be frozen even though we've changed the freeze
        # duration as once the frozen calculation is performed on a block it is
        # never changed
        self._mine_and_send_block(None, node, False,
                                  node.rpc.getbestblockhash())

        # all blocks are unfrozen - this proves that the old duration remained
        # in place
        self._mine_and_send_block(None, node)

    def _test_soft_consensus_freeze_clear_all(self, spendable_out, node):
        self.log.info("*** Performing soft consensus freeze on clear all")

        # perform initial clear so that other tests don't interfere with this one
        node.rpc.clearBlacklists({"removeAllEntries": True})

        first_frozen_tx = self._create_tx_mine_block_and_freeze_tx(
            node, spendable_out)

        # block is rejected as consensus freeze is in effect for parent transaction
        first_spend_frozen_tx = self._create_tx(
            PreviousSpendableOutput(first_frozen_tx, 0), b'',
            CScript([OP_TRUE]))
        first_frozen_block = self._mine_and_check_rejected(
            node, first_spend_frozen_tx)

        # clear all frozen entries
        result = node.rpc.clearBlacklists({"removeAllEntries": True})
        assert_equal(result["numRemovedEntries"], 1)

        # block is expected to still be frozen even though we've changed the freeze
        # duration as once the frozen calculation is performed on a block it is
        # never changed
        self._mine_and_send_block(None, node, False,
                                  node.rpc.getbestblockhash())
        self._mine_and_send_block(None, node, False,
                                  node.rpc.getbestblockhash())
        self._mine_and_send_block(None, node, False,
                                  node.rpc.getbestblockhash())
        self._mine_and_send_block(None, node, False,
                                  node.rpc.getbestblockhash())

        # all blocks are unfrozen - this proves that the old duration remained
        # in place
        self._mine_and_send_block(None, node)

    def _test_soft_consensus_freeze_invalidate_block(self, spendable_out,
                                                     node):
        self.log.info(
            "*** Performing soft consensus freeze and invalidate block checks")

        first_frozen_tx = self._create_tx_mine_block_and_freeze_tx(
            node, spendable_out[0])
        second_frozen_tx = self._create_tx_mine_block_and_freeze_tx(
            node, spendable_out[1])

        # block is rejected as consensus freeze is in effect for parent transaction
        first_spend_frozen_tx = self._create_tx(
            PreviousSpendableOutput(first_frozen_tx, 0), b'',
            CScript([OP_TRUE]))
        first_frozen_block = self._mine_and_check_rejected(
            node, first_spend_frozen_tx)

        # block is accepted but ignored since freeze is in place for previous block
        second_spend_frozen_tx = self._create_tx(
            PreviousSpendableOutput(second_frozen_tx, 0), b'',
            CScript([OP_TRUE]))
        second_frozen_block = self._mine_and_send_block(
            second_spend_frozen_tx, node, False, node.rpc.getbestblockhash())

        block_before_frozen_blocks_hash = node.rpc.getbestblockhash()

        # both blocks are still frozen
        self._mine_and_send_block(None, node, False,
                                  block_before_frozen_blocks_hash)
        self._mine_and_send_block(None, node, False,
                                  block_before_frozen_blocks_hash)
        self._mine_and_send_block(None, node, False,
                                  block_before_frozen_blocks_hash)

        # first block is unfrozen but since height restriction is not met due
        # to second block being frozen, we remain on the old tip
        self._mine_and_send_block(None, node, False,
                                  block_before_frozen_blocks_hash)
        node.reject_check(second_frozen_block)

        # save hash and time of the last soft frozen block for later
        last_soft_frozen_hash = self.chain.tip.hash
        last_soft_frozen_time = self.chain.tip.nTime

        # all blocks are unfrozen
        block = self._mine_and_send_block(None, node)
        node.rpc.invalidateblock(block.hash)

        assert (block_before_frozen_blocks_hash == node.rpc.getbestblockhash())

        # check that reconsidering the block works as expected
        node.rpc.reconsiderblock(block.hash)
        assert (block.hash == node.rpc.getbestblockhash())

        # check that verifychain works after node restart
        assert node.rpc.verifychain(4, 0)
        node.restart_node()
        assert node.rpc.verifychain(4, 0)

        # check that invalidateblock works after node restart
        node.restart_node()
        assert (block.hash == node.rpc.getbestblockhash())
        node.rpc.invalidateblock(block.hash)
        assert (block_before_frozen_blocks_hash == node.rpc.getbestblockhash())

        # create coinbase output that pays to much
        invalid_coinbase_tx = create_coinbase(height=node.rpc.getblockcount() +
                                              1,
                                              outputValue=300)
        invalid_block = create_block(int(last_soft_frozen_hash,
                                         16), invalid_coinbase_tx,
                                     last_soft_frozen_time + 1)
        invalid_block.solve()
        node.p2p.send_and_ping(msg_block(invalid_block))
        assert (node.check_log(
            f"ConnectBlock {invalid_block.hash} failed \\(bad-cb-amount \\(code 16\\)\\)"
        ))

        # make sure tip is still the same
        assert (block_before_frozen_blocks_hash == node.rpc.getbestblockhash())

        node.rpc.reconsiderblock(block.hash)
        assert (block.hash == node.rpc.getbestblockhash())

    def _test_soft_consensus_freeze_submitblock(self, spendable_out, node):
        self.log.info(
            "*** Performing soft consensus freeze with submitblock RPC")

        frozen_tx = self._create_tx_mine_block_and_freeze_tx(
            node, spendable_out)

        last_valid_block_hash = node.rpc.getbestblockhash()

        # block should not become new tip as it contains transaction spending frozen TXO
        spend_frozen_tx = self._create_tx(
            PreviousSpendableOutput(frozen_tx, 0), b'', CScript([OP_TRUE]))
        frozen_block = self._mine_block(spend_frozen_tx)
        self.submit_block_and_check_tip(node, frozen_block,
                                        last_valid_block_hash)

        # block is still frozen
        self.submit_block_and_check_tip(node, self._mine_block(None),
                                        last_valid_block_hash)
        self.submit_block_and_check_tip(node, self._mine_block(None),
                                        last_valid_block_hash)
        self.submit_block_and_check_tip(node, self._mine_block(None),
                                        last_valid_block_hash)
        self.submit_block_and_check_tip(node, self._mine_block(None),
                                        last_valid_block_hash)

        # all blocks are now unfrozen
        new_valid_block = self._mine_block(None)
        self.submit_block_and_check_tip(node, new_valid_block,
                                        new_valid_block.hash)

    def _test_soft_consensus_freeze_competing_chains(self, spendable_txo,
                                                     node):
        self.log.info(
            "*** Performing soft consensus freeze with competing chains")

        frozen_tx = self._create_tx_mine_block_and_freeze_tx(
            node, spendable_txo)

        root_chain_tip = self.get_chain_tip()

        # mine 5 blocks on valid chain (one less than is needed for the frozen chain to become active)
        self.log.info("Mining blocks on valid chain")
        self._mine_and_send_block(None, node)
        self._mine_and_send_block(None, node)
        self._mine_and_send_block(None, node)
        self._mine_and_send_block(None, node)
        last_valid_block = self._mine_and_send_block(None, node)

        valid_chain_tip = self.get_chain_tip()

        self.set_chain_tip(root_chain_tip)

        self.log.info("Mining blocks on frozen chain")
        # block should not become new tip as it contains transaction spending frozen TXO
        spend_frozen_tx = self._create_tx(
            PreviousSpendableOutput(frozen_tx, 0), b'', CScript([OP_TRUE]))
        frozen_block = self._mine_block(spend_frozen_tx)
        self.submit_block_and_check_tip(node, frozen_block,
                                        last_valid_block.hash)

        # next 4 blocks are also considered soft consensus frozen and must not become new tip
        self.submit_block_and_check_tip(node, self._mine_block(None),
                                        last_valid_block.hash)
        self.submit_block_and_check_tip(node, self._mine_block(None),
                                        last_valid_block.hash)
        self.submit_block_and_check_tip(node, self._mine_block(None),
                                        last_valid_block.hash)
        self.submit_block_and_check_tip(node, self._mine_block(None),
                                        last_valid_block.hash)

        # this block is high enough for the frozen chain to become active and should become new tip
        new_frozen_tip = self._mine_and_send_block(None, node)

        frozen_chain_tip = self.get_chain_tip()

        self.log.info("Mining blocks on valid chain")
        # 2 new blocks on valid chain should trigger reorg back to valid chain
        self.set_chain_tip(valid_chain_tip)
        next_frozen_tip = self._mine_block(None)
        self.submit_block_and_check_tip(node, next_frozen_tip,
                                        new_frozen_tip.hash)
        new_valid_tip = self._mine_block(None)
        node.p2p.send_and_ping(msg_block(new_valid_tip))
        assert_equal(new_valid_tip.hash, node.rpc.getbestblockhash())
        assert (
            node.check_frozen_tx_log(next_frozen_tip.hash)
        )  # NOTE: Reject is expected because transaction spending frozen TXO is added back to mempool and its validation must fail when checked against new tip.

        self.log.info("Mining blocks on frozen chain")
        # 2 new blocks on frozen chain should trigger reorg back to frozen chain
        self.set_chain_tip(frozen_chain_tip)
        self.submit_block_and_check_tip(node, self._mine_block(None),
                                        new_valid_tip.hash)
        self._mine_and_send_block(None, node)

    def _test_soft_consensus_freeze_invalid_frozen_block(
            self, spendable_txos, node):
        self.log.info(
            "*** Performing soft consensus freeze with invalid frozen block")

        frozen_tx = self._create_tx_mine_block_and_freeze_tx(
            node, spendable_txos[0])

        root_chain_tip = self.get_chain_tip()

        # mine 5 blocks on valid chain (one less than is needed for the frozen chain to become active)
        self.log.info("Mining blocks on valid chain")
        self._mine_and_send_block(None, node)
        self._mine_and_send_block(None, node)
        self._mine_and_send_block(None, node)
        self._mine_and_send_block(None, node)
        last_valid_block = self._mine_and_send_block(None, node)

        self.set_chain_tip(root_chain_tip)

        self.log.info("Mining blocks on frozen chain")
        # block should not become new tip as it is not high enough
        # it should also be considered soft consensus frozen because it contains transaction spending frozen TXO
        # and is invalid because coinbase pays too much
        spend_frozen_tx = self._create_tx(
            PreviousSpendableOutput(frozen_tx, 0), b'', CScript([OP_TRUE]))
        frozen_block = self._mine_block(spend_frozen_tx)
        frozen_block.vtx[0].vout[
            0].nValue = 300 * COIN  # coinbase that pays too much
        frozen_block.vtx[0].rehash()
        self.chain.update_block(self.block_count - 1, [])
        self.submit_block_and_check_tip(node, frozen_block,
                                        last_valid_block.hash)

        # next 4 blocks would also be considered soft consensus frozen and must not become new tip
        self.submit_block_and_check_tip(node, self._mine_block(None),
                                        last_valid_block.hash)
        self.submit_block_and_check_tip(node, self._mine_block(None),
                                        last_valid_block.hash)
        self.submit_block_and_check_tip(node, self._mine_block(None),
                                        last_valid_block.hash)
        self.submit_block_and_check_tip(node, self._mine_block(None),
                                        last_valid_block.hash)

        # invalid block has not yet been validated
        frozen_block_block_checked_log_string = f"ConnectBlock {frozen_block.hash} failed \\(bad-cb-amount \\(code 16\\)\\)"
        assert (not node.check_log(frozen_block_block_checked_log_string))

        # this block is high enough for the frozen chain to become active but
        # it should not, because the block is invalid
        new_frozen_tip = self._mine_and_send_block(None, node, False,
                                                   last_valid_block.hash)

        # invalid block has now been validated
        assert (node.check_log(frozen_block_block_checked_log_string))

        # same thing again but with frozen block that is also invalid because it contains invalid transaction
        self.set_chain_tip(root_chain_tip)
        frozen_block = self._mine_block(spend_frozen_tx)
        valid_tx = self._create_tx(spendable_txos[1], b'',
                                   CScript([OP_TRUE, OP_DROP] * 15))
        frozen_block.vtx.extend([valid_tx])
        invalid_tx = self._create_tx(PreviousSpendableOutput(valid_tx, 0),
                                     CScript([OP_FALSE]), CScript([OP_TRUE]))
        frozen_block.vtx.extend([invalid_tx])
        self.chain.update_block(self.block_count - 1, [])
        self.submit_block_and_check_tip(node, frozen_block,
                                        last_valid_block.hash)
        self.submit_block_and_check_tip(node, self._mine_block(None),
                                        last_valid_block.hash)
        self.submit_block_and_check_tip(node, self._mine_block(None),
                                        last_valid_block.hash)
        self.submit_block_and_check_tip(node, self._mine_block(None),
                                        last_valid_block.hash)
        self.submit_block_and_check_tip(node, self._mine_block(None),
                                        last_valid_block.hash)
        frozen_block_block_checked_log_string = f"ConnectBlock {frozen_block.hash} failed \\(blk-bad-inputs, parallel script check failed \\(code 16\\)\\)"
        assert (not node.check_log(frozen_block_block_checked_log_string))
        self._mine_and_send_block(None, node, False, last_valid_block.hash)
        assert (node.check_log(frozen_block_block_checked_log_string))

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

        send_node = Send_node(self.options.tmpdir, self.log, 0, node,
                              self.nodes[0])

        spendable_out_1 = [
            self.chain.get_spendable_output(),
            self.chain.get_spendable_output()
        ]
        spendable_out_2 = self.chain.get_spendable_output()
        spendable_out_3 = self.chain.get_spendable_output()
        spendable_out_4 = [
            self.chain.get_spendable_output(),
            self.chain.get_spendable_output()
        ]
        spendable_out_5 = self.chain.get_spendable_output()
        spendable_out_6 = self.chain.get_spendable_output()
        spendable_out_7 = [
            self.chain.get_spendable_output(),
            self.chain.get_spendable_output()
        ]

        self._test_soft_consensus_freeze(spendable_out_1, send_node)
        self._test_soft_consensus_freeze_on_refreeze(spendable_out_2,
                                                     send_node)
        self._test_soft_consensus_freeze_clear_all(spendable_out_3, send_node)
        self._test_soft_consensus_freeze_invalidate_block(
            spendable_out_4, send_node)
        self._test_soft_consensus_freeze_submitblock(spendable_out_5,
                                                     send_node)
        self._test_soft_consensus_freeze_competing_chains(
            spendable_out_6, send_node)
        self._test_soft_consensus_freeze_invalid_frozen_block(
            spendable_out_7, send_node)
コード例 #8
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)