Skip to content

Commit c826102

Browse files
author
MacroFake
committed
Merge bitcoin/bitcoin#25445: test: Return new_utxo from create_self_transfer in MiniWallet
fa83c0c test: Remove unused call to generate in rpc_mempool_info (MacroFake) fa13375 test: Sync MiniWallet utxo state after each generate call (MacroFake) dddd7c4 test: Drop spent utxos in MiniWallet scan_tx (MacroFake) fa04ff6 test: Return new_utxos from create_self_transfer_multi in MiniWallet (MacroFake) fa34e44 test: Return new_utxo from create_self_transfer in MiniWallet (MacroFake) Pull request description: I need this for some stuff, but it also makes sense on its own to: * unify the flow with a private `_create_utxo` helper * simplify the flow by giving the caller ownership of the utxo right away ACKs for top commit: kouloumos: re-ACK fa83c0c 🚀 Tree-SHA512: 381f0e814864ba207363a56859a6c0519e4a86d0562927f16a610a5b706c9fc942c1b5e93388cda0fa0b3cacd58f55bc2ffcc60355858a580263e5bef33c2f4b
2 parents 50a3921 + fa83c0c commit c826102

File tree

6 files changed

+52
-40
lines changed

6 files changed

+52
-40
lines changed

test/functional/feature_dbcrash.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -192,14 +192,12 @@ def generate_small_transactions(self, node, count, utxo_list):
192192
# Sanity check -- if we chose inputs that are too small, skip
193193
continue
194194

195-
tx = self.wallet.create_self_transfer_multi(
195+
self.wallet.send_self_transfer_multi(
196196
from_node=node,
197197
utxos_to_spend=utxos_to_spend,
198198
num_outputs=3,
199-
fee_per_output=FEE // 3)
200-
201-
# Send the transaction to get into the mempool (skip fee-checks to run faster)
202-
node.sendrawtransaction(hexstring=tx.serialize().hex(), maxfeerate=0)
199+
fee_per_output=FEE // 3,
200+
)
203201
num_transactions += 1
204202

205203
def run_test(self):

test/functional/feature_fee_estimation.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,8 @@ def small_txpuzzle_randfee(
5252
raise RuntimeError(f"Insufficient funds: need {amount + fee}, have {total_in}")
5353
tx = wallet.create_self_transfer_multi(
5454
utxos_to_spend=utxos_to_spend,
55-
fee_per_output=0)
55+
fee_per_output=0,
56+
)["tx"]
5657
tx.vout[0].nValue = int((total_in - amount - fee) * COIN)
5758
tx.vout.append(deepcopy(tx.vout[0]))
5859
tx.vout[1].nValue = int(amount * COIN)

test/functional/feature_rbf.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -472,11 +472,10 @@ def test_too_many_replacements_with_default_mempool_params(self):
472472

473473
# Now attempt to submit a tx that double-spends all the root tx inputs, which
474474
# would invalidate `num_txs_invalidated` transactions.
475-
double_tx = wallet.create_self_transfer_multi(
475+
tx_hex = wallet.create_self_transfer_multi(
476476
utxos_to_spend=root_utxos,
477477
fee_per_output=10_000_000, # absurdly high feerate
478-
)
479-
tx_hex = double_tx.serialize().hex()
478+
)["hex"]
480479

481480
if failure_expected:
482481
assert_raises_rpc_error(

test/functional/mempool_reorg.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -68,10 +68,8 @@ def run_test(self):
6868
assert_raises_rpc_error(-26, 'non-final', self.nodes[0].sendrawtransaction, timelock_tx)
6969

7070
self.log.info("Create spend_2_1 and spend_3_1")
71-
spend_2_utxo = wallet.get_utxo(txid=spend_2['txid'])
72-
spend_2_1 = wallet.create_self_transfer(utxo_to_spend=spend_2_utxo)
73-
spend_3_utxo = wallet.get_utxo(txid=spend_3['txid'])
74-
spend_3_1 = wallet.create_self_transfer(utxo_to_spend=spend_3_utxo)
71+
spend_2_1 = wallet.create_self_transfer(utxo_to_spend=spend_2["new_utxo"])
72+
spend_3_1 = wallet.create_self_transfer(utxo_to_spend=spend_3["new_utxo"])
7573

7674
self.log.info("Broadcast and mine spend_3_1")
7775
spend_3_1_id = self.nodes[0].sendrawtransaction(spend_3_1['hex'])

test/functional/rpc_mempool_info.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
55
"""Test RPCs that retrieve information from the mempool."""
66

7-
from test_framework.blocktools import COINBASE_MATURITY
87
from test_framework.test_framework import BitcoinTestFramework
98
from test_framework.util import (
109
assert_equal,
@@ -19,7 +18,6 @@ def set_test_params(self):
1918

2019
def run_test(self):
2120
self.wallet = MiniWallet(self.nodes[0])
22-
self.generate(self.wallet, COINBASE_MATURITY + 1)
2321
self.wallet.rescan_utxos()
2422
confirmed_utxo = self.wallet.get_utxo()
2523

test/functional/test_framework/wallet.py

Lines changed: 43 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,9 @@ def __init__(self, test_node, *, mode=MiniWalletMode.ADDRESS_OP_TRUE):
101101
self._address, self._internal_key = create_deterministic_address_bcrt1_p2tr_op_true()
102102
self._scriptPubKey = bytes.fromhex(self._test_node.validateaddress(self._address)['scriptPubKey'])
103103

104+
def _create_utxo(self, *, txid, vout, value, height):
105+
return {"txid": txid, "vout": vout, "value": value, "height": height}
106+
104107
def get_balance(self):
105108
return sum(u['value'] for u in self._utxos)
106109

@@ -110,13 +113,22 @@ def rescan_utxos(self):
110113
res = self._test_node.scantxoutset(action="start", scanobjects=[self.get_descriptor()])
111114
assert_equal(True, res['success'])
112115
for utxo in res['unspents']:
113-
self._utxos.append({'txid': utxo['txid'], 'vout': utxo['vout'], 'value': utxo['amount'], 'height': utxo['height']})
116+
self._utxos.append(self._create_utxo(txid=utxo["txid"], vout=utxo["vout"], value=utxo["amount"], height=utxo["height"]))
114117

115118
def scan_tx(self, tx):
116-
"""Scan the tx for self._scriptPubKey outputs and add them to self._utxos"""
119+
"""Scan the tx and adjust the internal list of owned utxos"""
120+
for spent in tx["vin"]:
121+
# Mark spent. This may happen when the caller has ownership of a
122+
# utxo that remained in this wallet. For example, by passing
123+
# mark_as_spent=False to get_utxo or by using an utxo returned by a
124+
# create_self_transfer* call.
125+
try:
126+
self.get_utxo(txid=spent["txid"], vout=spent["vout"])
127+
except StopIteration:
128+
pass
117129
for out in tx['vout']:
118130
if out['scriptPubKey']['hex'] == self._scriptPubKey.hex():
119-
self._utxos.append({'txid': tx['txid'], 'vout': out['n'], 'value': out['value'], 'height': 0})
131+
self._utxos.append(self._create_utxo(txid=tx["txid"], vout=out["n"], value=out["value"], height=0))
120132

121133
def sign_tx(self, tx, fixed_length=True):
122134
"""Sign tx that has been created by MiniWallet in P2PK mode"""
@@ -135,12 +147,16 @@ def sign_tx(self, tx, fixed_length=True):
135147
tx.rehash()
136148

137149
def generate(self, num_blocks, **kwargs):
138-
"""Generate blocks with coinbase outputs to the internal address, and append the outputs to the internal list"""
150+
"""Generate blocks with coinbase outputs to the internal address, and call rescan_utxos"""
139151
blocks = self._test_node.generatetodescriptor(num_blocks, self.get_descriptor(), **kwargs)
140-
for b in blocks:
141-
block_info = self._test_node.getblock(blockhash=b, verbosity=2)
142-
cb_tx = block_info['tx'][0]
143-
self._utxos.append({'txid': cb_tx['txid'], 'vout': 0, 'value': cb_tx['vout'][0]['value'], 'height': block_info['height']})
152+
# Calling rescan_utxos here makes sure that after a generate the utxo
153+
# set is in a clean state. For example, the wallet will update
154+
# - if the caller consumed utxos, but never used them
155+
# - if the caller sent a transaction that is not mined or got rbf'd
156+
# - after block re-orgs
157+
# - the utxo height for mined mempool txs
158+
# - However, the wallet will not consider remaining mempool txs
159+
self.rescan_utxos()
144160
return blocks
145161

146162
def get_scriptPubKey(self):
@@ -206,20 +222,10 @@ def send_to(self, *, from_node, scriptPubKey, amount, fee=1000):
206222
return txid, 1
207223

208224
def send_self_transfer_multi(self, *, from_node, **kwargs):
209-
"""
210-
Create and send a transaction that spends the given UTXOs and creates a
211-
certain number of outputs with equal amounts.
212-
213-
Returns a dictionary with
214-
- txid
215-
- serialized transaction in hex format
216-
- transaction as CTransaction instance
217-
- list of newly created UTXOs, ordered by vout index
218-
"""
225+
"""Call create_self_transfer_multi and send the transaction."""
219226
tx = self.create_self_transfer_multi(**kwargs)
220-
txid = self.sendrawtransaction(from_node=from_node, tx_hex=tx.serialize().hex())
221-
return {'new_utxos': [self.get_utxo(txid=txid, vout=vout) for vout in range(len(tx.vout))],
222-
'txid': txid, 'hex': tx.serialize().hex(), 'tx': tx}
227+
self.sendrawtransaction(from_node=from_node, tx_hex=tx["hex"])
228+
return tx
223229

224230
def create_self_transfer_multi(
225231
self,
@@ -253,7 +259,18 @@ def create_self_transfer_multi(
253259
outputs_value_total = inputs_value_total - fee_per_output * num_outputs
254260
for o in tx.vout:
255261
o.nValue = outputs_value_total // num_outputs
256-
return tx
262+
txid = tx.rehash()
263+
return {
264+
"new_utxos": [self._create_utxo(
265+
txid=txid,
266+
vout=i,
267+
value=Decimal(tx.vout[i].nValue) / COIN,
268+
height=0,
269+
) for i in range(len(tx.vout))],
270+
"txid": txid,
271+
"hex": tx.serialize().hex(),
272+
"tx": tx,
273+
}
257274

258275
def create_self_transfer(self, *, fee_rate=Decimal("0.003"), utxo_to_spend=None, locktime=0, sequence=0):
259276
"""Create and return a tx with the specified fee_rate. Fee may be exact or at most one satoshi higher than needed."""
@@ -264,12 +281,12 @@ def create_self_transfer(self, *, fee_rate=Decimal("0.003"), utxo_to_spend=None,
264281
vsize = Decimal(168) # P2PK (73 bytes scriptSig + 35 bytes scriptPubKey + 60 bytes other)
265282
else:
266283
assert False
267-
send_value = int(COIN * (utxo_to_spend['value'] - fee_rate * (vsize / 1000)))
284+
send_value = utxo_to_spend["value"] - (fee_rate * vsize / 1000)
268285
assert send_value > 0
269286

270287
tx = CTransaction()
271288
tx.vin = [CTxIn(COutPoint(int(utxo_to_spend['txid'], 16), utxo_to_spend['vout']), nSequence=sequence)]
272-
tx.vout = [CTxOut(send_value, self._scriptPubKey)]
289+
tx.vout = [CTxOut(int(COIN * send_value), self._scriptPubKey)]
273290
tx.nLockTime = locktime
274291
if self._mode == MiniWalletMode.RAW_P2PK:
275292
self.sign_tx(tx)
@@ -283,8 +300,9 @@ def create_self_transfer(self, *, fee_rate=Decimal("0.003"), utxo_to_spend=None,
283300
tx_hex = tx.serialize().hex()
284301

285302
assert_equal(tx.get_vsize(), vsize)
303+
new_utxo = self._create_utxo(txid=tx.rehash(), vout=0, value=send_value, height=0)
286304

287-
return {'txid': tx.rehash(), 'wtxid': tx.getwtxid(), 'hex': tx_hex, 'tx': tx}
305+
return {"txid": new_utxo["txid"], "wtxid": tx.getwtxid(), "hex": tx_hex, "tx": tx, "new_utxo": new_utxo}
288306

289307
def sendrawtransaction(self, *, from_node, tx_hex, maxfeerate=0, **kwargs):
290308
txid = from_node.sendrawtransaction(hexstring=tx_hex, maxfeerate=maxfeerate, **kwargs)

0 commit comments

Comments
 (0)