diff --git a/test/functional/p2p_opportunistic_1p1c.py b/test/functional/p2p_opportunistic_1p1c.py index 6652442c36..56ab394baa 100755 --- a/test/functional/p2p_opportunistic_1p1c.py +++ b/test/functional/p2p_opportunistic_1p1c.py @@ -353,7 +353,7 @@ class PackageRelayTest(BitcoinTestFramework): @cleanup def test_other_parent_in_mempool(self): - self.log.info("Check opportunistic 1p1c works even 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 @@ -550,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 @@ -583,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() diff --git a/test/functional/rpc_packages.py b/test/functional/rpc_packages.py index efb08784f5..d52fe33a84 100755 --- a/test/functional/rpc_packages.py +++ b/test/functional/rpc_packages.py @@ -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() @@ -501,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()