@@ -2600,8 +2600,12 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletT
26002600
26012601 scriptChange = GetScriptForDestination (vchPubKey.GetID ());
26022602 }
2603+ CTxOut change_prototype_txout (0 , scriptChange);
2604+ size_t change_prototype_size = GetSerializeSize (change_prototype_txout, SER_DISK, 0 );
26032605
26042606 nFeeRet = 0 ;
2607+ bool pick_new_inputs = true ;
2608+ CAmount nValueIn = 0 ;
26052609 // Start with no fee and loop until there is enough fee
26062610 while (true )
26072611 {
@@ -2647,15 +2651,18 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletT
26472651 }
26482652
26492653 // Choose coins to use
2650- CAmount nValueIn = 0 ;
2651- setCoins.clear ();
2652- if (!SelectCoins (vAvailableCoins, nValueToSelect, setCoins, nValueIn, coinControl))
2653- {
2654- strFailReason = _ (" Insufficient funds" );
2655- 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+ }
26562662 }
26572663
26582664 const CAmount nChange = nValueIn - nValueToSelect;
2665+
26592666 if (nChange > 0 )
26602667 {
26612668 // Fill a vout to ourself
@@ -2739,16 +2746,30 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletT
27392746 }
27402747
27412748 if (nFeeRet >= nFeeNeeded) {
2742- // Reduce fee to only the needed amount if we have change
2743- // output to increase. This prevents potential overpayment
2744- // in fees if the coins selected to meet nFeeNeeded result
2745- // in a transaction that requires less fee than the prior
2746- // 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+
27472754 // TODO: The case where nSubtractFeeFromAmount > 0 remains
27482755 // to be addressed because it requires returning the fee to
27492756 // the payees and not the change output.
2750- // TODO: The case where there is no change output remains
2751- // 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
27522773 if (nFeeRet > nFeeNeeded && nChangePosInOut != -1 && nSubtractFeeFromAmount == 0 ) {
27532774 CAmount extraFeePaid = nFeeRet - nFeeNeeded;
27542775 std::vector<CTxOut>::iterator change_position = txNew.vout .begin ()+nChangePosInOut;
@@ -2757,6 +2778,12 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletT
27572778 }
27582779 break ; // Done, enough fee included.
27592780 }
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+ }
27602787
27612788 // Try to reduce change to include necessary fee
27622789 if (nChangePosInOut != -1 && nSubtractFeeFromAmount == 0 ) {
0 commit comments