aboutsummaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
Diffstat (limited to 'test')
-rwxr-xr-xtest/functional/feature_bip68_sequence.py6
-rwxr-xr-xtest/functional/mempool_accept_v3.py418
-rwxr-xr-xtest/functional/mempool_package_limits.py2
-rwxr-xr-xtest/functional/mempool_sigoplimit.py3
-rw-r--r--test/functional/test_framework/wallet.py20
-rwxr-xr-xtest/functional/test_runner.py1
6 files changed, 438 insertions, 12 deletions
diff --git a/test/functional/feature_bip68_sequence.py b/test/functional/feature_bip68_sequence.py
index 894afffc79..8768d4040d 100755
--- a/test/functional/feature_bip68_sequence.py
+++ b/test/functional/feature_bip68_sequence.py
@@ -408,10 +408,8 @@ class BIP68Test(BitcoinTestFramework):
# Use self.nodes[1] to test that version 2 transactions are standard.
def test_version2_relay(self):
mini_wallet = MiniWallet(self.nodes[1])
- mini_wallet.rescan_utxos()
- tx = mini_wallet.create_self_transfer()["tx"]
- tx.nVersion = 2
- mini_wallet.sendrawtransaction(from_node=self.nodes[1], tx_hex=tx.serialize().hex())
+ mini_wallet.send_self_transfer(from_node=self.nodes[1], version=2)
+
if __name__ == '__main__':
BIP68Test().main()
diff --git a/test/functional/mempool_accept_v3.py b/test/functional/mempool_accept_v3.py
new file mode 100755
index 0000000000..ca599a9993
--- /dev/null
+++ b/test/functional/mempool_accept_v3.py
@@ -0,0 +1,418 @@
+#!/usr/bin/env python3
+# Copyright (c) 2024 The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+from test_framework.messages import (
+ MAX_BIP125_RBF_SEQUENCE,
+)
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import (
+ assert_equal,
+ assert_greater_than,
+ assert_greater_than_or_equal,
+ assert_raises_rpc_error,
+)
+from test_framework.wallet import (
+ DEFAULT_FEE,
+ MiniWallet,
+)
+
+def cleanup(extra_args=None):
+ def decorator(func):
+ def wrapper(self):
+ try:
+ if extra_args is not None:
+ self.restart_node(0, extra_args=extra_args)
+ func(self)
+ finally:
+ # Clear mempool again after test
+ self.generate(self.nodes[0], 1)
+ if extra_args is not None:
+ self.restart_node(0)
+ return wrapper
+ return decorator
+
+class MempoolAcceptV3(BitcoinTestFramework):
+ def set_test_params(self):
+ self.num_nodes = 1
+ self.extra_args = [["-acceptnonstdtxn=1"]]
+ self.setup_clean_chain = True
+
+ def check_mempool(self, txids):
+ """Assert exact contents of the node's mempool (by txid)."""
+ mempool_contents = self.nodes[0].getrawmempool()
+ assert_equal(len(txids), len(mempool_contents))
+ assert all([txid in txids for txid in mempool_contents])
+
+ @cleanup(extra_args=["-datacarriersize=1000", "-acceptnonstdtxn=1"])
+ def test_v3_acceptance(self):
+ node = self.nodes[0]
+ self.log.info("Test a child of a v3 transaction cannot be more than 1000vB")
+ tx_v3_parent_normal = self.wallet.send_self_transfer(from_node=node, version=3)
+ self.check_mempool([tx_v3_parent_normal["txid"]])
+ tx_v3_child_heavy = self.wallet.create_self_transfer(
+ utxo_to_spend=tx_v3_parent_normal["new_utxo"],
+ target_weight=4004,
+ version=3
+ )
+ assert_greater_than_or_equal(tx_v3_child_heavy["tx"].get_vsize(), 1000)
+ expected_error_child_heavy = f"v3-rule-violation, v3 child tx {tx_v3_child_heavy['txid']} (wtxid={tx_v3_child_heavy['wtxid']}) is too big"
+ assert_raises_rpc_error(-26, expected_error_child_heavy, node.sendrawtransaction, tx_v3_child_heavy["hex"])
+ self.check_mempool([tx_v3_parent_normal["txid"]])
+ # tx has no descendants
+ assert_equal(node.getmempoolentry(tx_v3_parent_normal["txid"])["descendantcount"], 1)
+
+ self.log.info("Test that, during replacements, only the new transaction counts for v3 descendant limit")
+ tx_v3_child_almost_heavy = self.wallet.send_self_transfer(
+ from_node=node,
+ fee_rate=DEFAULT_FEE,
+ utxo_to_spend=tx_v3_parent_normal["new_utxo"],
+ target_weight=3987,
+ version=3
+ )
+ assert_greater_than_or_equal(1000, tx_v3_child_almost_heavy["tx"].get_vsize())
+ self.check_mempool([tx_v3_parent_normal["txid"], tx_v3_child_almost_heavy["txid"]])
+ assert_equal(node.getmempoolentry(tx_v3_parent_normal["txid"])["descendantcount"], 2)
+ tx_v3_child_almost_heavy_rbf = self.wallet.send_self_transfer(
+ from_node=node,
+ fee_rate=DEFAULT_FEE * 2,
+ utxo_to_spend=tx_v3_parent_normal["new_utxo"],
+ target_weight=3500,
+ version=3
+ )
+ assert_greater_than_or_equal(tx_v3_child_almost_heavy["tx"].get_vsize() + tx_v3_child_almost_heavy_rbf["tx"].get_vsize(), 1000)
+ self.check_mempool([tx_v3_parent_normal["txid"], tx_v3_child_almost_heavy_rbf["txid"]])
+ assert_equal(node.getmempoolentry(tx_v3_parent_normal["txid"])["descendantcount"], 2)
+
+ @cleanup(extra_args=["-acceptnonstdtxn=1"])
+ def test_v3_replacement(self):
+ node = self.nodes[0]
+ self.log.info("Test v3 transactions may be replaced by v3 transactions")
+ utxo_v3_bip125 = self.wallet.get_utxo()
+ tx_v3_bip125 = self.wallet.send_self_transfer(
+ from_node=node,
+ fee_rate=DEFAULT_FEE,
+ utxo_to_spend=utxo_v3_bip125,
+ sequence=MAX_BIP125_RBF_SEQUENCE,
+ version=3
+ )
+ self.check_mempool([tx_v3_bip125["txid"]])
+
+ tx_v3_bip125_rbf = self.wallet.send_self_transfer(
+ from_node=node,
+ fee_rate=DEFAULT_FEE * 2,
+ utxo_to_spend=utxo_v3_bip125,
+ version=3
+ )
+ self.check_mempool([tx_v3_bip125_rbf["txid"]])
+
+ self.log.info("Test v3 transactions may be replaced by V2 transactions")
+ tx_v3_bip125_rbf_v2 = self.wallet.send_self_transfer(
+ from_node=node,
+ fee_rate=DEFAULT_FEE * 3,
+ utxo_to_spend=utxo_v3_bip125,
+ version=2
+ )
+ self.check_mempool([tx_v3_bip125_rbf_v2["txid"]])
+
+ self.log.info("Test that replacements cannot cause violation of inherited v3")
+ utxo_v3_parent = self.wallet.get_utxo()
+ tx_v3_parent = self.wallet.send_self_transfer(
+ from_node=node,
+ fee_rate=DEFAULT_FEE,
+ utxo_to_spend=utxo_v3_parent,
+ version=3
+ )
+ tx_v3_child = self.wallet.send_self_transfer(
+ from_node=node,
+ fee_rate=DEFAULT_FEE,
+ utxo_to_spend=tx_v3_parent["new_utxo"],
+ version=3
+ )
+ self.check_mempool([tx_v3_bip125_rbf_v2["txid"], tx_v3_parent["txid"], tx_v3_child["txid"]])
+
+ tx_v3_child_rbf_v2 = self.wallet.create_self_transfer(
+ fee_rate=DEFAULT_FEE * 2,
+ utxo_to_spend=tx_v3_parent["new_utxo"],
+ version=2
+ )
+ expected_error_v2_v3 = f"v3-rule-violation, non-v3 tx {tx_v3_child_rbf_v2['txid']} (wtxid={tx_v3_child_rbf_v2['wtxid']}) cannot spend from v3 tx {tx_v3_parent['txid']} (wtxid={tx_v3_parent['wtxid']})"
+ assert_raises_rpc_error(-26, expected_error_v2_v3, node.sendrawtransaction, tx_v3_child_rbf_v2["hex"])
+ self.check_mempool([tx_v3_bip125_rbf_v2["txid"], tx_v3_parent["txid"], tx_v3_child["txid"]])
+
+
+ @cleanup(extra_args=["-acceptnonstdtxn=1"])
+ def test_v3_bip125(self):
+ node = self.nodes[0]
+ self.log.info("Test v3 transactions that don't signal BIP125 are replaceable")
+ assert_equal(node.getmempoolinfo()["fullrbf"], False)
+ utxo_v3_no_bip125 = self.wallet.get_utxo()
+ tx_v3_no_bip125 = self.wallet.send_self_transfer(
+ from_node=node,
+ fee_rate=DEFAULT_FEE,
+ utxo_to_spend=utxo_v3_no_bip125,
+ sequence=MAX_BIP125_RBF_SEQUENCE + 1,
+ version=3
+ )
+
+ self.check_mempool([tx_v3_no_bip125["txid"]])
+ assert not node.getmempoolentry(tx_v3_no_bip125["txid"])["bip125-replaceable"]
+ tx_v3_no_bip125_rbf = self.wallet.send_self_transfer(
+ from_node=node,
+ fee_rate=DEFAULT_FEE * 2,
+ utxo_to_spend=utxo_v3_no_bip125,
+ version=3
+ )
+ self.check_mempool([tx_v3_no_bip125_rbf["txid"]])
+
+ @cleanup(extra_args=["-datacarriersize=40000", "-acceptnonstdtxn=1"])
+ def test_v3_reorg(self):
+ node = self.nodes[0]
+ self.log.info("Test that, during a reorg, v3 rules are not enforced")
+ tx_v2_block = self.wallet.send_self_transfer(from_node=node, version=2)
+ tx_v3_block = self.wallet.send_self_transfer(from_node=node, version=3)
+ tx_v3_block2 = self.wallet.send_self_transfer(from_node=node, version=3)
+ self.check_mempool([tx_v3_block["txid"], tx_v2_block["txid"], tx_v3_block2["txid"]])
+
+ block = self.generate(node, 1)
+ self.check_mempool([])
+ tx_v2_from_v3 = self.wallet.send_self_transfer(from_node=node, utxo_to_spend=tx_v3_block["new_utxo"], version=2)
+ tx_v3_from_v2 = self.wallet.send_self_transfer(from_node=node, utxo_to_spend=tx_v2_block["new_utxo"], version=3)
+ tx_v3_child_large = self.wallet.send_self_transfer(from_node=node, utxo_to_spend=tx_v3_block2["new_utxo"], target_weight=5000, version=3)
+ assert_greater_than(node.getmempoolentry(tx_v3_child_large["txid"])["vsize"], 1000)
+ self.check_mempool([tx_v2_from_v3["txid"], tx_v3_from_v2["txid"], tx_v3_child_large["txid"]])
+ node.invalidateblock(block[0])
+ self.check_mempool([tx_v3_block["txid"], tx_v2_block["txid"], tx_v3_block2["txid"], tx_v2_from_v3["txid"], tx_v3_from_v2["txid"], tx_v3_child_large["txid"]])
+ # This is needed because generate() will create the exact same block again.
+ node.reconsiderblock(block[0])
+
+
+ @cleanup(extra_args=["-limitdescendantsize=10", "-datacarriersize=40000", "-acceptnonstdtxn=1"])
+ def test_nondefault_package_limits(self):
+ """
+ Max standard tx size + v3 rules imply the ancestor/descendant rules (at their default
+ values), but those checks must not be skipped. Ensure both sets of checks are done by
+ changing the ancestor/descendant limit configurations.
+ """
+ node = self.nodes[0]
+ self.log.info("Test that a decreased limitdescendantsize also applies to v3 child")
+ tx_v3_parent_large1 = self.wallet.send_self_transfer(from_node=node, target_weight=99900, version=3)
+ tx_v3_child_large1 = self.wallet.create_self_transfer(utxo_to_spend=tx_v3_parent_large1["new_utxo"], version=3)
+ # Child is within v3 limits, but parent's descendant limit is exceeded
+ assert_greater_than(1000, tx_v3_child_large1["tx"].get_vsize())
+ assert_raises_rpc_error(-26, f"too-long-mempool-chain, exceeds descendant size limit for tx {tx_v3_parent_large1['txid']}", node.sendrawtransaction, tx_v3_child_large1["hex"])
+ self.check_mempool([tx_v3_parent_large1["txid"]])
+ assert_equal(node.getmempoolentry(tx_v3_parent_large1["txid"])["descendantcount"], 1)
+ self.generate(node, 1)
+
+ self.log.info("Test that a decreased limitancestorsize also applies to v3 parent")
+ self.restart_node(0, extra_args=["-limitancestorsize=10", "-datacarriersize=40000", "-acceptnonstdtxn=1"])
+ tx_v3_parent_large2 = self.wallet.send_self_transfer(from_node=node, target_weight=99900, version=3)
+ tx_v3_child_large2 = self.wallet.create_self_transfer(utxo_to_spend=tx_v3_parent_large2["new_utxo"], version=3)
+ # Child is within v3 limits
+ assert_greater_than_or_equal(1000, tx_v3_child_large2["tx"].get_vsize())
+ assert_raises_rpc_error(-26, f"too-long-mempool-chain, exceeds ancestor size limit", node.sendrawtransaction, tx_v3_child_large2["hex"])
+ self.check_mempool([tx_v3_parent_large2["txid"]])
+
+ @cleanup(extra_args=["-datacarriersize=1000", "-acceptnonstdtxn=1"])
+ def test_v3_ancestors_package(self):
+ self.log.info("Test that v3 ancestor limits are checked within the package")
+ node = self.nodes[0]
+ tx_v3_parent_normal = self.wallet.create_self_transfer(
+ fee_rate=0,
+ target_weight=4004,
+ version=3
+ )
+ tx_v3_parent_2_normal = self.wallet.create_self_transfer(
+ fee_rate=0,
+ target_weight=4004,
+ version=3
+ )
+ tx_v3_child_multiparent = self.wallet.create_self_transfer_multi(
+ utxos_to_spend=[tx_v3_parent_normal["new_utxo"], tx_v3_parent_2_normal["new_utxo"]],
+ fee_per_output=10000,
+ version=3
+ )
+ tx_v3_child_heavy = self.wallet.create_self_transfer_multi(
+ utxos_to_spend=[tx_v3_parent_normal["new_utxo"]],
+ target_weight=4004,
+ fee_per_output=10000,
+ version=3
+ )
+
+ self.check_mempool([])
+ result = node.submitpackage([tx_v3_parent_normal["hex"], tx_v3_parent_2_normal["hex"], tx_v3_child_multiparent["hex"]])
+ assert_equal(result['package_msg'], f"v3-violation, tx {tx_v3_child_multiparent['txid']} (wtxid={tx_v3_child_multiparent['wtxid']}) would have too many ancestors")
+ self.check_mempool([])
+
+ self.check_mempool([])
+ result = node.submitpackage([tx_v3_parent_normal["hex"], tx_v3_child_heavy["hex"]])
+ # tx_v3_child_heavy is heavy based on weight, not sigops.
+ assert_equal(result['package_msg'], f"v3-violation, v3 child tx {tx_v3_child_heavy['txid']} (wtxid={tx_v3_child_heavy['wtxid']}) is too big: {tx_v3_child_heavy['tx'].get_vsize()} > 1000 virtual bytes")
+ self.check_mempool([])
+
+ tx_v3_parent = self.wallet.create_self_transfer(version=3)
+ tx_v3_child = self.wallet.create_self_transfer(utxo_to_spend=tx_v3_parent["new_utxo"], version=3)
+ tx_v3_grandchild = self.wallet.create_self_transfer(utxo_to_spend=tx_v3_child["new_utxo"], version=3)
+ result = node.testmempoolaccept([tx_v3_parent["hex"], tx_v3_child["hex"], tx_v3_grandchild["hex"]])
+ assert all([txresult["package-error"] == f"v3-violation, tx {tx_v3_grandchild['txid']} (wtxid={tx_v3_grandchild['wtxid']}) would have too many ancestors" for txresult in result])
+
+ @cleanup(extra_args=["-acceptnonstdtxn=1"])
+ def test_v3_ancestors_package_and_mempool(self):
+ """
+ A v3 transaction in a package cannot have 2 v3 parents.
+ Test that if we have a transaction graph A -> B -> C, where A, B, C are
+ all v3 transactions, that we cannot use submitpackage to get the
+ transactions all into the mempool.
+
+ Verify, in particular, that if A is already in the mempool, then
+ submitpackage(B, C) will fail.
+ """
+ node = self.nodes[0]
+ self.log.info("Test that v3 ancestor limits include transactions within the package and all in-mempool ancestors")
+ # This is our transaction "A":
+ tx_in_mempool = self.wallet.send_self_transfer(from_node=node, version=3)
+
+ # Verify that A is in the mempool
+ self.check_mempool([tx_in_mempool["txid"]])
+
+ # tx_0fee_parent is our transaction "B"; just create it.
+ tx_0fee_parent = self.wallet.create_self_transfer(utxo_to_spend=tx_in_mempool["new_utxo"], fee=0, fee_rate=0, version=3)
+
+ # tx_child_violator is our transaction "C"; create it:
+ tx_child_violator = self.wallet.create_self_transfer_multi(utxos_to_spend=[tx_0fee_parent["new_utxo"]], version=3)
+
+ # submitpackage(B, C) should fail
+ result = node.submitpackage([tx_0fee_parent["hex"], tx_child_violator["hex"]])
+ assert_equal(result['package_msg'], f"v3-violation, tx {tx_child_violator['txid']} (wtxid={tx_child_violator['wtxid']}) would have too many ancestors")
+ self.check_mempool([tx_in_mempool["txid"]])
+
+ @cleanup(extra_args=["-acceptnonstdtxn=1"])
+ def test_mempool_sibling(self):
+ self.log.info("Test that v3 transaction cannot have mempool siblings")
+ node = self.nodes[0]
+ # Add a parent + child to mempool
+ tx_mempool_parent = self.wallet.send_self_transfer_multi(
+ from_node=node,
+ utxos_to_spend=[self.wallet.get_utxo()],
+ num_outputs=2,
+ version=3
+ )
+ tx_mempool_sibling = self.wallet.send_self_transfer(
+ from_node=node,
+ utxo_to_spend=tx_mempool_parent["new_utxos"][0],
+ version=3
+ )
+ self.check_mempool([tx_mempool_parent["txid"], tx_mempool_sibling["txid"]])
+
+ tx_has_mempool_sibling = self.wallet.create_self_transfer(
+ utxo_to_spend=tx_mempool_parent["new_utxos"][1],
+ version=3
+ )
+ expected_error_mempool_sibling = f"v3-rule-violation, tx {tx_mempool_parent['txid']} (wtxid={tx_mempool_parent['wtxid']}) would exceed descendant count limit"
+ assert_raises_rpc_error(-26, expected_error_mempool_sibling, node.sendrawtransaction, tx_has_mempool_sibling["hex"])
+
+ tx_has_mempool_uncle = self.wallet.create_self_transfer(utxo_to_spend=tx_has_mempool_sibling["new_utxo"], version=3)
+
+ # Also fails with another non-related transaction via testmempoolaccept
+ tx_unrelated = self.wallet.create_self_transfer(version=3)
+ result_test_unrelated = node.testmempoolaccept([tx_has_mempool_sibling["hex"], tx_unrelated["hex"]])
+ assert_equal(result_test_unrelated[0]["reject-reason"], "v3-rule-violation")
+
+ result_test_1p1c = node.testmempoolaccept([tx_has_mempool_sibling["hex"], tx_has_mempool_uncle["hex"]])
+ assert_equal(result_test_1p1c[0]["reject-reason"], "v3-rule-violation")
+
+ # Also fails with a child via submitpackage
+ result_submitpackage = node.submitpackage([tx_has_mempool_sibling["hex"], tx_has_mempool_uncle["hex"]])
+ assert_equal(result_submitpackage["tx-results"][tx_has_mempool_sibling['wtxid']]['error'], expected_error_mempool_sibling)
+
+
+ @cleanup(extra_args=["-datacarriersize=1000", "-acceptnonstdtxn=1"])
+ def test_v3_package_inheritance(self):
+ self.log.info("Test that v3 inheritance is checked within package")
+ node = self.nodes[0]
+ tx_v3_parent = self.wallet.create_self_transfer(
+ fee_rate=0,
+ target_weight=4004,
+ version=3
+ )
+ tx_v2_child = self.wallet.create_self_transfer_multi(
+ utxos_to_spend=[tx_v3_parent["new_utxo"]],
+ fee_per_output=10000,
+ version=2
+ )
+ self.check_mempool([])
+ result = node.submitpackage([tx_v3_parent["hex"], tx_v2_child["hex"]])
+ assert_equal(result['package_msg'], f"v3-violation, non-v3 tx {tx_v2_child['txid']} (wtxid={tx_v2_child['wtxid']}) cannot spend from v3 tx {tx_v3_parent['txid']} (wtxid={tx_v3_parent['wtxid']})")
+ self.check_mempool([])
+
+ @cleanup(extra_args=["-acceptnonstdtxn=1"])
+ def test_v3_in_testmempoolaccept(self):
+ node = self.nodes[0]
+
+ self.log.info("Test that v3 inheritance is accurately assessed in testmempoolaccept")
+ tx_v2 = self.wallet.create_self_transfer(version=2)
+ tx_v2_from_v2 = self.wallet.create_self_transfer(utxo_to_spend=tx_v2["new_utxo"], version=2)
+ tx_v3_from_v2 = self.wallet.create_self_transfer(utxo_to_spend=tx_v2["new_utxo"], version=3)
+ tx_v3 = self.wallet.create_self_transfer(version=3)
+ tx_v2_from_v3 = self.wallet.create_self_transfer(utxo_to_spend=tx_v3["new_utxo"], version=2)
+ tx_v3_from_v3 = self.wallet.create_self_transfer(utxo_to_spend=tx_v3["new_utxo"], version=3)
+
+ # testmempoolaccept paths don't require child-with-parents topology. Ensure that topology
+ # assumptions aren't made in inheritance checks.
+ test_accept_v2_and_v3 = node.testmempoolaccept([tx_v2["hex"], tx_v3["hex"]])
+ assert all([result["allowed"] for result in test_accept_v2_and_v3])
+
+ test_accept_v3_from_v2 = node.testmempoolaccept([tx_v2["hex"], tx_v3_from_v2["hex"]])
+ expected_error_v3_from_v2 = f"v3-violation, v3 tx {tx_v3_from_v2['txid']} (wtxid={tx_v3_from_v2['wtxid']}) cannot spend from non-v3 tx {tx_v2['txid']} (wtxid={tx_v2['wtxid']})"
+ assert all([result["package-error"] == expected_error_v3_from_v2 for result in test_accept_v3_from_v2])
+
+ test_accept_v2_from_v3 = node.testmempoolaccept([tx_v3["hex"], tx_v2_from_v3["hex"]])
+ expected_error_v2_from_v3 = f"v3-violation, non-v3 tx {tx_v2_from_v3['txid']} (wtxid={tx_v2_from_v3['wtxid']}) cannot spend from v3 tx {tx_v3['txid']} (wtxid={tx_v3['wtxid']})"
+ assert all([result["package-error"] == expected_error_v2_from_v3 for result in test_accept_v2_from_v3])
+
+ test_accept_pairs = node.testmempoolaccept([tx_v2["hex"], tx_v3["hex"], tx_v2_from_v2["hex"], tx_v3_from_v3["hex"]])
+ assert all([result["allowed"] for result in test_accept_pairs])
+
+ self.log.info("Test that descendant violations are caught in testmempoolaccept")
+ tx_v3_independent = self.wallet.create_self_transfer(version=3)
+ tx_v3_parent = self.wallet.create_self_transfer_multi(num_outputs=2, version=3)
+ tx_v3_child_1 = self.wallet.create_self_transfer(utxo_to_spend=tx_v3_parent["new_utxos"][0], version=3)
+ tx_v3_child_2 = self.wallet.create_self_transfer(utxo_to_spend=tx_v3_parent["new_utxos"][1], version=3)
+ test_accept_2children = node.testmempoolaccept([tx_v3_parent["hex"], tx_v3_child_1["hex"], tx_v3_child_2["hex"]])
+ expected_error_2children = f"v3-violation, tx {tx_v3_parent['txid']} (wtxid={tx_v3_parent['wtxid']}) would exceed descendant count limit"
+ assert all([result["package-error"] == expected_error_2children for result in test_accept_2children])
+
+ # Extra v3 transaction does not get incorrectly marked as extra descendant
+ test_accept_1child_with_exra = node.testmempoolaccept([tx_v3_parent["hex"], tx_v3_child_1["hex"], tx_v3_independent["hex"]])
+ assert all([result["allowed"] for result in test_accept_1child_with_exra])
+
+ # Extra v3 transaction does not make us ignore the extra descendant
+ test_accept_2children_with_exra = node.testmempoolaccept([tx_v3_parent["hex"], tx_v3_child_1["hex"], tx_v3_child_2["hex"], tx_v3_independent["hex"]])
+ expected_error_extra = f"v3-violation, tx {tx_v3_parent['txid']} (wtxid={tx_v3_parent['wtxid']}) would exceed descendant count limit"
+ assert all([result["package-error"] == expected_error_extra for result in test_accept_2children_with_exra])
+ # Same result if the parent is already in mempool
+ node.sendrawtransaction(tx_v3_parent["hex"])
+ test_accept_2children_with_in_mempool_parent = node.testmempoolaccept([tx_v3_child_1["hex"], tx_v3_child_2["hex"]])
+ assert all([result["package-error"] == expected_error_extra for result in test_accept_2children_with_in_mempool_parent])
+
+ def run_test(self):
+ self.log.info("Generate blocks to create UTXOs")
+ node = self.nodes[0]
+ self.wallet = MiniWallet(node)
+ self.generate(self.wallet, 110)
+ self.test_v3_acceptance()
+ self.test_v3_replacement()
+ self.test_v3_bip125()
+ self.test_v3_reorg()
+ self.test_nondefault_package_limits()
+ self.test_v3_ancestors_package()
+ self.test_v3_ancestors_package_and_mempool()
+ self.test_mempool_sibling()
+ self.test_v3_package_inheritance()
+ self.test_v3_in_testmempoolaccept()
+
+
+if __name__ == "__main__":
+ MempoolAcceptV3().main()
diff --git a/test/functional/mempool_package_limits.py b/test/functional/mempool_package_limits.py
index 81451bf2a5..2a64597511 100755
--- a/test/functional/mempool_package_limits.py
+++ b/test/functional/mempool_package_limits.py
@@ -29,7 +29,7 @@ def check_package_limits(func):
testres_error_expected = node.testmempoolaccept(rawtxs=package_hex)
assert_equal(len(testres_error_expected), len(package_hex))
for txres in testres_error_expected:
- assert_equal(txres["package-error"], "package-mempool-limits")
+ assert "package-mempool-limits" in txres["package-error"]
# Clear mempool and check that the package passes now
self.generate(node, 1)
diff --git a/test/functional/mempool_sigoplimit.py b/test/functional/mempool_sigoplimit.py
index 2e7850fb40..384423e5f5 100755
--- a/test/functional/mempool_sigoplimit.py
+++ b/test/functional/mempool_sigoplimit.py
@@ -165,7 +165,8 @@ class BytesPerSigOpTest(BitcoinTestFramework):
# But together, it's exceeding limits in the *package* context. If sigops adjusted vsize wasn't being checked
# here, it would get further in validation and give too-long-mempool-chain error instead.
packet_test = self.nodes[0].testmempoolaccept([tx_parent.serialize().hex(), tx_child.serialize().hex()])
- assert_equal([x["package-error"] for x in packet_test], ["package-mempool-limits", "package-mempool-limits"])
+ expected_package_error = f"package-mempool-limits, package size {2*20*5000} exceeds ancestor size limit [limit: 101000]"
+ assert_equal([x["package-error"] for x in packet_test], [expected_package_error] * 2)
# When we actually try to submit, the parent makes it into the mempool, but the child would exceed ancestor vsize limits
res = self.nodes[0].submitpackage([tx_parent.serialize().hex(), tx_child.serialize().hex()])
diff --git a/test/functional/test_framework/wallet.py b/test/functional/test_framework/wallet.py
index 53c8e1b0cc..470ed08ed4 100644
--- a/test/functional/test_framework/wallet.py
+++ b/test/functional/test_framework/wallet.py
@@ -286,11 +286,12 @@ class MiniWallet:
utxos_to_spend: Optional[list[dict]] = None,
num_outputs=1,
amount_per_output=0,
+ version=2,
locktime=0,
sequence=0,
fee_per_output=1000,
target_weight=0,
- confirmed_only=False
+ confirmed_only=False,
):
"""
Create and return a transaction that spends the given UTXOs and creates a
@@ -313,6 +314,7 @@ class MiniWallet:
tx = CTransaction()
tx.vin = [CTxIn(COutPoint(int(utxo_to_spend['txid'], 16), utxo_to_spend['vout']), nSequence=seq) for utxo_to_spend, seq in zip(utxos_to_spend, sequence)]
tx.vout = [CTxOut(amount_per_output, bytearray(self._scriptPubKey)) for _ in range(num_outputs)]
+ tx.nVersion = version
tx.nLockTime = locktime
self.sign_tx(tx)
@@ -337,14 +339,15 @@ class MiniWallet:
"tx": tx,
}
- def create_self_transfer(self, *,
+ def create_self_transfer(
+ self,
+ *,
fee_rate=Decimal("0.003"),
fee=Decimal("0"),
utxo_to_spend=None,
- locktime=0,
- sequence=0,
target_weight=0,
- confirmed_only=False
+ confirmed_only=False,
+ **kwargs,
):
"""Create and return a tx with the specified fee. If fee is 0, use fee_rate, where the resulting fee may be exact or at most one satoshi higher than needed."""
utxo_to_spend = utxo_to_spend or self.get_utxo(confirmed_only=confirmed_only)
@@ -360,7 +363,12 @@ class MiniWallet:
send_value = utxo_to_spend["value"] - (fee or (fee_rate * vsize / 1000))
# create tx
- tx = self.create_self_transfer_multi(utxos_to_spend=[utxo_to_spend], locktime=locktime, sequence=sequence, amount_per_output=int(COIN * send_value), target_weight=target_weight)
+ tx = self.create_self_transfer_multi(
+ utxos_to_spend=[utxo_to_spend],
+ amount_per_output=int(COIN * send_value),
+ target_weight=target_weight,
+ **kwargs,
+ )
if not target_weight:
assert_equal(tx["tx"].get_vsize(), vsize)
tx["new_utxo"] = tx.pop("new_utxos")[0]
diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py
index d037ccf6dd..e438a60edc 100755
--- a/test/functional/test_runner.py
+++ b/test/functional/test_runner.py
@@ -266,6 +266,7 @@ BASE_SCRIPTS = [
'p2p_v2_encrypted.py',
'p2p_v2_earlykeyresponse.py',
'example_test.py',
+ 'mempool_accept_v3.py',
'wallet_txn_doublespend.py --legacy-wallet',
'wallet_multisig_descriptor_psbt.py --descriptors',
'wallet_txn_doublespend.py --descriptors',