Example #1
0
    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 _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)
Example #3
0
    def _test_default_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)

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

        # 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())
        last_frozen_block = self._mine_and_send_block(
            None, node, False, node.rpc.getbestblockhash())

        bestblockhash = node.rpc.getbestblockhash()

        # check that precious block does not have impact on soft freeze duration
        node.rpc.preciousblock(last_frozen_block.hash)

        # assert preciousblock did not change the tip
        assert (bestblockhash == node.rpc.getbestblockhash())

        # all blocks are unfrozen
        self._mine_and_send_block(None, node)
    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)
Example #5
0
    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)
Example #6
0
    def _test_unlimited_freeze(self, spendable_out, node):
        self.log.info(
            "*** Performing soft consensus freeze checks (unlimited)")

        node.restart_node()

        first_frozen_tx = self._create_tx_mine_block_and_freeze_tx(
            node, spendable_out)

        last_valid_tip_hash = node.rpc.getbestblockhash()

        # this 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]))
        self.log.info(
            f"Mining block with transaction {first_spend_frozen_tx.hash} spending TXO {first_spend_frozen_tx.vin[0].prevout.hash:064x},{first_spend_frozen_tx.vin[0].prevout.n}"
        )
        first_frozen_block = self._mine_block(first_spend_frozen_tx)

        self.log.info(f"Mining descendants of block {first_frozen_block.hash}")
        subsequent_frozen_blocks = []
        for i in range(
                14
        ):  # since we cannot check unlimited number of blocks, number 14 was chosen arbitrarily and we assume it is close enough to infinity for the purpose of this test
            subsequent_frozen_blocks.append(self._mine_block(None))

        # Send block headers first to check that they are also all correctly marked as frozen after the actual block is received.
        self.log.info(
            f"Sending headers for block {first_frozen_block.hash} and descendants"
        )
        msg_hdrs = msg_headers()
        msg_hdrs.headers.append(first_frozen_block)
        msg_hdrs.headers.extend(subsequent_frozen_blocks)
        node.p2p.send_and_ping(msg_hdrs)
        # check that headers were received
        for tip in node.rpc.getchaintips():
            if tip["status"] == "active" and tip["hash"] == last_valid_tip_hash:
                continue
            if tip["status"] == "headers-only" and tip[
                    "hash"] == subsequent_frozen_blocks[-1].hash:
                continue
            assert False, "Unexpected tip: " + str(tip)

        self.log.info(
            f"Sending block {first_frozen_block.hash} and checking that it is rejected"
        )
        node.send_block(first_frozen_block, last_valid_tip_hash, True)
        assert (node.check_frozen_tx_log(first_frozen_block.hash))
        assert (node.check_log(
            "Block was rejected because it included a transaction, which tried to spend a frozen transaction output.*"
            + first_frozen_block.hash))

        self.log.info(
            f"Sending descendants of block {first_frozen_block.hash} and checking that they do not become tip"
        )
        for b in subsequent_frozen_blocks:
            node.send_block(b, last_valid_tip_hash, False)
    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 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 - 1000  # one satoshi to fee
         tx = create_transaction(spend.tx, spend.n, b"", money_to_spend, self.locking_script)
         self.sign_tx(tx, spend.tx, spend.n)
         tx.rehash()
         txns.append(tx)
         spend = PreviousSpendableOutput(tx, 0)
     return txns
    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)
Example #10
0
 def get_chained_transactions(self, spend, num_of_transactions, *, money_to_spend=5000000000, bad_chain=False):
     ok = []
     bad = []
     orphan = []
     bad_transaction = num_of_transactions // 2 if bad_chain else num_of_transactions
     for i in range(0, num_of_transactions):
         money_to_spend = money_to_spend - 1000  # one satoshi to fee
         tx = create_transaction(spend.tx, spend.n, b"", money_to_spend, self.locking_script)
         self.sign_tx(tx, spend.tx, spend.n,
                      key = self.coinbase_key if i != bad_transaction else self.wrong_key)
         tx.rehash()
         txns = ok if i < bad_transaction else bad if i == bad_transaction else orphan
         txns.append(tx)
         spend = PreviousSpendableOutput(tx, 0)
     return ok, bad, orphan
    def get_chained_txs(self, spend, num_of_txs, unlocking_script, locking_script, money_to_spend, vout_size):
        txns = []
        for _ in range(0, num_of_txs):
            # Create a new transaction.
            tx = create_transaction(spend.tx, spend.n, unlocking_script, money_to_spend, locking_script)
            # Extend the number of outputs to the required vout_size size.
            tx.vout.extend(tx.vout * (vout_size-1))
            # Sign txn.
            self.sign_tx(tx, spend.tx, spend.n)
            tx.rehash()
            txns.append(tx)
            # Use the first outpoint to spend in the second iteration.
            spend = PreviousSpendableOutput(tx, 0)

        return txns
Example #12
0
    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
Example #13
0
        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
Example #14
0
    def _test_minimal_freeze(self, spendable_out, node):
        self.log.info(
            "*** Performing soft consensus freeze checks (freeze for one block)"
        )

        node.restart_node([
            "-whitelist=127.0.0.1", "-minrelaytxfee=0",
            "-limitfreerelay=999999", "-softconsensusfreezeduration=1"
        ])

        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
        spend_frozen_tx = self._create_tx(
            PreviousSpendableOutput(frozen_tx, 0), b'', CScript([OP_TRUE]))
        self._mine_and_check_rejected(node, spend_frozen_tx)

        # this block will still be frozen
        self._mine_and_send_block(None, node, False,
                                  node.rpc.getbestblockhash())

        # blocks are unfrozen
        self._mine_and_send_block(None, node)
    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)
    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_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_cb = self._init()
        node = Send_node(self.options.tmpdir, self.log, 0, node_cb,
                         self.nodes[0])

        self.log.info(
            "*** Testing soft consensus freeze during node startup/IBD")

        spendable_out = self.chain.get_spendable_output()

        frozen_tx = self._create_tx_mine_block_and_freeze_tx(
            node, spendable_out)

        last_valid_tip_hash = node.rpc.getbestblockhash()
        last_valid_tip_height = node.rpc.getblockcount()

        # this block must not become tip because it contains a transaction trying to spend consensus frozen output
        spend_frozen_tx = self._create_tx(
            PreviousSpendableOutput(frozen_tx, 0), b'', CScript([OP_TRUE]))
        self._mine_and_check_rejected(node, spend_frozen_tx)

        # child blocks are still considered frozen
        self._mine_and_send_block(None, node, False, last_valid_tip_hash)
        self._mine_and_send_block(None, node, False, last_valid_tip_hash)
        self._mine_and_send_block(None, node, False, last_valid_tip_hash)

        node.restart_node()

        # must remain at the same tip as before
        assert_equal(last_valid_tip_hash, node.rpc.getbestblockhash())

        self.log.info("Starting second node")
        self.start_node(1)

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

        self.log.info("Connecting first and second node")
        connect_nodes(self.nodes, 1, 0)

        self.log.info(
            f"Waiting for block height {last_valid_tip_height} via rpc on second node"
        )
        self.nodes[1].waitforblockheight(last_valid_tip_height)

        self.log.info(
            "Checking that tip on second node stays on the last valid block")
        time.sleep(2)
        assert_equal(last_valid_tip_hash, self.nodes[1].getbestblockhash())

        self.log.info("Disconnecting first and second node")
        disconnect_nodes(self.nodes[1], 0)

        # mine another block that should still be frozen
        self._mine_and_send_block(None, node, False, last_valid_tip_hash)

        self.log.info("Connecting first and second node")
        connect_nodes(self.nodes, 1, 0)

        self.log.info(
            "Checking that tip on second node stays on the last valid block")
        time.sleep(2)
        assert_equal(last_valid_tip_hash, self.nodes[1].getbestblockhash())

        # all blocks are unfrozen
        new_valid_tip = self._mine_and_send_block(None, node)
        self.nodes[1].waitforblockheight(last_valid_tip_height + 6)
        assert_equal(new_valid_tip.hash, self.nodes[0].getbestblockhash())
        assert_equal(new_valid_tip.hash, self.nodes[1].getbestblockhash())
Example #20
0
    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(), [])
Example #21
0
    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