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