15
15
assert_raises_rpc_error ,
16
16
)
17
17
from test_framework .wallet import (
18
+ COIN ,
18
19
DEFAULT_FEE ,
19
20
MiniWallet ,
20
21
)
21
22
23
+ MAX_REPLACEMENT_CANDIDATES = 100
24
+
22
25
def cleanup (extra_args = None ):
23
26
def decorator (func ):
24
27
def wrapper (self ):
@@ -290,8 +293,13 @@ def test_v3_ancestors_package_and_mempool(self):
290
293
self .check_mempool ([tx_in_mempool ["txid" ]])
291
294
292
295
@cleanup (extra_args = ["-acceptnonstdtxn=1" ])
293
- def test_mempool_sibling (self ):
294
- self .log .info ("Test that v3 transaction cannot have mempool siblings" )
296
+ def test_sibling_eviction_package (self ):
297
+ """
298
+ When a transaction has a mempool sibling, it may be eligible for sibling eviction.
299
+ However, this option is only available in single transaction acceptance. It doesn't work in
300
+ a multi-testmempoolaccept (where RBF is disabled) or when doing package CPFP.
301
+ """
302
+ self .log .info ("Test v3 sibling eviction in submitpackage and multi-testmempoolaccept" )
295
303
node = self .nodes [0 ]
296
304
# Add a parent + child to mempool
297
305
tx_mempool_parent = self .wallet .send_self_transfer_multi (
@@ -307,26 +315,57 @@ def test_mempool_sibling(self):
307
315
)
308
316
self .check_mempool ([tx_mempool_parent ["txid" ], tx_mempool_sibling ["txid" ]])
309
317
310
- tx_has_mempool_sibling = self .wallet .create_self_transfer (
318
+ tx_sibling_1 = self .wallet .create_self_transfer (
311
319
utxo_to_spend = tx_mempool_parent ["new_utxos" ][1 ],
312
- version = 3
320
+ version = 3 ,
321
+ fee_rate = DEFAULT_FEE * 100 ,
313
322
)
314
- expected_error_mempool_sibling_no_eviction = f"insufficient fee (including sibling eviction), rejecting replacement"
315
- assert_raises_rpc_error (- 26 , expected_error_mempool_sibling_no_eviction , node .sendrawtransaction , tx_has_mempool_sibling ["hex" ])
323
+ tx_has_mempool_uncle = self .wallet .create_self_transfer (utxo_to_spend = tx_sibling_1 ["new_utxo" ], version = 3 )
316
324
317
- tx_has_mempool_uncle = self .wallet .create_self_transfer (utxo_to_spend = tx_has_mempool_sibling ["new_utxo" ], version = 3 )
325
+ tx_sibling_2 = self .wallet .create_self_transfer (
326
+ utxo_to_spend = tx_mempool_parent ["new_utxos" ][0 ],
327
+ version = 3 ,
328
+ fee_rate = DEFAULT_FEE * 200 ,
329
+ )
330
+
331
+ tx_sibling_3 = self .wallet .create_self_transfer (
332
+ utxo_to_spend = tx_mempool_parent ["new_utxos" ][1 ],
333
+ version = 3 ,
334
+ fee_rate = 0 ,
335
+ )
336
+ tx_bumps_parent_with_sibling = self .wallet .create_self_transfer (
337
+ utxo_to_spend = tx_sibling_3 ["new_utxo" ],
338
+ version = 3 ,
339
+ fee_rate = DEFAULT_FEE * 300 ,
340
+ )
318
341
319
- # Also fails with another non-related transaction via testmempoolaccept
342
+ # Fails with another non-related transaction via testmempoolaccept
320
343
tx_unrelated = self .wallet .create_self_transfer (version = 3 )
321
- result_test_unrelated = node .testmempoolaccept ([tx_has_mempool_sibling ["hex" ], tx_unrelated ["hex" ]])
344
+ result_test_unrelated = node .testmempoolaccept ([tx_sibling_1 ["hex" ], tx_unrelated ["hex" ]])
322
345
assert_equal (result_test_unrelated [0 ]["reject-reason" ], "v3-rule-violation" )
323
346
324
- result_test_1p1c = node .testmempoolaccept ([tx_has_mempool_sibling ["hex" ], tx_has_mempool_uncle ["hex" ]])
347
+ # Fails in a package via testmempoolaccept
348
+ result_test_1p1c = node .testmempoolaccept ([tx_sibling_1 ["hex" ], tx_has_mempool_uncle ["hex" ]])
325
349
assert_equal (result_test_1p1c [0 ]["reject-reason" ], "v3-rule-violation" )
326
350
327
- # Also fails with a child via submitpackage
328
- result_submitpackage = node .submitpackage ([tx_has_mempool_sibling ["hex" ], tx_has_mempool_uncle ["hex" ]])
329
- assert expected_error_mempool_sibling_no_eviction in result_submitpackage ["tx-results" ][tx_has_mempool_sibling ['wtxid' ]]['error' ]
351
+ # Allowed when tx is submitted in a package and evaluated individually.
352
+ # Note that the child failed since it would be the 3rd generation.
353
+ result_package_indiv = node .submitpackage ([tx_sibling_1 ["hex" ], tx_has_mempool_uncle ["hex" ]])
354
+ self .check_mempool ([tx_mempool_parent ["txid" ], tx_sibling_1 ["txid" ]])
355
+ expected_error_gen3 = f"v3-rule-violation, tx { tx_has_mempool_uncle ['txid' ]} (wtxid={ tx_has_mempool_uncle ['wtxid' ]} ) would have too many ancestors"
356
+
357
+ assert_equal (result_package_indiv ["tx-results" ][tx_has_mempool_uncle ['wtxid' ]]['error' ], expected_error_gen3 )
358
+
359
+ # Allowed when tx is submitted in a package with in-mempool parent (which is deduplicated).
360
+ node .submitpackage ([tx_mempool_parent ["hex" ], tx_sibling_2 ["hex" ]])
361
+ self .check_mempool ([tx_mempool_parent ["txid" ], tx_sibling_2 ["txid" ]])
362
+
363
+ # Child cannot pay for sibling eviction for parent, as it violates v3 topology limits
364
+ result_package_cpfp = node .submitpackage ([tx_sibling_3 ["hex" ], tx_bumps_parent_with_sibling ["hex" ]])
365
+ self .check_mempool ([tx_mempool_parent ["txid" ], tx_sibling_2 ["txid" ]])
366
+ expected_error_cpfp = f"v3-rule-violation, tx { tx_mempool_parent ['txid' ]} (wtxid={ tx_mempool_parent ['wtxid' ]} ) would exceed descendant count limit"
367
+
368
+ assert_equal (result_package_cpfp ["tx-results" ][tx_sibling_3 ['wtxid' ]]['error' ], expected_error_cpfp )
330
369
331
370
332
371
@cleanup (extra_args = ["-datacarriersize=1000" , "-acceptnonstdtxn=1" ])
@@ -429,22 +468,136 @@ def test_reorg_2child_rbf(self):
429
468
self .check_mempool ([ancestor_tx ["txid" ], child_1_conflict ["txid" ], child_2 ["txid" ]])
430
469
assert_equal (node .getmempoolentry (ancestor_tx ["txid" ])["descendantcount" ], 3 )
431
470
471
+ @cleanup (extra_args = ["-acceptnonstdtxn=1" ])
472
+ def test_v3_sibling_eviction (self ):
473
+ self .log .info ("Test sibling eviction for v3" )
474
+ node = self .nodes [0 ]
475
+ tx_v3_parent = self .wallet .send_self_transfer_multi (from_node = node , num_outputs = 2 , version = 3 )
476
+ # This is the sibling to replace
477
+ tx_v3_child_1 = self .wallet .send_self_transfer (
478
+ from_node = node , utxo_to_spend = tx_v3_parent ["new_utxos" ][0 ], fee_rate = DEFAULT_FEE * 2 , version = 3
479
+ )
480
+ assert tx_v3_child_1 ["txid" ] in node .getrawmempool ()
481
+
482
+ self .log .info ("Test tx must be higher feerate than sibling to evict it" )
483
+ tx_v3_child_2_rule6 = self .wallet .create_self_transfer (
484
+ utxo_to_spend = tx_v3_parent ["new_utxos" ][1 ], fee_rate = DEFAULT_FEE , version = 3
485
+ )
486
+ rule6_str = f"insufficient fee (including sibling eviction), rejecting replacement { tx_v3_child_2_rule6 ['txid' ]} ; new feerate"
487
+ assert_raises_rpc_error (- 26 , rule6_str , node .sendrawtransaction , tx_v3_child_2_rule6 ["hex" ])
488
+ self .check_mempool ([tx_v3_parent ['txid' ], tx_v3_child_1 ['txid' ]])
489
+
490
+ self .log .info ("Test tx must meet absolute fee rules to evict sibling" )
491
+ tx_v3_child_2_rule4 = self .wallet .create_self_transfer (
492
+ utxo_to_spend = tx_v3_parent ["new_utxos" ][1 ], fee_rate = 2 * DEFAULT_FEE + Decimal ("0.00000001" ), version = 3
493
+ )
494
+ rule4_str = f"insufficient fee (including sibling eviction), rejecting replacement { tx_v3_child_2_rule4 ['txid' ]} , not enough additional fees to relay"
495
+ assert_raises_rpc_error (- 26 , rule4_str , node .sendrawtransaction , tx_v3_child_2_rule4 ["hex" ])
496
+ self .check_mempool ([tx_v3_parent ['txid' ], tx_v3_child_1 ['txid' ]])
497
+
498
+ self .log .info ("Test tx cannot cause more than 100 evictions including RBF and sibling eviction" )
499
+ # First add 4 groups of 25 transactions.
500
+ utxos_for_conflict = []
501
+ txids_v2_100 = []
502
+ for _ in range (4 ):
503
+ confirmed_utxo = self .wallet .get_utxo (confirmed_only = True )
504
+ utxos_for_conflict .append (confirmed_utxo )
505
+ # 25 is within descendant limits
506
+ chain_length = int (MAX_REPLACEMENT_CANDIDATES / 4 )
507
+ chain = self .wallet .create_self_transfer_chain (chain_length = chain_length , utxo_to_spend = confirmed_utxo )
508
+ for item in chain :
509
+ txids_v2_100 .append (item ["txid" ])
510
+ node .sendrawtransaction (item ["hex" ])
511
+ self .check_mempool (txids_v2_100 + [tx_v3_parent ["txid" ], tx_v3_child_1 ["txid" ]])
512
+
513
+ # Replacing 100 transactions is fine
514
+ tx_v3_replacement_only = self .wallet .create_self_transfer_multi (utxos_to_spend = utxos_for_conflict , fee_per_output = 4000000 )
515
+ # Override maxfeerate - it costs a lot to replace these 100 transactions.
516
+ assert node .testmempoolaccept ([tx_v3_replacement_only ["hex" ]], maxfeerate = 0 )[0 ]["allowed" ]
517
+ # Adding another one exceeds the limit.
518
+ utxos_for_conflict .append (tx_v3_parent ["new_utxos" ][1 ])
519
+ tx_v3_child_2_rule5 = self .wallet .create_self_transfer_multi (utxos_to_spend = utxos_for_conflict , fee_per_output = 4000000 , version = 3 )
520
+ rule5_str = f"too many potential replacements (including sibling eviction), rejecting replacement { tx_v3_child_2_rule5 ['txid' ]} ; too many potential replacements (101 > 100)"
521
+ assert_raises_rpc_error (- 26 , rule5_str , node .sendrawtransaction , tx_v3_child_2_rule5 ["hex" ])
522
+ self .check_mempool (txids_v2_100 + [tx_v3_parent ["txid" ], tx_v3_child_1 ["txid" ]])
523
+
524
+ self .log .info ("Test sibling eviction is successful if it meets all RBF rules" )
525
+ tx_v3_child_2 = self .wallet .create_self_transfer (
526
+ utxo_to_spend = tx_v3_parent ["new_utxos" ][1 ], fee_rate = DEFAULT_FEE * 10 , version = 3
527
+ )
528
+ node .sendrawtransaction (tx_v3_child_2 ["hex" ])
529
+ self .check_mempool (txids_v2_100 + [tx_v3_parent ["txid" ], tx_v3_child_2 ["txid" ]])
530
+
531
+ self .log .info ("Test that it's possible to do a sibling eviction and RBF at the same time" )
532
+ utxo_unrelated_conflict = self .wallet .get_utxo (confirmed_only = True )
533
+ tx_unrelated_replacee = self .wallet .send_self_transfer (from_node = node , utxo_to_spend = utxo_unrelated_conflict )
534
+ assert tx_unrelated_replacee ["txid" ] in node .getrawmempool ()
535
+
536
+ fee_to_beat_child2 = int (tx_v3_child_2 ["fee" ] * COIN )
537
+
538
+ tx_v3_child_3 = self .wallet .create_self_transfer_multi (
539
+ utxos_to_spend = [tx_v3_parent ["new_utxos" ][0 ], utxo_unrelated_conflict ], fee_per_output = fee_to_beat_child2 * 5 , version = 3
540
+ )
541
+ node .sendrawtransaction (tx_v3_child_3 ["hex" ])
542
+ self .check_mempool (txids_v2_100 + [tx_v3_parent ["txid" ], tx_v3_child_3 ["txid" ]])
543
+
544
+ @cleanup (extra_args = ["-acceptnonstdtxn=1" ])
545
+ def test_reorg_sibling_eviction_1p2c (self ):
546
+ node = self .nodes [0 ]
547
+ self .log .info ("Test that sibling eviction is not allowed when multiple siblings exist" )
548
+
549
+ tx_with_multi_children = self .wallet .send_self_transfer_multi (from_node = node , num_outputs = 3 , version = 3 , confirmed_only = True )
550
+ self .check_mempool ([tx_with_multi_children ["txid" ]])
551
+
552
+ block_to_disconnect = self .generate (node , 1 )[0 ]
553
+ self .check_mempool ([])
554
+
555
+ tx_with_sibling1 = self .wallet .send_self_transfer (from_node = node , version = 3 , utxo_to_spend = tx_with_multi_children ["new_utxos" ][0 ])
556
+ tx_with_sibling2 = self .wallet .send_self_transfer (from_node = node , version = 3 , utxo_to_spend = tx_with_multi_children ["new_utxos" ][1 ])
557
+ self .check_mempool ([tx_with_sibling1 ["txid" ], tx_with_sibling2 ["txid" ]])
558
+
559
+ # Create a reorg, bringing tx_with_multi_children back into the mempool with a descendant count of 3.
560
+ node .invalidateblock (block_to_disconnect )
561
+ self .check_mempool ([tx_with_multi_children ["txid" ], tx_with_sibling1 ["txid" ], tx_with_sibling2 ["txid" ]])
562
+ assert_equal (node .getmempoolentry (tx_with_multi_children ["txid" ])["descendantcount" ], 3 )
563
+
564
+ # Sibling eviction is not allowed because there are two siblings
565
+ tx_with_sibling3 = self .wallet .create_self_transfer (
566
+ version = 3 ,
567
+ utxo_to_spend = tx_with_multi_children ["new_utxos" ][2 ],
568
+ fee_rate = DEFAULT_FEE * 50
569
+ )
570
+ expected_error_2siblings = f"v3-rule-violation, tx { tx_with_multi_children ['txid' ]} (wtxid={ tx_with_multi_children ['wtxid' ]} ) would exceed descendant count limit"
571
+ assert_raises_rpc_error (- 26 , expected_error_2siblings , node .sendrawtransaction , tx_with_sibling3 ["hex" ])
572
+
573
+ # However, an RBF (with conflicting inputs) is possible even if the resulting cluster size exceeds 2
574
+ tx_with_sibling3_rbf = self .wallet .send_self_transfer (
575
+ from_node = node ,
576
+ version = 3 ,
577
+ utxo_to_spend = tx_with_multi_children ["new_utxos" ][0 ],
578
+ fee_rate = DEFAULT_FEE * 50
579
+ )
580
+ self .check_mempool ([tx_with_multi_children ["txid" ], tx_with_sibling3_rbf ["txid" ], tx_with_sibling2 ["txid" ]])
581
+
582
+
432
583
def run_test (self ):
433
584
self .log .info ("Generate blocks to create UTXOs" )
434
585
node = self .nodes [0 ]
435
586
self .wallet = MiniWallet (node )
436
- self .generate (self .wallet , 110 )
587
+ self .generate (self .wallet , 120 )
437
588
self .test_v3_acceptance ()
438
589
self .test_v3_replacement ()
439
590
self .test_v3_bip125 ()
440
591
self .test_v3_reorg ()
441
592
self .test_nondefault_package_limits ()
442
593
self .test_v3_ancestors_package ()
443
594
self .test_v3_ancestors_package_and_mempool ()
444
- self .test_mempool_sibling ()
595
+ self .test_sibling_eviction_package ()
445
596
self .test_v3_package_inheritance ()
446
597
self .test_v3_in_testmempoolaccept ()
447
598
self .test_reorg_2child_rbf ()
599
+ self .test_v3_sibling_eviction ()
600
+ self .test_reorg_sibling_eviction_1p2c ()
448
601
449
602
450
603
if __name__ == "__main__" :
0 commit comments