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
+293
-302
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);
|
||||||
|
|||||||
+256
-265
@@ -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,287 +578,266 @@ bool CWallet::CreateTransactionInternal(
|
|||||||
FeeCalculation& fee_calc_out,
|
FeeCalculation& fee_calc_out,
|
||||||
bool sign)
|
bool sign)
|
||||||
{
|
{
|
||||||
CAmount nValue = 0;
|
AssertLockHeld(cs_wallet);
|
||||||
|
|
||||||
|
CMutableTransaction txNew; // The resulting transaction that we make
|
||||||
|
txNew.nLockTime = GetLocktimeForNewTransaction(chain(), GetLastBlockHash(), GetLastBlockHeight());
|
||||||
|
|
||||||
|
CoinSelectionParams coin_selection_params; // Parameters for coin selection, init with dummy
|
||||||
|
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);
|
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);
|
ReserveDestination reservedest(this, change_type);
|
||||||
unsigned int nSubtractFeeFromAmount = 0;
|
unsigned int outputs_to_subtract_fee_from = 0; // The number of outputs which we are subtracting the fee from
|
||||||
for (const auto& recipient : vecSend)
|
for (const auto& recipient : vecSend) {
|
||||||
{
|
recipients_sum += recipient.nAmount;
|
||||||
if (nValue < 0 || recipient.nAmount < 0)
|
|
||||||
{
|
|
||||||
error = _("Transaction amounts must not be negative");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
nValue += recipient.nAmount;
|
|
||||||
|
|
||||||
if (recipient.fSubtractFeeFromAmount)
|
if (recipient.fSubtractFeeFromAmount) {
|
||||||
nSubtractFeeFromAmount++;
|
outputs_to_subtract_fee_from++;
|
||||||
|
coin_selection_params.m_subtract_fee_outputs = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (vecSend.empty())
|
|
||||||
{
|
// Create change script that will be used if we need change
|
||||||
error = _("Transaction must have at least one recipient");
|
// TODO: pass in scriptChange instead of reservedest so
|
||||||
|
// change transaction isn't always pay-to-bitcoin-address
|
||||||
|
CScript scriptChange;
|
||||||
|
|
||||||
|
// coin control: send change to custom address
|
||||||
|
if (!std::get_if<CNoDestination>(&coin_control.destChange)) {
|
||||||
|
scriptChange = GetScriptForDestination(coin_control.destChange);
|
||||||
|
} else { // no coin control: send change to newly generated address
|
||||||
|
// 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. If it fails, provide a dummy
|
||||||
|
// destination in case we don't need change.
|
||||||
|
CTxDestination dest;
|
||||||
|
if (!reservedest.GetReservedDestination(dest, true)) {
|
||||||
|
error = _("Transaction needs a change address, but we can't generate it. Please call keypoolrefill first.");
|
||||||
|
}
|
||||||
|
scriptChange = GetScriptForDestination(dest);
|
||||||
|
// A valid destination implies a change script (and
|
||||||
|
// vice-versa). An empty change script will abort later, if the
|
||||||
|
// change keypool ran out, but change is required.
|
||||||
|
CHECK_NONFATAL(IsValidDestination(dest) != scriptChange.empty());
|
||||||
|
}
|
||||||
|
CTxOut change_prototype_txout(0, scriptChange);
|
||||||
|
coin_selection_params.change_output_size = GetSerializeSize(change_prototype_txout);
|
||||||
|
|
||||||
|
// Get size of spending the change output
|
||||||
|
int change_spend_size = CalculateMaximumSignedInputSize(change_prototype_txout, this);
|
||||||
|
// If the wallet doesn't know how to sign change output, assume p2sh-p2wpkh
|
||||||
|
// as lower-bound to allow BnB to do it's thing
|
||||||
|
if (change_spend_size == -1) {
|
||||||
|
coin_selection_params.change_spend_size = DUMMY_NESTED_P2WPKH_INPUT_SIZE;
|
||||||
|
} else {
|
||||||
|
coin_selection_params.change_spend_size = (size_t)change_spend_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set discard feerate
|
||||||
|
coin_selection_params.m_discard_feerate = GetDiscardRate(*this);
|
||||||
|
|
||||||
|
// Get the fee rate to use effective values in coin selection
|
||||||
|
FeeCalculation 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
|
||||||
|
// provided one
|
||||||
|
if (coin_control.m_feerate && coin_selection_params.m_effective_feerate > *coin_control.m_feerate) {
|
||||||
|
error = strprintf(_("Fee rate (%s) is lower than the minimum fee rate setting (%s)"), coin_control.m_feerate->ToString(FeeEstimateMode::SAT_VB), coin_selection_params.m_effective_feerate.ToString(FeeEstimateMode::SAT_VB));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (feeCalc.reason == FeeReason::FALLBACK && !m_allow_fallback_fee) {
|
||||||
|
// eventually allow a fallback fee
|
||||||
|
error = _("Fee estimation failed. Fallbackfee is disabled. Wait a few blocks or enable -fallbackfee.");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
CMutableTransaction txNew;
|
// Get long term estimate
|
||||||
FeeCalculation feeCalc;
|
CCoinControl cc_temp;
|
||||||
TxSize tx_sizes;
|
cc_temp.m_confirm_target = chain().estimateMaxBlocks();
|
||||||
int nBytes;
|
coin_selection_params.m_long_term_feerate = GetMinimumFeeRate(*this, cc_temp, nullptr);
|
||||||
|
|
||||||
|
// Calculate the cost of change
|
||||||
|
// Cost of change is the cost of creating the change output + cost of spending the change output in the future.
|
||||||
|
// For creating the change output now, we use the effective feerate.
|
||||||
|
// For spending the change output in the future, we use the discard feerate for now.
|
||||||
|
// So cost of change = (change output size * effective feerate) + (size of spending change output * discard feerate)
|
||||||
|
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;
|
||||||
|
|
||||||
|
// vouts to the payees
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
for (const auto& recipient : vecSend)
|
||||||
{
|
{
|
||||||
std::set<CInputCoin> setCoins;
|
CTxOut txout(recipient.nAmount, recipient.scriptPubKey);
|
||||||
LOCK(cs_wallet);
|
|
||||||
txNew.nLockTime = GetLocktimeForNewTransaction(chain(), GetLastBlockHash(), GetLastBlockHeight());
|
// Include the fee cost for outputs.
|
||||||
|
if (!coin_selection_params.m_subtract_fee_outputs) {
|
||||||
|
coin_selection_params.tx_noinputs_size += ::GetSerializeSize(txout, PROTOCOL_VERSION);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsDust(txout, chain().relayDustFee()))
|
||||||
{
|
{
|
||||||
std::vector<COutput> vAvailableCoins;
|
error = _("Transaction amount too small");
|
||||||
AvailableCoins(vAvailableCoins, &coin_control, 1, MAX_MONEY, MAX_MONEY, 0);
|
return false;
|
||||||
CoinSelectionParams coin_selection_params; // Parameters for coin selection, init with dummy
|
}
|
||||||
coin_selection_params.m_avoid_partial_spends = coin_control.m_avoid_partial_spends;
|
txNew.vout.push_back(txout);
|
||||||
|
}
|
||||||
|
|
||||||
// Create change script that will be used if we need change
|
// Include the fees for things that aren't inputs, excluding the change output
|
||||||
// TODO: pass in scriptChange instead of reservedest so
|
const CAmount not_input_fees = coin_selection_params.m_effective_feerate.GetFee(coin_selection_params.tx_noinputs_size);
|
||||||
// change transaction isn't always pay-to-bitcoin-address
|
CAmount selection_target = recipients_sum + not_input_fees;
|
||||||
CScript scriptChange;
|
|
||||||
|
|
||||||
// coin control: send change to custom address
|
// Get available coins
|
||||||
if (!std::get_if<CNoDestination>(&coin_control.destChange)) {
|
std::vector<COutput> vAvailableCoins;
|
||||||
scriptChange = GetScriptForDestination(coin_control.destChange);
|
AvailableCoins(vAvailableCoins, &coin_control, 1, MAX_MONEY, MAX_MONEY, 0);
|
||||||
} else { // no coin control: send change to newly generated address
|
|
||||||
// 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. If it fails, provide a dummy
|
// Choose coins to use
|
||||||
// destination in case we don't need change.
|
CAmount inputs_sum = 0;
|
||||||
CTxDestination dest;
|
std::set<CInputCoin> setCoins;
|
||||||
if (!reservedest.GetReservedDestination(dest, true)) {
|
if (!SelectCoins(vAvailableCoins, /* nTargetValue */ selection_target, setCoins, inputs_sum, coin_control, coin_selection_params))
|
||||||
error = _("Transaction needs a change address, but we can't generate it. Please call keypoolrefill first.");
|
{
|
||||||
}
|
error = _("Insufficient funds");
|
||||||
scriptChange = GetScriptForDestination(dest);
|
return false;
|
||||||
// A valid destination implies a change script (and
|
}
|
||||||
// vice-versa). An empty change script will abort later, if the
|
|
||||||
// change keypool ran out, but change is required.
|
// Always make a change output
|
||||||
CHECK_NONFATAL(IsValidDestination(dest) != scriptChange.empty());
|
// 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 - recipients_sum;
|
||||||
|
assert(change_and_fee >= 0);
|
||||||
|
CTxOut newTxOut(change_and_fee, scriptChange);
|
||||||
|
|
||||||
|
if (nChangePosInOut == -1)
|
||||||
|
{
|
||||||
|
// Insert change txn at random position:
|
||||||
|
nChangePosInOut = GetRandInt(txNew.vout.size()+1);
|
||||||
|
}
|
||||||
|
else if ((unsigned int)nChangePosInOut > txNew.vout.size())
|
||||||
|
{
|
||||||
|
error = _("Change index out of range");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(nChangePosInOut != -1);
|
||||||
|
auto change_position = txNew.vout.insert(txNew.vout.begin() + nChangePosInOut, newTxOut);
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
//
|
||||||
|
// 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));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate the transaction fee
|
||||||
|
TxSize tx_sizes = CalculateMaximumSignedTxSize(CTransaction(txNew), this, coin_control.fAllowWatchOnly);
|
||||||
|
int nBytes = tx_sizes.vsize;
|
||||||
|
if (nBytes < 0) {
|
||||||
|
error = _("Signing transaction failed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
nFeeRet = coin_selection_params.m_effective_feerate.GetFee(nBytes);
|
||||||
|
|
||||||
|
// Subtract fee from the change output if not subtracting it from recipient outputs
|
||||||
|
CAmount fee_needed = nFeeRet;
|
||||||
|
if (!coin_selection_params.m_subtract_fee_outputs) {
|
||||||
|
change_position->nValue -= fee_needed;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We want to drop the change to fees if:
|
||||||
|
// 1. The change output would be dust
|
||||||
|
// 2. The change is within the (almost) exact match window, i.e. it is less than or equal to the cost of the change output (cost_of_change)
|
||||||
|
CAmount change_amount = change_position->nValue;
|
||||||
|
if (IsDust(*change_position, coin_selection_params.m_discard_feerate) || change_amount <= coin_selection_params.m_cost_of_change)
|
||||||
|
{
|
||||||
|
nChangePosInOut = -1;
|
||||||
|
change_amount = 0;
|
||||||
|
txNew.vout.erase(change_position);
|
||||||
|
|
||||||
|
// Because we have dropped this change, the tx size and required fee will be different, so let's recalculate those
|
||||||
|
tx_sizes = CalculateMaximumSignedTxSize(CTransaction(txNew), this, coin_control.fAllowWatchOnly);
|
||||||
|
nBytes = tx_sizes.vsize;
|
||||||
|
fee_needed = coin_selection_params.m_effective_feerate.GetFee(nBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update nFeeRet in case fee_needed changed due to dropping the change output
|
||||||
|
if (fee_needed <= change_and_fee - change_amount) {
|
||||||
|
nFeeRet = change_and_fee - change_amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reduce output values for subtractFeeFromAmount
|
||||||
|
if (coin_selection_params.m_subtract_fee_outputs) {
|
||||||
|
CAmount to_reduce = fee_needed + change_amount - change_and_fee;
|
||||||
|
int i = 0;
|
||||||
|
bool fFirst = true;
|
||||||
|
for (const auto& recipient : vecSend)
|
||||||
|
{
|
||||||
|
if (i == nChangePosInOut) {
|
||||||
|
++i;
|
||||||
}
|
}
|
||||||
CTxOut change_prototype_txout(0, scriptChange);
|
CTxOut& txout = txNew.vout[i];
|
||||||
coin_selection_params.change_output_size = GetSerializeSize(change_prototype_txout);
|
|
||||||
|
|
||||||
// Get size of spending the change output
|
if (recipient.fSubtractFeeFromAmount)
|
||||||
int change_spend_size = CalculateMaximumSignedInputSize(change_prototype_txout, this);
|
|
||||||
// If the wallet doesn't know how to sign change output, assume p2sh-p2wpkh
|
|
||||||
// as lower-bound to allow BnB to do it's thing
|
|
||||||
if (change_spend_size == -1) {
|
|
||||||
coin_selection_params.change_spend_size = DUMMY_NESTED_P2WPKH_INPUT_SIZE;
|
|
||||||
} else {
|
|
||||||
coin_selection_params.change_spend_size = (size_t)change_spend_size;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set discard feerate
|
|
||||||
coin_selection_params.m_discard_feerate = GetDiscardRate(*this);
|
|
||||||
|
|
||||||
// Get the fee rate to use effective values in coin selection
|
|
||||||
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
|
|
||||||
// provided one
|
|
||||||
if (coin_control.m_feerate && coin_selection_params.m_effective_feerate > *coin_control.m_feerate) {
|
|
||||||
error = strprintf(_("Fee rate (%s) is lower than the minimum fee rate setting (%s)"), coin_control.m_feerate->ToString(FeeEstimateMode::SAT_VB), coin_selection_params.m_effective_feerate.ToString(FeeEstimateMode::SAT_VB));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (feeCalc.reason == FeeReason::FALLBACK && !m_allow_fallback_fee) {
|
|
||||||
// eventually allow a fallback fee
|
|
||||||
error = _("Fee estimation failed. Fallbackfee is disabled. Wait a few blocks or enable -fallbackfee.");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get long term estimate
|
|
||||||
CCoinControl cc_temp;
|
|
||||||
cc_temp.m_confirm_target = chain().estimateMaxBlocks();
|
|
||||||
coin_selection_params.m_long_term_feerate = GetMinimumFeeRate(*this, cc_temp, nullptr);
|
|
||||||
|
|
||||||
// Calculate the cost of change
|
|
||||||
// Cost of change is the cost of creating the change output + cost of spending the change output in the future.
|
|
||||||
// For creating the change output now, we use the effective feerate.
|
|
||||||
// For spending the change output in the future, we use the discard feerate for now.
|
|
||||||
// So cost of change = (change output size * effective feerate) + (size of spending change output * discard feerate)
|
|
||||||
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_subtract_fee_outputs = nSubtractFeeFromAmount != 0; // If we are doing subtract fee from recipient, don't use effective values
|
|
||||||
|
|
||||||
// vouts to the payees
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
for (const auto& recipient : vecSend)
|
|
||||||
{
|
{
|
||||||
CTxOut txout(recipient.nAmount, recipient.scriptPubKey);
|
txout.nValue -= to_reduce / outputs_to_subtract_fee_from; // Subtract fee equally from each selected recipient
|
||||||
|
|
||||||
// Include the fee cost for outputs.
|
if (fFirst) // first receiver pays the remainder not divisible by output count
|
||||||
if (!coin_selection_params.m_subtract_fee_outputs) {
|
{
|
||||||
coin_selection_params.tx_noinputs_size += ::GetSerializeSize(txout, PROTOCOL_VERSION);
|
fFirst = false;
|
||||||
|
txout.nValue -= to_reduce % outputs_to_subtract_fee_from;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (IsDust(txout, chain().relayDustFee()))
|
// Error if this output is reduced to be below dust
|
||||||
{
|
if (IsDust(txout, chain().relayDustFee())) {
|
||||||
error = _("Transaction amount too small");
|
if (txout.nValue < 0) {
|
||||||
|
error = _("The transaction amount is too small to pay the fee");
|
||||||
|
} else {
|
||||||
|
error = _("The transaction amount is too small to send after the fee has been deducted");
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
txNew.vout.push_back(txout);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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);
|
|
||||||
CAmount nValueToSelect = nValue + not_input_fees;
|
|
||||||
|
|
||||||
// Choose coins to use
|
|
||||||
CAmount inputs_sum = 0;
|
|
||||||
setCoins.clear();
|
|
||||||
if (!SelectCoins(vAvailableCoins, /* nTargetValue */ nValueToSelect, setCoins, inputs_sum, coin_control, coin_selection_params))
|
|
||||||
{
|
|
||||||
error = _("Insufficient funds");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Always make a change output
|
|
||||||
// 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;
|
|
||||||
assert(change_and_fee >= 0);
|
|
||||||
CTxOut newTxOut(change_and_fee, scriptChange);
|
|
||||||
|
|
||||||
if (nChangePosInOut == -1)
|
|
||||||
{
|
|
||||||
// Insert change txn at random position:
|
|
||||||
nChangePosInOut = GetRandInt(txNew.vout.size()+1);
|
|
||||||
}
|
|
||||||
else if ((unsigned int)nChangePosInOut > txNew.vout.size())
|
|
||||||
{
|
|
||||||
error = _("Change index out of range");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
assert(nChangePosInOut != -1);
|
|
||||||
auto change_position = txNew.vout.insert(txNew.vout.begin() + nChangePosInOut, newTxOut);
|
|
||||||
|
|
||||||
// Dummy fill vin for maximum size estimation
|
|
||||||
//
|
|
||||||
for (const auto& coin : setCoins) {
|
|
||||||
txNew.vin.push_back(CTxIn(coin.outpoint,CScript()));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate the transaction fee
|
|
||||||
tx_sizes = CalculateMaximumSignedTxSize(CTransaction(txNew), this, coin_control.fAllowWatchOnly);
|
|
||||||
nBytes = tx_sizes.vsize;
|
|
||||||
if (nBytes < 0) {
|
|
||||||
error = _("Signing transaction failed");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
nFeeRet = coin_selection_params.m_effective_feerate.GetFee(nBytes);
|
|
||||||
|
|
||||||
// Subtract fee from the change output if not subtrating it from recipient outputs
|
|
||||||
CAmount fee_needed = nFeeRet;
|
|
||||||
if (nSubtractFeeFromAmount == 0) {
|
|
||||||
change_position->nValue -= fee_needed;
|
|
||||||
}
|
|
||||||
|
|
||||||
// We want to drop the change to fees if:
|
|
||||||
// 1. The change output would be dust
|
|
||||||
// 2. The change is within the (almost) exact match window, i.e. it is less than or equal to the cost of the change output (cost_of_change)
|
|
||||||
CAmount change_amount = change_position->nValue;
|
|
||||||
if (IsDust(*change_position, coin_selection_params.m_discard_feerate) || change_amount <= coin_selection_params.m_cost_of_change)
|
|
||||||
{
|
|
||||||
nChangePosInOut = -1;
|
|
||||||
change_amount = 0;
|
|
||||||
txNew.vout.erase(change_position);
|
|
||||||
|
|
||||||
// Because we have dropped this change, the tx size and required fee will be different, so let's recalculate those
|
|
||||||
tx_sizes = CalculateMaximumSignedTxSize(CTransaction(txNew), this, coin_control.fAllowWatchOnly);
|
|
||||||
nBytes = tx_sizes.vsize;
|
|
||||||
fee_needed = coin_selection_params.m_effective_feerate.GetFee(nBytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update nFeeRet in case fee_needed changed due to dropping the change output
|
|
||||||
if (fee_needed <= change_and_fee - change_amount) {
|
|
||||||
nFeeRet = change_and_fee - change_amount;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reduce output values for subtractFeeFromAmount
|
|
||||||
if (nSubtractFeeFromAmount != 0) {
|
|
||||||
CAmount to_reduce = fee_needed + change_amount - change_and_fee;
|
|
||||||
int i = 0;
|
|
||||||
bool fFirst = true;
|
|
||||||
for (const auto& recipient : vecSend)
|
|
||||||
{
|
|
||||||
if (i == nChangePosInOut) {
|
|
||||||
++i;
|
|
||||||
}
|
|
||||||
CTxOut& txout = txNew.vout[i];
|
|
||||||
|
|
||||||
if (recipient.fSubtractFeeFromAmount)
|
|
||||||
{
|
|
||||||
txout.nValue -= to_reduce / nSubtractFeeFromAmount; // Subtract fee equally from each selected recipient
|
|
||||||
|
|
||||||
if (fFirst) // first receiver pays the remainder not divisible by output count
|
|
||||||
{
|
|
||||||
fFirst = false;
|
|
||||||
txout.nValue -= to_reduce % nSubtractFeeFromAmount;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Error if this output is reduced to be below dust
|
|
||||||
if (IsDust(txout, chain().relayDustFee())) {
|
|
||||||
if (txout.nValue < 0) {
|
|
||||||
error = _("The transaction amount is too small to pay the fee");
|
|
||||||
} else {
|
|
||||||
error = _("The transaction amount is too small to send after the fee has been deducted");
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
++i;
|
|
||||||
}
|
|
||||||
nFeeRet = fee_needed;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Give up if change keypool ran out and change is required
|
|
||||||
if (scriptChange.empty() && nChangePosInOut != -1) {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
++i;
|
||||||
}
|
}
|
||||||
|
nFeeRet = fee_needed;
|
||||||
|
}
|
||||||
|
|
||||||
// Shuffle selected coins and fill in final vin
|
// Give up if change keypool ran out and change is required
|
||||||
txNew.vin.clear();
|
if (scriptChange.empty() && nChangePosInOut != -1) {
|
||||||
std::vector<CInputCoin> selected_coins(setCoins.begin(), setCoins.end());
|
return false;
|
||||||
Shuffle(selected_coins.begin(), selected_coins.end(), FastRandomContext());
|
}
|
||||||
|
|
||||||
// Note how the sequence number is set to non-maxint so that
|
if (sign && !SignTransaction(txNew)) {
|
||||||
// the nLockTime set above actually works.
|
error = _("Signing transaction failed");
|
||||||
//
|
return false;
|
||||||
// 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)) {
|
// Return the constructed transaction data.
|
||||||
error = _("Signing transaction failed");
|
tx = MakeTransactionRef(std::move(txNew));
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return the constructed transaction data.
|
// Limit size
|
||||||
tx = MakeTransactionRef(std::move(txNew));
|
if ((sign && GetTransactionWeight(*tx) > MAX_STANDARD_TX_WEIGHT) ||
|
||||||
|
(!sign && tx_sizes.weight > MAX_STANDARD_TX_WEIGHT))
|
||||||
// Limit size
|
{
|
||||||
if ((sign && GetTransactionWeight(*tx) > MAX_STANDARD_TX_WEIGHT) ||
|
error = _("Transaction too large");
|
||||||
(!sign && tx_sizes.weight > MAX_STANDARD_TX_WEIGHT))
|
return false;
|
||||||
{
|
|
||||||
error = _("Transaction too large");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nFeeRet > m_default_max_tx_fee) {
|
if (nFeeRet > m_default_max_tx_fee) {
|
||||||
@@ -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