Skip to content

Commit e8b9523

Browse files
committed
Merge #10712: Add change output if necessary to reduce excess fee
0f402b9 Fix rare edge case of paying too many fees when transaction has no change. (Alex Morcos) 253cd7e Only reserve key for scriptChange once in CreateTransaction (Alex Morcos) Pull request description: This is an alternative to #10333 See commit messages. The first commit is mostly code move, it just moves the change creation code out of the loop. @instagibbs Tree-SHA512: f16287ae0f0c6f09cf8b1f0db5880bb567ffa74a50898e3d1ef549ba592c6309ae1a9b251739f63a8bb622d48f03ce2dff9e7a57a6bac4afb4b95b0a86613ea8
2 parents ca4c545 + 0f402b9 commit e8b9523

File tree

1 file changed

+74
-46
lines changed

1 file changed

+74
-46
lines changed

src/wallet/wallet.cpp

Lines changed: 74 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -2569,7 +2569,43 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletT
25692569
std::vector<COutput> vAvailableCoins;
25702570
AvailableCoins(vAvailableCoins, true, coinControl);
25712571

2572+
// Create change script that will be used if we need change
2573+
// TODO: pass in scriptChange instead of reservekey so
2574+
// change transaction isn't always pay-to-bitcoin-address
2575+
CScript scriptChange;
2576+
2577+
// coin control: send change to custom address
2578+
if (coinControl && !boost::get<CNoDestination>(&coinControl->destChange))
2579+
scriptChange = GetScriptForDestination(coinControl->destChange);
2580+
2581+
// no coin control: send change to newly generated address
2582+
else
2583+
{
2584+
// Note: We use a new key here to keep it from being obvious which side is the change.
2585+
// The drawback is that by not reusing a previous key, the change may be lost if a
2586+
// backup is restored, if the backup doesn't have the new private key for the change.
2587+
// If we reused the old key, it would be possible to add code to look for and
2588+
// rediscover unknown transactions that were written with keys of ours to recover
2589+
// post-backup change.
2590+
2591+
// Reserve a new key pair from key pool
2592+
CPubKey vchPubKey;
2593+
bool ret;
2594+
ret = reservekey.GetReservedKey(vchPubKey, true);
2595+
if (!ret)
2596+
{
2597+
strFailReason = _("Keypool ran out, please call keypoolrefill first");
2598+
return false;
2599+
}
2600+
2601+
scriptChange = GetScriptForDestination(vchPubKey.GetID());
2602+
}
2603+
CTxOut change_prototype_txout(0, scriptChange);
2604+
size_t change_prototype_size = GetSerializeSize(change_prototype_txout, SER_DISK, 0);
2605+
25722606
nFeeRet = 0;
2607+
bool pick_new_inputs = true;
2608+
CAmount nValueIn = 0;
25732609
// Start with no fee and loop until there is enough fee
25742610
while (true)
25752611
{
@@ -2615,49 +2651,21 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletT
26152651
}
26162652

26172653
// Choose coins to use
2618-
CAmount nValueIn = 0;
2619-
setCoins.clear();
2620-
if (!SelectCoins(vAvailableCoins, nValueToSelect, setCoins, nValueIn, coinControl))
2621-
{
2622-
strFailReason = _("Insufficient funds");
2623-
return false;
2654+
if (pick_new_inputs) {
2655+
nValueIn = 0;
2656+
setCoins.clear();
2657+
if (!SelectCoins(vAvailableCoins, nValueToSelect, setCoins, nValueIn, coinControl))
2658+
{
2659+
strFailReason = _("Insufficient funds");
2660+
return false;
2661+
}
26242662
}
26252663

26262664
const CAmount nChange = nValueIn - nValueToSelect;
2665+
26272666
if (nChange > 0)
26282667
{
26292668
// Fill a vout to ourself
2630-
// TODO: pass in scriptChange instead of reservekey so
2631-
// change transaction isn't always pay-to-bitcoin-address
2632-
CScript scriptChange;
2633-
2634-
// coin control: send change to custom address
2635-
if (coinControl && !boost::get<CNoDestination>(&coinControl->destChange))
2636-
scriptChange = GetScriptForDestination(coinControl->destChange);
2637-
2638-
// no coin control: send change to newly generated address
2639-
else
2640-
{
2641-
// Note: We use a new key here to keep it from being obvious which side is the change.
2642-
// The drawback is that by not reusing a previous key, the change may be lost if a
2643-
// backup is restored, if the backup doesn't have the new private key for the change.
2644-
// If we reused the old key, it would be possible to add code to look for and
2645-
// rediscover unknown transactions that were written with keys of ours to recover
2646-
// post-backup change.
2647-
2648-
// Reserve a new key pair from key pool
2649-
CPubKey vchPubKey;
2650-
bool ret;
2651-
ret = reservekey.GetReservedKey(vchPubKey, true);
2652-
if (!ret)
2653-
{
2654-
strFailReason = _("Keypool ran out, please call keypoolrefill first");
2655-
return false;
2656-
}
2657-
2658-
scriptChange = GetScriptForDestination(vchPubKey.GetID());
2659-
}
2660-
26612669
CTxOut newTxOut(nChange, scriptChange);
26622670

26632671
// Never create dust outputs; if we would, just
@@ -2666,7 +2674,6 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletT
26662674
{
26672675
nChangePosInOut = -1;
26682676
nFeeRet += nChange;
2669-
reservekey.ReturnKey();
26702677
}
26712678
else
26722679
{
@@ -2685,7 +2692,6 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletT
26852692
txNew.vout.insert(position, newTxOut);
26862693
}
26872694
} else {
2688-
reservekey.ReturnKey();
26892695
nChangePosInOut = -1;
26902696
}
26912697

@@ -2740,16 +2746,30 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletT
27402746
}
27412747

27422748
if (nFeeRet >= nFeeNeeded) {
2743-
// Reduce fee to only the needed amount if we have change
2744-
// output to increase. This prevents potential overpayment
2745-
// in fees if the coins selected to meet nFeeNeeded result
2746-
// in a transaction that requires less fee than the prior
2747-
// iteration.
2749+
// Reduce fee to only the needed amount if possible. This
2750+
// prevents potential overpayment in fees if the coins
2751+
// selected to meet nFeeNeeded result in a transaction that
2752+
// requires less fee than the prior iteration.
2753+
27482754
// TODO: The case where nSubtractFeeFromAmount > 0 remains
27492755
// to be addressed because it requires returning the fee to
27502756
// the payees and not the change output.
2751-
// TODO: The case where there is no change output remains
2752-
// to be addressed so we avoid creating too small an output.
2757+
2758+
// If we have no change and a big enough excess fee, then
2759+
// try to construct transaction again only without picking
2760+
// new inputs. We now know we only need the smaller fee
2761+
// (because of reduced tx size) and so we should add a
2762+
// change output. Only try this once.
2763+
CAmount fee_needed_for_change = GetMinimumFee(change_prototype_size, currentConfirmationTarget, ::mempool, ::feeEstimator, nullptr, false /* ignoreGlobalPayTxFee */, conservative_estimate);
2764+
CAmount minimum_value_for_change = GetDustThreshold(change_prototype_txout, ::dustRelayFee);
2765+
CAmount max_excess_fee = fee_needed_for_change + minimum_value_for_change;
2766+
if (nFeeRet > nFeeNeeded + max_excess_fee && nChangePosInOut == -1 && nSubtractFeeFromAmount == 0 && pick_new_inputs) {
2767+
pick_new_inputs = false;
2768+
nFeeRet = nFeeNeeded + fee_needed_for_change;
2769+
continue;
2770+
}
2771+
2772+
// If we have change output already, just increase it
27532773
if (nFeeRet > nFeeNeeded && nChangePosInOut != -1 && nSubtractFeeFromAmount == 0) {
27542774
CAmount extraFeePaid = nFeeRet - nFeeNeeded;
27552775
std::vector<CTxOut>::iterator change_position = txNew.vout.begin()+nChangePosInOut;
@@ -2758,6 +2778,12 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletT
27582778
}
27592779
break; // Done, enough fee included.
27602780
}
2781+
else if (!pick_new_inputs) {
2782+
// This shouldn't happen, we should have had enough excess
2783+
// fee to pay for the new output and still meet nFeeNeeded
2784+
strFailReason = _("Transaction fee and change calculation failed");
2785+
return false;
2786+
}
27612787

27622788
// Try to reduce change to include necessary fee
27632789
if (nChangePosInOut != -1 && nSubtractFeeFromAmount == 0) {
@@ -2777,6 +2803,8 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletT
27772803
}
27782804
}
27792805

2806+
if (nChangePosInOut == -1) reservekey.ReturnKey(); // Return any reserved key if we don't have change
2807+
27802808
if (sign)
27812809
{
27822810
CTransaction txNewConst(txNew);

0 commit comments

Comments
 (0)