p2p_1p1c_network.py raw
1 #!/usr/bin/env python3
2 # Copyright (c) 2024-present The Bitcoin Core developers
3 # Distributed under the MIT software license, see the accompanying
4 # file COPYING or http://www.opensource.org/licenses/mit-license.php.
5 """
6 Test that 1p1c package submission allows a 1p1c package to propagate in a "network" of nodes. Send
7 various packages from different nodes on a network in which some nodes have already received some of
8 the transactions (and submitted them to mempool, kept them as orphans or rejected them as
9 too-low-feerate transactions). The packages should be received and accepted by all nodes.
10 """
11
12 from decimal import Decimal
13
14 from test_framework.mempool_util import (
15 DEFAULT_MIN_RELAY_TX_FEE,
16 )
17 from test_framework.messages import (
18 COIN,
19 msg_tx,
20 )
21 from test_framework.p2p import (
22 P2PInterface,
23 )
24 from test_framework.test_framework import BitcoinTestFramework
25 from test_framework.util import (
26 assert_equal,
27 )
28 from test_framework.wallet import (
29 MiniWallet,
30 MiniWalletMode,
31 )
32
33 class PackageRelayTest(BitcoinTestFramework):
34 def set_test_params(self):
35 self.setup_clean_chain = True
36 self.num_nodes = 4
37 # hugely speeds up the test, as it involves multiple hops of tx relay.
38 self.noban_tx_relay = True
39
40 def create_basic_1p1c(self, wallet):
41 low_fee_parent = wallet.create_self_transfer(fee_rate=0, confirmed_only=True)
42 high_fee_child = wallet.create_self_transfer(utxo_to_spend=low_fee_parent["new_utxo"], fee_rate=999*Decimal(DEFAULT_MIN_RELAY_TX_FEE)/ COIN)
43 package_hex_basic = [low_fee_parent["hex"], high_fee_child["hex"]]
44 return package_hex_basic, low_fee_parent["tx"], high_fee_child["tx"]
45
46 def create_package_2outs(self, wallet):
47 # First create a tester tx to see the vsize, and then adjust the fees
48 utxo_for_2outs = wallet.get_utxo(confirmed_only=True)
49
50 low_fee_parent_2outs = wallet.create_self_transfer_multi(
51 utxos_to_spend=[utxo_for_2outs],
52 num_outputs=2,
53 fee_per_output=0,
54 )
55
56 # Now create the child
57 high_fee_child_2outs = wallet.create_self_transfer_multi(
58 utxos_to_spend=low_fee_parent_2outs["new_utxos"][::-1],
59 fee_per_output=10_000,
60 )
61 return [low_fee_parent_2outs["hex"], high_fee_child_2outs["hex"]], low_fee_parent_2outs["tx"], high_fee_child_2outs["tx"]
62
63 def create_package_2p1c(self, wallet):
64 parent1 = wallet.create_self_transfer(fee_rate=Decimal(DEFAULT_MIN_RELAY_TX_FEE) / COIN * 10, confirmed_only=True)
65 parent2 = wallet.create_self_transfer(fee_rate=Decimal(DEFAULT_MIN_RELAY_TX_FEE) / COIN * 20, confirmed_only=True)
66 child = wallet.create_self_transfer_multi(
67 utxos_to_spend=[parent1["new_utxo"], parent2["new_utxo"]],
68 fee_per_output=999*parent1["tx"].get_vsize(),
69 )
70 return [parent1["hex"], parent2["hex"], child["hex"]], parent1["tx"], parent2["tx"], child["tx"]
71
72 def create_packages(self):
73 # 1: Basic 1-parent-1-child package, parent 0sat/vB, child 999sat/vB
74 package_hex_1, parent_1, child_1 = self.create_basic_1p1c(self.wallet)
75
76 # 2: same as 1, parent's txid is the same as its wtxid.
77 package_hex_2, parent_2, child_2 = self.create_basic_1p1c(self.wallet_nonsegwit)
78
79 # 3: 2-parent-1-child package. Both parents are above mempool min feerate. No package submission happens.
80 # We require packages to be child-with-parents and only allow 1-parent-1-child packages.
81 package_hex_3, parent_31, _parent_32, child_3 = self.create_package_2p1c(self.wallet)
82
83 # 4: parent + child package where the child spends 2 different outputs from the parent.
84 package_hex_4, parent_4, child_4 = self.create_package_2outs(self.wallet)
85
86 # Assemble return results
87 packages_to_submit = [package_hex_1, package_hex_2, package_hex_3, package_hex_4]
88 # node0: sender
89 # node1: pre-received the children (orphans, will be dropped on peer disconnect)
90 # node3: pre-received the parents (too low fee)
91 # All nodes receive parent_31 ahead of time.
92 txns_to_send = [
93 [],
94 [child_1, child_2, parent_31, child_3, child_4],
95 [parent_31],
96 [parent_1, parent_2, parent_31, parent_4]
97 ]
98
99 return packages_to_submit, txns_to_send
100
101 def run_test(self):
102 self.wallet = MiniWallet(self.nodes[1])
103 self.wallet_nonsegwit = MiniWallet(self.nodes[2], mode=MiniWalletMode.RAW_P2PK)
104 self.generate(self.wallet_nonsegwit, 10)
105 self.generate(self.wallet, 120)
106
107 # Create the transactions.
108 self.wallet.rescan_utxos(include_mempool=True)
109 packages_to_submit, transactions_to_presend = self.create_packages()
110
111 self.peers = [self.nodes[i].add_p2p_connection(P2PInterface()) for i in range(self.num_nodes)]
112
113 self.log.info("Pre-send some transactions to nodes")
114 for (i, peer) in enumerate(self.peers):
115 for tx in transactions_to_presend[i]:
116 peer.send_and_ping(msg_tx(tx))
117
118 # The fee-having parent should be the only thing in mempools
119 self.sync_mempools()
120 sufficient_parent = transactions_to_presend[2][0].txid_hex
121 for i, node in enumerate(self.nodes):
122 # node1 has non-empty orphanage as well
123 if i == 1:
124 assert_equal(len(self.nodes[i].getorphantxs()), 4)
125 else:
126 assert_equal(self.nodes[i].getorphantxs(), [])
127
128 assert_equal(node.getrawmempool(), [sufficient_parent])
129
130 # Disconnect python peers to clear outstanding orphan requests with them, avoiding timeouts.
131 # We are only interested in the syncing behavior between real nodes.
132 for i in range(self.num_nodes):
133 self.nodes[i].disconnect_p2ps()
134 self.wait_until(lambda: len(self.nodes[i].getorphantxs()) == 0)
135
136 self.log.info("Submit full packages to node0")
137 for package_hex in packages_to_submit:
138 submitpackage_result = self.nodes[0].submitpackage(package_hex)
139 assert_equal(submitpackage_result["package_msg"], "success")
140
141 self.log.info("Wait for mempools to sync")
142 self.wait_until(lambda: len(self.nodes[0].getrawmempool()) == 9)
143 self.sync_mempools()
144
145
146 if __name__ == '__main__':
147 PackageRelayTest(__file__).main()
148