15
15
CTxInWitness ,
16
16
tx_from_hex ,
17
17
)
18
+ from test_framework .p2p import P2PTxInvStore
18
19
from test_framework .script import (
19
20
CScript ,
20
21
OP_TRUE ,
21
22
)
22
23
from test_framework .util import (
23
24
assert_equal ,
25
+ assert_fee_amount ,
26
+ assert_raises_rpc_error ,
24
27
)
25
28
from test_framework .wallet import (
26
29
create_child_with_parents ,
27
30
create_raw_chain ,
31
+ DEFAULT_FEE ,
28
32
make_chain ,
29
33
)
30
34
@@ -51,7 +55,7 @@ def run_test(self):
51
55
self .address = node .get_deterministic_priv_key ().address
52
56
self .coins = []
53
57
# The last 100 coinbase transactions are premature
54
- for b in self .generatetoaddress (node , 200 , self .address )[:100 ]:
58
+ for b in self .generatetoaddress (node , 220 , self .address )[:- 100 ]:
55
59
coinbase = node .getblock (blockhash = b , verbosity = 2 )["tx" ][0 ]
56
60
self .coins .append ({
57
61
"txid" : coinbase ["txid" ],
@@ -82,7 +86,7 @@ def run_test(self):
82
86
self .test_multiple_parents ()
83
87
self .test_conflicting ()
84
88
self .test_rbf ()
85
-
89
+ self . test_submitpackage ()
86
90
87
91
def test_independent (self ):
88
92
self .log .info ("Test multiple independent transactions in a package" )
@@ -132,8 +136,7 @@ def test_independent(self):
132
136
133
137
def test_chain (self ):
134
138
node = self .nodes [0 ]
135
- first_coin = self .coins .pop ()
136
- (chain_hex , chain_txns ) = create_raw_chain (node , first_coin , self .address , self .privkeys )
139
+ (chain_hex , chain_txns ) = create_raw_chain (node , self .coins .pop (), self .address , self .privkeys )
137
140
self .log .info ("Check that testmempoolaccept requires packages to be sorted by dependency" )
138
141
assert_equal (node .testmempoolaccept (rawtxs = chain_hex [::- 1 ]),
139
142
[{"txid" : tx .rehash (), "wtxid" : tx .getwtxid (), "package-error" : "package-not-sorted" } for tx in chain_txns [::- 1 ]])
@@ -306,5 +309,127 @@ def test_rbf(self):
306
309
}]
307
310
self .assert_testres_equal (self .independent_txns_hex + [signed_replacement_tx ["hex" ]], testres_rbf_package )
308
311
312
+ def assert_equal_package_results (self , node , testmempoolaccept_result , submitpackage_result ):
313
+ """Assert that a successful submitpackage result is consistent with testmempoolaccept
314
+ results and getmempoolentry info. Note that the result structs are different and, due to
315
+ policy differences between testmempoolaccept and submitpackage (i.e. package feerate),
316
+ some information may be different.
317
+ """
318
+ for testres_tx in testmempoolaccept_result :
319
+ # Grab this result from the submitpackage_result
320
+ submitres_tx = submitpackage_result ["tx-results" ][testres_tx ["wtxid" ]]
321
+ assert_equal (submitres_tx ["txid" ], testres_tx ["txid" ])
322
+ # No "allowed" if the tx was already in the mempool
323
+ if "allowed" in testres_tx and testres_tx ["allowed" ]:
324
+ assert_equal (submitres_tx ["vsize" ], testres_tx ["vsize" ])
325
+ assert_equal (submitres_tx ["fees" ]["base" ], testres_tx ["fees" ]["base" ])
326
+ entry_info = node .getmempoolentry (submitres_tx ["txid" ])
327
+ assert_equal (submitres_tx ["vsize" ], entry_info ["vsize" ])
328
+ assert_equal (submitres_tx ["fees" ]["base" ], entry_info ["fees" ]["base" ])
329
+
330
+ def test_submit_child_with_parents (self , num_parents , partial_submit ):
331
+ node = self .nodes [0 ]
332
+ peer = node .add_p2p_connection (P2PTxInvStore ())
333
+ # Test a package with num_parents parents and 1 child transaction.
334
+ package_hex = []
335
+ package_txns = []
336
+ values = []
337
+ scripts = []
338
+ for _ in range (num_parents ):
339
+ parent_coin = self .coins .pop ()
340
+ value = parent_coin ["amount" ]
341
+ (tx , txhex , value , spk ) = make_chain (node , self .address , self .privkeys , parent_coin ["txid" ], value )
342
+ package_hex .append (txhex )
343
+ package_txns .append (tx )
344
+ values .append (value )
345
+ scripts .append (spk )
346
+ if partial_submit and random .choice ([True , False ]):
347
+ node .sendrawtransaction (txhex )
348
+ child_hex = create_child_with_parents (node , self .address , self .privkeys , package_txns , values , scripts )
349
+ package_hex .append (child_hex )
350
+ package_txns .append (tx_from_hex (child_hex ))
351
+
352
+ testmempoolaccept_result = node .testmempoolaccept (rawtxs = package_hex )
353
+ submitpackage_result = node .submitpackage (package = package_hex )
354
+
355
+ # Check that each result is present, with the correct size and fees
356
+ for i in range (num_parents + 1 ):
357
+ tx = package_txns [i ]
358
+ wtxid = tx .getwtxid ()
359
+ assert wtxid in submitpackage_result ["tx-results" ]
360
+ tx_result = submitpackage_result ["tx-results" ][wtxid ]
361
+ assert_equal (tx_result , {
362
+ "txid" : tx .rehash (),
363
+ "vsize" : tx .get_vsize (),
364
+ "fees" : {
365
+ "base" : DEFAULT_FEE ,
366
+ }
367
+ })
368
+
369
+ # submitpackage result should be consistent with testmempoolaccept and getmempoolentry
370
+ self .assert_equal_package_results (node , testmempoolaccept_result , submitpackage_result )
371
+
372
+ # Package feerate is calculated for the remaining transactions after deduplication and
373
+ # individual submission. If only 0 or 1 transaction is left, e.g. because all transactions
374
+ # had high-feerates or were already in the mempool, no package feerate is provided.
375
+ # In this case, since all of the parents have high fees, each is accepted individually.
376
+ assert "package-feerate" not in submitpackage_result
377
+
378
+ # The node should announce each transaction. No guarantees for propagation.
379
+ peer .wait_for_broadcast ([tx .getwtxid () for tx in package_txns ])
380
+ self .generate (node , 1 )
381
+
382
+
383
+ def test_submit_cpfp (self ):
384
+ node = self .nodes [0 ]
385
+ peer = node .add_p2p_connection (P2PTxInvStore ())
386
+
387
+ # 2 parent 1 child CPFP. First parent pays high fees, second parent pays 0 fees and is
388
+ # fee-bumped by the child.
389
+ coin_rich = self .coins .pop ()
390
+ coin_poor = self .coins .pop ()
391
+ tx_rich , hex_rich , value_rich , spk_rich = make_chain (node , self .address , self .privkeys , coin_rich ["txid" ], coin_rich ["amount" ])
392
+ tx_poor , hex_poor , value_poor , spk_poor = make_chain (node , self .address , self .privkeys , coin_poor ["txid" ], coin_poor ["amount" ], fee = 0 )
393
+ package_txns = [tx_rich , tx_poor ]
394
+ hex_child = create_child_with_parents (node , self .address , self .privkeys , package_txns , [value_rich , value_poor ], [spk_rich , spk_poor ])
395
+ tx_child = tx_from_hex (hex_child )
396
+ package_txns .append (tx_child )
397
+
398
+ submitpackage_result = node .submitpackage ([hex_rich , hex_poor , hex_child ])
399
+
400
+ rich_parent_result = submitpackage_result ["tx-results" ][tx_rich .getwtxid ()]
401
+ poor_parent_result = submitpackage_result ["tx-results" ][tx_poor .getwtxid ()]
402
+ child_result = submitpackage_result ["tx-results" ][tx_child .getwtxid ()]
403
+ assert_equal (rich_parent_result ["fees" ]["base" ], DEFAULT_FEE )
404
+ assert_equal (poor_parent_result ["fees" ]["base" ], 0 )
405
+ assert_equal (child_result ["fees" ]["base" ], DEFAULT_FEE )
406
+ # Package feerate is calculated for the remaining transactions after deduplication and
407
+ # individual submission. Since this package had a 0-fee parent, package feerate must have
408
+ # been used and returned.
409
+ assert "package-feerate" in submitpackage_result
410
+ assert_fee_amount (DEFAULT_FEE , rich_parent_result ["vsize" ] + child_result ["vsize" ], submitpackage_result ["package-feerate" ])
411
+
412
+ # The node will broadcast each transaction, still abiding by its peer's fee filter
413
+ peer .wait_for_broadcast ([tx .getwtxid () for tx in package_txns ])
414
+ self .generate (node , 1 )
415
+
416
+
417
+ def test_submitpackage (self ):
418
+ node = self .nodes [0 ]
419
+
420
+ self .log .info ("Submitpackage valid packages with 1 child and some number of parents" )
421
+ for num_parents in [1 , 2 , 24 ]:
422
+ self .test_submit_child_with_parents (num_parents , False )
423
+ self .test_submit_child_with_parents (num_parents , True )
424
+
425
+ self .log .info ("Submitpackage valid packages with CPFP" )
426
+ self .test_submit_cpfp ()
427
+
428
+ self .log .info ("Submitpackage only allows packages of 1 child with its parents" )
429
+ # Chain of 3 transactions has too many generations
430
+ chain_hex , _ = create_raw_chain (node , self .coins .pop (), self .address , self .privkeys , 3 )
431
+ assert_raises_rpc_error (- 25 , "not-child-with-parents" , node .submitpackage , chain_hex )
432
+
433
+
309
434
if __name__ == "__main__" :
310
435
RPCPackagesTest ().main ()
0 commit comments