mirror of
https://github.com/bitcoin/bitcoin.git
synced 2024-11-19 18:09:47 +01:00
refactor: use CoinsResult struct in SelectCoins
Pass the whole CoinsResult struct to SelectCoins instead of only a vector. This means we now have to remove preselected coins from each OutputType vector and shuffle each vector individually. Pass the whole CoinsResult struct to AttemptSelection. This involves moving the logic in AttemptSelection to a newly named function, ChooseSelectionResult. This will allow us to run ChooseSelectionResult over each OutputType in a later commit. This ensures the backoffs work properly. Update unit and bench tests to use CoinResult.
This commit is contained in:
parent
2e67291ca3
commit
77b0707206
@ -56,10 +56,10 @@ static void CoinSelection(benchmark::Bench& bench)
|
||||
addCoin(3 * COIN, wallet, wtxs);
|
||||
|
||||
// Create coins
|
||||
std::vector<COutput> coins;
|
||||
wallet::CoinsResult available_coins;
|
||||
for (const auto& wtx : wtxs) {
|
||||
const auto txout = wtx->tx->vout.at(0);
|
||||
coins.emplace_back(COutPoint(wtx->GetHash(), 0), txout, /*depth=*/6 * 24, CalculateMaximumSignedInputSize(txout, &wallet, /*coin_control=*/nullptr), /*spendable=*/true, /*solvable=*/true, /*safe=*/true, wtx->GetTxTime(), /*from_me=*/true, /*fees=*/ 0);
|
||||
available_coins.bech32.emplace_back(COutPoint(wtx->GetHash(), 0), txout, /*depth=*/6 * 24, CalculateMaximumSignedInputSize(txout, &wallet, /*coin_control=*/nullptr), /*spendable=*/true, /*solvable=*/true, /*safe=*/true, wtx->GetTxTime(), /*from_me=*/true, /*fees=*/ 0);
|
||||
}
|
||||
|
||||
const CoinEligibilityFilter filter_standard(1, 6, 0);
|
||||
@ -76,7 +76,7 @@ static void CoinSelection(benchmark::Bench& bench)
|
||||
/*avoid_partial=*/ false,
|
||||
};
|
||||
bench.run([&] {
|
||||
auto result = AttemptSelection(wallet, 1003 * COIN, filter_standard, coins, coin_selection_params);
|
||||
auto result = AttemptSelection(wallet, 1003 * COIN, filter_standard, available_coins, coin_selection_params);
|
||||
assert(result);
|
||||
assert(result->GetSelectedValue() == 1003 * COIN);
|
||||
assert(result->GetInputSet().size() == 2);
|
||||
|
@ -450,20 +450,26 @@ std::vector<OutputGroup> GroupOutputs(const CWallet& wallet, const std::vector<C
|
||||
return groups_out;
|
||||
}
|
||||
|
||||
std::optional<SelectionResult> AttemptSelection(const CWallet& wallet, const CAmount& nTargetValue, const CoinEligibilityFilter& eligibility_filter, std::vector<COutput> coins,
|
||||
std::optional<SelectionResult> AttemptSelection(const CWallet& wallet, const CAmount& nTargetValue, const CoinEligibilityFilter& eligibility_filter, const CoinsResult& available_coins,
|
||||
const CoinSelectionParams& coin_selection_params)
|
||||
{
|
||||
std::optional<SelectionResult> result = ChooseSelectionResult(wallet, nTargetValue, eligibility_filter, available_coins.all(), coin_selection_params);
|
||||
return result;
|
||||
};
|
||||
|
||||
std::optional<SelectionResult> ChooseSelectionResult(const CWallet& wallet, const CAmount& nTargetValue, const CoinEligibilityFilter& eligibility_filter, const std::vector<COutput>& available_coins, const CoinSelectionParams& coin_selection_params)
|
||||
{
|
||||
// Vector of results. We will choose the best one based on waste.
|
||||
std::vector<SelectionResult> results;
|
||||
|
||||
// Note that unlike KnapsackSolver, we do not include the fee for creating a change output as BnB will not create a change output.
|
||||
std::vector<OutputGroup> positive_groups = GroupOutputs(wallet, coins, coin_selection_params, eligibility_filter, true /* positive_only */);
|
||||
std::vector<OutputGroup> positive_groups = GroupOutputs(wallet, available_coins, coin_selection_params, eligibility_filter, true /* positive_only */);
|
||||
if (auto bnb_result{SelectCoinsBnB(positive_groups, nTargetValue, coin_selection_params.m_cost_of_change)}) {
|
||||
results.push_back(*bnb_result);
|
||||
}
|
||||
|
||||
// The knapsack solver has some legacy behavior where it will spend dust outputs. We retain this behavior, so don't filter for positive only here.
|
||||
std::vector<OutputGroup> all_groups = GroupOutputs(wallet, coins, coin_selection_params, eligibility_filter, false /* positive_only */);
|
||||
std::vector<OutputGroup> all_groups = GroupOutputs(wallet, available_coins, coin_selection_params, eligibility_filter, false /* positive_only */);
|
||||
CAmount target_with_change = nTargetValue;
|
||||
// While nTargetValue includes the transaction fees for non-input things, it does not include the fee for creating a change output.
|
||||
// So we need to include that for KnapsackSolver and SRD as well, as we are expecting to create a change output.
|
||||
@ -496,9 +502,8 @@ std::optional<SelectionResult> AttemptSelection(const CWallet& wallet, const CAm
|
||||
return best_result;
|
||||
}
|
||||
|
||||
std::optional<SelectionResult> SelectCoins(const CWallet& wallet, const std::vector<COutput>& vAvailableCoins, const CAmount& nTargetValue, const CCoinControl& coin_control, const CoinSelectionParams& coin_selection_params)
|
||||
std::optional<SelectionResult> SelectCoins(const CWallet& wallet, CoinsResult& available_coins, const CAmount& nTargetValue, const CCoinControl& coin_control, const CoinSelectionParams& coin_selection_params)
|
||||
{
|
||||
std::vector<COutput> vCoins(vAvailableCoins);
|
||||
CAmount value_to_select = nTargetValue;
|
||||
|
||||
OutputGroup preset_inputs(coin_selection_params);
|
||||
@ -558,13 +563,13 @@ std::optional<SelectionResult> SelectCoins(const CWallet& wallet, const std::vec
|
||||
return result;
|
||||
}
|
||||
|
||||
// remove preset inputs from vCoins so that Coin Selection doesn't pick them.
|
||||
for (std::vector<COutput>::iterator it = vCoins.begin(); it != vCoins.end() && coin_control.HasSelected();)
|
||||
{
|
||||
if (preset_coins.count(it->outpoint))
|
||||
it = vCoins.erase(it);
|
||||
else
|
||||
++it;
|
||||
// remove preset inputs from coins so that Coin Selection doesn't pick them.
|
||||
if (coin_control.HasSelected()) {
|
||||
available_coins.legacy.erase(remove_if(available_coins.legacy.begin(), available_coins.legacy.end(), [&](const COutput& c) { return preset_coins.count(c.outpoint); }), available_coins.legacy.end());
|
||||
available_coins.P2SH_segwit.erase(remove_if(available_coins.P2SH_segwit.begin(), available_coins.P2SH_segwit.end(), [&](const COutput& c) { return preset_coins.count(c.outpoint); }), available_coins.P2SH_segwit.end());
|
||||
available_coins.bech32.erase(remove_if(available_coins.bech32.begin(), available_coins.bech32.end(), [&](const COutput& c) { return preset_coins.count(c.outpoint); }), available_coins.bech32.end());
|
||||
available_coins.bech32m.erase(remove_if(available_coins.bech32m.begin(), available_coins.bech32m.end(), [&](const COutput& c) { return preset_coins.count(c.outpoint); }), available_coins.bech32m.end());
|
||||
available_coins.other.erase(remove_if(available_coins.other.begin(), available_coins.other.end(), [&](const COutput& c) { return preset_coins.count(c.outpoint); }), available_coins.other.end());
|
||||
}
|
||||
|
||||
unsigned int limit_ancestor_count = 0;
|
||||
@ -576,11 +581,15 @@ std::optional<SelectionResult> SelectCoins(const CWallet& wallet, const std::vec
|
||||
|
||||
// form groups from remaining coins; note that preset coins will not
|
||||
// automatically have their associated (same address) coins included
|
||||
if (coin_control.m_avoid_partial_spends && vCoins.size() > OUTPUT_GROUP_MAX_ENTRIES) {
|
||||
if (coin_control.m_avoid_partial_spends && available_coins.size() > OUTPUT_GROUP_MAX_ENTRIES) {
|
||||
// Cases where we have 101+ outputs all pointing to the same destination may result in
|
||||
// privacy leaks as they will potentially be deterministically sorted. We solve that by
|
||||
// explicitly shuffling the outputs before processing
|
||||
Shuffle(vCoins.begin(), vCoins.end(), coin_selection_params.rng_fast);
|
||||
Shuffle(available_coins.legacy.begin(), available_coins.legacy.end(), coin_selection_params.rng_fast);
|
||||
Shuffle(available_coins.P2SH_segwit.begin(), available_coins.P2SH_segwit.end(), coin_selection_params.rng_fast);
|
||||
Shuffle(available_coins.bech32.begin(), available_coins.bech32.end(), coin_selection_params.rng_fast);
|
||||
Shuffle(available_coins.bech32m.begin(), available_coins.bech32m.end(), coin_selection_params.rng_fast);
|
||||
Shuffle(available_coins.other.begin(), available_coins.other.end(), coin_selection_params.rng_fast);
|
||||
}
|
||||
|
||||
// Coin Selection attempts to select inputs from a pool of eligible UTXOs to fund the
|
||||
@ -592,26 +601,26 @@ std::optional<SelectionResult> SelectCoins(const CWallet& wallet, const std::vec
|
||||
|
||||
// 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.
|
||||
if (auto r1{AttemptSelection(wallet, value_to_select, CoinEligibilityFilter(1, 6, 0), vCoins, coin_selection_params)}) return r1;
|
||||
if (auto r2{AttemptSelection(wallet, value_to_select, CoinEligibilityFilter(1, 1, 0), vCoins, coin_selection_params)}) return r2;
|
||||
if (auto r1{AttemptSelection(wallet, value_to_select, CoinEligibilityFilter(1, 6, 0), available_coins, coin_selection_params)}) return r1;
|
||||
if (auto r2{AttemptSelection(wallet, value_to_select, CoinEligibilityFilter(1, 1, 0), available_coins, coin_selection_params)}) return r2;
|
||||
|
||||
// Fall back to using zero confirmation change (but with as few ancestors in the mempool as
|
||||
// possible) if we cannot fund the transaction otherwise.
|
||||
if (wallet.m_spend_zero_conf_change) {
|
||||
if (auto r3{AttemptSelection(wallet, value_to_select, CoinEligibilityFilter(0, 1, 2), vCoins, coin_selection_params)}) return r3;
|
||||
if (auto r3{AttemptSelection(wallet, value_to_select, CoinEligibilityFilter(0, 1, 2), available_coins, coin_selection_params)}) return r3;
|
||||
if (auto r4{AttemptSelection(wallet, value_to_select, CoinEligibilityFilter(0, 1, std::min((size_t)4, max_ancestors/3), std::min((size_t)4, max_descendants/3)),
|
||||
vCoins, coin_selection_params)}) {
|
||||
available_coins, coin_selection_params)}) {
|
||||
return r4;
|
||||
}
|
||||
if (auto r5{AttemptSelection(wallet, value_to_select, CoinEligibilityFilter(0, 1, max_ancestors/2, max_descendants/2),
|
||||
vCoins, coin_selection_params)}) {
|
||||
available_coins, coin_selection_params)}) {
|
||||
return r5;
|
||||
}
|
||||
// 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)
|
||||
// in their entirety.
|
||||
if (auto r6{AttemptSelection(wallet, value_to_select, CoinEligibilityFilter(0, 1, max_ancestors-1, max_descendants-1, true /* include_partial_groups */),
|
||||
vCoins, coin_selection_params)}) {
|
||||
available_coins, coin_selection_params)}) {
|
||||
return r6;
|
||||
}
|
||||
// Try with unsafe inputs if they are allowed. This may spend unconfirmed outputs
|
||||
@ -619,7 +628,7 @@ std::optional<SelectionResult> SelectCoins(const CWallet& wallet, const std::vec
|
||||
if (coin_control.m_include_unsafe_inputs) {
|
||||
if (auto r7{AttemptSelection(wallet, value_to_select,
|
||||
CoinEligibilityFilter(0 /* conf_mine */, 0 /* conf_theirs */, max_ancestors-1, max_descendants-1, true /* include_partial_groups */),
|
||||
vCoins, coin_selection_params)}) {
|
||||
available_coins, coin_selection_params)}) {
|
||||
return r7;
|
||||
}
|
||||
}
|
||||
@ -629,7 +638,7 @@ std::optional<SelectionResult> SelectCoins(const CWallet& wallet, const std::vec
|
||||
if (!fRejectLongChains) {
|
||||
if (auto r8{AttemptSelection(wallet, value_to_select,
|
||||
CoinEligibilityFilter(0, 1, std::numeric_limits<uint64_t>::max(), std::numeric_limits<uint64_t>::max(), true /* include_partial_groups */),
|
||||
vCoins, coin_selection_params)}) {
|
||||
available_coins, coin_selection_params)}) {
|
||||
return r8;
|
||||
}
|
||||
}
|
||||
@ -849,7 +858,7 @@ static BResult<CreatedTransactionResult> CreateTransactionInternal(
|
||||
CAmount selection_target = recipients_sum + not_input_fees;
|
||||
|
||||
// Get available coins
|
||||
auto res_available_coins = AvailableCoins(wallet,
|
||||
auto available_coins = AvailableCoins(wallet,
|
||||
&coin_control,
|
||||
coin_selection_params.m_effective_feerate,
|
||||
1, /*nMinimumAmount*/
|
||||
@ -858,7 +867,7 @@ static BResult<CreatedTransactionResult> CreateTransactionInternal(
|
||||
0); /*nMaximumCount*/
|
||||
|
||||
// Choose coins to use
|
||||
std::optional<SelectionResult> result = SelectCoins(wallet, res_available_coins.all(), /*nTargetValue=*/selection_target, coin_control, coin_selection_params);
|
||||
std::optional<SelectionResult> result = SelectCoins(wallet, available_coins, /*nTargetValue=*/selection_target, coin_control, coin_selection_params);
|
||||
if (!result) {
|
||||
return _("Insufficient funds");
|
||||
}
|
||||
|
@ -35,7 +35,7 @@ TxSize CalculateMaximumSignedTxSize(const CTransaction& tx, const CWallet* walle
|
||||
* method for concatenating and returning all COutputs as one vector.
|
||||
*
|
||||
* clear(), size() methods are implemented so that one can interact with
|
||||
* the CoinsResult struct as if it were a vector
|
||||
* the CoinsResult struct as if it was a vector
|
||||
*/
|
||||
struct CoinsResult {
|
||||
/** Vectors for each OutputType */
|
||||
@ -100,26 +100,42 @@ std::vector<OutputGroup> GroupOutputs(const CWallet& wallet, const std::vector<C
|
||||
* param@[in] wallet The wallet which provides solving data for the coins
|
||||
* param@[in] nTargetValue The target value
|
||||
* param@[in] eligilibity_filter A filter containing rules for which coins are allowed to be included in this selection
|
||||
* param@[in] coins The vector of coins available for selection prior to filtering
|
||||
* param@[in] available_coins The struct of coins, organized by OutputType, available for selection prior to filtering
|
||||
* param@[in] coin_selection_params Parameters for the coin selection
|
||||
* returns If successful, a SelectionResult containing the input set
|
||||
* If failed, a nullopt
|
||||
*/
|
||||
std::optional<SelectionResult> AttemptSelection(const CWallet& wallet, const CAmount& nTargetValue, const CoinEligibilityFilter& eligibility_filter, std::vector<COutput> coins,
|
||||
std::optional<SelectionResult> AttemptSelection(const CWallet& wallet, const CAmount& nTargetValue, const CoinEligibilityFilter& eligibility_filter, const CoinsResult& available_coins,
|
||||
const CoinSelectionParams& coin_selection_params);
|
||||
|
||||
/**
|
||||
* Attempt to find a valid input set that meets the provided eligibility filter and target.
|
||||
* Multiple coin selection algorithms will be run and the input set that produces the least waste
|
||||
* (according to the waste metric) will be chosen.
|
||||
*
|
||||
* param@[in] wallet The wallet which provides solving data for the coins
|
||||
* param@[in] nTargetValue The target value
|
||||
* param@[in] eligilibity_filter A filter containing rules for which coins are allowed to be included in this selection
|
||||
* param@[in] available_coins The struct of coins, organized by OutputType, available for selection prior to filtering
|
||||
* param@[in] coin_selection_params Parameters for the coin selection
|
||||
* returns If successful, a SelectionResult containing the input set
|
||||
* If failed, a nullopt
|
||||
*/
|
||||
std::optional<SelectionResult> ChooseSelectionResult(const CWallet& wallet, const CAmount& nTargetValue, const CoinEligibilityFilter& eligibility_filter, const std::vector<COutput>& available_coins,
|
||||
const CoinSelectionParams& coin_selection_params);
|
||||
|
||||
/**
|
||||
* Select a set of coins such that nTargetValue is met and at least
|
||||
* all coins from coin_control are selected; never select unconfirmed coins if they are not ours
|
||||
* param@[in] wallet The wallet which provides data necessary to spend the selected coins
|
||||
* param@[in] vAvailableCoins The vector of coins available to be spent
|
||||
* param@[in] available_coins The struct of coins, organized by OutputType, available for selection prior to filtering
|
||||
* param@[in] nTargetValue The target value
|
||||
* param@[in] coin_selection_params Parameters for this coin selection such as feerates, whether to avoid partial spends,
|
||||
* and whether to subtract the fee from the outputs.
|
||||
* returns If successful, a SelectionResult containing the selected coins
|
||||
* If failed, a nullopt.
|
||||
*/
|
||||
std::optional<SelectionResult> SelectCoins(const CWallet& wallet, const std::vector<COutput>& vAvailableCoins, const CAmount& nTargetValue, const CCoinControl& coin_control,
|
||||
std::optional<SelectionResult> SelectCoins(const CWallet& wallet, CoinsResult& available_coins, const CAmount& nTargetValue, const CCoinControl& coin_control,
|
||||
const CoinSelectionParams& coin_selection_params) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet);
|
||||
|
||||
struct CreatedTransactionResult
|
||||
|
@ -67,7 +67,7 @@ static void add_coin(const CAmount& nValue, int nInput, CoinSet& set, CAmount fe
|
||||
set.insert(coin);
|
||||
}
|
||||
|
||||
static void add_coin(std::vector<COutput>& coins, CWallet& wallet, const CAmount& nValue, CFeeRate feerate = CFeeRate(0), int nAge = 6*24, bool fIsFromMe = false, int nInput=0, bool spendable = false)
|
||||
static void add_coin(CoinsResult& available_coins, CWallet& wallet, const CAmount& nValue, CFeeRate feerate = CFeeRate(0), int nAge = 6*24, bool fIsFromMe = false, int nInput =0, bool spendable = false)
|
||||
{
|
||||
CMutableTransaction tx;
|
||||
tx.nLockTime = nextLockTime++; // so all transactions get different hashes
|
||||
@ -85,7 +85,7 @@ static void add_coin(std::vector<COutput>& coins, CWallet& wallet, const CAmount
|
||||
assert(ret.second);
|
||||
CWalletTx& wtx = (*ret.first).second;
|
||||
const auto& txout = wtx.tx->vout.at(nInput);
|
||||
coins.emplace_back(COutPoint(wtx.GetHash(), nInput), txout, nAge, CalculateMaximumSignedInputSize(txout, &wallet, /*coin_control=*/nullptr), /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, wtx.GetTxTime(), fIsFromMe, feerate);
|
||||
available_coins.bech32.emplace_back(COutPoint(wtx.GetHash(), nInput), txout, nAge, CalculateMaximumSignedInputSize(txout, &wallet, /*coin_control=*/nullptr), /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, wtx.GetTxTime(), fIsFromMe, feerate);
|
||||
}
|
||||
|
||||
/** Check if SelectionResult a is equivalent to SelectionResult b.
|
||||
@ -129,18 +129,18 @@ static CAmount make_hard_case(int utxos, std::vector<COutput>& utxo_pool)
|
||||
return target;
|
||||
}
|
||||
|
||||
inline std::vector<OutputGroup>& GroupCoins(const std::vector<COutput>& coins)
|
||||
inline std::vector<OutputGroup>& GroupCoins(const std::vector<COutput>& available_coins)
|
||||
{
|
||||
static std::vector<OutputGroup> static_groups;
|
||||
static_groups.clear();
|
||||
for (auto& coin : coins) {
|
||||
for (auto& coin : available_coins) {
|
||||
static_groups.emplace_back();
|
||||
static_groups.back().Insert(coin, /*ancestors=*/ 0, /*descendants=*/ 0, /*positive_only=*/ false);
|
||||
}
|
||||
return static_groups;
|
||||
}
|
||||
|
||||
inline std::vector<OutputGroup>& KnapsackGroupOutputs(const std::vector<COutput>& coins, CWallet& wallet, const CoinEligibilityFilter& filter)
|
||||
inline std::vector<OutputGroup>& KnapsackGroupOutputs(const std::vector<COutput>& available_coins, CWallet& wallet, const CoinEligibilityFilter& filter)
|
||||
{
|
||||
FastRandomContext rand{};
|
||||
CoinSelectionParams coin_selection_params{
|
||||
@ -155,7 +155,7 @@ inline std::vector<OutputGroup>& KnapsackGroupOutputs(const std::vector<COutput>
|
||||
/*avoid_partial=*/ false,
|
||||
};
|
||||
static std::vector<OutputGroup> static_groups;
|
||||
static_groups = GroupOutputs(wallet, coins, coin_selection_params, filter, /*positive_only=*/false);
|
||||
static_groups = GroupOutputs(wallet, available_coins, coin_selection_params, filter, /*positive_only=*/false);
|
||||
return static_groups;
|
||||
}
|
||||
|
||||
@ -307,18 +307,18 @@ BOOST_AUTO_TEST_CASE(bnb_search_test)
|
||||
wallet->SetWalletFlag(WALLET_FLAG_DESCRIPTORS);
|
||||
wallet->SetupDescriptorScriptPubKeyMans();
|
||||
|
||||
std::vector<COutput> coins;
|
||||
CoinsResult available_coins;
|
||||
|
||||
add_coin(coins, *wallet, 1, coin_selection_params_bnb.m_effective_feerate);
|
||||
coins.at(0).input_bytes = 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(!SelectCoinsBnB(GroupCoins(coins), 1 * CENT, coin_selection_params_bnb.m_cost_of_change));
|
||||
add_coin(available_coins, *wallet, 1, coin_selection_params_bnb.m_effective_feerate);
|
||||
available_coins.all().at(0).input_bytes = 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(!SelectCoinsBnB(GroupCoins(available_coins.all()), 1 * CENT, coin_selection_params_bnb.m_cost_of_change));
|
||||
|
||||
// Test fees subtracted from output:
|
||||
coins.clear();
|
||||
add_coin(coins, *wallet, 1 * CENT, coin_selection_params_bnb.m_effective_feerate);
|
||||
coins.at(0).input_bytes = 40;
|
||||
available_coins.clear();
|
||||
add_coin(available_coins, *wallet, 1 * CENT, coin_selection_params_bnb.m_effective_feerate);
|
||||
available_coins.all().at(0).input_bytes = 40;
|
||||
coin_selection_params_bnb.m_subtract_fee_outputs = true;
|
||||
const auto result9 = SelectCoinsBnB(GroupCoins(coins), 1 * CENT, coin_selection_params_bnb.m_cost_of_change);
|
||||
const auto result9 = SelectCoinsBnB(GroupCoins(available_coins.all()), 1 * CENT, coin_selection_params_bnb.m_cost_of_change);
|
||||
BOOST_CHECK(result9);
|
||||
BOOST_CHECK_EQUAL(result9->GetSelectedValue(), 1 * CENT);
|
||||
}
|
||||
@ -330,16 +330,16 @@ BOOST_AUTO_TEST_CASE(bnb_search_test)
|
||||
wallet->SetWalletFlag(WALLET_FLAG_DESCRIPTORS);
|
||||
wallet->SetupDescriptorScriptPubKeyMans();
|
||||
|
||||
std::vector<COutput> coins;
|
||||
CoinsResult available_coins;
|
||||
|
||||
add_coin(coins, *wallet, 5 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true);
|
||||
add_coin(coins, *wallet, 3 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true);
|
||||
add_coin(coins, *wallet, 2 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true);
|
||||
add_coin(available_coins, *wallet, 5 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true);
|
||||
add_coin(available_coins, *wallet, 3 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true);
|
||||
add_coin(available_coins, *wallet, 2 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true);
|
||||
CCoinControl coin_control;
|
||||
coin_control.m_allow_other_inputs = true;
|
||||
coin_control.Select(coins.at(0).outpoint);
|
||||
coin_control.Select(available_coins.all().at(0).outpoint);
|
||||
coin_selection_params_bnb.m_effective_feerate = CFeeRate(0);
|
||||
const auto result10 = SelectCoins(*wallet, coins, 10 * CENT, coin_control, coin_selection_params_bnb);
|
||||
const auto result10 = SelectCoins(*wallet, available_coins, 10 * CENT, coin_control, coin_selection_params_bnb);
|
||||
BOOST_CHECK(result10);
|
||||
}
|
||||
{
|
||||
@ -349,52 +349,52 @@ BOOST_AUTO_TEST_CASE(bnb_search_test)
|
||||
wallet->SetWalletFlag(WALLET_FLAG_DESCRIPTORS);
|
||||
wallet->SetupDescriptorScriptPubKeyMans();
|
||||
|
||||
std::vector<COutput> coins;
|
||||
CoinsResult available_coins;
|
||||
|
||||
// single coin should be selected when effective fee > long term fee
|
||||
coin_selection_params_bnb.m_effective_feerate = CFeeRate(5000);
|
||||
coin_selection_params_bnb.m_long_term_feerate = CFeeRate(3000);
|
||||
|
||||
add_coin(coins, *wallet, 10 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true);
|
||||
add_coin(coins, *wallet, 9 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true);
|
||||
add_coin(coins, *wallet, 1 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true);
|
||||
add_coin(available_coins, *wallet, 10 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true);
|
||||
add_coin(available_coins, *wallet, 9 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true);
|
||||
add_coin(available_coins, *wallet, 1 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true);
|
||||
|
||||
expected_result.Clear();
|
||||
add_coin(10 * CENT, 2, expected_result);
|
||||
CCoinControl coin_control;
|
||||
const auto result11 = SelectCoins(*wallet, coins, 10 * CENT, coin_control, coin_selection_params_bnb);
|
||||
const auto result11 = SelectCoins(*wallet, available_coins, 10 * CENT, coin_control, coin_selection_params_bnb);
|
||||
BOOST_CHECK(EquivalentResult(expected_result, *result11));
|
||||
coins.clear();
|
||||
available_coins.clear();
|
||||
|
||||
// more coins should be selected when effective fee < long term fee
|
||||
coin_selection_params_bnb.m_effective_feerate = CFeeRate(3000);
|
||||
coin_selection_params_bnb.m_long_term_feerate = CFeeRate(5000);
|
||||
|
||||
add_coin(coins, *wallet, 10 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true);
|
||||
add_coin(coins, *wallet, 9 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true);
|
||||
add_coin(coins, *wallet, 1 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true);
|
||||
add_coin(available_coins, *wallet, 10 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true);
|
||||
add_coin(available_coins, *wallet, 9 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true);
|
||||
add_coin(available_coins, *wallet, 1 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true);
|
||||
|
||||
expected_result.Clear();
|
||||
add_coin(9 * CENT, 2, expected_result);
|
||||
add_coin(1 * CENT, 2, expected_result);
|
||||
const auto result12 = SelectCoins(*wallet, coins, 10 * CENT, coin_control, coin_selection_params_bnb);
|
||||
const auto result12 = SelectCoins(*wallet, available_coins, 10 * CENT, coin_control, coin_selection_params_bnb);
|
||||
BOOST_CHECK(EquivalentResult(expected_result, *result12));
|
||||
coins.clear();
|
||||
available_coins.clear();
|
||||
|
||||
// pre selected coin should be selected even if disadvantageous
|
||||
coin_selection_params_bnb.m_effective_feerate = CFeeRate(5000);
|
||||
coin_selection_params_bnb.m_long_term_feerate = CFeeRate(3000);
|
||||
|
||||
add_coin(coins, *wallet, 10 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true);
|
||||
add_coin(coins, *wallet, 9 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true);
|
||||
add_coin(coins, *wallet, 1 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true);
|
||||
add_coin(available_coins, *wallet, 10 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true);
|
||||
add_coin(available_coins, *wallet, 9 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true);
|
||||
add_coin(available_coins, *wallet, 1 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true);
|
||||
|
||||
expected_result.Clear();
|
||||
add_coin(9 * CENT, 2, expected_result);
|
||||
add_coin(1 * CENT, 2, expected_result);
|
||||
coin_control.m_allow_other_inputs = true;
|
||||
coin_control.Select(coins.at(1).outpoint); // pre select 9 coin
|
||||
const auto result13 = SelectCoins(*wallet, coins, 10 * CENT, coin_control, coin_selection_params_bnb);
|
||||
coin_control.Select(available_coins.all().at(1).outpoint); // pre select 9 coin
|
||||
const auto result13 = SelectCoins(*wallet, available_coins, 10 * CENT, coin_control, coin_selection_params_bnb);
|
||||
BOOST_CHECK(EquivalentResult(expected_result, *result13));
|
||||
}
|
||||
}
|
||||
@ -410,175 +410,175 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test)
|
||||
wallet->SetWalletFlag(WALLET_FLAG_DESCRIPTORS);
|
||||
wallet->SetupDescriptorScriptPubKeyMans();
|
||||
|
||||
std::vector<COutput> coins;
|
||||
CoinsResult available_coins;
|
||||
|
||||
// test multiple times to allow for differences in the shuffle order
|
||||
for (int i = 0; i < RUN_TESTS; i++)
|
||||
{
|
||||
coins.clear();
|
||||
available_coins.clear();
|
||||
|
||||
// with an empty wallet we can't even pay one cent
|
||||
BOOST_CHECK(!KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_standard), 1 * CENT, CENT));
|
||||
BOOST_CHECK(!KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_standard), 1 * CENT, CENT));
|
||||
|
||||
add_coin(coins, *wallet, 1*CENT, CFeeRate(0), 4); // add a new 1 cent coin
|
||||
add_coin(available_coins, *wallet, 1*CENT, CFeeRate(0), 4); // add a new 1 cent coin
|
||||
|
||||
// with a new 1 cent coin, we still can't find a mature 1 cent
|
||||
BOOST_CHECK(!KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_standard), 1 * CENT, CENT));
|
||||
BOOST_CHECK(!KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_standard), 1 * CENT, CENT));
|
||||
|
||||
// but we can find a new 1 cent
|
||||
const auto result1 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), 1 * CENT, CENT);
|
||||
const auto result1 = KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_confirmed), 1 * CENT, CENT);
|
||||
BOOST_CHECK(result1);
|
||||
BOOST_CHECK_EQUAL(result1->GetSelectedValue(), 1 * CENT);
|
||||
|
||||
add_coin(coins, *wallet, 2*CENT); // add a mature 2 cent coin
|
||||
add_coin(available_coins, *wallet, 2*CENT); // add a mature 2 cent coin
|
||||
|
||||
// we can't make 3 cents of mature coins
|
||||
BOOST_CHECK(!KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_standard), 3 * CENT, CENT));
|
||||
BOOST_CHECK(!KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_standard), 3 * CENT, CENT));
|
||||
|
||||
// we can make 3 cents of new coins
|
||||
const auto result2 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), 3 * CENT, CENT);
|
||||
const auto result2 = KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_confirmed), 3 * CENT, CENT);
|
||||
BOOST_CHECK(result2);
|
||||
BOOST_CHECK_EQUAL(result2->GetSelectedValue(), 3 * CENT);
|
||||
|
||||
add_coin(coins, *wallet, 5*CENT); // add a mature 5 cent coin,
|
||||
add_coin(coins, *wallet, 10*CENT, CFeeRate(0), 3, true); // a new 10 cent coin sent from one of our own addresses
|
||||
add_coin(coins, *wallet, 20*CENT); // and a mature 20 cent coin
|
||||
add_coin(available_coins, *wallet, 5*CENT); // add a mature 5 cent coin,
|
||||
add_coin(available_coins, *wallet, 10*CENT, CFeeRate(0), 3, true); // a new 10 cent coin sent from one of our own addresses
|
||||
add_coin(available_coins, *wallet, 20*CENT); // and a mature 20 cent coin
|
||||
|
||||
// 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:
|
||||
BOOST_CHECK(!KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_standard), 38 * CENT, CENT));
|
||||
BOOST_CHECK(!KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_standard), 38 * CENT, CENT));
|
||||
// we can't even make 37 cents if we don't allow new coins even if they're from us
|
||||
BOOST_CHECK(!KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_standard_extra), 38 * CENT, CENT));
|
||||
BOOST_CHECK(!KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_standard_extra), 38 * CENT, CENT));
|
||||
// but we can make 37 cents if we accept new coins from ourself
|
||||
const auto result3 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_standard), 37 * CENT, CENT);
|
||||
const auto result3 = KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_standard), 37 * CENT, CENT);
|
||||
BOOST_CHECK(result3);
|
||||
BOOST_CHECK_EQUAL(result3->GetSelectedValue(), 37 * CENT);
|
||||
// and we can make 38 cents if we accept all new coins
|
||||
const auto result4 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), 38 * CENT, CENT);
|
||||
const auto result4 = KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_confirmed), 38 * CENT, CENT);
|
||||
BOOST_CHECK(result4);
|
||||
BOOST_CHECK_EQUAL(result4->GetSelectedValue(), 38 * CENT);
|
||||
|
||||
// try making 34 cents from 1,2,5,10,20 - we can't do it exactly
|
||||
const auto result5 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), 34 * CENT, CENT);
|
||||
const auto result5 = KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_confirmed), 34 * CENT, CENT);
|
||||
BOOST_CHECK(result5);
|
||||
BOOST_CHECK_EQUAL(result5->GetSelectedValue(), 35 * CENT); // but 35 cents is closest
|
||||
BOOST_CHECK_EQUAL(result5->GetInputSet().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
|
||||
const auto result6 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), 7 * CENT, CENT);
|
||||
const auto result6 = KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_confirmed), 7 * CENT, CENT);
|
||||
BOOST_CHECK(result6);
|
||||
BOOST_CHECK_EQUAL(result6->GetSelectedValue(), 7 * CENT);
|
||||
BOOST_CHECK_EQUAL(result6->GetInputSet().size(), 2U);
|
||||
|
||||
// when we try making 8 cents, the smaller coins (1,2,5) are exactly enough.
|
||||
const auto result7 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), 8 * CENT, CENT);
|
||||
const auto result7 = KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_confirmed), 8 * CENT, CENT);
|
||||
BOOST_CHECK(result7);
|
||||
BOOST_CHECK(result7->GetSelectedValue() == 8 * CENT);
|
||||
BOOST_CHECK_EQUAL(result7->GetInputSet().size(), 3U);
|
||||
|
||||
// when we try making 9 cents, no subset of smaller coins is enough, and we get the next bigger coin (10)
|
||||
const auto result8 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), 9 * CENT, CENT);
|
||||
const auto result8 = KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_confirmed), 9 * CENT, CENT);
|
||||
BOOST_CHECK(result8);
|
||||
BOOST_CHECK_EQUAL(result8->GetSelectedValue(), 10 * CENT);
|
||||
BOOST_CHECK_EQUAL(result8->GetInputSet().size(), 1U);
|
||||
|
||||
// now clear out the wallet and start again to test choosing between subsets of smaller coins and the next biggest coin
|
||||
coins.clear();
|
||||
available_coins.clear();
|
||||
|
||||
add_coin(coins, *wallet, 6*CENT);
|
||||
add_coin(coins, *wallet, 7*CENT);
|
||||
add_coin(coins, *wallet, 8*CENT);
|
||||
add_coin(coins, *wallet, 20*CENT);
|
||||
add_coin(coins, *wallet, 30*CENT); // now we have 6+7+8+20+30 = 71 cents total
|
||||
add_coin(available_coins, *wallet, 6*CENT);
|
||||
add_coin(available_coins, *wallet, 7*CENT);
|
||||
add_coin(available_coins, *wallet, 8*CENT);
|
||||
add_coin(available_coins, *wallet, 20*CENT);
|
||||
add_coin(available_coins, *wallet, 30*CENT); // now we have 6+7+8+20+30 = 71 cents total
|
||||
|
||||
// check that we have 71 and not 72
|
||||
const auto result9 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), 71 * CENT, CENT);
|
||||
const auto result9 = KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_confirmed), 71 * CENT, CENT);
|
||||
BOOST_CHECK(result9);
|
||||
BOOST_CHECK(!KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), 72 * CENT, CENT));
|
||||
BOOST_CHECK(!KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_confirmed), 72 * CENT, CENT));
|
||||
|
||||
// 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
|
||||
const auto result10 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), 16 * CENT, CENT);
|
||||
const auto result10 = KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_confirmed), 16 * CENT, CENT);
|
||||
BOOST_CHECK(result10);
|
||||
BOOST_CHECK_EQUAL(result10->GetSelectedValue(), 20 * CENT); // we should get 20 in one coin
|
||||
BOOST_CHECK_EQUAL(result10->GetInputSet().size(), 1U);
|
||||
|
||||
add_coin(coins, *wallet, 5*CENT); // now we have 5+6+7+8+20+30 = 75 cents total
|
||||
add_coin(available_coins, *wallet, 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
|
||||
const auto result11 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), 16 * CENT, CENT);
|
||||
const auto result11 = KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_confirmed), 16 * CENT, CENT);
|
||||
BOOST_CHECK(result11);
|
||||
BOOST_CHECK_EQUAL(result11->GetSelectedValue(), 18 * CENT); // we should get 18 in 3 coins
|
||||
BOOST_CHECK_EQUAL(result11->GetInputSet().size(), 3U);
|
||||
|
||||
add_coin(coins, *wallet, 18*CENT); // now we have 5+6+7+8+18+20+30
|
||||
add_coin(available_coins, *wallet, 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
|
||||
const auto result12 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), 16 * CENT, CENT);
|
||||
const auto result12 = KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_confirmed), 16 * CENT, CENT);
|
||||
BOOST_CHECK(result12);
|
||||
BOOST_CHECK_EQUAL(result12->GetSelectedValue(), 18 * CENT); // we should get 18 in 1 coin
|
||||
BOOST_CHECK_EQUAL(result12->GetInputSet().size(), 1U); // because in the event of a tie, the biggest coin wins
|
||||
|
||||
// now try making 11 cents. we should get 5+6
|
||||
const auto result13 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), 11 * CENT, CENT);
|
||||
const auto result13 = KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_confirmed), 11 * CENT, CENT);
|
||||
BOOST_CHECK(result13);
|
||||
BOOST_CHECK_EQUAL(result13->GetSelectedValue(), 11 * CENT);
|
||||
BOOST_CHECK_EQUAL(result13->GetInputSet().size(), 2U);
|
||||
|
||||
// check that the smallest bigger coin is used
|
||||
add_coin(coins, *wallet, 1*COIN);
|
||||
add_coin(coins, *wallet, 2*COIN);
|
||||
add_coin(coins, *wallet, 3*COIN);
|
||||
add_coin(coins, *wallet, 4*COIN); // now we have 5+6+7+8+18+20+30+100+200+300+400 = 1094 cents
|
||||
const auto result14 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), 95 * CENT, CENT);
|
||||
add_coin(available_coins, *wallet, 1*COIN);
|
||||
add_coin(available_coins, *wallet, 2*COIN);
|
||||
add_coin(available_coins, *wallet, 3*COIN);
|
||||
add_coin(available_coins, *wallet, 4*COIN); // now we have 5+6+7+8+18+20+30+100+200+300+400 = 1094 cents
|
||||
const auto result14 = KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_confirmed), 95 * CENT, CENT);
|
||||
BOOST_CHECK(result14);
|
||||
BOOST_CHECK_EQUAL(result14->GetSelectedValue(), 1 * COIN); // we should get 1 BTC in 1 coin
|
||||
BOOST_CHECK_EQUAL(result14->GetInputSet().size(), 1U);
|
||||
|
||||
const auto result15 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), 195 * CENT, CENT);
|
||||
const auto result15 = KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_confirmed), 195 * CENT, CENT);
|
||||
BOOST_CHECK(result15);
|
||||
BOOST_CHECK_EQUAL(result15->GetSelectedValue(), 2 * COIN); // we should get 2 BTC in 1 coin
|
||||
BOOST_CHECK_EQUAL(result15->GetInputSet().size(), 1U);
|
||||
|
||||
// empty the wallet and start again, now with fractions of a cent, to test small change avoidance
|
||||
|
||||
coins.clear();
|
||||
add_coin(coins, *wallet, CENT * 1 / 10);
|
||||
add_coin(coins, *wallet, CENT * 2 / 10);
|
||||
add_coin(coins, *wallet, CENT * 3 / 10);
|
||||
add_coin(coins, *wallet, CENT * 4 / 10);
|
||||
add_coin(coins, *wallet, CENT * 5 / 10);
|
||||
available_coins.clear();
|
||||
add_coin(available_coins, *wallet, CENT * 1 / 10);
|
||||
add_coin(available_coins, *wallet, CENT * 2 / 10);
|
||||
add_coin(available_coins, *wallet, CENT * 3 / 10);
|
||||
add_coin(available_coins, *wallet, CENT * 4 / 10);
|
||||
add_coin(available_coins, *wallet, CENT * 5 / 10);
|
||||
|
||||
// try making 1 * CENT from the 1.5 * CENT
|
||||
// we'll get change smaller than CENT whatever happens, so can expect CENT exactly
|
||||
const auto result16 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), CENT, CENT);
|
||||
const auto result16 = KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_confirmed), CENT, CENT);
|
||||
BOOST_CHECK(result16);
|
||||
BOOST_CHECK_EQUAL(result16->GetSelectedValue(), CENT);
|
||||
|
||||
// but if we add a bigger coin, small change is avoided
|
||||
add_coin(coins, *wallet, 1111*CENT);
|
||||
add_coin(available_coins, *wallet, 1111*CENT);
|
||||
|
||||
// try making 1 from 0.1 + 0.2 + 0.3 + 0.4 + 0.5 + 1111 = 1112.5
|
||||
const auto result17 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), 1 * CENT, CENT);
|
||||
const auto result17 = KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_confirmed), 1 * CENT, CENT);
|
||||
BOOST_CHECK(result17);
|
||||
BOOST_CHECK_EQUAL(result17->GetSelectedValue(), 1 * CENT); // we should get the exact amount
|
||||
|
||||
// if we add more small coins:
|
||||
add_coin(coins, *wallet, CENT * 6 / 10);
|
||||
add_coin(coins, *wallet, CENT * 7 / 10);
|
||||
add_coin(available_coins, *wallet, CENT * 6 / 10);
|
||||
add_coin(available_coins, *wallet, CENT * 7 / 10);
|
||||
|
||||
// and try again to make 1.0 * CENT
|
||||
const auto result18 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), 1 * CENT, CENT);
|
||||
const auto result18 = KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_confirmed), 1 * CENT, CENT);
|
||||
BOOST_CHECK(result18);
|
||||
BOOST_CHECK_EQUAL(result18->GetSelectedValue(), 1 * CENT); // we should get the exact amount
|
||||
|
||||
// run the 'mtgox' test (see https://blockexplorer.com/tx/29a3efd3ef04f9153d47a990bd7b048a4b2d213daaa5fb8ed670fb85f13bdbcf)
|
||||
// they tried to consolidate 10 50k coins into one 500k coin, and ended up with 50k in change
|
||||
coins.clear();
|
||||
available_coins.clear();
|
||||
for (int j = 0; j < 20; j++)
|
||||
add_coin(coins, *wallet, 50000 * COIN);
|
||||
add_coin(available_coins, *wallet, 50000 * COIN);
|
||||
|
||||
const auto result19 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), 500000 * COIN, CENT);
|
||||
const auto result19 = KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_confirmed), 500000 * COIN, CENT);
|
||||
BOOST_CHECK(result19);
|
||||
BOOST_CHECK_EQUAL(result19->GetSelectedValue(), 500000 * COIN); // we should get the exact amount
|
||||
BOOST_CHECK_EQUAL(result19->GetInputSet().size(), 10U); // in ten coins
|
||||
@ -587,41 +587,41 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test)
|
||||
// we need to try finding an exact subset anyway
|
||||
|
||||
// sometimes it will fail, and so we use the next biggest coin:
|
||||
coins.clear();
|
||||
add_coin(coins, *wallet, CENT * 5 / 10);
|
||||
add_coin(coins, *wallet, CENT * 6 / 10);
|
||||
add_coin(coins, *wallet, CENT * 7 / 10);
|
||||
add_coin(coins, *wallet, 1111 * CENT);
|
||||
const auto result20 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), 1 * CENT, CENT);
|
||||
available_coins.clear();
|
||||
add_coin(available_coins, *wallet, CENT * 5 / 10);
|
||||
add_coin(available_coins, *wallet, CENT * 6 / 10);
|
||||
add_coin(available_coins, *wallet, CENT * 7 / 10);
|
||||
add_coin(available_coins, *wallet, 1111 * CENT);
|
||||
const auto result20 = KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_confirmed), 1 * CENT, CENT);
|
||||
BOOST_CHECK(result20);
|
||||
BOOST_CHECK_EQUAL(result20->GetSelectedValue(), 1111 * CENT); // we get the bigger coin
|
||||
BOOST_CHECK_EQUAL(result20->GetInputSet().size(), 1U);
|
||||
|
||||
// but sometimes it's possible, and we use an exact subset (0.4 + 0.6 = 1.0)
|
||||
coins.clear();
|
||||
add_coin(coins, *wallet, CENT * 4 / 10);
|
||||
add_coin(coins, *wallet, CENT * 6 / 10);
|
||||
add_coin(coins, *wallet, CENT * 8 / 10);
|
||||
add_coin(coins, *wallet, 1111 * CENT);
|
||||
const auto result21 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), CENT, CENT);
|
||||
available_coins.clear();
|
||||
add_coin(available_coins, *wallet, CENT * 4 / 10);
|
||||
add_coin(available_coins, *wallet, CENT * 6 / 10);
|
||||
add_coin(available_coins, *wallet, CENT * 8 / 10);
|
||||
add_coin(available_coins, *wallet, 1111 * CENT);
|
||||
const auto result21 = KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_confirmed), CENT, CENT);
|
||||
BOOST_CHECK(result21);
|
||||
BOOST_CHECK_EQUAL(result21->GetSelectedValue(), CENT); // we should get the exact amount
|
||||
BOOST_CHECK_EQUAL(result21->GetInputSet().size(), 2U); // in two coins 0.4+0.6
|
||||
|
||||
// test avoiding small change
|
||||
coins.clear();
|
||||
add_coin(coins, *wallet, CENT * 5 / 100);
|
||||
add_coin(coins, *wallet, CENT * 1);
|
||||
add_coin(coins, *wallet, CENT * 100);
|
||||
available_coins.clear();
|
||||
add_coin(available_coins, *wallet, CENT * 5 / 100);
|
||||
add_coin(available_coins, *wallet, CENT * 1);
|
||||
add_coin(available_coins, *wallet, CENT * 100);
|
||||
|
||||
// trying to make 100.01 from these three coins
|
||||
const auto result22 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), CENT * 10001 / 100, CENT);
|
||||
const auto result22 = KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_confirmed), CENT * 10001 / 100, CENT);
|
||||
BOOST_CHECK(result22);
|
||||
BOOST_CHECK_EQUAL(result22->GetSelectedValue(), CENT * 10105 / 100); // we should get all coins
|
||||
BOOST_CHECK_EQUAL(result22->GetInputSet().size(), 3U);
|
||||
|
||||
// but if we try to make 99.9, we should take the bigger of the two small coins to avoid small change
|
||||
const auto result23 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), CENT * 9990 / 100, CENT);
|
||||
const auto result23 = KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_confirmed), CENT * 9990 / 100, CENT);
|
||||
BOOST_CHECK(result23);
|
||||
BOOST_CHECK_EQUAL(result23->GetSelectedValue(), 101 * CENT);
|
||||
BOOST_CHECK_EQUAL(result23->GetInputSet().size(), 2U);
|
||||
@ -629,14 +629,14 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test)
|
||||
|
||||
// test with many inputs
|
||||
for (CAmount amt=1500; amt < COIN; amt*=10) {
|
||||
coins.clear();
|
||||
available_coins.clear();
|
||||
// Create 676 inputs (= (old MAX_STANDARD_TX_SIZE == 100000) / 148 bytes per input)
|
||||
for (uint16_t j = 0; j < 676; j++)
|
||||
add_coin(coins, *wallet, amt);
|
||||
add_coin(available_coins, *wallet, amt);
|
||||
|
||||
// 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++) {
|
||||
const auto result24 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), 2000, CENT);
|
||||
const auto result24 = KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_confirmed), 2000, CENT);
|
||||
BOOST_CHECK(result24);
|
||||
|
||||
if (amt - 2000 < CENT) {
|
||||
@ -655,17 +655,17 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test)
|
||||
|
||||
// test randomness
|
||||
{
|
||||
coins.clear();
|
||||
available_coins.clear();
|
||||
for (int i2 = 0; i2 < 100; i2++)
|
||||
add_coin(coins, *wallet, COIN);
|
||||
add_coin(available_coins, *wallet, COIN);
|
||||
|
||||
// Again, 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++) {
|
||||
// picking 50 from 100 coins doesn't depend on the shuffle,
|
||||
// but does depend on randomness in the stochastic approximation code
|
||||
const auto result25 = KnapsackSolver(GroupCoins(coins), 50 * COIN, CENT);
|
||||
const auto result25 = KnapsackSolver(GroupCoins(available_coins.all()), 50 * COIN, CENT);
|
||||
BOOST_CHECK(result25);
|
||||
const auto result26 = KnapsackSolver(GroupCoins(coins), 50 * COIN, CENT);
|
||||
const auto result26 = KnapsackSolver(GroupCoins(available_coins.all()), 50 * COIN, CENT);
|
||||
BOOST_CHECK(result26);
|
||||
BOOST_CHECK(!EqualResult(*result25, *result26));
|
||||
|
||||
@ -676,9 +676,9 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test)
|
||||
// When choosing 1 from 100 identical coins, 1% of the time, this test will choose the same coin twice
|
||||
// which will cause it to fail.
|
||||
// To avoid that issue, run the test RANDOM_REPEATS times and only complain if all of them fail
|
||||
const auto result27 = KnapsackSolver(GroupCoins(coins), COIN, CENT);
|
||||
const auto result27 = KnapsackSolver(GroupCoins(available_coins.all()), COIN, CENT);
|
||||
BOOST_CHECK(result27);
|
||||
const auto result28 = KnapsackSolver(GroupCoins(coins), COIN, CENT);
|
||||
const auto result28 = KnapsackSolver(GroupCoins(available_coins.all()), COIN, CENT);
|
||||
BOOST_CHECK(result28);
|
||||
if (EqualResult(*result27, *result28))
|
||||
fails++;
|
||||
@ -689,19 +689,19 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test)
|
||||
// add 75 cents in small change. not enough to make 90 cents,
|
||||
// then try making 90 cents. there are multiple competing "smallest bigger" coins,
|
||||
// one of which should be picked at random
|
||||
add_coin(coins, *wallet, 5 * CENT);
|
||||
add_coin(coins, *wallet, 10 * CENT);
|
||||
add_coin(coins, *wallet, 15 * CENT);
|
||||
add_coin(coins, *wallet, 20 * CENT);
|
||||
add_coin(coins, *wallet, 25 * CENT);
|
||||
add_coin(available_coins, *wallet, 5 * CENT);
|
||||
add_coin(available_coins, *wallet, 10 * CENT);
|
||||
add_coin(available_coins, *wallet, 15 * CENT);
|
||||
add_coin(available_coins, *wallet, 20 * CENT);
|
||||
add_coin(available_coins, *wallet, 25 * CENT);
|
||||
|
||||
for (int i = 0; i < RUN_TESTS; i++) {
|
||||
int fails = 0;
|
||||
for (int j = 0; j < RANDOM_REPEATS; j++)
|
||||
{
|
||||
const auto result29 = KnapsackSolver(GroupCoins(coins), 90 * CENT, CENT);
|
||||
const auto result29 = KnapsackSolver(GroupCoins(available_coins.all()), 90 * CENT, CENT);
|
||||
BOOST_CHECK(result29);
|
||||
const auto result30 = KnapsackSolver(GroupCoins(coins), 90 * CENT, CENT);
|
||||
const auto result30 = KnapsackSolver(GroupCoins(available_coins.all()), 90 * CENT, CENT);
|
||||
BOOST_CHECK(result30);
|
||||
if (EqualResult(*result29, *result30))
|
||||
fails++;
|
||||
@ -720,14 +720,14 @@ BOOST_AUTO_TEST_CASE(ApproximateBestSubset)
|
||||
wallet->SetWalletFlag(WALLET_FLAG_DESCRIPTORS);
|
||||
wallet->SetupDescriptorScriptPubKeyMans();
|
||||
|
||||
std::vector<COutput> coins;
|
||||
CoinsResult available_coins;
|
||||
|
||||
// Test vValue sort order
|
||||
for (int i = 0; i < 1000; i++)
|
||||
add_coin(coins, *wallet, 1000 * COIN);
|
||||
add_coin(coins, *wallet, 3 * COIN);
|
||||
add_coin(available_coins, *wallet, 1000 * COIN);
|
||||
add_coin(available_coins, *wallet, 3 * COIN);
|
||||
|
||||
const auto result = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_standard), 1003 * COIN, CENT, rand);
|
||||
const auto result = KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_standard), 1003 * COIN, CENT, rand);
|
||||
BOOST_CHECK(result);
|
||||
BOOST_CHECK_EQUAL(result->GetSelectedValue(), 1003 * COIN);
|
||||
BOOST_CHECK_EQUAL(result->GetInputSet().size(), 2U);
|
||||
@ -750,14 +750,14 @@ BOOST_AUTO_TEST_CASE(SelectCoins_test)
|
||||
// Run this test 100 times
|
||||
for (int i = 0; i < 100; ++i)
|
||||
{
|
||||
std::vector<COutput> coins;
|
||||
CoinsResult available_coins;
|
||||
CAmount balance{0};
|
||||
|
||||
// Make a wallet with 1000 exponentially distributed random inputs
|
||||
for (int j = 0; j < 1000; ++j)
|
||||
{
|
||||
CAmount val = distribution(generator)*10000000;
|
||||
add_coin(coins, *wallet, val);
|
||||
add_coin(available_coins, *wallet, val);
|
||||
balance += val;
|
||||
}
|
||||
|
||||
@ -780,7 +780,7 @@ BOOST_AUTO_TEST_CASE(SelectCoins_test)
|
||||
/*avoid_partial=*/ false,
|
||||
};
|
||||
CCoinControl cc;
|
||||
const auto result = SelectCoins(*wallet, coins, target, cc, cs_params);
|
||||
const auto result = SelectCoins(*wallet, available_coins, target, cc, cs_params);
|
||||
BOOST_CHECK(result);
|
||||
BOOST_CHECK_GE(result->GetSelectedValue(), target);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user