Skip to content

Commit a5cb9b4

Browse files
committed
partial bitcoin#25445: Return new_utxo from create_self_transfer in MiniWallet
excludes: - any changes to feature_{dbcrash,fee_estimation}, rpc_mempool_info - fa83c0c
1 parent b28d90f commit a5cb9b4

File tree

2 files changed

+45
-29
lines changed

2 files changed

+45
-29
lines changed

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/test_framework/wallet.py

Lines changed: 43 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,9 @@ def __init__(self, test_node, *, mode=MiniWalletMode.ADDRESS_OP_TRUE):
9090
self._address = ADDRESS_BCRT1_P2SH_OP_TRUE
9191
self._scriptPubKey = bytes.fromhex(self._test_node.validateaddress(self._address)['scriptPubKey'])
9292

93+
def _create_utxo(self, *, txid, vout, value, height):
94+
return {"txid": txid, "vout": vout, "value": value, "height": height}
95+
9396
def get_balance(self):
9497
return sum(u['value'] for u in self._utxos)
9598

@@ -99,13 +102,22 @@ def rescan_utxos(self):
99102
res = self._test_node.scantxoutset(action="start", scanobjects=[self.get_descriptor()])
100103
assert_equal(True, res['success'])
101104
for utxo in res['unspents']:
102-
self._utxos.append({'txid': utxo['txid'], 'vout': utxo['vout'], 'value': utxo['amount'], 'height': utxo['height']})
105+
self._utxos.append(self._create_utxo(txid=utxo["txid"], vout=utxo["vout"], value=utxo["amount"], height=utxo["height"]))
103106

104107
def scan_tx(self, tx):
105-
"""Scan the tx for self._scriptPubKey outputs and add them to self._utxos"""
108+
"""Scan the tx and adjust the internal list of owned utxos"""
109+
for spent in tx["vin"]:
110+
# Mark spent. This may happen when the caller has ownership of a
111+
# utxo that remained in this wallet. For example, by passing
112+
# mark_as_spent=False to get_utxo or by using an utxo returned by a
113+
# create_self_transfer* call.
114+
try:
115+
self.get_utxo(txid=spent["txid"], vout=spent["vout"])
116+
except StopIteration:
117+
pass
106118
for out in tx['vout']:
107119
if out['scriptPubKey']['hex'] == self._scriptPubKey.hex():
108-
self._utxos.append({'txid': tx['txid'], 'vout': out['n'], 'value': out['value'], 'height': 0})
120+
self._utxos.append(self._create_utxo(txid=tx["txid"], vout=out["n"], value=out["value"], height=0))
109121

110122
def sign_tx(self, tx, fixed_length=True):
111123
"""Sign tx that has been created by MiniWallet in P2PK mode"""
@@ -124,12 +136,16 @@ def sign_tx(self, tx, fixed_length=True):
124136
tx.rehash()
125137

126138
def generate(self, num_blocks, **kwargs):
127-
"""Generate blocks with coinbase outputs to the internal address, and append the outputs to the internal list"""
139+
"""Generate blocks with coinbase outputs to the internal address, and call rescan_utxos"""
128140
blocks = self._test_node.generatetodescriptor(num_blocks, self.get_descriptor(), **kwargs)
129-
for b in blocks:
130-
block_info = self._test_node.getblock(blockhash=b, verbosity=2)
131-
cb_tx = block_info['tx'][0]
132-
self._utxos.append({'txid': cb_tx['txid'], 'vout': 0, 'value': cb_tx['vout'][0]['value'], 'height': block_info['height']})
141+
# Calling rescan_utxos here makes sure that after a generate the utxo
142+
# set is in a clean state. For example, the wallet will update
143+
# - if the caller consumed utxos, but never used them
144+
# - if the caller sent a transaction that is not mined or got rbf'd
145+
# - after block re-orgs
146+
# - the utxo height for mined mempool txs
147+
# - However, the wallet will not consider remaining mempool txs
148+
self.rescan_utxos()
133149
return blocks
134150

135151
def get_scriptPubKey(self):
@@ -188,20 +204,10 @@ def send_to(self, *, from_node, scriptPubKey, amount, fee=1000):
188204
return txid, 1
189205

190206
def send_self_transfer_multi(self, *, from_node, **kwargs):
191-
"""
192-
Create and send a transaction that spends the given UTXOs and creates a
193-
certain number of outputs with equal amounts.
194-
195-
Returns a dictionary with
196-
- txid
197-
- serialized transaction in hex format
198-
- transaction as CTransaction instance
199-
- list of newly created UTXOs, ordered by vout index
200-
"""
207+
"""Call create_self_transfer_multi and send the transaction."""
201208
tx = self.create_self_transfer_multi(**kwargs)
202-
txid = self.sendrawtransaction(from_node=from_node, tx_hex=tx.serialize().hex())
203-
return {'new_utxos': [self.get_utxo(txid=txid, vout=vout) for vout in range(len(tx.vout))],
204-
'txid': txid, 'hex': tx.serialize().hex(), 'tx': tx}
209+
self.sendrawtransaction(from_node=from_node, tx_hex=tx["hex"])
210+
return tx
205211

206212
def create_self_transfer_multi(
207213
self,
@@ -234,7 +240,18 @@ def create_self_transfer_multi(
234240
outputs_value_total = inputs_value_total - fee_per_output * num_outputs
235241
for o in tx.vout:
236242
o.nValue = outputs_value_total // num_outputs
237-
return tx
243+
txid = tx.rehash()
244+
return {
245+
"new_utxos": [self._create_utxo(
246+
txid=txid,
247+
vout=i,
248+
value=Decimal(tx.vout[i].nValue) / COIN,
249+
height=0,
250+
) for i in range(len(tx.vout))],
251+
"txid": txid,
252+
"hex": tx.serialize().hex(),
253+
"tx": tx,
254+
}
238255

239256
def create_self_transfer(self, *, fee_rate=Decimal("0.003"), utxo_to_spend=None, locktime=0, sequence=0):
240257
"""Create and return a tx with the specified fee_rate. Fee may be exact or at most one satoshi higher than needed."""
@@ -245,12 +262,12 @@ def create_self_transfer(self, *, fee_rate=Decimal("0.003"), utxo_to_spend=None,
245262
vsize = Decimal(168) # P2PK (73 bytes scriptSig + 35 bytes scriptPubKey + 60 bytes other)
246263
else:
247264
assert False
248-
send_value = int(COIN * (utxo_to_spend['value'] - fee_rate * (vsize / 1000)))
265+
send_value = utxo_to_spend["value"] - (fee_rate * vsize / 1000)
249266
assert send_value > 0
250267

251268
tx = CTransaction()
252269
tx.vin = [CTxIn(COutPoint(int(utxo_to_spend['txid'], 16), utxo_to_spend['vout']), nSequence=sequence)]
253-
tx.vout = [CTxOut(send_value, self._scriptPubKey)]
270+
tx.vout = [CTxOut(int(COIN * send_value), self._scriptPubKey)]
254271
tx.nLockTime = locktime
255272
if self._mode == MiniWalletMode.RAW_P2PK:
256273
self.sign_tx(tx)
@@ -263,8 +280,9 @@ def create_self_transfer(self, *, fee_rate=Decimal("0.003"), utxo_to_spend=None,
263280
tx_hex = tx.serialize().hex()
264281

265282
assert_equal(tx.get_vsize(), vsize)
283+
new_utxo = self._create_utxo(txid=tx.rehash(), vout=0, value=send_value, height=0)
266284

267-
return {'txid': tx.rehash(), 'hex': tx_hex, 'tx': tx}
285+
return {"txid": new_utxo["txid"], "hex": tx_hex, "tx": tx, "new_utxo": new_utxo}
268286

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

0 commit comments

Comments
 (0)