Skip to content

Commit 085f839

Browse files
committed
Merge bitcoin/bitcoin#26344: wallet: Fix sendall with watchonly wallets and specified inputs
315fd4d test: Test for out of bounds vout in sendall (Andrew Chow) b132c85 wallet: Check utxo prevout index out of bounds in sendall (Andrew Chow) 708b72b test: Test that sendall works with watchonly spending specific utxos (Andrew Chow) 6bcd7e2 wallet: Correctly check ismine for sendall (Andrew Chow) Pull request description: The `sendall` RPC would previously fail when used with a watchonly wallet and specified inputs. This failure was caused by checking isminetype equality with ISMINE_ALL rather than a bitwise AND as IsMine can never return ISMINE_ALL. Also added a test. ACKs for top commit: w0xlt: ACK bitcoin/bitcoin@315fd4d furszy: ACK 315fd4d Tree-SHA512: fb55cf6524e789964770b803f401027319f0351433ea084ffa7c5e6f1797567a608c956b7f7c5bd542aa172c4b7b38b07d0976f5ec587569efead27266e8664c
2 parents fabc031 + 315fd4d commit 085f839

File tree

2 files changed

+36
-1
lines changed

2 files changed

+36
-1
lines changed

src/wallet/rpc/spend.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1379,7 +1379,7 @@ RPCHelpMan sendall()
13791379
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Input not available. UTXO (%s:%d) was already spent.", input.prevout.hash.ToString(), input.prevout.n));
13801380
}
13811381
const CWalletTx* tx{pwallet->GetWalletTx(input.prevout.hash)};
1382-
if (!tx || pwallet->IsMine(tx->tx->vout[input.prevout.n]) != (coin_control.fAllowWatchOnly ? ISMINE_ALL : ISMINE_SPENDABLE)) {
1382+
if (!tx || input.prevout.n >= tx->tx->vout.size() || !(pwallet->IsMine(tx->tx->vout[input.prevout.n]) & (coin_control.fAllowWatchOnly ? ISMINE_ALL : ISMINE_SPENDABLE))) {
13831383
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Input not found. UTXO (%s:%d) is not part of wallet.", input.prevout.hash.ToString(), input.prevout.n));
13841384
}
13851385
total_input_value += tx->tx->vout[input.prevout.n].nValue;

test/functional/wallet_sendall.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,11 @@ def sendall_fails_on_missing_input(self):
221221
self.add_utxos([16, 5])
222222
spent_utxo = self.wallet.listunspent()[0]
223223

224+
# fails on out of bounds vout
225+
assert_raises_rpc_error(-8,
226+
"Input not found. UTXO ({}:{}) is not part of wallet.".format(spent_utxo["txid"], 1000),
227+
self.wallet.sendall, recipients=[self.remainder_target], options={"inputs": [{"txid": spent_utxo["txid"], "vout": 1000}]})
228+
224229
# fails on unconfirmed spent UTXO
225230
self.wallet.sendall(recipients=[self.remainder_target])
226231
assert_raises_rpc_error(-8,
@@ -276,6 +281,33 @@ def sendall_fails_on_high_fee(self):
276281
recipients=[self.remainder_target],
277282
fee_rate=100000)
278283

284+
@cleanup
285+
def sendall_watchonly_specific_inputs(self):
286+
self.log.info("Test sendall with a subset of UTXO pool in a watchonly wallet")
287+
self.add_utxos([17, 4])
288+
utxo = self.wallet.listunspent()[0]
289+
290+
self.nodes[0].createwallet(wallet_name="watching", disable_private_keys=True)
291+
watchonly = self.nodes[0].get_wallet_rpc("watching")
292+
293+
import_req = [{
294+
"desc": utxo["desc"],
295+
"timestamp": 0,
296+
}]
297+
if self.options.descriptors:
298+
watchonly.importdescriptors(import_req)
299+
else:
300+
watchonly.importmulti(import_req)
301+
302+
sendall_tx_receipt = watchonly.sendall(recipients=[self.remainder_target], options={"inputs": [utxo]})
303+
psbt = sendall_tx_receipt["psbt"]
304+
decoded = self.nodes[0].decodepsbt(psbt)
305+
assert_equal(len(decoded["inputs"]), 1)
306+
assert_equal(len(decoded["outputs"]), 1)
307+
assert_equal(decoded["tx"]["vin"][0]["txid"], utxo["txid"])
308+
assert_equal(decoded["tx"]["vin"][0]["vout"], utxo["vout"])
309+
assert_equal(decoded["tx"]["vout"][0]["scriptPubKey"]["address"], self.remainder_target)
310+
279311
# This tests needs to be the last one otherwise @cleanup will fail with "Transaction too large" error
280312
def sendall_fails_with_transaction_too_large(self):
281313
self.log.info("Test that sendall fails if resulting transaction is too large")
@@ -341,6 +373,9 @@ def run_test(self):
341373
# Sendall fails when providing a fee that is too high
342374
self.sendall_fails_on_high_fee()
343375

376+
# Sendall succeeds with watchonly wallets spending specific UTXOs
377+
self.sendall_watchonly_specific_inputs()
378+
344379
# Sendall fails when many inputs result to too large transaction
345380
self.sendall_fails_with_transaction_too_large()
346381

0 commit comments

Comments
 (0)