mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-02-08 10:31:50 -05:00
[functional test] sibling eviction
This commit is contained in:
parent
5fbab37859
commit
1342a31f3a
1 changed files with 168 additions and 15 deletions
|
@ -15,10 +15,13 @@ from test_framework.util import (
|
|||
assert_raises_rpc_error,
|
||||
)
|
||||
from test_framework.wallet import (
|
||||
COIN,
|
||||
DEFAULT_FEE,
|
||||
MiniWallet,
|
||||
)
|
||||
|
||||
MAX_REPLACEMENT_CANDIDATES = 100
|
||||
|
||||
def cleanup(extra_args=None):
|
||||
def decorator(func):
|
||||
def wrapper(self):
|
||||
|
@ -290,8 +293,13 @@ class MempoolAcceptV3(BitcoinTestFramework):
|
|||
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")
|
||||
def test_sibling_eviction_package(self):
|
||||
"""
|
||||
When a transaction has a mempool sibling, it may be eligible for sibling eviction.
|
||||
However, this option is only available in single transaction acceptance. It doesn't work in
|
||||
a multi-testmempoolaccept (where RBF is disabled) or when doing package CPFP.
|
||||
"""
|
||||
self.log.info("Test v3 sibling eviction in submitpackage and multi-testmempoolaccept")
|
||||
node = self.nodes[0]
|
||||
# Add a parent + child to mempool
|
||||
tx_mempool_parent = self.wallet.send_self_transfer_multi(
|
||||
|
@ -307,26 +315,57 @@ class MempoolAcceptV3(BitcoinTestFramework):
|
|||
)
|
||||
self.check_mempool([tx_mempool_parent["txid"], tx_mempool_sibling["txid"]])
|
||||
|
||||
tx_has_mempool_sibling = self.wallet.create_self_transfer(
|
||||
tx_sibling_1 = self.wallet.create_self_transfer(
|
||||
utxo_to_spend=tx_mempool_parent["new_utxos"][1],
|
||||
version=3
|
||||
version=3,
|
||||
fee_rate=DEFAULT_FEE*100,
|
||||
)
|
||||
expected_error_mempool_sibling_no_eviction = f"insufficient fee (including sibling eviction), rejecting replacement"
|
||||
assert_raises_rpc_error(-26, expected_error_mempool_sibling_no_eviction, node.sendrawtransaction, tx_has_mempool_sibling["hex"])
|
||||
tx_has_mempool_uncle = self.wallet.create_self_transfer(utxo_to_spend=tx_sibling_1["new_utxo"], version=3)
|
||||
|
||||
tx_has_mempool_uncle = self.wallet.create_self_transfer(utxo_to_spend=tx_has_mempool_sibling["new_utxo"], version=3)
|
||||
tx_sibling_2 = self.wallet.create_self_transfer(
|
||||
utxo_to_spend=tx_mempool_parent["new_utxos"][0],
|
||||
version=3,
|
||||
fee_rate=DEFAULT_FEE*200,
|
||||
)
|
||||
|
||||
# Also fails with another non-related transaction via testmempoolaccept
|
||||
tx_sibling_3 = self.wallet.create_self_transfer(
|
||||
utxo_to_spend=tx_mempool_parent["new_utxos"][1],
|
||||
version=3,
|
||||
fee_rate=0,
|
||||
)
|
||||
tx_bumps_parent_with_sibling = self.wallet.create_self_transfer(
|
||||
utxo_to_spend=tx_sibling_3["new_utxo"],
|
||||
version=3,
|
||||
fee_rate=DEFAULT_FEE*300,
|
||||
)
|
||||
|
||||
# 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"]])
|
||||
result_test_unrelated = node.testmempoolaccept([tx_sibling_1["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"]])
|
||||
# Fails in a package via testmempoolaccept
|
||||
result_test_1p1c = node.testmempoolaccept([tx_sibling_1["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 expected_error_mempool_sibling_no_eviction in result_submitpackage["tx-results"][tx_has_mempool_sibling['wtxid']]['error']
|
||||
# Allowed when tx is submitted in a package and evaluated individually.
|
||||
# Note that the child failed since it would be the 3rd generation.
|
||||
result_package_indiv = node.submitpackage([tx_sibling_1["hex"], tx_has_mempool_uncle["hex"]])
|
||||
self.check_mempool([tx_mempool_parent["txid"], tx_sibling_1["txid"]])
|
||||
expected_error_gen3 = f"v3-rule-violation, tx {tx_has_mempool_uncle['txid']} (wtxid={tx_has_mempool_uncle['wtxid']}) would have too many ancestors"
|
||||
|
||||
assert_equal(result_package_indiv["tx-results"][tx_has_mempool_uncle['wtxid']]['error'], expected_error_gen3)
|
||||
|
||||
# Allowed when tx is submitted in a package with in-mempool parent (which is deduplicated).
|
||||
node.submitpackage([tx_mempool_parent["hex"], tx_sibling_2["hex"]])
|
||||
self.check_mempool([tx_mempool_parent["txid"], tx_sibling_2["txid"]])
|
||||
|
||||
# Child cannot pay for sibling eviction for parent, as it violates v3 topology limits
|
||||
result_package_cpfp = node.submitpackage([tx_sibling_3["hex"], tx_bumps_parent_with_sibling["hex"]])
|
||||
self.check_mempool([tx_mempool_parent["txid"], tx_sibling_2["txid"]])
|
||||
expected_error_cpfp = f"v3-rule-violation, tx {tx_mempool_parent['txid']} (wtxid={tx_mempool_parent['wtxid']}) would exceed descendant count limit"
|
||||
|
||||
assert_equal(result_package_cpfp["tx-results"][tx_sibling_3['wtxid']]['error'], expected_error_cpfp)
|
||||
|
||||
|
||||
@cleanup(extra_args=["-datacarriersize=1000", "-acceptnonstdtxn=1"])
|
||||
|
@ -429,11 +468,123 @@ class MempoolAcceptV3(BitcoinTestFramework):
|
|||
self.check_mempool([ancestor_tx["txid"], child_1_conflict["txid"], child_2["txid"]])
|
||||
assert_equal(node.getmempoolentry(ancestor_tx["txid"])["descendantcount"], 3)
|
||||
|
||||
@cleanup(extra_args=["-acceptnonstdtxn=1"])
|
||||
def test_v3_sibling_eviction(self):
|
||||
self.log.info("Test sibling eviction for v3")
|
||||
node = self.nodes[0]
|
||||
tx_v3_parent = self.wallet.send_self_transfer_multi(from_node=node, num_outputs=2, version=3)
|
||||
# This is the sibling to replace
|
||||
tx_v3_child_1 = self.wallet.send_self_transfer(
|
||||
from_node=node, utxo_to_spend=tx_v3_parent["new_utxos"][0], fee_rate=DEFAULT_FEE * 2, version=3
|
||||
)
|
||||
assert tx_v3_child_1["txid"] in node.getrawmempool()
|
||||
|
||||
self.log.info("Test tx must be higher feerate than sibling to evict it")
|
||||
tx_v3_child_2_rule6 = self.wallet.create_self_transfer(
|
||||
utxo_to_spend=tx_v3_parent["new_utxos"][1], fee_rate=DEFAULT_FEE, version=3
|
||||
)
|
||||
rule6_str = f"insufficient fee (including sibling eviction), rejecting replacement {tx_v3_child_2_rule6['txid']}; new feerate"
|
||||
assert_raises_rpc_error(-26, rule6_str, node.sendrawtransaction, tx_v3_child_2_rule6["hex"])
|
||||
self.check_mempool([tx_v3_parent['txid'], tx_v3_child_1['txid']])
|
||||
|
||||
self.log.info("Test tx must meet absolute fee rules to evict sibling")
|
||||
tx_v3_child_2_rule4 = self.wallet.create_self_transfer(
|
||||
utxo_to_spend=tx_v3_parent["new_utxos"][1], fee_rate=2 * DEFAULT_FEE + Decimal("0.00000001"), version=3
|
||||
)
|
||||
rule4_str = f"insufficient fee (including sibling eviction), rejecting replacement {tx_v3_child_2_rule4['txid']}, not enough additional fees to relay"
|
||||
assert_raises_rpc_error(-26, rule4_str, node.sendrawtransaction, tx_v3_child_2_rule4["hex"])
|
||||
self.check_mempool([tx_v3_parent['txid'], tx_v3_child_1['txid']])
|
||||
|
||||
self.log.info("Test tx cannot cause more than 100 evictions including RBF and sibling eviction")
|
||||
# First add 4 groups of 25 transactions.
|
||||
utxos_for_conflict = []
|
||||
txids_v2_100 = []
|
||||
for _ in range(4):
|
||||
confirmed_utxo = self.wallet.get_utxo(confirmed_only=True)
|
||||
utxos_for_conflict.append(confirmed_utxo)
|
||||
# 25 is within descendant limits
|
||||
chain_length = int(MAX_REPLACEMENT_CANDIDATES / 4)
|
||||
chain = self.wallet.create_self_transfer_chain(chain_length=chain_length, utxo_to_spend=confirmed_utxo)
|
||||
for item in chain:
|
||||
txids_v2_100.append(item["txid"])
|
||||
node.sendrawtransaction(item["hex"])
|
||||
self.check_mempool(txids_v2_100 + [tx_v3_parent["txid"], tx_v3_child_1["txid"]])
|
||||
|
||||
# Replacing 100 transactions is fine
|
||||
tx_v3_replacement_only = self.wallet.create_self_transfer_multi(utxos_to_spend=utxos_for_conflict, fee_per_output=4000000)
|
||||
# Override maxfeerate - it costs a lot to replace these 100 transactions.
|
||||
assert node.testmempoolaccept([tx_v3_replacement_only["hex"]], maxfeerate=0)[0]["allowed"]
|
||||
# Adding another one exceeds the limit.
|
||||
utxos_for_conflict.append(tx_v3_parent["new_utxos"][1])
|
||||
tx_v3_child_2_rule5 = self.wallet.create_self_transfer_multi(utxos_to_spend=utxos_for_conflict, fee_per_output=4000000, version=3)
|
||||
rule5_str = f"too many potential replacements (including sibling eviction), rejecting replacement {tx_v3_child_2_rule5['txid']}; too many potential replacements (101 > 100)"
|
||||
assert_raises_rpc_error(-26, rule5_str, node.sendrawtransaction, tx_v3_child_2_rule5["hex"])
|
||||
self.check_mempool(txids_v2_100 + [tx_v3_parent["txid"], tx_v3_child_1["txid"]])
|
||||
|
||||
self.log.info("Test sibling eviction is successful if it meets all RBF rules")
|
||||
tx_v3_child_2 = self.wallet.create_self_transfer(
|
||||
utxo_to_spend=tx_v3_parent["new_utxos"][1], fee_rate=DEFAULT_FEE*10, version=3
|
||||
)
|
||||
node.sendrawtransaction(tx_v3_child_2["hex"])
|
||||
self.check_mempool(txids_v2_100 + [tx_v3_parent["txid"], tx_v3_child_2["txid"]])
|
||||
|
||||
self.log.info("Test that it's possible to do a sibling eviction and RBF at the same time")
|
||||
utxo_unrelated_conflict = self.wallet.get_utxo(confirmed_only=True)
|
||||
tx_unrelated_replacee = self.wallet.send_self_transfer(from_node=node, utxo_to_spend=utxo_unrelated_conflict)
|
||||
assert tx_unrelated_replacee["txid"] in node.getrawmempool()
|
||||
|
||||
fee_to_beat_child2 = int(tx_v3_child_2["fee"] * COIN)
|
||||
|
||||
tx_v3_child_3 = self.wallet.create_self_transfer_multi(
|
||||
utxos_to_spend=[tx_v3_parent["new_utxos"][0], utxo_unrelated_conflict], fee_per_output=fee_to_beat_child2*5, version=3
|
||||
)
|
||||
node.sendrawtransaction(tx_v3_child_3["hex"])
|
||||
self.check_mempool(txids_v2_100 + [tx_v3_parent["txid"], tx_v3_child_3["txid"]])
|
||||
|
||||
@cleanup(extra_args=["-acceptnonstdtxn=1"])
|
||||
def test_reorg_sibling_eviction_1p2c(self):
|
||||
node = self.nodes[0]
|
||||
self.log.info("Test that sibling eviction is not allowed when multiple siblings exist")
|
||||
|
||||
tx_with_multi_children = self.wallet.send_self_transfer_multi(from_node=node, num_outputs=3, version=3, confirmed_only=True)
|
||||
self.check_mempool([tx_with_multi_children["txid"]])
|
||||
|
||||
block_to_disconnect = self.generate(node, 1)[0]
|
||||
self.check_mempool([])
|
||||
|
||||
tx_with_sibling1 = self.wallet.send_self_transfer(from_node=node, version=3, utxo_to_spend=tx_with_multi_children["new_utxos"][0])
|
||||
tx_with_sibling2 = self.wallet.send_self_transfer(from_node=node, version=3, utxo_to_spend=tx_with_multi_children["new_utxos"][1])
|
||||
self.check_mempool([tx_with_sibling1["txid"], tx_with_sibling2["txid"]])
|
||||
|
||||
# Create a reorg, bringing tx_with_multi_children back into the mempool with a descendant count of 3.
|
||||
node.invalidateblock(block_to_disconnect)
|
||||
self.check_mempool([tx_with_multi_children["txid"], tx_with_sibling1["txid"], tx_with_sibling2["txid"]])
|
||||
assert_equal(node.getmempoolentry(tx_with_multi_children["txid"])["descendantcount"], 3)
|
||||
|
||||
# Sibling eviction is not allowed because there are two siblings
|
||||
tx_with_sibling3 = self.wallet.create_self_transfer(
|
||||
version=3,
|
||||
utxo_to_spend=tx_with_multi_children["new_utxos"][2],
|
||||
fee_rate=DEFAULT_FEE*50
|
||||
)
|
||||
expected_error_2siblings = f"v3-rule-violation, tx {tx_with_multi_children['txid']} (wtxid={tx_with_multi_children['wtxid']}) would exceed descendant count limit"
|
||||
assert_raises_rpc_error(-26, expected_error_2siblings, node.sendrawtransaction, tx_with_sibling3["hex"])
|
||||
|
||||
# However, an RBF (with conflicting inputs) is possible even if the resulting cluster size exceeds 2
|
||||
tx_with_sibling3_rbf = self.wallet.send_self_transfer(
|
||||
from_node=node,
|
||||
version=3,
|
||||
utxo_to_spend=tx_with_multi_children["new_utxos"][0],
|
||||
fee_rate=DEFAULT_FEE*50
|
||||
)
|
||||
self.check_mempool([tx_with_multi_children["txid"], tx_with_sibling3_rbf["txid"], tx_with_sibling2["txid"]])
|
||||
|
||||
|
||||
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.generate(self.wallet, 120)
|
||||
self.test_v3_acceptance()
|
||||
self.test_v3_replacement()
|
||||
self.test_v3_bip125()
|
||||
|
@ -441,10 +592,12 @@ class MempoolAcceptV3(BitcoinTestFramework):
|
|||
self.test_nondefault_package_limits()
|
||||
self.test_v3_ancestors_package()
|
||||
self.test_v3_ancestors_package_and_mempool()
|
||||
self.test_mempool_sibling()
|
||||
self.test_sibling_eviction_package()
|
||||
self.test_v3_package_inheritance()
|
||||
self.test_v3_in_testmempoolaccept()
|
||||
self.test_reorg_2child_rbf()
|
||||
self.test_v3_sibling_eviction()
|
||||
self.test_reorg_sibling_eviction_1p2c()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
Loading…
Add table
Reference in a new issue