From ea71ab9c9dda689c790d3b5f4f6a30ba45243e79 Mon Sep 17 00:00:00 2001 From: Tom Trevethan Date: Fri, 25 Jul 2025 13:19:37 +0100 Subject: [PATCH 1/4] enable rawtransaction send multiple assets to same address --- src/rpc/rawtransaction_util.cpp | 16 ++++++++++------ test/functional/feature_issuance.py | 24 ++++++++++++++++++++++++ test/functional/rpc_rawtransaction.py | 2 +- 3 files changed, 35 insertions(+), 7 deletions(-) diff --git a/src/rpc/rawtransaction_util.cpp b/src/rpc/rawtransaction_util.cpp index 56a472a44c5..e41679401ac 100644 --- a/src/rpc/rawtransaction_util.cpp +++ b/src/rpc/rawtransaction_util.cpp @@ -283,7 +283,7 @@ CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniVal CTxOut fee_out; // Duplicate checking - std::set destinations; + std::set> destinations; bool has_data{false}; std::vector psbt_outs; @@ -297,6 +297,8 @@ CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniVal CTxOut out(::policyAsset, 0, CScript()); bool is_fee = false; + CTxDestination destination; + std::string dest; for (const std::string& name_ : output.getKeys()) { if (name_ == "data") { if (has_data) { @@ -337,14 +339,11 @@ CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniVal // For PSET psbt_out.m_blinder_index = find_value(output, name_).get_int(); } else { - CTxDestination destination = DecodeDestination(name_); + destination = DecodeDestination(name_); if (!IsValidDestination(destination)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, std::string("Invalid Bitcoin address: ") + name_); } - - if (!destinations.insert(destination).second) { - throw JSONRPCError(RPC_INVALID_PARAMETER, std::string("Invalid parameter, duplicated address: ") + name_); - } + dest = name_; CScript scriptPubKey = GetScriptForDestination(destination); CAmount nAmount = AmountFromValue(output[name_]); @@ -362,6 +361,11 @@ CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniVal psbt_out.m_blinding_pubkey = blind_pub; } } + + if (!destinations.emplace(destination, out.nAsset.GetAsset()).second) { + throw JSONRPCError(RPC_INVALID_PARAMETER, std::string("Invalid parameter, duplicated address and asset: " + dest + " " + out.nAsset.GetHex())); + } + if (is_fee) { fee_out = out; } else { diff --git a/test/functional/feature_issuance.py b/test/functional/feature_issuance.py index e93cbfc2ccf..fa7e5249756 100755 --- a/test/functional/feature_issuance.py +++ b/test/functional/feature_issuance.py @@ -502,5 +502,29 @@ def run_test(self): # 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 ().main () diff --git a/test/functional/rpc_rawtransaction.py b/test/functional/rpc_rawtransaction.py index 6f584a10a49..73d7650c873 100755 --- a/test/functional/rpc_rawtransaction.py +++ b/test/functional/rpc_rawtransaction.py @@ -221,7 +221,7 @@ def createrawtransaction_tests(self): assert_raises_rpc_error(-5, "Invalid Bitcoin address", self.nodes[0].createrawtransaction, [], [{'foo': 0}]) assert_raises_rpc_error(-3, "Invalid amount", self.nodes[0].createrawtransaction, [], [{address: 'foo'}]) assert_raises_rpc_error(-3, "Amount out of range", self.nodes[0].createrawtransaction, [], [{address: -1}]) - assert_raises_rpc_error(-8, "Invalid parameter, duplicated address: %s" % address, self.nodes[0].createrawtransaction, [], [{address: 1}, {address: 1}]) + assert_raises_rpc_error(-8, "Invalid parameter, duplicated address and asset: %s" % address, self.nodes[0].createrawtransaction, [], [{address: 1}, {address: 1}]) assert_raises_rpc_error(-8, "Invalid parameter, duplicate key: data", self.nodes[0].createrawtransaction, [], [{"data": 'aa'}, {"data": "bb"}]) assert_raises_rpc_error(-1, "JSON value is not an object as expected", self.nodes[0].createrawtransaction, [], [['key-value pair1'], ['2']]) From dadf453e73d0b9a580bf02b196f0a74dad9c89e0 Mon Sep 17 00:00:00 2001 From: Tom Trevethan Date: Fri, 25 Jul 2025 13:43:39 +0100 Subject: [PATCH 2/4] fix whitespace --- test/functional/feature_issuance.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/functional/feature_issuance.py b/test/functional/feature_issuance.py index fa7e5249756..ab4b5df67cf 100755 --- a/test/functional/feature_issuance.py +++ b/test/functional/feature_issuance.py @@ -509,7 +509,7 @@ def run_test(self): 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() + self.sync_all() balance = self.nodes[0].getwalletinfo()["balance"] assert_equal(balance[issued_1["asset"]], 1) assert_equal(balance[issued_2["asset"]], 1) From b1e32c2f363f80b4dcf5d1695c07787df79e097d Mon Sep 17 00:00:00 2001 From: Tom Trevethan Date: Fri, 25 Jul 2025 15:04:52 +0100 Subject: [PATCH 3/4] Update test/functional/feature_issuance.py Co-authored-by: Byron Hambly --- test/functional/feature_issuance.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/functional/feature_issuance.py b/test/functional/feature_issuance.py index ab4b5df67cf..e3858edeedc 100755 --- a/test/functional/feature_issuance.py +++ b/test/functional/feature_issuance.py @@ -517,7 +517,7 @@ def run_test(self): 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) + 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) From 55f0c7fc906a2ecfea8985f38cb6f5ec8311350f Mon Sep 17 00:00:00 2001 From: Tom Trevethan Date: Fri, 25 Jul 2025 15:05:01 +0100 Subject: [PATCH 4/4] Update test/functional/feature_issuance.py Co-authored-by: Byron Hambly --- test/functional/feature_issuance.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/functional/feature_issuance.py b/test/functional/feature_issuance.py index e3858edeedc..ba34ed7291f 100755 --- a/test/functional/feature_issuance.py +++ b/test/functional/feature_issuance.py @@ -520,7 +520,7 @@ def run_test(self): 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) + 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)