#!/usr/bin/env python3 # Copyright (c) 2018 The Zcash developers # Distributed under the MIT software license, see the accompanying # file COPYING or https://www.opensource.org/licenses/mit-license.php . from test_framework.authproxy import JSONRPCException from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, assert_true, assert_false, assert_raises_message, connect_nodes_bi, get_coinbase_address, nuparams, DEFAULT_FEE, DEFAULT_FEE_ZATS, NU5_BRANCH_ID, ) from test_framework.util import wait_and_assert_operationid_status, start_nodes from decimal import Decimal my_memo_str = 'c0ffee' # stay awake my_memo = '633066666565' 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.cache_behavior = 'clean' def setup_network(self): self.nodes = start_nodes( self.num_nodes, self.options.tmpdir, extra_args=[[ nuparams(NU5_BRANCH_ID, 225), ]] * self.num_nodes ) 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 generate_and_sync(self, new_height): current_height = self.nodes[0].getblockcount() assert(new_height > current_height) self.sync_all() self.nodes[0].generate(new_height - current_height) self.sync_all() assert_equal(new_height, self.nodes[0].getblockcount()) def test_received_sprout(self, height): self.generate_and_sync(height+2) zaddr1 = self.nodes[1].z_getnewaddress('sprout') # Send 10 ZEC each zaddr1 and zaddrExt via z_shieldcoinbase 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[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']) self.sync_all() # Decrypted transaction details should not be visible on node 0 pt = self.nodes[0].z_viewtransaction(txid_shielding1) assert_equal(pt['txid'], txid_shielding1) assert_equal(len(pt['spends']), 0) assert_equal(len(pt['outputs']), 0) # Decrypted transaction details should be correct on node 1 pt = self.nodes[1].z_viewtransaction(txid_shielding1) assert_equal(pt['txid'], txid_shielding1) assert_equal(len(pt['spends']), 0) assert_equal(len(pt['outputs']), 1) assert_equal(pt['outputs'][0]['type'], 'sprout') assert_equal(pt['outputs'][0]['js'], 0) assert_equal(pt['outputs'][0]['address'], zaddr1) assert_equal(pt['outputs'][0]['value'], Decimal('10')) assert_equal(pt['outputs'][0]['valueZat'], 1000000000) assert_equal(pt['outputs'][0]['memo'], no_memo) jsOutputPrev = pt['outputs'][0]['jsOutput'] # Second transaction should not be known to node 1 assert_raises_message( JSONRPCException, "Invalid or non-wallet transaction id", self.nodes[1].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) assert_equal(pt['outputs'][0]['type'], 'sprout') assert_equal(pt['outputs'][0]['js'], 0) assert_equal(pt['outputs'][0]['address'], zaddrExt) assert_equal(pt['outputs'][0]['value'], Decimal('10')) assert_equal(pt['outputs'][0]['valueZat'], 1000000000) assert_equal(pt['outputs'][0]['memo'], no_memo) r = self.nodes[1].z_listreceivedbyaddress(zaddr1) assert_equal(0, len(r), "Should have received no confirmed note") c = self.nodes[1].z_getnotescount() assert_equal(0, c['sprout'], "Count of confirmed notes should be 0") # No confirmation required, one note should be present r = self.nodes[1].z_listreceivedbyaddress(zaddr1, 0) assert_equal(1, len(r), "Should have received one (unconfirmed) note") assert_equal(txid_shielding1, r[0]['txid']) assert_equal(10, r[0]['amount']) assert_equal(1000000000, r[0]['amountZat']) assert_false(r[0]['change'], "Note should not be change") assert_equal(no_memo, r[0]['memo']) assert_equal(0, r[0]['confirmations']) assert_equal(-1, r[0]['blockindex']) assert_equal(0, r[0]['blockheight']) c = self.nodes[1].z_getnotescount(0) assert_equal(1, c['sprout'], "Count of unconfirmed notes should be 1") # Confirm transaction (10 ZEC shielded) self.generate_and_sync(height+3) # Require one confirmation, note should be present r0 = self.nodes[1].z_listreceivedbyaddress(zaddr1) assert_equal(1, len(r0), "Should have received one (unconfirmed) note") assert_equal(txid_shielding1, r0[0]['txid']) assert_equal(10, r0[0]['amount']) assert_equal(1000000000, r0[0]['amountZat']) assert_false(r0[0]['change'], "Note should not be change") assert_equal(no_memo, r0[0]['memo']) assert_equal(1, r0[0]['confirmations']) assert_equal(height + 3, r0[0]['blockheight']) taddr = self.nodes[1].getnewaddress() # Generate some change by sending part of zaddr1 back to taddr opid = self.nodes[1].z_sendmany(zaddr1, [{'address': taddr, 'amount': 0.6}], 1) txid = wait_and_assert_operationid_status(self.nodes[1], opid) self.generate_and_sync(height+4) # Decrypted transaction details should be correct pt = self.nodes[1].z_viewtransaction(txid) assert_equal(pt['txid'], txid) assert_equal(len(pt['spends']), 1) # TODO: enable once z_viewtransaction displays transparent elements # assert_equal(len(pt['outputs']), 2) assert_equal(len(pt['outputs']), 1) assert_equal(pt['spends'][0]['type'], 'sprout') assert_equal(pt['spends'][0]['txidPrev'], txid_shielding1) assert_equal(pt['spends'][0]['js'], 0) assert_equal(pt['spends'][0]['jsPrev'], 0) assert_equal(pt['spends'][0]['jsOutputPrev'], jsOutputPrev) assert_equal(pt['spends'][0]['address'], zaddr1) assert_equal(pt['spends'][0]['value'], Decimal('10.0')) assert_equal(pt['spends'][0]['valueZat'], 1000000000) # We expect a transparent output and a Sprout output, but the RPC does # not define any particular ordering of these within the returned JSON. outputs = [{ 'type': output['type'], 'address': output['address'], 'value': output['value'], 'valueZat': output['valueZat'], } for output in pt['outputs']] for (i, output) in enumerate(pt['outputs']): if 'memo' in output: outputs[i]['memo'] = output['memo'] # TODO: enable once z_viewtransaction displays transparent elements # assert({ # 'type': 'transparent', # 'address': taddr, # 'value': Decimal('0.6'), # 'valueZat': 60000000, # } in outputs) assert({ 'type': 'sprout', 'address': zaddr1, 'value': Decimal('9.4') - DEFAULT_FEE, 'valueZat': 940000000 - DEFAULT_FEE_ZATS, 'memo': no_memo, } in outputs) # zaddr1 should have a note with change r = self.nodes[1].z_listreceivedbyaddress(zaddr1, 0) assert_equal(2, len(r), "zaddr1 Should have received 2 notes") r = sorted(r, key = lambda received: received['amount']) assert_equal(txid, r[0]['txid']) assert_equal(Decimal('9.4')-DEFAULT_FEE, r[0]['amount']) assert_equal(940000000-DEFAULT_FEE_ZATS, r[0]['amountZat']) assert_true(r[0]['change'], "Note valued at (9.4-"+str(DEFAULT_FEE)+") should be change") assert_equal(no_memo, r[0]['memo']) # The old note still exists (it's immutable), even though it is spent assert_equal(Decimal('10.0'), r[1]['amount']) assert_equal(1000000000, r[1]['amountZat']) assert_false(r[1]['change'], "Note valued at 10.0 should not be change") assert_equal(no_memo, r[1]['memo']) def test_received_sapling(self, height): self.generate_and_sync(height+1) taddr = self.nodes[1].getnewaddress() zaddr1 = self.nodes[1].z_getnewaddress('sapling') zaddrExt = self.nodes[2].z_getnewaddress('sapling') txid_taddr = self.nodes[0].sendtoaddress(taddr, 4.0) self.generate_and_sync(height+2) # Send 1 ZEC to zaddr1 opid = self.nodes[1].z_sendmany(taddr, [ {'address': zaddr1, 'amount': 1, 'memo': my_memo}, {'address': zaddrExt, 'amount': 2}, ], 1) txid = wait_and_assert_operationid_status(self.nodes[1], opid) self.sync_all() # Decrypted transaction details should be correct pt = self.nodes[1].z_viewtransaction(txid) assert_equal(pt['txid'], txid) assert_equal(len(pt['spends']), 0) assert_equal(len(pt['outputs']), 2) # Outputs are not returned in a defined order but the amounts are deterministic outputs = sorted(pt['outputs'], key=lambda x: x['valueZat']) assert_equal(outputs[0]['type'], 'sapling') assert_equal(outputs[0]['address'], zaddr1) assert_equal(outputs[0]['value'], Decimal('1')) assert_equal(outputs[0]['valueZat'], 100000000) assert_equal(outputs[0]['output'], 0) assert_equal(outputs[0]['outgoing'], False) assert_equal(outputs[0]['memo'], my_memo) assert_equal(outputs[0]['memoStr'], my_memo_str) assert_equal(outputs[1]['type'], 'sapling') assert_equal(outputs[1]['address'], zaddrExt) assert_equal(outputs[1]['value'], Decimal('2')) assert_equal(outputs[1]['valueZat'], 200000000) assert_equal(outputs[1]['output'], 1) assert_equal(outputs[1]['outgoing'], True) assert_equal(outputs[1]['memo'], no_memo) assert 'memoStr' not in outputs[1] r = self.nodes[1].z_listreceivedbyaddress(zaddr1) assert_equal(0, len(r), "Should have received no confirmed note") c = self.nodes[1].z_getnotescount() assert_equal(0, c['sapling'], "Count of confirmed notes should be 0") # No confirmation required, one note should be present r = self.nodes[1].z_listreceivedbyaddress(zaddr1, 0) assert_equal(1, len(r), "Should have received one (unconfirmed) note") assert_equal(txid, r[0]['txid']) assert_equal(1, r[0]['amount']) assert_equal(100000000, r[0]['amountZat']) assert_false(r[0]['change'], "Note should not be change") assert_equal(my_memo, r[0]['memo']) assert_equal(0, r[0]['confirmations']) assert_equal(-1, r[0]['blockindex']) assert_equal(0, r[0]['blockheight']) c = self.nodes[1].z_getnotescount(0) assert_equal(1, c['sapling'], "Count of unconfirmed notes should be 1") # Confirm transaction (1 ZEC from taddr to zaddr1) self.generate_and_sync(height+3) # adjust confirmations r[0]['confirmations'] = 1 # adjust blockindex r[0]['blockindex'] = 1 # adjust height r[0]['blockheight'] = height + 3 # Require one confirmation, note should be present assert_equal(r, self.nodes[1].z_listreceivedbyaddress(zaddr1)) # Generate some change by sending part of zaddr1 to zaddr2 txidPrev = txid zaddr2 = self.nodes[1].z_getnewaddress('sapling') opid = self.nodes[1].z_sendmany(zaddr1, [{'address': zaddr2, 'amount': 0.6}], 1) txid = wait_and_assert_operationid_status(self.nodes[1], opid) self.sync_all() self.generate_and_sync(height+4) # Decrypted transaction details should be correct pt = self.nodes[1].z_viewtransaction(txid) assert_equal(pt['txid'], txid) assert_equal(len(pt['spends']), 1) assert_equal(len(pt['outputs']), 2) assert_equal(pt['spends'][0]['type'], 'sapling') assert_equal(pt['spends'][0]['txidPrev'], txidPrev) assert_equal(pt['spends'][0]['spend'], 0) assert_equal(pt['spends'][0]['outputPrev'], 0) assert_equal(pt['spends'][0]['address'], zaddr1) assert_equal(pt['spends'][0]['value'], Decimal('1.0')) assert_equal(pt['spends'][0]['valueZat'], 100000000) # Outputs are not returned in a defined order but the amounts are deterministic outputs = sorted(pt['outputs'], key=lambda x: x['valueZat']) assert_equal(outputs[0]['type'], 'sapling') assert_equal(outputs[0]['address'], zaddr1) assert_equal(outputs[0]['value'], Decimal('0.4') - DEFAULT_FEE) assert_equal(outputs[0]['valueZat'], 40000000 - DEFAULT_FEE_ZATS) assert_equal(outputs[0]['output'], 1) assert_equal(outputs[0]['outgoing'], False) assert_equal(outputs[0]['memo'], no_memo) assert 'memoStr' not in outputs[0] assert_equal(outputs[1]['type'], 'sapling') assert_equal(outputs[1]['address'], zaddr2) assert_equal(outputs[1]['value'], Decimal('0.6')) assert_equal(outputs[1]['valueZat'], 60000000) assert_equal(outputs[1]['output'], 0) assert_equal(outputs[1]['outgoing'], False) assert_equal(outputs[1]['memo'], no_memo) assert 'memoStr' not in outputs[1] # zaddr1 should have a note with change r = self.nodes[1].z_listreceivedbyaddress(zaddr1, 0) assert_equal(2, len(r), "zaddr1 Should have received 2 notes") r = sorted(r, key = lambda received: received['amount']) assert_equal(txid, r[0]['txid']) assert_equal(Decimal('0.4')-DEFAULT_FEE, r[0]['amount']) assert_equal(40000000-DEFAULT_FEE_ZATS, r[0]['amountZat']) assert_equal(r[0]['change'], True, "Note valued at (0.4-"+str(DEFAULT_FEE)+") should be change") assert_equal(no_memo, r[0]['memo']) # The old note still exists (it's immutable), even though it is spent assert_equal(Decimal('1.0'), r[1]['amount']) assert_equal(100000000, r[1]['amountZat']) assert_equal(r[1]['change'], False, "Note valued at 1.0 should not be change") assert_equal(my_memo, r[1]['memo']) # zaddr2 should not have change r = self.nodes[1].z_listreceivedbyaddress(zaddr2, 0) assert_equal(len(r), 1, "zaddr2 Should have received 1 notes") r = sorted(r, key = lambda received: received['amount']) assert_equal(r[0]['txid'], txid) assert_equal(r[0]['amount'], Decimal('0.6')) assert_equal(r[0]['amountZat'], 60000000) assert_equal(r[0]['change'], False, "Note valued at 0.6 should not be change") assert_equal(r[0]['memo'], no_memo) assert 0 <= r[0]['outindex'] < 2 c = self.nodes[1].z_getnotescount(0) assert_equal(c['sapling'], 3, "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 0 <= 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['address'] # 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['address'] receivers = node.z_listunifiedreceivers(unified_addr) assert_equal(len(receivers), 3) assert 'p2pkh' in receivers assert 'sapling' in receivers assert 'orchard' in receivers assert_raises_message( JSONRPCException, "The provided address is a bare receiver from a Unified Address in this wallet.", node.z_listreceivedbyaddress, receivers['p2pkh'], 0) assert_raises_message( JSONRPCException, "The provided address is a bare receiver from a Unified Address in this wallet.", node.z_listreceivedbyaddress, receivers['sapling'], 0) # 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}], 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['p2pkh']: 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") # The return list order isn't defined, so sort by pool name r = sorted(r, key=lambda x: x['pool']) assert_equal(r[0]['pool'], 'sapling') assert_equal(r[0]['txid'], txid_sapling) assert_equal(r[0]['amount'], Decimal('0.1')) assert_equal(r[0]['amountZat'], 10000000) assert_equal(r[0]['memo'], no_memo) assert 0 <= r[0]['outindex'] < 2 assert_equal(r[0]['confirmations'], 1) assert_equal(r[0]['change'], False) assert_equal(r[0]['blockheight'], height+5) assert_equal(r[0]['blockindex'], 1) assert 'blocktime' in r[0] assert_equal(r[1]['pool'], 'transparent') assert_equal(r[1]['txid'], txid_taddr) assert_equal(r[1]['amount'], Decimal('0.2')) assert_equal(r[1]['amountZat'], 20000000) assert 0 <= r[1]['outindex'] < 2 assert_equal(r[1]['confirmations'], 0) assert_equal(r[1]['change'], False) assert 'memo' not in r[1] assert_equal(r[1]['blockheight'], 0) # not yet mined assert_equal(r[1]['blockindex'], -1) # not yet mined assert 'blocktime' in r[1] def test_received_orchard(self, height): self.generate_and_sync(height+1) taddr = self.nodes[1].getnewaddress() acct1 = self.nodes[1].z_getnewaccount()['account'] acct2 = self.nodes[1].z_getnewaccount()['account'] addrResO = self.nodes[1].z_getaddressforaccount(acct1, ['orchard']) assert_equal(addrResO['receiver_types'], ['orchard']) uao = addrResO['address'] addrResSO = self.nodes[1].z_getaddressforaccount(acct2, ['sapling', 'orchard']) assert_equal(addrResSO['receiver_types'], ['sapling', 'orchard']) uaso = addrResSO['address'] self.nodes[0].sendtoaddress(taddr, 4.0) self.generate_and_sync(height+2) acct_node0 = self.nodes[0].z_getnewaccount()['account'] ua_node0 = self.nodes[0].z_getaddressforaccount(acct_node0, ['sapling', 'orchard'])['address'] opid = self.nodes[1].z_sendmany(taddr, [ {'address': uao, 'amount': 1, 'memo': my_memo}, {'address': uaso, 'amount': 2}, ], 1, 0, 'AllowRevealedSenders') txid0 = wait_and_assert_operationid_status(self.nodes[1], opid) self.sync_all() # Decrypted transaction details should be correct, even though # the transaction is still just in the mempool pt = self.nodes[1].z_viewtransaction(txid0) assert_equal(pt['txid'], txid0) assert_equal(len(pt['spends']), 0) assert_equal(len(pt['outputs']), 2) # Outputs are not returned in a defined order but the amounts are deterministic outputs = sorted(pt['outputs'], key=lambda x: x['valueZat']) assert_equal(outputs[0]['type'], 'orchard') assert_equal(outputs[0]['address'], uao) assert_equal(outputs[0]['value'], Decimal('1')) assert_equal(outputs[0]['valueZat'], 100000000) assert_equal(outputs[0]['outgoing'], False) assert_equal(outputs[0]['memo'], my_memo) assert_equal(outputs[0]['memoStr'], my_memo_str) actionToSpend = outputs[0]['action'] assert_equal(outputs[1]['type'], 'orchard') assert_equal(outputs[1]['address'], uaso) assert_equal(outputs[1]['value'], Decimal('2')) assert_equal(outputs[1]['valueZat'], 200000000) assert_equal(outputs[1]['outgoing'], False) assert_equal(outputs[1]['memo'], no_memo) assert 'memoStr' not in outputs[1] self.generate_and_sync(height+3) opid = self.nodes[1].z_sendmany(uao, [ {'address': uaso, 'amount': Decimal('0.3')}, {'address': ua_node0, 'amount': Decimal('0.2')} ], 1) txid1 = wait_and_assert_operationid_status(self.nodes[1], opid) self.sync_all() pt = self.nodes[1].z_viewtransaction(txid1) assert_equal(pt['txid'], txid1) assert_equal(len(pt['spends']), 1) # one spend we can see assert_equal(len(pt['outputs']), 3) # one output + one change output we can see spends = pt['spends'] assert_equal(spends[0]['type'], 'orchard') assert_equal(spends[0]['txidPrev'], txid0) assert_equal(spends[0]['actionPrev'], actionToSpend) assert_equal(spends[0]['address'], uao) assert_equal(spends[0]['value'], Decimal('1.0')) assert_equal(spends[0]['valueZat'], 100000000) outputs = sorted(pt['outputs'], key=lambda x: x['valueZat']) assert_equal(outputs[0]['type'], 'orchard') assert_equal(outputs[0]['address'], ua_node0) assert_equal(outputs[0]['value'], Decimal('0.2')) assert_equal(outputs[0]['valueZat'], 20000000) assert_equal(outputs[0]['outgoing'], True) assert_equal(outputs[0]['walletInternal'], False) assert_equal(outputs[0]['memo'], no_memo) assert_equal(outputs[1]['type'], 'orchard') assert_equal(outputs[1]['address'], uaso) assert_equal(outputs[1]['value'], Decimal('0.3')) assert_equal(outputs[1]['valueZat'], 30000000) assert_equal(outputs[1]['outgoing'], False) assert_equal(outputs[1]['walletInternal'], False) assert_equal(outputs[1]['memo'], no_memo) # Verify that we observe the change output assert_equal(outputs[2]['type'], 'orchard') assert_equal(outputs[2]['value'], Decimal('0.49999')) assert_equal(outputs[2]['valueZat'], 49999000) assert_equal(outputs[2]['outgoing'], False) assert_equal(outputs[2]['walletInternal'], True) assert_equal(outputs[2]['memo'], no_memo) # The change address should have been erased assert_true('address' not in outputs[2]) def run_test(self): self.test_received_sprout(200) self.test_received_sapling(214) self.test_received_orchard(230) if __name__ == '__main__': ListReceivedTest().main()