mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-07-28 00:03:09 +02:00
wallet: CreateTransaction(): return out-params as (optional) struct
This commit is contained in:
@@ -218,21 +218,20 @@ Result CreateRateBumpTransaction(CWallet& wallet, const uint256& txid, const CCo
|
|||||||
// We cannot source new unconfirmed inputs(bip125 rule 2)
|
// We cannot source new unconfirmed inputs(bip125 rule 2)
|
||||||
new_coin_control.m_min_depth = 1;
|
new_coin_control.m_min_depth = 1;
|
||||||
|
|
||||||
CTransactionRef tx_new;
|
constexpr int RANDOM_CHANGE_POSITION = -1;
|
||||||
CAmount fee_ret;
|
|
||||||
int change_pos_in_out = -1; // No requested location for change
|
|
||||||
bilingual_str fail_reason;
|
bilingual_str fail_reason;
|
||||||
FeeCalculation fee_calc_out;
|
FeeCalculation fee_calc_out;
|
||||||
if (!CreateTransaction(wallet, recipients, tx_new, fee_ret, change_pos_in_out, fail_reason, new_coin_control, fee_calc_out, false)) {
|
std::optional<CreatedTransactionResult> txr = CreateTransaction(wallet, recipients, RANDOM_CHANGE_POSITION, fail_reason, new_coin_control, fee_calc_out, false);
|
||||||
|
if (!txr) {
|
||||||
errors.push_back(Untranslated("Unable to create transaction.") + Untranslated(" ") + fail_reason);
|
errors.push_back(Untranslated("Unable to create transaction.") + Untranslated(" ") + fail_reason);
|
||||||
return Result::WALLET_ERROR;
|
return Result::WALLET_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write back new fee if successful
|
// Write back new fee if successful
|
||||||
new_fee = fee_ret;
|
new_fee = txr->fee;
|
||||||
|
|
||||||
// Write back transaction
|
// Write back transaction
|
||||||
mtx = CMutableTransaction(*tx_new);
|
mtx = CMutableTransaction(*txr->tx);
|
||||||
|
|
||||||
return Result::OK;
|
return Result::OK;
|
||||||
}
|
}
|
||||||
|
@@ -257,13 +257,14 @@ public:
|
|||||||
bilingual_str& fail_reason) override
|
bilingual_str& fail_reason) override
|
||||||
{
|
{
|
||||||
LOCK(m_wallet->cs_wallet);
|
LOCK(m_wallet->cs_wallet);
|
||||||
CTransactionRef tx;
|
|
||||||
FeeCalculation fee_calc_out;
|
FeeCalculation fee_calc_out;
|
||||||
if (!CreateTransaction(*m_wallet, recipients, tx, fee, change_pos,
|
std::optional<CreatedTransactionResult> txr = CreateTransaction(*m_wallet, recipients, change_pos,
|
||||||
fail_reason, coin_control, fee_calc_out, sign)) {
|
fail_reason, coin_control, fee_calc_out, sign);
|
||||||
return {};
|
if (!txr) return {};
|
||||||
}
|
fee = txr->fee;
|
||||||
return tx;
|
change_pos = txr->change_pos;
|
||||||
|
|
||||||
|
return txr->tx;
|
||||||
}
|
}
|
||||||
void commitTransaction(CTransactionRef tx,
|
void commitTransaction(CTransactionRef tx,
|
||||||
WalletValueMap value_map,
|
WalletValueMap value_map,
|
||||||
|
@@ -155,15 +155,14 @@ UniValue SendMoney(CWallet& wallet, const CCoinControl &coin_control, std::vecto
|
|||||||
std::shuffle(recipients.begin(), recipients.end(), FastRandomContext());
|
std::shuffle(recipients.begin(), recipients.end(), FastRandomContext());
|
||||||
|
|
||||||
// Send
|
// Send
|
||||||
CAmount nFeeRequired = 0;
|
constexpr int RANDOM_CHANGE_POSITION = -1;
|
||||||
int nChangePosRet = -1;
|
|
||||||
bilingual_str error;
|
bilingual_str error;
|
||||||
CTransactionRef tx;
|
|
||||||
FeeCalculation fee_calc_out;
|
FeeCalculation fee_calc_out;
|
||||||
const bool fCreated = CreateTransaction(wallet, recipients, tx, nFeeRequired, nChangePosRet, error, coin_control, fee_calc_out, true);
|
std::optional<CreatedTransactionResult> txr = CreateTransaction(wallet, recipients, RANDOM_CHANGE_POSITION, error, coin_control, fee_calc_out, true);
|
||||||
if (!fCreated) {
|
if (!txr) {
|
||||||
throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, error.original);
|
throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, error.original);
|
||||||
}
|
}
|
||||||
|
CTransactionRef tx = txr->tx;
|
||||||
wallet.CommitTransaction(tx, std::move(map_value), {} /* orderForm */);
|
wallet.CommitTransaction(tx, std::move(map_value), {} /* orderForm */);
|
||||||
if (verbose) {
|
if (verbose) {
|
||||||
UniValue entry(UniValue::VOBJ);
|
UniValue entry(UniValue::VOBJ);
|
||||||
|
@@ -961,14 +961,10 @@ static std::optional<CreatedTransactionResult> CreateTransactionInternal(
|
|||||||
return CreatedTransactionResult(tx, nFeeRet, nChangePosInOut);
|
return CreatedTransactionResult(tx, nFeeRet, nChangePosInOut);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: also return std::optional<CreatedTransactionResult> here in order
|
std::optional<CreatedTransactionResult> CreateTransaction(
|
||||||
// to eliminate the out parameters and to simplify the function
|
|
||||||
bool CreateTransaction(
|
|
||||||
CWallet& wallet,
|
CWallet& wallet,
|
||||||
const std::vector<CRecipient>& vecSend,
|
const std::vector<CRecipient>& vecSend,
|
||||||
CTransactionRef& tx,
|
int change_pos,
|
||||||
CAmount& nFeeRet,
|
|
||||||
int& nChangePosInOut,
|
|
||||||
bilingual_str& error,
|
bilingual_str& error,
|
||||||
const CCoinControl& coin_control,
|
const CCoinControl& coin_control,
|
||||||
FeeCalculation& fee_calc_out,
|
FeeCalculation& fee_calc_out,
|
||||||
@@ -976,54 +972,37 @@ bool CreateTransaction(
|
|||||||
{
|
{
|
||||||
if (vecSend.empty()) {
|
if (vecSend.empty()) {
|
||||||
error = _("Transaction must have at least one recipient");
|
error = _("Transaction must have at least one recipient");
|
||||||
return false;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (std::any_of(vecSend.cbegin(), vecSend.cend(), [](const auto& recipient){ return recipient.nAmount < 0; })) {
|
if (std::any_of(vecSend.cbegin(), vecSend.cend(), [](const auto& recipient){ return recipient.nAmount < 0; })) {
|
||||||
error = _("Transaction amounts must not be negative");
|
error = _("Transaction amounts must not be negative");
|
||||||
return false;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
LOCK(wallet.cs_wallet);
|
LOCK(wallet.cs_wallet);
|
||||||
|
|
||||||
int nChangePosIn = nChangePosInOut;
|
std::optional<CreatedTransactionResult> txr_ungrouped = CreateTransactionInternal(wallet, vecSend, change_pos, error, coin_control, fee_calc_out, sign);
|
||||||
Assert(!tx); // tx is an out-param. TODO change the return type from bool to tx (or nullptr)
|
TRACE4(coin_selection, normal_create_tx_internal, wallet.GetName().c_str(), txr_ungrouped.has_value(),
|
||||||
std::optional<CreatedTransactionResult> txr_ungrouped = CreateTransactionInternal(wallet, vecSend, nChangePosInOut, error, coin_control, fee_calc_out, sign);
|
txr_ungrouped.has_value() ? txr_ungrouped->fee : 0, txr_ungrouped.has_value() ? txr_ungrouped->change_pos : 0);
|
||||||
bool res = false;
|
if (!txr_ungrouped) return std::nullopt;
|
||||||
if (txr_ungrouped) {
|
|
||||||
tx = txr_ungrouped->tx;
|
|
||||||
nFeeRet = txr_ungrouped->fee;
|
|
||||||
nChangePosInOut = txr_ungrouped->change_pos;
|
|
||||||
res = true;
|
|
||||||
}
|
|
||||||
TRACE4(coin_selection, normal_create_tx_internal, wallet.GetName().c_str(), res, nFeeRet, nChangePosInOut);
|
|
||||||
// try with avoidpartialspends unless it's enabled already
|
// try with avoidpartialspends unless it's enabled already
|
||||||
if (res && nFeeRet > 0 /* 0 means non-functional fee rate estimation */ && wallet.m_max_aps_fee > -1 && !coin_control.m_avoid_partial_spends) {
|
if (txr_ungrouped->fee > 0 /* 0 means non-functional fee rate estimation */ && wallet.m_max_aps_fee > -1 && !coin_control.m_avoid_partial_spends) {
|
||||||
TRACE1(coin_selection, attempting_aps_create_tx, wallet.GetName().c_str());
|
TRACE1(coin_selection, attempting_aps_create_tx, wallet.GetName().c_str());
|
||||||
CCoinControl tmp_cc = coin_control;
|
CCoinControl tmp_cc = coin_control;
|
||||||
tmp_cc.m_avoid_partial_spends = true;
|
tmp_cc.m_avoid_partial_spends = true;
|
||||||
CAmount nFeeRet2;
|
|
||||||
CTransactionRef tx2;
|
|
||||||
int nChangePosInOut2 = nChangePosIn;
|
|
||||||
bilingual_str error2; // fired and forgotten; if an error occurs, we discard the results
|
bilingual_str error2; // fired and forgotten; if an error occurs, we discard the results
|
||||||
std::optional<CreatedTransactionResult> txr_grouped = CreateTransactionInternal(wallet, vecSend, nChangePosInOut2, error2, tmp_cc, fee_calc_out, sign);
|
std::optional<CreatedTransactionResult> txr_grouped = CreateTransactionInternal(wallet, vecSend, change_pos, error2, tmp_cc, fee_calc_out, sign);
|
||||||
if (txr_grouped) {
|
if (txr_grouped) {
|
||||||
tx2 = txr_grouped->tx;
|
|
||||||
nFeeRet2 = txr_grouped->fee;
|
|
||||||
nChangePosInOut2 = txr_grouped->change_pos;
|
|
||||||
|
|
||||||
// if fee of this alternative one is within the range of the max fee, we use this one
|
// if fee of this alternative one is within the range of the max fee, we use this one
|
||||||
const bool use_aps = nFeeRet2 <= nFeeRet + wallet.m_max_aps_fee;
|
const bool use_aps = txr_grouped->fee <= txr_ungrouped->fee + wallet.m_max_aps_fee;
|
||||||
wallet.WalletLogPrintf("Fee non-grouped = %lld, grouped = %lld, using %s\n", nFeeRet, nFeeRet2, use_aps ? "grouped" : "non-grouped");
|
wallet.WalletLogPrintf("Fee non-grouped = %lld, grouped = %lld, using %s\n",
|
||||||
TRACE5(coin_selection, aps_create_tx_internal, wallet.GetName().c_str(), use_aps, res, nFeeRet2, nChangePosInOut2);
|
txr_ungrouped->fee, txr_grouped->fee, use_aps ? "grouped" : "non-grouped");
|
||||||
if (use_aps) {
|
TRACE5(coin_selection, aps_create_tx_internal, wallet.GetName().c_str(), use_aps, true, txr_grouped->fee, txr_grouped->change_pos);
|
||||||
tx = tx2;
|
if (use_aps) return txr_grouped;
|
||||||
nFeeRet = nFeeRet2;
|
|
||||||
nChangePosInOut = nChangePosInOut2;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return res;
|
return txr_ungrouped;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool FundTransaction(CWallet& wallet, CMutableTransaction& tx, CAmount& nFeeRet, int& nChangePosInOut, bilingual_str& error, bool lockUnspents, const std::set<int>& setSubtractFeeFromOutputs, CCoinControl coinControl)
|
bool FundTransaction(CWallet& wallet, CMutableTransaction& tx, CAmount& nFeeRet, int& nChangePosInOut, bilingual_str& error, bool lockUnspents, const std::set<int>& setSubtractFeeFromOutputs, CCoinControl coinControl)
|
||||||
@@ -1047,11 +1026,12 @@ bool FundTransaction(CWallet& wallet, CMutableTransaction& tx, CAmount& nFeeRet,
|
|||||||
// CreateTransaction call and LockCoin calls (when lockUnspents is true).
|
// CreateTransaction call and LockCoin calls (when lockUnspents is true).
|
||||||
LOCK(wallet.cs_wallet);
|
LOCK(wallet.cs_wallet);
|
||||||
|
|
||||||
CTransactionRef tx_new;
|
|
||||||
FeeCalculation fee_calc_out;
|
FeeCalculation fee_calc_out;
|
||||||
if (!CreateTransaction(wallet, vecSend, tx_new, nFeeRet, nChangePosInOut, error, coinControl, fee_calc_out, false)) {
|
std::optional<CreatedTransactionResult> txr = CreateTransaction(wallet, vecSend, nChangePosInOut, error, coinControl, fee_calc_out, false);
|
||||||
return false;
|
if (!txr) return false;
|
||||||
}
|
CTransactionRef tx_new = txr->tx;
|
||||||
|
nFeeRet = txr->fee;
|
||||||
|
nChangePosInOut = txr->change_pos;
|
||||||
|
|
||||||
if (nChangePosInOut != -1) {
|
if (nChangePosInOut != -1) {
|
||||||
tx.vout.insert(tx.vout.begin() + nChangePosInOut, tx_new->vout[nChangePosInOut]);
|
tx.vout.insert(tx.vout.begin() + nChangePosInOut, tx_new->vout[nChangePosInOut]);
|
||||||
|
@@ -10,6 +10,8 @@
|
|||||||
#include <wallet/transaction.h>
|
#include <wallet/transaction.h>
|
||||||
#include <wallet/wallet.h>
|
#include <wallet/wallet.h>
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
namespace wallet {
|
namespace wallet {
|
||||||
/** Get the marginal bytes if spending the specified output from this transaction.
|
/** Get the marginal bytes if spending the specified output from this transaction.
|
||||||
* use_max_sig indicates whether to use the maximum sized, 72 byte signature when calculating the
|
* use_max_sig indicates whether to use the maximum sized, 72 byte signature when calculating the
|
||||||
@@ -95,9 +97,9 @@ struct CreatedTransactionResult
|
|||||||
/**
|
/**
|
||||||
* Create a new transaction paying the recipients with a set of coins
|
* Create a new transaction paying the recipients with a set of coins
|
||||||
* selected by SelectCoins(); Also create the change output, when needed
|
* selected by SelectCoins(); Also create the change output, when needed
|
||||||
* @note passing nChangePosInOut as -1 will result in setting a random position
|
* @note passing change_pos as -1 will result in setting a random position
|
||||||
*/
|
*/
|
||||||
bool CreateTransaction(CWallet& wallet, const std::vector<CRecipient>& vecSend, CTransactionRef& tx, CAmount& nFeeRet, int& nChangePosInOut, bilingual_str& error, const CCoinControl& coin_control, FeeCalculation& fee_calc_out, bool sign = true);
|
std::optional<CreatedTransactionResult> CreateTransaction(CWallet& wallet, const std::vector<CRecipient>& vecSend, int change_pos, bilingual_str& error, const CCoinControl& coin_control, FeeCalculation& fee_calc_out, bool sign = true);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Insert additional inputs into the transaction by
|
* Insert additional inputs into the transaction by
|
||||||
|
@@ -27,9 +27,7 @@ BOOST_FIXTURE_TEST_CASE(SubtractFee, TestChain100Setup)
|
|||||||
// instead of the miner.
|
// instead of the miner.
|
||||||
auto check_tx = [&wallet](CAmount leftover_input_amount) {
|
auto check_tx = [&wallet](CAmount leftover_input_amount) {
|
||||||
CRecipient recipient{GetScriptForRawPubKey({}), 50 * COIN - leftover_input_amount, true /* subtract fee */};
|
CRecipient recipient{GetScriptForRawPubKey({}), 50 * COIN - leftover_input_amount, true /* subtract fee */};
|
||||||
CTransactionRef tx;
|
constexpr int RANDOM_CHANGE_POSITION = -1;
|
||||||
CAmount fee;
|
|
||||||
int change_pos = -1;
|
|
||||||
bilingual_str error;
|
bilingual_str error;
|
||||||
CCoinControl coin_control;
|
CCoinControl coin_control;
|
||||||
coin_control.m_feerate.emplace(10000);
|
coin_control.m_feerate.emplace(10000);
|
||||||
@@ -37,11 +35,12 @@ BOOST_FIXTURE_TEST_CASE(SubtractFee, TestChain100Setup)
|
|||||||
// We need to use a change type with high cost of change so that the leftover amount will be dropped to fee instead of added as a change output
|
// We need to use a change type with high cost of change so that the leftover amount will be dropped to fee instead of added as a change output
|
||||||
coin_control.m_change_type = OutputType::LEGACY;
|
coin_control.m_change_type = OutputType::LEGACY;
|
||||||
FeeCalculation fee_calc;
|
FeeCalculation fee_calc;
|
||||||
BOOST_CHECK(CreateTransaction(*wallet, {recipient}, tx, fee, change_pos, error, coin_control, fee_calc));
|
std::optional<CreatedTransactionResult> txr = CreateTransaction(*wallet, {recipient}, RANDOM_CHANGE_POSITION, error, coin_control, fee_calc);
|
||||||
BOOST_CHECK_EQUAL(tx->vout.size(), 1);
|
BOOST_CHECK(txr.has_value());
|
||||||
BOOST_CHECK_EQUAL(tx->vout[0].nValue, recipient.nAmount + leftover_input_amount - fee);
|
BOOST_CHECK_EQUAL(txr->tx->vout.size(), 1);
|
||||||
BOOST_CHECK_GT(fee, 0);
|
BOOST_CHECK_EQUAL(txr->tx->vout[0].nValue, recipient.nAmount + leftover_input_amount - txr->fee);
|
||||||
return fee;
|
BOOST_CHECK_GT(txr->fee, 0);
|
||||||
|
return txr->fee;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Send full input amount to recipient, check that only nonzero fee is
|
// Send full input amount to recipient, check that only nonzero fee is
|
||||||
|
@@ -521,13 +521,14 @@ public:
|
|||||||
CWalletTx& AddTx(CRecipient recipient)
|
CWalletTx& AddTx(CRecipient recipient)
|
||||||
{
|
{
|
||||||
CTransactionRef tx;
|
CTransactionRef tx;
|
||||||
CAmount fee;
|
|
||||||
int changePos = -1;
|
|
||||||
bilingual_str error;
|
bilingual_str error;
|
||||||
CCoinControl dummy;
|
CCoinControl dummy;
|
||||||
FeeCalculation fee_calc_out;
|
FeeCalculation fee_calc_out;
|
||||||
{
|
{
|
||||||
BOOST_CHECK(CreateTransaction(*wallet, {recipient}, tx, fee, changePos, error, dummy, fee_calc_out));
|
constexpr int RANDOM_CHANGE_POSITION = -1;
|
||||||
|
std::optional<CreatedTransactionResult> txr = CreateTransaction(*wallet, {recipient}, RANDOM_CHANGE_POSITION, error, dummy, fee_calc_out);
|
||||||
|
BOOST_CHECK(txr.has_value());
|
||||||
|
tx = txr->tx;
|
||||||
}
|
}
|
||||||
wallet->CommitTransaction(tx, {}, {});
|
wallet->CommitTransaction(tx, {}, {});
|
||||||
CMutableTransaction blocktx;
|
CMutableTransaction blocktx;
|
||||||
|
Reference in New Issue
Block a user