mirror of
https://github.com/bitcoin/bitcoin.git
synced 2026-06-08 21:59:10 +02:00
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.
This commit is contained in:
@@ -3419,6 +3419,122 @@ static UniValue listunspent(const JSONRPCRequest& request)
|
||||
return results;
|
||||
}
|
||||
|
||||
void FundTransaction(CWallet* const pwallet, CMutableTransaction& tx, CAmount& 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<int> 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)},
|
||||
{"change_type", UniValueType(UniValue::VSTR)},
|
||||
{"includeWatching", UniValueType(UniValue::VBOOL)},
|
||||
{"lockUnspents", UniValueType(UniValue::VBOOL)},
|
||||
{"feeRate", UniValueType()}, // will be checked below
|
||||
{"subtractFeeFromOutputs", UniValueType(UniValue::VARR)},
|
||||
{"replaceable", UniValueType(UniValue::VBOOL)},
|
||||
{"conf_target", UniValueType(UniValue::VNUM)},
|
||||
{"estimate_mode", UniValueType(UniValue::VSTR)},
|
||||
},
|
||||
true, true);
|
||||
|
||||
if (options.exists("changeAddress")) {
|
||||
CTxDestination dest = DecodeDestination(options["changeAddress"].get_str());
|
||||
|
||||
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("change_type")) {
|
||||
if (options.exists("changeAddress")) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot specify both changeAddress and address_type options");
|
||||
}
|
||||
coinControl.m_change_type = pwallet->m_default_change_type;
|
||||
if (!ParseOutputType(options["change_type"].get_str(), *coinControl.m_change_type)) {
|
||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Unknown change type '%s'", options["change_type"].get_str()));
|
||||
}
|
||||
}
|
||||
|
||||
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 (options.exists("replaceable")) {
|
||||
coinControl.m_signal_bip125_rbf = options["replaceable"].get_bool();
|
||||
}
|
||||
if (options.exists("conf_target")) {
|
||||
if (options.exists("feeRate")) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot specify both conf_target and feeRate");
|
||||
}
|
||||
coinControl.m_confirm_target = ParseConfirmTarget(options["conf_target"]);
|
||||
}
|
||||
if (options.exists("estimate_mode")) {
|
||||
if (options.exists("feeRate")) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot specify both estimate_mode and feeRate");
|
||||
}
|
||||
if (!FeeModeFromString(options["estimate_mode"].get_str(), coinControl.m_fee_mode)) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid estimate_mode parameter");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 (unsigned int 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 JSONRPCRequest& request)
|
||||
{
|
||||
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
|
||||
@@ -3486,100 +3602,7 @@ static UniValue fundrawtransaction(const JSONRPCRequest& request)
|
||||
+ 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<int> 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::VBOOL});
|
||||
|
||||
UniValue options = request.params[1];
|
||||
|
||||
RPCTypeCheckObj(options,
|
||||
{
|
||||
{"changeAddress", UniValueType(UniValue::VSTR)},
|
||||
{"changePosition", UniValueType(UniValue::VNUM)},
|
||||
{"change_type", UniValueType(UniValue::VSTR)},
|
||||
{"includeWatching", UniValueType(UniValue::VBOOL)},
|
||||
{"lockUnspents", UniValueType(UniValue::VBOOL)},
|
||||
{"feeRate", UniValueType()}, // will be checked below
|
||||
{"subtractFeeFromOutputs", UniValueType(UniValue::VARR)},
|
||||
{"replaceable", UniValueType(UniValue::VBOOL)},
|
||||
{"conf_target", UniValueType(UniValue::VNUM)},
|
||||
{"estimate_mode", UniValueType(UniValue::VSTR)},
|
||||
},
|
||||
true, true);
|
||||
|
||||
if (options.exists("changeAddress")) {
|
||||
CTxDestination dest = DecodeDestination(options["changeAddress"].get_str());
|
||||
|
||||
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("change_type")) {
|
||||
if (options.exists("changeAddress")) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot specify both changeAddress and address_type options");
|
||||
}
|
||||
coinControl.m_change_type = pwallet->m_default_change_type;
|
||||
if (!ParseOutputType(options["change_type"].get_str(), *coinControl.m_change_type)) {
|
||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Unknown change type '%s'", options["change_type"].get_str()));
|
||||
}
|
||||
}
|
||||
|
||||
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 (options.exists("replaceable")) {
|
||||
coinControl.m_signal_bip125_rbf = options["replaceable"].get_bool();
|
||||
}
|
||||
if (options.exists("conf_target")) {
|
||||
if (options.exists("feeRate")) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot specify both conf_target and feeRate");
|
||||
}
|
||||
coinControl.m_confirm_target = ParseConfirmTarget(options["conf_target"]);
|
||||
}
|
||||
if (options.exists("estimate_mode")) {
|
||||
if (options.exists("feeRate")) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot specify both estimate_mode and feeRate");
|
||||
}
|
||||
if (!FeeModeFromString(options["estimate_mode"].get_str(), coinControl.m_fee_mode)) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid estimate_mode parameter");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
RPCTypeCheck(request.params, {UniValue::VSTR, UniValueType(), UniValue::VBOOL});
|
||||
|
||||
// parse hex string from parameter
|
||||
CMutableTransaction tx;
|
||||
@@ -3589,34 +3612,14 @@ static UniValue fundrawtransaction(const JSONRPCRequest& request)
|
||||
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 (unsigned int 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);
|
||||
}
|
||||
|
||||
CAmount nFeeOut;
|
||||
std::string strFailReason;
|
||||
|
||||
if (!pwallet->FundTransaction(tx, nFeeOut, changePosition, strFailReason, lockUnspents, setSubtractFeeFromOutputs, coinControl)) {
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, strFailReason);
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user