p2p_1p1c_network.py raw
1 #!/usr/bin/env python3
2 # Copyright (c) 2024-present The Limenka 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 from math import ceil
14
15 from test_framework.mempool_util import (
16 DEFAULT_MIN_RELAY_TX_FEE,
17 fill_mempool,
18 )
19 from test_framework.messages import (
20 COIN,
21 msg_tx,
22 )
23 from test_framework.p2p import (
24 P2PInterface,
25 )
26 from test_framework.test_framework import LimenkaTestFramework
27 from test_framework.util import (
28 assert_equal,
29 assert_greater_than,
30 )
31 from test_framework.wallet import (
32 MiniWallet,
33 MiniWalletMode,
34 )
35
36 class PackageRelayTest(LimenkaTestFramework):
37 def set_test_params(self):
38 self.setup_clean_chain = True
39 self.num_nodes = 4
40 # hugely speeds up the test, as it involves multiple hops of tx relay.
41 self.noban_tx_relay = True
42 self.extra_args = [[
43 "-datacarriersize=100000",
44 "-maxmempool=5",
45 ]] * self.num_nodes
46 self.supports_cli = False
47
48 def raise_network_minfee(self):
49 fill_mempool(self, self.nodes[0])
50
51 self.log.debug("Check that all nodes' mempool minimum feerates are above min relay feerate")
52 for node in self.nodes:
53 assert_equal(node.getmempoolinfo()['minrelaytxfee'], Decimal(DEFAULT_MIN_RELAY_TX_FEE) / COIN)
54 assert_greater_than(node.getmempoolinfo()['mempoolminfee'], Decimal(DEFAULT_MIN_RELAY_TX_FEE) / COIN)
55
56 # Store mempoolminfee for dynamic feerate calculation
57 self.mempoolminfee = self.nodes[0].getmempoolinfo()['mempoolminfee']
58 self.log.info(f"mempoolminfee after fill_mempool: {self.mempoolminfee} BTC/kvB ({self.mempoolminfee * 100000:.4f} sat/vB)")
59
60 def create_basic_1p1c(self, wallet):
61 low_fee_parent = wallet.create_self_transfer(fee_rate=Decimal(DEFAULT_MIN_RELAY_TX_FEE) / COIN, confirmed_only=True)
62 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)
63 package_hex_basic = [low_fee_parent["hex"], high_fee_child["hex"]]
64 return package_hex_basic, low_fee_parent["tx"], high_fee_child["tx"]
65
66 def create_package_2outs(self, wallet):
67 # First create a tester tx to see the vsize, and then adjust the fees
68 utxo_for_2outs = wallet.get_utxo(confirmed_only=True)
69
70 low_fee_parent_2outs_tester = wallet.create_self_transfer_multi(
71 utxos_to_spend=[utxo_for_2outs],
72 num_outputs=2,
73 )
74
75 # Target 1sat/vB so the number of satoshis is equal to the vsize.
76 # Round up. The goal is to be between min relay feerate and mempool min feerate.
77 fee_2outs = ceil(low_fee_parent_2outs_tester["tx"].get_vsize() / 2)
78
79 low_fee_parent_2outs = wallet.create_self_transfer_multi(
80 utxos_to_spend=[utxo_for_2outs],
81 num_outputs=2,
82 fee_per_output=fee_2outs,
83 )
84
85 # Now create the child
86 high_fee_child_2outs = wallet.create_self_transfer_multi(
87 utxos_to_spend=low_fee_parent_2outs["new_utxos"][::-1],
88 fee_per_output=fee_2outs*100,
89 )
90 return [low_fee_parent_2outs["hex"], high_fee_child_2outs["hex"]], low_fee_parent_2outs["tx"], high_fee_child_2outs["tx"]
91
92 def create_package_2p1c(self, wallet):
93 # Use dynamic feerates based on actual mempoolminfee to ensure parents are above eviction threshold
94 # Set parent1 at 2x threshold, parent2 at 4x threshold (same relative ratio as before)
95 parent1_feerate = self.mempoolminfee * 2
96 parent2_feerate = self.mempoolminfee * 4
97
98 self.log.info(f"Creating 2p1c package with parent1={parent1_feerate} BTC/kvB, parent2={parent2_feerate} BTC/kvB")
99
100 parent1 = wallet.create_self_transfer(fee_rate=parent1_feerate, confirmed_only=True)
101 parent2 = wallet.create_self_transfer(fee_rate=parent2_feerate, confirmed_only=True)
102 child = wallet.create_self_transfer_multi(
103 utxos_to_spend=[parent1["new_utxo"], parent2["new_utxo"]],
104 fee_per_output=999*parent1["tx"].get_vsize(),
105 )
106 return [parent1["hex"], parent2["hex"], child["hex"]], parent1["tx"], parent2["tx"], child["tx"]
107
108 def create_packages(self):
109 # 1: Basic 1-parent-1-child package, parent 1sat/vB, child 999sat/vB
110 package_hex_1, parent_1, child_1 = self.create_basic_1p1c(self.wallet)
111
112 # 2: same as 1, parent's txid is the same as its wtxid.
113 package_hex_2, parent_2, child_2 = self.create_basic_1p1c(self.wallet_nonsegwit)
114
115 # 3: 2-parent-1-child package. Both parents are above mempool min feerate. No package submission happens.
116 # We require packages to be child-with-unconfirmed-parents and only allow 1-parent-1-child packages.
117 package_hex_3, parent_31, _parent_32, child_3 = self.create_package_2p1c(self.wallet)
118
119 # 4: parent + child package where the child spends 2 different outputs from the parent.
120 package_hex_4, parent_4, child_4 = self.create_package_2outs(self.wallet)
121
122 # Assemble return results
123 packages_to_submit = [package_hex_1, package_hex_2, package_hex_3, package_hex_4]
124 # node0: sender
125 # node1: pre-received the children (orphan)
126 # node3: pre-received the parents (too low fee)
127 # All nodes receive parent_31 ahead of time.
128 txns_to_send = [
129 [],
130 [child_1, child_2, parent_31, child_3, child_4],
131 [parent_31],
132 [parent_1, parent_2, parent_31, parent_4]
133 ]
134
135 return packages_to_submit, txns_to_send
136
137 def run_test(self):
138 self.wallet = MiniWallet(self.nodes[1])
139 self.wallet_nonsegwit = MiniWallet(self.nodes[2], mode=MiniWalletMode.RAW_P2PK)
140 self.generate(self.wallet_nonsegwit, 10)
141 self.generate(self.wallet, 120)
142
143 self.log.info("Fill mempools with large transactions to raise mempool minimum feerates")
144 self.raise_network_minfee()
145
146 # Create the transactions.
147 self.wallet.rescan_utxos(include_mempool=True)
148 packages_to_submit, transactions_to_presend = self.create_packages()
149
150 self.peers = [self.nodes[i].add_p2p_connection(P2PInterface()) for i in range(self.num_nodes)]
151
152 self.log.info("Pre-send some transactions to nodes")
153 for (i, peer) in enumerate(self.peers):
154 for tx in transactions_to_presend[i]:
155 peer.send_and_ping(msg_tx(tx))
156
157 # Disconnect python peers to clear outstanding orphan requests with them, avoiding timeouts.
158 # We are only interested in the syncing behavior between real nodes.
159 for i in range(self.num_nodes):
160 self.nodes[i].disconnect_p2ps()
161
162 self.log.info("Submit full packages to node0")
163 for package_hex in packages_to_submit:
164 submitpackage_result = self.nodes[0].submitpackage(package_hex)
165 assert_equal(submitpackage_result["package_msg"], "success")
166
167 self.log.info("Wait for mempools to sync")
168 self.sync_mempools()
169
170
171 if __name__ == '__main__':
172 PackageRelayTest(__file__).main()
173