Auto merge of #1431 - bitcartel:master_1373_taddr_coinbase_error, r=bitcartel

Return a more informative error message when trying to spend coinbase; select non-coinbase inputs when sending to a transparent output if needed

For #1373 and #1519

Code change:
- Extra parameter added to AvailableCoins to include or exclude Coinbase coins.  Default value of parameter is 'true' as current behaviour is to include Coinbase coins.
- SelectCoins, used for sending taddr->taddr, will now exclude Coinbase coins.

Unit test:
Tried to write a test to focus on the extra parameter added to AvailableCoins but could not.

Empirical testing on Testnet:
Current behaviour is that upstream RPC commands sendfrom and sendtoaddress try to spend coinbase coins returned by AvailableCoins.  So the user will see:

```
./zcash-cli sendtoaddress mrEGRmGJhmwAa4MQjzGd86ry63vrvovu9b 1000.0
error: {"code":-6,"message":"Insufficient funds"}

./zcash-cli sendtoaddress mrEGRmGJhmwAa4MQjzGd86ry63vrvovu9b 0.00003000
error: {"code":-4,"message":"Error: The transaction was rejected! This might happen if some of the coins in your wallet were already spent, such as if you used a copy of wallet.dat and coins were spent in the copy but not marked as spent here."}

./zcash-cli sendfrom "" mrEGRmGJhmwAa4MQjzGd86ry63vrvovu9b 0.00003000
error: {"code":-4,"message":"Error: The transaction was rejected! This might happen if some of the coins in your wallet were already spent, such as if you used a copy of wallet.dat and coins were spent in the copy but not marked as spent here."}
```

After fix is applied:

```
./zcash-cli sendtoaddress mrEGRmGJhmwAa4MQjzGd86ry63vrvovu9b 1000.0
error: {"code":-6,"message":"Insufficient funds"}

./zcash-cli sendtoaddress mrEGRmGJhmwAa4MQjzGd86ry63vrvovu9b 0.00003000
error: {"code":-4,"message":"Coinbase funds can only be sent to a zaddr"}
```

When non-coinbase UTXOs exist, they will now be selected and used:

```
./zcash-cli z_sendmany tnPJZHeVxegCg91utaquBRPEDBGjozfz9iLDHt7zvphFbZdspNgkTVLCGjDcadQBKNyUwKs8pNjDXuEZKrE1aNLpFwHgz4t '[{"address":"mx5fTRhLZwbYE7ZqhAPueZgQGSnwTbdvKU", "amount":0.01}]'

./zcash-cli sendtoaddress mrEGRmGJhmwAa4MQjzGd86ry63vrvovu9b 1000.0
error: {"code":-6,"message":"Insufficient funds"}

./zcash-cli sendtoaddress mrEGRmGJhmwAa4MQjzGd86ry63vrvovu9b 0.00003000
9818e543ac2f689d4ce8b52087607d73fecd771d45d316a1d9db092f0485aff2

./zcash-cli sendfrom "" mrEGRmGJhmwAa4MQjzGd86ry63vrvovu9b 0.00003000
899f2894823f51f15fc73b5e0871ac943edbe0ff88e1635f86906087b72caf30
```
This commit is contained in:
zkbot
2016-10-16 22:08:07 -04:00
7 changed files with 182 additions and 9 deletions

View File

@@ -11,6 +11,7 @@ export BITCOIND=${REAL_BITCOIND}
#Run the tests
testScripts=(
'wallet_protectcoinbase.py'
'wallet.py'
'wallet_nullifiers.py'
'listtransactions.py'

View File

@@ -0,0 +1,126 @@
#!/usr/bin/env python2
# Copyright (c) 2016 The Zcash developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import *
from time import *
class Wallet2Test (BitcoinTestFramework):
def setup_chain(self):
print("Initializing test directory "+self.options.tmpdir)
initialize_chain_clean(self.options.tmpdir, 4)
# Start nodes with -regtestprotectcoinbase to set fCoinbaseMustBeProtected to true.
def setup_network(self, split=False):
self.nodes = start_nodes(3, self.options.tmpdir, extra_args=[['-regtestprotectcoinbase']] * 3 )
connect_nodes_bi(self.nodes,0,1)
connect_nodes_bi(self.nodes,1,2)
connect_nodes_bi(self.nodes,0,2)
self.is_network_split=False
self.sync_all()
def wait_for_operationd_success(self, myopid):
print('waiting for async operation {}'.format(myopid))
opids = []
opids.append(myopid)
timeout = 120
status = None
for x in xrange(1, timeout):
results = self.nodes[0].z_getoperationresult(opids)
if len(results)==0:
sleep(1)
else:
status = results[0]["status"]
break
print('...returned status: {}'.format(status))
assert_equal("success", status)
def run_test (self):
print "Mining blocks..."
self.nodes[0].generate(4)
walletinfo = self.nodes[0].getwalletinfo()
assert_equal(walletinfo['immature_balance'], 40)
assert_equal(walletinfo['balance'], 0)
self.sync_all()
self.nodes[1].generate(101)
self.sync_all()
assert_equal(self.nodes[0].getbalance(), 40)
assert_equal(self.nodes[1].getbalance(), 10)
assert_equal(self.nodes[2].getbalance(), 0)
# Send will fail because we are enforcing the consensus rule that
# coinbase utxos can only be sent to a zaddr.
errorString = ""
try:
self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 1.0)
except JSONRPCException,e:
errorString = e.error['message']
assert_equal("Coinbase funds can only be sent to a zaddr" in errorString, True)
# send node 0 taddr to node 0 zaddr
mytaddr = self.nodes[0].getnewaddress()
myzaddr = self.nodes[0].z_getnewaddress()
recipients = []
recipients.append({"address":myzaddr, "amount":20.0})
myopid = self.nodes[0].z_sendmany(mytaddr, recipients)
self.wait_for_operationd_success(myopid)
self.nodes[1].generate(1)
self.sync_all()
# check balances (the z_sendmany consumes 3 coinbase utxos)
resp = self.nodes[0].z_gettotalbalance()
assert_equal(Decimal(resp["transparent"]), Decimal('10.0'))
assert_equal(Decimal(resp["private"]), Decimal('29.9999'))
assert_equal(Decimal(resp["total"]), Decimal('39.9999'))
# convert note to transparent funds
recipients = []
recipients.append({"address":mytaddr, "amount":20.0})
myopid = self.nodes[0].z_sendmany(myzaddr, recipients)
self.wait_for_operationd_success(myopid)
self.nodes[1].generate(1)
self.sync_all()
# check balances
resp = self.nodes[0].z_gettotalbalance()
assert_equal(Decimal(resp["transparent"]), Decimal('30.0'))
assert_equal(Decimal(resp["private"]), Decimal('9.9998'))
assert_equal(Decimal(resp["total"]), Decimal('39.9998'))
# Send will fail because send amount is too big, even when including coinbase utxos
errorString = ""
try:
self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 99999.0)
except JSONRPCException,e:
errorString = e.error['message']
assert_equal("Insufficient funds" in errorString, True)
# Send will fail because of insufficient funds unless sender uses coinbase utxos
try:
self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 21.0)
except JSONRPCException,e:
errorString = e.error['message']
assert_equal("Insufficient funds, coinbase funds can only be spent after they have been sent to a zaddr" in errorString, True)
# Send will succeed because the balance of non-coinbase utxos is 20.0
try:
self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 19.0)
except JSONRPCException:
assert(False)
self.nodes[1].generate(10)
self.sync_all()
# check balance
assert_equal(self.nodes[2].getbalance(), Decimal('19'))
if __name__ == '__main__':
Wallet2Test ().main ()