Skip to content

Commit

Permalink
Fixes zcash#1345 so that UTXO debit and credits are computed correctl…
Browse files Browse the repository at this point in the history
…y for a transaction.
  • Loading branch information
bitcartel committed Oct 3, 2016
1 parent 2f15f58 commit 86cf60b
Show file tree
Hide file tree
Showing 2 changed files with 178 additions and 7 deletions.
109 changes: 107 additions & 2 deletions qa/rpc-tests/wallet.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import *
from time import *

class WalletTest (BitcoinTestFramework):

Expand Down Expand Up @@ -37,6 +38,9 @@ def run_test (self):
assert_equal(self.nodes[0].getbalance(), 40)
assert_equal(self.nodes[1].getbalance(), 10)
assert_equal(self.nodes[2].getbalance(), 0)
assert_equal(self.nodes[0].getbalance("*"), 40)
assert_equal(self.nodes[1].getbalance("*"), 10)
assert_equal(self.nodes[2].getbalance("*"), 0)

# Send 21 BTC from 0 to 2 using sendtoaddress call.
# Second transaction will be child of first, and will require a fee
Expand All @@ -58,6 +62,8 @@ def run_test (self):
# minus the 21 plus fees sent to node2
assert_equal(self.nodes[0].getbalance(), 50-21)
assert_equal(self.nodes[2].getbalance(), 21)
assert_equal(self.nodes[0].getbalance("*"), 50-21)
assert_equal(self.nodes[2].getbalance("*"), 21)

# Node0 should have three unspent outputs.
# Create a couple of transactions to send them to node2, submit them through
Expand Down Expand Up @@ -87,6 +93,8 @@ def run_test (self):
assert_equal(self.nodes[0].getbalance(), 0)
assert_equal(self.nodes[2].getbalance(), 50)
assert_equal(self.nodes[2].getbalance("from1"), 50-21)
assert_equal(self.nodes[0].getbalance("*"), 0)
assert_equal(self.nodes[2].getbalance("*"), 50)

# Send 10 BTC normal
address = self.nodes[0].getnewaddress("test")
Expand All @@ -96,27 +104,35 @@ def run_test (self):
self.sync_all()
assert_equal(self.nodes[2].getbalance(), Decimal('39.99900000'))
assert_equal(self.nodes[0].getbalance(), Decimal('10.00000000'))
assert_equal(self.nodes[2].getbalance("*"), Decimal('39.99900000'))
assert_equal(self.nodes[0].getbalance("*"), Decimal('10.00000000'))

# Send 10 BTC with subtract fee from amount
txid = self.nodes[2].sendtoaddress(address, 10, "", "", True)
self.nodes[2].generate(1)
self.sync_all()
assert_equal(self.nodes[2].getbalance(), Decimal('29.99900000'))
assert_equal(self.nodes[0].getbalance(), Decimal('19.99900000'))
assert_equal(self.nodes[2].getbalance("*"), Decimal('29.99900000'))
assert_equal(self.nodes[0].getbalance("*"), Decimal('19.99900000'))

# Sendmany 10 BTC
txid = self.nodes[2].sendmany('from1', {address: 10}, 0, "", [])
self.nodes[2].generate(1)
self.sync_all()
assert_equal(self.nodes[2].getbalance(), Decimal('19.99800000'))
assert_equal(self.nodes[0].getbalance(), Decimal('29.99900000'))
assert_equal(self.nodes[2].getbalance("*"), Decimal('19.99800000'))
assert_equal(self.nodes[0].getbalance("*"), Decimal('29.99900000'))

# Sendmany 10 BTC with subtract fee from amount
txid = self.nodes[2].sendmany('from1', {address: 10}, 0, "", [address])
self.nodes[2].generate(1)
self.sync_all()
assert_equal(self.nodes[2].getbalance(), Decimal('9.99800000'))
assert_equal(self.nodes[0].getbalance(), Decimal('39.99800000'))
assert_equal(self.nodes[2].getbalance("*"), Decimal('9.99800000'))
assert_equal(self.nodes[0].getbalance("*"), Decimal('39.99800000'))

# Test ResendWalletTransactions:
# Create a couple of transactions, then start up a fourth
Expand Down Expand Up @@ -178,13 +194,15 @@ def run_test (self):
self.nodes[1].generate(1) #mine a block, tx should not be in there
self.sync_all()
assert_equal(self.nodes[2].getbalance(), Decimal('9.99800000')); #should not be changed because tx was not broadcasted

assert_equal(self.nodes[2].getbalance("*"), Decimal('9.99800000')); #should not be changed because tx was not broadcasted

#now broadcast from another node, mine a block, sync, and check the balance
self.nodes[1].sendrawtransaction(txObjNotBroadcasted['hex'])
self.nodes[1].generate(1)
self.sync_all()
txObjNotBroadcasted = self.nodes[0].gettransaction(txIdNotBroadcasted)
assert_equal(self.nodes[2].getbalance(), Decimal('11.99800000')); #should not be
assert_equal(self.nodes[2].getbalance("*"), Decimal('11.99800000')); #should not be

#create another tx
txIdNotBroadcasted = self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 2);
Expand All @@ -203,6 +221,93 @@ def run_test (self):

#tx should be added to balance because after restarting the nodes tx should be broadcastet
assert_equal(self.nodes[2].getbalance(), Decimal('13.99800000')); #should not be

assert_equal(self.nodes[2].getbalance("*"), Decimal('13.99800000')); #should not be

# send from node 0 to node 2 taddr
mytaddr = self.nodes[2].getnewaddress();
self.nodes[0].sendtoaddress(mytaddr, 10.0);
self.nodes[0].generate(1)
self.sync_all()

mybalance = self.nodes[2].z_getbalance(mytaddr)
assert_equal(self.nodes[2].z_getbalance(mytaddr), Decimal('10.0'));

# add zaddr to node 2
# payment address: tneMWwNSjPkPaz7p5ed3XJbrz8XwpfBvaha3jaGU26EstNN8HKMYVyzgwMmVcmtaw7b5uuaF4Hr8P4UPZEMkuTkXQa8STzF
# spending key: TKWRfN47drnaFDbHBSYT2McbPjbmFjjTrGuntj3tAMnnGDg2Kp19
self.nodes[2].z_importkey("TKWRfN47drnaFDbHBSYT2McbPjbmFjjTrGuntj3tAMnnGDg2Kp19")
myzaddr = "tneMWwNSjPkPaz7p5ed3XJbrz8XwpfBvaha3jaGU26EstNN8HKMYVyzgwMmVcmtaw7b5uuaF4Hr8P4UPZEMkuTkXQa8STzF"

# send node 2 taddr to zaddr
recipients = []
recipients.append({"address":myzaddr, "amount":7.0})
myopid = self.nodes[2].z_sendmany(mytaddr, recipients)

opids = []
opids.append(myopid)

timeout = 120
status = None
for x in xrange(1, timeout):
results = self.nodes[2].z_getoperationresult(opids)
if len(results)==0:
sleep(1)
else:
status = results[0]["status"]
break

assert_equal("success", status)
self.nodes[2].generate(1)
self.sync_all()

# check balances
zsendmanynotevalue = Decimal('7.0')
zsendmanyfee = Decimal('0.0001')
node2utxobalance = Decimal('23.998') - zsendmanynotevalue - zsendmanyfee

assert_equal(self.nodes[2].getbalance(), node2utxobalance)
assert_equal(self.nodes[2].getbalance("*"), node2utxobalance)

# check zaddr balance
assert_equal(self.nodes[2].z_getbalance(myzaddr), zsendmanynotevalue);

# check via z_gettotalbalance
resp = self.nodes[2].z_gettotalbalance()
assert_equal(Decimal(resp["transparent"]), node2utxobalance)
assert_equal(Decimal(resp["private"]), zsendmanynotevalue)
assert_equal(Decimal(resp["total"]), node2utxobalance + zsendmanynotevalue)


# send from private note to node 0 and node 2
node0balance = self.nodes[0].getbalance() # 25.99794745
node2balance = self.nodes[2].getbalance() # 16.99790000

recipients = []
recipients.append({"address":self.nodes[0].getnewaddress(), "amount":1.0})
recipients.append({"address":self.nodes[2].getnewaddress(), "amount":1.0})
myopid = self.nodes[2].z_sendmany(myzaddr, recipients)

status = None
opids = []
opids.append(myopid)
for x in xrange(1, timeout):
results = self.nodes[2].z_getoperationresult(opids)
if len(results)==0:
sleep(1)
else:
status = results[0]["status"]
break

assert_equal("success", status)
self.nodes[2].generate(1)
self.sync_all()

node0balance += Decimal('1.0')
node2balance += Decimal('1.0')
assert_equal(Decimal(self.nodes[0].getbalance()), node0balance)
assert_equal(Decimal(self.nodes[0].getbalance("*")), node0balance)
assert_equal(Decimal(self.nodes[2].getbalance()), node2balance)
assert_equal(Decimal(self.nodes[2].getbalance("*")), node2balance)

if __name__ == '__main__':
WalletTest ().main ()
76 changes: 71 additions & 5 deletions src/wallet/wallet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1328,6 +1328,7 @@ int CWalletTx::GetRequestCount() const
return nRequests;
}

// GetAmounts will determine the transparent debits and credits for a given wallet tx.
void CWalletTx::GetAmounts(list<COutputEntry>& listReceived,
list<COutputEntry>& listSent, CAmount& nFee, string& strSentAccount, const isminefilter& filter) const
{
Expand All @@ -1336,12 +1337,77 @@ void CWalletTx::GetAmounts(list<COutputEntry>& listReceived,
listSent.clear();
strSentAccount = strFromAccount;

// Compute fee:
// Is this tx sent/signed by me?
CAmount nDebit = GetDebit(filter);
if (nDebit > 0) // debit>0 means we signed/sent this transaction
{
CAmount nValueOut = GetValueOut();
nFee = nDebit - nValueOut;
bool isFromMyTaddr = nDebit > 0; // debit>0 means we signed/sent this transaction

// Does this tx spend my notes?
bool isFromMyZaddr = false;
for (const JSDescription& js : vjoinsplit) {
for (const uint256& nullifier : js.nullifiers) {
if (pwallet->IsFromMe(nullifier)) {
isFromMyZaddr = true;
break;
}
}
if (isFromMyZaddr) {
break;
}
}

// Compute fee if we sent this transaction.
if (isFromMyTaddr) {
CAmount nValueOut = GetValueOut(); // transparent outputs plus all vpub_old
CAmount nValueIn = 0;
for (const JSDescription & js : vjoinsplit) {
nValueIn += js.vpub_new;
}
nFee = nDebit - nValueOut + nValueIn;
}

// Create output entry for vpub_old/new, if we sent utxos from this transaction
if (isFromMyTaddr) {
CAmount myVpubOld = 0;
CAmount myVpubNew = 0;
for (const JSDescription& js : vjoinsplit) {
bool fMyJSDesc = false;

// Check input side
for (const uint256& nullifier : js.nullifiers) {
if (pwallet->IsFromMe(nullifier)) {
fMyJSDesc = true;
break;
}
}

// Check output side
if (!fMyJSDesc) {
for (const std::pair<JSOutPoint, CNoteData> nd : this->mapNoteData) {
if (nd.first.js < vjoinsplit.size() && nd.first.n < vjoinsplit[nd.first.js].ciphertexts.size()) {
fMyJSDesc = true;
break;
}
}
}

if (fMyJSDesc) {
myVpubOld += js.vpub_old;
myVpubNew += js.vpub_new;
}

if (!MoneyRange(js.vpub_old) || !MoneyRange(js.vpub_new) || !MoneyRange(myVpubOld) || !MoneyRange(myVpubNew)) {
throw std::runtime_error("CWalletTx::GetAmounts: value out of range");
}
}

// Create an output for the value taken from or added to the transparent value pool by JoinSplits
if (myVpubOld > myVpubNew) {
COutputEntry output = {CNoDestination(), myVpubOld - myVpubNew, (int)vout.size()};
listSent.push_back(output);
} else if (myVpubNew > myVpubOld) {
COutputEntry output = {CNoDestination(), myVpubNew - myVpubOld, (int)vout.size()};
listReceived.push_back(output);
}
}

// Sent/received.
Expand Down

0 comments on commit 86cf60b

Please sign in to comment.