def test_anc_count_limits_bushy(self): """Create a tree with 20 transactions in the mempool and 6 in the package: M1...M4 M5...M8 M9...M12 M13...M16 M17...M20 ^ ^ ^ ^ ^ (each with 4 parents) P0 P1 P2 P3 P4 ^ ^ ^ ^ ^ (5 parents) PC Where M(4i+1)...M+(4i+4) are the parents of Pi and P0, P1, P2, P3, and P4 are the parents of PC. P0... P4 individually only have 4 parents each, and PC has no in-mempool parents. But combined, PC has 25 in-mempool and in-package parents. """ node = self.nodes[0] assert_equal(0, node.getmempoolinfo()["size"]) package_hex = [] parent_txns = [] parent_values = [] scripts = [] for _ in range(5): # Make package transactions P0 ... P4 gp_tx = [] gp_values = [] gp_scripts = [] for _ in range(4): # Make mempool transactions M(4i+1)...M(4i+4) parent_coin = self.coins.pop() value = parent_coin["amount"] txid = parent_coin["txid"] (tx, txhex, value, spk) = make_chain(node, self.address, self.privkeys, txid, value) gp_tx.append(tx) gp_values.append(value) gp_scripts.append(spk) node.sendrawtransaction(txhex) # Package transaction Pi pi_hex = create_child_with_parents(node, self.address, self.privkeys, gp_tx, gp_values, gp_scripts) package_hex.append(pi_hex) pi_tx = tx_from_hex(pi_hex) parent_txns.append(pi_tx) parent_values.append(Decimal(pi_tx.vout[0].nValue) / COIN) scripts.append(pi_tx.vout[0].scriptPubKey.hex()) # Package transaction PC package_hex.append( create_child_with_parents(node, self.address, self.privkeys, parent_txns, parent_values, scripts)) assert_equal(20, node.getmempoolinfo()["size"]) assert_equal(6, len(package_hex)) testres = node.testmempoolaccept(rawtxs=package_hex) for txres in testres: assert_equal(txres["package-error"], "package-mempool-limits") # Clear mempool and check that the package passes now self.generate(node, 1) assert all([ res["allowed"] for res in node.testmempoolaccept(rawtxs=package_hex) ])
def test_multiple_parents(self): node = self.nodes[0] self.log.info("Testmempoolaccept a package in which a transaction has multiple parents within the package") for num_parents in [2, 10, 24]: # Test a package with num_parents parents and 1 child transaction. package_hex = [] parents_tx = [] values = [] parent_locking_scripts = [] for _ in range(num_parents): parent_coin = self.coins.pop() value = parent_coin["amount"] (tx, txhex, value, parent_locking_script) = make_chain(node, self.address, self.privkeys, parent_coin["txid"], value) package_hex.append(txhex) parents_tx.append(tx) values.append(value) parent_locking_scripts.append(parent_locking_script) child_hex = create_child_with_parents(node, self.address, self.privkeys, parents_tx, values, parent_locking_scripts) # Package accept should work with the parents in any order (as long as parents come before child) for _ in range(10): random.shuffle(package_hex) testres_multiple = node.testmempoolaccept(rawtxs=package_hex + [child_hex]) assert all([testres["allowed"] for testres in testres_multiple]) testres_single = [] # Test accept and then submit each one individually, which should be identical to package testaccept for rawtx in package_hex + [child_hex]: testres_single.append(node.testmempoolaccept([rawtx])[0]) # Submit the transaction now so its child should have no problem validating node.sendrawtransaction(rawtx) assert_equal(testres_single, testres_multiple)
def test_anc_size_limits(self): """Test Case with 2 independent transactions in the mempool and a parent + child in the package, where the package parent is the child of both mempool transactions (30KvB each): A B ^ ^ C ^ D The lowest descendant, D, exceeds ancestor size limits, but only if the in-mempool and in-package ancestors are all considered together. """ node = self.nodes[0] assert_equal(0, node.getmempoolinfo()["size"]) parents_tx = [] values = [] scripts = [] target_weight = WITNESS_SCALE_FACTOR * 1000 * 30 # 30KvB high_fee = Decimal("0.003") # 10 sats/vB self.log.info("Check that in-mempool and in-package ancestor size limits are calculated properly in packages") # Mempool transactions A and B for _ in range(2): spk = None top_coin = self.coins.pop() txid = top_coin["txid"] value = top_coin["amount"] (tx, _, _, _) = make_chain(node, self.address, self.privkeys, txid, value, 0, spk, high_fee) bulked_tx = bulk_transaction(tx, node, target_weight, self.privkeys) node.sendrawtransaction(bulked_tx.serialize().hex()) parents_tx.append(bulked_tx) values.append(Decimal(bulked_tx.vout[0].nValue) / COIN) scripts.append(bulked_tx.vout[0].scriptPubKey.hex()) # Package transaction C small_pc_hex = create_child_with_parents(node, self.address, self.privkeys, parents_tx, values, scripts, high_fee) pc_tx = bulk_transaction(tx_from_hex(small_pc_hex), node, target_weight, self.privkeys) pc_value = Decimal(pc_tx.vout[0].nValue) / COIN pc_spk = pc_tx.vout[0].scriptPubKey.hex() pc_hex = pc_tx.serialize().hex() # Package transaction D (small_pd, _, val, spk) = make_chain(node, self.address, self.privkeys, pc_tx.rehash(), pc_value, 0, pc_spk, high_fee) prevtxs = [{ "txid": pc_tx.rehash(), "vout": 0, "scriptPubKey": spk, "amount": val, }] pd_tx = bulk_transaction(small_pd, node, target_weight, self.privkeys, prevtxs) pd_hex = pd_tx.serialize().hex() assert_equal(2, node.getmempoolinfo()["size"]) testres_too_heavy = node.testmempoolaccept(rawtxs=[pc_hex, pd_hex]) for txres in testres_too_heavy: assert_equal(txres["package-error"], "package-mempool-limits") # Clear mempool and check that the package passes now node.generate(1) assert all([res["allowed"] for res in node.testmempoolaccept(rawtxs=[pc_hex, pd_hex])])
def test_submit_child_with_parents(self, num_parents, partial_submit): node = self.nodes[0] peer = node.add_p2p_connection(P2PTxInvStore()) # Test a package with num_parents parents and 1 child transaction. package_hex = [] package_txns = [] values = [] scripts = [] for _ in range(num_parents): parent_coin = self.coins.pop() value = parent_coin["amount"] (tx, txhex, value, spk) = make_chain(node, self.address, self.privkeys, parent_coin["txid"], value) package_hex.append(txhex) package_txns.append(tx) values.append(value) scripts.append(spk) if partial_submit and random.choice([True, False]): node.sendrawtransaction(txhex) child_hex = create_child_with_parents(node, self.address, self.privkeys, package_txns, values, scripts) package_hex.append(child_hex) package_txns.append(tx_from_hex(child_hex)) testmempoolaccept_result = node.testmempoolaccept(rawtxs=package_hex) submitpackage_result = node.submitpackage(package=package_hex) # Check that each result is present, with the correct size and fees for i in range(num_parents + 1): tx = package_txns[i] wtxid = tx.getwtxid() assert wtxid in submitpackage_result["tx-results"] tx_result = submitpackage_result["tx-results"][wtxid] assert_equal( tx_result, { "txid": tx.rehash(), "vsize": tx.get_vsize(), "fees": { "base": DEFAULT_FEE, } }) # submitpackage result should be consistent with testmempoolaccept and getmempoolentry self.assert_equal_package_results(node, testmempoolaccept_result, submitpackage_result) # Package feerate is calculated for the remaining transactions after deduplication and # individual submission. If only 0 or 1 transaction is left, e.g. because all transactions # had high-feerates or were already in the mempool, no package feerate is provided. # In this case, since all of the parents have high fees, each is accepted individually. assert "package-feerate" not in submitpackage_result # The node should announce each transaction. No guarantees for propagation. peer.wait_for_broadcast([tx.getwtxid() for tx in package_txns]) self.generate(node, 1)
def test_anc_count_limits_2(self): """Create a 'Y' shaped chain with 24 transactions in the mempool and 2 in the package: M1a M1b ^ ^ M2a M2b . . . . . . M12a M12b ^ ^ Pc ^ Pd The lowest descendant, Pd, exceeds ancestor limits, but only if the in-mempool and in-package ancestors are all considered together. """ node = self.nodes[0] assert_equal(0, node.getmempoolinfo()["size"]) parents_tx = [] values = [] scripts = [] self.log.info("Check that in-mempool and in-package ancestors are calculated properly in packages") # Two chains of 12 transactions each for _ in range(2): spk = None top_coin = self.coins.pop() txid = top_coin["txid"] value = top_coin["amount"] for i in range(12): (tx, txhex, value, spk) = make_chain(node, self.address, self.privkeys, txid, value, 0, spk) txid = tx.rehash() value -= Decimal("0.0001") node.sendrawtransaction(txhex) if i == 11: # last 2 transactions will be the parents of Pc parents_tx.append(tx) values.append(value) scripts.append(spk) # Child Pc pc_hex = create_child_with_parents(node, self.address, self.privkeys, parents_tx, values, scripts) pc_tx = tx_from_hex(pc_hex) pc_value = sum(values) - Decimal("0.0002") pc_spk = pc_tx.vout[0].scriptPubKey.hex() # Child Pd (_, pd_hex, _, _) = make_chain(node, self.address, self.privkeys, pc_tx.rehash(), pc_value, 0, pc_spk) assert_equal(24, node.getmempoolinfo()["size"]) testres_too_long = node.testmempoolaccept(rawtxs=[pc_hex, pd_hex]) for txres in testres_too_long: assert_equal(txres["package-error"], "package-mempool-limits") # Clear mempool and check that the package passes now node.generate(1) assert all([res["allowed"] for res in node.testmempoolaccept(rawtxs=[pc_hex, pd_hex])])
def test_anc_count_limits(self): """Create a 'V' shaped chain with 24 transactions in the mempool and 3 in the package: M1a M1b ^ ^ M2a M2b . . . . . . M12a M12b ^ ^ Pa Pb ^ ^ Pc The lowest descendant, Pc, exceeds ancestor limits, but only if the in-mempool and in-package ancestors are all considered together. """ node = self.nodes[0] assert_equal(0, node.getmempoolinfo()["size"]) package_hex = [] parents_tx = [] values = [] scripts = [] self.log.info("Check that in-mempool and in-package ancestors are calculated properly in packages") # Two chains of 13 transactions each for _ in range(2): spk = None top_coin = self.coins.pop() txid = top_coin["txid"] value = top_coin["amount"] for i in range(13): (tx, txhex, value, spk) = make_chain(node, self.address, self.privkeys, txid, value, 0, spk) txid = tx.rehash() if i < 12: node.sendrawtransaction(txhex) else: # Save the 13th transaction for the package package_hex.append(txhex) parents_tx.append(tx) scripts.append(spk) values.append(value) # Child Pc child_hex = create_child_with_parents(node, self.address, self.privkeys, parents_tx, values, scripts) package_hex.append(child_hex) assert_equal(24, node.getmempoolinfo()["size"]) assert_equal(3, len(package_hex)) testres_too_long = node.testmempoolaccept(rawtxs=package_hex) for txres in testres_too_long: assert_equal(txres["package-error"], "package-mempool-limits") # Clear mempool and check that the package passes now node.generate(1) assert all([res["allowed"] for res in node.testmempoolaccept(rawtxs=package_hex)])
def test_submit_cpfp(self): node = self.nodes[0] peer = node.add_p2p_connection(P2PTxInvStore()) # 2 parent 1 child CPFP. First parent pays high fees, second parent pays 0 fees and is # fee-bumped by the child. coin_rich = self.coins.pop() coin_poor = self.coins.pop() tx_rich, hex_rich, value_rich, spk_rich = make_chain( node, self.address, self.privkeys, coin_rich["txid"], coin_rich["amount"]) tx_poor, hex_poor, value_poor, spk_poor = make_chain( node, self.address, self.privkeys, coin_poor["txid"], coin_poor["amount"], fee=0) package_txns = [tx_rich, tx_poor] hex_child = create_child_with_parents(node, self.address, self.privkeys, package_txns, [value_rich, value_poor], [spk_rich, spk_poor]) tx_child = tx_from_hex(hex_child) package_txns.append(tx_child) submitpackage_result = node.submitpackage( [hex_rich, hex_poor, hex_child]) rich_parent_result = submitpackage_result["tx-results"][ tx_rich.getwtxid()] poor_parent_result = submitpackage_result["tx-results"][ tx_poor.getwtxid()] child_result = submitpackage_result["tx-results"][tx_child.getwtxid()] assert_equal(rich_parent_result["fees"]["base"], DEFAULT_FEE) assert_equal(poor_parent_result["fees"]["base"], 0) assert_equal(child_result["fees"]["base"], DEFAULT_FEE) # Package feerate is calculated for the remaining transactions after deduplication and # individual submission. Since this package had a 0-fee parent, package feerate must have # been used and returned. assert "package-feerate" in submitpackage_result assert_fee_amount(DEFAULT_FEE, rich_parent_result["vsize"] + child_result["vsize"], submitpackage_result["package-feerate"]) # The node will broadcast each transaction, still abiding by its peer's fee filter peer.wait_for_broadcast([tx.getwtxid() for tx in package_txns]) self.generate(node, 1)