#!/usr/bin/env python3
# Copyright (c) 2016 The Bitcoin Core developers
# Distributed under the MIT/X11 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, assert_greater_than_or_equal, assert_raises_rpc_error
from test_framework.authproxy import JSONRPCException
from decimal import Decimal

" Tests issued assets functionality including (re)issuance, and de-issuance "

# Creates a raw issuance transaction based on the passed in list, checking important details after
def process_raw_issuance(test, node, issuance_list):
    if len(issuance_list) > 5:
        raise Exception('Issuance list too long')
    # Make enough outputs for any subsequent spend
    next_destinations = []
    output_values = Decimal('%.8f' % ((node.getbalance()['bitcoin']-1)/5))
    for i in range(5):
        next_destinations.append({node.getnewaddress(): output_values})

    raw_tx = node.createrawtransaction([], next_destinations)
    # We "over-fund" these transactions to 50 sat/vbyte since issuances aren't baked in yet
    # Otherwise huge multi-issuances may fail min-relay
    funded_tx = node.fundrawtransaction(raw_tx, {"feeRate":Decimal('0.00050000')})['hex']
    issued_call_details = node.rawissueasset(funded_tx, issuance_list)
    issued_tx = issued_call_details[-1]["hex"] # Get hex from end
    # don't accept blinding fail, and blind all issuances or none at all
    blind_tx = node.blindrawtransaction(issued_tx, False, [], issuance_list[0]["blind"])
    signed_tx = node.signrawtransactionwithwallet(blind_tx)
    tx_id = node.sendrawtransaction(signed_tx['hex'])
    test.generate(node, 1)
    assert_equal(node.gettransaction(tx_id)["confirmations"], 1)
    num_issuance = 0
    decoded_tx = node.decoderawtransaction(signed_tx['hex'])
    decoded_unblind_tx = node.decoderawtransaction(issued_tx)
    for i, (issuance_req, tx_input, issuance_result) in enumerate(zip(issuance_list, decoded_tx["vin"], issued_call_details)):
        if "issuance" not in tx_input:
            break

        num_issuance += 1
        issuance_details = tx_input["issuance"]
        if "blind" not in issuance_req or issuance_req["blind"] == True:

            assert "assetamount" not in issuance_details
            assert "tokenamount" not in issuance_details
            assert_equal(issuance_details["assetBlindingNonce"], "00"*32)
            if "asset_amount" in issuance_req:
                assert "assetamountcommitment" in issuance_details
            if "token_amount" in issuance_req:
                assert "tokenamountcommitment" in issuance_details
        else:
            if "asset_amount" in issuance_req:
                assert_equal(issuance_details["assetamount"], issuance_req["asset_amount"])
            if "token_amount" in issuance_req:
                assert_equal(issuance_details["tokenamount"], issuance_req["token_amount"])

        # Cross-check RPC call result details with raw details
        assert_equal(issuance_result["vin"], i)
        assert_equal(issuance_result["entropy"], issuance_details["assetEntropy"])
        if "asset" in issuance_details:
            assert_equal(issuance_result["asset"], issuance_details["asset"])
        if "token" in issuance_details:
            assert_equal(issuance_result["token"], issuance_details["token"])

        # Look for outputs assets where we expect them, or not, initial issuance first then token
        for issuance_type in ["asset", "token"]:
            blind_dest = issuance_type+"_address" not in issuance_req or node.validateaddress(issuance_req[issuance_type+"_address"])["confidential_key"] != ""
            if blind_dest:
                # We should not find any the issuances we made since the addresses confidential
                for output in decoded_tx["vout"]:
                    if "asset" in output and output["asset"] == issuance_details[issuance_type]:
                        raise Exception("Found asset in plaintext that should be confidential!")

            # Now scan unblinded version of issuance outputs
            asset_found = False
            for output in decoded_unblind_tx["vout"]:
                if "asset" in output and output["asset"] == issuance_details[issuance_type]:
                    if issuance_type+"_address" not in issuance_req:
                        raise Exception("Found asset type not requested")
                    if "value" in output and output["value"] == issuance_req[issuance_type+"_amount"]:
                        asset_found = True

            # Find the asset type if it was created
            assert asset_found if issuance_type+"_address" in issuance_req else True

    assert_equal(num_issuance, len(issuance_list))

class IssuanceTest(BitcoinTestFramework):

    def set_test_params(self):
        self.num_nodes = 3
        self.extra_args = [["-blindedaddresses=1"]] * self.num_nodes
        self.setup_clean_chain = True

    def add_options(self, parser):
        self.add_wallet_options(parser)

    def skip_test_if_missing_module(self):
        self.skip_if_no_wallet()

    def setup_network(self, split=False):
        self.setup_nodes()
        self.connect_nodes(0, 1)
        self.connect_nodes(1, 0) ## ELEMENTS: investigate why we need this connection
        self.connect_nodes(1, 2)
        self.sync_all()

    def raw_issuance(self, node, issuance_list):
        process_raw_issuance(self, node, issuance_list)

    def run_test(self):
        self.generate(self.nodes[0], 105)
        # Make sure test starts with no initial issuance.
        assert_equal(len(self.nodes[0].listissuances()), 0)

        self.log.info("listissuances with null argument")
        assert_equal(self.nodes[0].listissuances(None), [])

        # Unblinded issuance of asset
        contract_hash = "deadbeef"*8
        issued = self.nodes[0].issueasset(1, 1, False, contract_hash)
        balance = self.nodes[0].getwalletinfo()["balance"]
        assert_equal(balance[issued["asset"]], 1)
        assert_equal(balance[issued["token"]], 1)

        self.log.info("issueasset with null argument")
        assert_equal(len(self.nodes[0].listissuances(None)), len(self.nodes[0].listissuances()))

        # Quick unblinded reissuance check, making 2*COIN total
        self.nodes[0].reissueasset(issued["asset"], 1)

        self.generate(self.nodes[0], 1)
        self.sync_all()

        contract_hash = "deadbeee"*8
        issued2 = self.nodes[0].issueasset(2, 1, True, contract_hash)
        test_asset = issued2["asset"]
        assert_equal(self.nodes[0].getwalletinfo()['balance'][test_asset], Decimal(2))
        node1balance = self.nodes[1].getwalletinfo()['balance']
        if test_asset in node1balance:
            assert_equal(node1balance[test_asset], Decimal(0))

        # Send bitcoin to node 1 and then from 1 to 2 to force node 1 to
        # spend confidential money.
        self.nodes[0].sendtoaddress(self.nodes[1].getnewaddress(), 4)
        self.generate(self.nodes[0], 1)
        self.sync_all()
        self.nodes[1].sendtoaddress(self.nodes[2].getnewaddress(), 3, "", "", False, False, 1, "UNSET", False, "", False)
        self.sync_all()
        self.generate(self.nodes[0], 1)
        self.sync_all()

        # Destroy assets
        pre_destroy_btc_balance = self.nodes[2].getwalletinfo()['balance']['bitcoin']
        self.nodes[2].destroyamount('bitcoin', 2) # Destroy 2 BTC
        self.generate(self.nodes[2], 1)
        self.sync_all()
        assert_greater_than_or_equal(pre_destroy_btc_balance - Decimal('2'), self.nodes[2].getbalance()['bitcoin'])

        issuedamount = self.nodes[0].getwalletinfo()['balance'][issued["token"]]
        assert_equal(issuedamount, Decimal('1.0'))
        self.nodes[0].destroyamount(issued["token"], issuedamount) # Destroy all reissuance tokens of one type

        self.generate(self.nodes[0], 1)
        self.sync_all()
        assert issued["token"] not in self.nodes[0].getwalletinfo()['balance']

        # Test various issuance and auditing paths

        issuancedata = self.nodes[0].issueasset(Decimal('0.00000002'), Decimal('0.00000001')) #2 of asset, 1 reissuance token
        self.generate(self.nodes[1], 1)
        self.sync_all()
        assert_equal(self.nodes[0].getwalletinfo()["balance"][issuancedata["asset"]], Decimal('0.00000002'))
        assert_equal(self.nodes[0].getwalletinfo()["balance"][issuancedata["token"]], Decimal('0.00000001'))
        self.nodes[0].reissueasset(issuancedata["asset"], Decimal('0.00000001'))
        self.sync_all()
        assert_equal(self.nodes[0].getwalletinfo()["balance"][issuancedata["asset"]], Decimal('0.00000003'))
        # Can't reissue an issuance token (yet)
        try:
            self.nodes[0].reissueasset(issuancedata["token"], Decimal('0.00000001'))
            raise AssertionError("You shouldn't be able to reissue a token yet")
        except JSONRPCException:
            pass


        issuancedata = self.nodes[2].issueasset(Decimal('0.00000005'), 0) #5 of asset, 0 reissuance token
        # No reissuance tokens
        try:
            self.nodes[2].reissueasset(issuancedata["token"], 5)
            raise AssertionError("You shouldn't be able to reissue without a token")
        except JSONRPCException:
            pass

        issuancedata = self.nodes[2].issueasset(0, Decimal('0.00000006')) #0 of asset, 6 reissuance token

        # Node 2 will send node 1 a reissuance token, both will generate assets
        self.nodes[2].sendtoaddress(self.nodes[1].getnewaddress(), Decimal('0.00000001'), "", "", False, False, 1, "UNSET", False, issuancedata["token"])
        # node 1 needs to know about a (re)issuance to reissue itself
        self.nodes[1].importaddress(self.nodes[2].gettransaction(issuancedata["txid"])["details"][0]["address"])
        # also send some bitcoin
        self.generate(self.nodes[2], 1)
        self.sync_all()

        assert_equal(self.nodes[2].getwalletinfo()["balance"][issuancedata["token"]], Decimal('0.00000005'))
        assert_equal(self.nodes[1].getwalletinfo()["balance"][issuancedata["token"]], Decimal('0.00000001'))
        redata1 = self.nodes[1].reissueasset(issuancedata["asset"], Decimal('0.05'))
        redata2 = self.nodes[2].reissueasset(issuancedata["asset"], Decimal('0.025'))

        self.sync_all()
        # Watch-only issuances won't show up in wallet until confirmed
        self.generate(self.nodes[1], 1)
        self.sync_all()

        # Now have node 0 audit these issuances
        blindingkey1 = self.nodes[1].dumpissuanceblindingkey(redata1["txid"], redata1["vin"])
        blindingkey2 = self.nodes[2].dumpissuanceblindingkey(redata2["txid"], redata2["vin"])
        blindingkey3 = self.nodes[2].dumpissuanceblindingkey(issuancedata["txid"], issuancedata["vin"])

        # Need addr to get transactions in wallet. TODO: importissuances?
        txdet1 = self.nodes[1].gettransaction(redata1["txid"])["details"]
        txdet2 = self.nodes[2].gettransaction(redata2["txid"])["details"]
        txdet3 = self.nodes[2].gettransaction(issuancedata["txid"])["details"]

        # Receive addresses added last
        addr1 = txdet1[len(txdet1)-1]["address"]
        addr2 = txdet2[len(txdet2)-1]["address"]
        addr3 = txdet3[len(txdet3)-1]["address"]

        assert_equal(len(self.nodes[0].listissuances()), 5)
        self.nodes[0].importaddress(addr1)
        self.nodes[0].importaddress(addr2)
        self.nodes[0].importaddress(addr3)

        issuances = self.nodes[0].listissuances()
        assert_equal(len(issuances), 8)

        for issue in issuances:
            if issue['txid'] == redata1["txid"] and issue['vin'] == redata1["vin"]:
                assert_equal(issue['assetamount'], Decimal('-1'))
            if issue['txid'] == redata2["txid"] and issue['vin'] == redata2["vin"]:
                assert_equal(issue['assetamount'], Decimal('-1'))
            if issue['txid'] == issuancedata["txid"] and issue['vin'] == issuancedata["vin"]:
                assert_equal(issue['assetamount'], Decimal('-1'))
                assert_equal(issue['tokenamount'], Decimal('-1'))

        # Test that importing the issuance blinding keys then reveals the issuance amounts
        self.nodes[0].importissuanceblindingkey(redata1["txid"], redata1["vin"], blindingkey1)
        self.nodes[0].importissuanceblindingkey(redata2["txid"], redata2["vin"], blindingkey2)
        self.nodes[0].importissuanceblindingkey(issuancedata["txid"], issuancedata["vin"], blindingkey3)

        issuances = self.nodes[0].listissuances()

        for issue in issuances:
            if issue['txid'] == redata1["txid"] and issue['vin'] == redata1["vin"]:
                assert_equal(issue['assetamount'], Decimal('0.05'))
            if issue['txid'] == redata2["txid"] and issue['vin'] == redata2["vin"]:
                assert_equal(issue['assetamount'], Decimal('0.025'))
            if issue['txid'] == issuancedata["txid"] and issue['vin'] == issuancedata["vin"]:
                assert_equal(issue['assetamount'], Decimal('0'))
                assert_equal(issue['tokenamount'], Decimal('0.00000006'))

        # Check for value accounting when asset issuance is null but token not, ie unblinded
        issued = self.nodes[0].issueasset(0, 1, False)
        assert issued["asset"] not in self.nodes[0].getwalletinfo()["balance"]
        assert_equal(self.nodes[0].getwalletinfo()["balance"][issued["token"]], 1)


        print("Raw issuance tests")
        # Addresses to send to to check proper blinding
        blind_addr = self.nodes[0].getnewaddress()
        nonblind_addr = self.nodes[0].validateaddress(blind_addr)['unconfidential']

        # Fail making non-witness issuance sourcing a single unblinded output.
        # See: https://github.com/ElementsProject/elements/issues/473
        total_amount = self.nodes[0].getbalance()['bitcoin']
        self.nodes[0].sendtoaddress(nonblind_addr, total_amount, "", "", True)
        self.generate(self.nodes[1], 1)
        raw_tx = self.nodes[0].createrawtransaction([], [{nonblind_addr: 1}])
        funded_tx = self.nodes[0].fundrawtransaction(raw_tx)['hex']
        issued_tx = self.nodes[2].rawissueasset(funded_tx, [{"asset_amount":1, "asset_address":nonblind_addr, "blind":False}])[0]["hex"]
        blind_tx = self.nodes[0].blindrawtransaction(issued_tx) # This is a no-op
        signed_tx = self.nodes[0].signrawtransactionwithwallet(blind_tx)
        assert_raises_rpc_error(-26, "", self.nodes[0].sendrawtransaction, signed_tx['hex'])

        # Make single blinded output to ensure we work around above issue
        total_amount = self.nodes[0].getbalance()['bitcoin']
        self.nodes[0].sendtoaddress(blind_addr, total_amount, "", "", True)
        self.generate(self.nodes[1], 1)

        # Start with single issuance input, unblinded (makes 5 outputs for later larger issuances)
        self.raw_issuance(self.nodes[0], [{"asset_amount": 2, "asset_address": nonblind_addr, "blind": False}])
        self.raw_issuance(self.nodes[0], [{"asset_amount": 2, "asset_address": nonblind_addr, "blind": True}])
        self.raw_issuance(self.nodes[0], [{"token_amount": 5, "token_address": nonblind_addr, "blind": False}])
        self.raw_issuance(self.nodes[0], [{"asset_amount": 7, "asset_address": nonblind_addr, "token_amount":2, "token_address":nonblind_addr, "blind":False}])
        self.raw_issuance(self.nodes[0], [{"asset_amount": 7, "asset_address": nonblind_addr, "token_amount":2, "token_address":blind_addr, "blind":False}])
        self.raw_issuance(self.nodes[0], [{"asset_amount": 7, "asset_address": blind_addr, "token_amount":2, "token_address":nonblind_addr, "blind":False}])
        self.raw_issuance(self.nodes[0], [{"asset_amount": 7, "asset_address": blind_addr, "token_amount":2, "token_address":blind_addr, "blind":False}])
        # Now do multiple with some issuance outputs blind, some unblinded
        self.raw_issuance(self.nodes[0], [{"asset_amount": 7, "asset_address": nonblind_addr, "token_amount":2, "token_address":nonblind_addr, "blind":False}, {"asset_amount":2, "asset_address":nonblind_addr, "blind":False}])
        self.raw_issuance(self.nodes[0], [{"asset_amount": 7, "asset_address": blind_addr, "token_amount":2, "token_address":nonblind_addr, "blind":False}, {"asset_amount":2, "asset_address":nonblind_addr, "blind":False}])
        # Up to 5 issuances since we're making 5 outputs each time
        self.raw_issuance(self.nodes[0], [{"asset_amount": 7, "asset_address": nonblind_addr, "token_amount":2, "token_address":blind_addr, "blind":False}, {"asset_amount":2, "asset_address":nonblind_addr, "blind":False}])
        self.raw_issuance(self.nodes[0], [{"asset_amount": 1, "asset_address": nonblind_addr, "token_amount":2, "token_address":blind_addr, "blind":False}, {"asset_amount":3, "asset_address":nonblind_addr, "blind":False}, {"asset_amount":4, "asset_address":nonblind_addr, "token_amount":5, "token_address":blind_addr, "blind":False}, {"asset_amount":6, "asset_address":nonblind_addr, "token_amount":7, "token_address":blind_addr, "blind":False}, {"asset_amount":8, "asset_address":nonblind_addr, "token_amount":9, "token_address":blind_addr, "blind":False}])
        # Default "blind" value is true, omitting explicit argument for last
        self.raw_issuance(self.nodes[0], [{"asset_amount": 1, "asset_address": nonblind_addr, "token_amount":2, "token_address":blind_addr, "blind":True}, {"asset_amount":3, "asset_address":nonblind_addr, "blind":True}, {"asset_amount":4, "asset_address":nonblind_addr, "token_amount":5, "token_address":blind_addr, "blind":True}, {"asset_amount":6, "asset_address":nonblind_addr, "token_amount":7, "token_address":blind_addr, "blind":True}, {"asset_amount":8, "asset_address":nonblind_addr, "token_amount":9, "token_address":blind_addr}])


        # Make sure that fee is checked
        valid_addr = self.nodes[0].getnewaddress()
        raw_tx = self.nodes[0].createrawtransaction([], [])
        assert_raises_rpc_error(-8, "Transaction must have at least one output.",
                self.nodes[0].rawissueasset, raw_tx, [{"asset_amount": 1, "asset_address": valid_addr}])
        raw_tx = self.nodes[0].createrawtransaction([], [{valid_addr: Decimal("1")}])
        assert_raises_rpc_error(-8, "Last transaction output must be fee.",
                self.nodes[0].rawissueasset, raw_tx, [{"asset_amount": 1, "asset_address": valid_addr}])

        # Make sure that invalid addresses are rejected.
        valid_addr = self.nodes[0].getnewaddress()
        raw_tx = self.nodes[0].createrawtransaction([], [{valid_addr: Decimal("1")}])
        funded_tx = self.nodes[0].fundrawtransaction(raw_tx, {"feeRate": Decimal('0.00050000')})['hex']
        assert_raises_rpc_error(-8, "Invalid asset address provided: foobar",
                self.nodes[0].rawissueasset, funded_tx, [{"asset_amount": 1, "asset_address": "foobar"}])
        assert_raises_rpc_error(-8, "Invalid token address provided: foobar",
                self.nodes[0].rawissueasset, funded_tx, [{"token_amount": 1, "token_address": "foobar"}])
        # Also test for missing value.
        assert_raises_rpc_error(-8, "Invalid parameter, missing corresponding asset_address",
                self.nodes[0].rawissueasset, funded_tx, [{"asset_amount": 1, "token_address": valid_addr}])
        assert_raises_rpc_error(-8, "Invalid parameter, missing corresponding token_address",
                self.nodes[0].rawissueasset, funded_tx, [{"token_amount": 1, "asset_address": valid_addr}])

        # Make sure contract hash is being interpreted as expected, resulting in different asset ids
        raw_tx = self.nodes[0].createrawtransaction([], [{nonblind_addr:self.nodes[0].getbalance()['bitcoin']-1}])
        funded_tx = self.nodes[0].fundrawtransaction(raw_tx)['hex']
        id_set = set()

        # First issue an asset with no argument
        issued_tx = self.nodes[2].rawissueasset(funded_tx, [{"asset_amount":1, "asset_address":nonblind_addr}])[0]["hex"]
        decode_tx = self.nodes[0].decoderawtransaction(issued_tx)
        id_set.add(decode_tx["vin"][0]["issuance"]["asset"])
        calc_asset = self.nodes[0].calculateasset(decode_tx["vin"][0]["txid"], decode_tx["vin"][0]["vout"])
        assert_equal(calc_asset["final_asset_entropy"], decode_tx["vin"][0]["issuance"]["assetEntropy"])
        assert_equal(calc_asset["asset_tag"], decode_tx["vin"][0]["issuance"]["asset"])

        # Again with 00..00 argument, which match the no-argument case
        issued_tx = self.nodes[2].rawissueasset(funded_tx, [{"asset_amount":1, "asset_address":nonblind_addr, "contract_hash":"00"*32}])[0]["hex"]
        decode_tx = self.nodes[0].decoderawtransaction(issued_tx)
        id_set.add(decode_tx["vin"][0]["issuance"]["asset"])
        assert_equal(len(id_set), 1)
        calc_asset = self.nodes[0].calculateasset(decode_tx["vin"][0]["txid"], decode_tx["vin"][0]["vout"], "00"*32)
        assert_equal(calc_asset["final_asset_entropy"], decode_tx["vin"][0]["issuance"]["assetEntropy"])
        assert_equal(calc_asset["asset_tag"], decode_tx["vin"][0]["issuance"]["asset"])

        # Random contract string should again differ
        issued_tx = self.nodes[2].rawissueasset(funded_tx, [{"asset_amount":1, "asset_address":nonblind_addr, "contract_hash":"deadbeef"*8}])[0]["hex"]
        decode_tx = self.nodes[0].decoderawtransaction(issued_tx)
        id_set.add(decode_tx["vin"][0]["issuance"]["asset"])
        assert_equal(len(id_set), 2)
        calc_asset = self.nodes[0].calculateasset(decode_tx["vin"][0]["txid"], decode_tx["vin"][0]["vout"], "deadbeef"*8)
        assert_equal(calc_asset["final_asset_entropy"], decode_tx["vin"][0]["issuance"]["assetEntropy"])
        assert_equal(calc_asset["asset_tag"], decode_tx["vin"][0]["issuance"]["asset"])
        issued_tx = self.nodes[2].rawissueasset(funded_tx, [{"asset_amount":1, "asset_address":nonblind_addr, "contract_hash":"deadbeee"*8}])[0]["hex"]
        decode_tx = self.nodes[0].decoderawtransaction(issued_tx)
        id_set.add(decode_tx["vin"][0]["issuance"]["asset"])
        assert_equal(len(id_set), 3)
        calc_asset = self.nodes[0].calculateasset(decode_tx["vin"][0]["txid"], decode_tx["vin"][0]["vout"], "deadbeee"*8)
        assert_equal(calc_asset["final_asset_entropy"], decode_tx["vin"][0]["issuance"]["assetEntropy"])
        assert_equal(calc_asset["asset_tag"], decode_tx["vin"][0]["issuance"]["asset"])

        # Finally, append an issuance on top of an already-"issued" raw tx
        # Same contract, different utxo being spent results in new asset type
        # We also create a reissuance token to test reissuance with contract hash
        issued_tx = self.nodes[2].rawissueasset(issued_tx, [{"asset_amount":1, "asset_address":nonblind_addr, "token_address":nonblind_addr, "contract":"deadbeee"*8}])[0]["hex"]
        decode_tx = self.nodes[0].decoderawtransaction(issued_tx)
        id_set.add(decode_tx["vin"][1]["issuance"]["asset"])
        assert_equal(len(id_set), 4)
        calc_asset = self.nodes[0].calculateasset(decode_tx["vin"][1]["txid"], decode_tx["vin"][1]["vout"], None, False)
        assert_equal(calc_asset["final_asset_entropy"], decode_tx["vin"][1]["issuance"]["assetEntropy"])
        assert_equal(calc_asset["asset_tag"], decode_tx["vin"][1]["issuance"]["asset"])
        assert_equal(calc_asset["reissuance_asset_tag"], decode_tx["vin"][1]["issuance"]["token"])
        # This issuance should not have changed
        id_set.add(decode_tx["vin"][0]["issuance"]["asset"])
        assert_equal(len(id_set), 4)

        print("Raw reissuance tests")
        issued_asset = self.nodes[0].issueasset(0, 1)
        self.generate(self.nodes[0], 1)
        utxo_info = None
        # Find info about the token output using wallet
        for utxo in self.nodes[0].listunspent():
            if utxo["asset"] == issued_asset["token"]:
                utxo_info = utxo
                break
        assert utxo_info is not None

        issued_address = self.nodes[0].getnewaddress()
        # Create transaction spending the reissuance token
        raw_tx = self.nodes[0].createrawtransaction([], [{issued_address:Decimal('0.00000001'), "asset": issued_asset["token"]}], 0, False)
        funded_tx = self.nodes[0].fundrawtransaction(raw_tx)['hex']
        # Find the reissuance input
        reissuance_index = -1
        for i, tx_input in enumerate(self.nodes[0].decoderawtransaction(funded_tx)["vin"]):
            if tx_input["txid"] == utxo_info["txid"] and tx_input["vout"] == utxo_info["vout"]:
                reissuance_index = i
                break
        assert reissuance_index != -1
        reissued_tx = self.nodes[0].rawreissueasset(funded_tx, [{"asset_amount":3, "asset_address":self.nodes[0].getnewaddress(), "input_index":reissuance_index, "asset_blinder":utxo_info["assetblinder"], "entropy":issued_asset["entropy"]}])
        blind_tx = self.nodes[0].blindrawtransaction(reissued_tx["hex"])
        signed_tx = self.nodes[0].signrawtransactionwithwallet(blind_tx)
        tx_id = self.nodes[0].sendrawtransaction(signed_tx["hex"])
        self.generate(self.nodes[0], 1)
        assert_equal(self.nodes[0].gettransaction(tx_id)["confirmations"], 1)

        # Now send reissuance token to blinded multisig, then reissue
        addrs = []
        for i in range(3):
            addrs.append(self.nodes[0].getaddressinfo(self.nodes[0].getnewaddress())["pubkey"])


        multisig_addr = self.nodes[0].addmultisigaddress(2,addrs)
        blinded_addr = self.nodes[0].getnewaddress()
        blinding_pubkey = self.nodes[0].validateaddress(blinded_addr)["confidential_key"]
        blinding_privkey = self.nodes[0].dumpblindingkey(blinded_addr)
        blinded_multisig = self.nodes[0].createblindedaddress(multisig_addr["address"], blinding_pubkey)
        # Import blinding key to be able to decrypt values sent to it
        self.nodes[0].importblindingkey(blinded_multisig, blinding_privkey)
        # Sending to this address must achieve blinding to reissue from this address
        self.nodes[0].sendtoaddress(blinded_multisig, self.nodes[0].getbalance()[issued_asset["token"]], "", "", False, False, 1, "UNSET", False, issued_asset["token"], False)
        self.generate(self.nodes[0], 1)

        # Get that multisig output
        utxo_info = None
        # Find info about the token output using wallet
        for utxo in self.nodes[0].listunspent():
            if utxo["asset"] == issued_asset["token"]:
                utxo_info = utxo
                assert_equal(blinded_multisig, self.nodes[0].getaddressinfo(utxo_info["address"])["confidential"])
                break
        assert utxo_info is not None
        assert utxo_info["amountblinder"] != "0000000000000000000000000000000000000000000000000000000000000000"

        # Now make transaction spending that input
        raw_tx = self.nodes[0].createrawtransaction([], [{issued_address:1, "asset": issued_asset["token"]}], 0, False)
        funded_tx = self.nodes[0].fundrawtransaction(raw_tx)["hex"]
        # Find the reissuance input
        reissuance_index = -1
        for i, tx_input in enumerate(self.nodes[0].decoderawtransaction(funded_tx)["vin"]):
            if tx_input["txid"] == utxo_info["txid"] and tx_input["vout"] == utxo_info["vout"]:
                reissuance_index = i
                break
        assert reissuance_index != -1
        reissued_tx = self.nodes[0].rawreissueasset(funded_tx, [{"asset_amount":3, "asset_address":self.nodes[0].getnewaddress(), "input_index":reissuance_index, "asset_blinder":utxo_info["assetblinder"], "entropy":issued_asset["entropy"]}])

        blind_tx = self.nodes[0].blindrawtransaction(reissued_tx["hex"])
        signed_tx = self.nodes[0].signrawtransactionwithwallet(blind_tx)
        tx_id = self.nodes[0].sendrawtransaction(signed_tx["hex"])
        self.generate(self.nodes[0], 1)
        assert_equal(self.nodes[0].gettransaction(tx_id)["confirmations"], 1)

        # Now make transaction spending a token that had non-null contract_hash
        contract_hash = "deadbeee"*8
        raw_tx = self.nodes[0].createrawtransaction([], [{self.nodes[0].getnewaddress():1}])
        funded_tx = self.nodes[0].fundrawtransaction(raw_tx)["hex"]
        issued_tx = self.nodes[0].rawissueasset(funded_tx, [{"token_amount":1, "token_address":self.nodes[0].getnewaddress(), "contract_hash":contract_hash}])[0]
        blinded_tx = self.nodes[0].blindrawtransaction(issued_tx["hex"])
        signed_tx = self.nodes[0].signrawtransactionwithwallet(blinded_tx)
        tx_id = self.nodes[0].sendrawtransaction(signed_tx["hex"])
        self.generate(self.nodes[0], 1)
        assert_equal(self.nodes[0].gettransaction(tx_id)["confirmations"], 1)

        utxo_info = None
        # Find info about the token output using wallet
        for utxo in self.nodes[0].listunspent():
            if utxo["asset"] == issued_tx["token"]:
                utxo_info = utxo
                break
        assert utxo_info is not None

        # Now spend the token, and create reissuance
        raw_tx = self.nodes[0].createrawtransaction([], [{issued_address:1, "asset": issued_tx["token"]}], 0, False)
        funded_tx = self.nodes[0].fundrawtransaction(raw_tx)["hex"]
        # Find the reissuance input
        reissuance_index = -1
        for i, tx_input in enumerate(self.nodes[0].decoderawtransaction(funded_tx)["vin"]):
            if tx_input["txid"] == utxo_info["txid"] and tx_input["vout"] == utxo_info["vout"]:
                reissuance_index = i
                break
        assert reissuance_index != -1
        reissued_tx = self.nodes[0].rawreissueasset(funded_tx, [{"asset_amount":3, "asset_address":self.nodes[0].getnewaddress(), "input_index":reissuance_index, "asset_blinder":utxo_info["assetblinder"], "entropy":issued_tx["entropy"]}])

        blind_tx = self.nodes[0].blindrawtransaction(reissued_tx["hex"], False)
        signed_tx = self.nodes[0].signrawtransactionwithwallet(blind_tx)
        tx_id = self.nodes[0].sendrawtransaction(signed_tx["hex"])
        self.generate(self.nodes[0], 1)
        assert_equal(self.nodes[0].gettransaction(tx_id)["confirmations"], 1)

        # Regression for one form of https://github.com/bitcoin/bitcoin/issues/20347
        # 1. Leave node 1 with only a single small explicit output
        self.nodes[1].sendtoaddress(self.nodes[0].getnewaddress(), self.nodes[1].getbalance()['bitcoin'], "", "", True)
        blind_addr = self.nodes[1].getnewaddress()
        nonblind_addr = self.nodes[1].validateaddress(blind_addr)['unconfidential']
        self.nodes[0].sendtoaddress(nonblind_addr, 0.0005)
        self.sync_all()
        self.generate(self.nodes[0], 2)
        self.sync_all()

        # 2. Try to do an unblinded issuance with only tokens -- the result will
        #    be an entirely unblinded tx in the 2-output case but a blinded tx
        #    in the 3-output case. As the bug causes us to do coin selection for
        #    the former while attempting to produce the latter, we will trigger
        #    an "impossible" case and a confusing/generic error message.
        self.nodes[1].issueasset(0, 1, False)["txid"]

        # Send different assets to the same address in a transaction with createrawtransaction
        # Unblinded issuance of asset
        contract_hash_1 = "deadbee1"*8
        contract_hash_2 = "deadbee2"*8
        issued_1 = self.nodes[0].issueasset(1, 1, False, contract_hash_1)
        issued_2 = self.nodes[0].issueasset(1, 1, False, contract_hash_2)
        self.generate(self.nodes[0], 1)
        self.sync_all()
        balance = self.nodes[0].getwalletinfo()["balance"]
        assert_equal(balance[issued_1["asset"]], 1)
        assert_equal(balance[issued_2["asset"]], 1)

        send_address = self.nodes[0].getnewaddress()

        # error generated if duplicated address:asset pair
        assert_raises_rpc_error(-8, "Invalid parameter, duplicated address and asset", self.nodes[0].createrawtransaction, [], [{send_address: 1, "asset": issued_1["asset"]}, {send_address: 1, "asset": issued_1["asset"]}], 0, False)

        # repeated address with different asset accepted
        raw_tx = self.nodes[0].createrawtransaction([], [{send_address: 1, "asset": issued_1["asset"]}, {send_address: 1, "asset": issued_2["asset"]}], 0, False)
        funded_tx = self.nodes[0].fundrawtransaction(raw_tx)["hex"]
        blind_tx = self.nodes[0].blindrawtransaction(funded_tx)
        signed_tx = self.nodes[0].signrawtransactionwithwallet(blind_tx)
        self.nodes[0].sendrawtransaction(signed_tx["hex"])

if __name__ == '__main__':
    IssuanceTest(__file__).main()
