wallet: Ignore coinbase UTXOs with z_sendmany ANY_TADDR

Coinbase UTXOs from multiple transparent addresses may be shielded
together using z_shieldcoinbase.
This commit is contained in:
Jack Grigg 2020-09-18 15:41:28 +01:00
parent 06d44392be
commit e33f530a65
3 changed files with 54 additions and 22 deletions

View File

@ -3,8 +3,6 @@
# Distributed under the MIT software license, see the accompanying
# file COPYING or https://www.opensource.org/licenses/mit-license.php .
from decimal import Decimal
from test_framework.mininode import COIN
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
@ -17,33 +15,65 @@ class WalletSendManyAnyTaddr(BitcoinTestFramework):
# Sanity-check the test harness
assert_equal(self.nodes[0].getblockcount(), 200)
# Each node should have 25 spendable UTXOs, in 25 separate
# transparent addresses, all with value 10 ZEC.
for node in self.nodes:
unspent = node.listunspent()
addresses = set([utxo['address'] for utxo in unspent])
amounts = set([utxo['amountZat'] for utxo in unspent])
assert_equal(len(unspent), 25)
assert_equal(len(addresses), 25)
assert_equal(amounts, set([10 * COIN]))
assert_equal(node.getbalance(), 250)
assert_equal(node.z_gettotalbalance()['private'], '0.00')
# Create the addresses we will be using.
recipient = self.nodes[1].z_getnewaddress()
node3zaddr = self.nodes[3].z_getnewaddress()
node3taddr1 = self.nodes[3].getnewaddress()
node3taddr2 = self.nodes[3].getnewaddress()
# We should not be able to spend multiple coinbase UTXOs at once.
wait_and_assert_operationid_status(
self.nodes[3],
self.nodes[3].z_sendmany('ANY_TADDR', [{'address': recipient, 'amount': 100}]),
'failed',
'Could not find any non-coinbase UTXOs to spend. Coinbase UTXOs can only be sent to a single zaddr recipient from a single taddr.',
)
# Prepare some non-coinbase UTXOs
wait_and_assert_operationid_status(
self.nodes[3],
self.nodes[3].z_shieldcoinbase("*", node3zaddr, 0)['opid'],
)
self.sync_all()
self.nodes[0].generate(1)
self.sync_all()
assert_equal(self.nodes[3].z_getbalance(node3zaddr), 250)
wait_and_assert_operationid_status(
self.nodes[3],
self.nodes[3].z_sendmany(
node3zaddr,
[
{'address': node3taddr1, 'amount': 60},
{'address': node3taddr2, 'amount': 75},
],
),
)
self.sync_all()
self.nodes[0].generate(1)
self.sync_all()
# Check the various balances.
assert_equal(self.nodes[1].z_getbalance(recipient), 0)
assert_equal(self.nodes[3].z_getbalance(node3taddr1), 60)
assert_equal(self.nodes[3].z_getbalance(node3taddr2), 75)
# We should be able to spend multiple UTXOs at once
recipient = self.nodes[1].z_getnewaddress()
wait_and_assert_operationid_status(
self.nodes[3],
self.nodes[3].z_sendmany('ANY_TADDR', [{'address': recipient, 'amount': 100}]),
)
self.sync_all()
self.nodes[0].generate(1)
self.sync_all()
# In regtest mode, coinbase is not required to be shielded,
# so we get a coinbase-spending transaction with transparent
# change.
assert_equal(self.nodes[3].getbalance(), Decimal('149.99990000'))
assert_equal(self.nodes[1].z_gettotalbalance()['private'], '100.00')
# The recipient has their funds!
assert_equal(self.nodes[1].z_getbalance(recipient), 100)
# Change is sent to a new t-address.
assert_equal(self.nodes[3].z_getbalance(node3taddr1), 0)
assert_equal(self.nodes[3].z_getbalance(node3taddr2), 0)
if __name__ == '__main__':
WalletSendManyAnyTaddr().main()

View File

@ -214,7 +214,8 @@ bool AsyncRPCOperation_sendmany::main_impl() {
// When spending coinbase utxos, you can only specify a single zaddr as the change must go somewhere
// and if there are multiple zaddrs, we don't know where to send it.
if (isfromtaddr_) {
if (isSingleZaddrOutput) {
// Only select coinbase if we are spending from a single t-address to a single z-address.
if (!useanyutxo_ && isSingleZaddrOutput) {
bool b = find_utxos(true);
if (!b) {
throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Insufficient funds, no UTXOs found for taddr from address.");
@ -223,7 +224,7 @@ bool AsyncRPCOperation_sendmany::main_impl() {
bool b = find_utxos(false);
if (!b) {
if (isMultipleZaddrOutput) {
throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Could not find any non-coinbase UTXOs to spend. Coinbase UTXOs can only be sent to a single zaddr recipient.");
throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Could not find any non-coinbase UTXOs to spend. Coinbase UTXOs can only be sent to a single zaddr recipient from a single taddr.");
} else {
throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Could not find any non-coinbase UTXOs to spend.");
}

View File

@ -4011,7 +4011,8 @@ UniValue z_sendmany(const UniValue& params, bool fHelp)
"\nArguments:\n"
"1. \"fromaddress\" (string, required) The transparent or shielded address to send the funds from.\n"
" The following special strings are also accepted:\n"
" - \"ANY_TADDR\": Select UTXOs from any transparent addresses belonging to the wallet.\n"
" - \"ANY_TADDR\": Select non-coinbase UTXOs from any transparent addresses belonging to the wallet.\n"
" Use z_shieldcoinbase to shield coinbase UTXOs from multiple transparent addresses.\n"
"2. \"amounts\" (array, required) An array of json objects representing the amounts to send.\n"
" [{\n"
" \"address\":address (string, required) The address is a taddr or zaddr\n"