diff options
author | Wladimir J. van der Laan <laanwj@gmail.com> | 2018-03-13 17:58:52 +0100 |
---|---|---|
committer | Wladimir J. van der Laan <laanwj@gmail.com> | 2018-03-13 18:00:06 +0100 |
commit | 702e8b70bd870f506ce8157e58b77cb7d0a0f98d (patch) | |
tree | 089aad138eced3caaf758d325214c28d1b83cc35 /src | |
parent | 0f0229d0c36310eea329716fbc81541c7c3a62b1 (diff) | |
parent | fac70134a9e3a1fc8e491aecb3c36d0184ba7b82 (diff) | |
download | bitcoin-702e8b70bd870f506ce8157e58b77cb7d0a0f98d.tar.xz |
Merge #11872: [rpc] createrawtransaction: Accept sorted outputs
fac70134a rpc: Update createrawtransaction examples (MarcoFalke)
fa06dfce0 [rpc] createrawtransaction: Accept sorted outputs (MarcoFalke)
8acd25d85 rpc: Allow typeAny in RPCTypeCheck (MarcoFalke)
Pull request description:
The second parameter of the `createrawtransaction` is a dictionary of the outputs. This comes with at least two drawbacks:
* In case of duplicate keys, either of them might silently disappear, with no user feedback at all. A user needs to make other mistakes, but this could eventually lead to abnormal tx fees.
* A dictionary does not guarantee that keys are sorted. Again, a user needs to keep this in mind, as it could eventually lead to excessive tx fees.
Even though my scenario of loss-of-funds is unlikely to happen, I see it as a inconvenience that should be fixed.
Tree-SHA512: cd562f34f7f9f79c7d3433805971325c388c2035611be283980f4049066a622df4f0afdc11d7ac96662260ec0115147cb65e1ab5268f5a1b063242f3fe425f77
Diffstat (limited to 'src')
-rw-r--r-- | src/rpc/rawtransaction.cpp | 66 | ||||
-rw-r--r-- | src/rpc/server.cpp | 11 | ||||
-rw-r--r-- | src/rpc/server.h | 8 | ||||
-rw-r--r-- | src/test/rpc_tests.cpp | 1 |
4 files changed, 57 insertions, 29 deletions
diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index 8dcfb48e9a..883ec71747 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -316,9 +316,10 @@ UniValue verifytxoutproof(const JSONRPCRequest& request) UniValue createrawtransaction(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() < 2 || request.params.size() > 4) + if (request.fHelp || request.params.size() < 2 || request.params.size() > 4) { throw std::runtime_error( - "createrawtransaction [{\"txid\":\"id\",\"vout\":n},...] {\"address\":amount,\"data\":\"hex\",...} ( locktime ) ( replaceable )\n" + // clang-format off + "createrawtransaction [{\"txid\":\"id\",\"vout\":n},...] [{\"address\":amount},{\"data\":\"hex\"},...] ( locktime ) ( replaceable )\n" "\nCreate a transaction spending the given inputs and creating new outputs.\n" "Outputs can be addresses or data.\n" "Returns hex-encoded raw transaction.\n" @@ -329,18 +330,23 @@ UniValue createrawtransaction(const JSONRPCRequest& request) "1. \"inputs\" (array, required) A json array of json objects\n" " [\n" " {\n" - " \"txid\":\"id\", (string, required) The transaction id\n" + " \"txid\":\"id\", (string, required) The transaction id\n" " \"vout\":n, (numeric, required) The output number\n" " \"sequence\":n (numeric, optional) The sequence number\n" " } \n" " ,...\n" " ]\n" - "2. \"outputs\" (object, required) a json object with outputs\n" + "2. \"outputs\" (array, required) a json array with outputs (key-value pairs)\n" + " [\n" " {\n" - " \"address\": x.xxx, (numeric or string, required) The key is the bitcoin address, the numeric value (can be string) is the " + CURRENCY_UNIT + " amount\n" - " \"data\": \"hex\" (string, required) The key is \"data\", the value is hex encoded data\n" - " ,...\n" + " \"address\": x.xxx, (obj, optional) A key-value pair. The key (string) is the bitcoin address, the value (float or string) is the amount in " + CURRENCY_UNIT + "\n" + " },\n" + " {\n" + " \"data\": \"hex\" (obj, optional) A key-value pair. The key must be \"data\", the value is hex encoded data\n" " }\n" + " ,... More key-value pairs of the above form. For compatibility reasons, a dictionary, which holds the key-value pairs directly, is also\n" + " accepted as second parameter.\n" + " ]\n" "3. locktime (numeric, optional, default=0) Raw locktime. Non-0 value also locktime-activates inputs\n" "4. replaceable (boolean, optional, default=false) Marks this transaction as BIP125 replaceable.\n" " Allows this transaction to be replaced by a transaction with higher fees. If provided, it is an error if explicit sequence numbers are incompatible.\n" @@ -348,18 +354,29 @@ UniValue createrawtransaction(const JSONRPCRequest& request) "\"transaction\" (string) hex string of the transaction\n" "\nExamples:\n" - + HelpExampleCli("createrawtransaction", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\" \"{\\\"address\\\":0.01}\"") - + HelpExampleCli("createrawtransaction", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\" \"{\\\"data\\\":\\\"00010203\\\"}\"") - + HelpExampleRpc("createrawtransaction", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\", \"{\\\"address\\\":0.01}\"") - + HelpExampleRpc("createrawtransaction", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\", \"{\\\"data\\\":\\\"00010203\\\"}\"") + + HelpExampleCli("createrawtransaction", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\" \"[{\\\"address\\\":0.01}]\"") + + HelpExampleCli("createrawtransaction", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\" \"[{\\\"data\\\":\\\"00010203\\\"}]\"") + + HelpExampleRpc("createrawtransaction", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\", \"[{\\\"address\\\":0.01}]\"") + + HelpExampleRpc("createrawtransaction", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\", \"[{\\\"data\\\":\\\"00010203\\\"}]\"") + // clang-format on ); + } - RPCTypeCheck(request.params, {UniValue::VARR, UniValue::VOBJ, UniValue::VNUM, UniValue::VBOOL}, true); + RPCTypeCheck(request.params, { + UniValue::VARR, + UniValueType(), // ARR or OBJ, checked later + UniValue::VNUM, + UniValue::VBOOL + }, true + ); if (request.params[0].isNull() || request.params[1].isNull()) throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, arguments 1 and 2 must be non-null"); UniValue inputs = request.params[0].get_array(); - UniValue sendTo = request.params[1].get_obj(); + const bool outputs_is_obj = request.params[1].isObject(); + UniValue outputs = outputs_is_obj ? + request.params[1].get_obj() : + request.params[1].get_array(); CMutableTransaction rawTx; @@ -411,11 +428,24 @@ UniValue createrawtransaction(const JSONRPCRequest& request) } std::set<CTxDestination> destinations; - std::vector<std::string> addrList = sendTo.getKeys(); - for (const std::string& name_ : addrList) { - + if (!outputs_is_obj) { + // Translate array of key-value pairs into dict + UniValue outputs_dict = UniValue(UniValue::VOBJ); + for (size_t i = 0; i < outputs.size(); ++i) { + const UniValue& output = outputs[i]; + if (!output.isObject()) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, key-value pair not an object as expected"); + } + if (output.size() != 1) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, key-value pair must contain exactly one key"); + } + outputs_dict.pushKVs(output); + } + outputs = std::move(outputs_dict); + } + for (const std::string& name_ : outputs.getKeys()) { if (name_ == "data") { - std::vector<unsigned char> data = ParseHexV(sendTo[name_].getValStr(),"Data"); + std::vector<unsigned char> data = ParseHexV(outputs[name_].getValStr(), "Data"); CTxOut out(0, CScript() << OP_RETURN << data); rawTx.vout.push_back(out); @@ -430,7 +460,7 @@ UniValue createrawtransaction(const JSONRPCRequest& request) } CScript scriptPubKey = GetScriptForDestination(destination); - CAmount nAmount = AmountFromValue(sendTo[name_]); + CAmount nAmount = AmountFromValue(outputs[name_]); CTxOut out(nAmount, scriptPubKey); rawTx.vout.push_back(out); diff --git a/src/rpc/server.cpp b/src/rpc/server.cpp index 35401bf876..54995ef000 100644 --- a/src/rpc/server.cpp +++ b/src/rpc/server.cpp @@ -50,12 +50,11 @@ void RPCServer::OnStopped(std::function<void ()> slot) } void RPCTypeCheck(const UniValue& params, - const std::list<UniValue::VType>& typesExpected, + const std::list<UniValueType>& typesExpected, bool fAllowNull) { unsigned int i = 0; - for (UniValue::VType t : typesExpected) - { + for (const UniValueType& t : typesExpected) { if (params.size() <= i) break; @@ -67,10 +66,10 @@ void RPCTypeCheck(const UniValue& params, } } -void RPCTypeCheckArgument(const UniValue& value, UniValue::VType typeExpected) +void RPCTypeCheckArgument(const UniValue& value, const UniValueType& typeExpected) { - if (value.type() != typeExpected) { - throw JSONRPCError(RPC_TYPE_ERROR, strprintf("Expected type %s, got %s", uvTypeName(typeExpected), uvTypeName(value.type()))); + if (!typeExpected.typeAny && value.type() != typeExpected.type) { + throw JSONRPCError(RPC_TYPE_ERROR, strprintf("Expected type %s, got %s", uvTypeName(typeExpected.type), uvTypeName(value.type()))); } } diff --git a/src/rpc/server.h b/src/rpc/server.h index 075940cb90..8b32924fbc 100644 --- a/src/rpc/server.h +++ b/src/rpc/server.h @@ -28,9 +28,9 @@ namespace RPCServer } /** Wrapper for UniValue::VType, which includes typeAny: - * Used to denote don't care type. Only used by RPCTypeCheckObj */ + * Used to denote don't care type. */ struct UniValueType { - explicit UniValueType(UniValue::VType _type) : typeAny(false), type(_type) {} + UniValueType(UniValue::VType _type) : typeAny(false), type(_type) {} UniValueType() : typeAny(true) {} bool typeAny; UniValue::VType type; @@ -69,12 +69,12 @@ bool RPCIsInWarmup(std::string *outStatus); * the right number of arguments are passed, just that any passed are the correct type. */ void RPCTypeCheck(const UniValue& params, - const std::list<UniValue::VType>& typesExpected, bool fAllowNull=false); + const std::list<UniValueType>& typesExpected, bool fAllowNull=false); /** * Type-check one argument; throws JSONRPCError if wrong type given. */ -void RPCTypeCheckArgument(const UniValue& value, UniValue::VType typeExpected); +void RPCTypeCheckArgument(const UniValue& value, const UniValueType& typeExpected); /* Check for expected keys/value types in an Object. diff --git a/src/test/rpc_tests.cpp b/src/test/rpc_tests.cpp index 892e4f2dac..8d9f80ada0 100644 --- a/src/test/rpc_tests.cpp +++ b/src/test/rpc_tests.cpp @@ -52,7 +52,6 @@ BOOST_AUTO_TEST_CASE(rpc_rawparams) BOOST_CHECK_THROW(CallRPC("createrawtransaction"), std::runtime_error); BOOST_CHECK_THROW(CallRPC("createrawtransaction null null"), std::runtime_error); BOOST_CHECK_THROW(CallRPC("createrawtransaction not_array"), std::runtime_error); - BOOST_CHECK_THROW(CallRPC("createrawtransaction [] []"), std::runtime_error); BOOST_CHECK_THROW(CallRPC("createrawtransaction {} {}"), std::runtime_error); BOOST_CHECK_NO_THROW(CallRPC("createrawtransaction [] {}")); BOOST_CHECK_THROW(CallRPC("createrawtransaction [] {} extra"), std::runtime_error); |