diff --git a/test/functional/mining_cpfp_depth_1.py b/test/functional/mining_cpfp_depth_1.py new file mode 100755 --- /dev/null +++ b/test/functional/mining_cpfp_depth_1.py @@ -0,0 +1,124 @@ +#!/usr/bin/env python3 +# Copyright (c) 2019 The Bitcoin developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +""" + Child-Pays-for-Parent: Depth 1. + + A child transaction with a sufficiently high fee can lead to + the inclusion of its parent transaction into a block; even if + the block is full and the parent has a fee which is lower than + that of all other transactions in the mempool. +""" + +from test_framework.blocktools import ( + create_confirmed_utxos, +) +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import ( + assert_equal, + assert_less_than, +) + + +class ChildPaysForParentTest(BitcoinTestFramework): + + def set_test_params(self): + """ + Sets -blockmaxsize such that we can create a full block + with approximately 20 transactions during the test. The + exacty amount does not matter; only that it is full. + """ + + self.setup_clean_chain = True + self.num_nodes = 1 + self.block_max_tx_count = 20 + self.block_max_size = 200 + 192 * self.block_max_tx_count + self.extra_args = [[ + "-blockprioritypercentage=0", + "-blockmaxsize={}".format(self.block_max_size) + ]] + + def run_test(self): + """ + Broadcasts more transactions that can fit into a block, and then + generates a block. The set of transactions includes many medium-fee + transactions, as well as a low-fee transaction [parent tx] and a + high-fee dependent transaction [child tx]. CPFP guarantees that + parent tx will be included in the block, even though all other + transactions in the mempool have a significantly higher fee. + """ + + node = self.nodes[0] + + # Define fees. + network_info = node.getnetworkinfo() + relay_fee = network_info['relayfee'] + fee_parent = relay_fee + fee_child = 10 * relay_fee + fee_others = 5 * relay_fee + + # Create confirmed utxos. We need enough of them such that we can + # create sufficient independent transactions to saturate a block. + utxos = create_confirmed_utxos(node, 2 * self.block_max_tx_count) + utxo_parent = utxos[0] + utxos_other = utxos[1:] + + # Create the first independent transaction. [parent tx] + node.settxfee(fee_parent) + output_address_parent = node.getnewaddress() + inputs_parent = [ + {"txid": utxo_parent["txid"], "vout": utxo_parent["vout"]}] + outputs_parent = {output_address_parent: 0.02} + tx_parent = node.createrawtransaction(inputs_parent, outputs_parent) + tx_parent = node.fundrawtransaction(tx_parent) + tx_parent = node.signrawtransaction(tx_parent['hex']) + txid_parent = node.sendrawtransaction(tx_parent['hex']) + self.log.debug("Broadcasted parent tx: {}.".format(txid_parent)) + + # Create a transaction dependent on the first transaction. [child tx] + node.settxfee(fee_child) + output_address_child = node.getnewaddress() + inputs_child = [{"txid": txid_parent, "vout": 0}] + outputs_child = {output_address_child: 0.01} + tx_child = node.createrawtransaction(inputs_child, outputs_child) + tx_child = node.fundrawtransaction(tx_child) + tx_child = node.signrawtransaction(tx_child['hex']) + txid_child = node.sendrawtransaction(tx_child['hex']) + self.log.debug("Broadcasted child tx: {}.".format(txid_child)) + + # Create more independent transactions. [other txs] + for utxo_other in utxos_other: + node.settxfee(fee_others) + output_address_other = node.getnewaddress() + inputs_other = [ + {"txid": utxo_other["txid"], "vout": utxo_other["vout"]}] + outputs_other = {output_address_other: 0.01} + tx_other = node.createrawtransaction(inputs_other, outputs_other) + tx_other = node.fundrawtransaction(tx_other) + tx_other = node.signrawtransaction(tx_other['hex']) + txid_other = node.sendrawtransaction(tx_other['hex']) + self.log.debug("Broadcasted other tx: {}.".format(txid_other)) + + # Generate block. + block_hashes = node.generate(nblocks=1) + block_hash = block_hashes[0] + block = node.getblock(block_hash) + block_txids = block["tx"] + self.log.debug("Generated block: {}.".format(block_hash)) + + # Make sure that the block *does not* contain all broadcasted + # txs; ie, some of them where left out. + confirmed_transactions = len(block_txids) + broadcasted_transactions = 1 + 1 + len(utxos_other) + assert_less_than(confirmed_transactions, broadcasted_transactions) + + # Make sure that the block contains both the parent and child + # txs, even though the block is full and the parent tx has a + # lower fee than all the other transactions. + assert_equal(txid_parent in block_txids, True) + assert_equal(txid_child in block_txids, True) + + +if __name__ == '__main__': + ChildPaysForParentTest().main() diff --git a/test/functional/test_framework/util.py b/test/functional/test_framework/util.py --- a/test/functional/test_framework/util.py +++ b/test/functional/test_framework/util.py @@ -54,6 +54,11 @@ raise AssertionError("%s <= %s" % (str(thing1), str(thing2))) +def assert_less_than(thing1, thing2): + if thing1 >= thing2: + raise AssertionError("%s >= %s" % (str(thing1), str(thing2))) + + def assert_greater_than_or_equal(thing1, thing2): if thing1 < thing2: raise AssertionError("%s < %s" % (str(thing1), str(thing2)))