aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorAndrew Chow <achow101-github@achow101.com>2018-06-27 17:02:07 -0700
committerAndrew Chow <achow101-github@achow101.com>2018-07-13 14:27:31 -0700
commit58a8e28918025c28f19ba19cbaa4a72374162942 (patch)
tree0970665902001b81b521753d146d065f6e04e401 /src
parente9d86a43ad8b1ab83b324e9a7a64c43a61337501 (diff)
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.
Diffstat (limited to 'src')
-rw-r--r--src/rpc/rawtransaction.cpp134
-rw-r--r--src/rpc/rawtransaction.h3
-rw-r--r--src/wallet/rpcwallet.cpp183
3 files changed, 166 insertions, 154 deletions
diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp
index 63548bff05..c27601e47f 100644
--- a/src/rpc/rawtransaction.cpp
+++ b/src/rpc/rawtransaction.cpp
@@ -332,80 +332,25 @@ static UniValue verifytxoutproof(const JSONRPCRequest& request)
return res;
}
-static UniValue createrawtransaction(const JSONRPCRequest& request)
+CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniValue& outputs_in, const UniValue& locktime, const UniValue& rbf)
{
- if (request.fHelp || request.params.size() < 2 || request.params.size() > 4) {
- throw std::runtime_error(
- // 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"
- "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"
- "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"
- "\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())
+ 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<uint32_t>::max())
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, locktime out of range");
rawTx.nLockTime = nLockTime;
}
- bool rbfOptIn = request.params[3].isTrue();
+ bool rbfOptIn = rbf.isTrue();
for (unsigned int idx = 0; idx < inputs.size(); idx++) {
const UniValue& input = inputs[idx];
@@ -485,10 +430,71 @@ static UniValue createrawtransaction(const JSONRPCRequest& request)
}
}
- if (!request.params[3].isNull() && rbfOptIn != SignalsOptInRBF(rawTx)) {
+ if (!rbf.isNull() && rbfOptIn != SignalsOptInRBF(rawTx)) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter combination: Sequence number(s) contradict replaceable option");
}
+ return rawTx;
+}
+
+static UniValue createrawtransaction(const JSONRPCRequest& request)
+{
+ if (request.fHelp || request.params.size() < 2 || request.params.size() > 4) {
+ throw std::runtime_error(
+ // 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"
+ "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"
+ "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"
+ "\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
+ );
+
+ CMutableTransaction rawTx = ConstructTransaction(request.params[0], request.params[1], request.params[2], request.params[3]);
+
return EncodeHexTx(rawTx);
}
diff --git a/src/rpc/rawtransaction.h b/src/rpc/rawtransaction.h
index ec9d1f2cf0..52dccc90e8 100644
--- a/src/rpc/rawtransaction.h
+++ b/src/rpc/rawtransaction.h
@@ -12,4 +12,7 @@ class UniValue;
/** Sign a transaction with the given keystore and previous transactions */
UniValue SignTransaction(CMutableTransaction& mtx, const UniValue& prevTxs, CBasicKeyStore *keystore, bool tempKeystore, const UniValue& hashType);
+/** Create a transaction from univalue parameters */
+CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniValue& outputs_in, const UniValue& locktime, const UniValue& rbf);
+
#endif // BITCOIN_RPC_RAWTRANSACTION_H
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<CWallet> 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<int> 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<CWallet> 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;
}