@@ -115,6 +115,9 @@ def run_test(self):
115
115
self .log .info ("Running test prioritised transactions..." )
116
116
self .test_prioritised_transactions ()
117
117
118
+ self .log .info ("Running test no inherited signaling..." )
119
+ self .test_no_inherited_signaling ()
120
+
118
121
self .log .info ("Passed" )
119
122
120
123
def test_simple_doublespend (self ):
@@ -563,5 +566,69 @@ def test_rpc(self):
563
566
assert_equal (json0 ["vin" ][0 ]["sequence" ], 4294967293 )
564
567
assert_equal (json1 ["vin" ][0 ]["sequence" ], 4294967294 )
565
568
569
+ def test_no_inherited_signaling (self ):
570
+ # Send tx from which to conflict outputs later
571
+ base_txid = self .nodes [0 ].sendtoaddress (self .nodes [0 ].getnewaddress (), Decimal ("10" ))
572
+ self .nodes [0 ].generate (1 )
573
+ self .sync_blocks ()
574
+
575
+ # Create an explicitly opt-in parent transaction
576
+ optin_parent_tx = self .nodes [0 ].createrawtransaction ([{
577
+ 'txid' : base_txid ,
578
+ 'vout' : 0 ,
579
+ "sequence" : 0xfffffffd ,
580
+ }], {self .nodes [0 ].getnewaddress (): Decimal ("9.99998" )})
581
+
582
+ optin_parent_tx = self .nodes [0 ].signrawtransactionwithwallet (optin_parent_tx )
583
+
584
+ # Broadcast parent tx
585
+ optin_parent_txid = self .nodes [0 ].sendrawtransaction (hexstring = optin_parent_tx ["hex" ], maxfeerate = 0 )
586
+ assert optin_parent_txid in self .nodes [0 ].getrawmempool ()
587
+
588
+ replacement_parent_tx = self .nodes [0 ].createrawtransaction ([{
589
+ 'txid' : base_txid ,
590
+ 'vout' : 0 ,
591
+ "sequence" : 0xfffffffd ,
592
+ }], {self .nodes [0 ].getnewaddress (): Decimal ("9.90000" )})
593
+
594
+ replacement_parent_tx = self .nodes [0 ].signrawtransactionwithwallet (replacement_parent_tx )
595
+
596
+ # Test if parent tx can be replaced.
597
+ res = self .nodes [0 ].testmempoolaccept (rawtxs = [replacement_parent_tx ['hex' ]], maxfeerate = 0 )[0 ]
598
+
599
+ # Parent can be replaced.
600
+ assert_equal (res ['allowed' ], True )
601
+
602
+ # Create an opt-out child tx spending the opt-in parent
603
+ optout_child_tx = self .nodes [0 ].createrawtransaction ([{
604
+ 'txid' : optin_parent_txid ,
605
+ 'vout' : 0 ,
606
+ "sequence" : 0xffffffff ,
607
+ }], {self .nodes [0 ].getnewaddress (): Decimal ("9.99990" )})
608
+
609
+ optout_child_tx = self .nodes [0 ].signrawtransactionwithwallet (optout_child_tx )
610
+
611
+ # Broadcast child tx
612
+ optout_child_txid = self .nodes [0 ].sendrawtransaction (hexstring = optout_child_tx ["hex" ], maxfeerate = 0 )
613
+ assert optout_child_txid in self .nodes [0 ].getrawmempool ()
614
+
615
+ replacement_child_tx = self .nodes [0 ].createrawtransaction ([{
616
+ 'txid' : optin_parent_txid ,
617
+ 'vout' : 0 ,
618
+ "sequence" : 0xffffffff ,
619
+ }], {self .nodes [0 ].getnewaddress (): Decimal ("9.00000" )})
620
+
621
+ replacement_child_tx = self .nodes [0 ].signrawtransactionwithwallet (replacement_child_tx )
622
+
623
+ # Broadcast replacement child tx
624
+ # BIP 125 :
625
+ # 1. The original transactions signal replaceability explicitly or through inheritance as described in the above
626
+ # Summary section.
627
+ # The original transaction (`optout_child_tx`) doesn't signal RBF but its parent (`optin_parent_txid`) does.
628
+ # The replacement transaction (`replacement_child_tx`) should be able to replace the original transaction.
629
+ # See CVE-2021-31876 for further explanations.
630
+ assert optin_parent_txid in self .nodes [0 ].getrawmempool ()
631
+ assert_raises_rpc_error (- 26 , 'txn-mempool-conflict' , self .nodes [0 ].sendrawtransaction , replacement_child_tx ["hex" ], 0 )
632
+
566
633
if __name__ == '__main__' :
567
634
ReplaceByFeeTest ().main ()
0 commit comments