diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -460,7 +460,10 @@ "outputs", RPCArg::Type::ARR, RPCArg::Optional::NO, - "a json array with outputs (key-value pairs).\n" + "a json array with outputs (key-value pairs), where none of " + "the keys are duplicated.\n" + "That is, each address can only appear once and there can only " + "be one 'data' object.\n" "For compatibility reasons, a dictionary, which holds the " "key-value pairs directly, is also\n" " accepted as second parameter.", @@ -1518,7 +1521,10 @@ "outputs", RPCArg::Type::ARR, RPCArg::Optional::NO, - "a json array with outputs (key-value pairs).\n" + "a json array with outputs (key-value pairs), where none of " + "the keys are duplicated.\n" + "That is, each address can only appear once and there can only " + "be one 'data' object.\n" "For compatibility reasons, a dictionary, which holds the " "key-value pairs directly, is also\n" " accepted as second parameter.", diff --git a/src/rpc/rawtransaction_util.cpp b/src/rpc/rawtransaction_util.cpp --- a/src/rpc/rawtransaction_util.cpp +++ b/src/rpc/rawtransaction_util.cpp @@ -89,7 +89,6 @@ rawTx.vin.push_back(in); } - std::set destinations; if (!outputs_is_obj) { // Translate array of key-value pairs into dict UniValue outputs_dict = UniValue(UniValue::VOBJ); @@ -109,8 +108,18 @@ } outputs = std::move(outputs_dict); } + + // Duplicate checking + std::set destinations; + bool has_data{false}; + for (const std::string &name_ : outputs.getKeys()) { if (name_ == "data") { + if (has_data) { + throw JSONRPCError(RPC_INVALID_PARAMETER, + "Invalid parameter, duplicate key: data"); + } + has_data = true; std::vector data = ParseHexV(outputs[name_].getValStr(), "Data"); diff --git a/src/test/rpc_tests.cpp b/src/test/rpc_tests.cpp --- a/src/test/rpc_tests.cpp +++ b/src/test/rpc_tests.cpp @@ -203,14 +203,6 @@ "\"a3b807410df0b60fcb9736768df5823938b2f838694939ba45f3c0a1bff1" "50ed\",\"vout\":0}] {\"data\":\"68656c6c6f776f726c64\"}")); - // Allow more than one data transaction output - BOOST_CHECK_NO_THROW(CallRPC("createrawtransaction " - "[{\"txid\":" - "\"a3b807410df0b60fcb9736768df5823938b2f838694" - "939ba45f3c0a1bff150ed\",\"vout\":0}] " - "{\"data\":\"68656c6c6f776f726c64\",\"data\":" - "\"68656c6c6f776f726c64\"}")); - // Key not "data" (bad address) BOOST_CHECK_THROW( CallRPC("createrawtransaction " diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -4567,7 +4567,10 @@ "outputs", RPCArg::Type::ARR, RPCArg::Optional::NO, - "a json array with outputs (key-value pairs).\n" + "a json array with outputs (key-value pairs), where none of " + "the keys are duplicated.\n" + "That is, each address can only appear once and there can only " + "be one 'data' object.\n" "For compatibility reasons, a dictionary, which holds the " "key-value pairs directly, is also\n" " accepted as second parameter.", diff --git a/test/functional/rpc_rawtransaction.py b/test/functional/rpc_rawtransaction.py --- a/test/functional/rpc_rawtransaction.py +++ b/test/functional/rpc_rawtransaction.py @@ -147,6 +147,20 @@ address), self.nodes[0].createrawtransaction, [], multidict([(address, 1), (address, 1)])) assert_raises_rpc_error(-8, "Invalid parameter, duplicated address: {}".format( 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(-8, + "Invalid parameter, duplicate key: data", + self.nodes[0].createrawtransaction, + [], + multidict([("data", + 'aa'), + ("data", + "bb")])) assert_raises_rpc_error(-8, "Invalid parameter, key-value pair must contain exactly one key", self.nodes[0].createrawtransaction, [], [{'a': 1, 'b': 2}]) assert_raises_rpc_error(-8, "Invalid parameter, key-value pair not an object as expected", @@ -181,23 +195,14 @@ self.nodes[2].createrawtransaction(inputs=[{'txid': txid, 'vout': 9}], outputs=[ {address: 99}, {address2: 99}]), ) - # Two data outputs - tx.deserialize(BytesIO(hex_str_to_bytes(self.nodes[2].createrawtransaction(inputs=[ - {'txid': txid, 'vout': 9}], outputs=multidict([('data', '99'), ('data', '99')]))))) - assert_equal(len(tx.vout), 2) - assert_equal( - tx.serialize().hex(), - self.nodes[2].createrawtransaction(inputs=[{'txid': txid, 'vout': 9}], outputs=[ - {'data': '99'}, {'data': '99'}]), - ) # Multiple mixed outputs tx.deserialize(BytesIO(hex_str_to_bytes(self.nodes[2].createrawtransaction(inputs=[ - {'txid': txid, 'vout': 9}], outputs=multidict([(address, 99), ('data', '99'), ('data', '99')]))))) + {'txid': txid, 'vout': 9}], outputs=multidict([(address, 99), (address2, 99), ('data', '99')]))))) assert_equal(len(tx.vout), 3) assert_equal( tx.serialize().hex(), self.nodes[2].createrawtransaction(inputs=[{'txid': txid, 'vout': 9}], outputs=[ - {address: 99}, {'data': '99'}, {'data': '99'}]), + {address: 99}, {address2: 99}, {'data': '99'}]), ) self.log.info('sendrawtransaction with missing input')