diff --git a/src/rpc/rawtransaction.h b/src/rpc/rawtransaction.h --- a/src/rpc/rawtransaction.h +++ b/src/rpc/rawtransaction.h @@ -6,6 +6,7 @@ #define BITCOIN_RPC_RAWTRANSACTION_H class CBasicKeyStore; +class CChainParams; class CMutableTransaction; class UniValue; @@ -14,4 +15,10 @@ CBasicKeyStore *keystore, bool tempKeystore, const UniValue &hashType); +/** Create a transaction from univalue parameters */ +CMutableTransaction ConstructTransaction(const CChainParams ¶ms, + const UniValue &inputs_in, + const UniValue &outputs_in, + const UniValue &locktime); + #endif // BITCOIN_RPC_RAWTRANSACTION_H diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -409,74 +409,25 @@ return res; } -static UniValue createrawtransaction(const Config &config, - const JSONRPCRequest &request) { - if (request.fHelp || request.params.size() < 2 || - request.params.size() > 3) { - throw std::runtime_error( - // clang-format off - "createrawtransaction [{\"txid\":\"id\",\"vout\":n},...] [{\"address\":amount},{\"data\":\"hex\"},...] ( locktime )\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" - "Note that the transaction's inputs are not signed, and\n" - "it is not stored in the wallet or transmitted to the network.\n" - - "\nArguments:\n" - "1. \"inputs\" (array, required) A json array of " - "json objects\n" - " [\n" - " {\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\" (array, required) a json array with outputs (key-value pairs)\n" - " [\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" - "\nResult:\n" - "\"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\\\"}]\"") - // clang-format on - ); - } - - 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()) { +CMutableTransaction ConstructTransaction(const CChainParams ¶ms, + const UniValue &inputs_in, + const UniValue &outputs_in, + const UniValue &locktime) { + if (inputs_in.isNull() || outputs_in.isNull()) { throw JSONRPCError( RPC_INVALID_PARAMETER, "Invalid parameter, arguments 1 and 2 must be non-null"); } - UniValue inputs = request.params[0].get_array(); - const bool outputs_is_obj = request.params[1].isObject(); - UniValue outputs = outputs_is_obj ? request.params[1].get_obj() - : request.params[1].get_array(); + UniValue inputs = inputs_in.get_array(); + const bool outputs_is_obj = outputs_in.isObject(); + UniValue outputs = + outputs_is_obj ? outputs_in.get_obj() : outputs_in.get_array(); CMutableTransaction rawTx; - if (!request.params[2].isNull()) { - int64_t nLockTime = request.params[2].get_int64(); + if (!locktime.isNull()) { + int64_t nLockTime = locktime.get_int64(); if (nLockTime < 0 || nLockTime > std::numeric_limits::max()) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, locktime out of range"); @@ -557,8 +508,7 @@ CTxOut out(Amount::zero(), CScript() << OP_RETURN << data); rawTx.vout.push_back(out); } else { - CTxDestination destination = - DecodeDestination(name_, config.GetChainParams()); + CTxDestination destination = DecodeDestination(name_, params); if (!IsValidDestination(destination)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, std::string("Invalid Bitcoin address: ") + @@ -580,6 +530,67 @@ } } + return rawTx; +} + +static UniValue createrawtransaction(const Config &config, + const JSONRPCRequest &request) { + if (request.fHelp || request.params.size() < 2 || + request.params.size() > 3) { + throw std::runtime_error( + // clang-format off + "createrawtransaction [{\"txid\":\"id\",\"vout\":n},...] [{\"address\":amount},{\"data\":\"hex\"},...] ( locktime )\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" + "Note that the transaction's inputs are not signed, and\n" + "it is not stored in the wallet or transmitted to the network.\n" + + "\nArguments:\n" + "1. \"inputs\" (array, required) A json array of " + "json objects\n" + " [\n" + " {\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\" (array, required) a json array with outputs (key-value pairs)\n" + " [\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" + "\nResult:\n" + "\"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\\\"}]\"") + // clang-format on + ); + } + + RPCTypeCheck(request.params, + {UniValue::VARR, + UniValueType(), // ARR or OBJ, checked later + UniValue::VNUM}, + true); + + CMutableTransaction rawTx = + ConstructTransaction(config.GetChainParams(), request.params[0], + request.params[1], request.params[2]); + return EncodeHexTx(CTransaction(rawTx)); } diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -3806,6 +3806,117 @@ return results; } +void FundTransaction(CWallet *const pwallet, CMutableTransaction &tx, + Amount &fee_out, int &change_position, UniValue options) { + // Make sure the results are valid at least up to the most recent block + // the user could have gotten from another RPC command prior to now + pwallet->BlockUntilSyncedToCurrentChain(); + + CCoinControl coinControl; + change_position = -1; + bool lockUnspents = false; + UniValue subtractFeeFromOutputs; + std::set setSubtractFeeFromOutputs; + + if (!options.isNull()) { + if (options.type() == UniValue::VBOOL) { + // backward compatibility bool only fallback + coinControl.fAllowWatchOnly = options.get_bool(); + } else { + RPCTypeCheckArgument(options, UniValue::VOBJ); + RPCTypeCheckObj( + options, + { + {"changeAddress", UniValueType(UniValue::VSTR)}, + {"changePosition", UniValueType(UniValue::VNUM)}, + {"includeWatching", UniValueType(UniValue::VBOOL)}, + {"lockUnspents", UniValueType(UniValue::VBOOL)}, + // will be checked below + {"feeRate", UniValueType()}, + {"subtractFeeFromOutputs", UniValueType(UniValue::VARR)}, + }, + true, true); + + if (options.exists("changeAddress")) { + CTxDestination dest = DecodeDestination( + options["changeAddress"].get_str(), pwallet->chainParams); + + if (!IsValidDestination(dest)) { + throw JSONRPCError( + RPC_INVALID_ADDRESS_OR_KEY, + "changeAddress must be a valid bitcoin address"); + } + + coinControl.destChange = dest; + } + + if (options.exists("changePosition")) { + change_position = options["changePosition"].get_int(); + } + + if (options.exists("includeWatching")) { + coinControl.fAllowWatchOnly = + options["includeWatching"].get_bool(); + } + + if (options.exists("lockUnspents")) { + lockUnspents = options["lockUnspents"].get_bool(); + } + + if (options.exists("feeRate")) { + coinControl.m_feerate = + CFeeRate(AmountFromValue(options["feeRate"])); + coinControl.fOverrideFeeRate = true; + } + + if (options.exists("subtractFeeFromOutputs")) { + subtractFeeFromOutputs = + options["subtractFeeFromOutputs"].get_array(); + } + } + } + + if (tx.vout.size() == 0) { + throw JSONRPCError(RPC_INVALID_PARAMETER, + "TX must have at least one output"); + } + + if (change_position != -1 && + (change_position < 0 || + (unsigned int)change_position > tx.vout.size())) { + throw JSONRPCError(RPC_INVALID_PARAMETER, + "changePosition out of bounds"); + } + + for (size_t idx = 0; idx < subtractFeeFromOutputs.size(); idx++) { + int pos = subtractFeeFromOutputs[idx].get_int(); + if (setSubtractFeeFromOutputs.count(pos)) { + throw JSONRPCError( + RPC_INVALID_PARAMETER, + strprintf("Invalid parameter, duplicated position: %d", pos)); + } + if (pos < 0) { + throw JSONRPCError( + RPC_INVALID_PARAMETER, + strprintf("Invalid parameter, negative position: %d", pos)); + } + if (pos >= int(tx.vout.size())) { + throw JSONRPCError( + RPC_INVALID_PARAMETER, + strprintf("Invalid parameter, position too large: %d", pos)); + } + setSubtractFeeFromOutputs.insert(pos); + } + + std::string strFailReason; + + if (!pwallet->FundTransaction(tx, fee_out, change_position, strFailReason, + lockUnspents, setSubtractFeeFromOutputs, + coinControl)) { + throw JSONRPCError(RPC_WALLET_ERROR, strFailReason); + } +} + static UniValue fundrawtransaction(const Config &config, const JSONRPCRequest &request) { std::shared_ptr const wallet = GetWalletForJSONRPCRequest(request); @@ -3895,79 +4006,7 @@ HelpExampleCli("sendrawtransaction", "\"signedtransactionhex\"")); } - RPCTypeCheck(request.params, {UniValue::VSTR}); - - // Make sure the results are valid at least up to the most recent block - // the user could have gotten from another RPC command prior to now - pwallet->BlockUntilSyncedToCurrentChain(); - - CCoinControl coinControl; - int changePosition = -1; - bool lockUnspents = false; - UniValue subtractFeeFromOutputs; - std::set setSubtractFeeFromOutputs; - - if (!request.params[1].isNull()) { - if (request.params[1].type() == UniValue::VBOOL) { - // backward compatibility bool only fallback - coinControl.fAllowWatchOnly = request.params[1].get_bool(); - } else { - RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VOBJ}); - - UniValue options = request.params[1]; - - RPCTypeCheckObj( - options, - { - {"changeAddress", UniValueType(UniValue::VSTR)}, - {"changePosition", UniValueType(UniValue::VNUM)}, - {"includeWatching", UniValueType(UniValue::VBOOL)}, - {"lockUnspents", UniValueType(UniValue::VBOOL)}, - // will be checked below - {"feeRate", UniValueType()}, - {"subtractFeeFromOutputs", UniValueType(UniValue::VARR)}, - }, - true, true); - - if (options.exists("changeAddress")) { - CTxDestination dest = - DecodeDestination(options["changeAddress"].get_str(), - config.GetChainParams()); - - if (!IsValidDestination(dest)) { - throw JSONRPCError( - RPC_INVALID_ADDRESS_OR_KEY, - "changeAddress must be a valid bitcoin address"); - } - - coinControl.destChange = dest; - } - - if (options.exists("changePosition")) { - changePosition = options["changePosition"].get_int(); - } - - if (options.exists("includeWatching")) { - coinControl.fAllowWatchOnly = - options["includeWatching"].get_bool(); - } - - if (options.exists("lockUnspents")) { - lockUnspents = options["lockUnspents"].get_bool(); - } - - if (options.exists("feeRate")) { - coinControl.m_feerate = - CFeeRate(AmountFromValue(options["feeRate"])); - coinControl.fOverrideFeeRate = true; - } - - if (options.exists("subtractFeeFromOutputs")) { - subtractFeeFromOutputs = - options["subtractFeeFromOutputs"].get_array(); - } - } - } + RPCTypeCheck(request.params, {UniValue::VSTR, UniValueType()}); // parse hex string from parameter CMutableTransaction tx; @@ -3975,50 +4014,14 @@ throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed"); } - if (tx.vout.size() == 0) { - throw JSONRPCError(RPC_INVALID_PARAMETER, - "TX must have at least one output"); - } - - if (changePosition != -1 && - (changePosition < 0 || (unsigned int)changePosition > tx.vout.size())) { - throw JSONRPCError(RPC_INVALID_PARAMETER, - "changePosition out of bounds"); - } - - for (size_t idx = 0; idx < subtractFeeFromOutputs.size(); idx++) { - int pos = subtractFeeFromOutputs[idx].get_int(); - if (setSubtractFeeFromOutputs.count(pos)) { - throw JSONRPCError( - RPC_INVALID_PARAMETER, - strprintf("Invalid parameter, duplicated position: %d", pos)); - } - if (pos < 0) { - throw JSONRPCError( - RPC_INVALID_PARAMETER, - strprintf("Invalid parameter, negative position: %d", pos)); - } - if (pos >= int(tx.vout.size())) { - throw JSONRPCError( - RPC_INVALID_PARAMETER, - strprintf("Invalid parameter, position too large: %d", pos)); - } - setSubtractFeeFromOutputs.insert(pos); - } - - Amount nFeeOut; - std::string strFailReason; - - if (!pwallet->FundTransaction(tx, nFeeOut, changePosition, strFailReason, - lockUnspents, setSubtractFeeFromOutputs, - coinControl)) { - throw JSONRPCError(RPC_WALLET_ERROR, strFailReason); - } + Amount fee; + int change_position; + FundTransaction(pwallet, tx, fee, change_position, request.params[1]); UniValue result(UniValue::VOBJ); result.pushKV("hex", EncodeHexTx(CTransaction(tx))); - result.pushKV("changepos", changePosition); - result.pushKV("fee", ValueFromAmount(nFeeOut)); + result.pushKV("fee", ValueFromAmount(fee)); + result.pushKV("changepos", change_position); return result; }