mirror of
https://github.com/dashpay/dash.git
synced 2024-12-26 20:42:59 +01:00
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 Signed-off-by: Pasta <pasta@dashboost.org>
This commit is contained in:
parent
e789608728
commit
00ceca6216
@ -3559,7 +3559,43 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletT
|
||||
std::vector<COutput> vAvailableCoins;
|
||||
AvailableCoins(vAvailableCoins, true, coinControl, 1, MAX_MONEY, MAX_MONEY, 0, 0, 9999999, nCoinType);
|
||||
|
||||
// Create change script that will be used if we need change
|
||||
// TODO: pass in scriptChange instead of reservekey so
|
||||
// change transaction isn't always pay-to-bitcoin-address
|
||||
CScript scriptChange;
|
||||
|
||||
// coin control: send change to custom address
|
||||
if (coinControl && !boost::get<CNoDestination>(&coinControl->destChange))
|
||||
scriptChange = GetScriptForDestination(coinControl->destChange);
|
||||
|
||||
// no coin control: send change to newly generated address
|
||||
else
|
||||
{
|
||||
// Note: We use a new key here to keep it from being obvious which side is the change.
|
||||
// The drawback is that by not reusing a previous key, the change may be lost if a
|
||||
// backup is restored, if the backup doesn't have the new private key for the change.
|
||||
// If we reused the old key, it would be possible to add code to look for and
|
||||
// rediscover unknown transactions that were written with keys of ours to recover
|
||||
// post-backup change.
|
||||
|
||||
// Reserve a new key pair from key pool
|
||||
CPubKey vchPubKey;
|
||||
bool ret;
|
||||
ret = reservekey.GetReservedKey(vchPubKey, true);
|
||||
if (!ret)
|
||||
{
|
||||
strFailReason = _("Keypool ran out, please call keypoolrefill first");
|
||||
return false;
|
||||
}
|
||||
|
||||
scriptChange = GetScriptForDestination(vchPubKey.GetID());
|
||||
}
|
||||
CTxOut change_prototype_txout(0, scriptChange);
|
||||
size_t change_prototype_size = GetSerializeSize(change_prototype_txout, SER_DISK, 0);
|
||||
|
||||
nFeeRet = 0;
|
||||
bool pick_new_inputs = true;
|
||||
CAmount nValueIn = 0;
|
||||
// Start with no fee and loop until there is enough fee
|
||||
while (true)
|
||||
{
|
||||
@ -3605,7 +3641,8 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletT
|
||||
}
|
||||
|
||||
// Choose coins to use
|
||||
CAmount nValueIn = 0;
|
||||
if (pick_new_inputs) {
|
||||
nValueIn = 0;
|
||||
setCoins.clear();
|
||||
if (!SelectCoins(vAvailableCoins, nValueToSelect, setCoins, nValueIn, coinControl, nCoinType))
|
||||
{
|
||||
@ -3618,7 +3655,7 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletT
|
||||
strFailReason = _("Insufficient funds.");
|
||||
}
|
||||
|
||||
return false;
|
||||
return false;}
|
||||
}
|
||||
|
||||
const CAmount nChange = nValueIn - nValueToSelect;
|
||||
@ -3626,42 +3663,12 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletT
|
||||
|
||||
if (nChange > 0)
|
||||
{
|
||||
|
||||
//over pay for denominated transactions
|
||||
if (nCoinType == ONLY_DENOMINATED) {
|
||||
nFeeRet += nChange;
|
||||
wtxNew.mapValue["DS"] = "1";
|
||||
} else {
|
||||
|
||||
// Fill a vout to ourself
|
||||
// TODO: pass in scriptChange instead of reservekey so
|
||||
// change transaction isn't always pay-to-dash-address
|
||||
CScript scriptChange;
|
||||
|
||||
// coin control: send change to custom address
|
||||
if (coinControl && !boost::get<CNoDestination>(&coinControl->destChange))
|
||||
scriptChange = GetScriptForDestination(coinControl->destChange);
|
||||
|
||||
// no coin control: send change to newly generated address
|
||||
else
|
||||
{
|
||||
// Note: We use a new key here to keep it from being obvious which side is the change.
|
||||
// The drawback is that by not reusing a previous key, the change may be lost if a
|
||||
// backup is restored, if the backup doesn't have the new private key for the change.
|
||||
// If we reused the old key, it would be possible to add code to look for and
|
||||
// rediscover unknown transactions that were written with keys of ours to recover
|
||||
// post-backup change.
|
||||
|
||||
// Reserve a new key pair from key pool
|
||||
CPubKey vchPubKey;
|
||||
if (!reservekey.GetReservedKey(vchPubKey, true))
|
||||
{
|
||||
strFailReason = _("Keypool ran out, please call keypoolrefill first");
|
||||
return false;
|
||||
}
|
||||
scriptChange = GetScriptForDestination(vchPubKey.GetID());
|
||||
}
|
||||
|
||||
newTxOut = CTxOut(nChange, scriptChange);
|
||||
|
||||
// Never create dust outputs; if we would, just
|
||||
@ -3670,7 +3677,7 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletT
|
||||
{
|
||||
nChangePosInOut = -1;
|
||||
nFeeRet += nChange;
|
||||
reservekey.ReturnKey();
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -3690,7 +3697,6 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletT
|
||||
}
|
||||
}
|
||||
} else {
|
||||
reservekey.ReturnKey();
|
||||
nChangePosInOut = -1;
|
||||
}
|
||||
|
||||
@ -3776,16 +3782,30 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletT
|
||||
}
|
||||
|
||||
if (nFeeRet >= nFeeNeeded) {
|
||||
// Reduce fee to only the needed amount if we have change
|
||||
// output to increase. This prevents potential overpayment
|
||||
// in fees if the coins selected to meet nFeeNeeded result
|
||||
// in a transaction that requires less fee than the prior
|
||||
// iteration.
|
||||
// Reduce fee to only the needed amount if possible. This
|
||||
// prevents potential overpayment in fees if the coins
|
||||
// selected to meet nFeeNeeded result in a transaction that
|
||||
// requires less fee than the prior iteration.
|
||||
|
||||
// TODO: The case where nSubtractFeeFromAmount > 0 remains
|
||||
// to be addressed because it requires returning the fee to
|
||||
// the payees and not the change output.
|
||||
// TODO: The case where there is no change output remains
|
||||
// to be addressed so we avoid creating too small an output.
|
||||
|
||||
// If we have no change and a big enough excess fee, then
|
||||
// try to construct transaction again only without picking
|
||||
// new inputs. We now know we only need the smaller fee
|
||||
// (because of reduced tx size) and so we should add a
|
||||
// change output. Only try this once.
|
||||
CAmount fee_needed_for_change = GetMinimumFee(change_prototype_size, currentConfirmationTarget, ::mempool, ::feeEstimator, nullptr, false /* ignoreGlobalPayTxFee */);
|
||||
CAmount minimum_value_for_change = GetDustThreshold(change_prototype_txout, ::dustRelayFee);
|
||||
CAmount max_excess_fee = fee_needed_for_change + minimum_value_for_change;
|
||||
if (nFeeRet > nFeeNeeded + max_excess_fee && nChangePosInOut == -1 && nSubtractFeeFromAmount == 0 && pick_new_inputs) {
|
||||
pick_new_inputs = false;
|
||||
nFeeRet = nFeeNeeded + fee_needed_for_change;
|
||||
continue;
|
||||
}
|
||||
|
||||
// If we have change output already, just increase it
|
||||
if (nFeeRet > nFeeNeeded && nChangePosInOut != -1 && nSubtractFeeFromAmount == 0) {
|
||||
CAmount extraFeePaid = nFeeRet - nFeeNeeded;
|
||||
std::vector<CTxOut>::iterator change_position = txNew.vout.begin()+nChangePosInOut;
|
||||
@ -3794,6 +3814,12 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletT
|
||||
}
|
||||
break; // Done, enough fee included.
|
||||
}
|
||||
else if (!pick_new_inputs) {
|
||||
// This shouldn't happen, we should have had enough excess
|
||||
// fee to pay for the new output and still meet nFeeNeeded
|
||||
strFailReason = _("Transaction fee and change calculation failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Try to reduce change to include necessary fee
|
||||
if (nChangePosInOut != -1 && nSubtractFeeFromAmount == 0) {
|
||||
@ -3813,6 +3839,8 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletT
|
||||
}
|
||||
}
|
||||
|
||||
if (nChangePosInOut == -1) reservekey.ReturnKey(); // Return any reserved key if we don't have change
|
||||
|
||||
if (sign)
|
||||
{
|
||||
CTransaction txNewConst(txNew);
|
||||
|
Loading…
Reference in New Issue
Block a user