From a687dd9a34418e86360e9fd2448012bb846b6e31 Mon Sep 17 00:00:00 2001 From: Steven Roose Date: Wed, 20 Mar 2019 11:23:45 +0000 Subject: [PATCH] Add assets and issuance functional tests --- test/functional/feature_assetsdir.py | 70 +++ test/functional/feature_default_asset_name.py | 70 +++ .../feature_initial_reissuance_token.py | 87 ++++ test/functional/feature_issuance.py | 442 ++++++++++++++++++ 4 files changed, 669 insertions(+) create mode 100755 test/functional/feature_assetsdir.py create mode 100755 test/functional/feature_default_asset_name.py create mode 100755 test/functional/feature_initial_reissuance_token.py create mode 100755 test/functional/feature_issuance.py diff --git a/test/functional/feature_assetsdir.py b/test/functional/feature_assetsdir.py new file mode 100755 index 0000000000000..4d96d54f45eca --- /dev/null +++ b/test/functional/feature_assetsdir.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python3 +# Copyright (c) 2014-2016 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. + +# +# Test use of assetdir to locally label assets. +# Test listissuances returns a list of all issuances or specific issuances based on asset hex or asset label. +# + +from decimal import Decimal +from test_framework.test_framework import BitcoinTestFramework +from test_framework.authproxy import JSONRPCException +from test_framework.util import assert_equal, assert_greater_than +from test_framework.util import * + +class AssetdirTests(BitcoinTestFramework): + """ + Test use of assetdir to specify asset labels. + Test listissuances returns a list of all issuances or specific issuances based on asset hex or asset label. + """ + + def set_test_params(self): + self.setup_clean_chain = True + self.num_nodes = 1 + [["-initialfreecoins=2100000000000000", "-anyonecanspendaremine=1", "-con_connect_coinbase=1", "-con_blocksubsidy=0"]] + + def setup_network(self, split=False): + self.setup_nodes() + + def run_test(self): + self.nodes[0].generate(101) + + #Issue two assets that we will later label using the assetdir parameter + issuance1 = self.nodes[0].issueasset(100, 1, False) + asset1hex = issuance1["asset"] + + issuance2 = self.nodes[0].issueasset(100, 1, False) + asset2hex = issuance2["asset"] + + #Stop and restart the nodes, providing the assetdir parameter to locally label the assets + self.stop_nodes() + self.start_nodes([["-assetdir=" + asset1hex + ":asset1", "-assetdir=" + asset2hex + ":asset2"]]) + + #Check that listissuances return all issuances + issuances = self.nodes[0].listissuances() + assert_equal(len(issuances), 2) + + #Check all asset labels have been set: 'asset1', 'asset2' + #We can not be sure they will always be returned in the same order so will loop each one + label = "" + for issue in issuances: + label += issue["assetlabel"] + + assert_greater_than(label.find("asset1"), -1) + assert_greater_than(label.find("asset2"), -1) + + #Check we can get a list of isuances for a given label + issuances = self.nodes[0].listissuances("asset1") + assert_equal(len(issuances), 1) + assert_equal(issuances[0]["assetlabel"], "asset1") + + #Check we can get a list of issuances for a given hex + issuances = self.nodes[0].listissuances(asset2hex) + assert_equal(len(issuances), 1) + assert_equal(issuances[0]["assetlabel"], "asset2") + +if __name__ == '__main__': + AssetdirTests().main() + diff --git a/test/functional/feature_default_asset_name.py b/test/functional/feature_default_asset_name.py new file mode 100755 index 0000000000000..a56eb5a2fcbc6 --- /dev/null +++ b/test/functional/feature_default_asset_name.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python3 +# Copyright (c) 2014-2016 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. + +# +# Test chain initialisation when specifying default asset name. +# + +from decimal import Decimal +from test_framework.test_framework import BitcoinTestFramework +from test_framework.authproxy import JSONRPCException +from test_framework.util import assert_equal, connect_nodes_bi + +class NamedDefaultAssetTest(BitcoinTestFramework): + """ + Test creation of default asset is named according to defaultpeggedasset parameter + + """ + + def set_test_params(self): + self.setup_clean_chain = True + self.num_nodes = 2 + + #Set default asset name + self.extra_args = [["-defaultpeggedassetname=testasset", "-initialfreecoins=2100000000000000", "-anyonecanspendaremine=1", "-con_connect_coinbase=1", "-con_blocksubsidy=0"]]*2 + + def setup_network(self, split=False): + self.setup_nodes() + connect_nodes_bi(self.nodes, 0, 1) + self.is_network_split = False + self.sync_all() + + def run_test(self): + #Claim all anyone-can-spend coins and test that calling sendtoaddress without providing the assetlabel parameter results in the specified default pegged asset being sent. + self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 21000000, "", "", True) + self.nodes[0].generate(101) + self.sync_all() + + #Check the default asset is named correctly + walletinfo1 = self.nodes[0].getwalletinfo() + assert_equal(walletinfo1["balance"]["testasset"], 21000000) + + #Send some of the default asset to the second node + self.nodes[0].sendtoaddress(self.nodes[1].getnewaddress(), 1, "", "", False) + self.nodes[0].generate(101) + self.sync_all() + + #Check balances are correct and asset is named correctly + walletinfo1 = self.nodes[0].getwalletinfo() + assert_equal(walletinfo1["balance"]["testasset"], 20999999) + + walletinfo2 = self.nodes[1].getwalletinfo() + assert_equal(walletinfo2["balance"]["testasset"], 1) + + #Check we send the default 'testasset' when calling 'sendmany' without needing to provide the relevant asset label + outputs = {self.nodes[1].getnewaddress(): 1.0, self.nodes[1].getnewaddress(): 3.0} + self.nodes[0].sendmany("", outputs) + self.nodes[0].generate(101) + self.sync_all() + + #Check balances are correct and asset is named correctly + walletinfo1 = self.nodes[0].getwalletinfo() + assert_equal(walletinfo1["balance"]["testasset"], 20999995) + + walletinfo2 = self.nodes[1].getwalletinfo() + assert_equal(walletinfo2["balance"]["testasset"], 5) + +if __name__ == '__main__': + NamedDefaultAssetTest().main() diff --git a/test/functional/feature_initial_reissuance_token.py b/test/functional/feature_initial_reissuance_token.py new file mode 100755 index 0000000000000..c048d6a55344d --- /dev/null +++ b/test/functional/feature_initial_reissuance_token.py @@ -0,0 +1,87 @@ +#!/usr/bin/env python3 +# Copyright (c) 2014-2016 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. + +# +# Test chain initialisation when specifying number of reissuance tokens to issue. +# + +from decimal import Decimal +from test_framework.test_framework import BitcoinTestFramework +from test_framework.authproxy import JSONRPCException +from test_framework.util import connect_nodes_bi, assert_equal +from test_framework.util import * + +class InitialReissuanceTokenTest(BitcoinTestFramework): + """ + Test creation of initial reissuance token for default asset on chain set up + + """ + + def set_test_params(self): + self.setup_clean_chain = True + self.num_nodes = 2 + + #Set number of initial reissuance tokens and also set initial free coins less than max so we can reissue more later + self.extra_args = [["-initialreissuancetokens=200000000", "-initialfreecoins=2000000000000000", "-anyonecanspendaremine=1", "-con_connect_coinbase=1", "-con_blocksubsidy=0", "-blindedaddresses=1"]]*2 + + def setup_network(self, split=False): + self.setup_nodes() + connect_nodes_bi(self.nodes, 0, 1) + self.is_network_split = False + self.sync_all() + + def run_test(self): + self.nodes[0].generate(101) + self.sync_all() + + walletinfo = self.nodes[0].getwalletinfo() + balance = walletinfo['balance'] + token = "" + + #Get the hex of the reissuance token + for i in balance: + token = i + if token != "bitcoin": + break + + # Claim all anyone-can-spend reissuance tokens, which also blinds the token output + # which is required for re-issuance: https://github.com/ElementsProject/elements/issues/259 + self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 2, "", "", False, False, 6, "UNSET", token) + self.nodes[0].generate(101) + self.sync_all() + + #Check balances + walletinfo1 = self.nodes[0].getwalletinfo() + assert_equal(walletinfo1["balance"]["bitcoin"], 20000000) + assert_equal(walletinfo1["balance"][token], 2) + + #Reissue some of the default asset + self.nodes[0].reissueasset("bitcoin", 1234) + self.nodes[0].generate(101) + self.sync_all() + + #Check the reissuance worked + walletinfo1 = self.nodes[0].getwalletinfo() + assert_equal(walletinfo1["balance"]["bitcoin"], 20001234) + + #Send some 'bitcoin' to node 2 so they can fund a reissuance transaction + self.nodes[0].sendtoaddress(self.nodes[1].getnewaddress(), 1, "", "", False) + + #Send a reissuance token to node 2 so they can reissue the default asset + self.nodes[0].sendtoaddress(self.nodes[1].getnewaddress(), 1, "", "", False, False, 6, "UNSET", token) + self.nodes[0].generate(101) + self.sync_all() + + #Reissue some of the default asset + self.nodes[1].reissueasset("bitcoin", 1000) + self.nodes[1].generate(101) + self.sync_all() + + #Check balance is the 1 'bitcoin' sent from node 1 plus the 1000 'bitcoin' reissued by node 2 + walletinfo2 = self.nodes[1].getwalletinfo() + assert_equal(walletinfo2["balance"]["bitcoin"], 1001) + +if __name__ == '__main__': + InitialReissuanceTokenTest().main() diff --git a/test/functional/feature_issuance.py b/test/functional/feature_issuance.py new file mode 100755 index 0000000000000..f2fbaf254982b --- /dev/null +++ b/test/functional/feature_issuance.py @@ -0,0 +1,442 @@ +#!/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, connect_nodes_bi +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(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 = (node.getbalance()['bitcoin']-1)/5 + for i in range(5): + next_destinations[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']) + node.generate(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 setup_network(self, split=False): + self.setup_nodes() + connect_nodes_bi(self.nodes, 0, 1) + connect_nodes_bi(self.nodes, 1, 2) + self.is_network_split = False + self.sync_all() + + def run_test(self): + self.nodes[0].generate(105) + # Make sure test starts with no initial issuance. + assert_equal(len(self.nodes[0].listissuances()), 0); + + # Unblinded issuance of asset + issued = self.nodes[0].issueasset(1, 1, False) + balance = self.nodes[0].getwalletinfo()["balance"] + assert_equal(balance[issued["asset"]], 1) + assert_equal(balance[issued["token"]], 1) + # Quick unblinded reissuance check, making 2*COIN total + self.nodes[0].reissueasset(issued["asset"], 1) + + self.nodes[0].generate(1) + self.sync_all() + + issued2 = self.nodes[0].issueasset(2, 1) + 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.nodes[0].generate(1) + self.sync_all() + self.nodes[1].sendtoaddress(self.nodes[2].getnewaddress(), 3, "", "", False, False, 1, "UNSET", "", False) + self.sync_all() + self.nodes[0].generate(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.nodes[2].generate(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.nodes[0].generate(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.nodes[1].generate(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", 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.nodes[2].generate(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.nodes[1].generate(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.nodes[1].generate(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.nodes[1].generate(1) + + # Start with single issuance input, unblinded (makes 5 outputs for later larger issuances) + process_raw_issuance(self.nodes[0], [{"asset_amount": 2, "asset_address": nonblind_addr, "blind": False}]) + process_raw_issuance(self.nodes[0], [{"asset_amount": 2, "asset_address": nonblind_addr, "blind": True}]) + process_raw_issuance(self.nodes[0], [{"token_amount": 5, "token_address": nonblind_addr, "blind": False}]) + process_raw_issuance(self.nodes[0], [{"asset_amount": 7, "asset_address": nonblind_addr, "token_amount":2, "token_address":nonblind_addr, "blind":False}]) + process_raw_issuance(self.nodes[0], [{"asset_amount": 7, "asset_address": nonblind_addr, "token_amount":2, "token_address":blind_addr, "blind":False}]) + process_raw_issuance(self.nodes[0], [{"asset_amount": 7, "asset_address": blind_addr, "token_amount":2, "token_address":nonblind_addr, "blind":False}]) + process_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 + process_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}]) + process_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 + process_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}]) + process_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, ommitting explicit argument for last + process_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 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"]) + + # 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) + + # 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) + 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) + + # 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) + # 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.nodes[0].generate(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')}, 0, False, {issued_address:issued_asset["token"]}) + 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.nodes[0].generate(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", issued_asset["token"], False) + self.nodes[0].generate(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"] is not "0000000000000000000000000000000000000000000000000000000000000000") + + # Now make transaction spending that input + raw_tx = self.nodes[0].createrawtransaction([], {issued_address:1}, 0, False, {issued_address:issued_asset["token"]}) + 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.nodes[0].generate(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.nodes[0].generate(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}, 0, False, {issued_address:issued_tx["token"]}) + 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.nodes[0].generate(1) + assert_equal(self.nodes[0].gettransaction(tx_id)["confirmations"], 1) + +if __name__ == '__main__': + IssuanceTest ().main ()