Skip to content

Commit 9d3bd74

Browse files
committed
Remove CreateTransaction while loop and some related variables
Remove the CreateTransaction while loop. Removes variables that were only needed because of that loop. Also renames a few variables and moves their declarations to where they are used. Some subtractFeeFromOutputs handling is moved to after coin selection in order to reduce their amounts once the fee is known. If subtracting the fee reduces the change to dust, we will also now remove the change output
1 parent 6f0d518 commit 9d3bd74

File tree

1 file changed

+104
-117
lines changed

1 file changed

+104
-117
lines changed

src/wallet/wallet.cpp

Lines changed: 104 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -2804,7 +2804,6 @@ bool CWallet::CreateTransactionInternal(
28042804
CAmount nValue = 0;
28052805
const OutputType change_type = TransactionChangeType(coin_control.m_change_type ? *coin_control.m_change_type : m_default_change_type, vecSend);
28062806
ReserveDestination reservedest(this, change_type);
2807-
int nChangePosRequest = nChangePosInOut;
28082807
unsigned int nSubtractFeeFromAmount = 0;
28092808
for (const auto& recipient : vecSend)
28102809
{
@@ -2909,151 +2908,139 @@ bool CWallet::CreateTransactionInternal(
29092908
coin_selection_params.m_change_fee = coin_selection_params.m_effective_feerate.GetFee(coin_selection_params.change_output_size);
29102909
coin_selection_params.m_cost_of_change = coin_selection_params.m_discard_feerate.GetFee(coin_selection_params.change_spend_size) + coin_selection_params.m_change_fee;
29112910

2912-
nFeeRet = 0;
2913-
CAmount nValueIn = 0;
2914-
29152911
coin_selection_params.m_subtract_fee_outputs = nSubtractFeeFromAmount != 0; // If we are doing subtract fee from recipient, don't use effective values
2916-
// Start with no fee and loop until there is enough fee
2917-
while (true)
2912+
2913+
// vouts to the payees
2914+
if (!coin_selection_params.m_subtract_fee_outputs) {
2915+
coin_selection_params.tx_noinputs_size = 11; // Static vsize overhead + outputs vsize. 4 nVersion, 4 nLocktime, 1 input count, 1 output count, 1 witness overhead (dummy, flag, stack size)
2916+
}
2917+
for (const auto& recipient : vecSend)
29182918
{
2919-
nChangePosInOut = nChangePosRequest;
2920-
txNew.vin.clear();
2921-
txNew.vout.clear();
2919+
CTxOut txout(recipient.nAmount, recipient.scriptPubKey);
29222920

2923-
// vouts to the payees
2921+
// Include the fee cost for outputs.
29242922
if (!coin_selection_params.m_subtract_fee_outputs) {
2925-
coin_selection_params.tx_noinputs_size = 11; // Static vsize overhead + outputs vsize. 4 nVersion, 4 nLocktime, 1 input count, 1 output count, 1 witness overhead (dummy, flag, stack size)
2923+
coin_selection_params.tx_noinputs_size += ::GetSerializeSize(txout, PROTOCOL_VERSION);
29262924
}
2927-
for (const auto& recipient : vecSend)
2925+
2926+
if (IsDust(txout, chain().relayDustFee()))
29282927
{
2929-
CTxOut txout(recipient.nAmount, recipient.scriptPubKey);
2928+
error = _("Transaction amount too small");
2929+
return false;
2930+
}
2931+
txNew.vout.push_back(txout);
2932+
}
29302933

2931-
// Include the fee cost for outputs.
2932-
if (!coin_selection_params.m_subtract_fee_outputs) {
2933-
coin_selection_params.tx_noinputs_size += ::GetSerializeSize(txout, PROTOCOL_VERSION);
2934-
}
2934+
// Include the fees for things that aren't inputs, excluding the change output
2935+
const CAmount not_input_fees = coin_selection_params.m_effective_feerate.GetFee(coin_selection_params.tx_noinputs_size);
2936+
CAmount nValueToSelect = nValue + not_input_fees;
29352937

2936-
if (IsDust(txout, chain().relayDustFee()))
2937-
{
2938-
error = _("Transaction amount too small");
2939-
return false;
2940-
}
2941-
txNew.vout.push_back(txout);
2942-
}
2938+
// Choose coins to use
2939+
CAmount inputs_sum = 0;
2940+
setCoins.clear();
2941+
if (!SelectCoins(vAvailableCoins, /* nTargetValue */ nValueToSelect, setCoins, inputs_sum, coin_control, coin_selection_params))
2942+
{
2943+
error = _("Insufficient funds");
2944+
return false;
2945+
}
29432946

2944-
// Include the fees for things that aren't inputs, excluding the change output
2945-
const CAmount not_input_fees = coin_selection_params.m_effective_feerate.GetFee(coin_selection_params.tx_noinputs_size);
2946-
CAmount nValueToSelect = nValue + not_input_fees;
2947+
// Always make a change output
2948+
// We will reduce the fee from this change output later, and remove the output if it is too small.
2949+
const CAmount change_and_fee = inputs_sum - nValue;
2950+
assert(change_and_fee >= 0);
2951+
CTxOut newTxOut(change_and_fee, scriptChange);
29472952

2948-
// Choose coins to use
2949-
nValueIn = 0;
2950-
setCoins.clear();
2951-
if (!SelectCoins(vAvailableCoins, /* nTargetValue */ nValueToSelect, setCoins, nValueIn, coin_control, coin_selection_params))
2952-
{
2953-
error = _("Insufficient funds");
2954-
return false;
2955-
}
2953+
if (nChangePosInOut == -1)
2954+
{
2955+
// Insert change txn at random position:
2956+
nChangePosInOut = GetRandInt(txNew.vout.size()+1);
2957+
}
2958+
else if ((unsigned int)nChangePosInOut > txNew.vout.size())
2959+
{
2960+
error = _("Change index out of range");
2961+
return false;
2962+
}
29562963

2957-
// Always make a change output
2958-
// We will reduce the fee from this change output later, and remove the output if it is too small.
2959-
const CAmount change_and_fee = nValueIn - nValue;
2960-
assert(change_and_fee >= 0);
2961-
CTxOut newTxOut(change_and_fee, scriptChange);
2964+
assert(nChangePosInOut != -1);
2965+
auto change_position = txNew.vout.insert(txNew.vout.begin() + nChangePosInOut, newTxOut);
29622966

2963-
if (nChangePosInOut == -1)
2964-
{
2965-
// Insert change txn at random position:
2966-
nChangePosInOut = GetRandInt(txNew.vout.size()+1);
2967-
}
2968-
else if ((unsigned int)nChangePosInOut > txNew.vout.size())
2969-
{
2970-
error = _("Change index out of range");
2971-
return false;
2972-
}
2967+
// Dummy fill vin for maximum size estimation
2968+
//
2969+
for (const auto& coin : setCoins) {
2970+
txNew.vin.push_back(CTxIn(coin.outpoint,CScript()));
2971+
}
29732972

2974-
assert(nChangePosInOut != -1);
2975-
auto change_position = txNew.vout.insert(txNew.vout.begin() + nChangePosInOut, newTxOut);
2973+
// Calculate the transaction fee
2974+
tx_sizes = CalculateMaximumSignedTxSize(CTransaction(txNew), this, coin_control.fAllowWatchOnly);
2975+
nBytes = tx_sizes.first;
2976+
if (nBytes < 0) {
2977+
error = _("Signing transaction failed");
2978+
return false;
2979+
}
2980+
nFeeRet = coin_selection_params.m_effective_feerate.GetFee(nBytes);
29762981

2977-
// Dummy fill vin for maximum size estimation
2978-
//
2979-
for (const auto& coin : setCoins) {
2980-
txNew.vin.push_back(CTxIn(coin.outpoint,CScript()));
2981-
}
2982+
// Subtract fee from the change output if not subtrating it from recipient outputs
2983+
CAmount fee_needed = nFeeRet;
2984+
if (nSubtractFeeFromAmount == 0) {
2985+
change_position->nValue -= fee_needed;
2986+
}
29822987

2983-
// Calculate the transaction fee
2988+
// We want to drop the change to fees if:
2989+
// 1. The change output would be dust
2990+
// 2. The change is within the (almost) exact match window, i.e. it is less than or equal to the cost of the change output (cost_of_change)
2991+
CAmount change_amount = change_position->nValue;
2992+
if (IsDust(*change_position, coin_selection_params.m_discard_feerate) || change_amount <= coin_selection_params.m_cost_of_change)
2993+
{
2994+
nChangePosInOut = -1;
2995+
change_amount = 0;
2996+
txNew.vout.erase(change_position);
2997+
2998+
// Because we have dropped this change, the tx size and required fee will be different, so let's recalculate those
29842999
tx_sizes = CalculateMaximumSignedTxSize(CTransaction(txNew), this, coin_control.fAllowWatchOnly);
29853000
nBytes = tx_sizes.first;
2986-
if (nBytes < 0) {
2987-
error = _("Signing transaction failed");
2988-
return false;
2989-
}
2990-
nFeeRet = coin_selection_params.m_effective_feerate.GetFee(nBytes);
3001+
fee_needed = coin_selection_params.m_effective_feerate.GetFee(nBytes);
3002+
}
29913003

2992-
// Subtract fee from the change output if not subtrating it from recipient outputs
2993-
CAmount fee_needed = nFeeRet;
2994-
if (nSubtractFeeFromAmount == 0) {
2995-
change_position->nValue -= fee_needed;
2996-
}
3004+
// Update nFeeRet in case fee_needed changed due to dropping the change output
3005+
if (fee_needed <= change_and_fee - change_amount) {
3006+
nFeeRet = change_and_fee - change_amount;
3007+
}
29973008

2998-
// We want to drop the change to fees if:
2999-
// 1. The change output would be dust
3000-
// 2. The change is within the (almost) exact match window, i.e. it is less than or equal to the cost of the change output (cost_of_change)
3001-
CAmount change_amount = change_position->nValue;
3002-
if (IsDust(*change_position, coin_selection_params.m_discard_feerate) || change_amount <= coin_selection_params.m_cost_of_change)
3009+
// Reduce output values for subtractFeeFromAmount
3010+
if (nSubtractFeeFromAmount != 0) {
3011+
CAmount to_reduce = fee_needed + change_amount - change_and_fee;
3012+
int i = 0;
3013+
bool fFirst = true;
3014+
for (const auto& recipient : vecSend)
30033015
{
3004-
nChangePosInOut = -1;
3005-
change_amount = 0;
3006-
txNew.vout.erase(change_position);
3007-
3008-
// Because we have dropped this change, the tx size and required fee will be different, so let's recalculate those
3009-
tx_sizes = CalculateMaximumSignedTxSize(CTransaction(txNew), this, coin_control.fAllowWatchOnly);
3010-
nBytes = tx_sizes.first;
3011-
fee_needed = coin_selection_params.m_effective_feerate.GetFee(nBytes);
3012-
}
3013-
3014-
// If the fee is covered, there's no need to loop or subtract from recipients
3015-
if (fee_needed <= change_and_fee - change_amount) {
3016-
nFeeRet = change_and_fee - change_amount;
3017-
break;
3018-
}
3016+
if (i == nChangePosInOut) {
3017+
++i;
3018+
}
3019+
CTxOut& txout = txNew.vout[i];
30193020

3020-
// Reduce output values for subtractFeeFromAmount
3021-
if (nSubtractFeeFromAmount != 0) {
3022-
CAmount to_reduce = fee_needed + change_amount - change_and_fee;
3023-
int i = 0;
3024-
bool fFirst = true;
3025-
for (const auto& recipient : vecSend)
3021+
if (recipient.fSubtractFeeFromAmount)
30263022
{
3027-
if (i == nChangePosInOut) {
3028-
++i;
3029-
}
3030-
CTxOut& txout = txNew.vout[i];
3023+
txout.nValue -= to_reduce / nSubtractFeeFromAmount; // Subtract fee equally from each selected recipient
30313024

3032-
if (recipient.fSubtractFeeFromAmount)
3025+
if (fFirst) // first receiver pays the remainder not divisible by output count
30333026
{
3034-
txout.nValue -= to_reduce / nSubtractFeeFromAmount; // Subtract fee equally from each selected recipient
3035-
3036-
if (fFirst) // first receiver pays the remainder not divisible by output count
3037-
{
3038-
fFirst = false;
3039-
txout.nValue -= to_reduce % nSubtractFeeFromAmount;
3040-
}
3027+
fFirst = false;
3028+
txout.nValue -= to_reduce % nSubtractFeeFromAmount;
3029+
}
30413030

3042-
// Error if this output is reduced to be below dust
3043-
if (IsDust(txout, chain().relayDustFee())) {
3044-
if (txout.nValue < 0) {
3045-
error = _("The transaction amount is too small to pay the fee");
3046-
} else {
3047-
error = _("The transaction amount is too small to send after the fee has been deducted");
3048-
}
3049-
return false;
3031+
// Error if this output is reduced to be below dust
3032+
if (IsDust(txout, chain().relayDustFee())) {
3033+
if (txout.nValue < 0) {
3034+
error = _("The transaction amount is too small to pay the fee");
3035+
} else {
3036+
error = _("The transaction amount is too small to send after the fee has been deducted");
30503037
}
3038+
return false;
30513039
}
3052-
++i;
30533040
}
3054-
nFeeRet = fee_needed;
3055-
break; // The fee has been deducted from the recipients, nothing left to do here
3041+
++i;
30563042
}
3043+
nFeeRet = fee_needed;
30573044
}
30583045

30593046
// Give up if change keypool ran out and change is required

0 commit comments

Comments
 (0)