diff --git a/qa/pull-tester/rpc-tests.py b/qa/pull-tester/rpc-tests.py index c51c27567..c1e747e4c 100755 --- a/qa/pull-tester/rpc-tests.py +++ b/qa/pull-tester/rpc-tests.py @@ -82,6 +82,7 @@ BASE_SCRIPTS= [ 'wallet_sapling.py', 'wallet_sendmany_any_taddr.py', 'wallet_treestate.py', + 'wallet_unified_change.py', 'listtransactions.py', 'mempool_resurrect_test.py', 'txn_doublespend.py', diff --git a/qa/rpc-tests/wallet_unified_change.py b/qa/rpc-tests/wallet_unified_change.py new file mode 100755 index 000000000..375c7c422 --- /dev/null +++ b/qa/rpc-tests/wallet_unified_change.py @@ -0,0 +1,114 @@ +#!/usr/bin/env python3 +# Copyright (c) 2022 The Zcash developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or https://www.opensource.org/licenses/mit-license.php . + +from decimal import Decimal +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import ( + NU5_BRANCH_ID, + assert_equal, + get_coinbase_address, + nuparams, + start_nodes, + wait_and_assert_operationid_status, +) + +# Test wallet accounts behaviour +class WalletUnifiedChangeTest(BitcoinTestFramework): + def __init__(self): + super().__init__() + self.num_nodes = 4 + + def setup_nodes(self): + return start_nodes(self.num_nodes, self.options.tmpdir, [[ + nuparams(NU5_BRANCH_ID, 201), + ]] * self.num_nodes) + + def run_test(self): + # Activate Nu5 + self.sync_all() + self.nodes[0].generate(1) + self.sync_all() + + account0 = self.nodes[0].z_getnewaccount()['account'] + ua0_sapling = self.nodes[0].z_getaddressforaccount(account0, ['sapling'])['address'] + ua0_orchard = self.nodes[0].z_getaddressforaccount(account0, ['orchard'])['address'] + + account1 = self.nodes[1].z_getnewaccount()['account'] + ua1_sapling = self.nodes[1].z_getaddressforaccount(account1, ['sapling'])['address'] + ua1 = self.nodes[1].z_getaddressforaccount(account1)['address'] + + # Fund both of ua0_sapling and ua0_orchard + recipients = [{'address': ua0_sapling, 'amount': 10}] + opid = self.nodes[0].z_sendmany(get_coinbase_address(self.nodes[0]), recipients, 1, 0, 'AllowRevealedSenders') + wait_and_assert_operationid_status(self.nodes[0], opid) + + recipients = [{'address': ua0_orchard, 'amount': 10}] + opid = self.nodes[0].z_sendmany(get_coinbase_address(self.nodes[0]), recipients, 1, 0, 'AllowRevealedSenders') + wait_and_assert_operationid_status(self.nodes[0], opid) + + self.sync_all() + self.nodes[0].generate(1) + self.sync_all() + + assert_equal( + {'pools': {'sapling': {'valueZat': 1000000000}, 'orchard': {'valueZat': 1000000000}}, 'minimum_confirmations': 1}, + self.nodes[0].z_getbalanceforaccount(account0)) + + # Send both amounts to ua1 in fully-shielded transactions. This will result + # in account1 having both a Sapling and an Orchard balances. + + recipients = [{'address': ua1_sapling, 'amount': 5}] + opid = self.nodes[0].z_sendmany(ua0_sapling, recipients, 1, Decimal('0.00001')) + txid_sapling = wait_and_assert_operationid_status(self.nodes[0], opid) + + recipients = [{'address': ua1, 'amount': 5}] + opid = self.nodes[0].z_sendmany(ua0_orchard, recipients, 1, Decimal('0.00001')) + txid_orchard = wait_and_assert_operationid_status(self.nodes[0], opid) + + assert_equal(set([txid_sapling, txid_orchard]), set(self.nodes[0].getrawmempool())) + + self.sync_all() + self.nodes[0].generate(1) + self.sync_all() + + assert_equal([], self.nodes[0].getrawmempool()) + assert_equal(1, self.nodes[0].gettransaction(txid_orchard)['confirmations']) + assert_equal(1, self.nodes[0].gettransaction(txid_sapling)['confirmations']) + + assert_equal( + {'pools': {'sapling': {'valueZat': 500000000}, 'orchard': {'valueZat': 500000000}}, 'minimum_confirmations': 1}, + self.nodes[1].z_getbalanceforaccount(account1)) + + # Now send sapling->sapling, generating change. + recipients = [{'address': ua0_sapling, 'amount': Decimal('2.5')}] + opid = self.nodes[1].z_sendmany(ua1_sapling, recipients, 1, 0) + txid_sapling = wait_and_assert_operationid_status(self.nodes[1], opid) + + self.sync_all() + self.nodes[1].generate(1) + self.sync_all() + + # Since this is entirely sapling->sapling, change should be returned + # to the Sapling pool. + assert_equal( + {'pools': {'sapling': {'valueZat': 250000000}, 'orchard': {'valueZat': 500000000}}, 'minimum_confirmations': 1}, + self.nodes[1].z_getbalanceforaccount(account1)) + + # If we send from an unrestricted UA, change should still not cross + # the pool boundary, since we can build a purely sapling->sapling tx. + recipients = [{'address': ua0_sapling, 'amount': Decimal('1.25')}] + opid = self.nodes[1].z_sendmany(ua1, recipients, 1, 0) + txid_sapling = wait_and_assert_operationid_status(self.nodes[1], opid) + + self.sync_all() + self.nodes[1].generate(1) + self.sync_all() + + assert_equal( + {'pools': {'sapling': {'valueZat': 125000000}, 'orchard': {'valueZat': 500000000}}, 'minimum_confirmations': 1}, + self.nodes[1].z_getbalanceforaccount(account1)) + +if __name__ == '__main__': + WalletUnifiedChangeTest().main()