mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-08-27 19:39:02 +02:00
Merge bitcoin/bitcoin#31385: package validation: relax the package-not-child-with-unconfirmed-parents rule
ea17a9423f
[doc] release note for relaxing requirement of all unconfirmed parents present (glozow)12f48d5ed3
test: add chained 1p1c propagation test (Greg Sanders)525be56741
[unit test] package submission 2p1c with 1 parent missing (glozow)f24771af05
relax child-with-unconfirmed-parents rule (glozow) Pull request description: Broadens the package validation interface, see #27463 for wider context. On master, package rules include that (1) the package topology must be child-wth-parents (2) all of the child's unconfirmed parents must be present. This PR relaxes the second rule, leaving the first rule untouched (there are plans to change that as well, but not here). Original motivation for this rule was based on the idea that we would have a child-with-unconfirmed-parents package relay protocol, and this would verify that the peer provided the "correct" package. For various reasons, we're not planning on doing this. We could potentially do this for ancestor packages (with a similar definition that all UTXOs to make the tx valid are available in this package), but it's also questionable whether it's useful to enforce this. This rule gets in the way of certain usage of 1p1c package relay currently. If a transaction has multiple parents, of which only 1 requires a package CPFP, this rule blocks the package from relaying. Even if all the non-low-feerate parents are already in mempool, when the p2p logic submits the 1p1c package, it gets rejected for not meeting this rule. ACKs for top commit: ishaanam: re-utACKea17a9423f
instagibbs: ACKea17a9423f
Tree-SHA512: c2231761ae7b2acea10a96735e7a36c646f517964d0acb59bacbae1c5a1950e0223458b84c6d5ce008f0c1d53c1605df0fb3cd0064ee535ead006eb7c0fa625b
This commit is contained in:
@@ -101,7 +101,7 @@ class PackageRelayTest(BitcoinTestFramework):
|
||||
package_hex_2, parent_2, child_2 = self.create_basic_1p1c(self.wallet_nonsegwit)
|
||||
|
||||
# 3: 2-parent-1-child package. Both parents are above mempool min feerate. No package submission happens.
|
||||
# We require packages to be child-with-unconfirmed-parents and only allow 1-parent-1-child packages.
|
||||
# We require packages to be child-with-parents and only allow 1-parent-1-child packages.
|
||||
package_hex_3, parent_31, _parent_32, child_3 = self.create_package_2p1c(self.wallet)
|
||||
|
||||
# 4: parent + child package where the child spends 2 different outputs from the parent.
|
||||
|
@@ -78,14 +78,14 @@ class PackageRelayTest(BitcoinTestFramework):
|
||||
"-maxmempool=5",
|
||||
]]
|
||||
|
||||
def create_tx_below_mempoolminfee(self, wallet):
|
||||
def create_tx_below_mempoolminfee(self, wallet, utxo_to_spend=None):
|
||||
"""Create a 1-input 1sat/vB transaction using a confirmed UTXO. Decrement and use
|
||||
self.sequence so that subsequent calls to this function result in unique transactions."""
|
||||
|
||||
self.sequence -= 1
|
||||
assert_greater_than(self.nodes[0].getmempoolinfo()["mempoolminfee"], FEERATE_1SAT_VB)
|
||||
|
||||
return wallet.create_self_transfer(fee_rate=FEERATE_1SAT_VB, sequence=self.sequence, confirmed_only=True)
|
||||
return wallet.create_self_transfer(fee_rate=FEERATE_1SAT_VB, sequence=self.sequence, utxo_to_spend=utxo_to_spend, confirmed_only=True)
|
||||
|
||||
@cleanup
|
||||
def test_basic_child_then_parent(self):
|
||||
@@ -353,11 +353,14 @@ class PackageRelayTest(BitcoinTestFramework):
|
||||
|
||||
@cleanup
|
||||
def test_other_parent_in_mempool(self):
|
||||
self.log.info("Check opportunistic 1p1c fails if child already has another parent in mempool")
|
||||
self.log.info("Check opportunistic 1p1c works when part of a 2p1c (child already has another parent in mempool)")
|
||||
node = self.nodes[0]
|
||||
|
||||
# Grandparent will enter mempool by itself
|
||||
grandparent_high = self.wallet.create_self_transfer(fee_rate=FEERATE_1SAT_VB*10, confirmed_only=True)
|
||||
|
||||
# This parent needs CPFP
|
||||
parent_low = self.create_tx_below_mempoolminfee(self.wallet)
|
||||
parent_low = self.create_tx_below_mempoolminfee(self.wallet, utxo_to_spend=grandparent_high["new_utxo"])
|
||||
# This parent does not need CPFP and can be submitted alone ahead of time
|
||||
parent_high = self.wallet.create_self_transfer(fee_rate=FEERATE_1SAT_VB*10, confirmed_only=True)
|
||||
child = self.wallet.create_self_transfer_multi(
|
||||
@@ -367,27 +370,27 @@ class PackageRelayTest(BitcoinTestFramework):
|
||||
|
||||
peer_sender = node.add_outbound_p2p_connection(P2PInterface(), p2p_idx=1, connection_type="outbound-full-relay")
|
||||
|
||||
# 1. Send first parent which will be accepted.
|
||||
# 1. Send grandparent which is accepted
|
||||
peer_sender.send_and_ping(msg_tx(grandparent_high["tx"]))
|
||||
assert grandparent_high["txid"] in node.getrawmempool()
|
||||
|
||||
# 2. Send first parent which is accepted.
|
||||
peer_sender.send_and_ping(msg_tx(parent_high["tx"]))
|
||||
assert parent_high["txid"] in node.getrawmempool()
|
||||
|
||||
# 2. Send child.
|
||||
# 3. Send child which is handled as an orphan.
|
||||
peer_sender.send_and_ping(msg_tx(child["tx"]))
|
||||
|
||||
# 3. Node requests parent_low. However, 1p1c fails because package-not-child-with-unconfirmed-parents
|
||||
# 4. Node requests parent_low.
|
||||
parent_low_txid_int = int(parent_low["txid"], 16)
|
||||
peer_sender.wait_for_getdata([parent_low_txid_int])
|
||||
peer_sender.send_and_ping(msg_tx(parent_low["tx"]))
|
||||
|
||||
node_mempool = node.getrawmempool()
|
||||
assert grandparent_high["txid"] in node_mempool
|
||||
assert parent_high["txid"] in node_mempool
|
||||
assert parent_low["txid"] not in node_mempool
|
||||
assert child["txid"] not in node_mempool
|
||||
|
||||
# Same error if submitted through submitpackage without parent_high
|
||||
package_hex_missing_parent = [parent_low["hex"], child["hex"]]
|
||||
result_missing_parent = node.submitpackage(package_hex_missing_parent)
|
||||
assert_equal(result_missing_parent["package_msg"], "package-not-child-with-unconfirmed-parents")
|
||||
assert parent_low["txid"] in node_mempool
|
||||
assert child["txid"] in node_mempool
|
||||
|
||||
def create_small_orphan(self):
|
||||
"""Create small orphan transaction"""
|
||||
@@ -547,6 +550,46 @@ class PackageRelayTest(BitcoinTestFramework):
|
||||
assert orphan_tx.txid_hex in node.getrawmempool()
|
||||
assert_equal(node.getmempoolentry(orphan_tx.txid_hex)["ancestorcount"], 2)
|
||||
|
||||
@cleanup
|
||||
def test_1p1c_on_1p1c(self):
|
||||
self.log.info("Test that opportunistic 1p1c works when part of a 4-generation chain (1p1c chained from a 1p1c)")
|
||||
node = self.nodes[0]
|
||||
|
||||
# Prep 2 generations of 1p1c packages to be relayed
|
||||
low_fee_great_grandparent = self.create_tx_below_mempoolminfee(self.wallet)
|
||||
high_fee_grandparent = self.wallet.create_self_transfer(utxo_to_spend=low_fee_great_grandparent["new_utxo"], fee_rate=20*FEERATE_1SAT_VB)
|
||||
|
||||
low_fee_parent = self.create_tx_below_mempoolminfee(self.wallet, utxo_to_spend=high_fee_grandparent["new_utxo"])
|
||||
high_fee_child = self.wallet.create_self_transfer(utxo_to_spend=low_fee_parent["new_utxo"], fee_rate=20*FEERATE_1SAT_VB)
|
||||
|
||||
peer_sender = node.add_p2p_connection(P2PInterface())
|
||||
|
||||
# The 1p1c that spends the confirmed utxo must be received first. Afterwards, the "younger" 1p1c can be received.
|
||||
for package in [[low_fee_great_grandparent, high_fee_grandparent], [low_fee_parent, high_fee_child]]:
|
||||
# Aliases
|
||||
parent_relative, child_relative = package
|
||||
|
||||
# 1. Child is received first (perhaps the low feerate parent didn't meet feefilter or the requests were sent to different nodes). It is missing an input.
|
||||
high_child_wtxid_int = child_relative["tx"].wtxid_int
|
||||
peer_sender.send_and_ping(msg_inv([CInv(t=MSG_WTX, h=high_child_wtxid_int)]))
|
||||
peer_sender.wait_for_getdata([high_child_wtxid_int])
|
||||
peer_sender.send_and_ping(msg_tx(child_relative["tx"]))
|
||||
|
||||
# 2. Node requests the missing parent by txid.
|
||||
parent_txid_int = parent_relative["tx"].txid_int
|
||||
peer_sender.wait_for_getdata([parent_txid_int])
|
||||
|
||||
# 3. Sender relays the parent. Parent+Child are evaluated as a package and accepted.
|
||||
peer_sender.send_and_ping(msg_tx(parent_relative["tx"]))
|
||||
|
||||
# 4. All transactions should now be in mempool.
|
||||
node_mempool = node.getrawmempool()
|
||||
assert low_fee_great_grandparent["txid"] in node_mempool
|
||||
assert high_fee_grandparent["txid"] in node_mempool
|
||||
assert low_fee_parent["txid"] in node_mempool
|
||||
assert high_fee_child["txid"] in node_mempool
|
||||
assert_equal(node.getmempoolentry(low_fee_great_grandparent["txid"])["descendantcount"], 4)
|
||||
|
||||
def run_test(self):
|
||||
node = self.nodes[0]
|
||||
# To avoid creating transactions with the same txid (can happen if we set the same feerate
|
||||
@@ -580,6 +623,7 @@ class PackageRelayTest(BitcoinTestFramework):
|
||||
self.test_parent_consensus_failure()
|
||||
self.test_multiple_parents()
|
||||
self.test_other_parent_in_mempool()
|
||||
self.test_1p1c_on_1p1c()
|
||||
|
||||
self.test_orphanage_dos_large()
|
||||
self.test_orphanage_dos_many()
|
||||
|
@@ -82,6 +82,7 @@ class RPCPackagesTest(BitcoinTestFramework):
|
||||
self.independent_txns_testres_blank = [{
|
||||
"txid": res["txid"], "wtxid": res["wtxid"]} for res in self.independent_txns_testres]
|
||||
|
||||
self.test_submitpackage_with_ancestors()
|
||||
self.test_independent(coin)
|
||||
self.test_chain()
|
||||
self.test_multiple_children()
|
||||
@@ -271,9 +272,11 @@ class RPCPackagesTest(BitcoinTestFramework):
|
||||
assert_equal(submitres, {'package_msg': 'conflict-in-package', 'tx-results': {}, 'replaced-transactions': []})
|
||||
assert tx_child["txid"] not in node.getrawmempool()
|
||||
|
||||
# ... and without the in-mempool ancestor tx1 included in the call
|
||||
# without the in-mempool ancestor tx1 included in the call, tx2 can be submitted, but
|
||||
# tx_child is missing an input.
|
||||
submitres = node.submitpackage([tx2["hex"], tx_child["hex"]])
|
||||
assert_equal(submitres, {'package_msg': 'package-not-child-with-unconfirmed-parents', 'tx-results': {}, 'replaced-transactions': []})
|
||||
assert_equal(submitres["tx-results"][tx_child["wtxid"]], {"txid": tx_child["txid"], "error": "bad-txns-inputs-missingorspent"})
|
||||
assert tx2["txid"] in node.getrawmempool()
|
||||
|
||||
# Regardless of error type, the child can never enter the mempool
|
||||
assert tx_child["txid"] not in node.getrawmempool()
|
||||
@@ -499,5 +502,40 @@ class RPCPackagesTest(BitcoinTestFramework):
|
||||
assert_equal(pkg_result["tx-results"][tx.wtxid_hex]["error"], "scriptpubkey")
|
||||
assert_equal(node.getrawmempool(), [chained_txns_burn[0]["txid"]])
|
||||
|
||||
def test_submitpackage_with_ancestors(self):
|
||||
self.log.info("Test that submitpackage can send a package that has in-mempool ancestors")
|
||||
node = self.nodes[0]
|
||||
peer = node.add_p2p_connection(P2PTxInvStore())
|
||||
|
||||
parent_tx = self.wallet.create_self_transfer()
|
||||
child_tx = self.wallet.create_self_transfer(utxo_to_spend=parent_tx["new_utxo"])
|
||||
grandchild_tx = self.wallet.create_self_transfer(utxo_to_spend=child_tx["new_utxo"])
|
||||
ggrandchild_tx = self.wallet.create_self_transfer(utxo_to_spend=grandchild_tx["new_utxo"])
|
||||
|
||||
# Submitting them all together doesn't work, as the topology is not child-with-parents
|
||||
assert_raises_rpc_error(-25, "package topology disallowed", node.submitpackage, [parent_tx["hex"], child_tx["hex"], grandchild_tx["hex"], ggrandchild_tx["hex"]])
|
||||
|
||||
# Submit older package and check acceptance
|
||||
result_submit_older = node.submitpackage(package=[parent_tx["hex"], child_tx["hex"]])
|
||||
assert_equal(result_submit_older["package_msg"], "success")
|
||||
mempool = node.getrawmempool()
|
||||
assert parent_tx["txid"] in mempool
|
||||
assert child_tx["txid"] in mempool
|
||||
|
||||
# Submit younger package and check acceptance
|
||||
result_submit_younger = node.submitpackage(package=[grandchild_tx["hex"], ggrandchild_tx["hex"]])
|
||||
assert_equal(result_submit_younger["package_msg"], "success")
|
||||
mempool = node.getrawmempool()
|
||||
|
||||
assert parent_tx["txid"] in mempool
|
||||
assert child_tx["txid"] in mempool
|
||||
assert grandchild_tx["txid"] in mempool
|
||||
assert ggrandchild_tx["txid"] in mempool
|
||||
|
||||
# The node should announce each transaction.
|
||||
peer.wait_for_broadcast([tx["tx"].wtxid_hex for tx in [parent_tx, child_tx, grandchild_tx, ggrandchild_tx]])
|
||||
self.generate(node, 1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
RPCPackagesTest(__file__).main()
|
||||
|
Reference in New Issue
Block a user