From 58a8e28918025c28f19ba19cbaa4a72374162942 Mon Sep 17 00:00:00 2001 From: Andrew Chow Date: Wed, 27 Jun 2018 17:02:07 -0700 Subject: Refactor transaction creation and transaction funding logic In preparation for more create transaction and fund transcation RPCs, refactor the transaction creation and funding logic into separate functions. --- src/wallet/rpcwallet.cpp | 183 ++++++++++++++++++++++++----------------------- 1 file changed, 93 insertions(+), 90 deletions(-) (limited to 'src/wallet/rpcwallet.cpp') diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index c1f4c99851..7cd9f4cfd3 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -3419,95 +3419,25 @@ static UniValue listunspent(const JSONRPCRequest& request) return results; } -static UniValue fundrawtransaction(const JSONRPCRequest& request) +void FundTransaction(CWallet* const pwallet, CMutableTransaction& tx, CAmount& fee_out, int& change_position, UniValue options) { - std::shared_ptr const wallet = GetWalletForJSONRPCRequest(request); - CWallet* const pwallet = wallet.get(); - - if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { - return NullUniValue; - } - - if (request.fHelp || request.params.size() < 1 || request.params.size() > 3) - throw std::runtime_error( - "fundrawtransaction \"hexstring\" ( options iswitness )\n" - "\nAdd inputs to a transaction until it has enough in value to meet its out value.\n" - "This will not modify existing inputs, and will add at most one change output to the outputs.\n" - "No existing outputs will be modified unless \"subtractFeeFromOutputs\" is specified.\n" - "Note that inputs which were signed may need to be resigned after completion since in/outputs have been added.\n" - "The inputs added will not be signed, use signrawtransaction for that.\n" - "Note that all existing inputs must have their previous output transaction be in the wallet.\n" - "Note that all inputs selected must be of standard form and P2SH scripts must be\n" - "in the wallet using importaddress or addmultisigaddress (to calculate fees).\n" - "You can see whether this is the case by checking the \"solvable\" field in the listunspent output.\n" - "Only pay-to-pubkey, multisig, and P2SH versions thereof are currently supported for watch-only\n" - "\nArguments:\n" - "1. \"hexstring\" (string, required) The hex string of the raw transaction\n" - "2. options (object, optional)\n" - " {\n" - " \"changeAddress\" (string, optional, default pool address) The bitcoin address to receive the change\n" - " \"changePosition\" (numeric, optional, default random) The index of the change output\n" - " \"change_type\" (string, optional) The output type to use. Only valid if changeAddress is not specified. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\". Default is set by -changetype.\n" - " \"includeWatching\" (boolean, optional, default false) Also select inputs which are watch only\n" - " \"lockUnspents\" (boolean, optional, default false) Lock selected unspent outputs\n" - " \"feeRate\" (numeric, optional, default not set: makes wallet determine the fee) Set a specific fee rate in " + CURRENCY_UNIT + "/kB\n" - " \"subtractFeeFromOutputs\" (array, optional) A json array of integers.\n" - " The fee will be equally deducted from the amount of each specified output.\n" - " The outputs are specified by their zero-based index, before any change output is added.\n" - " Those recipients will receive less bitcoins than you enter in their corresponding amount field.\n" - " If no outputs are specified here, the sender pays the fee.\n" - " [vout_index,...]\n" - " \"replaceable\" (boolean, optional) Marks this transaction as BIP125 replaceable.\n" - " Allows this transaction to be replaced by a transaction with higher fees\n" - " \"conf_target\" (numeric, optional) Confirmation target (in blocks)\n" - " \"estimate_mode\" (string, optional, default=UNSET) The fee estimate mode, must be one of:\n" - " \"UNSET\"\n" - " \"ECONOMICAL\"\n" - " \"CONSERVATIVE\"\n" - " }\n" - " for backward compatibility: passing in a true instead of an object will result in {\"includeWatching\":true}\n" - "3. iswitness (boolean, optional) Whether the transaction hex is a serialized witness transaction \n" - " If iswitness is not present, heuristic tests will be used in decoding\n" - - "\nResult:\n" - "{\n" - " \"hex\": \"value\", (string) The resulting raw transaction (hex-encoded string)\n" - " \"fee\": n, (numeric) Fee in " + CURRENCY_UNIT + " the resulting transaction pays\n" - " \"changepos\": n (numeric) The position of the added change output, or -1\n" - "}\n" - "\nExamples:\n" - "\nCreate a transaction with no inputs\n" - + HelpExampleCli("createrawtransaction", "\"[]\" \"{\\\"myaddress\\\":0.01}\"") + - "\nAdd sufficient unsigned inputs to meet the output value\n" - + HelpExampleCli("fundrawtransaction", "\"rawtransactionhex\"") + - "\nSign the transaction\n" - + HelpExampleCli("signrawtransaction", "\"fundedtransactionhex\"") + - "\nSend the transaction\n" - + 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; + change_position = -1; bool lockUnspents = false; UniValue subtractFeeFromOutputs; std::set setSubtractFeeFromOutputs; - if (!request.params[1].isNull()) { - if (request.params[1].type() == UniValue::VBOOL) { + if (!options.isNull()) { + if (options.type() == UniValue::VBOOL) { // backward compatibility bool only fallback - coinControl.fAllowWatchOnly = request.params[1].get_bool(); + coinControl.fAllowWatchOnly = options.get_bool(); } else { - RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VOBJ, UniValue::VBOOL}); - - UniValue options = request.params[1]; - + RPCTypeCheckArgument(options, UniValue::VOBJ); RPCTypeCheckObj(options, { {"changeAddress", UniValueType(UniValue::VSTR)}, @@ -3534,7 +3464,7 @@ static UniValue fundrawtransaction(const JSONRPCRequest& request) } if (options.exists("changePosition")) - changePosition = options["changePosition"].get_int(); + change_position = options["changePosition"].get_int(); if (options.exists("change_type")) { if (options.exists("changeAddress")) { @@ -3581,18 +3511,10 @@ static UniValue fundrawtransaction(const JSONRPCRequest& request) } } - // parse hex string from parameter - CMutableTransaction tx; - bool try_witness = request.params[2].isNull() ? true : request.params[2].get_bool(); - bool try_no_witness = request.params[2].isNull() ? true : !request.params[2].get_bool(); - if (!DecodeHexTx(tx, request.params[0].get_str(), try_no_witness, try_witness)) { - 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())) + if (change_position != -1 && (change_position < 0 || (unsigned int)change_position > tx.vout.size())) throw JSONRPCError(RPC_INVALID_PARAMETER, "changePosition out of bounds"); for (unsigned int idx = 0; idx < subtractFeeFromOutputs.size(); idx++) { @@ -3606,17 +3528,98 @@ static UniValue fundrawtransaction(const JSONRPCRequest& request) setSubtractFeeFromOutputs.insert(pos); } - CAmount nFeeOut; std::string strFailReason; - if (!pwallet->FundTransaction(tx, nFeeOut, changePosition, strFailReason, lockUnspents, setSubtractFeeFromOutputs, coinControl)) { + if (!pwallet->FundTransaction(tx, fee_out, change_position, strFailReason, lockUnspents, setSubtractFeeFromOutputs, coinControl)) { throw JSONRPCError(RPC_WALLET_ERROR, strFailReason); } +} + +static UniValue fundrawtransaction(const JSONRPCRequest& request) +{ + std::shared_ptr const wallet = GetWalletForJSONRPCRequest(request); + CWallet* const pwallet = wallet.get(); + + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { + return NullUniValue; + } + + if (request.fHelp || request.params.size() < 1 || request.params.size() > 3) + throw std::runtime_error( + "fundrawtransaction \"hexstring\" ( options iswitness )\n" + "\nAdd inputs to a transaction until it has enough in value to meet its out value.\n" + "This will not modify existing inputs, and will add at most one change output to the outputs.\n" + "No existing outputs will be modified unless \"subtractFeeFromOutputs\" is specified.\n" + "Note that inputs which were signed may need to be resigned after completion since in/outputs have been added.\n" + "The inputs added will not be signed, use signrawtransaction for that.\n" + "Note that all existing inputs must have their previous output transaction be in the wallet.\n" + "Note that all inputs selected must be of standard form and P2SH scripts must be\n" + "in the wallet using importaddress or addmultisigaddress (to calculate fees).\n" + "You can see whether this is the case by checking the \"solvable\" field in the listunspent output.\n" + "Only pay-to-pubkey, multisig, and P2SH versions thereof are currently supported for watch-only\n" + "\nArguments:\n" + "1. \"hexstring\" (string, required) The hex string of the raw transaction\n" + "2. options (object, optional)\n" + " {\n" + " \"changeAddress\" (string, optional, default pool address) The bitcoin address to receive the change\n" + " \"changePosition\" (numeric, optional, default random) The index of the change output\n" + " \"change_type\" (string, optional) The output type to use. Only valid if changeAddress is not specified. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\". Default is set by -changetype.\n" + " \"includeWatching\" (boolean, optional, default false) Also select inputs which are watch only\n" + " \"lockUnspents\" (boolean, optional, default false) Lock selected unspent outputs\n" + " \"feeRate\" (numeric, optional, default not set: makes wallet determine the fee) Set a specific fee rate in " + CURRENCY_UNIT + "/kB\n" + " \"subtractFeeFromOutputs\" (array, optional) A json array of integers.\n" + " The fee will be equally deducted from the amount of each specified output.\n" + " The outputs are specified by their zero-based index, before any change output is added.\n" + " Those recipients will receive less bitcoins than you enter in their corresponding amount field.\n" + " If no outputs are specified here, the sender pays the fee.\n" + " [vout_index,...]\n" + " \"replaceable\" (boolean, optional) Marks this transaction as BIP125 replaceable.\n" + " Allows this transaction to be replaced by a transaction with higher fees\n" + " \"conf_target\" (numeric, optional) Confirmation target (in blocks)\n" + " \"estimate_mode\" (string, optional, default=UNSET) The fee estimate mode, must be one of:\n" + " \"UNSET\"\n" + " \"ECONOMICAL\"\n" + " \"CONSERVATIVE\"\n" + " }\n" + " for backward compatibility: passing in a true instead of an object will result in {\"includeWatching\":true}\n" + "3. iswitness (boolean, optional) Whether the transaction hex is a serialized witness transaction \n" + " If iswitness is not present, heuristic tests will be used in decoding\n" + + "\nResult:\n" + "{\n" + " \"hex\": \"value\", (string) The resulting raw transaction (hex-encoded string)\n" + " \"fee\": n, (numeric) Fee in " + CURRENCY_UNIT + " the resulting transaction pays\n" + " \"changepos\": n (numeric) The position of the added change output, or -1\n" + "}\n" + "\nExamples:\n" + "\nCreate a transaction with no inputs\n" + + HelpExampleCli("createrawtransaction", "\"[]\" \"{\\\"myaddress\\\":0.01}\"") + + "\nAdd sufficient unsigned inputs to meet the output value\n" + + HelpExampleCli("fundrawtransaction", "\"rawtransactionhex\"") + + "\nSign the transaction\n" + + HelpExampleCli("signrawtransaction", "\"fundedtransactionhex\"") + + "\nSend the transaction\n" + + HelpExampleCli("sendrawtransaction", "\"signedtransactionhex\"") + ); + + RPCTypeCheck(request.params, {UniValue::VSTR, UniValueType(), UniValue::VBOOL}); + + // parse hex string from parameter + CMutableTransaction tx; + bool try_witness = request.params[2].isNull() ? true : request.params[2].get_bool(); + bool try_no_witness = request.params[2].isNull() ? true : !request.params[2].get_bool(); + if (!DecodeHexTx(tx, request.params[0].get_str(), try_no_witness, try_witness)) { + throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed"); + } + + CAmount fee; + int change_position; + FundTransaction(pwallet, tx, fee, change_position, request.params[1]); UniValue result(UniValue::VOBJ); result.pushKV("hex", EncodeHexTx(tx)); - result.pushKV("changepos", changePosition); - result.pushKV("fee", ValueFromAmount(nFeeOut)); + result.pushKV("fee", ValueFromAmount(fee)); + result.pushKV("changepos", change_position); return result; } -- cgit v1.2.3