7
7
from decimal import Decimal
8
8
9
9
from test_framework .blocktools import COINBASE_MATURITY
10
- from test_framework .messages import COIN , COutPoint , CTransaction , CTxIn , CTxOut
10
+ from test_framework .messages import COIN , COutPoint , CTransaction , CTxIn , CTxOut , BIP125_SEQUENCE_NUMBER
11
11
from test_framework .script import CScript , OP_DROP
12
12
from test_framework .test_framework import BitcoinTestFramework
13
13
from test_framework .util import assert_equal , assert_raises_rpc_error , satoshi_round
14
14
from test_framework .script_util import DUMMY_P2WPKH_SCRIPT , DUMMY_2_P2WPKH_SCRIPT
15
+ from test_framework .wallet import MiniWallet
15
16
16
17
MAX_REPLACEMENT_LIMIT = 100
17
18
19
+
18
20
def txToHex (tx ):
19
21
return tx .serialize ().hex ()
20
22
23
+
21
24
def make_utxo (node , amount , confirmed = True , scriptPubKey = DUMMY_P2WPKH_SCRIPT ):
22
25
"""Create a txout with a given amount and scriptPubKey
23
26
@@ -26,12 +29,12 @@ def make_utxo(node, amount, confirmed=True, scriptPubKey=DUMMY_P2WPKH_SCRIPT):
26
29
confirmed - txouts created will be confirmed in the blockchain;
27
30
unconfirmed otherwise.
28
31
"""
29
- fee = 1 * COIN
30
- while node .getbalance () < satoshi_round ((amount + fee )/ COIN ):
32
+ fee = 1 * COIN
33
+ while node .getbalance () < satoshi_round ((amount + fee ) / COIN ):
31
34
node .generate (COINBASE_MATURITY )
32
35
33
36
new_addr = node .getnewaddress ()
34
- txid = node .sendtoaddress (new_addr , satoshi_round ((amount + fee )/ COIN ))
37
+ txid = node .sendtoaddress (new_addr , satoshi_round ((amount + fee ) / COIN ))
35
38
tx1 = node .getrawtransaction (txid , 1 )
36
39
txid = int (txid , 16 )
37
40
i , _ = next (filter (lambda vout : new_addr == vout [1 ]['scriptPubKey' ]['address' ], enumerate (tx1 ['vout' ])))
@@ -78,10 +81,7 @@ def skip_test_if_missing_module(self):
78
81
self .skip_if_no_wallet ()
79
82
80
83
def run_test (self ):
81
- # Leave IBD
82
- self .nodes [0 ].generate (1 )
83
-
84
- make_utxo (self .nodes [0 ], 1 * COIN )
84
+ make_utxo (self .nodes [0 ], 1 * COIN )
85
85
86
86
# Ensure nodes are synced
87
87
self .sync_all ()
@@ -123,7 +123,7 @@ def run_test(self):
123
123
124
124
def test_simple_doublespend (self ):
125
125
"""Simple doublespend"""
126
- tx0_outpoint = make_utxo (self .nodes [0 ], int (1.1 * COIN ))
126
+ tx0_outpoint = make_utxo (self .nodes [0 ], int (1.1 * COIN ))
127
127
128
128
# make_utxo may have generated a bunch of blocks, so we need to sync
129
129
# before we can spend the coins generated, or else the resulting
@@ -165,14 +165,14 @@ def test_simple_doublespend(self):
165
165
def test_doublespend_chain (self ):
166
166
"""Doublespend of a long chain"""
167
167
168
- initial_nValue = 50 * COIN
168
+ initial_nValue = 50 * COIN
169
169
tx0_outpoint = make_utxo (self .nodes [0 ], initial_nValue )
170
170
171
171
prevout = tx0_outpoint
172
172
remaining_value = initial_nValue
173
173
chain_txids = []
174
- while remaining_value > 10 * COIN :
175
- remaining_value -= 1 * COIN
174
+ while remaining_value > 10 * COIN :
175
+ remaining_value -= 1 * COIN
176
176
tx = CTransaction ()
177
177
tx .vin = [CTxIn (prevout , nSequence = 0 )]
178
178
tx .vout = [CTxOut (remaining_value , CScript ([1 , OP_DROP ] * 15 + [1 ]))]
@@ -205,10 +205,10 @@ def test_doublespend_chain(self):
205
205
def test_doublespend_tree (self ):
206
206
"""Doublespend of a big tree of transactions"""
207
207
208
- initial_nValue = 50 * COIN
208
+ initial_nValue = 50 * COIN
209
209
tx0_outpoint = make_utxo (self .nodes [0 ], initial_nValue )
210
210
211
- def branch (prevout , initial_value , max_txs , tree_width = 5 , fee = 0.0001 * COIN , _total_txs = None ):
211
+ def branch (prevout , initial_value , max_txs , tree_width = 5 , fee = 0.0001 * COIN , _total_txs = None ):
212
212
if _total_txs is None :
213
213
_total_txs = [0 ]
214
214
if _total_txs [0 ] >= max_txs :
@@ -239,7 +239,7 @@ def branch(prevout, initial_value, max_txs, tree_width=5, fee=0.0001*COIN, _tota
239
239
_total_txs = _total_txs ):
240
240
yield x
241
241
242
- fee = int (0.0001 * COIN )
242
+ fee = int (0.0001 * COIN )
243
243
n = MAX_REPLACEMENT_LIMIT
244
244
tree_txs = list (branch (tx0_outpoint , initial_nValue , n , fee = fee ))
245
245
assert_equal (len (tree_txs ), n )
@@ -267,8 +267,8 @@ def branch(prevout, initial_value, max_txs, tree_width=5, fee=0.0001*COIN, _tota
267
267
268
268
# Try again, but with more total transactions than the "max txs
269
269
# double-spent at once" anti-DoS limit.
270
- for n in (MAX_REPLACEMENT_LIMIT + 1 , MAX_REPLACEMENT_LIMIT * 2 ):
271
- fee = int (0.0001 * COIN )
270
+ for n in (MAX_REPLACEMENT_LIMIT + 1 , MAX_REPLACEMENT_LIMIT * 2 ):
271
+ fee = int (0.0001 * COIN )
272
272
tx0_outpoint = make_utxo (self .nodes [0 ], initial_nValue )
273
273
tree_txs = list (branch (tx0_outpoint , initial_nValue , n , fee = fee ))
274
274
assert_equal (len (tree_txs ), n )
@@ -286,7 +286,7 @@ def branch(prevout, initial_value, max_txs, tree_width=5, fee=0.0001*COIN, _tota
286
286
287
287
def test_replacement_feeperkb (self ):
288
288
"""Replacement requires fee-per-KB to be higher"""
289
- tx0_outpoint = make_utxo (self .nodes [0 ], int (1.1 * COIN ))
289
+ tx0_outpoint = make_utxo (self .nodes [0 ], int (1.1 * COIN ))
290
290
291
291
tx1a = CTransaction ()
292
292
tx1a .vin = [CTxIn (tx0_outpoint , nSequence = 0 )]
@@ -298,16 +298,16 @@ def test_replacement_feeperkb(self):
298
298
# rejected.
299
299
tx1b = CTransaction ()
300
300
tx1b .vin = [CTxIn (tx0_outpoint , nSequence = 0 )]
301
- tx1b .vout = [CTxOut (int (0.001 * COIN ), CScript ([b'a' * 999000 ]))]
301
+ tx1b .vout = [CTxOut (int (0.001 * COIN ), CScript ([b'a' * 999000 ]))]
302
302
tx1b_hex = txToHex (tx1b )
303
303
304
304
# This will raise an exception due to insufficient fee
305
305
assert_raises_rpc_error (- 26 , "insufficient fee" , self .nodes [0 ].sendrawtransaction , tx1b_hex , 0 )
306
306
307
307
def test_spends_of_conflicting_outputs (self ):
308
308
"""Replacements that spend conflicting tx outputs are rejected"""
309
- utxo1 = make_utxo (self .nodes [0 ], int (1.2 * COIN ))
310
- utxo2 = make_utxo (self .nodes [0 ], 3 * COIN )
309
+ utxo1 = make_utxo (self .nodes [0 ], int (1.2 * COIN ))
310
+ utxo2 = make_utxo (self .nodes [0 ], 3 * COIN )
311
311
312
312
tx1a = CTransaction ()
313
313
tx1a .vin = [CTxIn (utxo1 , nSequence = 0 )]
@@ -346,8 +346,8 @@ def test_spends_of_conflicting_outputs(self):
346
346
347
347
def test_new_unconfirmed_inputs (self ):
348
348
"""Replacements that add new unconfirmed inputs are rejected"""
349
- confirmed_utxo = make_utxo (self .nodes [0 ], int (1.1 * COIN ))
350
- unconfirmed_utxo = make_utxo (self .nodes [0 ], int (0.1 * COIN ), False )
349
+ confirmed_utxo = make_utxo (self .nodes [0 ], int (1.1 * COIN ))
350
+ unconfirmed_utxo = make_utxo (self .nodes [0 ], int (0.1 * COIN ), False )
351
351
352
352
tx1 = CTransaction ()
353
353
tx1 .vin = [CTxIn (confirmed_utxo )]
@@ -369,13 +369,13 @@ def test_too_many_replacements(self):
369
369
# transactions
370
370
371
371
# Start by creating a single transaction with many outputs
372
- initial_nValue = 10 * COIN
372
+ initial_nValue = 10 * COIN
373
373
utxo = make_utxo (self .nodes [0 ], initial_nValue )
374
- fee = int (0.0001 * COIN )
375
- split_value = int ((initial_nValue - fee )/ (MAX_REPLACEMENT_LIMIT + 1 ))
374
+ fee = int (0.0001 * COIN )
375
+ split_value = int ((initial_nValue - fee ) / (MAX_REPLACEMENT_LIMIT + 1 ))
376
376
377
377
outputs = []
378
- for _ in range (MAX_REPLACEMENT_LIMIT + 1 ):
378
+ for _ in range (MAX_REPLACEMENT_LIMIT + 1 ):
379
379
outputs .append (CTxOut (split_value , CScript ([1 ])))
380
380
381
381
splitting_tx = CTransaction ()
@@ -387,7 +387,7 @@ def test_too_many_replacements(self):
387
387
txid = int (txid , 16 )
388
388
389
389
# Now spend each of those outputs individually
390
- for i in range (MAX_REPLACEMENT_LIMIT + 1 ):
390
+ for i in range (MAX_REPLACEMENT_LIMIT + 1 ):
391
391
tx_i = CTransaction ()
392
392
tx_i .vin = [CTxIn (COutPoint (txid , i ), nSequence = 0 )]
393
393
tx_i .vout = [CTxOut (split_value - fee , DUMMY_P2WPKH_SCRIPT )]
@@ -397,9 +397,9 @@ def test_too_many_replacements(self):
397
397
# Now create doublespend of the whole lot; should fail.
398
398
# Need a big enough fee to cover all spending transactions and have
399
399
# a higher fee rate
400
- double_spend_value = (split_value - 100 * fee )* (MAX_REPLACEMENT_LIMIT + 1 )
400
+ double_spend_value = (split_value - 100 * fee ) * (MAX_REPLACEMENT_LIMIT + 1 )
401
401
inputs = []
402
- for i in range (MAX_REPLACEMENT_LIMIT + 1 ):
402
+ for i in range (MAX_REPLACEMENT_LIMIT + 1 ):
403
403
inputs .append (CTxIn (COutPoint (txid , i ), nSequence = 0 ))
404
404
double_tx = CTransaction ()
405
405
double_tx .vin = inputs
@@ -418,7 +418,7 @@ def test_too_many_replacements(self):
418
418
419
419
def test_opt_in (self ):
420
420
"""Replacing should only work if orig tx opted in"""
421
- tx0_outpoint = make_utxo (self .nodes [0 ], int (1.1 * COIN ))
421
+ tx0_outpoint = make_utxo (self .nodes [0 ], int (1.1 * COIN ))
422
422
423
423
# Create a non-opting in transaction
424
424
tx1a = CTransaction ()
@@ -439,7 +439,7 @@ def test_opt_in(self):
439
439
# This will raise an exception
440
440
assert_raises_rpc_error (- 26 , "txn-mempool-conflict" , self .nodes [0 ].sendrawtransaction , tx1b_hex , 0 )
441
441
442
- tx1_outpoint = make_utxo (self .nodes [0 ], int (1.1 * COIN ))
442
+ tx1_outpoint = make_utxo (self .nodes [0 ], int (1.1 * COIN ))
443
443
444
444
# Create a different non-opting in transaction
445
445
tx2a = CTransaction ()
@@ -467,7 +467,7 @@ def test_opt_in(self):
467
467
tx3a = CTransaction ()
468
468
tx3a .vin = [CTxIn (COutPoint (tx1a_txid , 0 ), nSequence = 0xffffffff ),
469
469
CTxIn (COutPoint (tx2a_txid , 0 ), nSequence = 0xfffffffd )]
470
- tx3a .vout = [CTxOut (int (0.9 * COIN ), CScript ([b'c' ])), CTxOut (int (0.9 * COIN ), CScript ([b'd' ]))]
470
+ tx3a .vout = [CTxOut (int (0.9 * COIN ), CScript ([b'c' ])), CTxOut (int (0.9 * COIN ), CScript ([b'd' ]))]
471
471
tx3a_hex = txToHex (tx3a )
472
472
473
473
tx3a_txid = self .nodes [0 ].sendrawtransaction (tx3a_hex , 0 )
@@ -495,7 +495,7 @@ def test_prioritised_transactions(self):
495
495
# correctly used by replacement logic
496
496
497
497
# 1. Check that feeperkb uses modified fees
498
- tx0_outpoint = make_utxo (self .nodes [0 ], int (1.1 * COIN ))
498
+ tx0_outpoint = make_utxo (self .nodes [0 ], int (1.1 * COIN ))
499
499
500
500
tx1a = CTransaction ()
501
501
tx1a .vin = [CTxIn (tx0_outpoint , nSequence = 0 )]
@@ -506,22 +506,22 @@ def test_prioritised_transactions(self):
506
506
# Higher fee, but the actual fee per KB is much lower.
507
507
tx1b = CTransaction ()
508
508
tx1b .vin = [CTxIn (tx0_outpoint , nSequence = 0 )]
509
- tx1b .vout = [CTxOut (int (0.001 * COIN ), CScript ([b'a' * 740000 ]))]
509
+ tx1b .vout = [CTxOut (int (0.001 * COIN ), CScript ([b'a' * 740000 ]))]
510
510
tx1b_hex = txToHex (tx1b )
511
511
512
512
# Verify tx1b cannot replace tx1a.
513
513
assert_raises_rpc_error (- 26 , "insufficient fee" , self .nodes [0 ].sendrawtransaction , tx1b_hex , 0 )
514
514
515
515
# Use prioritisetransaction to set tx1a's fee to 0.
516
- self .nodes [0 ].prioritisetransaction (txid = tx1a_txid , fee_delta = int (- 0.1 * COIN ))
516
+ self .nodes [0 ].prioritisetransaction (txid = tx1a_txid , fee_delta = int (- 0.1 * COIN ))
517
517
518
518
# Now tx1b should be able to replace tx1a
519
519
tx1b_txid = self .nodes [0 ].sendrawtransaction (tx1b_hex , 0 )
520
520
521
521
assert tx1b_txid in self .nodes [0 ].getrawmempool ()
522
522
523
523
# 2. Check that absolute fee checks use modified fee.
524
- tx1_outpoint = make_utxo (self .nodes [0 ], int (1.1 * COIN ))
524
+ tx1_outpoint = make_utxo (self .nodes [0 ], int (1.1 * COIN ))
525
525
526
526
tx2a = CTransaction ()
527
527
tx2a .vin = [CTxIn (tx1_outpoint , nSequence = 0 )]
@@ -540,7 +540,7 @@ def test_prioritised_transactions(self):
540
540
assert_raises_rpc_error (- 26 , "insufficient fee" , self .nodes [0 ].sendrawtransaction , tx2b_hex , 0 )
541
541
542
542
# Now prioritise tx2b to have a higher modified fee
543
- self .nodes [0 ].prioritisetransaction (txid = tx2b .hash , fee_delta = int (0.1 * COIN ))
543
+ self .nodes [0 ].prioritisetransaction (txid = tx2b .hash , fee_delta = int (0.1 * COIN ))
544
544
545
545
# tx2b should now be accepted
546
546
tx2b_txid = self .nodes [0 ].sendrawtransaction (tx2b_hex , 0 )
@@ -550,86 +550,80 @@ def test_prioritised_transactions(self):
550
550
def test_rpc (self ):
551
551
us0 = self .nodes [0 ].listunspent ()[0 ]
552
552
ins = [us0 ]
553
- outs = {self .nodes [0 ].getnewaddress () : Decimal (1.0000000 )}
553
+ outs = {self .nodes [0 ].getnewaddress (): Decimal (1.0000000 )}
554
554
rawtx0 = self .nodes [0 ].createrawtransaction (ins , outs , 0 , True )
555
555
rawtx1 = self .nodes [0 ].createrawtransaction (ins , outs , 0 , False )
556
- json0 = self .nodes [0 ].decoderawtransaction (rawtx0 )
557
- json1 = self .nodes [0 ].decoderawtransaction (rawtx1 )
556
+ json0 = self .nodes [0 ].decoderawtransaction (rawtx0 )
557
+ json1 = self .nodes [0 ].decoderawtransaction (rawtx1 )
558
558
assert_equal (json0 ["vin" ][0 ]["sequence" ], 4294967293 )
559
559
assert_equal (json1 ["vin" ][0 ]["sequence" ], 4294967295 )
560
560
561
561
rawtx2 = self .nodes [0 ].createrawtransaction ([], outs )
562
562
frawtx2a = self .nodes [0 ].fundrawtransaction (rawtx2 , {"replaceable" : True })
563
563
frawtx2b = self .nodes [0 ].fundrawtransaction (rawtx2 , {"replaceable" : False })
564
564
565
- json0 = self .nodes [0 ].decoderawtransaction (frawtx2a ['hex' ])
566
- json1 = self .nodes [0 ].decoderawtransaction (frawtx2b ['hex' ])
565
+ json0 = self .nodes [0 ].decoderawtransaction (frawtx2a ['hex' ])
566
+ json1 = self .nodes [0 ].decoderawtransaction (frawtx2b ['hex' ])
567
567
assert_equal (json0 ["vin" ][0 ]["sequence" ], 4294967293 )
568
568
assert_equal (json1 ["vin" ][0 ]["sequence" ], 4294967294 )
569
569
570
570
def test_no_inherited_signaling (self ):
571
- # Send tx from which to conflict outputs later
572
- base_txid = self .nodes [0 ].sendtoaddress (self .nodes [0 ].getnewaddress (), Decimal ("10" ))
573
- self .nodes [0 ].generate (1 )
574
- self .sync_blocks ()
571
+ wallet = MiniWallet (self .nodes [0 ])
572
+ wallet .scan_blocks (start = 76 , num = 1 )
573
+ confirmed_utxo = wallet .get_utxo ()
575
574
576
575
# Create an explicitly opt-in parent transaction
577
- optin_parent_tx = self .nodes [0 ].createrawtransaction ([{
578
- 'txid' : base_txid ,
579
- 'vout' : 0 ,
580
- "sequence" : 0xfffffffd ,
581
- }], {self .nodes [0 ].getnewaddress (): Decimal ("9.99998" )})
582
-
583
- optin_parent_tx = self .nodes [0 ].signrawtransactionwithwallet (optin_parent_tx )
584
-
585
- # Broadcast parent tx
586
- optin_parent_txid = self .nodes [0 ].sendrawtransaction (hexstring = optin_parent_tx ["hex" ], maxfeerate = 0 )
587
- assert optin_parent_txid in self .nodes [0 ].getrawmempool ()
588
-
589
- replacement_parent_tx = self .nodes [0 ].createrawtransaction ([{
590
- 'txid' : base_txid ,
591
- 'vout' : 0 ,
592
- "sequence" : 0xfffffffd ,
593
- }], {self .nodes [0 ].getnewaddress (): Decimal ("9.90000" )})
594
-
595
- replacement_parent_tx = self .nodes [0 ].signrawtransactionwithwallet (replacement_parent_tx )
576
+ optin_parent_tx = wallet .send_self_transfer (
577
+ from_node = self .nodes [0 ],
578
+ utxo_to_spend = confirmed_utxo ,
579
+ sequence = BIP125_SEQUENCE_NUMBER ,
580
+ fee_rate = Decimal ('0.01' ),
581
+ )
582
+ assert_equal (True , self .nodes [0 ].getmempoolentry (optin_parent_tx ['txid' ])['bip125-replaceable' ])
583
+
584
+ replacement_parent_tx = wallet .create_self_transfer (
585
+ from_node = self .nodes [0 ],
586
+ utxo_to_spend = confirmed_utxo ,
587
+ sequence = BIP125_SEQUENCE_NUMBER ,
588
+ fee_rate = Decimal ('0.02' ),
589
+ )
596
590
597
591
# Test if parent tx can be replaced.
598
- res = self .nodes [0 ].testmempoolaccept (rawtxs = [replacement_parent_tx ['hex' ]], maxfeerate = 0 )[0 ]
592
+ res = self .nodes [0 ].testmempoolaccept (rawtxs = [replacement_parent_tx ['hex' ]])[0 ]
599
593
600
594
# Parent can be replaced.
601
595
assert_equal (res ['allowed' ], True )
602
596
603
597
# Create an opt-out child tx spending the opt-in parent
604
- optout_child_tx = self .nodes [0 ].createrawtransaction ([{
605
- 'txid' : optin_parent_txid ,
606
- 'vout' : 0 ,
607
- "sequence" : 0xffffffff ,
608
- }], {self .nodes [0 ].getnewaddress (): Decimal ("9.99990" )})
609
-
610
- optout_child_tx = self .nodes [0 ].signrawtransactionwithwallet (optout_child_tx )
611
-
612
- # Broadcast child tx
613
- optout_child_txid = self .nodes [0 ].sendrawtransaction (hexstring = optout_child_tx ["hex" ], maxfeerate = 0 )
614
- assert optout_child_txid in self .nodes [0 ].getrawmempool ()
615
-
616
- replacement_child_tx = self .nodes [0 ].createrawtransaction ([{
617
- 'txid' : optin_parent_txid ,
618
- 'vout' : 0 ,
619
- "sequence" : 0xffffffff ,
620
- }], {self .nodes [0 ].getnewaddress (): Decimal ("9.00000" )})
621
-
622
- replacement_child_tx = self .nodes [0 ].signrawtransactionwithwallet (replacement_child_tx )
598
+ parent_utxo = wallet .get_utxo (txid = optin_parent_tx ['txid' ])
599
+ optout_child_tx = wallet .send_self_transfer (
600
+ from_node = self .nodes [0 ],
601
+ utxo_to_spend = parent_utxo ,
602
+ sequence = 0xffffffff ,
603
+ fee_rate = Decimal ('0.01' ),
604
+ )
605
+
606
+ # Reports true due to inheritance
607
+ assert_equal (True , self .nodes [0 ].getmempoolentry (optout_child_tx ['txid' ])['bip125-replaceable' ])
608
+
609
+ replacement_child_tx = wallet .create_self_transfer (
610
+ from_node = self .nodes [0 ],
611
+ utxo_to_spend = parent_utxo ,
612
+ sequence = 0xffffffff ,
613
+ fee_rate = Decimal ('0.02' ),
614
+ mempool_valid = False ,
615
+ )
623
616
624
617
# Broadcast replacement child tx
625
618
# BIP 125 :
626
619
# 1. The original transactions signal replaceability explicitly or through inheritance as described in the above
627
620
# Summary section.
628
- # The original transaction (`optout_child_tx`) doesn't signal RBF but its parent (`optin_parent_txid `) does.
621
+ # The original transaction (`optout_child_tx`) doesn't signal RBF but its parent (`optin_parent_tx `) does.
629
622
# The replacement transaction (`replacement_child_tx`) should be able to replace the original transaction.
630
623
# See CVE-2021-31876 for further explanations.
631
- assert optin_parent_txid in self .nodes [0 ].getrawmempool ( )
624
+ assert_equal ( True , self .nodes [0 ].getmempoolentry ( optin_parent_tx [ 'txid' ])[ 'bip125-replaceable' ] )
632
625
assert_raises_rpc_error (- 26 , 'txn-mempool-conflict' , self .nodes [0 ].sendrawtransaction , replacement_child_tx ["hex" ], 0 )
633
626
627
+
634
628
if __name__ == '__main__' :
635
629
ReplaceByFeeTest ().main ()
0 commit comments