Merge bitcoin/bitcoin#26344: wallet: Fix sendall with watchonly wallets and specified inputs

315fd4dbabb6b631b755811742a3bdf93e1241bf test: Test for out of bounds vout in sendall (Andrew Chow)
b132c85650afb2182f2e58e903f3d6f86fd3fb22 wallet: Check utxo prevout index out of bounds in sendall (Andrew Chow)
708b72b7151c855cb5dac2fb6a81e8f35153c46f test: Test that sendall works with watchonly spending specific utxos (Andrew Chow)
6bcd7e2a3b52f855db84cd23b5ee70d27be3434f 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 315fd4dbab
  furszy:
    ACK 315fd4db

Tree-SHA512: fb55cf6524e789964770b803f401027319f0351433ea084ffa7c5e6f1797567a608c956b7f7c5bd542aa172c4b7b38b07d0976f5ec587569efead27266e8664c
This commit is contained in:
fanquake 2022-10-21 16:21:34 +08:00
commit 085f83940d
No known key found for this signature in database
GPG Key ID: 2EEB9F5CC09526C1
2 changed files with 36 additions and 1 deletions

View File

@ -1379,7 +1379,7 @@ RPCHelpMan sendall()
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Input not available. UTXO (%s:%d) was already spent.", input.prevout.hash.ToString(), input.prevout.n));
}
const CWalletTx* tx{pwallet->GetWalletTx(input.prevout.hash)};
if (!tx || pwallet->IsMine(tx->tx->vout[input.prevout.n]) != (coin_control.fAllowWatchOnly ? ISMINE_ALL : ISMINE_SPENDABLE)) {
if (!tx || input.prevout.n >= tx->tx->vout.size() || !(pwallet->IsMine(tx->tx->vout[input.prevout.n]) & (coin_control.fAllowWatchOnly ? ISMINE_ALL : ISMINE_SPENDABLE))) {
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Input not found. UTXO (%s:%d) is not part of wallet.", input.prevout.hash.ToString(), input.prevout.n));
}
total_input_value += tx->tx->vout[input.prevout.n].nValue;

View File

@ -221,6 +221,11 @@ class SendallTest(BitcoinTestFramework):
self.add_utxos([16, 5])
spent_utxo = self.wallet.listunspent()[0]
# fails on out of bounds vout
assert_raises_rpc_error(-8,
"Input not found. UTXO ({}:{}) is not part of wallet.".format(spent_utxo["txid"], 1000),
self.wallet.sendall, recipients=[self.remainder_target], options={"inputs": [{"txid": spent_utxo["txid"], "vout": 1000}]})
# fails on unconfirmed spent UTXO
self.wallet.sendall(recipients=[self.remainder_target])
assert_raises_rpc_error(-8,
@ -276,6 +281,33 @@ class SendallTest(BitcoinTestFramework):
recipients=[self.remainder_target],
fee_rate=100000)
@cleanup
def sendall_watchonly_specific_inputs(self):
self.log.info("Test sendall with a subset of UTXO pool in a watchonly wallet")
self.add_utxos([17, 4])
utxo = self.wallet.listunspent()[0]
self.nodes[0].createwallet(wallet_name="watching", disable_private_keys=True)
watchonly = self.nodes[0].get_wallet_rpc("watching")
import_req = [{
"desc": utxo["desc"],
"timestamp": 0,
}]
if self.options.descriptors:
watchonly.importdescriptors(import_req)
else:
watchonly.importmulti(import_req)
sendall_tx_receipt = watchonly.sendall(recipients=[self.remainder_target], options={"inputs": [utxo]})
psbt = sendall_tx_receipt["psbt"]
decoded = self.nodes[0].decodepsbt(psbt)
assert_equal(len(decoded["inputs"]), 1)
assert_equal(len(decoded["outputs"]), 1)
assert_equal(decoded["tx"]["vin"][0]["txid"], utxo["txid"])
assert_equal(decoded["tx"]["vin"][0]["vout"], utxo["vout"])
assert_equal(decoded["tx"]["vout"][0]["scriptPubKey"]["address"], self.remainder_target)
# This tests needs to be the last one otherwise @cleanup will fail with "Transaction too large" error
def sendall_fails_with_transaction_too_large(self):
self.log.info("Test that sendall fails if resulting transaction is too large")
@ -341,6 +373,9 @@ class SendallTest(BitcoinTestFramework):
# Sendall fails when providing a fee that is too high
self.sendall_fails_on_high_fee()
# Sendall succeeds with watchonly wallets spending specific UTXOs
self.sendall_watchonly_specific_inputs()
# Sendall fails when many inputs result to too large transaction
self.sendall_fails_with_transaction_too_large()