Update z_listreceivedbyaddress to support unified addresses (5467)
This commit is contained in:
parent
d2b900a0c8
commit
c48e35bb66
|
@ -10,11 +10,12 @@ from test_framework.util import (
|
|||
assert_true,
|
||||
assert_false,
|
||||
assert_raises_message,
|
||||
connect_nodes_bi,
|
||||
get_coinbase_address,
|
||||
DEFAULT_FEE,
|
||||
DEFAULT_FEE_ZATS
|
||||
)
|
||||
from test_framework.util import wait_and_assert_operationid_status
|
||||
from test_framework.util import wait_and_assert_operationid_status, start_nodes
|
||||
from decimal import Decimal
|
||||
|
||||
my_memo_str = 'c0ffee' # stay awake
|
||||
|
@ -24,6 +25,20 @@ my_memo = my_memo + '0'*(1024-len(my_memo))
|
|||
no_memo = 'f6' + ('0'*1022) # see section 5.5 of the protocol spec
|
||||
|
||||
class ListReceivedTest (BitcoinTestFramework):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.num_nodes = 3
|
||||
self.setup_clean_chain = True
|
||||
|
||||
def setup_network(self):
|
||||
self.nodes = start_nodes(
|
||||
self.num_nodes, self.options.tmpdir,
|
||||
extra_args=[['-experimentalfeatures', '-orchardwallet']] * self.num_nodes)
|
||||
connect_nodes_bi(self.nodes, 0, 1)
|
||||
connect_nodes_bi(self.nodes, 1, 2)
|
||||
self.is_network_split = False
|
||||
self.sync_all()
|
||||
|
||||
def generate_and_sync(self, new_height):
|
||||
current_height = self.nodes[0].getblockcount()
|
||||
assert(new_height > current_height)
|
||||
|
@ -40,7 +55,7 @@ class ListReceivedTest (BitcoinTestFramework):
|
|||
result = self.nodes[0].z_shieldcoinbase(get_coinbase_address(self.nodes[0]), zaddr1, 0, 1)
|
||||
txid_shielding1 = wait_and_assert_operationid_status(self.nodes[0], result['opid'])
|
||||
|
||||
zaddrExt = self.nodes[3].z_getnewaddress('sprout')
|
||||
zaddrExt = self.nodes[2].z_getnewaddress('sprout')
|
||||
result = self.nodes[0].z_shieldcoinbase(get_coinbase_address(self.nodes[0]), zaddrExt, 0, 1)
|
||||
txid_shieldingExt = wait_and_assert_operationid_status(self.nodes[0], result['opid'])
|
||||
|
||||
|
@ -72,8 +87,8 @@ class ListReceivedTest (BitcoinTestFramework):
|
|||
self.nodes[1].z_viewtransaction,
|
||||
txid_shieldingExt)
|
||||
|
||||
# Second transaction should be visible on node3
|
||||
pt = self.nodes[3].z_viewtransaction(txid_shieldingExt)
|
||||
# Second transaction should be visible on node0
|
||||
pt = self.nodes[2].z_viewtransaction(txid_shieldingExt)
|
||||
assert_equal(pt['txid'], txid_shieldingExt)
|
||||
assert_equal(len(pt['spends']), 0)
|
||||
assert_equal(len(pt['outputs']), 1)
|
||||
|
@ -190,9 +205,9 @@ class ListReceivedTest (BitcoinTestFramework):
|
|||
self.generate_and_sync(height+1)
|
||||
taddr = self.nodes[1].getnewaddress()
|
||||
zaddr1 = self.nodes[1].z_getnewaddress('sapling')
|
||||
zaddrExt = self.nodes[3].z_getnewaddress('sapling')
|
||||
zaddrExt = self.nodes[2].z_getnewaddress('sapling')
|
||||
|
||||
self.nodes[0].sendtoaddress(taddr, 4.0)
|
||||
txid_taddr = self.nodes[0].sendtoaddress(taddr, 4.0)
|
||||
self.generate_and_sync(height+2)
|
||||
|
||||
# Send 1 ZEC to zaddr1
|
||||
|
@ -219,6 +234,7 @@ class ListReceivedTest (BitcoinTestFramework):
|
|||
|
||||
assert_equal(pt['outputs'][0]['type'], 'sapling')
|
||||
if pt['outputs'][0]['address'] == zaddr1:
|
||||
assert_false('jsOutput' in pt['outputs'][0])
|
||||
assert_equal(pt['outputs'][0]['outgoing'], False)
|
||||
assert_equal(pt['outputs'][0]['memoStr'], my_memo_str)
|
||||
else:
|
||||
|
@ -380,9 +396,96 @@ class ListReceivedTest (BitcoinTestFramework):
|
|||
c = self.nodes[1].z_getnotescount(0)
|
||||
assert_equal(3, c['sapling'], "Count of unconfirmed notes should be 3(2 in zaddr1 + 1 in zaddr2)")
|
||||
|
||||
# As part of UA support, a transparent address is now accepted
|
||||
r = self.nodes[1].z_listreceivedbyaddress(taddr, 0)
|
||||
assert_equal(len(r), 1)
|
||||
assert_equal(r[0]['pool'], 'transparent')
|
||||
assert_equal(r[0]['txid'], txid_taddr)
|
||||
assert_equal(r[0]['amount'], Decimal('4'))
|
||||
assert_equal(r[0]['amountZat'], 400000000)
|
||||
assert_equal(r[0]['confirmations'], 3)
|
||||
assert r[0]['outindex'] < 2
|
||||
|
||||
# Test unified address
|
||||
node = self.nodes[1]
|
||||
|
||||
# Create a unified address on one node, try z_listreceivedbyaddress on another node
|
||||
account = self.nodes[0].z_getnewaccount()['account']
|
||||
r = self.nodes[0].z_getaddressforaccount(account)
|
||||
unified_addr = r['unifiedaddress']
|
||||
# this address isn't in node1's wallet
|
||||
assert_raises_message(
|
||||
JSONRPCException,
|
||||
"From address does not belong to this node",
|
||||
node.z_listreceivedbyaddress, unified_addr, 0)
|
||||
|
||||
# create a UA on node1
|
||||
r = node.z_getnewaccount()
|
||||
account = r['account']
|
||||
r = node.z_getaddressforaccount(account)
|
||||
unified_addr = r['unifiedaddress']
|
||||
receivers = node.z_listunifiedreceivers(unified_addr)
|
||||
assert_equal(len(receivers), 2)
|
||||
assert 'transparent' in receivers
|
||||
assert 'sapling' in receivers
|
||||
# Wallet contains no notes
|
||||
r = node.z_listreceivedbyaddress(unified_addr, 0)
|
||||
assert_equal(len(r), 0, "unified_addr should have received zero notes")
|
||||
|
||||
# Create a note in this UA on node1
|
||||
opid = node.z_sendmany(zaddr1, [{'address': unified_addr, 'amount': 0.1}])
|
||||
txid_sapling = wait_and_assert_operationid_status(node, opid)
|
||||
self.generate_and_sync(height+5)
|
||||
|
||||
# Create a UTXO that unified_address's transparent component references, on node1
|
||||
outputs = {receivers['transparent']: 0.2}
|
||||
txid_taddr = node.sendmany("", outputs)
|
||||
|
||||
r = node.z_listreceivedbyaddress(unified_addr, 0)
|
||||
assert_equal(len(r), 2, "unified_addr should have received 2 payments")
|
||||
print(r)
|
||||
outputs = [];
|
||||
outputs.append({
|
||||
'pool': r[0]['pool'],
|
||||
'txid': r[0]['txid'],
|
||||
'amount': r[0]['amount'],
|
||||
'amountZat': r[0]['amountZat'],
|
||||
'change': r[0]['change'] if 'change' in r[0] else None,
|
||||
'memo': r[0]['memo'] if 'memo' in r[0] else None,
|
||||
'confirmations': r[0]['confirmations']
|
||||
})
|
||||
outputs.append({
|
||||
'pool': r[1]['pool'],
|
||||
'txid': r[1]['txid'],
|
||||
'amount': r[1]['amount'],
|
||||
'amountZat': r[1]['amountZat'],
|
||||
'change': r[1]['change'] if 'change' in r[1] else None,
|
||||
'memo': r[1]['memo'] if 'memo' in r[1] else None,
|
||||
'confirmations': r[1]['confirmations']
|
||||
})
|
||||
assert({
|
||||
'pool': 'sapling',
|
||||
'txid': txid_sapling,
|
||||
'amount': Decimal('0.1'),
|
||||
'amountZat': 10000000,
|
||||
'change': False,
|
||||
'memo': no_memo,
|
||||
'confirmations': 0,
|
||||
} in outputs)
|
||||
assert({
|
||||
'pool': 'transparent',
|
||||
'txid': txid_taddr,
|
||||
'amount': Decimal('0.2'),
|
||||
'amountZat': 20000000,
|
||||
'change': None,
|
||||
'memo': None,
|
||||
'confirmations': 0,
|
||||
} in outputs)
|
||||
|
||||
def run_test(self):
|
||||
self.test_received_sprout(200)
|
||||
self.test_received_sapling(214)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
ListReceivedTest().main()
|
||||
|
|
|
@ -2271,8 +2271,7 @@ UniValue z_listunspent(const UniValue& params, bool fHelp)
|
|||
" \"type\" : \"sprout|sapling|orchard\", (string) The shielded pool\n"
|
||||
" \"jsindex\" (sprout) : n, (numeric) the joinsplit index\n"
|
||||
" \"jsoutindex\" (sprout) : n, (numeric) the output index of the joinsplit\n"
|
||||
" \"outindex\" (sapling) : n, (numeric) the output index\n"
|
||||
" \"actionindex\" (orchard) : n, (numeric) the output index\n"
|
||||
" \"outindex\" (transparent, sapling, orchard) : n, (numeric) the output index\n"
|
||||
" \"confirmations\" : n, (numeric) the number of confirmations\n"
|
||||
" \"spendable\" : true|false, (boolean) true if note can be spent by wallet, false if address is watchonly\n"
|
||||
" \"address\" : \"address\", (string) the shielded address\n"
|
||||
|
@ -3432,6 +3431,7 @@ UniValue z_listreceivedbyaddress(const UniValue& params, bool fHelp)
|
|||
"2. minconf (numeric, optional, default=1) Only include transactions confirmed at least this many times.\n"
|
||||
"\nResult (output indices for only one pool will be present):\n"
|
||||
"{\n"
|
||||
" \"pool\": \"pool\" (string) one of (\"transparent\", \"sprout\", \"sapling\", \"orchard\")\n"
|
||||
" \"txid\": \"txid\", (string) the transaction id\n"
|
||||
" \"amount\": xxxxx, (numeric) the amount of value in the note\n"
|
||||
" \"amountZat\" : xxxx (numeric) The amount in " + MINOR_CURRENCY_UNIT + "\n"
|
||||
|
@ -3442,8 +3442,7 @@ UniValue z_listreceivedbyaddress(const UniValue& params, bool fHelp)
|
|||
" \"blocktime\": xxx, (numeric) The transaction time in seconds since epoch (midnight Jan 1 1970 GMT).\n"
|
||||
" \"jsindex\" (sprout) : n, (numeric) the joinsplit index\n"
|
||||
" \"jsoutindex\" (sprout) : n, (numeric) the output index of the joinsplit\n"
|
||||
" \"outindex\" (sapling) : n, (numeric) the output index\n"
|
||||
" \"actionindex\" (orchard) : n, (numeric) the output index\n"
|
||||
" \"outindex\" (transparent, sapling, orchard) : n, (numeric) the output index for transparent and Sapling outputs, or the action index for Orchard\n"
|
||||
" \"change\": true|false, (boolean) true if the address that received the note is also one of the sending addresses\n"
|
||||
"}\n"
|
||||
"\nExamples:\n"
|
||||
|
@ -3460,6 +3459,7 @@ UniValue z_listreceivedbyaddress(const UniValue& params, bool fHelp)
|
|||
if (nMinDepth < 0) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Minimum number of confirmations cannot be less than 0");
|
||||
}
|
||||
UniValue result(UniValue::VARR);
|
||||
|
||||
// Check that the from address is valid.
|
||||
auto fromaddress = params[0].get_str();
|
||||
|
@ -3475,19 +3475,66 @@ UniValue z_listreceivedbyaddress(const UniValue& params, bool fHelp)
|
|||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "From address does not belong to this node, zaddr spending key or viewing key not found.");
|
||||
}
|
||||
|
||||
UniValue result(UniValue::VARR);
|
||||
std::vector<SproutNoteEntry> sproutEntries;
|
||||
std::vector<SaplingNoteEntry> saplingEntries;
|
||||
auto noteFilter = AddrSet::ForPaymentAddresses(std::vector({decoded.value()}));
|
||||
pwalletMain->GetFilteredNotes(sproutEntries, saplingEntries, noteFilter, nMinDepth, INT_MAX, false, false);
|
||||
|
||||
auto push_transparent_result = [&](const CTxDestination& dest) -> void {
|
||||
const CScript scriptPubKey{GetScriptForDestination(dest)};
|
||||
for (const auto& [_txid, wtx] : pwalletMain->mapWallet) {
|
||||
if (!CheckFinalTx(wtx))
|
||||
continue;
|
||||
|
||||
int nDepth = wtx.GetDepthInMainChain();
|
||||
if (nDepth < nMinDepth) continue;
|
||||
for (size_t i = 0; i < wtx.vout.size(); ++i) {
|
||||
const CTxOut& txout{wtx.vout[i]};
|
||||
if (txout.scriptPubKey == scriptPubKey) {
|
||||
UniValue obj(UniValue::VOBJ);
|
||||
obj.pushKV("pool", "transparent");
|
||||
obj.pushKV("txid", wtx.GetHash().ToString());
|
||||
obj.pushKV("amount", ValueFromAmount(txout.nValue));
|
||||
obj.pushKV("amountZat", txout.nValue);
|
||||
obj.pushKV("outindex", int(i));
|
||||
obj.pushKV("confirmations", nDepth);
|
||||
result.push_back(obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
auto push_sapling_result = [&](const libzcash::SaplingPaymentAddress& addr) -> void {
|
||||
bool hasSpendingKey = pwalletMain->HaveSaplingSpendingKeyForAddress(addr);
|
||||
std::set<std::pair<libzcash::SaplingPaymentAddress, uint256>> nullifierSet;
|
||||
if (hasSpendingKey) {
|
||||
nullifierSet = pwalletMain->GetSaplingNullifiers({addr});
|
||||
}
|
||||
for (const SaplingNoteEntry& entry : saplingEntries) {
|
||||
UniValue obj(UniValue::VOBJ);
|
||||
obj.pushKV("pool", "sapling");
|
||||
obj.pushKV("txid", entry.op.hash.ToString());
|
||||
obj.pushKV("amount", ValueFromAmount(CAmount(entry.note.value())));
|
||||
obj.pushKV("amountZat", CAmount(entry.note.value()));
|
||||
obj.pushKV("memo", HexStr(entry.memo));
|
||||
obj.pushKV("outindex", (int)entry.op.n);
|
||||
obj.pushKV("confirmations", entry.confirmations);
|
||||
|
||||
txblock BlockData(entry.op.hash);
|
||||
obj.pushKV("blockheight", BlockData.height);
|
||||
obj.pushKV("blockindex", BlockData.index);
|
||||
obj.pushKV("blocktime", BlockData.time);
|
||||
|
||||
if (hasSpendingKey) {
|
||||
obj.pushKV("change", pwalletMain->IsNoteSaplingChange(nullifierSet, entry.address, entry.op));
|
||||
}
|
||||
result.push_back(obj);
|
||||
}
|
||||
};
|
||||
|
||||
std::visit(match {
|
||||
[&](const CKeyID& addr) {
|
||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transparent addresses are not supported by this endpoint.");
|
||||
},
|
||||
[&](const CScriptID& addr) {
|
||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transparent addresses are not supported by this endpoint.");
|
||||
},
|
||||
[&](const CKeyID& addr) { push_transparent_result(addr); },
|
||||
[&](const CScriptID& addr) { push_transparent_result(addr); },
|
||||
[&](const libzcash::SproutPaymentAddress& addr) {
|
||||
bool hasSpendingKey = pwalletMain->HaveSproutSpendingKey(addr);
|
||||
std::set<std::pair<libzcash::SproutPaymentAddress, uint256>> nullifierSet;
|
||||
|
@ -3496,6 +3543,7 @@ UniValue z_listreceivedbyaddress(const UniValue& params, bool fHelp)
|
|||
}
|
||||
for (const SproutNoteEntry& entry : sproutEntries) {
|
||||
UniValue obj(UniValue::VOBJ);
|
||||
obj.pushKV("pool", "sprout");
|
||||
obj.pushKV("txid", entry.jsop.hash.ToString());
|
||||
obj.pushKV("amount", ValueFromAmount(CAmount(entry.note.value())));
|
||||
obj.pushKV("amountZat", CAmount(entry.note.value()));
|
||||
|
@ -3517,36 +3565,28 @@ UniValue z_listreceivedbyaddress(const UniValue& params, bool fHelp)
|
|||
}
|
||||
},
|
||||
[&](const libzcash::SaplingPaymentAddress& addr) {
|
||||
bool hasSpendingKey = pwalletMain->HaveSaplingSpendingKeyForAddress(addr);
|
||||
std::set<std::pair<libzcash::SaplingPaymentAddress, uint256>> nullifierSet;
|
||||
if (hasSpendingKey) {
|
||||
nullifierSet = pwalletMain->GetSaplingNullifiers({addr});
|
||||
}
|
||||
for (const SaplingNoteEntry& entry : saplingEntries) {
|
||||
UniValue obj(UniValue::VOBJ);
|
||||
obj.pushKV("txid", entry.op.hash.ToString());
|
||||
obj.pushKV("amount", ValueFromAmount(CAmount(entry.note.value())));
|
||||
obj.pushKV("amountZat", CAmount(entry.note.value()));
|
||||
obj.pushKV("memo", HexStr(entry.memo));
|
||||
obj.pushKV("outindex", (int)entry.op.n);
|
||||
obj.pushKV("confirmations", entry.confirmations);
|
||||
|
||||
txblock BlockData(entry.op.hash);
|
||||
obj.pushKV("blockheight", BlockData.height);
|
||||
obj.pushKV("blockindex", BlockData.index);
|
||||
obj.pushKV("blocktime", BlockData.time);
|
||||
|
||||
if (hasSpendingKey) {
|
||||
obj.pushKV("change", pwalletMain->IsNoteSaplingChange(nullifierSet, entry.address, entry.op));
|
||||
}
|
||||
result.push_back(obj);
|
||||
}
|
||||
push_sapling_result(addr);
|
||||
},
|
||||
[&](const libzcash::UnifiedAddress& addr) {
|
||||
// TODO UNIFIED
|
||||
for (const auto& receiver : addr) {
|
||||
std::visit(match {
|
||||
[&](const libzcash::SaplingPaymentAddress& addr) {
|
||||
push_sapling_result(addr);
|
||||
},
|
||||
[&](const CScriptID& addr) {
|
||||
CTxDestination dest = addr;
|
||||
push_transparent_result(dest);
|
||||
},
|
||||
[&](const CKeyID& addr) {
|
||||
CTxDestination dest = addr;
|
||||
push_transparent_result(dest);
|
||||
},
|
||||
[&](const auto& other) { } // TODO orchard
|
||||
|
||||
}, receiver);
|
||||
}
|
||||
}
|
||||
}, decoded.value());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
|
@ -398,8 +398,6 @@ BOOST_AUTO_TEST_CASE(rpc_wallet_getbalance)
|
|||
BOOST_CHECK_THROW(CallRPC("z_listreceivedbyaddress too many args"), runtime_error);
|
||||
// negative minconf not allowed
|
||||
BOOST_CHECK_THROW(CallRPC("z_listreceivedbyaddress tmC6YZnCUhm19dEXxh3Jb7srdBJxDawaCab -1"), runtime_error);
|
||||
// invalid zaddr, taddr not allowed
|
||||
BOOST_CHECK_THROW(CallRPC("z_listreceivedbyaddress tmC6YZnCUhm19dEXxh3Jb7srdBJxDawaCab 0"), runtime_error);
|
||||
// don't have the spending key
|
||||
BOOST_CHECK_THROW(CallRPC("z_listreceivedbyaddress tnRZ8bPq2pff3xBWhTJhNkVUkm2uhzksDeW5PvEa7aFKGT9Qi3YgTALZfjaY4jU3HLVKBtHdSXxoPoLA3naMPcHBcY88FcF 1"), runtime_error);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue