diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp index dbfd849fb8c..cf91b602e84 100644 --- a/src/rpc/client.cpp +++ b/src/rpc/client.cpp @@ -191,6 +191,8 @@ static const CRPCConvertParam vRPCConvertParams[] = { "sendmany", 9 , "ignoreblindfail" }, { "sendtoaddress", 9 , "ignoreblindfail" }, { "createrawtransaction", 4, "output_assets" }, + { "computeissuanceasset", 1, "prevout_n" }, + { "computeissuanceasset", 2, "blinded" }, }; // clang-format on diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index a69db61faa9..6e67a3404eb 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -2264,6 +2264,53 @@ UniValue rawreissueasset(const JSONRPCRequest& request) return ret; } +UniValue computeissuanceasset(const JSONRPCRequest& request) +{ + if (request.fHelp || request.params.size() < 2 || request.params.size() > 4) + throw std::runtime_error( + "computeissuanceasset prevout_hash prevout_n ( contract_hash blinded )\n" + "\nComputes asset id from previous transaction information as well as the contract hash.\n" + "\nArguments:\n" + "1. \"prevout_hash\" (string, required) Transaction prevout hash for issuance input.\n" + "2. \"prevout_n\" (numeric, required) Transaction prevout index for issaunce input.\n" + "3. \"contract_hash\" (string, optional, default=00..00) Contract hash that is put into issuance definition. Must be 32 bytes worth in hex string form." + "4. \"blinded\" (bool, optional, default=true) Whether or not the issuance is blinded. Only effects `token` derivation.\n" + "\nResult:\n" + " { (json object)\n" + " \"entropy\":\"\" (string) Entropy of the asset type.\n" + " \"asset\":\"\", (string) Asset type for issuance if known.\n" + " \"token\":\"\", (string) Token type for issuance.\n" + " }\n" + ); + + uint256 prevout_hash = ParseHashV(request.params[0], "prevout_hash"); + + int32_t prevout_n = request.params[1].get_int(); + + // Check for optional contract to hash into definition + uint256 contract_hash; + if (!request.params[2].isNull()) { + contract_hash = ParseHashV(request.params[2], "contract_hash"); + } + + const bool blind_issuance = request.params[3].isNull() ? true : request.params[3].get_bool(); + + uint256 entropy; + CAsset asset; + CAsset token; + COutPoint prevout; + prevout.hash = prevout_hash; + prevout.n = prevout_n; + GenerateAssetEntropy(entropy, prevout, contract_hash); + CalculateAsset(asset, entropy); + CalculateReissuanceToken(token, entropy, blind_issuance); + UniValue obj(UniValue::VOBJ); + obj.pushKV("entropy", entropy.GetHex()); + obj.pushKV("asset", asset.GetHex()); + obj.pushKV("token", token.GetHex()); + return obj; +} + // clang-format off static const CRPCCommand commands[] = @@ -2289,6 +2336,7 @@ static const CRPCCommand commands[] = { "blockchain", "verifytxoutproof", &verifytxoutproof, {"proof"} }, { "rawtransactions", "rawissueasset", &rawissueasset, {"transaction", "issuances"}}, { "rawtransactions", "rawreissueasset", &rawreissueasset, {"transaction", "reissuances"}}, + { "rawtransactions", "computeissuanceasset", &computeissuanceasset, {"prevout_hash", "prevout_n", "contract_hash", "blinded"}}, }; // clang-format on diff --git a/test/functional/feature_issuance.py b/test/functional/feature_issuance.py index 98065e96c56..232a823a088 100755 --- a/test/functional/feature_issuance.py +++ b/test/functional/feature_issuance.py @@ -297,26 +297,49 @@ def run_test(self): 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) + issued_tx = self.nodes[2].rawissueasset(funded_tx, [{"asset_amount":1, "asset_address":nonblind_addr}])[0] + decode_tx = self.nodes[0].decoderawtransaction(issued_tx["hex"]) id_set.add(decode_tx["vin"][0]["issuance"]["asset"]) + # Recompute details minimal helper API + computed_asset = self.nodes[0].computeissuanceasset(decode_tx["vin"][0]["txid"], 0) + assert_equal(computed_asset["entropy"], issued_tx["entropy"]) + assert_equal(computed_asset["asset"], issued_tx["asset"]) + assert_equal(computed_asset["token"], issued_tx["token"]) + # 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) + issued_tx = self.nodes[2].rawissueasset(funded_tx, [{"asset_amount":1, "asset_address":nonblind_addr, "contract_hash":"00"*32}])[0] + decode_tx = self.nodes[0].decoderawtransaction(issued_tx["hex"]) id_set.add(decode_tx["vin"][0]["issuance"]["asset"]) assert_equal(len(id_set), 1) + # Test optional blind arg + computed_asset = self.nodes[0].computeissuanceasset(decode_tx["vin"][0]["txid"], 0, "00"*32, True) + assert_equal(computed_asset["entropy"], issued_tx["entropy"]) + assert_equal(computed_asset["asset"], issued_tx["asset"]) + assert_equal(computed_asset["token"], issued_tx["token"]) + # 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) + issued_tx = self.nodes[2].rawissueasset(funded_tx, [{"asset_amount":1, "asset_address":nonblind_addr, "contract_hash":"deadbeef"*8}])[0] + decode_tx = self.nodes[0].decoderawtransaction(issued_tx["hex"]) 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) + issued_tx = self.nodes[2].rawissueasset(funded_tx, [{"asset_amount":1, "asset_address":nonblind_addr, "contract_hash":"deadbeee"*8}])[0] + decode_tx = self.nodes[0].decoderawtransaction(issued_tx["hex"]) id_set.add(decode_tx["vin"][0]["issuance"]["asset"]) assert_equal(len(id_set), 3) + computed_asset = self.nodes[0].computeissuanceasset(decode_tx["vin"][0]["txid"], 0, "deadbeee"*8) + assert_equal(computed_asset["entropy"], issued_tx["entropy"]) + assert_equal(computed_asset["asset"], issued_tx["asset"]) + assert_equal(computed_asset["token"], issued_tx["token"]) + + # Only change blind arg to false, token should mistmatch + unblind_computed = self.nodes[0].computeissuanceasset(decode_tx["vin"][0]["txid"], 0, "deadbeee"*8, False) + assert_equal(computed_asset["entropy"], issued_tx["entropy"]) + assert_equal(computed_asset["asset"], issued_tx["asset"]) + assert computed_asset["token"] != issued_tx["token"] + # 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