#!/usr/bin/env python3
# Copyright (c) 2016-2024 The Hush developers
# Copyright (c) 2017 The Zcash developers
# Distributed under the GPLv3 software license, see the accompanying
# file COPYING or https://www.gnu.org/licenses/gpl-3.0.en.html

from test_framework.test_framework import BitcoinTestFramework
from test_framework.authproxy import JSONRPCException
from test_framework.util import assert_equal, initialize_chain_clean, \
    start_node, connect_nodes_bi, sync_blocks, sync_mempools, \
    wait_and_assert_operationid_status

from decimal import Decimal

class WalletShieldCoinbaseTest (BitcoinTestFramework):
    def __init__(self, addr_type):
        super(WalletShieldCoinbaseTest, self).__init__()
        self.addr_type = addr_type

    def setup_chain(self):
        print("Initializing test directory "+self.options.tmpdir)
        initialize_chain_clean(self.options.tmpdir, 4)

    def setup_network(self, split=False):
        # DragonX-specific environment flags. None of these change what the test
        # asserts; without them the test cannot run at all on this daemon:
        #   -listen=1/-bind=127.0.0.1: the framework's start_node() always passes
        #     -connect=0, and AppInit2 soft-sets -listen=0 whenever -connect is
        #     present, so the nodes never listen and connect_nodes_bi() silently
        #     builds an empty topology (sync_all() then spins forever). An explicit
        #     -listen=1 beats the SoftSetBoolArg.
        #   -dnsseed=0: chainparams_commandline() keeps DRAGONX's DNS seeds and
        #     overwrites pchMessageStart with the DRAGONX chain magic on *every*
        #     network including regtest, so a regtest node otherwise dials and
        #     handshakes with live mainnet peers (observed: 8 mainnet peers,
        #     blocks=3254237, feeding mainnet headers into the regtest node).
        #   -autoshield=0: DragonX auto-shields matured coinbase every 25 blocks
        #     by default, which would race the manual z_shieldcoinbase calls under
        #     test and move the balances this test checks.
        isolate = ['-listen=1', '-bind=127.0.0.1', '-dnsseed=0', '-autoshield=0']
        args = ['-regtestprotectcoinbase', '-debug=zrpcunsafe'] + isolate
        args2 = ['-regtestprotectcoinbase', '-debug=zrpcunsafe', "-mempooltxinputlimit=7"] + isolate
        if self.addr_type != 'sprout':
            nu = [
                '-nuparams=5ba81b19:0', # Overwinter
                '-nuparams=76b809bb:1', # Sapling
            ]
            args.extend(nu)
            args2 = args
        self.nodes = []
        self.nodes.append(start_node(0, self.options.tmpdir, args))
        self.nodes.append(start_node(1, self.options.tmpdir, args))
        self.nodes.append(start_node(2, self.options.tmpdir, args2))
        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 run_test (self):
        print("Mining blocks...")

        self.nodes[0].generate(1)
        self.sync_all()
        do_not_shield_taddr = self.nodes[0].getnewaddress()

        self.nodes[0].generate(4)
        walletinfo = self.nodes[0].getwalletinfo()
        assert_equal(walletinfo['immature_balance'], 50)
        assert_equal(walletinfo['balance'], 0)
        self.sync_all()
        self.nodes[2].generate(1)
        self.nodes[2].getnewaddress()
        self.nodes[2].generate(1)
        self.nodes[2].getnewaddress()
        self.nodes[2].generate(1)
        self.sync_all()
        self.nodes[1].generate(101)
        self.sync_all()
        assert_equal(self.nodes[0].getbalance(), 50)
        assert_equal(self.nodes[1].getbalance(), 10)
        assert_equal(self.nodes[2].getbalance(), 30)

        # Prepare to send taddr->zaddr
        mytaddr = self.nodes[0].getnewaddress()
        myzaddr = self.nodes[0].z_getnewaddress(self.addr_type)

        # Shielding will fail when trying to spend from watch-only address
        self.nodes[2].importaddress(mytaddr)
        try:
            self.nodes[2].z_shieldcoinbase(mytaddr, myzaddr)
        except JSONRPCException as e:
            errorString = e.error['message']
        assert_equal("Could not find any coinbase funds to shield" in errorString, True)

        # Shielding will fail because fee is negative
        try:
            self.nodes[0].z_shieldcoinbase("*", myzaddr, -1)
        except JSONRPCException as e:
            errorString = e.error['message']
        assert_equal("Amount out of range" in errorString, True)

        # Shielding will fail because fee is larger than MAX_MONEY
        try:
            self.nodes[0].z_shieldcoinbase("*", myzaddr, Decimal('21000000.00000001'))
        except JSONRPCException as e:
            errorString = e.error['message']
        assert_equal("Amount out of range" in errorString, True)

        # Shielding will fail because fee is larger than sum of utxos
        try:
            self.nodes[0].z_shieldcoinbase("*", myzaddr, 999)
        except JSONRPCException as e:
            errorString = e.error['message']
        assert_equal("Insufficient coinbase funds" in errorString, True)

        # Shielding will fail because limit parameter must be at least 0
        try:
            self.nodes[0].z_shieldcoinbase("*", myzaddr, Decimal('0.001'), -1)
        except JSONRPCException as e:
            errorString = e.error['message']
        assert_equal("Limit on maximum number of utxos cannot be negative" in errorString, True)

        # Shielding will fail because limit parameter is absurdly large
        try:
            self.nodes[0].z_shieldcoinbase("*", myzaddr, Decimal('0.001'), 99999999999999)
        except JSONRPCException as e:
            errorString = e.error['message']
        assert_equal("JSON integer out of range" in errorString, True)

        # Shield coinbase utxos from node 0 of value 40, standard fee of 0.00010000
        result = self.nodes[0].z_shieldcoinbase(mytaddr, myzaddr)
        wait_and_assert_operationid_status(self.nodes[0], result['opid'])
        self.sync_all()
        self.nodes[1].generate(1)
        self.sync_all()

        # Confirm balances and that do_not_shield_taddr containing funds of 10 was left alone
        assert_equal(self.nodes[0].getbalance(), 10)
        assert_equal(self.nodes[0].z_getbalance(do_not_shield_taddr), Decimal('10.0'))
        assert_equal(self.nodes[0].z_getbalance(myzaddr), Decimal('39.99990000'))
        assert_equal(self.nodes[1].getbalance(), 20)
        assert_equal(self.nodes[2].getbalance(), 30)

        # Shield coinbase utxos from any node 2 taddr, and set fee to 0
        result = self.nodes[2].z_shieldcoinbase("*", myzaddr, 0)
        wait_and_assert_operationid_status(self.nodes[2], result['opid'])
        self.sync_all()
        self.nodes[1].generate(1)
        self.sync_all()

        assert_equal(self.nodes[0].getbalance(), 10)
        assert_equal(self.nodes[0].z_getbalance(myzaddr), Decimal('69.99990000'))
        assert_equal(self.nodes[1].getbalance(), 30)
        assert_equal(self.nodes[2].getbalance(), 0)

        # Generate 800 coinbase utxos on node 0, and 20 coinbase utxos on node 2
        self.nodes[0].generate(800)
        self.sync_all()
        self.nodes[2].generate(20)
        self.sync_all()
        self.nodes[1].generate(100)
        self.sync_all()
        mytaddr = self.nodes[0].getnewaddress()

        def verify_locking(first, second, limit):
            result = self.nodes[0].z_shieldcoinbase(mytaddr, myzaddr, 0, limit)
            assert_equal(result["shieldingUTXOs"], Decimal(first))
            assert_equal(result["remainingUTXOs"], Decimal(second))
            remainingValue = result["remainingValue"]
            opid1 = result['opid']

            # Verify that utxos are locked (not available for selection) by queuing up another shielding operation
            result = self.nodes[0].z_shieldcoinbase(mytaddr, myzaddr, 0, 0)
            assert_equal(result["shieldingValue"], Decimal(remainingValue))
            assert_equal(result["shieldingUTXOs"], Decimal(second))
            assert_equal(result["remainingValue"], Decimal('0'))
            assert_equal(result["remainingUTXOs"], Decimal('0'))
            opid2 = result['opid']

            # wait for both aysnc operations to complete
            wait_and_assert_operationid_status(self.nodes[0], opid1)
            wait_and_assert_operationid_status(self.nodes[0], opid2)

        if self.addr_type == 'sprout':
            # Shielding the 800 utxos will occur over two transactions, since max tx size is 100,000 bytes.
            # We don't verify shieldingValue as utxos are not selected in any specific order, so value can change on each test run.
            # We set an unrealistically high limit parameter of 99999, to verify that max tx size will constrain the number of utxos.
            verify_locking('662', '138', 99999)
        else:
            # Shield the 800 utxos over two transactions
            verify_locking('500', '300', 500)

        # sync_all() invokes sync_mempool() but node 2's mempool limit will cause tx1 and tx2 to be rejected.
        # So instead, we sync on blocks and mempool for node 0 and node 1, and after a new block is generated
        # which mines tx1 and tx2, all nodes will have an empty mempool which can then be synced.
        sync_blocks(self.nodes[:2])
        sync_mempools(self.nodes[:2])
        self.nodes[1].generate(1)
        self.sync_all()

        if self.addr_type == 'sprout':
            # Verify maximum number of utxos which node 2 can shield is limited by option -mempooltxinputlimit
            # This option is used when the limit parameter is set to 0.
            mytaddr = self.nodes[2].getnewaddress()
            result = self.nodes[2].z_shieldcoinbase(mytaddr, myzaddr, Decimal('0.0001'), 0)
            assert_equal(result["shieldingUTXOs"], Decimal('7'))
            assert_equal(result["remainingUTXOs"], Decimal('13'))
            wait_and_assert_operationid_status(self.nodes[2], result['opid'])
            self.sync_all()
            self.nodes[1].generate(1)
            self.sync_all()

        # Verify maximum number of utxos which node 0 can shield is set by default limit parameter of 50
        self.nodes[0].generate(200)
        self.sync_all()
        mytaddr = self.nodes[0].getnewaddress()
        result = self.nodes[0].z_shieldcoinbase(mytaddr, myzaddr, Decimal('0.0001'))
        assert_equal(result["shieldingUTXOs"], Decimal('50'))
        assert_equal(result["remainingUTXOs"], Decimal('50'))
        wait_and_assert_operationid_status(self.nodes[0], result['opid'])

        # Verify maximum number of utxos which node 0 can shield can be set by the limit parameter
        result = self.nodes[0].z_shieldcoinbase(mytaddr, myzaddr, Decimal('0.0001'), 33)
        assert_equal(result["shieldingUTXOs"], Decimal('33'))
        assert_equal(result["remainingUTXOs"], Decimal('17'))
        wait_and_assert_operationid_status(self.nodes[0], result['opid'])
        # Don't sync node 2 which rejects the tx due to its mempooltxinputlimit
        sync_blocks(self.nodes[:2])
        sync_mempools(self.nodes[:2])
        self.nodes[1].generate(1)
        self.sync_all()

if __name__ == '__main__':
    # Upstream (Zcash/Hush) ran this test twice: once with Sprout zaddrs and once
    # with Sapling. DragonX has no Sprout support at all -- z_getnewaddress only
    # accepts "sapling" or "amnesia" (src/wallet/rpcwallet.cpp z_getnewaddress),
    # so WalletShieldCoinbaseTest('sprout') cannot even allocate its target
    # address. The sprout-only branches inside run_test() are kept intact for
    # reference but only the sapling variant is executed.
    print("Running for sapling...")
    WalletShieldCoinbaseTest('sapling').main()
