Merge bitcoin/bitcoin#22008: wallet: Cleanup and refactor CreateTransactionInternal
96c2c9520escripted-diff: Rename SelectCoinsMinConf to AttemptSelection (Andrew Chow)b583f73354Move vin filling to before final fee setting (Andrew Chow)d39cac0547Set m_subtract_fee_outputs during recipients vector loop (Andrew Chow)364e0698a5Move variable initializations to where they are used (Andrew Chow)32ab430651Move recipients vector checks to beginning of CreateTransaction (Andrew Chow)cd1d6d3324Rename nSubtractFeeFromAmount in CreateTransaction (Andrew Chow)dac21c793fRename nValue and nValueToSelect (Andrew Chow)d2aee3bbc7Remove extraneous scope in CreateTransactionInternal (Andrew Chow)b2995963b5Move cs_wallet lock in CreateTransactionInternal to top of function (Andrew Chow) Pull request description: #17331 did some refactors and cleanup of `CreateTransactionInternal` to make it easier to understand, however it is still a bit convoluted even though it doesn't have to be. This PR does additional cleanup and refactoring to `CreateTransactionInternal` so that it is easier to understand. Some unnecessary code was removed, some variables moved around to where they matter, and several indents removed. ACKs for top commit: glozow: reACK96c2c95ryanofsky: Code review ACK96c2c9520ealso acked previously (was reverted). meshcollider: re-utACK96c2c9520eTree-SHA512: 3dba67ed436968a07bfd82d435d566ad74e116c6e50ac9baed7144a46ad5c0f630b1ba59d91e8e8972ac2af559d7c0576f0560f09684d2ab20fad6689902866f
This commit is contained in:
4 files changed
+105
-114
No files matched your search
@@ -56,7 +56,7 @@ static void CoinSelection(benchmark::Bench& bench)
|
|||||||
bench.run([&] {
|
bench.run([&] {
|
||||||
std::set<CInputCoin> setCoinsRet;
|
std::set<CInputCoin> setCoinsRet;
|
||||||
CAmount nValueRet;
|
CAmount nValueRet;
|
||||||
bool success = wallet.SelectCoinsMinConf(1003 * COIN, filter_standard, coins, setCoinsRet, nValueRet, coin_selection_params);
|
bool success = wallet.AttemptSelection(1003 * COIN, filter_standard, coins, setCoinsRet, nValueRet, coin_selection_params);
|
||||||
assert(success);
|
assert(success);
|
||||||
assert(nValueRet == 1003 * COIN);
|
assert(nValueRet == 1003 * COIN);
|
||||||
assert(setCoinsRet.size() == 2);
|
assert(setCoinsRet.size() == 2);
|
||||||
|
|||||||
+68
-77
@@ -352,7 +352,7 @@ std::vector<OutputGroup> CWallet::GroupOutputs(const std::vector<COutput>& outpu
|
|||||||
return groups_out;
|
return groups_out;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CWallet::SelectCoinsMinConf(const CAmount& nTargetValue, const CoinEligibilityFilter& eligibility_filter, std::vector<COutput> coins,
|
bool CWallet::AttemptSelection(const CAmount& nTargetValue, const CoinEligibilityFilter& eligibility_filter, std::vector<COutput> coins,
|
||||||
std::set<CInputCoin>& setCoinsRet, CAmount& nValueRet, const CoinSelectionParams& coin_selection_params) const
|
std::set<CInputCoin>& setCoinsRet, CAmount& nValueRet, const CoinSelectionParams& coin_selection_params) const
|
||||||
{
|
{
|
||||||
setCoinsRet.clear();
|
setCoinsRet.clear();
|
||||||
@@ -456,32 +456,32 @@ bool CWallet::SelectCoins(const std::vector<COutput>& vAvailableCoins, const CAm
|
|||||||
|
|
||||||
// If possible, fund the transaction with confirmed UTXOs only. Prefer at least six
|
// If possible, fund the transaction with confirmed UTXOs only. Prefer at least six
|
||||||
// confirmations on outputs received from other wallets and only spend confirmed change.
|
// confirmations on outputs received from other wallets and only spend confirmed change.
|
||||||
if (SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(1, 6, 0), vCoins, setCoinsRet, nValueRet, coin_selection_params)) return true;
|
if (AttemptSelection(value_to_select, CoinEligibilityFilter(1, 6, 0), vCoins, setCoinsRet, nValueRet, coin_selection_params)) return true;
|
||||||
if (SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(1, 1, 0), vCoins, setCoinsRet, nValueRet, coin_selection_params)) return true;
|
if (AttemptSelection(value_to_select, CoinEligibilityFilter(1, 1, 0), vCoins, setCoinsRet, nValueRet, coin_selection_params)) return true;
|
||||||
|
|
||||||
// Fall back to using zero confirmation change (but with as few ancestors in the mempool as
|
// Fall back to using zero confirmation change (but with as few ancestors in the mempool as
|
||||||
// possible) if we cannot fund the transaction otherwise.
|
// possible) if we cannot fund the transaction otherwise.
|
||||||
if (m_spend_zero_conf_change) {
|
if (m_spend_zero_conf_change) {
|
||||||
if (SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(0, 1, 2), vCoins, setCoinsRet, nValueRet, coin_selection_params)) return true;
|
if (AttemptSelection(value_to_select, CoinEligibilityFilter(0, 1, 2), vCoins, setCoinsRet, nValueRet, coin_selection_params)) return true;
|
||||||
if (SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(0, 1, std::min((size_t)4, max_ancestors/3), std::min((size_t)4, max_descendants/3)),
|
if (AttemptSelection(value_to_select, CoinEligibilityFilter(0, 1, std::min((size_t)4, max_ancestors/3), std::min((size_t)4, max_descendants/3)),
|
||||||
vCoins, setCoinsRet, nValueRet, coin_selection_params)) {
|
vCoins, setCoinsRet, nValueRet, coin_selection_params)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(0, 1, max_ancestors/2, max_descendants/2),
|
if (AttemptSelection(value_to_select, CoinEligibilityFilter(0, 1, max_ancestors/2, max_descendants/2),
|
||||||
vCoins, setCoinsRet, nValueRet, coin_selection_params)) {
|
vCoins, setCoinsRet, nValueRet, coin_selection_params)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
// If partial groups are allowed, relax the requirement of spending OutputGroups (groups
|
// If partial groups are allowed, relax the requirement of spending OutputGroups (groups
|
||||||
// of UTXOs sent to the same address, which are obviously controlled by a single wallet)
|
// of UTXOs sent to the same address, which are obviously controlled by a single wallet)
|
||||||
// in their entirety.
|
// in their entirety.
|
||||||
if (SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(0, 1, max_ancestors-1, max_descendants-1, true /* include_partial_groups */),
|
if (AttemptSelection(value_to_select, CoinEligibilityFilter(0, 1, max_ancestors-1, max_descendants-1, true /* include_partial_groups */),
|
||||||
vCoins, setCoinsRet, nValueRet, coin_selection_params)) {
|
vCoins, setCoinsRet, nValueRet, coin_selection_params)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
// Try with unsafe inputs if they are allowed. This may spend unconfirmed outputs
|
// Try with unsafe inputs if they are allowed. This may spend unconfirmed outputs
|
||||||
// received from other wallets.
|
// received from other wallets.
|
||||||
if (coin_control.m_include_unsafe_inputs
|
if (coin_control.m_include_unsafe_inputs
|
||||||
&& SelectCoinsMinConf(value_to_select,
|
&& AttemptSelection(value_to_select,
|
||||||
CoinEligibilityFilter(0 /* conf_mine */, 0 /* conf_theirs */, max_ancestors-1, max_descendants-1, true /* include_partial_groups */),
|
CoinEligibilityFilter(0 /* conf_mine */, 0 /* conf_theirs */, max_ancestors-1, max_descendants-1, true /* include_partial_groups */),
|
||||||
vCoins, setCoinsRet, nValueRet, coin_selection_params)) {
|
vCoins, setCoinsRet, nValueRet, coin_selection_params)) {
|
||||||
return true;
|
return true;
|
||||||
@@ -489,7 +489,7 @@ bool CWallet::SelectCoins(const std::vector<COutput>& vAvailableCoins, const CAm
|
|||||||
// Try with unlimited ancestors/descendants. The transaction will still need to meet
|
// Try with unlimited ancestors/descendants. The transaction will still need to meet
|
||||||
// mempool ancestor/descendant policy to be accepted to mempool and broadcasted, but
|
// mempool ancestor/descendant policy to be accepted to mempool and broadcasted, but
|
||||||
// OutputGroups use heuristics that may overestimate ancestor/descendant counts.
|
// OutputGroups use heuristics that may overestimate ancestor/descendant counts.
|
||||||
if (!fRejectLongChains && SelectCoinsMinConf(value_to_select,
|
if (!fRejectLongChains && AttemptSelection(value_to_select,
|
||||||
CoinEligibilityFilter(0, 1, std::numeric_limits<uint64_t>::max(), std::numeric_limits<uint64_t>::max(), true /* include_partial_groups */),
|
CoinEligibilityFilter(0, 1, std::numeric_limits<uint64_t>::max(), std::numeric_limits<uint64_t>::max(), true /* include_partial_groups */),
|
||||||
vCoins, setCoinsRet, nValueRet, coin_selection_params)) {
|
vCoins, setCoinsRet, nValueRet, coin_selection_params)) {
|
||||||
return true;
|
return true;
|
||||||
@@ -499,7 +499,7 @@ bool CWallet::SelectCoins(const std::vector<COutput>& vAvailableCoins, const CAm
|
|||||||
return false;
|
return false;
|
||||||
}();
|
}();
|
||||||
|
|
||||||
// SelectCoinsMinConf clears setCoinsRet, so add the preset inputs from coin_control to the coinset
|
// AttemptSelection clears setCoinsRet, so add the preset inputs from coin_control to the coinset
|
||||||
util::insert(setCoinsRet, setPresetCoins);
|
util::insert(setCoinsRet, setPresetCoins);
|
||||||
|
|
||||||
// add preset inputs to the total value selected
|
// add preset inputs to the total value selected
|
||||||
@@ -578,42 +578,27 @@ bool CWallet::CreateTransactionInternal(
|
|||||||
FeeCalculation& fee_calc_out,
|
FeeCalculation& fee_calc_out,
|
||||||
bool sign)
|
bool sign)
|
||||||
{
|
{
|
||||||
CAmount nValue = 0;
|
AssertLockHeld(cs_wallet);
|
||||||
const OutputType change_type = TransactionChangeType(coin_control.m_change_type ? *coin_control.m_change_type : m_default_change_type, vecSend);
|
|
||||||
ReserveDestination reservedest(this, change_type);
|
|
||||||
unsigned int nSubtractFeeFromAmount = 0;
|
|
||||||
for (const auto& recipient : vecSend)
|
|
||||||
{
|
|
||||||
if (nValue < 0 || recipient.nAmount < 0)
|
|
||||||
{
|
|
||||||
error = _("Transaction amounts must not be negative");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
nValue += recipient.nAmount;
|
|
||||||
|
|
||||||
if (recipient.fSubtractFeeFromAmount)
|
CMutableTransaction txNew; // The resulting transaction that we make
|
||||||
nSubtractFeeFromAmount++;
|
|
||||||
}
|
|
||||||
if (vecSend.empty())
|
|
||||||
{
|
|
||||||
error = _("Transaction must have at least one recipient");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
CMutableTransaction txNew;
|
|
||||||
FeeCalculation feeCalc;
|
|
||||||
TxSize tx_sizes;
|
|
||||||
int nBytes;
|
|
||||||
{
|
|
||||||
std::set<CInputCoin> setCoins;
|
|
||||||
LOCK(cs_wallet);
|
|
||||||
txNew.nLockTime = GetLocktimeForNewTransaction(chain(), GetLastBlockHash(), GetLastBlockHeight());
|
txNew.nLockTime = GetLocktimeForNewTransaction(chain(), GetLastBlockHash(), GetLastBlockHeight());
|
||||||
{
|
|
||||||
std::vector<COutput> vAvailableCoins;
|
|
||||||
AvailableCoins(vAvailableCoins, &coin_control, 1, MAX_MONEY, MAX_MONEY, 0);
|
|
||||||
CoinSelectionParams coin_selection_params; // Parameters for coin selection, init with dummy
|
CoinSelectionParams coin_selection_params; // Parameters for coin selection, init with dummy
|
||||||
coin_selection_params.m_avoid_partial_spends = coin_control.m_avoid_partial_spends;
|
coin_selection_params.m_avoid_partial_spends = coin_control.m_avoid_partial_spends;
|
||||||
|
|
||||||
|
CAmount recipients_sum = 0;
|
||||||
|
const OutputType change_type = TransactionChangeType(coin_control.m_change_type ? *coin_control.m_change_type : m_default_change_type, vecSend);
|
||||||
|
ReserveDestination reservedest(this, change_type);
|
||||||
|
unsigned int outputs_to_subtract_fee_from = 0; // The number of outputs which we are subtracting the fee from
|
||||||
|
for (const auto& recipient : vecSend) {
|
||||||
|
recipients_sum += recipient.nAmount;
|
||||||
|
|
||||||
|
if (recipient.fSubtractFeeFromAmount) {
|
||||||
|
outputs_to_subtract_fee_from++;
|
||||||
|
coin_selection_params.m_subtract_fee_outputs = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Create change script that will be used if we need change
|
// Create change script that will be used if we need change
|
||||||
// TODO: pass in scriptChange instead of reservedest so
|
// TODO: pass in scriptChange instead of reservedest so
|
||||||
// change transaction isn't always pay-to-bitcoin-address
|
// change transaction isn't always pay-to-bitcoin-address
|
||||||
@@ -659,6 +644,7 @@ bool CWallet::CreateTransactionInternal(
|
|||||||
coin_selection_params.m_discard_feerate = GetDiscardRate(*this);
|
coin_selection_params.m_discard_feerate = GetDiscardRate(*this);
|
||||||
|
|
||||||
// Get the fee rate to use effective values in coin selection
|
// Get the fee rate to use effective values in coin selection
|
||||||
|
FeeCalculation feeCalc;
|
||||||
coin_selection_params.m_effective_feerate = GetMinimumFeeRate(*this, coin_control, &feeCalc);
|
coin_selection_params.m_effective_feerate = GetMinimumFeeRate(*this, coin_control, &feeCalc);
|
||||||
// Do not, ever, assume that it's fine to change the fee rate if the user has explicitly
|
// Do not, ever, assume that it's fine to change the fee rate if the user has explicitly
|
||||||
// provided one
|
// provided one
|
||||||
@@ -685,8 +671,6 @@ bool CWallet::CreateTransactionInternal(
|
|||||||
coin_selection_params.m_change_fee = coin_selection_params.m_effective_feerate.GetFee(coin_selection_params.change_output_size);
|
coin_selection_params.m_change_fee = coin_selection_params.m_effective_feerate.GetFee(coin_selection_params.change_output_size);
|
||||||
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;
|
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;
|
||||||
|
|
||||||
coin_selection_params.m_subtract_fee_outputs = nSubtractFeeFromAmount != 0; // If we are doing subtract fee from recipient, don't use effective values
|
|
||||||
|
|
||||||
// vouts to the payees
|
// vouts to the payees
|
||||||
if (!coin_selection_params.m_subtract_fee_outputs) {
|
if (!coin_selection_params.m_subtract_fee_outputs) {
|
||||||
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)
|
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)
|
||||||
@@ -710,12 +694,16 @@ bool CWallet::CreateTransactionInternal(
|
|||||||
|
|
||||||
// Include the fees for things that aren't inputs, excluding the change output
|
// Include the fees for things that aren't inputs, excluding the change output
|
||||||
const CAmount not_input_fees = coin_selection_params.m_effective_feerate.GetFee(coin_selection_params.tx_noinputs_size);
|
const CAmount not_input_fees = coin_selection_params.m_effective_feerate.GetFee(coin_selection_params.tx_noinputs_size);
|
||||||
CAmount nValueToSelect = nValue + not_input_fees;
|
CAmount selection_target = recipients_sum + not_input_fees;
|
||||||
|
|
||||||
|
// Get available coins
|
||||||
|
std::vector<COutput> vAvailableCoins;
|
||||||
|
AvailableCoins(vAvailableCoins, &coin_control, 1, MAX_MONEY, MAX_MONEY, 0);
|
||||||
|
|
||||||
// Choose coins to use
|
// Choose coins to use
|
||||||
CAmount inputs_sum = 0;
|
CAmount inputs_sum = 0;
|
||||||
setCoins.clear();
|
std::set<CInputCoin> setCoins;
|
||||||
if (!SelectCoins(vAvailableCoins, /* nTargetValue */ nValueToSelect, setCoins, inputs_sum, coin_control, coin_selection_params))
|
if (!SelectCoins(vAvailableCoins, /* nTargetValue */ selection_target, setCoins, inputs_sum, coin_control, coin_selection_params))
|
||||||
{
|
{
|
||||||
error = _("Insufficient funds");
|
error = _("Insufficient funds");
|
||||||
return false;
|
return false;
|
||||||
@@ -723,7 +711,7 @@ bool CWallet::CreateTransactionInternal(
|
|||||||
|
|
||||||
// Always make a change output
|
// Always make a change output
|
||||||
// We will reduce the fee from this change output later, and remove the output if it is too small.
|
// We will reduce the fee from this change output later, and remove the output if it is too small.
|
||||||
const CAmount change_and_fee = inputs_sum - nValue;
|
const CAmount change_and_fee = inputs_sum - recipients_sum;
|
||||||
assert(change_and_fee >= 0);
|
assert(change_and_fee >= 0);
|
||||||
CTxOut newTxOut(change_and_fee, scriptChange);
|
CTxOut newTxOut(change_and_fee, scriptChange);
|
||||||
|
|
||||||
@@ -741,24 +729,35 @@ bool CWallet::CreateTransactionInternal(
|
|||||||
assert(nChangePosInOut != -1);
|
assert(nChangePosInOut != -1);
|
||||||
auto change_position = txNew.vout.insert(txNew.vout.begin() + nChangePosInOut, newTxOut);
|
auto change_position = txNew.vout.insert(txNew.vout.begin() + nChangePosInOut, newTxOut);
|
||||||
|
|
||||||
// Dummy fill vin for maximum size estimation
|
// Shuffle selected coins and fill in final vin
|
||||||
|
std::vector<CInputCoin> selected_coins(setCoins.begin(), setCoins.end());
|
||||||
|
Shuffle(selected_coins.begin(), selected_coins.end(), FastRandomContext());
|
||||||
|
|
||||||
|
// Note how the sequence number is set to non-maxint so that
|
||||||
|
// the nLockTime set above actually works.
|
||||||
//
|
//
|
||||||
for (const auto& coin : setCoins) {
|
// BIP125 defines opt-in RBF as any nSequence < maxint-1, so
|
||||||
txNew.vin.push_back(CTxIn(coin.outpoint,CScript()));
|
// we use the highest possible value in that range (maxint-2)
|
||||||
|
// to avoid conflicting with other possible uses of nSequence,
|
||||||
|
// and in the spirit of "smallest possible change from prior
|
||||||
|
// behavior."
|
||||||
|
const uint32_t nSequence = coin_control.m_signal_bip125_rbf.value_or(m_signal_rbf) ? MAX_BIP125_RBF_SEQUENCE : (CTxIn::SEQUENCE_FINAL - 1);
|
||||||
|
for (const auto& coin : selected_coins) {
|
||||||
|
txNew.vin.push_back(CTxIn(coin.outpoint, CScript(), nSequence));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate the transaction fee
|
// Calculate the transaction fee
|
||||||
tx_sizes = CalculateMaximumSignedTxSize(CTransaction(txNew), this, coin_control.fAllowWatchOnly);
|
TxSize tx_sizes = CalculateMaximumSignedTxSize(CTransaction(txNew), this, coin_control.fAllowWatchOnly);
|
||||||
nBytes = tx_sizes.vsize;
|
int nBytes = tx_sizes.vsize;
|
||||||
if (nBytes < 0) {
|
if (nBytes < 0) {
|
||||||
error = _("Signing transaction failed");
|
error = _("Signing transaction failed");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
nFeeRet = coin_selection_params.m_effective_feerate.GetFee(nBytes);
|
nFeeRet = coin_selection_params.m_effective_feerate.GetFee(nBytes);
|
||||||
|
|
||||||
// Subtract fee from the change output if not subtrating it from recipient outputs
|
// Subtract fee from the change output if not subtracting it from recipient outputs
|
||||||
CAmount fee_needed = nFeeRet;
|
CAmount fee_needed = nFeeRet;
|
||||||
if (nSubtractFeeFromAmount == 0) {
|
if (!coin_selection_params.m_subtract_fee_outputs) {
|
||||||
change_position->nValue -= fee_needed;
|
change_position->nValue -= fee_needed;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -784,7 +783,7 @@ bool CWallet::CreateTransactionInternal(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Reduce output values for subtractFeeFromAmount
|
// Reduce output values for subtractFeeFromAmount
|
||||||
if (nSubtractFeeFromAmount != 0) {
|
if (coin_selection_params.m_subtract_fee_outputs) {
|
||||||
CAmount to_reduce = fee_needed + change_amount - change_and_fee;
|
CAmount to_reduce = fee_needed + change_amount - change_and_fee;
|
||||||
int i = 0;
|
int i = 0;
|
||||||
bool fFirst = true;
|
bool fFirst = true;
|
||||||
@@ -797,12 +796,12 @@ bool CWallet::CreateTransactionInternal(
|
|||||||
|
|
||||||
if (recipient.fSubtractFeeFromAmount)
|
if (recipient.fSubtractFeeFromAmount)
|
||||||
{
|
{
|
||||||
txout.nValue -= to_reduce / nSubtractFeeFromAmount; // Subtract fee equally from each selected recipient
|
txout.nValue -= to_reduce / outputs_to_subtract_fee_from; // Subtract fee equally from each selected recipient
|
||||||
|
|
||||||
if (fFirst) // first receiver pays the remainder not divisible by output count
|
if (fFirst) // first receiver pays the remainder not divisible by output count
|
||||||
{
|
{
|
||||||
fFirst = false;
|
fFirst = false;
|
||||||
txout.nValue -= to_reduce % nSubtractFeeFromAmount;
|
txout.nValue -= to_reduce % outputs_to_subtract_fee_from;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Error if this output is reduced to be below dust
|
// Error if this output is reduced to be below dust
|
||||||
@@ -824,25 +823,6 @@ bool CWallet::CreateTransactionInternal(
|
|||||||
if (scriptChange.empty() && nChangePosInOut != -1) {
|
if (scriptChange.empty() && nChangePosInOut != -1) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Shuffle selected coins and fill in final vin
|
|
||||||
txNew.vin.clear();
|
|
||||||
std::vector<CInputCoin> selected_coins(setCoins.begin(), setCoins.end());
|
|
||||||
Shuffle(selected_coins.begin(), selected_coins.end(), FastRandomContext());
|
|
||||||
|
|
||||||
// Note how the sequence number is set to non-maxint so that
|
|
||||||
// the nLockTime set above actually works.
|
|
||||||
//
|
|
||||||
// BIP125 defines opt-in RBF as any nSequence < maxint-1, so
|
|
||||||
// we use the highest possible value in that range (maxint-2)
|
|
||||||
// to avoid conflicting with other possible uses of nSequence,
|
|
||||||
// and in the spirit of "smallest possible change from prior
|
|
||||||
// behavior."
|
|
||||||
const uint32_t nSequence = coin_control.m_signal_bip125_rbf.value_or(m_signal_rbf) ? MAX_BIP125_RBF_SEQUENCE : (CTxIn::SEQUENCE_FINAL - 1);
|
|
||||||
for (const auto& coin : selected_coins) {
|
|
||||||
txNew.vin.push_back(CTxIn(coin.outpoint, CScript(), nSequence));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sign && !SignTransaction(txNew)) {
|
if (sign && !SignTransaction(txNew)) {
|
||||||
error = _("Signing transaction failed");
|
error = _("Signing transaction failed");
|
||||||
@@ -859,7 +839,6 @@ bool CWallet::CreateTransactionInternal(
|
|||||||
error = _("Transaction too large");
|
error = _("Transaction too large");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (nFeeRet > m_default_max_tx_fee) {
|
if (nFeeRet > m_default_max_tx_fee) {
|
||||||
error = TransactionErrorString(TransactionError::MAX_FEE_EXCEEDED);
|
error = TransactionErrorString(TransactionError::MAX_FEE_EXCEEDED);
|
||||||
@@ -900,6 +879,18 @@ bool CWallet::CreateTransaction(
|
|||||||
FeeCalculation& fee_calc_out,
|
FeeCalculation& fee_calc_out,
|
||||||
bool sign)
|
bool sign)
|
||||||
{
|
{
|
||||||
|
if (vecSend.empty()) {
|
||||||
|
error = _("Transaction must have at least one recipient");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (std::any_of(vecSend.cbegin(), vecSend.cend(), [](const auto& recipient){ return recipient.nAmount < 0; })) {
|
||||||
|
error = _("Transaction amounts must not be negative");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOCK(cs_wallet);
|
||||||
|
|
||||||
int nChangePosIn = nChangePosInOut;
|
int nChangePosIn = nChangePosInOut;
|
||||||
Assert(!tx); // tx is an out-param. TODO change the return type from bool to tx (or nullptr)
|
Assert(!tx); // tx is an out-param. TODO change the return type from bool to tx (or nullptr)
|
||||||
bool res = CreateTransactionInternal(vecSend, tx, nFeeRet, nChangePosInOut, error, coin_control, fee_calc_out, sign);
|
bool res = CreateTransactionInternal(vecSend, tx, nFeeRet, nChangePosInOut, error, coin_control, fee_calc_out, sign);
|
||||||
|
|||||||
@@ -270,7 +270,7 @@ BOOST_AUTO_TEST_CASE(bnb_search_test)
|
|||||||
BOOST_CHECK(!SelectCoinsBnB(GroupCoins(utxo_pool), 1 * CENT, 2 * CENT, selection, value_ret));
|
BOOST_CHECK(!SelectCoinsBnB(GroupCoins(utxo_pool), 1 * CENT, 2 * CENT, selection, value_ret));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make sure that effective value is working in SelectCoinsMinConf when BnB is used
|
// Make sure that effective value is working in AttemptSelection when BnB is used
|
||||||
CoinSelectionParams coin_selection_params_bnb(/* change_output_size= */ 0,
|
CoinSelectionParams coin_selection_params_bnb(/* change_output_size= */ 0,
|
||||||
/* change_spend_size= */ 0, /* effective_feerate= */ CFeeRate(3000),
|
/* change_spend_size= */ 0, /* effective_feerate= */ CFeeRate(3000),
|
||||||
/* long_term_feerate= */ CFeeRate(1000), /* discard_feerate= */ CFeeRate(1000),
|
/* long_term_feerate= */ CFeeRate(1000), /* discard_feerate= */ CFeeRate(1000),
|
||||||
@@ -280,14 +280,14 @@ BOOST_AUTO_TEST_CASE(bnb_search_test)
|
|||||||
empty_wallet();
|
empty_wallet();
|
||||||
add_coin(1);
|
add_coin(1);
|
||||||
vCoins.at(0).nInputBytes = 40; // Make sure that it has a negative effective value. The next check should assert if this somehow got through. Otherwise it will fail
|
vCoins.at(0).nInputBytes = 40; // Make sure that it has a negative effective value. The next check should assert if this somehow got through. Otherwise it will fail
|
||||||
BOOST_CHECK(!testWallet.SelectCoinsMinConf( 1 * CENT, filter_standard, vCoins, setCoinsRet, nValueRet, coin_selection_params_bnb));
|
BOOST_CHECK(!testWallet.AttemptSelection( 1 * CENT, filter_standard, vCoins, setCoinsRet, nValueRet, coin_selection_params_bnb));
|
||||||
|
|
||||||
// Test fees subtracted from output:
|
// Test fees subtracted from output:
|
||||||
empty_wallet();
|
empty_wallet();
|
||||||
add_coin(1 * CENT);
|
add_coin(1 * CENT);
|
||||||
vCoins.at(0).nInputBytes = 40;
|
vCoins.at(0).nInputBytes = 40;
|
||||||
coin_selection_params_bnb.m_subtract_fee_outputs = true;
|
coin_selection_params_bnb.m_subtract_fee_outputs = true;
|
||||||
BOOST_CHECK(testWallet.SelectCoinsMinConf( 1 * CENT, filter_standard, vCoins, setCoinsRet, nValueRet, coin_selection_params_bnb));
|
BOOST_CHECK(testWallet.AttemptSelection( 1 * CENT, filter_standard, vCoins, setCoinsRet, nValueRet, coin_selection_params_bnb));
|
||||||
BOOST_CHECK_EQUAL(nValueRet, 1 * CENT);
|
BOOST_CHECK_EQUAL(nValueRet, 1 * CENT);
|
||||||
|
|
||||||
// Make sure that can use BnB when there are preset inputs
|
// Make sure that can use BnB when there are preset inputs
|
||||||
@@ -322,24 +322,24 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test)
|
|||||||
empty_wallet();
|
empty_wallet();
|
||||||
|
|
||||||
// with an empty wallet we can't even pay one cent
|
// with an empty wallet we can't even pay one cent
|
||||||
BOOST_CHECK(!testWallet.SelectCoinsMinConf( 1 * CENT, filter_standard, vCoins, setCoinsRet, nValueRet, coin_selection_params));
|
BOOST_CHECK(!testWallet.AttemptSelection( 1 * CENT, filter_standard, vCoins, setCoinsRet, nValueRet, coin_selection_params));
|
||||||
|
|
||||||
add_coin(1*CENT, 4); // add a new 1 cent coin
|
add_coin(1*CENT, 4); // add a new 1 cent coin
|
||||||
|
|
||||||
// with a new 1 cent coin, we still can't find a mature 1 cent
|
// with a new 1 cent coin, we still can't find a mature 1 cent
|
||||||
BOOST_CHECK(!testWallet.SelectCoinsMinConf( 1 * CENT, filter_standard, vCoins, setCoinsRet, nValueRet, coin_selection_params));
|
BOOST_CHECK(!testWallet.AttemptSelection( 1 * CENT, filter_standard, vCoins, setCoinsRet, nValueRet, coin_selection_params));
|
||||||
|
|
||||||
// but we can find a new 1 cent
|
// but we can find a new 1 cent
|
||||||
BOOST_CHECK( testWallet.SelectCoinsMinConf( 1 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params));
|
BOOST_CHECK( testWallet.AttemptSelection( 1 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params));
|
||||||
BOOST_CHECK_EQUAL(nValueRet, 1 * CENT);
|
BOOST_CHECK_EQUAL(nValueRet, 1 * CENT);
|
||||||
|
|
||||||
add_coin(2*CENT); // add a mature 2 cent coin
|
add_coin(2*CENT); // add a mature 2 cent coin
|
||||||
|
|
||||||
// we can't make 3 cents of mature coins
|
// we can't make 3 cents of mature coins
|
||||||
BOOST_CHECK(!testWallet.SelectCoinsMinConf( 3 * CENT, filter_standard, vCoins, setCoinsRet, nValueRet, coin_selection_params));
|
BOOST_CHECK(!testWallet.AttemptSelection( 3 * CENT, filter_standard, vCoins, setCoinsRet, nValueRet, coin_selection_params));
|
||||||
|
|
||||||
// we can make 3 cents of new coins
|
// we can make 3 cents of new coins
|
||||||
BOOST_CHECK( testWallet.SelectCoinsMinConf( 3 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params));
|
BOOST_CHECK( testWallet.AttemptSelection( 3 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params));
|
||||||
BOOST_CHECK_EQUAL(nValueRet, 3 * CENT);
|
BOOST_CHECK_EQUAL(nValueRet, 3 * CENT);
|
||||||
|
|
||||||
add_coin(5*CENT); // add a mature 5 cent coin,
|
add_coin(5*CENT); // add a mature 5 cent coin,
|
||||||
@@ -349,33 +349,33 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test)
|
|||||||
// now we have new: 1+10=11 (of which 10 was self-sent), and mature: 2+5+20=27. total = 38
|
// now we have new: 1+10=11 (of which 10 was self-sent), and mature: 2+5+20=27. total = 38
|
||||||
|
|
||||||
// we can't make 38 cents only if we disallow new coins:
|
// we can't make 38 cents only if we disallow new coins:
|
||||||
BOOST_CHECK(!testWallet.SelectCoinsMinConf(38 * CENT, filter_standard, vCoins, setCoinsRet, nValueRet, coin_selection_params));
|
BOOST_CHECK(!testWallet.AttemptSelection(38 * CENT, filter_standard, vCoins, setCoinsRet, nValueRet, coin_selection_params));
|
||||||
// we can't even make 37 cents if we don't allow new coins even if they're from us
|
// we can't even make 37 cents if we don't allow new coins even if they're from us
|
||||||
BOOST_CHECK(!testWallet.SelectCoinsMinConf(38 * CENT, filter_standard_extra, vCoins, setCoinsRet, nValueRet, coin_selection_params));
|
BOOST_CHECK(!testWallet.AttemptSelection(38 * CENT, filter_standard_extra, vCoins, setCoinsRet, nValueRet, coin_selection_params));
|
||||||
// but we can make 37 cents if we accept new coins from ourself
|
// but we can make 37 cents if we accept new coins from ourself
|
||||||
BOOST_CHECK( testWallet.SelectCoinsMinConf(37 * CENT, filter_standard, vCoins, setCoinsRet, nValueRet, coin_selection_params));
|
BOOST_CHECK( testWallet.AttemptSelection(37 * CENT, filter_standard, vCoins, setCoinsRet, nValueRet, coin_selection_params));
|
||||||
BOOST_CHECK_EQUAL(nValueRet, 37 * CENT);
|
BOOST_CHECK_EQUAL(nValueRet, 37 * CENT);
|
||||||
// and we can make 38 cents if we accept all new coins
|
// and we can make 38 cents if we accept all new coins
|
||||||
BOOST_CHECK( testWallet.SelectCoinsMinConf(38 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params));
|
BOOST_CHECK( testWallet.AttemptSelection(38 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params));
|
||||||
BOOST_CHECK_EQUAL(nValueRet, 38 * CENT);
|
BOOST_CHECK_EQUAL(nValueRet, 38 * CENT);
|
||||||
|
|
||||||
// try making 34 cents from 1,2,5,10,20 - we can't do it exactly
|
// try making 34 cents from 1,2,5,10,20 - we can't do it exactly
|
||||||
BOOST_CHECK( testWallet.SelectCoinsMinConf(34 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params));
|
BOOST_CHECK( testWallet.AttemptSelection(34 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params));
|
||||||
BOOST_CHECK_EQUAL(nValueRet, 35 * CENT); // but 35 cents is closest
|
BOOST_CHECK_EQUAL(nValueRet, 35 * CENT); // but 35 cents is closest
|
||||||
BOOST_CHECK_EQUAL(setCoinsRet.size(), 3U); // the best should be 20+10+5. it's incredibly unlikely the 1 or 2 got included (but possible)
|
BOOST_CHECK_EQUAL(setCoinsRet.size(), 3U); // the best should be 20+10+5. it's incredibly unlikely the 1 or 2 got included (but possible)
|
||||||
|
|
||||||
// when we try making 7 cents, the smaller coins (1,2,5) are enough. We should see just 2+5
|
// when we try making 7 cents, the smaller coins (1,2,5) are enough. We should see just 2+5
|
||||||
BOOST_CHECK( testWallet.SelectCoinsMinConf( 7 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params));
|
BOOST_CHECK( testWallet.AttemptSelection( 7 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params));
|
||||||
BOOST_CHECK_EQUAL(nValueRet, 7 * CENT);
|
BOOST_CHECK_EQUAL(nValueRet, 7 * CENT);
|
||||||
BOOST_CHECK_EQUAL(setCoinsRet.size(), 2U);
|
BOOST_CHECK_EQUAL(setCoinsRet.size(), 2U);
|
||||||
|
|
||||||
// when we try making 8 cents, the smaller coins (1,2,5) are exactly enough.
|
// when we try making 8 cents, the smaller coins (1,2,5) are exactly enough.
|
||||||
BOOST_CHECK( testWallet.SelectCoinsMinConf( 8 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params));
|
BOOST_CHECK( testWallet.AttemptSelection( 8 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params));
|
||||||
BOOST_CHECK(nValueRet == 8 * CENT);
|
BOOST_CHECK(nValueRet == 8 * CENT);
|
||||||
BOOST_CHECK_EQUAL(setCoinsRet.size(), 3U);
|
BOOST_CHECK_EQUAL(setCoinsRet.size(), 3U);
|
||||||
|
|
||||||
// when we try making 9 cents, no subset of smaller coins is enough, and we get the next bigger coin (10)
|
// when we try making 9 cents, no subset of smaller coins is enough, and we get the next bigger coin (10)
|
||||||
BOOST_CHECK( testWallet.SelectCoinsMinConf( 9 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params));
|
BOOST_CHECK( testWallet.AttemptSelection( 9 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params));
|
||||||
BOOST_CHECK_EQUAL(nValueRet, 10 * CENT);
|
BOOST_CHECK_EQUAL(nValueRet, 10 * CENT);
|
||||||
BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U);
|
BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U);
|
||||||
|
|
||||||
@@ -389,30 +389,30 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test)
|
|||||||
add_coin(30*CENT); // now we have 6+7+8+20+30 = 71 cents total
|
add_coin(30*CENT); // now we have 6+7+8+20+30 = 71 cents total
|
||||||
|
|
||||||
// check that we have 71 and not 72
|
// check that we have 71 and not 72
|
||||||
BOOST_CHECK( testWallet.SelectCoinsMinConf(71 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params));
|
BOOST_CHECK( testWallet.AttemptSelection(71 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params));
|
||||||
BOOST_CHECK(!testWallet.SelectCoinsMinConf(72 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params));
|
BOOST_CHECK(!testWallet.AttemptSelection(72 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params));
|
||||||
|
|
||||||
// now try making 16 cents. the best smaller coins can do is 6+7+8 = 21; not as good at the next biggest coin, 20
|
// now try making 16 cents. the best smaller coins can do is 6+7+8 = 21; not as good at the next biggest coin, 20
|
||||||
BOOST_CHECK( testWallet.SelectCoinsMinConf(16 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params));
|
BOOST_CHECK( testWallet.AttemptSelection(16 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params));
|
||||||
BOOST_CHECK_EQUAL(nValueRet, 20 * CENT); // we should get 20 in one coin
|
BOOST_CHECK_EQUAL(nValueRet, 20 * CENT); // we should get 20 in one coin
|
||||||
BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U);
|
BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U);
|
||||||
|
|
||||||
add_coin( 5*CENT); // now we have 5+6+7+8+20+30 = 75 cents total
|
add_coin( 5*CENT); // now we have 5+6+7+8+20+30 = 75 cents total
|
||||||
|
|
||||||
// now if we try making 16 cents again, the smaller coins can make 5+6+7 = 18 cents, better than the next biggest coin, 20
|
// now if we try making 16 cents again, the smaller coins can make 5+6+7 = 18 cents, better than the next biggest coin, 20
|
||||||
BOOST_CHECK( testWallet.SelectCoinsMinConf(16 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params));
|
BOOST_CHECK( testWallet.AttemptSelection(16 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params));
|
||||||
BOOST_CHECK_EQUAL(nValueRet, 18 * CENT); // we should get 18 in 3 coins
|
BOOST_CHECK_EQUAL(nValueRet, 18 * CENT); // we should get 18 in 3 coins
|
||||||
BOOST_CHECK_EQUAL(setCoinsRet.size(), 3U);
|
BOOST_CHECK_EQUAL(setCoinsRet.size(), 3U);
|
||||||
|
|
||||||
add_coin( 18*CENT); // now we have 5+6+7+8+18+20+30
|
add_coin( 18*CENT); // now we have 5+6+7+8+18+20+30
|
||||||
|
|
||||||
// and now if we try making 16 cents again, the smaller coins can make 5+6+7 = 18 cents, the same as the next biggest coin, 18
|
// and now if we try making 16 cents again, the smaller coins can make 5+6+7 = 18 cents, the same as the next biggest coin, 18
|
||||||
BOOST_CHECK( testWallet.SelectCoinsMinConf(16 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params));
|
BOOST_CHECK( testWallet.AttemptSelection(16 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params));
|
||||||
BOOST_CHECK_EQUAL(nValueRet, 18 * CENT); // we should get 18 in 1 coin
|
BOOST_CHECK_EQUAL(nValueRet, 18 * CENT); // we should get 18 in 1 coin
|
||||||
BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U); // because in the event of a tie, the biggest coin wins
|
BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U); // because in the event of a tie, the biggest coin wins
|
||||||
|
|
||||||
// now try making 11 cents. we should get 5+6
|
// now try making 11 cents. we should get 5+6
|
||||||
BOOST_CHECK( testWallet.SelectCoinsMinConf(11 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params));
|
BOOST_CHECK( testWallet.AttemptSelection(11 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params));
|
||||||
BOOST_CHECK_EQUAL(nValueRet, 11 * CENT);
|
BOOST_CHECK_EQUAL(nValueRet, 11 * CENT);
|
||||||
BOOST_CHECK_EQUAL(setCoinsRet.size(), 2U);
|
BOOST_CHECK_EQUAL(setCoinsRet.size(), 2U);
|
||||||
|
|
||||||
@@ -421,11 +421,11 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test)
|
|||||||
add_coin( 2*COIN);
|
add_coin( 2*COIN);
|
||||||
add_coin( 3*COIN);
|
add_coin( 3*COIN);
|
||||||
add_coin( 4*COIN); // now we have 5+6+7+8+18+20+30+100+200+300+400 = 1094 cents
|
add_coin( 4*COIN); // now we have 5+6+7+8+18+20+30+100+200+300+400 = 1094 cents
|
||||||
BOOST_CHECK( testWallet.SelectCoinsMinConf(95 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params));
|
BOOST_CHECK( testWallet.AttemptSelection(95 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params));
|
||||||
BOOST_CHECK_EQUAL(nValueRet, 1 * COIN); // we should get 1 BTC in 1 coin
|
BOOST_CHECK_EQUAL(nValueRet, 1 * COIN); // we should get 1 BTC in 1 coin
|
||||||
BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U);
|
BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U);
|
||||||
|
|
||||||
BOOST_CHECK( testWallet.SelectCoinsMinConf(195 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params));
|
BOOST_CHECK( testWallet.AttemptSelection(195 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params));
|
||||||
BOOST_CHECK_EQUAL(nValueRet, 2 * COIN); // we should get 2 BTC in 1 coin
|
BOOST_CHECK_EQUAL(nValueRet, 2 * COIN); // we should get 2 BTC in 1 coin
|
||||||
BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U);
|
BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U);
|
||||||
|
|
||||||
@@ -440,14 +440,14 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test)
|
|||||||
|
|
||||||
// try making 1 * MIN_CHANGE from the 1.5 * MIN_CHANGE
|
// try making 1 * MIN_CHANGE from the 1.5 * MIN_CHANGE
|
||||||
// we'll get change smaller than MIN_CHANGE whatever happens, so can expect MIN_CHANGE exactly
|
// we'll get change smaller than MIN_CHANGE whatever happens, so can expect MIN_CHANGE exactly
|
||||||
BOOST_CHECK( testWallet.SelectCoinsMinConf(MIN_CHANGE, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params));
|
BOOST_CHECK( testWallet.AttemptSelection(MIN_CHANGE, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params));
|
||||||
BOOST_CHECK_EQUAL(nValueRet, MIN_CHANGE);
|
BOOST_CHECK_EQUAL(nValueRet, MIN_CHANGE);
|
||||||
|
|
||||||
// but if we add a bigger coin, small change is avoided
|
// but if we add a bigger coin, small change is avoided
|
||||||
add_coin(1111*MIN_CHANGE);
|
add_coin(1111*MIN_CHANGE);
|
||||||
|
|
||||||
// try making 1 from 0.1 + 0.2 + 0.3 + 0.4 + 0.5 + 1111 = 1112.5
|
// try making 1 from 0.1 + 0.2 + 0.3 + 0.4 + 0.5 + 1111 = 1112.5
|
||||||
BOOST_CHECK( testWallet.SelectCoinsMinConf(1 * MIN_CHANGE, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params));
|
BOOST_CHECK( testWallet.AttemptSelection(1 * MIN_CHANGE, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params));
|
||||||
BOOST_CHECK_EQUAL(nValueRet, 1 * MIN_CHANGE); // we should get the exact amount
|
BOOST_CHECK_EQUAL(nValueRet, 1 * MIN_CHANGE); // we should get the exact amount
|
||||||
|
|
||||||
// if we add more small coins:
|
// if we add more small coins:
|
||||||
@@ -455,7 +455,7 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test)
|
|||||||
add_coin(MIN_CHANGE * 7 / 10);
|
add_coin(MIN_CHANGE * 7 / 10);
|
||||||
|
|
||||||
// and try again to make 1.0 * MIN_CHANGE
|
// and try again to make 1.0 * MIN_CHANGE
|
||||||
BOOST_CHECK( testWallet.SelectCoinsMinConf(1 * MIN_CHANGE, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params));
|
BOOST_CHECK( testWallet.AttemptSelection(1 * MIN_CHANGE, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params));
|
||||||
BOOST_CHECK_EQUAL(nValueRet, 1 * MIN_CHANGE); // we should get the exact amount
|
BOOST_CHECK_EQUAL(nValueRet, 1 * MIN_CHANGE); // we should get the exact amount
|
||||||
|
|
||||||
// run the 'mtgox' test (see https://blockexplorer.com/tx/29a3efd3ef04f9153d47a990bd7b048a4b2d213daaa5fb8ed670fb85f13bdbcf)
|
// run the 'mtgox' test (see https://blockexplorer.com/tx/29a3efd3ef04f9153d47a990bd7b048a4b2d213daaa5fb8ed670fb85f13bdbcf)
|
||||||
@@ -464,7 +464,7 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test)
|
|||||||
for (int j = 0; j < 20; j++)
|
for (int j = 0; j < 20; j++)
|
||||||
add_coin(50000 * COIN);
|
add_coin(50000 * COIN);
|
||||||
|
|
||||||
BOOST_CHECK( testWallet.SelectCoinsMinConf(500000 * COIN, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params));
|
BOOST_CHECK( testWallet.AttemptSelection(500000 * COIN, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params));
|
||||||
BOOST_CHECK_EQUAL(nValueRet, 500000 * COIN); // we should get the exact amount
|
BOOST_CHECK_EQUAL(nValueRet, 500000 * COIN); // we should get the exact amount
|
||||||
BOOST_CHECK_EQUAL(setCoinsRet.size(), 10U); // in ten coins
|
BOOST_CHECK_EQUAL(setCoinsRet.size(), 10U); // in ten coins
|
||||||
|
|
||||||
@@ -477,7 +477,7 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test)
|
|||||||
add_coin(MIN_CHANGE * 6 / 10);
|
add_coin(MIN_CHANGE * 6 / 10);
|
||||||
add_coin(MIN_CHANGE * 7 / 10);
|
add_coin(MIN_CHANGE * 7 / 10);
|
||||||
add_coin(1111 * MIN_CHANGE);
|
add_coin(1111 * MIN_CHANGE);
|
||||||
BOOST_CHECK( testWallet.SelectCoinsMinConf(1 * MIN_CHANGE, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params));
|
BOOST_CHECK( testWallet.AttemptSelection(1 * MIN_CHANGE, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params));
|
||||||
BOOST_CHECK_EQUAL(nValueRet, 1111 * MIN_CHANGE); // we get the bigger coin
|
BOOST_CHECK_EQUAL(nValueRet, 1111 * MIN_CHANGE); // we get the bigger coin
|
||||||
BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U);
|
BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U);
|
||||||
|
|
||||||
@@ -487,7 +487,7 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test)
|
|||||||
add_coin(MIN_CHANGE * 6 / 10);
|
add_coin(MIN_CHANGE * 6 / 10);
|
||||||
add_coin(MIN_CHANGE * 8 / 10);
|
add_coin(MIN_CHANGE * 8 / 10);
|
||||||
add_coin(1111 * MIN_CHANGE);
|
add_coin(1111 * MIN_CHANGE);
|
||||||
BOOST_CHECK( testWallet.SelectCoinsMinConf(MIN_CHANGE, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params));
|
BOOST_CHECK( testWallet.AttemptSelection(MIN_CHANGE, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params));
|
||||||
BOOST_CHECK_EQUAL(nValueRet, MIN_CHANGE); // we should get the exact amount
|
BOOST_CHECK_EQUAL(nValueRet, MIN_CHANGE); // we should get the exact amount
|
||||||
BOOST_CHECK_EQUAL(setCoinsRet.size(), 2U); // in two coins 0.4+0.6
|
BOOST_CHECK_EQUAL(setCoinsRet.size(), 2U); // in two coins 0.4+0.6
|
||||||
|
|
||||||
@@ -498,12 +498,12 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test)
|
|||||||
add_coin(MIN_CHANGE * 100);
|
add_coin(MIN_CHANGE * 100);
|
||||||
|
|
||||||
// trying to make 100.01 from these three coins
|
// trying to make 100.01 from these three coins
|
||||||
BOOST_CHECK(testWallet.SelectCoinsMinConf(MIN_CHANGE * 10001 / 100, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params));
|
BOOST_CHECK(testWallet.AttemptSelection(MIN_CHANGE * 10001 / 100, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params));
|
||||||
BOOST_CHECK_EQUAL(nValueRet, MIN_CHANGE * 10105 / 100); // we should get all coins
|
BOOST_CHECK_EQUAL(nValueRet, MIN_CHANGE * 10105 / 100); // we should get all coins
|
||||||
BOOST_CHECK_EQUAL(setCoinsRet.size(), 3U);
|
BOOST_CHECK_EQUAL(setCoinsRet.size(), 3U);
|
||||||
|
|
||||||
// but if we try to make 99.9, we should take the bigger of the two small coins to avoid small change
|
// but if we try to make 99.9, we should take the bigger of the two small coins to avoid small change
|
||||||
BOOST_CHECK(testWallet.SelectCoinsMinConf(MIN_CHANGE * 9990 / 100, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params));
|
BOOST_CHECK(testWallet.AttemptSelection(MIN_CHANGE * 9990 / 100, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params));
|
||||||
BOOST_CHECK_EQUAL(nValueRet, 101 * MIN_CHANGE);
|
BOOST_CHECK_EQUAL(nValueRet, 101 * MIN_CHANGE);
|
||||||
BOOST_CHECK_EQUAL(setCoinsRet.size(), 2U);
|
BOOST_CHECK_EQUAL(setCoinsRet.size(), 2U);
|
||||||
}
|
}
|
||||||
@@ -517,7 +517,7 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test)
|
|||||||
|
|
||||||
// We only create the wallet once to save time, but we still run the coin selection RUN_TESTS times.
|
// We only create the wallet once to save time, but we still run the coin selection RUN_TESTS times.
|
||||||
for (int i = 0; i < RUN_TESTS; i++) {
|
for (int i = 0; i < RUN_TESTS; i++) {
|
||||||
BOOST_CHECK(testWallet.SelectCoinsMinConf(2000, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params));
|
BOOST_CHECK(testWallet.AttemptSelection(2000, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params));
|
||||||
|
|
||||||
if (amt - 2000 < MIN_CHANGE) {
|
if (amt - 2000 < MIN_CHANGE) {
|
||||||
// needs more than one input:
|
// needs more than one input:
|
||||||
@@ -602,7 +602,7 @@ BOOST_AUTO_TEST_CASE(ApproximateBestSubset)
|
|||||||
add_coin(1000 * COIN);
|
add_coin(1000 * COIN);
|
||||||
add_coin(3 * COIN);
|
add_coin(3 * COIN);
|
||||||
|
|
||||||
BOOST_CHECK(testWallet.SelectCoinsMinConf(1003 * COIN, filter_standard, vCoins, setCoinsRet, nValueRet, coin_selection_params));
|
BOOST_CHECK(testWallet.AttemptSelection(1003 * COIN, filter_standard, vCoins, setCoinsRet, nValueRet, coin_selection_params));
|
||||||
BOOST_CHECK_EQUAL(nValueRet, 1003 * COIN);
|
BOOST_CHECK_EQUAL(nValueRet, 1003 * COIN);
|
||||||
BOOST_CHECK_EQUAL(setCoinsRet.size(), 2U);
|
BOOST_CHECK_EQUAL(setCoinsRet.size(), 2U);
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -326,7 +326,7 @@ private:
|
|||||||
// ScriptPubKeyMan::GetID. In many cases it will be the hash of an internal structure
|
// ScriptPubKeyMan::GetID. In many cases it will be the hash of an internal structure
|
||||||
std::map<uint256, std::unique_ptr<ScriptPubKeyMan>> m_spk_managers;
|
std::map<uint256, std::unique_ptr<ScriptPubKeyMan>> m_spk_managers;
|
||||||
|
|
||||||
bool CreateTransactionInternal(const std::vector<CRecipient>& vecSend, CTransactionRef& tx, CAmount& nFeeRet, int& nChangePosInOut, bilingual_str& error, const CCoinControl& coin_control, FeeCalculation& fee_calc_out, bool sign);
|
bool CreateTransactionInternal(const std::vector<CRecipient>& vecSend, CTransactionRef& tx, CAmount& nFeeRet, int& nChangePosInOut, bilingual_str& error, const CCoinControl& coin_control, FeeCalculation& fee_calc_out, bool sign) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Catch wallet up to current chain, scanning new blocks, updating the best
|
* Catch wallet up to current chain, scanning new blocks, updating the best
|
||||||
@@ -445,7 +445,7 @@ public:
|
|||||||
* param@[out] setCoinsRet Populated with the coins selected if successful.
|
* param@[out] setCoinsRet Populated with the coins selected if successful.
|
||||||
* param@[out] nValueRet Used to return the total value of selected coins.
|
* param@[out] nValueRet Used to return the total value of selected coins.
|
||||||
*/
|
*/
|
||||||
bool SelectCoinsMinConf(const CAmount& nTargetValue, const CoinEligibilityFilter& eligibility_filter, std::vector<COutput> coins,
|
bool AttemptSelection(const CAmount& nTargetValue, const CoinEligibilityFilter& eligibility_filter, std::vector<COutput> coins,
|
||||||
std::set<CInputCoin>& setCoinsRet, CAmount& nValueRet, const CoinSelectionParams& coin_selection_params) const;
|
std::set<CInputCoin>& setCoinsRet, CAmount& nValueRet, const CoinSelectionParams& coin_selection_params) const;
|
||||||
|
|
||||||
bool IsSpent(const uint256& hash, unsigned int n) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
bool IsSpent(const uint256& hash, unsigned int n) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
||||||
|
|||||||
Reference in new issue
Block a user