Changeset View
Changeset View
Standalone View
Standalone View
src/wallet/wallet.cpp
Show First 20 Lines • Show All 2,863 Lines • ▼ Show 20 Lines | bool CWallet::CreateTransaction(const std::vector<CRecipient> &vecSend, | ||||
{ | { | ||||
std::set<CInputCoin> setCoins; | std::set<CInputCoin> setCoins; | ||||
LOCK2(cs_main, cs_wallet); | LOCK2(cs_main, cs_wallet); | ||||
std::vector<COutput> vAvailableCoins; | std::vector<COutput> vAvailableCoins; | ||||
AvailableCoins(vAvailableCoins, true, coinControl); | AvailableCoins(vAvailableCoins, true, coinControl); | ||||
// 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) { | |||||
deadalnix: This is very strange, but okay. That's how it is in the original diff. | |||||
strFailReason = | |||||
_("Keypool ran out, please call keypoolrefill first"); | |||||
return false; | |||||
} | |||||
scriptChange = GetScriptForDestination(vchPubKey.GetID()); | |||||
} | |||||
CTxOut change_prototype_txout(Amount::zero(), scriptChange); | |||||
size_t change_prototype_size = | |||||
GetSerializeSize(change_prototype_txout, SER_DISK, 0); | |||||
nFeeRet = Amount::zero(); | nFeeRet = Amount::zero(); | ||||
// Start with no fee and loop until there is enough fee. | bool pick_new_inputs = true; | ||||
Amount nValueIn = Amount::zero(); | |||||
// Start with no fee and loop until there is enough fee | |||||
while (true) { | while (true) { | ||||
nChangePosInOut = nChangePosRequest; | nChangePosInOut = nChangePosRequest; | ||||
txNew.vin.clear(); | txNew.vin.clear(); | ||||
txNew.vout.clear(); | txNew.vout.clear(); | ||||
wtxNew.fFromMe = true; | wtxNew.fFromMe = true; | ||||
bool fFirst = true; | bool fFirst = true; | ||||
Amount nValueToSelect = nValue; | Amount nValueToSelect = nValue; | ||||
Show All 34 Lines | assert(txNew.nLockTime < LOCKTIME_THRESHOLD); | ||||
} | } | ||||
return false; | return false; | ||||
} | } | ||||
txNew.vout.push_back(txout); | txNew.vout.push_back(txout); | ||||
} | } | ||||
// Choose coins to use. | // Choose coins to use | ||||
Amount nValueIn = Amount::zero(); | if (pick_new_inputs) { | ||||
nValueIn = Amount::zero(); | |||||
setCoins.clear(); | setCoins.clear(); | ||||
if (!SelectCoins(vAvailableCoins, nValueToSelect, setCoins, | if (!SelectCoins(vAvailableCoins, nValueToSelect, setCoins, | ||||
nValueIn, coinControl)) { | nValueIn, coinControl)) { | ||||
strFailReason = _("Insufficient funds"); | strFailReason = _("Insufficient funds"); | ||||
return false; | return false; | ||||
} | } | ||||
} | |||||
for (const auto &pcoin : setCoins) { | for (const auto &pcoin : setCoins) { | ||||
Amount nCredit = pcoin.txout.nValue; | Amount nCredit = pcoin.txout.nValue; | ||||
// The coin age after the next block (depth+1) is used instead | // The coin age after the next block (depth+1) is used instead | ||||
// of the current, reflecting an assumption the user would | // of the current, reflecting an assumption the user would | ||||
// accept a bit more delay for a chance at a free transaction. | // accept a bit more delay for a chance at a free transaction. | ||||
// But mempool inputs might still be in the mempool, so their | // But mempool inputs might still be in the mempool, so their | ||||
// age stays 0. | // age stays 0. | ||||
int age = pcoin.wtx->GetDepthInMainChain(); | int age = pcoin.wtx->GetDepthInMainChain(); | ||||
assert(age >= 0); | assert(age >= 0); | ||||
if (age != 0) { | if (age != 0) { | ||||
age += 1; | age += 1; | ||||
} | } | ||||
dPriority += (age * nCredit) / SATOSHI; | dPriority += (age * nCredit) / SATOSHI; | ||||
} | } | ||||
const Amount nChange = nValueIn - nValueToSelect; | const Amount nChange = nValueIn - nValueToSelect; | ||||
if (nChange > Amount::zero()) { | if (nChange > Amount::zero()) { | ||||
// Fill a vout to ourself. | // Fill a vout to ourself. | ||||
// 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 newTxOut(nChange, scriptChange); | CTxOut newTxOut(nChange, scriptChange); | ||||
// We do not move dust-change to fees, because the sender would | // We do not move dust-change to fees, because the sender would | ||||
// end up paying more than requested. This would be against the | // end up paying more than requested. This would be against the | ||||
// purpose of the all-inclusive feature. So instead we raise the | // purpose of the all-inclusive feature. So instead we raise the | ||||
// change and deduct from the recipient. | // change and deduct from the recipient. | ||||
if (nSubtractFeeFromAmount > 0 && | if (nSubtractFeeFromAmount > 0 && | ||||
newTxOut.IsDust(dustRelayFee)) { | newTxOut.IsDust(dustRelayFee)) { | ||||
Show All 18 Lines | assert(txNew.nLockTime < LOCKTIME_THRESHOLD); | ||||
} | } | ||||
} | } | ||||
// Never create dust outputs; if we would, just add the dust to | // Never create dust outputs; if we would, just add the dust to | ||||
// the fee. | // the fee. | ||||
if (newTxOut.IsDust(dustRelayFee)) { | if (newTxOut.IsDust(dustRelayFee)) { | ||||
nChangePosInOut = -1; | nChangePosInOut = -1; | ||||
nFeeRet += nChange; | nFeeRet += nChange; | ||||
reservekey.ReturnKey(); | |||||
} else { | } else { | ||||
if (nChangePosInOut == -1) { | if (nChangePosInOut == -1) { | ||||
// Insert change txn at random position: | // Insert change txn at random position: | ||||
nChangePosInOut = GetRandInt(txNew.vout.size() + 1); | nChangePosInOut = GetRandInt(txNew.vout.size() + 1); | ||||
} else if ((unsigned int)nChangePosInOut > | } else if ((unsigned int)nChangePosInOut > | ||||
txNew.vout.size()) { | txNew.vout.size()) { | ||||
strFailReason = _("Change index out of range"); | strFailReason = _("Change index out of range"); | ||||
return false; | return false; | ||||
} | } | ||||
std::vector<CTxOut>::iterator position = | std::vector<CTxOut>::iterator position = | ||||
txNew.vout.begin() + nChangePosInOut; | txNew.vout.begin() + nChangePosInOut; | ||||
txNew.vout.insert(position, newTxOut); | txNew.vout.insert(position, newTxOut); | ||||
} | } | ||||
} else { | } else { | ||||
reservekey.ReturnKey(); | |||||
nChangePosInOut = -1; | nChangePosInOut = -1; | ||||
} | } | ||||
// Fill vin | // Fill vin | ||||
// | // | ||||
// Note how the sequence number is set to non-maxint so that the | // Note how the sequence number is set to non-maxint so that the | ||||
// nLockTime set above actually works. | // nLockTime set above actually works. | ||||
for (const auto &coin : setCoins) { | for (const auto &coin : setCoins) { | ||||
Show All 36 Lines | assert(txNew.nLockTime < LOCKTIME_THRESHOLD); | ||||
// allowed fee. | // allowed fee. | ||||
Amount minFee = GetConfig().GetMinFeePerKB().GetFeeCeiling(nBytes); | Amount minFee = GetConfig().GetMinFeePerKB().GetFeeCeiling(nBytes); | ||||
if (nFeeNeeded < minFee) { | if (nFeeNeeded < minFee) { | ||||
strFailReason = _("Transaction too large for fee policy"); | strFailReason = _("Transaction too large for fee policy"); | ||||
return false; | return false; | ||||
} | } | ||||
if (nFeeRet >= nFeeNeeded) { | if (nFeeRet >= nFeeNeeded) { | ||||
// Reduce fee to only the needed amount if we have change output | // Reduce fee to only the needed amount if | ||||
// to increase. This prevents potential overpayment in fees if | // possible. This prevents potential overpayment | ||||
// the coins selected to meet nFeeNeeded result in a transaction | // in fees if the coins selected to meet | ||||
// that requires less fee than the prior iteration. | // nFeeNeeded result in a transaction that | ||||
// TODO: The case where nSubtractFeeFromAmount > 0 remains to be | // requires less fee than the prior iteration. | ||||
deadalnixUnsubmitted Not Done Inline ActionsHas the indentation level been changed ? If not then why reformat comment blocks ? This doesn't even correspond to what's in the original PR. deadalnix: Has the indentation level been changed ? If not then why reformat comment blocks ? This doesn't… | |||||
jasonbcoxAuthorUnsubmitted Done Inline ActionsI re-ran the linter while working on this diff and may have screwed up something because at a moment in time the indentation was different. Will fix. Edit: I figured out what happened. I expected since the linter passed on the first version, that it must either match the original PR or otherwise have needed line breaks at those locations. Neither of these were the case, but it was a valid linting, so it remained. jasonbcox: I re-ran the linter while working on this diff and may have screwed up something because at a… | |||||
// addressed because it requires returning the fee to the payees | |||||
// and not the change output. | // TODO: The case where nSubtractFeeFromAmount > | ||||
// TODO: The case where there is no change output remains to be | // 0 remains to be addressed because it requires | ||||
// addressed so we avoid creating too small an output. | // returning the fee to the payees and not the | ||||
// change 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. | |||||
Amount fee_needed_for_change = | |||||
GetMinimumFee(change_prototype_size, | |||||
currentConfirmationTarget, g_mempool); | |||||
Amount minimum_value_for_change = | |||||
change_prototype_txout.GetDustThreshold(dustRelayFee); | |||||
Amount 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 && | if (nFeeRet > nFeeNeeded && nChangePosInOut != -1 && | ||||
nSubtractFeeFromAmount == 0) { | nSubtractFeeFromAmount == 0) { | ||||
Amount extraFeePaid = nFeeRet - nFeeNeeded; | Amount extraFeePaid = nFeeRet - nFeeNeeded; | ||||
std::vector<CTxOut>::iterator change_position = | std::vector<CTxOut>::iterator change_position = | ||||
txNew.vout.begin() + nChangePosInOut; | txNew.vout.begin() + nChangePosInOut; | ||||
change_position->nValue += extraFeePaid; | change_position->nValue += extraFeePaid; | ||||
nFeeRet -= extraFeePaid; | nFeeRet -= extraFeePaid; | ||||
} | } | ||||
// Done, enough fee included. | // Done, enough fee included. | ||||
break; | break; | ||||
} 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. | // Try to reduce change to include necessary fee. | ||||
if (nChangePosInOut != -1 && nSubtractFeeFromAmount == 0) { | if (nChangePosInOut != -1 && nSubtractFeeFromAmount == 0) { | ||||
Amount additionalFeeNeeded = nFeeNeeded - nFeeRet; | Amount additionalFeeNeeded = nFeeNeeded - nFeeRet; | ||||
std::vector<CTxOut>::iterator change_position = | std::vector<CTxOut>::iterator change_position = | ||||
txNew.vout.begin() + nChangePosInOut; | txNew.vout.begin() + nChangePosInOut; | ||||
// Only reduce change if remaining amount is still a large | // Only reduce change if remaining amount is still a large | ||||
// enough output. | // enough output. | ||||
if (change_position->nValue >= | if (change_position->nValue >= | ||||
MIN_FINAL_CHANGE + additionalFeeNeeded) { | MIN_FINAL_CHANGE + additionalFeeNeeded) { | ||||
change_position->nValue -= additionalFeeNeeded; | change_position->nValue -= additionalFeeNeeded; | ||||
nFeeRet += additionalFeeNeeded; | nFeeRet += additionalFeeNeeded; | ||||
// Done, able to increase fee from change. | // Done, able to increase fee from change. | ||||
break; | break; | ||||
} | } | ||||
} | } | ||||
// Include more fee and try again. | // Include more fee and try again. | ||||
nFeeRet = nFeeNeeded; | nFeeRet = nFeeNeeded; | ||||
continue; | continue; | ||||
} | } | ||||
if (nChangePosInOut == -1) { | |||||
// Return any reserved key if we don't have change | |||||
reservekey.ReturnKey(); | |||||
} | |||||
if (sign) { | if (sign) { | ||||
SigHashType sigHashType = SigHashType().withForkId(); | SigHashType sigHashType = SigHashType().withForkId(); | ||||
CTransaction txNewConst(txNew); | CTransaction txNewConst(txNew); | ||||
int nIn = 0; | int nIn = 0; | ||||
for (const auto &coin : setCoins) { | for (const auto &coin : setCoins) { | ||||
const CScript &scriptPubKey = coin.txout.scriptPubKey; | const CScript &scriptPubKey = coin.txout.scriptPubKey; | ||||
SignatureData sigdata; | SignatureData sigdata; | ||||
▲ Show 20 Lines • Show All 1,276 Lines • Show Last 20 Lines |
This is very strange, but okay. That's how it is in the original diff.