rpc_packages.py raw
1 #!/usr/bin/env python3
2 # Copyright (c) 2021-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 """RPCs that handle raw transaction packages."""
6
7 from decimal import Decimal
8 import random
9
10 from test_framework.blocktools import COINBASE_MATURITY
11 from test_framework.mempool_util import (
12 fill_mempool,
13 )
14 from test_framework.messages import (
15 MAX_BIP125_RBF_SEQUENCE,
16 tx_from_hex,
17 )
18 from test_framework.p2p import P2PTxInvStore
19 from test_framework.test_framework import BitcoinTestFramework
20 from test_framework.util import (
21 assert_equal,
22 assert_fee_amount,
23 assert_raises_rpc_error,
24 )
25 from test_framework.wallet import (
26 COIN,
27 DEFAULT_FEE,
28 MiniWallet,
29 )
30
31
32 MAX_PACKAGE_COUNT = 25
33
34
35 class RPCPackagesTest(BitcoinTestFramework):
36 def set_test_params(self):
37 self.num_nodes = 1
38 self.setup_clean_chain = True
39 # whitelist peers to speed up tx relay / mempool sync
40 self.noban_tx_relay = True
41
42 def assert_testres_equal(self, package_hex, testres_expected):
43 """Shuffle package_hex and assert that the testmempoolaccept result matches testres_expected. This should only
44 be used to test packages where the order does not matter. The ordering of transactions in package_hex and
45 testres_expected must match.
46 """
47 shuffled_indices = list(range(len(package_hex)))
48 random.shuffle(shuffled_indices)
49 shuffled_package = [package_hex[i] for i in shuffled_indices]
50 shuffled_testres = [testres_expected[i] for i in shuffled_indices]
51 assert_equal(shuffled_testres, self.nodes[0].testmempoolaccept(shuffled_package))
52
53 def run_test(self):
54 node = self.nodes[0]
55
56 # get an UTXO that requires signature to be spent
57 deterministic_address = node.get_deterministic_priv_key().address
58 blockhash = self.generatetoaddress(node, 1, deterministic_address)[0]
59 coinbase = node.getblock(blockhash=blockhash, verbosity=2)["tx"][0]
60 coin = {
61 "txid": coinbase["txid"],
62 "amount": coinbase["vout"][0]["value"],
63 "scriptPubKey": coinbase["vout"][0]["scriptPubKey"],
64 "vout": 0,
65 "height": 0
66 }
67
68 self.wallet = MiniWallet(self.nodes[0])
69 self.generate(self.wallet, COINBASE_MATURITY + 100) # blocks generated for inputs
70
71 self.log.info("Create some transactions")
72 # Create some transactions that can be reused throughout the test. Never submit these to mempool.
73 self.independent_txns_hex = []
74 self.independent_txns_testres = []
75 for _ in range(3):
76 tx_hex = self.wallet.create_self_transfer(fee_rate=Decimal("0.0001"))["hex"]
77 testres = self.nodes[0].testmempoolaccept([tx_hex])
78 assert testres[0]["allowed"]
79 self.independent_txns_hex.append(tx_hex)
80 # testmempoolaccept returns a list of length one, avoid creating a 2D list
81 self.independent_txns_testres.append(testres[0])
82 self.independent_txns_testres_blank = [{
83 "txid": res["txid"], "wtxid": res["wtxid"]} for res in self.independent_txns_testres]
84
85 self.test_submitpackage_with_ancestors()
86 self.test_independent(coin)
87 self.test_chain()
88 self.test_multiple_children()
89 self.test_multiple_parents()
90 self.test_conflicting()
91 self.test_rbf()
92 self.test_submitpackage()
93 self.test_maxfeerate_submitpackage()
94 self.test_maxburn_submitpackage()
95
96 def test_independent(self, coin):
97 self.log.info("Test multiple independent transactions in a package")
98 node = self.nodes[0]
99 # For independent transactions, order doesn't matter.
100 self.assert_testres_equal(self.independent_txns_hex, self.independent_txns_testres)
101
102 self.log.info("Test an otherwise valid package with an extra garbage tx appended")
103 address = node.get_deterministic_priv_key().address
104 garbage_tx = node.createrawtransaction([{"txid": "00" * 32, "vout": 5}], {address: 1})
105 tx = tx_from_hex(garbage_tx)
106 # Only the txid and wtxids are returned because validation is incomplete for the independent txns.
107 # Package validation is atomic: if the node cannot find a UTXO for any single tx in the package,
108 # it terminates immediately to avoid unnecessary, expensive signature verification.
109 package_bad = self.independent_txns_hex + [garbage_tx]
110 testres_bad = self.independent_txns_testres_blank + [{"txid": tx.txid_hex, "wtxid": tx.wtxid_hex, "allowed": False, "reject-reason": "missing-inputs"}]
111 self.assert_testres_equal(package_bad, testres_bad)
112
113 self.log.info("Check testmempoolaccept tells us when some transactions completed validation successfully")
114 tx_bad_sig_hex = node.createrawtransaction([{"txid": coin["txid"], "vout": coin["vout"]}],
115 {address : coin["amount"] - Decimal("0.0001")})
116 tx_bad_sig = tx_from_hex(tx_bad_sig_hex)
117 testres_bad_sig = node.testmempoolaccept(self.independent_txns_hex + [tx_bad_sig_hex])
118 # By the time the signature for the last transaction is checked, all the other transactions
119 # have been fully validated, which is why the node returns full validation results for all
120 # transactions here but empty results in other cases.
121 tx_bad_sig_txid = tx_bad_sig.txid_hex
122 tx_bad_sig_wtxid = tx_bad_sig.wtxid_hex
123 assert_equal(testres_bad_sig, self.independent_txns_testres + [{
124 "txid": tx_bad_sig_txid,
125 "wtxid": tx_bad_sig_wtxid, "allowed": False,
126 "reject-reason": "mempool-script-verify-flag-failed (Operation not valid with the current stack size)",
127 "reject-details": "mempool-script-verify-flag-failed (Operation not valid with the current stack size), " +
128 f"input 0 of {tx_bad_sig_txid} (wtxid {tx_bad_sig_wtxid}), spending {coin['txid']}:{coin['vout']}"
129 }])
130
131 self.log.info("Check testmempoolaccept reports txns in packages that exceed max feerate")
132 tx_high_fee = self.wallet.create_self_transfer(fee=Decimal("0.999"))
133 testres_high_fee = node.testmempoolaccept([tx_high_fee["hex"]])
134 assert_equal(testres_high_fee, [
135 {"txid": tx_high_fee["txid"], "wtxid": tx_high_fee["wtxid"], "allowed": False, "reject-reason": "max-fee-exceeded"}
136 ])
137 package_high_fee = [tx_high_fee["hex"]] + self.independent_txns_hex
138 testres_package_high_fee = node.testmempoolaccept(package_high_fee)
139 assert_equal(testres_package_high_fee, testres_high_fee + self.independent_txns_testres_blank)
140
141 def test_chain(self):
142 node = self.nodes[0]
143
144 chain = self.wallet.create_self_transfer_chain(chain_length=25)
145 chain_hex = [t["hex"] for t in chain]
146 chain_txns = [t["tx"] for t in chain]
147
148 self.log.info("Check that testmempoolaccept requires packages to be sorted by dependency")
149 assert_equal(node.testmempoolaccept(rawtxs=chain_hex[::-1]),
150 [{"txid": tx.txid_hex, "wtxid": tx.wtxid_hex, "package-error": "package-not-sorted"} for tx in chain_txns[::-1]])
151
152 self.log.info("Testmempoolaccept a chain of 25 transactions")
153 testres_multiple = node.testmempoolaccept(rawtxs=chain_hex)
154
155 testres_single = []
156 # Test accept and then submit each one individually, which should be identical to package test accept
157 for rawtx in chain_hex:
158 testres = node.testmempoolaccept([rawtx])
159 testres_single.append(testres[0])
160 # Submit the transaction now so its child should have no problem validating
161 node.sendrawtransaction(rawtx)
162 assert_equal(testres_single, testres_multiple)
163
164 # Clean up by clearing the mempool
165 self.generate(node, 1)
166
167 def test_multiple_children(self):
168 node = self.nodes[0]
169 self.log.info("Testmempoolaccept a package in which a transaction has two children within the package")
170
171 parent_tx = self.wallet.create_self_transfer_multi(num_outputs=2)
172 assert node.testmempoolaccept([parent_tx["hex"]])[0]["allowed"]
173
174 # Child A
175 child_a_tx = self.wallet.create_self_transfer(utxo_to_spend=parent_tx["new_utxos"][0])
176 assert not node.testmempoolaccept([child_a_tx["hex"]])[0]["allowed"]
177
178 # Child B
179 child_b_tx = self.wallet.create_self_transfer(utxo_to_spend=parent_tx["new_utxos"][1])
180 assert not node.testmempoolaccept([child_b_tx["hex"]])[0]["allowed"]
181
182 self.log.info("Testmempoolaccept with entire package, should work with children in either order")
183 testres_multiple_ab = node.testmempoolaccept(rawtxs=[parent_tx["hex"], child_a_tx["hex"], child_b_tx["hex"]])
184 testres_multiple_ba = node.testmempoolaccept(rawtxs=[parent_tx["hex"], child_b_tx["hex"], child_a_tx["hex"]])
185 assert all([testres["allowed"] for testres in testres_multiple_ab + testres_multiple_ba])
186
187 testres_single = []
188 # Test accept and then submit each one individually, which should be identical to package testaccept
189 for rawtx in [parent_tx["hex"], child_a_tx["hex"], child_b_tx["hex"]]:
190 testres = node.testmempoolaccept([rawtx])
191 testres_single.append(testres[0])
192 # Submit the transaction now so its child should have no problem validating
193 node.sendrawtransaction(rawtx)
194 assert_equal(testres_single, testres_multiple_ab)
195
196 def test_multiple_parents(self):
197 node = self.nodes[0]
198 self.log.info("Testmempoolaccept a package in which a transaction has multiple parents within the package")
199
200 for num_parents in [2, 10, 24]:
201 # Test a package with num_parents parents and 1 child transaction.
202 parent_coins = []
203 package_hex = []
204
205 for _ in range(num_parents):
206 # Package accept should work with the parents in any order (as long as parents come before child)
207 parent_tx = self.wallet.create_self_transfer()
208 parent_coins.append(parent_tx["new_utxo"])
209 package_hex.append(parent_tx["hex"])
210
211 child_tx = self.wallet.create_self_transfer_multi(utxos_to_spend=parent_coins, fee_per_output=2000)
212 for _ in range(10):
213 random.shuffle(package_hex)
214 testres_multiple = node.testmempoolaccept(rawtxs=package_hex + [child_tx['hex']])
215 assert all([testres["allowed"] for testres in testres_multiple])
216
217 testres_single = []
218 # Test accept and then submit each one individually, which should be identical to package testaccept
219 for rawtx in package_hex + [child_tx["hex"]]:
220 testres_single.append(node.testmempoolaccept([rawtx])[0])
221 # Submit the transaction now so its child should have no problem validating
222 node.sendrawtransaction(rawtx)
223 assert_equal(testres_single, testres_multiple)
224
225 def test_conflicting(self):
226 node = self.nodes[0]
227 coin = self.wallet.get_utxo()
228
229 # tx1 and tx2 share the same inputs
230 tx1 = self.wallet.create_self_transfer(utxo_to_spend=coin, fee_rate=DEFAULT_FEE)
231 tx2 = self.wallet.create_self_transfer(utxo_to_spend=coin, fee_rate=2*DEFAULT_FEE)
232
233 # Ensure tx1 and tx2 are valid by themselves
234 assert node.testmempoolaccept([tx1["hex"]])[0]["allowed"]
235 assert node.testmempoolaccept([tx2["hex"]])[0]["allowed"]
236
237 self.log.info("Test duplicate transactions in the same package")
238 testres = node.testmempoolaccept([tx1["hex"], tx1["hex"]])
239 assert_equal(testres, [
240 {"txid": tx1["txid"], "wtxid": tx1["wtxid"], "package-error": "package-contains-duplicates"},
241 {"txid": tx1["txid"], "wtxid": tx1["wtxid"], "package-error": "package-contains-duplicates"}
242 ])
243
244 self.log.info("Test conflicting transactions in the same package")
245 testres = node.testmempoolaccept([tx1["hex"], tx2["hex"]])
246 assert_equal(testres, [
247 {"txid": tx1["txid"], "wtxid": tx1["wtxid"], "package-error": "conflict-in-package"},
248 {"txid": tx2["txid"], "wtxid": tx2["wtxid"], "package-error": "conflict-in-package"}
249 ])
250
251 # Add a child that spends both at high feerate to submit via submitpackage
252 tx_child = self.wallet.create_self_transfer_multi(
253 fee_per_output=int(DEFAULT_FEE * 5 * COIN),
254 utxos_to_spend=[tx1["new_utxo"], tx2["new_utxo"]],
255 )
256
257 testres = node.testmempoolaccept([tx1["hex"], tx2["hex"], tx_child["hex"]])
258
259 assert_equal(testres, [
260 {"txid": tx1["txid"], "wtxid": tx1["wtxid"], "package-error": "conflict-in-package"},
261 {"txid": tx2["txid"], "wtxid": tx2["wtxid"], "package-error": "conflict-in-package"},
262 {"txid": tx_child["txid"], "wtxid": tx_child["wtxid"], "package-error": "conflict-in-package"}
263 ])
264
265 submitres = node.submitpackage([tx1["hex"], tx2["hex"], tx_child["hex"]])
266 expected = {
267 tx1["wtxid"]: {"txid": tx1["txid"], "error": "package-not-validated"},
268 tx2["wtxid"]: {"txid": tx2["txid"], "error": "package-not-validated"},
269 tx_child["wtxid"]: {"txid": tx_child["txid"], "error": "package-not-validated"},
270 }
271 assert_equal(submitres, {"package_msg": "conflict-in-package", "tx-results": expected,"replaced-transactions": []})
272
273 # Submit tx1 to mempool, then try the same package again
274 node.sendrawtransaction(tx1["hex"])
275
276 submitres = node.submitpackage([tx1["hex"], tx2["hex"], tx_child["hex"]])
277 expected = {
278 tx1["wtxid"]: {"txid": tx1["txid"], "error": "package-not-validated"},
279 tx2["wtxid"]: {"txid": tx2["txid"], "error": "package-not-validated"},
280 tx_child["wtxid"]: {"txid": tx_child["txid"], "error": "package-not-validated"},
281 }
282 assert_equal(submitres, {"package_msg": "conflict-in-package", "tx-results": expected,"replaced-transactions": []})
283 assert tx_child["txid"] not in node.getrawmempool()
284
285 # without the in-mempool ancestor tx1 included in the call, tx2 can be submitted, but
286 # tx_child is missing an input.
287 submitres = node.submitpackage([tx2["hex"], tx_child["hex"]])
288 assert_equal(submitres["tx-results"][tx_child["wtxid"]], {"txid": tx_child["txid"], "error": "bad-txns-inputs-missingorspent"})
289 assert tx2["txid"] in node.getrawmempool()
290
291 # Regardless of error type, the child can never enter the mempool
292 assert tx_child["txid"] not in node.getrawmempool()
293
294 def test_rbf(self):
295 node = self.nodes[0]
296
297 coin = self.wallet.get_utxo()
298 fee = Decimal("0.00125000")
299 replaceable_tx = self.wallet.create_self_transfer(utxo_to_spend=coin, sequence=MAX_BIP125_RBF_SEQUENCE, fee = fee)
300 testres_replaceable = node.testmempoolaccept([replaceable_tx["hex"]])[0]
301 assert_equal(testres_replaceable["txid"], replaceable_tx["txid"])
302 assert_equal(testres_replaceable["wtxid"], replaceable_tx["wtxid"])
303 assert testres_replaceable["allowed"]
304 assert_equal(testres_replaceable["vsize"], replaceable_tx["tx"].get_vsize())
305 assert_equal(testres_replaceable["fees"]["base"], fee)
306 assert_fee_amount(fee, replaceable_tx["tx"].get_vsize(), testres_replaceable["fees"]["effective-feerate"])
307 assert_equal(testres_replaceable["fees"]["effective-includes"], [replaceable_tx["wtxid"]])
308
309 # Replacement transaction is identical except has double the fee
310 replacement_tx = self.wallet.create_self_transfer(utxo_to_spend=coin, sequence=MAX_BIP125_RBF_SEQUENCE, fee = 2 * fee)
311 testres_rbf_conflicting = node.testmempoolaccept([replaceable_tx["hex"], replacement_tx["hex"]])
312 assert_equal(testres_rbf_conflicting, [
313 {"txid": replaceable_tx["txid"], "wtxid": replaceable_tx["wtxid"], "package-error": "conflict-in-package"},
314 {"txid": replacement_tx["txid"], "wtxid": replacement_tx["wtxid"], "package-error": "conflict-in-package"}
315 ])
316
317 self.log.info("Test that packages cannot conflict with mempool transactions, even if a valid BIP125 RBF")
318 # This transaction is a valid BIP125 replace-by-fee
319 self.wallet.sendrawtransaction(from_node=node, tx_hex=replaceable_tx["hex"])
320 testres_rbf_single = node.testmempoolaccept([replacement_tx["hex"]])
321 assert testres_rbf_single[0]["allowed"]
322 testres_rbf_package = self.independent_txns_testres_blank + [{
323 "txid": replacement_tx["txid"], "wtxid": replacement_tx["wtxid"], "allowed": False,
324 "reject-reason": "bip125-replacement-disallowed",
325 "reject-details": "bip125-replacement-disallowed"
326 }]
327 self.assert_testres_equal(self.independent_txns_hex + [replacement_tx["hex"]], testres_rbf_package)
328
329 def assert_equal_package_results(self, node, testmempoolaccept_result, submitpackage_result):
330 """Assert that a successful submitpackage result is consistent with testmempoolaccept
331 results and getmempoolentry info. Note that the result structs are different and, due to
332 policy differences between testmempoolaccept and submitpackage (i.e. package feerate),
333 some information may be different.
334 """
335 for testres_tx in testmempoolaccept_result:
336 # Grab this result from the submitpackage_result
337 submitres_tx = submitpackage_result["tx-results"][testres_tx["wtxid"]]
338 assert_equal(submitres_tx["txid"], testres_tx["txid"])
339 # No "allowed" if the tx was already in the mempool
340 if "allowed" in testres_tx and testres_tx["allowed"]:
341 assert_equal(submitres_tx["vsize"], testres_tx["vsize"])
342 assert_equal(submitres_tx["fees"]["base"], testres_tx["fees"]["base"])
343 entry_info = node.getmempoolentry(submitres_tx["txid"])
344 assert_equal(submitres_tx["vsize"], entry_info["vsize"])
345 assert_equal(submitres_tx["fees"]["base"], entry_info["fees"]["base"])
346
347 def test_submit_child_with_parents(self, num_parents, partial_submit):
348 node = self.nodes[0]
349 peer = node.add_p2p_connection(P2PTxInvStore())
350
351 package_txns = []
352 presubmitted_wtxids = set()
353 for _ in range(num_parents):
354 parent_tx = self.wallet.create_self_transfer(fee=DEFAULT_FEE)
355 package_txns.append(parent_tx)
356 if partial_submit and random.choice([True, False]):
357 node.sendrawtransaction(parent_tx["hex"])
358 presubmitted_wtxids.add(parent_tx["wtxid"])
359 child_tx = self.wallet.create_self_transfer_multi(utxos_to_spend=[tx["new_utxo"] for tx in package_txns], fee_per_output=10000) #DEFAULT_FEE
360 package_txns.append(child_tx)
361
362 testmempoolaccept_result = node.testmempoolaccept(rawtxs=[tx["hex"] for tx in package_txns])
363 submitpackage_result = node.submitpackage(package=[tx["hex"] for tx in package_txns])
364
365 # Check that each result is present, with the correct size and fees
366 assert_equal(submitpackage_result["package_msg"], "success")
367 for package_txn in package_txns:
368 tx = package_txn["tx"]
369 assert tx.wtxid_hex in submitpackage_result["tx-results"]
370 wtxid = tx.wtxid_hex
371 assert wtxid in submitpackage_result["tx-results"]
372 tx_result = submitpackage_result["tx-results"][wtxid]
373 assert_equal(tx_result["txid"], tx.txid_hex)
374 assert_equal(tx_result["vsize"], tx.get_vsize())
375 assert_equal(tx_result["fees"]["base"], DEFAULT_FEE)
376 if wtxid not in presubmitted_wtxids:
377 assert_fee_amount(DEFAULT_FEE, tx.get_vsize(), tx_result["fees"]["effective-feerate"])
378 assert_equal(tx_result["fees"]["effective-includes"], [wtxid])
379
380 # submitpackage result should be consistent with testmempoolaccept and getmempoolentry
381 self.assert_equal_package_results(node, testmempoolaccept_result, submitpackage_result)
382
383 # The node should announce each transaction. No guarantees for propagation.
384 peer.wait_for_broadcast([tx["tx"].wtxid_hex for tx in package_txns])
385 self.generate(node, 1)
386
387 def test_submitpackage(self):
388 node = self.nodes[0]
389
390 self.log.info("Submitpackage only allows valid hex inputs")
391 valid_tx_list = self.wallet.create_self_transfer_chain(chain_length=2)
392 hex_list = [valid_tx_list[0]["hex"][:-1] + 'X', valid_tx_list[1]["hex"]]
393 txid_list = [valid_tx_list[0]["txid"], valid_tx_list[1]["txid"]]
394 assert_raises_rpc_error(-22, "TX decode failed:", node.submitpackage, hex_list)
395 assert txid_list[0] not in node.getrawmempool()
396 assert txid_list[1] not in node.getrawmempool()
397
398 self.log.info("Submitpackage valid packages with 1 child and some number of parents (or none)")
399 for num_parents in [0, 1, 2, 24]:
400 self.test_submit_child_with_parents(num_parents, False)
401 self.test_submit_child_with_parents(num_parents, True)
402
403 self.log.info("Submitpackage only allows packages of 1 child with its parents")
404 # Chain of 3 transactions has too many generations
405 legacy_pool = node.getrawmempool()
406 chain_hex = [t["hex"] for t in self.wallet.create_self_transfer_chain(chain_length=3)]
407 assert_raises_rpc_error(-25, "package topology disallowed", node.submitpackage, chain_hex)
408 assert_equal(legacy_pool, node.getrawmempool())
409
410 assert_raises_rpc_error(-8, f"Array must contain between 1 and {MAX_PACKAGE_COUNT} transactions.", node.submitpackage, [])
411 assert_raises_rpc_error(
412 -8, f"Array must contain between 1 and {MAX_PACKAGE_COUNT} transactions.",
413 node.submitpackage, [chain_hex[0]] * (MAX_PACKAGE_COUNT + 1)
414 )
415
416 # Create a transaction chain such as only the parent gets accepted (by making the child's
417 # version non-standard). Make sure the parent does get broadcast.
418 self.log.info("If a package is partially submitted, transactions included in mempool get broadcast")
419 peer = node.add_p2p_connection(P2PTxInvStore())
420 txs = self.wallet.create_self_transfer_chain(chain_length=2)
421 bad_child = tx_from_hex(txs[1]["hex"])
422 bad_child.version = 0xffffffff
423 hex_partial_acceptance = [txs[0]["hex"], bad_child.serialize().hex()]
424 res = node.submitpackage(hex_partial_acceptance)
425 assert_equal(res["package_msg"], "transaction failed")
426 first_wtxid = txs[0]["tx"].wtxid_hex
427 assert "error" not in res["tx-results"][first_wtxid]
428 sec_wtxid = bad_child.wtxid_hex
429 assert_equal(res["tx-results"][sec_wtxid]["error"], "version")
430 peer.wait_for_broadcast([first_wtxid])
431
432 def test_maxfeerate_submitpackage(self):
433 node = self.nodes[0]
434 # clear mempool
435 deterministic_address = node.get_deterministic_priv_key().address
436 self.generatetoaddress(node, 1, deterministic_address)
437
438 self.log.info("Submitpackage maxfeerate arg testing")
439 chained_txns = self.wallet.create_self_transfer_chain(chain_length=2)
440 minrate_btc_kvb = min([chained_txn["fee"] / chained_txn["tx"].get_vsize() * 1000 for chained_txn in chained_txns])
441 chain_hex = [t["hex"] for t in chained_txns]
442 pkg_result = node.submitpackage(chain_hex, maxfeerate=minrate_btc_kvb - Decimal("0.00000001"))
443
444 # First tx failed in single transaction evaluation, so package message is generic
445 assert_equal(pkg_result["package_msg"], "transaction failed")
446 assert_equal(pkg_result["tx-results"][chained_txns[0]["wtxid"]]["error"], "max feerate exceeded")
447 assert_equal(pkg_result["tx-results"][chained_txns[1]["wtxid"]]["error"], "bad-txns-inputs-missingorspent")
448 assert_equal(node.getrawmempool(), [])
449
450 # Make chain of two transactions where parent doesn't make minfee threshold
451 # but child is too high fee
452 # Lower mempool limit to make it easier to fill_mempool
453 self.restart_node(0, extra_args=[
454 "-maxmempool=5",
455 "-persistmempool=0",
456 ])
457 self.wallet.rescan_utxos()
458
459 fill_mempool(self, node)
460
461 minrelay = node.getmempoolinfo()["minrelaytxfee"]
462 parent = self.wallet.create_self_transfer(
463 fee_rate=minrelay,
464 confirmed_only=True,
465 )
466
467 child = self.wallet.create_self_transfer(
468 fee_rate=DEFAULT_FEE,
469 utxo_to_spend=parent["new_utxo"],
470 )
471
472 pkg_result = node.submitpackage([parent["hex"], child["hex"]], maxfeerate=DEFAULT_FEE - Decimal("0.00000001"))
473
474 # Child is connected even though parent is invalid and still reports fee exceeded
475 # this implies sub-package evaluation of both entries together.
476 assert_equal(pkg_result["package_msg"], "transaction failed")
477 assert "mempool min fee not met" in pkg_result["tx-results"][parent["wtxid"]]["error"]
478 assert_equal(pkg_result["tx-results"][child["wtxid"]]["error"], "max feerate exceeded")
479 assert parent["txid"] not in node.getrawmempool()
480 assert child["txid"] not in node.getrawmempool()
481
482 # Reset maxmempool, reset dynamic mempool minimum feerate, and empty mempool.
483 self.restart_node(0)
484 self.wallet.rescan_utxos()
485
486 assert_equal(node.getrawmempool(), [])
487
488 def test_maxburn_submitpackage(self):
489 node = self.nodes[0]
490
491 assert_equal(node.getrawmempool(), [])
492
493 self.log.info("Submitpackage maxburnamount arg testing")
494 chained_txns_burn = self.wallet.create_self_transfer_chain(
495 chain_length=2,
496 utxo_to_spend=self.wallet.get_utxo(confirmed_only=True),
497 )
498 chained_burn_hex = [t["hex"] for t in chained_txns_burn]
499
500 tx = tx_from_hex(chained_burn_hex[1])
501 tx.vout[-1].scriptPubKey = b'a' * 10001 # scriptPubKey bigger than 10k IsUnspendable
502 chained_burn_hex = [chained_burn_hex[0], tx.serialize().hex()]
503 # burn test is run before any package evaluation; nothing makes it in and we get broader exception
504 assert_raises_rpc_error(-25, "Unspendable output exceeds maximum configured by user", node.submitpackage, chained_burn_hex, 0, chained_txns_burn[1]["new_utxo"]["value"] - Decimal("0.00000001"))
505 assert_equal(node.getrawmempool(), [])
506
507 minrate_btc_kvb_burn = min([chained_txn_burn["fee"] / chained_txn_burn["tx"].get_vsize() * 1000 for chained_txn_burn in chained_txns_burn])
508
509 # Relax the restrictions for both and send it; parent gets through as own subpackage
510 pkg_result = node.submitpackage(chained_burn_hex, maxfeerate=minrate_btc_kvb_burn, maxburnamount=chained_txns_burn[1]["new_utxo"]["value"])
511 assert "error" not in pkg_result["tx-results"][chained_txns_burn[0]["wtxid"]]
512 assert_equal(pkg_result["tx-results"][tx.wtxid_hex]["error"], "scriptpubkey")
513 assert_equal(node.getrawmempool(), [chained_txns_burn[0]["txid"]])
514
515 def test_submitpackage_with_ancestors(self):
516 self.log.info("Test that submitpackage can send a package that has in-mempool ancestors")
517 node = self.nodes[0]
518 peer = node.add_p2p_connection(P2PTxInvStore())
519
520 parent_tx = self.wallet.create_self_transfer()
521 child_tx = self.wallet.create_self_transfer(utxo_to_spend=parent_tx["new_utxo"])
522 grandchild_tx = self.wallet.create_self_transfer(utxo_to_spend=child_tx["new_utxo"])
523 ggrandchild_tx = self.wallet.create_self_transfer(utxo_to_spend=grandchild_tx["new_utxo"])
524
525 # Submitting them all together doesn't work, as the topology is not child-with-parents
526 assert_raises_rpc_error(-25, "package topology disallowed", node.submitpackage, [parent_tx["hex"], child_tx["hex"], grandchild_tx["hex"], ggrandchild_tx["hex"]])
527
528 # Submit older package and check acceptance
529 result_submit_older = node.submitpackage(package=[parent_tx["hex"], child_tx["hex"]])
530 assert_equal(result_submit_older["package_msg"], "success")
531 mempool = node.getrawmempool()
532 assert parent_tx["txid"] in mempool
533 assert child_tx["txid"] in mempool
534
535 # Submit younger package and check acceptance
536 result_submit_younger = node.submitpackage(package=[grandchild_tx["hex"], ggrandchild_tx["hex"]])
537 assert_equal(result_submit_younger["package_msg"], "success")
538 mempool = node.getrawmempool()
539
540 assert parent_tx["txid"] in mempool
541 assert child_tx["txid"] in mempool
542 assert grandchild_tx["txid"] in mempool
543 assert ggrandchild_tx["txid"] in mempool
544
545 # The node should announce each transaction.
546 peer.wait_for_broadcast([tx["tx"].wtxid_hex for tx in [parent_tx, child_tx, grandchild_tx, ggrandchild_tx]])
547 self.generate(node, 1)
548
549
550 if __name__ == "__main__":
551 RPCPackagesTest(__file__).main()
552