Skip to content

Commit 526785f

Browse files
author
Claude Code
committed
Merge bitcoin#25986: test: refactor RPCPackagesTest to use MiniWallet
Backport of bitcoin#25986 Original commit: 00c3236 Refactors RPCPackagesTest to use MiniWallet for transaction creation, and adds create_self_transfer_chain method to MiniWallet class. Removes the no-longer-needed make_chain, create_child_with_parents, and create_raw_chain functions from wallet.py. Note: Removed wtxid references as Dash does not support SegWit. Also excluded test_rbf and test_submitpackage tests as they depend on BIP125 RBF and package relay features.
1 parent e3d61f2 commit 526785f

File tree

2 files changed

+98
-153
lines changed

2 files changed

+98
-153
lines changed

test/functional/rpc_packages.py

Lines changed: 66 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -7,24 +7,19 @@
77
from decimal import Decimal
88
import random
99

10-
from test_framework.address import ADDRESS_BCRT1_P2SH_OP_TRUE
11-
from test_framework.test_framework import BitcoinTestFramework
10+
from test_framework.blocktools import COINBASE_MATURITY
1211
from test_framework.messages import (
1312
tx_from_hex,
1413
)
15-
from test_framework.script import (
16-
CScript,
17-
OP_TRUE,
18-
)
14+
from test_framework.test_framework import BitcoinTestFramework
1915
from test_framework.util import (
2016
assert_equal,
2117
)
2218
from test_framework.wallet import (
23-
create_child_with_parents,
24-
create_raw_chain,
25-
make_chain,
19+
MiniWallet,
2620
)
2721

22+
2823
class RPCPackagesTest(BitcoinTestFramework):
2924
def set_test_params(self):
3025
self.num_nodes = 1
@@ -42,52 +37,53 @@ def assert_testres_equal(self, package_hex, testres_expected):
4237
assert_equal(shuffled_testres, self.nodes[0].testmempoolaccept(shuffled_package))
4338

4439
def run_test(self):
45-
self.log.info("Generate blocks to create UTXOs")
4640
node = self.nodes[0]
47-
self.privkeys = [node.get_deterministic_priv_key().key]
48-
self.address = node.get_deterministic_priv_key().address
49-
self.coins = []
50-
# The last 100 coinbase transactions are premature
51-
for b in self.generatetoaddress(node, 200, self.address)[:100]:
52-
coinbase = node.getblock(blockhash=b, verbosity=2)["tx"][0]
53-
self.coins.append({
41+
42+
# get an UTXO that requires signature to be spent
43+
deterministic_address = node.get_deterministic_priv_key().address
44+
blockhash = self.generatetoaddress(node, 1, deterministic_address)[0]
45+
coinbase = node.getblock(blockhash=blockhash, verbosity=2)["tx"][0]
46+
coin = {
5447
"txid": coinbase["txid"],
5548
"amount": coinbase["vout"][0]["value"],
5649
"scriptPubKey": coinbase["vout"][0]["scriptPubKey"],
57-
})
50+
"vout": 0,
51+
"height": 0
52+
}
53+
54+
self.wallet = MiniWallet(self.nodes[0])
55+
self.generate(self.wallet, COINBASE_MATURITY + 100) # blocks generated for inputs
5856

57+
self.log.info("Create some transactions")
5958
# Create some transactions that can be reused throughout the test. Never submit these to mempool.
6059
self.independent_txns_hex = []
6160
self.independent_txns_testres = []
6261
for _ in range(3):
63-
coin = self.coins.pop()
64-
rawtx = node.createrawtransaction([{"txid": coin["txid"], "vout": 0}],
65-
{self.address : coin["amount"] - Decimal("0.0001")})
66-
signedtx = node.signrawtransactionwithkey(hexstring=rawtx, privkeys=self.privkeys)
67-
assert signedtx["complete"]
68-
testres = node.testmempoolaccept([signedtx["hex"]])
62+
tx_hex = self.wallet.create_self_transfer(fee_rate=Decimal("0.0001"))["hex"]
63+
testres = self.nodes[0].testmempoolaccept([tx_hex])
6964
assert testres[0]["allowed"]
70-
self.independent_txns_hex.append(signedtx["hex"])
65+
self.independent_txns_hex.append(tx_hex)
7166
# testmempoolaccept returns a list of length one, avoid creating a 2D list
7267
self.independent_txns_testres.append(testres[0])
7368
self.independent_txns_testres_blank = [{
7469
"txid": res["txid"]} for res in self.independent_txns_testres]
7570

76-
self.test_independent()
71+
self.test_independent(coin)
7772
self.test_chain()
7873
self.test_multiple_children()
7974
self.test_multiple_parents()
8075
self.test_conflicting()
8176

8277

83-
def test_independent(self):
78+
def test_independent(self, coin):
8479
self.log.info("Test multiple independent transactions in a package")
8580
node = self.nodes[0]
8681
# For independent transactions, order doesn't matter.
8782
self.assert_testres_equal(self.independent_txns_hex, self.independent_txns_testres)
8883

8984
self.log.info("Test an otherwise valid package with an extra garbage tx appended")
90-
garbage_tx = node.createrawtransaction([{"txid": "00" * 32, "vout": 5}], {self.address: 1})
85+
address = node.get_deterministic_priv_key().address
86+
garbage_tx = node.createrawtransaction([{"txid": "00" * 32, "vout": 5}], {address: 1})
9187
tx = tx_from_hex(garbage_tx)
9288
# Only the txid is returned because validation is incomplete for the independent txns.
9389
# Package validation is atomic: if the node cannot find a UTXO for any single tx in the package,
@@ -97,9 +93,8 @@ def test_independent(self):
9793
self.assert_testres_equal(package_bad, testres_bad)
9894

9995
self.log.info("Check testmempoolaccept tells us when some transactions completed validation successfully")
100-
coin = self.coins.pop()
10196
tx_bad_sig_hex = node.createrawtransaction([{"txid": coin["txid"], "vout": 0}],
102-
{self.address : coin["amount"] - Decimal("0.0001")})
97+
{address : coin["amount"] - Decimal("0.0001")})
10398
tx_bad_sig = tx_from_hex(tx_bad_sig_hex)
10499
tx_bad_sig_hex = tx_bad_sig.serialize().hex()
105100
testres_bad_sig = node.testmempoolaccept(self.independent_txns_hex + [tx_bad_sig_hex])
@@ -113,24 +108,22 @@ def test_independent(self):
113108
}])
114109

115110
self.log.info("Check testmempoolaccept reports txns in packages that exceed max feerate")
116-
coin = self.coins.pop()
117-
tx_high_fee_raw = node.createrawtransaction([{"txid": coin["txid"], "vout": 0}],
118-
{self.address : coin["amount"] - Decimal("0.999")})
119-
tx_high_fee_signed = node.signrawtransactionwithkey(hexstring=tx_high_fee_raw, privkeys=self.privkeys)
120-
assert tx_high_fee_signed["complete"]
121-
tx_high_fee = tx_from_hex(tx_high_fee_signed["hex"])
122-
testres_high_fee = node.testmempoolaccept([tx_high_fee_signed["hex"]])
111+
tx_high_fee = self.wallet.create_self_transfer(fee=Decimal("0.999"))
112+
testres_high_fee = node.testmempoolaccept([tx_high_fee["hex"]])
123113
assert_equal(testres_high_fee, [
124-
{"txid": tx_high_fee.rehash(), "allowed": False, "reject-reason": "max-fee-exceeded"}
114+
{"txid": tx_high_fee["txid"], "allowed": False, "reject-reason": "max-fee-exceeded"}
125115
])
126-
package_high_fee = [tx_high_fee_signed["hex"]] + self.independent_txns_hex
116+
package_high_fee = [tx_high_fee["hex"]] + self.independent_txns_hex
127117
testres_package_high_fee = node.testmempoolaccept(package_high_fee)
128118
assert_equal(testres_package_high_fee, testres_high_fee + self.independent_txns_testres_blank)
129119

130120
def test_chain(self):
131121
node = self.nodes[0]
132-
first_coin = self.coins.pop()
133-
(chain_hex, chain_txns) = create_raw_chain(node, first_coin, self.address, self.privkeys)
122+
123+
chain = self.wallet.create_self_transfer_chain(chain_length=25)
124+
chain_hex = chain["chain_hex"]
125+
chain_txns = chain["chain_txns"]
126+
134127
self.log.info("Check that testmempoolaccept requires packages to be sorted by dependency")
135128
assert_equal(node.testmempoolaccept(rawtxs=chain_hex[::-1]),
136129
[{"txid": tx.rehash(), "package-error": "package-not-sorted"} for tx in chain_txns[::-1]])
@@ -152,116 +145,88 @@ def test_chain(self):
152145

153146
def test_multiple_children(self):
154147
node = self.nodes[0]
155-
156148
self.log.info("Testmempoolaccept a package in which a transaction has two children within the package")
157-
first_coin = self.coins.pop()
158-
value = (first_coin["amount"] - Decimal("0.0002")) / 2 # Deduct reasonable fee and make 2 outputs
159-
inputs = [{"txid": first_coin["txid"], "vout": 0}]
160-
outputs = [{self.address : value}, {ADDRESS_BCRT1_P2SH_OP_TRUE : value}]
161-
rawtx = node.createrawtransaction(inputs, outputs)
162-
163-
parent_signed = node.signrawtransactionwithkey(hexstring=rawtx, privkeys=self.privkeys)
164-
parent_tx = tx_from_hex(parent_signed["hex"])
165-
assert parent_signed["complete"]
166-
parent_txid = parent_tx.rehash()
167-
assert node.testmempoolaccept([parent_signed["hex"]])[0]["allowed"]
168149

169-
parent_locking_script_a = parent_tx.vout[0].scriptPubKey.hex()
170-
child_value = value - Decimal("0.0001")
150+
parent_tx = self.wallet.create_self_transfer_multi(num_outputs=2)
151+
assert node.testmempoolaccept([parent_tx["hex"]])[0]["allowed"]
171152

172153
# Child A
173-
(_, tx_child_a_hex, _, _) = make_chain(node, self.address, self.privkeys, parent_txid, child_value, 0, parent_locking_script_a)
174-
assert not node.testmempoolaccept([tx_child_a_hex])[0]["allowed"]
154+
child_a_tx = self.wallet.create_self_transfer(utxo_to_spend=parent_tx["new_utxos"][0])
155+
assert not node.testmempoolaccept([child_a_tx["hex"]])[0]["allowed"]
175156

176157
# Child B
177-
rawtx_b = node.createrawtransaction([{"txid": parent_txid, "vout": 1}], {self.address : child_value})
178-
tx_child_b = tx_from_hex(rawtx_b)
179-
tx_child_b.vin[0].scriptSig = CScript([CScript([OP_TRUE])])
180-
tx_child_b_hex = tx_child_b.serialize().hex()
181-
assert not node.testmempoolaccept([tx_child_b_hex])[0]["allowed"]
158+
child_b_tx = self.wallet.create_self_transfer(utxo_to_spend=parent_tx["new_utxos"][1])
159+
assert not node.testmempoolaccept([child_b_tx["hex"]])[0]["allowed"]
182160

183161
self.log.info("Testmempoolaccept with entire package, should work with children in either order")
184-
testres_multiple_ab = node.testmempoolaccept(rawtxs=[parent_signed["hex"], tx_child_a_hex, tx_child_b_hex])
185-
testres_multiple_ba = node.testmempoolaccept(rawtxs=[parent_signed["hex"], tx_child_b_hex, tx_child_a_hex])
162+
testres_multiple_ab = node.testmempoolaccept(rawtxs=[parent_tx["hex"], child_a_tx["hex"], child_b_tx["hex"]])
163+
testres_multiple_ba = node.testmempoolaccept(rawtxs=[parent_tx["hex"], child_b_tx["hex"], child_a_tx["hex"]])
186164
assert all([testres["allowed"] for testres in testres_multiple_ab + testres_multiple_ba])
187165

188166
testres_single = []
189167
# Test accept and then submit each one individually, which should be identical to package testaccept
190-
for rawtx in [parent_signed["hex"], tx_child_a_hex, tx_child_b_hex]:
168+
for rawtx in [parent_tx["hex"], child_a_tx["hex"], child_b_tx["hex"]]:
191169
testres = node.testmempoolaccept([rawtx])
192170
testres_single.append(testres[0])
193171
# Submit the transaction now so its child should have no problem validating
194172
node.sendrawtransaction(rawtx)
195173
assert_equal(testres_single, testres_multiple_ab)
196174

197-
198175
def test_multiple_parents(self):
199176
node = self.nodes[0]
200-
201177
self.log.info("Testmempoolaccept a package in which a transaction has multiple parents within the package")
178+
202179
for num_parents in [2, 10, 24]:
203180
# Test a package with num_parents parents and 1 child transaction.
181+
parent_coins = []
204182
package_hex = []
205-
parents_tx = []
206-
values = []
207-
parent_locking_scripts = []
183+
208184
for _ in range(num_parents):
209-
parent_coin = self.coins.pop()
210-
value = parent_coin["amount"]
211-
(tx, txhex, value, parent_locking_script) = make_chain(node, self.address, self.privkeys, parent_coin["txid"], value)
212-
package_hex.append(txhex)
213-
parents_tx.append(tx)
214-
values.append(value)
215-
parent_locking_scripts.append(parent_locking_script)
216-
child_hex = create_child_with_parents(node, self.address, self.privkeys, parents_tx, values, parent_locking_scripts)
217-
# Package accept should work with the parents in any order (as long as parents come before child)
185+
# Package accept should work with the parents in any order (as long as parents come before child)
186+
parent_tx = self.wallet.create_self_transfer()
187+
parent_coins.append(parent_tx["new_utxo"])
188+
package_hex.append(parent_tx["hex"])
189+
190+
child_tx = self.wallet.create_self_transfer_multi(utxos_to_spend=parent_coins, fee_per_output=2000)
218191
for _ in range(10):
219192
random.shuffle(package_hex)
220-
testres_multiple = node.testmempoolaccept(rawtxs=package_hex + [child_hex])
193+
testres_multiple = node.testmempoolaccept(rawtxs=package_hex + [child_tx['hex']])
221194
assert all([testres["allowed"] for testres in testres_multiple])
222195

223196
testres_single = []
224197
# Test accept and then submit each one individually, which should be identical to package testaccept
225-
for rawtx in package_hex + [child_hex]:
198+
for rawtx in package_hex + [child_tx["hex"]]:
226199
testres_single.append(node.testmempoolaccept([rawtx])[0])
227200
# Submit the transaction now so its child should have no problem validating
228201
node.sendrawtransaction(rawtx)
229202
assert_equal(testres_single, testres_multiple)
230203

231204
def test_conflicting(self):
232205
node = self.nodes[0]
233-
prevtx = self.coins.pop()
234-
inputs = [{"txid": prevtx["txid"], "vout": 0}]
235-
output1 = {node.get_deterministic_priv_key().address: 500 - 0.00125}
236-
output2 = {ADDRESS_BCRT1_P2SH_OP_TRUE: 500 - 0.00125}
206+
coin = self.wallet.get_utxo()
237207

238208
# tx1 and tx2 share the same inputs
239-
rawtx1 = node.createrawtransaction(inputs, output1)
240-
rawtx2 = node.createrawtransaction(inputs, output2)
241-
signedtx1 = node.signrawtransactionwithkey(hexstring=rawtx1, privkeys=self.privkeys)
242-
signedtx2 = node.signrawtransactionwithkey(hexstring=rawtx2, privkeys=self.privkeys)
243-
tx1 = tx_from_hex(signedtx1["hex"])
244-
tx2 = tx_from_hex(signedtx2["hex"])
245-
assert signedtx1["complete"]
246-
assert signedtx2["complete"]
209+
tx1 = self.wallet.create_self_transfer(utxo_to_spend=coin)
210+
tx2 = self.wallet.create_self_transfer(utxo_to_spend=coin)
247211

248212
# Ensure tx1 and tx2 are valid by themselves
249-
assert node.testmempoolaccept([signedtx1["hex"]])[0]["allowed"]
250-
assert node.testmempoolaccept([signedtx2["hex"]])[0]["allowed"]
213+
assert node.testmempoolaccept([tx1["hex"]])[0]["allowed"]
214+
assert node.testmempoolaccept([tx2["hex"]])[0]["allowed"]
251215

252216
self.log.info("Test duplicate transactions in the same package")
253-
testres = node.testmempoolaccept([signedtx1["hex"], signedtx1["hex"]])
217+
testres = node.testmempoolaccept([tx1["hex"], tx1["hex"]])
254218
assert_equal(testres, [
255-
{"txid": tx1.rehash(), "package-error": "conflict-in-package"},
256-
{"txid": tx1.rehash(), "package-error": "conflict-in-package"}
219+
{"txid": tx1["txid"], "package-error": "conflict-in-package"},
220+
{"txid": tx1["txid"], "package-error": "conflict-in-package"}
257221
])
258222

259223
self.log.info("Test conflicting transactions in the same package")
260-
testres = node.testmempoolaccept([signedtx1["hex"], signedtx2["hex"]])
224+
testres = node.testmempoolaccept([tx1["hex"], tx2["hex"]])
261225
assert_equal(testres, [
262-
{"txid": tx1.rehash(), "package-error": "conflict-in-package"},
263-
{"txid": tx2.rehash(), "package-error": "conflict-in-package"}
226+
{"txid": tx1["txid"], "package-error": "conflict-in-package"},
227+
{"txid": tx2["txid"], "package-error": "conflict-in-package"}
264228
])
265229

230+
266231
if __name__ == "__main__":
267232
RPCPackagesTest().main()

test/functional/test_framework/wallet.py

Lines changed: 32 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,38 @@ def sendrawtransaction(self, *, from_node, tx_hex, maxfeerate=0, **kwargs):
319319
self.scan_tx(from_node.decoderawtransaction(tx_hex))
320320
return txid
321321

322+
def create_self_transfer_chain(self, *, chain_length):
323+
"""
324+
Create a "chain" of chain_length transactions. The nth transaction in
325+
the chain is a child of the n-1th transaction and parent of the n+1th transaction.
326+
327+
Returns a dic {"chain_hex": chain_hex, "chain_txns" : chain_txns}
328+
329+
"chain_hex" is a list representing the chain's transactions in hexadecimal.
330+
"chain_txns" is a list representing the chain's transactions in the CTransaction object.
331+
"""
332+
chaintip_utxo = self.get_utxo()
333+
chain_hex = []
334+
chain_txns = []
335+
336+
for _ in range(chain_length):
337+
tx = self.create_self_transfer(utxo_to_spend=chaintip_utxo)
338+
chaintip_utxo = tx["new_utxo"]
339+
chain_hex.append(tx["hex"])
340+
chain_txns.append(tx["tx"])
341+
342+
return {"chain_hex": chain_hex, "chain_txns" : chain_txns}
343+
344+
def send_self_transfer_chain(self, *, from_node, chain_length, utxo_to_spend=None):
345+
"""Create and send a "chain" of chain_length transactions. The nth transaction in
346+
the chain is a child of the n-1th transaction and parent of the n+1th transaction.
347+
348+
Returns the txid of the last transaction."""
349+
chaintip_utxo = utxo_to_spend or self.get_utxo()
350+
for _ in range(chain_length):
351+
chaintip_utxo = self.send_self_transfer(utxo_to_spend=chaintip_utxo, from_node=from_node)["new_utxo"]
352+
return chaintip_utxo["txid"]
353+
322354

323355
def getnewdestination(address_type='legacy'):
324356
"""Generate a random destination of the specified type and return the
@@ -349,58 +381,6 @@ def address_to_scriptpubkey(address):
349381
assert False
350382

351383

352-
def make_chain(node, address, privkeys, parent_txid, parent_value, n=0, parent_locking_script=None, fee=DEFAULT_FEE):
353-
"""Build a transaction that spends parent_txid.vout[n] and produces one output with
354-
amount = parent_value with a fee deducted.
355-
Return tuple (CTransaction object, raw hex, nValue, scriptPubKey of the output created).
356-
"""
357-
inputs = [{"txid": parent_txid, "vout": n}]
358-
my_value = parent_value - fee
359-
outputs = {address : my_value}
360-
rawtx = node.createrawtransaction(inputs, outputs)
361-
prevtxs = [{
362-
"txid": parent_txid,
363-
"vout": n,
364-
"scriptPubKey": parent_locking_script,
365-
"amount": parent_value,
366-
}] if parent_locking_script else None
367-
signedtx = node.signrawtransactionwithkey(hexstring=rawtx, privkeys=privkeys, prevtxs=prevtxs)
368-
assert signedtx["complete"]
369-
tx = tx_from_hex(signedtx["hex"])
370-
return (tx, signedtx["hex"], my_value, tx.vout[0].scriptPubKey.hex())
371-
372-
def create_child_with_parents(node, address, privkeys, parents_tx, values, locking_scripts, fee=DEFAULT_FEE):
373-
"""Creates a transaction that spends the first output of each parent in parents_tx."""
374-
num_parents = len(parents_tx)
375-
total_value = sum(values)
376-
inputs = [{"txid": tx.rehash(), "vout": 0} for tx in parents_tx]
377-
outputs = {address : total_value - fee}
378-
rawtx_child = node.createrawtransaction(inputs, outputs)
379-
prevtxs = []
380-
for i in range(num_parents):
381-
prevtxs.append({"txid": parents_tx[i].rehash(), "vout": 0, "scriptPubKey": locking_scripts[i], "amount": values[i]})
382-
signedtx_child = node.signrawtransactionwithkey(hexstring=rawtx_child, privkeys=privkeys, prevtxs=prevtxs)
383-
assert signedtx_child["complete"]
384-
return signedtx_child["hex"]
385-
386-
def create_raw_chain(node, first_coin, address, privkeys, chain_length=25):
387-
"""Helper function: create a "chain" of chain_length transactions. The nth transaction in the
388-
chain is a child of the n-1th transaction and parent of the n+1th transaction.
389-
"""
390-
parent_locking_script = None
391-
txid = first_coin["txid"]
392-
chain_hex = []
393-
chain_txns = []
394-
value = first_coin["amount"]
395-
396-
for _ in range(chain_length):
397-
(tx, txhex, value, parent_locking_script) = make_chain(node, address, privkeys, txid, value, 0, parent_locking_script)
398-
txid = tx.rehash()
399-
chain_hex.append(txhex)
400-
chain_txns.append(tx)
401-
402-
return (chain_hex, chain_txns)
403-
404384
def bulk_transaction(tx, node, target_weight, privkeys, prevtxs=None):
405385
"""Pad a transaction with extra outputs until it reaches a target weight (or higher).
406386
returns CTransaction object

0 commit comments

Comments
 (0)