Skip to content

Commit e172ea8

Browse files
author
MarcoFalke
committed
Merge bitcoin/bitcoin#22210: test: Use MiniWallet in test_no_inherited_signaling RBF test
fa7d71f test: Run pep-8 on touched test (MarcoFalke) fab7e99 test: Use MiniWallet in test_no_inherited_signaling RBF test (MarcoFalke) fab871f test: Remove unused generate() from test (MarcoFalke) faff3f3 test: Add txin.sequence option to MiniWallet (MarcoFalke) Pull request description: This comes with nice benefits: * Less code and complexity * Test can be run without wallet compiled in Also add some additional checks for `getmempoolentry` (#22209) and other cleanups 🎨 ACKs for top commit: mjdietzx: Tested ACK fa7d71f thanks for the explanations, nicely done theStack: ACK fa7d71f 🍷 Tree-SHA512: 0e9b8fe985779d8d7034d256deed627125bb374b6ae2972c461b3a220739a51061c6147ad69339bee16282f82716c7f3f8a7a89c693ceb1e47ea50709272332a
2 parents 0f47e01 + fa7d71f commit e172ea8

File tree

3 files changed

+88
-94
lines changed

3 files changed

+88
-94
lines changed

test/functional/feature_rbf.py

Lines changed: 82 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,20 @@
77
from decimal import Decimal
88

99
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
1111
from test_framework.script import CScript, OP_DROP
1212
from test_framework.test_framework import BitcoinTestFramework
1313
from test_framework.util import assert_equal, assert_raises_rpc_error, satoshi_round
1414
from test_framework.script_util import DUMMY_P2WPKH_SCRIPT, DUMMY_2_P2WPKH_SCRIPT
15+
from test_framework.wallet import MiniWallet
1516

1617
MAX_REPLACEMENT_LIMIT = 100
1718

19+
1820
def txToHex(tx):
1921
return tx.serialize().hex()
2022

23+
2124
def make_utxo(node, amount, confirmed=True, scriptPubKey=DUMMY_P2WPKH_SCRIPT):
2225
"""Create a txout with a given amount and scriptPubKey
2326
@@ -26,12 +29,12 @@ def make_utxo(node, amount, confirmed=True, scriptPubKey=DUMMY_P2WPKH_SCRIPT):
2629
confirmed - txouts created will be confirmed in the blockchain;
2730
unconfirmed otherwise.
2831
"""
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):
3134
node.generate(COINBASE_MATURITY)
3235

3336
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))
3538
tx1 = node.getrawtransaction(txid, 1)
3639
txid = int(txid, 16)
3740
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):
7881
self.skip_if_no_wallet()
7982

8083
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)
8585

8686
# Ensure nodes are synced
8787
self.sync_all()
@@ -123,7 +123,7 @@ def run_test(self):
123123

124124
def test_simple_doublespend(self):
125125
"""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))
127127

128128
# make_utxo may have generated a bunch of blocks, so we need to sync
129129
# before we can spend the coins generated, or else the resulting
@@ -165,14 +165,14 @@ def test_simple_doublespend(self):
165165
def test_doublespend_chain(self):
166166
"""Doublespend of a long chain"""
167167

168-
initial_nValue = 50*COIN
168+
initial_nValue = 50 * COIN
169169
tx0_outpoint = make_utxo(self.nodes[0], initial_nValue)
170170

171171
prevout = tx0_outpoint
172172
remaining_value = initial_nValue
173173
chain_txids = []
174-
while remaining_value > 10*COIN:
175-
remaining_value -= 1*COIN
174+
while remaining_value > 10 * COIN:
175+
remaining_value -= 1 * COIN
176176
tx = CTransaction()
177177
tx.vin = [CTxIn(prevout, nSequence=0)]
178178
tx.vout = [CTxOut(remaining_value, CScript([1, OP_DROP] * 15 + [1]))]
@@ -205,10 +205,10 @@ def test_doublespend_chain(self):
205205
def test_doublespend_tree(self):
206206
"""Doublespend of a big tree of transactions"""
207207

208-
initial_nValue = 50*COIN
208+
initial_nValue = 50 * COIN
209209
tx0_outpoint = make_utxo(self.nodes[0], initial_nValue)
210210

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):
212212
if _total_txs is None:
213213
_total_txs = [0]
214214
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
239239
_total_txs=_total_txs):
240240
yield x
241241

242-
fee = int(0.0001*COIN)
242+
fee = int(0.0001 * COIN)
243243
n = MAX_REPLACEMENT_LIMIT
244244
tree_txs = list(branch(tx0_outpoint, initial_nValue, n, fee=fee))
245245
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
267267

268268
# Try again, but with more total transactions than the "max txs
269269
# 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)
272272
tx0_outpoint = make_utxo(self.nodes[0], initial_nValue)
273273
tree_txs = list(branch(tx0_outpoint, initial_nValue, n, fee=fee))
274274
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
286286

287287
def test_replacement_feeperkb(self):
288288
"""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))
290290

291291
tx1a = CTransaction()
292292
tx1a.vin = [CTxIn(tx0_outpoint, nSequence=0)]
@@ -298,16 +298,16 @@ def test_replacement_feeperkb(self):
298298
# rejected.
299299
tx1b = CTransaction()
300300
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]))]
302302
tx1b_hex = txToHex(tx1b)
303303

304304
# This will raise an exception due to insufficient fee
305305
assert_raises_rpc_error(-26, "insufficient fee", self.nodes[0].sendrawtransaction, tx1b_hex, 0)
306306

307307
def test_spends_of_conflicting_outputs(self):
308308
"""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)
311311

312312
tx1a = CTransaction()
313313
tx1a.vin = [CTxIn(utxo1, nSequence=0)]
@@ -346,8 +346,8 @@ def test_spends_of_conflicting_outputs(self):
346346

347347
def test_new_unconfirmed_inputs(self):
348348
"""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)
351351

352352
tx1 = CTransaction()
353353
tx1.vin = [CTxIn(confirmed_utxo)]
@@ -369,13 +369,13 @@ def test_too_many_replacements(self):
369369
# transactions
370370

371371
# Start by creating a single transaction with many outputs
372-
initial_nValue = 10*COIN
372+
initial_nValue = 10 * COIN
373373
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))
376376

377377
outputs = []
378-
for _ in range(MAX_REPLACEMENT_LIMIT+1):
378+
for _ in range(MAX_REPLACEMENT_LIMIT + 1):
379379
outputs.append(CTxOut(split_value, CScript([1])))
380380

381381
splitting_tx = CTransaction()
@@ -387,7 +387,7 @@ def test_too_many_replacements(self):
387387
txid = int(txid, 16)
388388

389389
# 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):
391391
tx_i = CTransaction()
392392
tx_i.vin = [CTxIn(COutPoint(txid, i), nSequence=0)]
393393
tx_i.vout = [CTxOut(split_value - fee, DUMMY_P2WPKH_SCRIPT)]
@@ -397,9 +397,9 @@ def test_too_many_replacements(self):
397397
# Now create doublespend of the whole lot; should fail.
398398
# Need a big enough fee to cover all spending transactions and have
399399
# 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)
401401
inputs = []
402-
for i in range(MAX_REPLACEMENT_LIMIT+1):
402+
for i in range(MAX_REPLACEMENT_LIMIT + 1):
403403
inputs.append(CTxIn(COutPoint(txid, i), nSequence=0))
404404
double_tx = CTransaction()
405405
double_tx.vin = inputs
@@ -418,7 +418,7 @@ def test_too_many_replacements(self):
418418

419419
def test_opt_in(self):
420420
"""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))
422422

423423
# Create a non-opting in transaction
424424
tx1a = CTransaction()
@@ -439,7 +439,7 @@ def test_opt_in(self):
439439
# This will raise an exception
440440
assert_raises_rpc_error(-26, "txn-mempool-conflict", self.nodes[0].sendrawtransaction, tx1b_hex, 0)
441441

442-
tx1_outpoint = make_utxo(self.nodes[0], int(1.1*COIN))
442+
tx1_outpoint = make_utxo(self.nodes[0], int(1.1 * COIN))
443443

444444
# Create a different non-opting in transaction
445445
tx2a = CTransaction()
@@ -467,7 +467,7 @@ def test_opt_in(self):
467467
tx3a = CTransaction()
468468
tx3a.vin = [CTxIn(COutPoint(tx1a_txid, 0), nSequence=0xffffffff),
469469
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']))]
471471
tx3a_hex = txToHex(tx3a)
472472

473473
tx3a_txid = self.nodes[0].sendrawtransaction(tx3a_hex, 0)
@@ -495,7 +495,7 @@ def test_prioritised_transactions(self):
495495
# correctly used by replacement logic
496496

497497
# 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))
499499

500500
tx1a = CTransaction()
501501
tx1a.vin = [CTxIn(tx0_outpoint, nSequence=0)]
@@ -506,22 +506,22 @@ def test_prioritised_transactions(self):
506506
# Higher fee, but the actual fee per KB is much lower.
507507
tx1b = CTransaction()
508508
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]))]
510510
tx1b_hex = txToHex(tx1b)
511511

512512
# Verify tx1b cannot replace tx1a.
513513
assert_raises_rpc_error(-26, "insufficient fee", self.nodes[0].sendrawtransaction, tx1b_hex, 0)
514514

515515
# 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))
517517

518518
# Now tx1b should be able to replace tx1a
519519
tx1b_txid = self.nodes[0].sendrawtransaction(tx1b_hex, 0)
520520

521521
assert tx1b_txid in self.nodes[0].getrawmempool()
522522

523523
# 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))
525525

526526
tx2a = CTransaction()
527527
tx2a.vin = [CTxIn(tx1_outpoint, nSequence=0)]
@@ -540,7 +540,7 @@ def test_prioritised_transactions(self):
540540
assert_raises_rpc_error(-26, "insufficient fee", self.nodes[0].sendrawtransaction, tx2b_hex, 0)
541541

542542
# 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))
544544

545545
# tx2b should now be accepted
546546
tx2b_txid = self.nodes[0].sendrawtransaction(tx2b_hex, 0)
@@ -550,86 +550,80 @@ def test_prioritised_transactions(self):
550550
def test_rpc(self):
551551
us0 = self.nodes[0].listunspent()[0]
552552
ins = [us0]
553-
outs = {self.nodes[0].getnewaddress() : Decimal(1.0000000)}
553+
outs = {self.nodes[0].getnewaddress(): Decimal(1.0000000)}
554554
rawtx0 = self.nodes[0].createrawtransaction(ins, outs, 0, True)
555555
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)
558558
assert_equal(json0["vin"][0]["sequence"], 4294967293)
559559
assert_equal(json1["vin"][0]["sequence"], 4294967295)
560560

561561
rawtx2 = self.nodes[0].createrawtransaction([], outs)
562562
frawtx2a = self.nodes[0].fundrawtransaction(rawtx2, {"replaceable": True})
563563
frawtx2b = self.nodes[0].fundrawtransaction(rawtx2, {"replaceable": False})
564564

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'])
567567
assert_equal(json0["vin"][0]["sequence"], 4294967293)
568568
assert_equal(json1["vin"][0]["sequence"], 4294967294)
569569

570570
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()
575574

576575
# 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+
)
596590

597591
# 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]
599593

600594
# Parent can be replaced.
601595
assert_equal(res['allowed'], True)
602596

603597
# 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+
)
623616

624617
# Broadcast replacement child tx
625618
# BIP 125 :
626619
# 1. The original transactions signal replaceability explicitly or through inheritance as described in the above
627620
# 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.
629622
# The replacement transaction (`replacement_child_tx`) should be able to replace the original transaction.
630623
# 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'])
632625
assert_raises_rpc_error(-26, 'txn-mempool-conflict', self.nodes[0].sendrawtransaction, replacement_child_tx["hex"], 0)
633626

627+
634628
if __name__ == '__main__':
635629
ReplaceByFeeTest().main()

test/functional/test_framework/messages.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
COIN = 100000000 # 1 btc in satoshis
4040
MAX_MONEY = 21000000 * COIN
4141

42-
BIP125_SEQUENCE_NUMBER = 0xfffffffd # Sequence number that is BIP 125 opt-in and BIP 68-opt-out
42+
BIP125_SEQUENCE_NUMBER = 0xfffffffd # Sequence number that is rbf-opt-in (BIP 125) and csv-opt-out (BIP 68)
4343

4444
MAX_PROTOCOL_MESSAGE_LENGTH = 4000000 # Maximum length of incoming protocol messages
4545
MAX_HEADERS_RESULTS = 2000 # Number of headers sent in one getheaders result

0 commit comments

Comments
 (0)