Auto merge of #3510 - arcalinea:3378_z_listunspent, r=bitcartel
Sapling support for z_listunspent Closes #3378.
This commit is contained in:
commit
4ba35551d4
|
@ -27,6 +27,7 @@ testScripts=(
|
||||||
'wallet_1941.py'
|
'wallet_1941.py'
|
||||||
'wallet_addresses.py'
|
'wallet_addresses.py'
|
||||||
'wallet_sapling.py'
|
'wallet_sapling.py'
|
||||||
|
'wallet_listnotes.py'
|
||||||
'listtransactions.py'
|
'listtransactions.py'
|
||||||
'mempool_resurrect_test.py'
|
'mempool_resurrect_test.py'
|
||||||
'txn_doublespend.py'
|
'txn_doublespend.py'
|
||||||
|
|
|
@ -0,0 +1,168 @@
|
||||||
|
#!/usr/bin/env python2
|
||||||
|
# Copyright (c) 2018 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 assert_equal, start_nodes, wait_and_assert_operationid_status
|
||||||
|
|
||||||
|
from decimal import Decimal
|
||||||
|
|
||||||
|
# Test wallet z_listunspent behaviour across network upgrades
|
||||||
|
class WalletListNotes(BitcoinTestFramework):
|
||||||
|
|
||||||
|
def setup_nodes(self):
|
||||||
|
return start_nodes(4, self.options.tmpdir, [[
|
||||||
|
'-nuparams=5ba81b19:202', # Overwinter
|
||||||
|
'-nuparams=76b809bb:204', # Sapling
|
||||||
|
]] * 4)
|
||||||
|
|
||||||
|
def run_test(self):
|
||||||
|
# Current height = 200 -> Sprout
|
||||||
|
assert_equal(200, self.nodes[0].getblockcount())
|
||||||
|
sproutzaddr = self.nodes[0].z_getnewaddress('sprout')
|
||||||
|
|
||||||
|
# test that we can create a sapling zaddr before sapling activates
|
||||||
|
saplingzaddr = self.nodes[0].z_getnewaddress('sapling')
|
||||||
|
|
||||||
|
# we've got lots of coinbase (taddr) but no shielded funds yet
|
||||||
|
assert_equal(0, Decimal(self.nodes[0].z_gettotalbalance()['private']))
|
||||||
|
|
||||||
|
# Set current height to 201 -> Sprout
|
||||||
|
self.nodes[0].generate(1)
|
||||||
|
self.sync_all()
|
||||||
|
assert_equal(201, self.nodes[0].getblockcount())
|
||||||
|
|
||||||
|
mining_addr = self.nodes[0].listunspent()[0]['address']
|
||||||
|
|
||||||
|
# Shield coinbase funds (must be a multiple of 10, no change allowed pre-sapling)
|
||||||
|
receive_amount_10 = Decimal('10.0') - Decimal('0.0001')
|
||||||
|
recipients = [{"address":sproutzaddr, "amount":receive_amount_10}]
|
||||||
|
myopid = self.nodes[0].z_sendmany(mining_addr, recipients)
|
||||||
|
txid_1 = wait_and_assert_operationid_status(self.nodes[0], myopid)
|
||||||
|
self.sync_all()
|
||||||
|
|
||||||
|
# No funds (with (default) one or more confirmations) in sproutzaddr yet
|
||||||
|
assert_equal(0, len(self.nodes[0].z_listunspent()))
|
||||||
|
assert_equal(0, len(self.nodes[0].z_listunspent(1)))
|
||||||
|
|
||||||
|
# no private balance because no confirmations yet
|
||||||
|
assert_equal(0, Decimal(self.nodes[0].z_gettotalbalance()['private']))
|
||||||
|
|
||||||
|
# list private unspent, this time allowing 0 confirmations
|
||||||
|
unspent_cb = self.nodes[0].z_listunspent(0)
|
||||||
|
assert_equal(1, len(unspent_cb))
|
||||||
|
assert_equal(False, unspent_cb[0]['change'])
|
||||||
|
assert_equal(txid_1, unspent_cb[0]['txid'])
|
||||||
|
assert_equal(True, unspent_cb[0]['spendable'])
|
||||||
|
assert_equal(sproutzaddr, unspent_cb[0]['address'])
|
||||||
|
assert_equal(receive_amount_10, unspent_cb[0]['amount'])
|
||||||
|
|
||||||
|
# list unspent, filtering by address, should produce same result
|
||||||
|
unspent_cb_filter = self.nodes[0].z_listunspent(0, 9999, False, [sproutzaddr])
|
||||||
|
assert_equal(unspent_cb, unspent_cb_filter)
|
||||||
|
|
||||||
|
# Generate a block to confirm shield coinbase tx
|
||||||
|
self.nodes[0].generate(1)
|
||||||
|
self.sync_all()
|
||||||
|
|
||||||
|
# Current height = 202 -> Overwinter. Default address type remains Sprout
|
||||||
|
assert_equal(202, self.nodes[0].getblockcount())
|
||||||
|
|
||||||
|
# Send 1.0 (actually 0.9999) from sproutzaddr to a new zaddr
|
||||||
|
sproutzaddr2 = self.nodes[0].z_getnewaddress()
|
||||||
|
receive_amount_1 = Decimal('1.0') - Decimal('0.0001')
|
||||||
|
change_amount_9 = receive_amount_10 - Decimal('1.0')
|
||||||
|
assert_equal('sprout', self.nodes[0].z_validateaddress(sproutzaddr2)['type'])
|
||||||
|
recipients = [{"address": sproutzaddr2, "amount":receive_amount_1}]
|
||||||
|
myopid = self.nodes[0].z_sendmany(sproutzaddr, recipients)
|
||||||
|
txid_2 = wait_and_assert_operationid_status(self.nodes[0], myopid)
|
||||||
|
self.sync_all()
|
||||||
|
|
||||||
|
# list unspent, allowing 0conf txs
|
||||||
|
unspent_tx = self.nodes[0].z_listunspent(0)
|
||||||
|
assert_equal(len(unspent_tx), 2)
|
||||||
|
# sort low-to-high by amount (order of returned entries is not guaranteed)
|
||||||
|
unspent_tx = sorted(unspent_tx, key=lambda k: k['amount'])
|
||||||
|
assert_equal(False, unspent_tx[0]['change'])
|
||||||
|
assert_equal(txid_2, unspent_tx[0]['txid'])
|
||||||
|
assert_equal(True, unspent_tx[0]['spendable'])
|
||||||
|
assert_equal(sproutzaddr2, unspent_tx[0]['address'])
|
||||||
|
assert_equal(receive_amount_1, unspent_tx[0]['amount'])
|
||||||
|
|
||||||
|
assert_equal(True, unspent_tx[1]['change'])
|
||||||
|
assert_equal(txid_2, unspent_tx[1]['txid'])
|
||||||
|
assert_equal(True, unspent_tx[1]['spendable'])
|
||||||
|
assert_equal(sproutzaddr, unspent_tx[1]['address'])
|
||||||
|
assert_equal(change_amount_9, unspent_tx[1]['amount'])
|
||||||
|
|
||||||
|
unspent_tx_filter = self.nodes[0].z_listunspent(0, 9999, False, [sproutzaddr2])
|
||||||
|
assert_equal(1, len(unspent_tx_filter))
|
||||||
|
assert_equal(unspent_tx[0], unspent_tx_filter[0])
|
||||||
|
|
||||||
|
unspent_tx_filter = self.nodes[0].z_listunspent(0, 9999, False, [sproutzaddr])
|
||||||
|
assert_equal(1, len(unspent_tx_filter))
|
||||||
|
assert_equal(unspent_tx[1], unspent_tx_filter[0])
|
||||||
|
|
||||||
|
# Set current height to 204 -> Sapling
|
||||||
|
self.nodes[0].generate(2)
|
||||||
|
self.sync_all()
|
||||||
|
assert_equal(204, self.nodes[0].getblockcount())
|
||||||
|
|
||||||
|
# No funds in saplingzaddr yet
|
||||||
|
assert_equal(0, len(self.nodes[0].z_listunspent(0, 9999, False, [saplingzaddr])))
|
||||||
|
|
||||||
|
# Send 0.9999 to our sapling zaddr
|
||||||
|
# (sending from a sprout zaddr to a sapling zaddr is disallowed,
|
||||||
|
# so send from coin base)
|
||||||
|
receive_amount_2 = Decimal('2.0') - Decimal('0.0001')
|
||||||
|
recipients = [{"address": saplingzaddr, "amount":receive_amount_2}]
|
||||||
|
myopid = self.nodes[0].z_sendmany(mining_addr, recipients)
|
||||||
|
txid_3 = wait_and_assert_operationid_status(self.nodes[0], myopid)
|
||||||
|
self.sync_all()
|
||||||
|
unspent_tx = self.nodes[0].z_listunspent(0)
|
||||||
|
assert_equal(3, len(unspent_tx))
|
||||||
|
|
||||||
|
# low-to-high in amount
|
||||||
|
unspent_tx = sorted(unspent_tx, key=lambda k: k['amount'])
|
||||||
|
|
||||||
|
assert_equal(False, unspent_tx[0]['change'])
|
||||||
|
assert_equal(txid_2, unspent_tx[0]['txid'])
|
||||||
|
assert_equal(True, unspent_tx[0]['spendable'])
|
||||||
|
assert_equal(sproutzaddr2, unspent_tx[0]['address'])
|
||||||
|
assert_equal(receive_amount_1, unspent_tx[0]['amount'])
|
||||||
|
|
||||||
|
assert_equal(False, unspent_tx[1]['change'])
|
||||||
|
assert_equal(txid_3, unspent_tx[1]['txid'])
|
||||||
|
assert_equal(True, unspent_tx[1]['spendable'])
|
||||||
|
assert_equal(saplingzaddr, unspent_tx[1]['address'])
|
||||||
|
assert_equal(receive_amount_2, unspent_tx[1]['amount'])
|
||||||
|
|
||||||
|
assert_equal(True, unspent_tx[2]['change'])
|
||||||
|
assert_equal(txid_2, unspent_tx[2]['txid'])
|
||||||
|
assert_equal(True, unspent_tx[2]['spendable'])
|
||||||
|
assert_equal(sproutzaddr, unspent_tx[2]['address'])
|
||||||
|
assert_equal(change_amount_9, unspent_tx[2]['amount'])
|
||||||
|
|
||||||
|
unspent_tx_filter = self.nodes[0].z_listunspent(0, 9999, False, [saplingzaddr])
|
||||||
|
assert_equal(1, len(unspent_tx_filter))
|
||||||
|
assert_equal(unspent_tx[1], unspent_tx_filter[0])
|
||||||
|
|
||||||
|
# test that pre- and post-sapling can be filtered in a single call
|
||||||
|
unspent_tx_filter = self.nodes[0].z_listunspent(0, 9999, False,
|
||||||
|
[sproutzaddr, saplingzaddr])
|
||||||
|
assert_equal(2, len(unspent_tx_filter))
|
||||||
|
unspent_tx_filter = sorted(unspent_tx_filter, key=lambda k: k['amount'])
|
||||||
|
assert_equal(unspent_tx[1], unspent_tx_filter[0])
|
||||||
|
assert_equal(unspent_tx[2], unspent_tx_filter[1])
|
||||||
|
|
||||||
|
# so far, this node has no watchonly addresses, so results are the same
|
||||||
|
unspent_tx_watchonly = self.nodes[0].z_listunspent(0, 9999, True)
|
||||||
|
unspent_tx_watchonly = sorted(unspent_tx_watchonly, key=lambda k: k['amount'])
|
||||||
|
assert_equal(unspent_tx, unspent_tx_watchonly)
|
||||||
|
|
||||||
|
# TODO: use z_exportviewingkey, z_importviewingkey to test includeWatchonly
|
||||||
|
# but this requires Sapling support for those RPCs
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
WalletListNotes().main()
|
|
@ -2458,12 +2458,13 @@ UniValue z_listunspent(const UniValue& params, bool fHelp)
|
||||||
"Optionally filter to only include notes sent to specified addresses.\n"
|
"Optionally filter to only include notes sent to specified addresses.\n"
|
||||||
"When minconf is 0, unspent notes with zero confirmations are returned, even though they are not immediately spendable.\n"
|
"When minconf is 0, unspent notes with zero confirmations are returned, even though they are not immediately spendable.\n"
|
||||||
"Results are an array of Objects, each of which has:\n"
|
"Results are an array of Objects, each of which has:\n"
|
||||||
"{txid, jsindex, jsoutindex, confirmations, address, amount, memo}\n"
|
"{txid, jsindex, jsoutindex, confirmations, address, amount, memo} (Sprout)\n"
|
||||||
|
"{txid, outindex, confirmations, address, amount, memo} (Sapling)\n"
|
||||||
"\nArguments:\n"
|
"\nArguments:\n"
|
||||||
"1. minconf (numeric, optional, default=1) The minimum confirmations to filter\n"
|
"1. minconf (numeric, optional, default=1) The minimum confirmations to filter\n"
|
||||||
"2. maxconf (numeric, optional, default=9999999) The maximum confirmations to filter\n"
|
"2. maxconf (numeric, optional, default=9999999) The maximum confirmations to filter\n"
|
||||||
"3. includeWatchonly (bool, optional, default=false) Also include watchonly addresses (see 'z_importviewingkey')\n"
|
"3. includeWatchonly (bool, optional, default=false) Also include watchonly addresses (see 'z_importviewingkey')\n"
|
||||||
"4. \"addresses\" (string) A json array of zaddrs to filter on. Duplicate addresses not allowed.\n"
|
"4. \"addresses\" (string) A json array of zaddrs (both Sprout and Sapling) to filter on. Duplicate addresses not allowed.\n"
|
||||||
" [\n"
|
" [\n"
|
||||||
" \"address\" (string) zaddr\n"
|
" \"address\" (string) zaddr\n"
|
||||||
" ,...\n"
|
" ,...\n"
|
||||||
|
@ -2473,7 +2474,8 @@ UniValue z_listunspent(const UniValue& params, bool fHelp)
|
||||||
" {\n"
|
" {\n"
|
||||||
" \"txid\" : \"txid\", (string) the transaction id \n"
|
" \"txid\" : \"txid\", (string) the transaction id \n"
|
||||||
" \"jsindex\" : n (numeric) the joinsplit index\n"
|
" \"jsindex\" : n (numeric) the joinsplit index\n"
|
||||||
" \"jsoutindex\" : n (numeric) the output index of the joinsplit\n"
|
" \"jsoutindex\" (sprout) : n (numeric) the output index of the joinsplit\n"
|
||||||
|
" \"outindex\" (sapling) : n (numeric) the output index\n"
|
||||||
" \"confirmations\" : n (numeric) the number of confirmations\n"
|
" \"confirmations\" : n (numeric) the number of confirmations\n"
|
||||||
" \"spendable\" : true|false (boolean) true if note can be spent by wallet, false if note has zero confirmations, false if address is watchonly\n"
|
" \"spendable\" : true|false (boolean) true if note can be spent by wallet, false if note has zero confirmations, false if address is watchonly\n"
|
||||||
" \"address\" : \"address\", (string) the shielded address\n"
|
" \"address\" : \"address\", (string) the shielded address\n"
|
||||||
|
@ -2533,17 +2535,14 @@ UniValue z_listunspent(const UniValue& params, bool fHelp)
|
||||||
}
|
}
|
||||||
string address = o.get_str();
|
string address = o.get_str();
|
||||||
auto zaddr = DecodePaymentAddress(address);
|
auto zaddr = DecodePaymentAddress(address);
|
||||||
if (IsValidPaymentAddress(zaddr)) {
|
if (!IsValidPaymentAddress(zaddr)) {
|
||||||
// TODO: Add Sapling support. For now, ensure we can freely convert.
|
|
||||||
assert(boost::get<libzcash::SproutPaymentAddress>(&zaddr) != nullptr);
|
|
||||||
libzcash::SproutPaymentAddress addr = boost::get<libzcash::SproutPaymentAddress>(zaddr);
|
|
||||||
if (!fIncludeWatchonly && !pwalletMain->HaveSproutSpendingKey(addr)) {
|
|
||||||
throw JSONRPCError(RPC_INVALID_PARAMETER, string("Invalid parameter, spending key for address does not belong to wallet: ") + address);
|
|
||||||
}
|
|
||||||
zaddrs.insert(addr);
|
|
||||||
} else {
|
|
||||||
throw JSONRPCError(RPC_INVALID_PARAMETER, string("Invalid parameter, address is not a valid zaddr: ") + address);
|
throw JSONRPCError(RPC_INVALID_PARAMETER, string("Invalid parameter, address is not a valid zaddr: ") + address);
|
||||||
}
|
}
|
||||||
|
auto hasSpendingKey = boost::apply_visitor(HaveSpendingKeyForPaymentAddress(pwalletMain), zaddr);
|
||||||
|
if (!fIncludeWatchonly && !hasSpendingKey) {
|
||||||
|
throw JSONRPCError(RPC_INVALID_PARAMETER, string("Invalid parameter, spending key for address does not belong to wallet: ") + address);
|
||||||
|
}
|
||||||
|
zaddrs.insert(zaddr);
|
||||||
|
|
||||||
if (setAddress.count(address)) {
|
if (setAddress.count(address)) {
|
||||||
throw JSONRPCError(RPC_INVALID_PARAMETER, string("Invalid parameter, duplicated address: ") + address);
|
throw JSONRPCError(RPC_INVALID_PARAMETER, string("Invalid parameter, duplicated address: ") + address);
|
||||||
|
@ -2553,10 +2552,15 @@ UniValue z_listunspent(const UniValue& params, bool fHelp)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// User did not provide zaddrs, so use default i.e. all addresses
|
// User did not provide zaddrs, so use default i.e. all addresses
|
||||||
// TODO: Add Sapling support
|
|
||||||
std::set<libzcash::SproutPaymentAddress> sproutzaddrs = {};
|
std::set<libzcash::SproutPaymentAddress> sproutzaddrs = {};
|
||||||
pwalletMain->GetSproutPaymentAddresses(sproutzaddrs);
|
pwalletMain->GetSproutPaymentAddresses(sproutzaddrs);
|
||||||
|
|
||||||
|
// Sapling support
|
||||||
|
std::set<libzcash::SaplingPaymentAddress> saplingzaddrs = {};
|
||||||
|
pwalletMain->GetSaplingPaymentAddresses(saplingzaddrs);
|
||||||
|
|
||||||
zaddrs.insert(sproutzaddrs.begin(), sproutzaddrs.end());
|
zaddrs.insert(sproutzaddrs.begin(), sproutzaddrs.end());
|
||||||
|
zaddrs.insert(saplingzaddrs.begin(), saplingzaddrs.end());
|
||||||
}
|
}
|
||||||
|
|
||||||
UniValue results(UniValue::VARR);
|
UniValue results(UniValue::VARR);
|
||||||
|
@ -2566,6 +2570,7 @@ UniValue z_listunspent(const UniValue& params, bool fHelp)
|
||||||
std::vector<UnspentSaplingNoteEntry> saplingEntries;
|
std::vector<UnspentSaplingNoteEntry> saplingEntries;
|
||||||
pwalletMain->GetUnspentFilteredNotes(sproutEntries, saplingEntries, zaddrs, nMinDepth, nMaxDepth, !fIncludeWatchonly);
|
pwalletMain->GetUnspentFilteredNotes(sproutEntries, saplingEntries, zaddrs, nMinDepth, nMaxDepth, !fIncludeWatchonly);
|
||||||
std::set<std::pair<PaymentAddress, uint256>> nullifierSet = pwalletMain->GetNullifiersForAddresses(zaddrs);
|
std::set<std::pair<PaymentAddress, uint256>> nullifierSet = pwalletMain->GetNullifiersForAddresses(zaddrs);
|
||||||
|
|
||||||
for (CUnspentSproutNotePlaintextEntry & entry : sproutEntries) {
|
for (CUnspentSproutNotePlaintextEntry & entry : sproutEntries) {
|
||||||
UniValue obj(UniValue::VOBJ);
|
UniValue obj(UniValue::VOBJ);
|
||||||
obj.push_back(Pair("txid", entry.jsop.hash.ToString()));
|
obj.push_back(Pair("txid", entry.jsop.hash.ToString()));
|
||||||
|
@ -2583,7 +2588,26 @@ UniValue z_listunspent(const UniValue& params, bool fHelp)
|
||||||
}
|
}
|
||||||
results.push_back(obj);
|
results.push_back(obj);
|
||||||
}
|
}
|
||||||
// TODO: Sapling
|
|
||||||
|
for (UnspentSaplingNoteEntry & entry : saplingEntries) {
|
||||||
|
UniValue obj(UniValue::VOBJ);
|
||||||
|
obj.push_back(Pair("txid", entry.op.hash.ToString()));
|
||||||
|
obj.push_back(Pair("outindex", (int)entry.op.n));
|
||||||
|
obj.push_back(Pair("confirmations", entry.nHeight));
|
||||||
|
libzcash::SaplingIncomingViewingKey ivk;
|
||||||
|
libzcash::SaplingFullViewingKey fvk;
|
||||||
|
pwalletMain->GetSaplingIncomingViewingKey(boost::get<libzcash::SaplingPaymentAddress>(entry.address), ivk);
|
||||||
|
pwalletMain->GetSaplingFullViewingKey(ivk, fvk);
|
||||||
|
bool hasSaplingSpendingKey = pwalletMain->HaveSaplingSpendingKey(fvk);
|
||||||
|
obj.push_back(Pair("spendable", hasSaplingSpendingKey));
|
||||||
|
obj.push_back(Pair("address", EncodePaymentAddress(entry.address)));
|
||||||
|
obj.push_back(Pair("amount", ValueFromAmount(CAmount(entry.note.value())))); // note.value() is equivalent to plaintext.value()
|
||||||
|
obj.push_back(Pair("memo", HexStr(entry.memo)));
|
||||||
|
if (hasSaplingSpendingKey) {
|
||||||
|
obj.push_back(Pair("change", pwalletMain->IsNoteSaplingChange(nullifierSet, entry.address, entry.op)));
|
||||||
|
}
|
||||||
|
results.push_back(obj);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return results;
|
return results;
|
||||||
|
|
Loading…
Reference in New Issue