LCOV - code coverage report
Current view: top level - src/wallet/test - coinselector_tests.cpp (source / functions) Hit Total Coverage
Test: total_coverage.info Lines: 714 715 99.9 %
Date: 2026-06-25 07:23:43 Functions: 96 96 100.0 %

          Line data    Source code
       1             : // Copyright (c) 2017-2021 The Bitcoin Core developers
       2             : // Distributed under the MIT software license, see the accompanying
       3             : // file COPYING or http://www.opensource.org/licenses/mit-license.php.
       4             : 
       5             : #include <test/util/setup_common.h>
       6             : 
       7             : #include <consensus/amount.h>
       8             : #include <node/context.h>
       9             : #include <primitives/transaction.h>
      10             : #include <random.h>
      11             : #include <util/translation.h>
      12             : #include <validation.h>
      13             : #include <wallet/coincontrol.h>
      14             : #include <wallet/coinselection.h>
      15             : #include <wallet/spend.h>
      16             : #include <wallet/test/wallet_test_fixture.h>
      17             : #include <wallet/wallet.h>
      18             : 
      19             : #include <algorithm>
      20             : #include <boost/test/unit_test.hpp>
      21             : #include <random>
      22             : 
      23             : namespace wallet {
      24         146 : BOOST_FIXTURE_TEST_SUITE(coinselector_tests, WalletTestingSetup)
      25             : 
      26             : // how many times to run all the tests to have a chance to catch errors that only show up with particular random shuffles
      27             : #define RUN_TESTS 100
      28             : 
      29             : // some tests fail 1% of the time due to bad luck.
      30             : // we repeat those tests this many times and only complain if all iterations of the test fail
      31             : #define RANDOM_REPEATS 5
      32             : 
      33             : typedef std::set<COutput> CoinSet;
      34             : 
      35         146 : static const CoinEligibilityFilter filter_standard(1, 6, 0);
      36         146 : static const CoinEligibilityFilter filter_confirmed(1, 1, 0);
      37         146 : static const CoinEligibilityFilter filter_standard_extra(6, 6, 0);
      38             : static int nextLockTime = 0;
      39             : 
      40       50088 : static void add_coin(const CAmount& nValue, int nInput, std::vector<COutput>& set)
      41             : {
      42       50088 :     CMutableTransaction tx;
      43       50088 :     tx.vout.resize(nInput + 1);
      44       50088 :     tx.vout[nInput].nValue = nValue;
      45       50088 :     tx.nLockTime = nextLockTime++;        // so all transactions get different hashes
      46       50088 :     set.emplace_back(COutPoint(tx.GetHash(), nInput), tx.vout.at(nInput), /*depth=*/ 1, /*input_bytes=*/ -1, /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, /*time=*/ 0, /*from_me=*/ false, /*fees=*/ 0);
      47       50088 : }
      48             : 
      49          19 : static void add_coin(const CAmount& nValue, int nInput, SelectionResult& result)
      50             : {
      51          19 :     CMutableTransaction tx;
      52          19 :     tx.vout.resize(nInput + 1);
      53          19 :     tx.vout[nInput].nValue = nValue;
      54          19 :     tx.nLockTime = nextLockTime++;        // so all transactions get different hashes
      55          19 :     COutput output(COutPoint(tx.GetHash(), nInput), tx.vout.at(nInput), /*depth=*/ 1, /*input_bytes=*/ -1, /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, /*time=*/ 0, /*from_me=*/ false, /*fees=*/ 0);
      56          19 :     OutputGroup group;
      57          19 :     group.Insert(output, /*ancestors=*/ 0, /*descendants=*/ 0, /*positive_only=*/ true);
      58          19 :     result.AddInput(group);
      59          19 : }
      60             : 
      61          24 : static void add_coin(const CAmount& nValue, int nInput, CoinSet& set, CAmount fee = 0, CAmount long_term_fee = 0)
      62             : {
      63          24 :     CMutableTransaction tx;
      64          24 :     tx.vout.resize(nInput + 1);
      65          24 :     tx.vout[nInput].nValue = nValue;
      66          24 :     tx.nLockTime = nextLockTime++;        // so all transactions get different hashes
      67          24 :     COutput coin(COutPoint(tx.GetHash(), nInput), tx.vout.at(nInput), /*depth=*/ 1, /*input_bytes=*/ 148, /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, /*time=*/ 0, /*from_me=*/ false, fee);
      68          24 :     coin.long_term_fee = long_term_fee;
      69          24 :     set.insert(coin);
      70          24 : }
      71             : 
      72      115544 : 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, int custom_size = 0)
      73             : {
      74      115544 :     CMutableTransaction tx;
      75      115544 :     tx.nLockTime = nextLockTime++;        // so all transactions get different hashes
      76      115544 :     tx.vout.resize(nInput + 1);
      77      115544 :     tx.vout[nInput].nValue = nValue;
      78      115544 :     if (spendable) {
      79        5536 :         tx.vout[nInput].scriptPubKey = GetScriptForDestination(*Assert(wallet.GetNewDestination("")));
      80        5536 :     }
      81      115544 :     uint256 txid = tx.GetHash();
      82             : 
      83      115544 :     LOCK(wallet.cs_wallet);
      84      115544 :     auto ret = wallet.mapWallet.emplace(std::piecewise_construct, std::forward_as_tuple(txid), std::forward_as_tuple(MakeTransactionRef(std::move(tx)), TxStateInactive{}));
      85      115544 :     assert(ret.second);
      86      115544 :     CWalletTx& wtx = (*ret.first).second;
      87      115544 :     const auto& txout = wtx.tx->vout.at(nInput);
      88      115544 :     available_coins.legacy.emplace_back(COutPoint(wtx.GetHash(), nInput), txout, nAge, custom_size == 0 ? CalculateMaximumSignedInputSize(txout, &wallet, /*coin_control=*/nullptr) : custom_size, /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, wtx.GetTxTime(), fIsFromMe, feerate);
      89      115544 : }
      90             : 
      91         114 : inline std::optional<SelectionResult> SelectCoinsBnB(std::vector<OutputGroup>& utxo_pool, const CAmount& selection_target, const CAmount& cost_of_change)
      92             : {
      93         114 :     auto res{wallet::SelectCoinsBnB(utxo_pool, selection_target, cost_of_change, MAX_STANDARD_TX_SIZE)};
      94         114 :     return res ? std::optional<SelectionResult>(*res) : std::nullopt;
      95         114 : }
      96             : 
      97             : /** Check if SelectionResult a is equivalent to SelectionResult b.
      98             :  * Equivalent means same input values, but maybe different inputs (i.e. same value, different prevout) */
      99           8 : static bool EquivalentResult(const SelectionResult& a, const SelectionResult& b)
     100             : {
     101           8 :     std::vector<CAmount> a_amts;
     102           8 :     std::vector<CAmount> b_amts;
     103          25 :     for (const auto& coin : a.GetInputSet()) {
     104          17 :         a_amts.push_back(coin.txout.nValue);
     105             :     }
     106          25 :     for (const auto& coin : b.GetInputSet()) {
     107          17 :         b_amts.push_back(coin.txout.nValue);
     108             :     }
     109           8 :     std::sort(a_amts.begin(), a_amts.end());
     110           8 :     std::sort(b_amts.begin(), b_amts.end());
     111             : 
     112           8 :     std::pair<std::vector<CAmount>::iterator, std::vector<CAmount>::iterator> ret = std::mismatch(a_amts.begin(), a_amts.end(), b_amts.begin());
     113           8 :     return ret.first == a_amts.end() && ret.second == b_amts.end();
     114           8 : }
     115             : 
     116             : /** Check if this selection is equal to another one. Equal means same inputs (i.e same value and prevout) */
     117        1100 : static bool EqualResult(const SelectionResult& a, const SelectionResult& b)
     118             : {
     119        1100 :     std::pair<CoinSet::iterator, CoinSet::iterator> ret = std::mismatch(a.GetInputSet().begin(), a.GetInputSet().end(), b.GetInputSet().begin(),
     120        1143 :         [](const COutput& a, const COutput& b) {
     121        1143 :             return a.outpoint == b.outpoint;
     122             :         });
     123        1100 :     return ret.first == a.GetInputSet().end() && ret.second == b.GetInputSet().end();
     124             : }
     125             : 
     126           2 : static int GetSelectionWeight(const SelectionResult& result)
     127             : {
     128        1383 :     return std::accumulate(result.GetInputSet().cbegin(), result.GetInputSet().cend(), 0, [](int sum, const COutput& coin) {
     129        1381 :         return sum + std::max(coin.input_bytes, 0);
     130             :     });
     131             : }
     132             : 
     133           2 : static CAmount make_hard_case(int utxos, std::vector<COutput>& utxo_pool)
     134             : {
     135           2 :     utxo_pool.clear();
     136           2 :     CAmount target = 0;
     137          33 :     for (int i = 0; i < utxos; ++i) {
     138          31 :         target += CAmount{1} << (utxos+i);
     139          31 :         add_coin(CAmount{1} << (utxos+i), 2*i, utxo_pool);
     140          31 :         add_coin((CAmount{1} << (utxos+i)) + (CAmount{1} << (utxos-1-i)), 2*i + 1, utxo_pool);
     141          31 :     }
     142           2 :     return target;
     143             : }
     144             : 
     145        2314 : inline std::vector<OutputGroup>& GroupCoins(const std::vector<COutput>& available_coins)
     146             : {
     147        2314 :     static std::vector<OutputGroup> static_groups;
     148        2314 :     static_groups.clear();
     149      279017 :     for (auto& coin : available_coins) {
     150      276703 :         static_groups.emplace_back();
     151      276703 :         static_groups.back().Insert(coin, /*ancestors=*/ 0, /*descendants=*/ 0, /*positive_only=*/ false);
     152             :     }
     153        2314 :     return static_groups;
     154             : }
     155             : 
     156        5601 : inline std::optional<SelectionResult> KnapsackSolver(std::vector<OutputGroup>& groups, const CAmount& nTargetValue, CAmount change_target, FastRandomContext& rng)
     157             : {
     158        5601 :     auto res{wallet::KnapsackSolver(groups, nTargetValue, change_target, rng, MAX_STANDARD_TX_SIZE, /*fFullyMixedOnly=*/false, /*maxTxFee=*/DEFAULT_TRANSACTION_MAXFEE)};
     159        5601 :     return res ? std::optional<SelectionResult>(*res) : std::nullopt;
     160        5601 : }
     161             : 
     162        3401 : inline std::vector<OutputGroup>& KnapsackGroupOutputs(const std::vector<COutput>& available_coins, CWallet& wallet, const CoinEligibilityFilter& filter)
     163             : {
     164        3401 :     FastRandomContext rand{};
     165        3401 :     CoinSelectionParams coin_selection_params{
     166             :         rand,
     167             :         /*change_output_size=*/ 0,
     168             :         /*change_spend_size=*/ 0,
     169             :         /*min_change_target=*/ CENT,
     170        3401 :         /*effective_feerate=*/ CFeeRate(0),
     171        3401 :         /*long_term_feerate=*/ CFeeRate(0),
     172        3401 :         /*discard_feerate=*/ CFeeRate(0),
     173             :         /*tx_noinputs_size=*/ 0,
     174             :         /*avoid_partial=*/ false,
     175             :     };
     176        3401 :     static std::vector<OutputGroup> static_groups;
     177        3401 :     static_groups = GroupOutputs(wallet, available_coins, coin_selection_params, filter, /*positive_only=*/false);
     178             :     return static_groups;
     179        3401 : }
     180             : 
     181             : // Branch and bound coin selection tests
     182         148 : BOOST_AUTO_TEST_CASE(bnb_search_test)
     183             : {
     184           1 :     FastRandomContext rand{};
     185             :     // Setup
     186           1 :     std::vector<COutput> utxo_pool;
     187           1 :     SelectionResult expected_result(CAmount(0), SelectionAlgorithm::BNB);
     188             : 
     189             :     /////////////////////////
     190             :     // Known Outcome tests //
     191             :     /////////////////////////
     192             : 
     193             :     // Empty utxo pool
     194           1 :     BOOST_CHECK(!SelectCoinsBnB(GroupCoins(utxo_pool), 1 * CENT, 0.5 * CENT));
     195             : 
     196             :     // Add utxos
     197           1 :     add_coin(1 * CENT, 1, utxo_pool);
     198           1 :     add_coin(2 * CENT, 2, utxo_pool);
     199           1 :     add_coin(3 * CENT, 3, utxo_pool);
     200           1 :     add_coin(4 * CENT, 4, utxo_pool);
     201             : 
     202             :     // Select 1 Cent
     203           1 :     add_coin(1 * CENT, 1, expected_result);
     204           1 :     const auto result1 = SelectCoinsBnB(GroupCoins(utxo_pool), 1 * CENT, 0.5 * CENT);
     205           1 :     BOOST_CHECK(result1);
     206           1 :     BOOST_CHECK(EquivalentResult(expected_result, *result1));
     207           1 :     BOOST_CHECK_EQUAL(result1->GetSelectedValue(), 1 * CENT);
     208           1 :     expected_result.Clear();
     209             : 
     210             :     // Select 2 Cent
     211           1 :     add_coin(2 * CENT, 2, expected_result);
     212           1 :     const auto result2 = SelectCoinsBnB(GroupCoins(utxo_pool), 2 * CENT, 0.5 * CENT);
     213           1 :     BOOST_CHECK(result2);
     214           1 :     BOOST_CHECK(EquivalentResult(expected_result, *result2));
     215           1 :     BOOST_CHECK_EQUAL(result2->GetSelectedValue(), 2 * CENT);
     216           1 :     expected_result.Clear();
     217             : 
     218             :     // Select 5 Cent
     219           1 :     add_coin(3 * CENT, 3, expected_result);
     220           1 :     add_coin(2 * CENT, 2, expected_result);
     221           1 :     const auto result3 = SelectCoinsBnB(GroupCoins(utxo_pool), 5 * CENT, 0.5 * CENT);
     222           1 :     BOOST_CHECK(result3);
     223           1 :     BOOST_CHECK(EquivalentResult(expected_result, *result3));
     224           1 :     BOOST_CHECK_EQUAL(result3->GetSelectedValue(), 5 * CENT);
     225           1 :     expected_result.Clear();
     226             : 
     227             :     // Select 11 Cent, not possible
     228           1 :     BOOST_CHECK(!SelectCoinsBnB(GroupCoins(utxo_pool), 11 * CENT, 0.5 * CENT));
     229           1 :     expected_result.Clear();
     230             : 
     231             :     // Cost of change is greater than the difference between target value and utxo sum
     232           1 :     add_coin(1 * CENT, 1, expected_result);
     233           1 :     const auto result4 = SelectCoinsBnB(GroupCoins(utxo_pool), 0.9 * CENT, 0.5 * CENT);
     234           1 :     BOOST_CHECK(result4);
     235           1 :     BOOST_CHECK_EQUAL(result4->GetSelectedValue(), 1 * CENT);
     236           1 :     BOOST_CHECK(EquivalentResult(expected_result, *result4));
     237           1 :     expected_result.Clear();
     238             : 
     239             :     // Cost of change is less than the difference between target value and utxo sum
     240           1 :     BOOST_CHECK(!SelectCoinsBnB(GroupCoins(utxo_pool), 0.9 * CENT, 0));
     241           1 :     expected_result.Clear();
     242             : 
     243             :     // Select 10 Cent
     244           1 :     add_coin(5 * CENT, 5, utxo_pool);
     245           1 :     add_coin(4 * CENT, 4, expected_result);
     246           1 :     add_coin(3 * CENT, 3, expected_result);
     247           1 :     add_coin(2 * CENT, 2, expected_result);
     248           1 :     add_coin(1 * CENT, 1, expected_result);
     249           1 :     const auto result5 = SelectCoinsBnB(GroupCoins(utxo_pool), 10 * CENT, 0.5 * CENT);
     250           1 :     BOOST_CHECK(result5);
     251           1 :     BOOST_CHECK(EquivalentResult(expected_result, *result5));
     252           1 :     BOOST_CHECK_EQUAL(result5->GetSelectedValue(), 10 * CENT);
     253           1 :     expected_result.Clear();
     254             : 
     255             :     // Select 0.25 Cent, not possible
     256           1 :     BOOST_CHECK(!SelectCoinsBnB(GroupCoins(utxo_pool), 0.25 * CENT, 0.5 * CENT));
     257           1 :     expected_result.Clear();
     258             : 
     259             :     // Iteration exhaustion test
     260           1 :     CAmount target = make_hard_case(17, utxo_pool);
     261           1 :     BOOST_CHECK(!SelectCoinsBnB(GroupCoins(utxo_pool), target, 0)); // Should exhaust
     262           1 :     target = make_hard_case(14, utxo_pool);
     263           1 :     const auto result7 = SelectCoinsBnB(GroupCoins(utxo_pool), target, 0); // Should not exhaust
     264           1 :     BOOST_CHECK(result7);
     265             : 
     266             :     // Test same value early bailout optimization
     267           1 :     utxo_pool.clear();
     268           1 :     add_coin(7 * CENT, 7, expected_result);
     269           1 :     add_coin(7 * CENT, 7, expected_result);
     270           1 :     add_coin(7 * CENT, 7, expected_result);
     271           1 :     add_coin(7 * CENT, 7, expected_result);
     272           1 :     add_coin(2 * CENT, 7, expected_result);
     273           1 :     add_coin(7 * CENT, 7, utxo_pool);
     274           1 :     add_coin(7 * CENT, 7, utxo_pool);
     275           1 :     add_coin(7 * CENT, 7, utxo_pool);
     276           1 :     add_coin(7 * CENT, 7, utxo_pool);
     277           1 :     add_coin(2 * CENT, 7, utxo_pool);
     278       50001 :     for (int i = 0; i < 50000; ++i) {
     279       50000 :         add_coin(5 * CENT, 7, utxo_pool);
     280       50000 :     }
     281           1 :     const auto result8 = SelectCoinsBnB(GroupCoins(utxo_pool), 30 * CENT, 5000);
     282           1 :     BOOST_CHECK(result8);
     283           1 :     BOOST_CHECK_EQUAL(result8->GetSelectedValue(), 30 * CENT);
     284           1 :     BOOST_CHECK(EquivalentResult(expected_result, *result8));
     285             : 
     286             :     ////////////////////
     287             :     // Behavior tests //
     288             :     ////////////////////
     289             :     // Select 1 Cent with pool of only greater than 5 Cent
     290           1 :     utxo_pool.clear();
     291          17 :     for (int i = 5; i <= 20; ++i) {
     292          16 :         add_coin(i * CENT, i, utxo_pool);
     293          16 :     }
     294             :     // Run 100 times, to make sure it is never finding a solution
     295         101 :     for (int i = 0; i < 100; ++i) {
     296         100 :         BOOST_CHECK(!SelectCoinsBnB(GroupCoins(utxo_pool), 1 * CENT, 2 * CENT));
     297         100 :     }
     298             : 
     299             :     // Make sure that effective value is working in AttemptSelection when BnB is used
     300           1 :     CoinSelectionParams coin_selection_params_bnb{
     301             :         rand,
     302             :         /*change_output_size=*/ 0,
     303             :         /*change_spend_size=*/ 0,
     304             :         /*min_change_target=*/ 0,
     305           1 :         /*effective_feerate=*/ CFeeRate(3000),
     306           1 :         /*long_term_feerate=*/ CFeeRate(1000),
     307           1 :         /*discard_feerate=*/ CFeeRate(1000),
     308             :         /*tx_noinputs_size=*/ 0,
     309             :         /*avoid_partial=*/ false,
     310             :     };
     311           1 :     coin_selection_params_bnb.m_subtract_fee_outputs = true;
     312             : 
     313             :     {
     314           1 :         std::unique_ptr<CWallet> wallet = std::make_unique<CWallet>(m_node.chain.get(), /*coinjoin_loader=*/nullptr, "", m_args, CreateMockWalletDatabase());
     315           1 :         wallet->LoadWallet();
     316           1 :         LOCK(wallet->cs_wallet);
     317           1 :         wallet->SetWalletFlag(WALLET_FLAG_DESCRIPTORS);
     318           1 :         wallet->SetupDescriptorScriptPubKeyMans("", "");
     319             : 
     320           1 :         CoinsResult available_coins;
     321             : 
     322           1 :         add_coin(available_coins, *wallet, 1, coin_selection_params_bnb.m_effective_feerate);
     323           1 :         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
     324           1 :         BOOST_CHECK(!SelectCoinsBnB(GroupCoins(available_coins.all()), 1 * CENT, coin_selection_params_bnb.m_cost_of_change));
     325             : 
     326             :         // Test fees subtracted from output:
     327           1 :         available_coins.clear();
     328           1 :         add_coin(available_coins, *wallet, 1 * CENT, coin_selection_params_bnb.m_effective_feerate);
     329           1 :         available_coins.all().at(0).input_bytes = 40;
     330           1 :         const auto result9 = SelectCoinsBnB(GroupCoins(available_coins.all()), 1 * CENT, coin_selection_params_bnb.m_cost_of_change);
     331           1 :         BOOST_CHECK(result9);
     332           1 :         BOOST_CHECK_EQUAL(result9->GetSelectedValue(), 1 * CENT);
     333           1 :     }
     334             : 
     335             :     {
     336           1 :         std::unique_ptr<CWallet> wallet = std::make_unique<CWallet>(m_node.chain.get(), /*coinjoin_loader=*/nullptr, "", m_args, CreateMockWalletDatabase());
     337           1 :         wallet->LoadWallet();
     338           1 :         LOCK(wallet->cs_wallet);
     339           1 :         wallet->SetWalletFlag(WALLET_FLAG_DESCRIPTORS);
     340           1 :         wallet->SetupDescriptorScriptPubKeyMans("", "");
     341             : 
     342           1 :         CoinsResult available_coins;
     343             : 
     344           1 :         add_coin(available_coins, *wallet, 5 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true);
     345           1 :         add_coin(available_coins, *wallet, 3 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true);
     346           1 :         add_coin(available_coins, *wallet, 2 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true);
     347           1 :         CCoinControl coin_control;
     348           1 :         coin_control.m_allow_other_inputs = true;
     349           1 :         coin_control.Select(available_coins.all().at(0).outpoint);
     350           1 :         coin_selection_params_bnb.m_effective_feerate = CFeeRate(0);
     351           1 :         const auto result10 = SelectCoins(*wallet, available_coins, 10 * CENT, coin_control, coin_selection_params_bnb);
     352           1 :         BOOST_CHECK(result10);
     353           1 :     }
     354             :     {
     355           1 :         std::unique_ptr<CWallet> wallet = std::make_unique<CWallet>(m_node.chain.get(), /*coinjoin_loader=*/nullptr, "", m_args, CreateMockWalletDatabase());
     356           1 :         wallet->LoadWallet();
     357           1 :         LOCK(wallet->cs_wallet);
     358           1 :         wallet->SetWalletFlag(WALLET_FLAG_DESCRIPTORS);
     359           1 :         wallet->SetupDescriptorScriptPubKeyMans("", "");
     360             : 
     361           1 :         CoinsResult available_coins;
     362             : 
     363             :         // single coin should be selected when effective fee > long term fee
     364           1 :         coin_selection_params_bnb.m_effective_feerate = CFeeRate(5000);
     365           1 :         coin_selection_params_bnb.m_long_term_feerate = CFeeRate(3000);
     366             : 
     367           1 :         add_coin(available_coins, *wallet, 10 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true);
     368           1 :         add_coin(available_coins, *wallet, 9 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true);
     369           1 :         add_coin(available_coins, *wallet, 1 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true);
     370             : 
     371           1 :         expected_result.Clear();
     372           1 :         add_coin(10 * CENT, 2, expected_result);
     373           1 :         CCoinControl coin_control;
     374           1 :         const auto result11 = SelectCoins(*wallet, available_coins, 10 * CENT, coin_control, coin_selection_params_bnb);
     375           1 :         BOOST_CHECK(EquivalentResult(expected_result, *result11));
     376           1 :         available_coins.clear();
     377             : 
     378             :         // more coins should be selected when effective fee < long term fee
     379           1 :         coin_selection_params_bnb.m_effective_feerate = CFeeRate(3000);
     380           1 :         coin_selection_params_bnb.m_long_term_feerate = CFeeRate(5000);
     381             : 
     382           1 :         add_coin(available_coins, *wallet, 10 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true);
     383           1 :         add_coin(available_coins, *wallet, 9 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true);
     384           1 :         add_coin(available_coins, *wallet, 1 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true);
     385             : 
     386           1 :         expected_result.Clear();
     387           1 :         add_coin(9 * CENT, 2, expected_result);
     388           1 :         add_coin(1 * CENT, 2, expected_result);
     389           1 :         const auto result12 = SelectCoins(*wallet, available_coins, 10 * CENT, coin_control, coin_selection_params_bnb);
     390             :         // NOTE: Dash does not use BnB and therefore, this check will fail
     391             :         // BOOST_CHECK(EquivalentResult(expected_result, *result12));
     392           1 :         available_coins.clear();
     393             : 
     394             :         // pre selected coin should be selected even if disadvantageous
     395           1 :         coin_selection_params_bnb.m_effective_feerate = CFeeRate(5000);
     396           1 :         coin_selection_params_bnb.m_long_term_feerate = CFeeRate(3000);
     397             : 
     398           1 :         add_coin(available_coins, *wallet, 10 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true);
     399           1 :         add_coin(available_coins, *wallet, 9 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true);
     400           1 :         add_coin(available_coins, *wallet, 1 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true);
     401             : 
     402           1 :         expected_result.Clear();
     403           1 :         add_coin(9 * CENT, 2, expected_result);
     404           1 :         add_coin(1 * CENT, 2, expected_result);
     405           1 :         coin_control.m_allow_other_inputs = true;
     406           1 :         coin_control.Select(available_coins.all().at(1).outpoint); // pre select 9 coin
     407           1 :         const auto result13 = SelectCoins(*wallet, available_coins, 10 * CENT, coin_control, coin_selection_params_bnb);
     408           1 :         BOOST_CHECK(EquivalentResult(expected_result, *result13));
     409           1 :     }
     410           1 : }
     411             : 
     412         148 : BOOST_AUTO_TEST_CASE(knapsack_solver_test)
     413             : {
     414           1 :     FastRandomContext rand{};
     415        5601 :     const auto temp1{[&rand](std::vector<OutputGroup>& g, const CAmount& v, CAmount c) { return KnapsackSolver(g, v, c, rand); }};
     416           1 :     const auto KnapsackSolver{temp1};
     417           1 :     std::unique_ptr<CWallet> wallet = std::make_unique<CWallet>(m_node.chain.get(), /*coinjoin_loader=*/nullptr, "", m_args, CreateMockWalletDatabase());
     418           1 :     wallet->LoadWallet();
     419           1 :     LOCK(wallet->cs_wallet);
     420           1 :     wallet->SetWalletFlag(WALLET_FLAG_DESCRIPTORS);
     421           1 :     wallet->SetupDescriptorScriptPubKeyMans("", "");
     422             : 
     423           1 :     CoinsResult available_coins;
     424             : 
     425             :     // test multiple times to allow for differences in the shuffle order
     426         101 :     for (int i = 0; i < RUN_TESTS; i++)
     427             :     {
     428         100 :         available_coins.clear();
     429             : 
     430             :         // with an empty wallet we can't even pay one cent
     431         100 :         BOOST_CHECK(!KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_standard), 1 * CENT, CENT));
     432             : 
     433         100 :         add_coin(available_coins, *wallet, 1*CENT, CFeeRate(0), 4);        // add a new 1 cent coin
     434             : 
     435             :         // with a new 1 cent coin, we still can't find a mature 1 cent
     436         100 :         BOOST_CHECK(!KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_standard), 1 * CENT, CENT));
     437             : 
     438             :         // but we can find a new 1 cent
     439         100 :         const auto result1 = KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_confirmed), 1 * CENT, CENT);
     440         100 :         BOOST_CHECK(result1);
     441         100 :         BOOST_CHECK_EQUAL(result1->GetSelectedValue(), 1 * CENT);
     442             : 
     443         100 :         add_coin(available_coins, *wallet, 2*CENT);           // add a mature 2 cent coin
     444             : 
     445             :         // we can't make 3 cents of mature coins
     446         100 :         BOOST_CHECK(!KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_standard), 3 * CENT, CENT));
     447             : 
     448             :         // we can make 3 cents of new coins
     449         100 :         const auto result2 = KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_confirmed), 3 * CENT, CENT);
     450         100 :         BOOST_CHECK(result2);
     451         100 :         BOOST_CHECK_EQUAL(result2->GetSelectedValue(), 3 * CENT);
     452             : 
     453         100 :         add_coin(available_coins, *wallet, 5*CENT);           // add a mature 5 cent coin,
     454         100 :         add_coin(available_coins, *wallet, 10*CENT, CFeeRate(0), 3, true); // a new 10 cent coin sent from one of our own addresses
     455         100 :         add_coin(available_coins, *wallet, 20*CENT);          // and a mature 20 cent coin
     456             : 
     457             :         // now we have new: 1+10=11 (of which 10 was self-sent), and mature: 2+5+20=27.  total = 38
     458             : 
     459             :         // we can't make 38 cents only if we disallow new coins:
     460         100 :         BOOST_CHECK(!KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_standard), 38 * CENT, CENT));
     461             :         // we can't even make 37 cents if we don't allow new coins even if they're from us
     462         100 :         BOOST_CHECK(!KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_standard_extra), 38 * CENT, CENT));
     463             :         // but we can make 37 cents if we accept new coins from ourself
     464         100 :         const auto result3 = KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_standard), 37 * CENT, CENT);
     465         100 :         BOOST_CHECK(result3);
     466         100 :         BOOST_CHECK_EQUAL(result3->GetSelectedValue(), 37 * CENT);
     467             :         // and we can make 38 cents if we accept all new coins
     468         100 :         const auto result4 = KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_confirmed), 38 * CENT, CENT);
     469         100 :         BOOST_CHECK(result4);
     470         100 :         BOOST_CHECK_EQUAL(result4->GetSelectedValue(), 38 * CENT);
     471             : 
     472             :         // try making 34 cents from 1,2,5,10,20 - we can't do it exactly
     473         100 :         const auto result5 = KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_confirmed), 34 * CENT, CENT);
     474         100 :         BOOST_CHECK(result5);
     475         100 :         BOOST_CHECK_EQUAL(result5->GetSelectedValue(), 35 * CENT);       // but 35 cents is closest
     476         100 :         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)
     477             : 
     478             :         // when we try making 7 cents, the smaller coins (1,2,5) are enough.  We should see just 2+5
     479         100 :         const auto result6 = KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_confirmed), 7 * CENT, CENT);
     480         100 :         BOOST_CHECK(result6);
     481         100 :         BOOST_CHECK_EQUAL(result6->GetSelectedValue(), 7 * CENT);
     482         100 :         BOOST_CHECK_EQUAL(result6->GetInputSet().size(), 2U);
     483             : 
     484             :         // when we try making 8 cents, the smaller coins (1,2,5) are exactly enough.
     485         100 :         const auto result7 = KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_confirmed), 8 * CENT, CENT);
     486         100 :         BOOST_CHECK(result7);
     487         100 :         BOOST_CHECK(result7->GetSelectedValue() == 8 * CENT);
     488         100 :         BOOST_CHECK_EQUAL(result7->GetInputSet().size(), 3U);
     489             : 
     490             :         // when we try making 9 cents, no subset of smaller coins is enough, and we get the next bigger coin (10)
     491         100 :         const auto result8 = KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_confirmed), 9 * CENT, CENT);
     492         100 :         BOOST_CHECK(result8);
     493         100 :         BOOST_CHECK_EQUAL(result8->GetSelectedValue(), 10 * CENT);
     494         100 :         BOOST_CHECK_EQUAL(result8->GetInputSet().size(), 1U);
     495             : 
     496             :         // now clear out the wallet and start again to test choosing between subsets of smaller coins and the next biggest coin
     497         100 :         available_coins.clear();
     498             : 
     499         100 :         add_coin(available_coins, *wallet,  6*CENT);
     500         100 :         add_coin(available_coins, *wallet,  7*CENT);
     501         100 :         add_coin(available_coins, *wallet,  8*CENT);
     502         100 :         add_coin(available_coins, *wallet, 20*CENT);
     503         100 :         add_coin(available_coins, *wallet, 30*CENT); // now we have 6+7+8+20+30 = 71 cents total
     504             : 
     505             :         // check that we have 71 and not 72
     506         100 :         const auto result9 = KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_confirmed), 71 * CENT, CENT);
     507         100 :         BOOST_CHECK(result9);
     508         100 :         BOOST_CHECK(!KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_confirmed), 72 * CENT, CENT));
     509             : 
     510             :         // 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
     511         100 :         const auto result10 = KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_confirmed), 16 * CENT, CENT);
     512         100 :         BOOST_CHECK(result10);
     513         100 :         BOOST_CHECK_EQUAL(result10->GetSelectedValue(), 20 * CENT); // we should get 20 in one coin
     514         100 :         BOOST_CHECK_EQUAL(result10->GetInputSet().size(), 1U);
     515             : 
     516         100 :         add_coin(available_coins, *wallet,  5*CENT); // now we have 5+6+7+8+20+30 = 75 cents total
     517             : 
     518             :         // 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
     519         100 :         const auto result11 = KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_confirmed), 16 * CENT, CENT);
     520         100 :         BOOST_CHECK(result11);
     521         100 :         BOOST_CHECK_EQUAL(result11->GetSelectedValue(), 18 * CENT); // we should get 18 in 3 coins
     522         100 :         BOOST_CHECK_EQUAL(result11->GetInputSet().size(), 3U);
     523             : 
     524         100 :         add_coin(available_coins, *wallet,  18*CENT); // now we have 5+6+7+8+18+20+30
     525             : 
     526             :         // 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
     527         100 :         const auto result12 = KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_confirmed), 16 * CENT, CENT);
     528         100 :         BOOST_CHECK(result12);
     529         100 :         BOOST_CHECK_EQUAL(result12->GetSelectedValue(), 18 * CENT);  // we should get 18 in 1 coin
     530         100 :         BOOST_CHECK_EQUAL(result12->GetInputSet().size(), 1U); // because in the event of a tie, the biggest coin wins
     531             : 
     532             :         // now try making 11 cents.  we should get 5+6
     533         100 :         const auto result13 = KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_confirmed), 11 * CENT, CENT);
     534         100 :         BOOST_CHECK(result13);
     535         100 :         BOOST_CHECK_EQUAL(result13->GetSelectedValue(), 11 * CENT);
     536         100 :         BOOST_CHECK_EQUAL(result13->GetInputSet().size(), 2U);
     537             : 
     538             :         // check that the smallest bigger coin is used
     539         100 :         add_coin(available_coins, *wallet,  1*COIN);
     540         100 :         add_coin(available_coins, *wallet,  2*COIN);
     541         100 :         add_coin(available_coins, *wallet,  3*COIN);
     542         100 :         add_coin(available_coins, *wallet,  4*COIN); // now we have 5+6+7+8+18+20+30+100+200+300+400 = 1094 cents
     543         100 :         const auto result14 = KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_confirmed), 95 * CENT, CENT);
     544         100 :         BOOST_CHECK(result14);
     545         100 :         BOOST_CHECK_EQUAL(result14->GetSelectedValue(), 1 * COIN);  // we should get 1 BTC in 1 coin
     546         100 :         BOOST_CHECK_EQUAL(result14->GetInputSet().size(), 1U);
     547             : 
     548         100 :         const auto result15 = KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_confirmed), 195 * CENT, CENT);
     549         100 :         BOOST_CHECK(result15);
     550         100 :         BOOST_CHECK_EQUAL(result15->GetSelectedValue(), 2 * COIN);  // we should get 2 BTC in 1 coin
     551         100 :         BOOST_CHECK_EQUAL(result15->GetInputSet().size(), 1U);
     552             : 
     553             :         // empty the wallet and start again, now with fractions of a cent, to test small change avoidance
     554             : 
     555         100 :         available_coins.clear();
     556         100 :         add_coin(available_coins, *wallet, CENT * 1 / 10);
     557         100 :         add_coin(available_coins, *wallet, CENT * 2 / 10);
     558         100 :         add_coin(available_coins, *wallet, CENT * 3 / 10);
     559         100 :         add_coin(available_coins, *wallet, CENT * 4 / 10);
     560         100 :         add_coin(available_coins, *wallet, CENT * 5 / 10);
     561             : 
     562             :         // try making 1 * CENT from the 1.5 * CENT
     563             :         // we'll get change smaller than CENT whatever happens, so can expect CENT exactly
     564         100 :         const auto result16 = KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_confirmed), CENT, CENT);
     565         100 :         BOOST_CHECK(result16);
     566         100 :         BOOST_CHECK_EQUAL(result16->GetSelectedValue(), CENT);
     567             : 
     568             :         // but if we add a bigger coin, small change is avoided
     569         100 :         add_coin(available_coins, *wallet, 1111*CENT);
     570             : 
     571             :         // try making 1 from 0.1 + 0.2 + 0.3 + 0.4 + 0.5 + 1111 = 1112.5
     572         100 :         const auto result17 = KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_confirmed), 1 * CENT, CENT);
     573         100 :         BOOST_CHECK(result17);
     574         100 :         BOOST_CHECK_EQUAL(result17->GetSelectedValue(), 1 * CENT); // we should get the exact amount
     575             : 
     576             :         // if we add more small coins:
     577         100 :         add_coin(available_coins, *wallet, CENT * 6 / 10);
     578         100 :         add_coin(available_coins, *wallet, CENT * 7 / 10);
     579             : 
     580             :         // and try again to make 1.0 * CENT
     581         100 :         const auto result18 = KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_confirmed), 1 * CENT, CENT);
     582         100 :         BOOST_CHECK(result18);
     583         100 :         BOOST_CHECK_EQUAL(result18->GetSelectedValue(), 1 * CENT); // we should get the exact amount
     584             : 
     585             :         // run the 'mtgox' test (see https://blockexplorer.com/tx/29a3efd3ef04f9153d47a990bd7b048a4b2d213daaa5fb8ed670fb85f13bdbcf)
     586             :         // they tried to consolidate 10 50k coins into one 500k coin, and ended up with 50k in change
     587         100 :         available_coins.clear();
     588        2100 :         for (int j = 0; j < 20; j++)
     589        2000 :             add_coin(available_coins, *wallet, 50000 * COIN);
     590             : 
     591         100 :         const auto result19 = KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_confirmed), 500000 * COIN, CENT);
     592         100 :         BOOST_CHECK(result19);
     593         100 :         BOOST_CHECK_EQUAL(result19->GetSelectedValue(), 500000 * COIN); // we should get the exact amount
     594         100 :         BOOST_CHECK_EQUAL(result19->GetInputSet().size(), 10U); // in ten coins
     595             : 
     596             :         // if there's not enough in the smaller coins to make at least 1 * CENT change (0.5+0.6+0.7 < 1.0+1.0),
     597             :         // we need to try finding an exact subset anyway
     598             : 
     599             :         // sometimes it will fail, and so we use the next biggest coin:
     600         100 :         available_coins.clear();
     601         100 :         add_coin(available_coins, *wallet, CENT * 5 / 10);
     602         100 :         add_coin(available_coins, *wallet, CENT * 6 / 10);
     603         100 :         add_coin(available_coins, *wallet, CENT * 7 / 10);
     604         100 :         add_coin(available_coins, *wallet, 1111 * CENT);
     605         100 :         const auto result20 = KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_confirmed), 1 * CENT, CENT);
     606         100 :         BOOST_CHECK(result20);
     607         100 :         BOOST_CHECK_EQUAL(result20->GetSelectedValue(), 1111 * CENT); // we get the bigger coin
     608         100 :         BOOST_CHECK_EQUAL(result20->GetInputSet().size(), 1U);
     609             : 
     610             :         // but sometimes it's possible, and we use an exact subset (0.4 + 0.6 = 1.0)
     611         100 :         available_coins.clear();
     612         100 :         add_coin(available_coins, *wallet, CENT * 4 / 10);
     613         100 :         add_coin(available_coins, *wallet, CENT * 6 / 10);
     614         100 :         add_coin(available_coins, *wallet, CENT * 8 / 10);
     615         100 :         add_coin(available_coins, *wallet, 1111 * CENT);
     616         100 :         const auto result21 = KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_confirmed), CENT, CENT);
     617         100 :         BOOST_CHECK(result21);
     618         100 :         BOOST_CHECK_EQUAL(result21->GetSelectedValue(), CENT);   // we should get the exact amount
     619         100 :         BOOST_CHECK_EQUAL(result21->GetInputSet().size(), 2U); // in two coins 0.4+0.6
     620             : 
     621             :         // test avoiding small change
     622         100 :         available_coins.clear();
     623         100 :         add_coin(available_coins, *wallet, CENT * 5 / 100);
     624         100 :         add_coin(available_coins, *wallet, CENT * 1);
     625         100 :         add_coin(available_coins, *wallet, CENT * 100);
     626             : 
     627             :         // trying to make 100.01 from these three coins
     628         100 :         const auto result22 = KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_confirmed), CENT * 10001 / 100, CENT);
     629         100 :         BOOST_CHECK(result22);
     630         100 :         BOOST_CHECK_EQUAL(result22->GetSelectedValue(), CENT * 10105 / 100); // we should get all coins
     631         100 :         BOOST_CHECK_EQUAL(result22->GetInputSet().size(), 3U);
     632             : 
     633             :         // but if we try to make 99.9, we should take the bigger of the two small coins to avoid small change
     634         100 :         const auto result23 = KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_confirmed), CENT * 9990 / 100, CENT);
     635         100 :         BOOST_CHECK(result23);
     636         100 :         BOOST_CHECK_EQUAL(result23->GetSelectedValue(), 101 * CENT);
     637         100 :         BOOST_CHECK_EQUAL(result23->GetInputSet().size(), 2U);
     638         100 :     }
     639             : 
     640             :     // test with many inputs
     641           6 :     for (CAmount amt=1500; amt < COIN; amt*=10) {
     642           5 :         available_coins.clear();
     643             :         // Create 676 inputs (=  (old MAX_STANDARD_TX_SIZE == 100000)  / 148 bytes per input)
     644        3385 :         for (uint16_t j = 0; j < 676; j++)
     645        3380 :             add_coin(available_coins, *wallet, amt);
     646             : 
     647             :         // We only create the wallet once to save time, but we still run the coin selection RUN_TESTS times.
     648         505 :         for (int i = 0; i < RUN_TESTS; i++) {
     649         500 :             const auto result24 = KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_confirmed), 2000, CENT);
     650         500 :             BOOST_CHECK(result24);
     651             : 
     652         500 :             if (amt - 2000 < CENT) {
     653             :                 // needs more than one input:
     654         300 :                 uint16_t returnSize = std::ceil((2000.0 + CENT)/amt);
     655         300 :                 CAmount returnValue = amt * returnSize;
     656         300 :                 BOOST_CHECK_EQUAL(result24->GetSelectedValue(), returnValue);
     657         300 :                 BOOST_CHECK_EQUAL(result24->GetInputSet().size(), returnSize);
     658         300 :             } else {
     659             :                 // one input is sufficient:
     660         200 :                 BOOST_CHECK_EQUAL(result24->GetSelectedValue(), amt);
     661         200 :                 BOOST_CHECK_EQUAL(result24->GetInputSet().size(), 1U);
     662             :             }
     663         500 :         }
     664           5 :     }
     665             : 
     666             :     // test randomness
     667             :     {
     668           1 :         available_coins.clear();
     669         101 :         for (int i2 = 0; i2 < 100; i2++)
     670         100 :             add_coin(available_coins, *wallet, COIN);
     671             : 
     672             :         // Again, we only create the wallet once to save time, but we still run the coin selection RUN_TESTS times.
     673         101 :         for (int i = 0; i < RUN_TESTS; i++) {
     674             :             // picking 50 from 100 coins doesn't depend on the shuffle,
     675             :             // but does depend on randomness in the stochastic approximation code
     676         100 :             const auto result25 = KnapsackSolver(GroupCoins(available_coins.all()), 50 * COIN, CENT);
     677         100 :             BOOST_CHECK(result25);
     678         100 :             const auto result26 = KnapsackSolver(GroupCoins(available_coins.all()), 50 * COIN, CENT);
     679         100 :             BOOST_CHECK(result26);
     680         100 :             BOOST_CHECK(!EqualResult(*result25, *result26));
     681             : 
     682         100 :             int fails = 0;
     683         600 :             for (int j = 0; j < RANDOM_REPEATS; j++)
     684             :             {
     685             :                 // Test that the KnapsackSolver selects randomly from equivalent coins (same value and same input size).
     686             :                 // When choosing 1 from 100 identical coins, 1% of the time, this test will choose the same coin twice
     687             :                 // which will cause it to fail.
     688             :                 // To avoid that issue, run the test RANDOM_REPEATS times and only complain if all of them fail
     689         500 :                 const auto result27 = KnapsackSolver(GroupCoins(available_coins.all()), COIN, CENT);
     690         500 :                 BOOST_CHECK(result27);
     691         500 :                 const auto result28 = KnapsackSolver(GroupCoins(available_coins.all()), COIN, CENT);
     692         500 :                 BOOST_CHECK(result28);
     693         500 :                 if (EqualResult(*result27, *result28))
     694           9 :                     fails++;
     695         500 :             }
     696         100 :             BOOST_CHECK_NE(fails, RANDOM_REPEATS);
     697         100 :         }
     698             : 
     699             :         // add 75 cents in small change.  not enough to make 90 cents,
     700             :         // then try making 90 cents.  there are multiple competing "smallest bigger" coins,
     701             :         // one of which should be picked at random
     702           1 :         add_coin(available_coins, *wallet, 5 * CENT);
     703           1 :         add_coin(available_coins, *wallet, 10 * CENT);
     704           1 :         add_coin(available_coins, *wallet, 15 * CENT);
     705           1 :         add_coin(available_coins, *wallet, 20 * CENT);
     706           1 :         add_coin(available_coins, *wallet, 25 * CENT);
     707             : 
     708         101 :         for (int i = 0; i < RUN_TESTS; i++) {
     709         100 :             int fails = 0;
     710         600 :             for (int j = 0; j < RANDOM_REPEATS; j++)
     711             :             {
     712         500 :                 const auto result29 = KnapsackSolver(GroupCoins(available_coins.all()), 90 * CENT, CENT);
     713         500 :                 BOOST_CHECK(result29);
     714         500 :                 const auto result30 = KnapsackSolver(GroupCoins(available_coins.all()), 90 * CENT, CENT);
     715         500 :                 BOOST_CHECK(result30);
     716         500 :                 if (EqualResult(*result29, *result30))
     717           5 :                     fails++;
     718         500 :             }
     719         100 :             BOOST_CHECK_NE(fails, RANDOM_REPEATS);
     720         100 :         }
     721             :     }
     722           1 : }
     723             : 
     724         148 : BOOST_AUTO_TEST_CASE(ApproximateBestSubset)
     725             : {
     726           1 :     FastRandomContext rand{};
     727           1 :     std::unique_ptr<CWallet> wallet = std::make_unique<CWallet>(m_node.chain.get(), /*coinjoin_loader=*/nullptr, "", m_args, CreateMockWalletDatabase());
     728           1 :     wallet->LoadWallet();
     729           1 :     LOCK(wallet->cs_wallet);
     730           1 :     wallet->SetWalletFlag(WALLET_FLAG_DESCRIPTORS);
     731           1 :     wallet->SetupDescriptorScriptPubKeyMans("", "");
     732             : 
     733           1 :     CoinsResult available_coins;
     734             : 
     735             :     // Test vValue sort order
     736        1001 :     for (int i = 0; i < 1000; i++)
     737        1000 :         add_coin(available_coins, *wallet, 1000 * COIN);
     738           1 :     add_coin(available_coins, *wallet, 3 * COIN);
     739             : 
     740           1 :     const auto result = KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_standard), 1003 * COIN, CENT, rand);
     741           1 :     BOOST_CHECK(result);
     742           1 :     BOOST_CHECK_EQUAL(result->GetSelectedValue(), 1003 * COIN);
     743           1 :     BOOST_CHECK_EQUAL(result->GetInputSet().size(), 2U);
     744           1 : }
     745             : 
     746             : // Tests that with the ideal conditions, the coin selector will always be able to find a solution that can pay the target value
     747         148 : BOOST_AUTO_TEST_CASE(SelectCoins_test)
     748             : {
     749           1 :     std::unique_ptr<CWallet> wallet = std::make_unique<CWallet>(m_node.chain.get(), /*coinjoin_loader=*/nullptr, "", m_args, CreateMockWalletDatabase());
     750           1 :     wallet->LoadWallet();
     751           1 :     LOCK(wallet->cs_wallet);
     752           1 :     wallet->SetWalletFlag(WALLET_FLAG_DESCRIPTORS);
     753           1 :     wallet->SetupDescriptorScriptPubKeyMans("", "");
     754             : 
     755             :     // Random generator stuff
     756           1 :     std::default_random_engine generator;
     757           1 :     std::exponential_distribution<double> distribution (100);
     758           1 :     FastRandomContext rand;
     759             : 
     760             :     // Run this test 100 times
     761         101 :     for (int i = 0; i < 100; ++i)
     762             :     {
     763         100 :         CoinsResult available_coins;
     764         100 :         CAmount balance{0};
     765             : 
     766             :         // Make a wallet with 1000 exponentially distributed random inputs
     767      100100 :         for (int j = 0; j < 1000; ++j)
     768             :         {
     769      100000 :             CAmount val = distribution(generator)*10000000;
     770      100000 :             add_coin(available_coins, *wallet, val);
     771      100000 :             balance += val;
     772      100000 :         }
     773             : 
     774             :         // Generate a random fee rate in the range of 100 - 400
     775         100 :         CFeeRate rate(rand.randrange(300) + 100);
     776             : 
     777             :         // Generate a random target value between 1000 and wallet balance
     778         100 :         CAmount target = rand.randrange(balance - 1000) + 1000;
     779             : 
     780             :         // Perform selection
     781         100 :         CoinSelectionParams cs_params{
     782             :             rand,
     783             :             /*change_output_size=*/ 34,
     784             :             /*change_spend_size=*/ 148,
     785             :             /*min_change_target=*/ CENT,
     786         100 :             /*effective_feerate=*/ CFeeRate(0),
     787         100 :             /*long_term_feerate=*/ CFeeRate(0),
     788         100 :             /*discard_feerate=*/ CFeeRate(0),
     789             :             /*tx_noinputs_size=*/ 0,
     790             :             /*avoid_partial=*/ false,
     791             :         };
     792         100 :         CCoinControl cc;
     793         100 :         const auto result = SelectCoins(*wallet, available_coins, target, cc, cs_params);
     794         100 :         BOOST_CHECK(result);
     795         100 :         BOOST_CHECK_GE(result->GetSelectedValue(), target);
     796         100 :     }
     797           1 : }
     798             : 
     799         148 : BOOST_AUTO_TEST_CASE(waste_test)
     800             : {
     801           1 :     CoinSet selection;
     802           1 :     const CAmount fee{100};
     803           1 :     const CAmount change_cost{125};
     804           1 :     const CAmount fee_diff{40};
     805           1 :     const CAmount in_amt{3 * COIN};
     806           1 :     const CAmount target{2 * COIN};
     807           1 :     const CAmount excess{in_amt - fee * 2 - target};
     808             : 
     809             :     // Waste with change is the change cost and difference between fee and long term fee
     810           1 :     add_coin(1 * COIN, 1, selection, fee, fee - fee_diff);
     811           1 :     add_coin(2 * COIN, 2, selection, fee, fee - fee_diff);
     812           1 :     const CAmount waste1 = GetSelectionWaste(selection, change_cost, target);
     813           1 :     BOOST_CHECK_EQUAL(fee_diff * 2 + change_cost, waste1);
     814           1 :     selection.clear();
     815             : 
     816             :     // Waste without change is the excess and difference between fee and long term fee
     817           1 :     add_coin(1 * COIN, 1, selection, fee, fee - fee_diff);
     818           1 :     add_coin(2 * COIN, 2, selection, fee, fee - fee_diff);
     819           1 :     const CAmount waste_nochange1 = GetSelectionWaste(selection, 0, target);
     820           1 :     BOOST_CHECK_EQUAL(fee_diff * 2 + excess, waste_nochange1);
     821           1 :     selection.clear();
     822             : 
     823             :     // Waste with change and fee == long term fee is just cost of change
     824           1 :     add_coin(1 * COIN, 1, selection, fee, fee);
     825           1 :     add_coin(2 * COIN, 2, selection, fee, fee);
     826           1 :     BOOST_CHECK_EQUAL(change_cost, GetSelectionWaste(selection, change_cost, target));
     827           1 :     selection.clear();
     828             : 
     829             :     // Waste without change and fee == long term fee is just the excess
     830           1 :     add_coin(1 * COIN, 1, selection, fee, fee);
     831           1 :     add_coin(2 * COIN, 2, selection, fee, fee);
     832           1 :     BOOST_CHECK_EQUAL(excess, GetSelectionWaste(selection, 0, target));
     833           1 :     selection.clear();
     834             : 
     835             :     // Waste will be greater when fee is greater, but long term fee is the same
     836           1 :     add_coin(1 * COIN, 1, selection, fee * 2, fee - fee_diff);
     837           1 :     add_coin(2 * COIN, 2, selection, fee * 2, fee - fee_diff);
     838           1 :     const CAmount waste2 = GetSelectionWaste(selection, change_cost, target);
     839           1 :     BOOST_CHECK_GT(waste2, waste1);
     840           1 :     selection.clear();
     841             : 
     842             :     // Waste with change is the change cost and difference between fee and long term fee
     843             :     // With long term fee greater than fee, waste should be less than when long term fee is less than fee
     844           1 :     add_coin(1 * COIN, 1, selection, fee, fee + fee_diff);
     845           1 :     add_coin(2 * COIN, 2, selection, fee, fee + fee_diff);
     846           1 :     const CAmount waste3 = GetSelectionWaste(selection, change_cost, target);
     847           1 :     BOOST_CHECK_EQUAL(fee_diff * -2 + change_cost, waste3);
     848           1 :     BOOST_CHECK_LT(waste3, waste1);
     849           1 :     selection.clear();
     850             : 
     851             :     // Waste without change is the excess and difference between fee and long term fee
     852             :     // With long term fee greater than fee, waste should be less than when long term fee is less than fee
     853           1 :     add_coin(1 * COIN, 1, selection, fee, fee + fee_diff);
     854           1 :     add_coin(2 * COIN, 2, selection, fee, fee + fee_diff);
     855           1 :     const CAmount waste_nochange2 = GetSelectionWaste(selection, 0, target);
     856           1 :     BOOST_CHECK_EQUAL(fee_diff * -2 + excess, waste_nochange2);
     857           1 :     BOOST_CHECK_LT(waste_nochange2, waste_nochange1);
     858           1 :     selection.clear();
     859             : 
     860             :     // No Waste when fee == long_term_fee, no change, and no excess
     861           1 :     add_coin(1 * COIN, 1, selection, fee, fee);
     862           1 :     add_coin(2 * COIN, 2, selection, fee, fee);
     863           1 :     const CAmount exact_target{in_amt - fee * 2};
     864           1 :     BOOST_CHECK_EQUAL(0, GetSelectionWaste(selection, /*change_cost=*/0, exact_target));
     865           1 :     selection.clear();
     866             : 
     867             :     // No Waste when (fee - long_term_fee) == (-cost_of_change), and no excess
     868           1 :     const CAmount new_change_cost{fee_diff * 2};
     869           1 :     add_coin(1 * COIN, 1, selection, fee, fee + fee_diff);
     870           1 :     add_coin(2 * COIN, 2, selection, fee, fee + fee_diff);
     871           1 :     BOOST_CHECK_EQUAL(0, GetSelectionWaste(selection, new_change_cost, target));
     872           1 :     selection.clear();
     873             : 
     874             :     // No Waste when (fee - long_term_fee) == (-excess), no change cost
     875           1 :     const CAmount new_target{in_amt - fee * 2 - fee_diff * 2};
     876           1 :     add_coin(1 * COIN, 1, selection, fee, fee + fee_diff);
     877           1 :     add_coin(2 * COIN, 2, selection, fee, fee + fee_diff);
     878           1 :     BOOST_CHECK_EQUAL(0, GetSelectionWaste(selection, /*change_cost=*/ 0, new_target));
     879           1 :     selection.clear();
     880             : 
     881             :     // Negative waste when the long term fee is greater than the current fee and the selected value == target
     882           1 :     const CAmount exact_target1{3 * COIN - 2 * fee};
     883           1 :     const CAmount target_waste1{-2 * fee_diff}; // = (2 * fee) - (2 * (fee + fee_diff))
     884           1 :     add_coin(1 * COIN, 1, selection, fee, fee + fee_diff);
     885           1 :     add_coin(2 * COIN, 2, selection, fee, fee + fee_diff);
     886           1 :     BOOST_CHECK_EQUAL(target_waste1, GetSelectionWaste(selection, /*change_cost=*/ 0, exact_target1));
     887           1 :     selection.clear();
     888             : 
     889             :     // Negative waste when the long term fee is greater than the current fee and change_cost < - (inputs * (fee - long_term_fee))
     890           1 :     const CAmount large_fee_diff{90};
     891           1 :     const CAmount target_waste2{-2 * large_fee_diff + change_cost}; // = (2 * fee) - (2 * (fee + large_fee_diff)) + change_cost
     892           1 :     add_coin(1 * COIN, 1, selection, fee, fee + large_fee_diff);
     893           1 :     add_coin(2 * COIN, 2, selection, fee, fee + large_fee_diff);
     894           1 :     BOOST_CHECK_EQUAL(target_waste2, GetSelectionWaste(selection, change_cost, target));
     895           1 : }
     896             : 
     897         148 : BOOST_AUTO_TEST_CASE(effective_value_test)
     898             : {
     899           1 :     const int input_bytes = 148;
     900           1 :     const CFeeRate feerate(1000);
     901           1 :     const CAmount nValue = 10000;
     902           1 :     const int nInput = 0;
     903             : 
     904           1 :     CMutableTransaction tx;
     905           1 :     tx.vout.resize(1);
     906           1 :     tx.vout[nInput].nValue = nValue;
     907             : 
     908             :     // standard case, pass feerate in constructor
     909           1 :     COutput output1(COutPoint(tx.GetHash(), nInput), tx.vout.at(nInput), /*depth=*/ 1, input_bytes, /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, /*time=*/ 0, /*from_me=*/ false, feerate);
     910           1 :     const CAmount expected_ev1 = 9852; // 10000 - 148
     911           1 :     BOOST_CHECK_EQUAL(output1.GetEffectiveValue(), expected_ev1);
     912             : 
     913             :     // input bytes unknown (input_bytes = -1), pass feerate in constructor
     914           1 :     COutput output2(COutPoint(tx.GetHash(), nInput), tx.vout.at(nInput), /*depth=*/ 1, /*input_bytes=*/ -1, /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, /*time=*/ 0, /*from_me=*/ false, feerate);
     915           1 :     BOOST_CHECK_EQUAL(output2.GetEffectiveValue(), nValue); // The effective value should be equal to the absolute value if input_bytes is -1
     916             : 
     917             :     // negative effective value, pass feerate in constructor
     918           1 :     COutput output3(COutPoint(tx.GetHash(), nInput), tx.vout.at(nInput), /*depth=*/ 1, input_bytes, /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, /*time=*/ 0, /*from_me=*/ false, CFeeRate(100000));
     919           1 :     const CAmount expected_ev3 = -4800; // 10000 - 14800
     920           1 :     BOOST_CHECK_EQUAL(output3.GetEffectiveValue(), expected_ev3);
     921             : 
     922             :     // standard case, pass fees in constructor
     923           1 :     const CAmount fees = 148;
     924           1 :     COutput output4(COutPoint(tx.GetHash(), nInput), tx.vout.at(nInput), /*depth=*/ 1, input_bytes, /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, /*time=*/ 0, /*from_me=*/ false, fees);
     925           1 :     BOOST_CHECK_EQUAL(output4.GetEffectiveValue(), expected_ev1);
     926             : 
     927             :     // input bytes unknown (input_bytes = -1), pass fees in constructor
     928           1 :     COutput output5(COutPoint(tx.GetHash(), nInput), tx.vout.at(nInput), /*depth=*/ 1, /*input_bytes=*/ -1, /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, /*time=*/ 0, /*from_me=*/ false, /*fees=*/ 0);
     929           1 :     BOOST_CHECK_EQUAL(output5.GetEffectiveValue(), nValue); // The effective value should be equal to the absolute value if input_bytes is -1
     930           1 : }
     931             : 
     932           3 : static util::Result<SelectionResult> SelectCoinsSRDResult(const CAmount& target,
     933             :                                                           const CoinSelectionParams& cs_params,
     934             :                                                           const node::NodeContext& m_node,
     935             :                                                           int max_weight,
     936             :                                                           std::function<CoinsResult(CWallet&)> coin_setup)
     937             : {
     938           3 :     std::unique_ptr<CWallet> wallet = std::make_unique<CWallet>(m_node.chain.get(), /*coinjoin_loader=*/nullptr, "", gArgs, CreateMockWalletDatabase());
     939           3 :     wallet->LoadWallet();
     940           3 :     LOCK(wallet->cs_wallet);
     941           3 :     wallet->SetWalletFlag(WALLET_FLAG_DESCRIPTORS);
     942           3 :     wallet->SetupDescriptorScriptPubKeyMans("", "");
     943             : 
     944           3 :     CoinEligibilityFilter filter(0, 0, 0);
     945           3 :     auto available_coins = coin_setup(*wallet);
     946           3 :     auto groups = GroupOutputs(*wallet, available_coins.all(), cs_params, filter, /*positive_only=*/true);
     947           3 :     return wallet::SelectCoinsSRD(groups, target, cs_params.rng_fast, max_weight);
     948           3 : }
     949             : 
     950         148 : BOOST_AUTO_TEST_CASE(srd_tests)
     951             : {
     952           1 :     FastRandomContext rand;
     953           1 :     CoinSelectionParams dummy_params{
     954             :         rand,
     955             :         /*change_output_size=*/34,
     956             :         /*change_spend_size=*/68,
     957             :         /*min_change_target=*/CENT,
     958           1 :         /*effective_feerate=*/CFeeRate(0),
     959           1 :         /*long_term_feerate=*/CFeeRate(0),
     960           1 :         /*discard_feerate=*/CFeeRate(0),
     961             :         /*tx_noinputs_size=*/10 + 34,
     962             :         /*avoid_partial=*/false,
     963             :     };
     964             : 
     965             :     {
     966           2 :         const auto& res = SelectCoinsSRDResult(49.5L * COIN, dummy_params, m_node, /*max_weight=*/10000, [&](CWallet& wallet) {
     967           1 :             CoinsResult available_coins;
     968          11 :             for (int j = 0; j < 10; ++j) {
     969          10 :                 add_coin(available_coins, wallet, CAmount(1 * COIN));
     970          10 :                 add_coin(available_coins, wallet, CAmount(2 * COIN));
     971          10 :             }
     972           1 :             return available_coins;
     973           1 :         });
     974           1 :         BOOST_CHECK(!res);
     975           1 :         BOOST_CHECK(util::ErrorString(res).empty());
     976           1 :     }
     977             : 
     978             :     {
     979           2 :         const auto& res = SelectCoinsSRDResult(49.5L * COIN, dummy_params, m_node, /*max_weight=*/1000, [&](CWallet& wallet) {
     980           1 :             CoinsResult available_coins;
     981          11 :             for (int j = 0; j < 10; ++j) {
     982          10 :                 add_coin(available_coins, wallet, CAmount(1 * COIN), CFeeRate(0), 144, false, 0, true, 68);
     983          10 :                 add_coin(available_coins, wallet, CAmount(2 * COIN), CFeeRate(0), 144, false, 0, true, 68);
     984          10 :             }
     985           1 :             return available_coins;
     986           1 :         });
     987           1 :         BOOST_CHECK(!res);
     988           1 :         BOOST_CHECK(util::ErrorString(res).original.find("The inputs size exceeds the maximum weight") != std::string::npos);
     989           1 :     }
     990             : 
     991             :     {
     992           2 :         const auto& res = SelectCoinsSRDResult(25.33L * COIN, dummy_params, m_node, /*max_weight=*/10000, [&](CWallet& wallet) {
     993           1 :             CoinsResult available_coins;
     994          61 :             for (int j = 0; j < 60; ++j) {
     995          60 :                 add_coin(available_coins, wallet, CAmount(0.33 * COIN), CFeeRate(0), 144, false, 0, true, 68);
     996          60 :             }
     997          11 :             for (int i = 0; i < 10; ++i) {
     998          10 :                 add_coin(available_coins, wallet, CAmount(2 * COIN), CFeeRate(0), 144, false, 0, true, 68);
     999          10 :             }
    1000           1 :             return available_coins;
    1001           1 :         });
    1002           1 :         BOOST_CHECK(res);
    1003           1 :     }
    1004           1 : }
    1005             : 
    1006           3 : static std::optional<SelectionResult> select_coins(const CAmount& target, const CoinSelectionParams& cs_params, const CCoinControl& cc, std::function<CoinsResult(CWallet&)> coin_setup, const node::NodeContext& m_node)
    1007             : {
    1008           3 :     std::unique_ptr<CWallet> wallet = std::make_unique<CWallet>(m_node.chain.get(), /*coinjoin_loader=*/nullptr, "", gArgs, CreateMockWalletDatabase());
    1009           3 :     wallet->LoadWallet();
    1010           3 :     LOCK(wallet->cs_wallet);
    1011           3 :     wallet->SetWalletFlag(WALLET_FLAG_DESCRIPTORS);
    1012           3 :     wallet->SetupDescriptorScriptPubKeyMans("", "");
    1013             : 
    1014           3 :     auto available_coins = coin_setup(*wallet);
    1015           3 :     auto result = SelectCoins(*wallet, available_coins, target, cc, cs_params);
    1016           3 :     if (result) {
    1017           2 :         BOOST_CHECK_LE(static_cast<int>(cs_params.tx_noinputs_size) + GetSelectionWeight(*result), MAX_STANDARD_TX_SIZE);
    1018           2 :         BOOST_CHECK_GE(result->GetSelectedValue(), target);
    1019           2 :     }
    1020           3 :     return result;
    1021           3 : }
    1022             : 
    1023           2 : static bool has_coin(const CoinSet& set, CAmount amount)
    1024             : {
    1025           9 :     return std::any_of(set.begin(), set.end(), [&](const auto& coin) { return coin.GetEffectiveValue() == amount; });
    1026             : }
    1027             : 
    1028         148 : BOOST_AUTO_TEST_CASE(check_max_weight)
    1029             : {
    1030           1 :     const CAmount target = 49.5L * COIN;
    1031           1 :     CCoinControl cc;
    1032             : 
    1033           1 :     FastRandomContext rand;
    1034           1 :     CoinSelectionParams cs_params{
    1035             :         rand,
    1036             :         /*change_output_size=*/34,
    1037             :         /*change_spend_size=*/68,
    1038             :         /*min_change_target=*/CENT,
    1039           1 :         /*effective_feerate=*/CFeeRate(0),
    1040           1 :         /*long_term_feerate=*/CFeeRate(0),
    1041           1 :         /*discard_feerate=*/CFeeRate(0),
    1042             :         /*tx_noinputs_size=*/10 + 34,
    1043             :         /*avoid_partial=*/false,
    1044             :     };
    1045             : 
    1046             :     {
    1047           2 :         const auto result = select_coins(target, cs_params, cc, [&](CWallet& wallet) {
    1048           1 :             CoinsResult available_coins;
    1049        1516 :             for (int j = 0; j < 1515; ++j) {
    1050        1515 :                 add_coin(available_coins, wallet, CAmount(0.033 * COIN), CFeeRate(0), 144, false, 0, true, 68);
    1051        1515 :             }
    1052           1 :             add_coin(available_coins, wallet, CAmount(50 * COIN), CFeeRate(0), 144, false, 0, true, 68);
    1053           1 :             return available_coins;
    1054           2 :         }, m_node);
    1055             : 
    1056           1 :         BOOST_CHECK(result);
    1057           1 :         BOOST_CHECK_EQUAL(result->GetInputSet().size(), 1U);
    1058           1 :         BOOST_CHECK_EQUAL(result->GetInputSet().begin()->GetEffectiveValue(), 50 * COIN);
    1059           1 :     }
    1060             : 
    1061             :     {
    1062           2 :         const auto result = select_coins(target, cs_params, cc, [&](CWallet& wallet) {
    1063           1 :             CoinsResult available_coins;
    1064         401 :             for (int j = 0; j < 400; ++j) {
    1065         400 :                 add_coin(available_coins, wallet, CAmount(0.0625 * COIN), CFeeRate(0), 144, false, 0, true, 68);
    1066         400 :             }
    1067        2001 :             for (int j = 0; j < 2000; ++j) {
    1068        2000 :                 add_coin(available_coins, wallet, CAmount(0.025 * COIN), CFeeRate(0), 144, false, 0, true, 68);
    1069        2000 :             }
    1070           1 :             return available_coins;
    1071           2 :         }, m_node);
    1072             : 
    1073           1 :         BOOST_REQUIRE(result);
    1074           1 :         BOOST_CHECK(has_coin(result->GetInputSet(), CAmount(0.0625 * COIN)));
    1075           1 :         BOOST_CHECK(has_coin(result->GetInputSet(), CAmount(0.025 * COIN)));
    1076           1 :     }
    1077             : 
    1078             :     {
    1079           2 :         const auto result = select_coins(target, cs_params, cc, [&](CWallet& wallet) {
    1080           1 :             CoinsResult available_coins;
    1081        1516 :             for (int j = 0; j < 1515; ++j) {
    1082        1515 :                 add_coin(available_coins, wallet, CAmount(0.033 * COIN), CFeeRate(0), 144, false, 0, true, 68);
    1083        1515 :             }
    1084           1 :             return available_coins;
    1085           2 :         }, m_node);
    1086             : 
    1087           1 :         BOOST_CHECK(!result);
    1088           1 :     }
    1089           1 : }
    1090             : 
    1091             : /* --------------------------- Dash-specific tests start here --------------------------- */
    1092         148 : BOOST_AUTO_TEST_CASE(minimum_inputs_test)
    1093             : {
    1094           1 :     std::unique_ptr<CWallet> wallet = std::make_unique<CWallet>(m_node.chain.get(), /*coinjoin_loader=*/nullptr, "", m_args, CreateMockWalletDatabase());
    1095           1 :     wallet->LoadWallet();
    1096           1 :     LOCK(wallet->cs_wallet);
    1097           1 :     wallet->SetWalletFlag(WALLET_FLAG_DESCRIPTORS);
    1098           1 :     wallet->SetupDescriptorScriptPubKeyMans("", "");
    1099             : 
    1100             :     // Create coins (denominations) for a target that can be met without consuming all the coins
    1101           1 :     CoinsResult available_coins{};
    1102             :     CAmount target{25 * COIN};
    1103             :     add_coin(available_coins, *wallet,  9 * COIN, CFeeRate(0), /*nAge=*/6*24, /*fIsFromMe=*/false, /*nInput=*/0, /*spendable=*/true);
    1104             :     add_coin(available_coins, *wallet, 16 * COIN, CFeeRate(0), /*nAge=*/6*24, /*fIsFromMe=*/false, /*nInput=*/0, /*spendable=*/true);
    1105             :     add_coin(available_coins, *wallet, 24 * COIN, CFeeRate(0), /*nAge=*/6*24, /*fIsFromMe=*/false, /*nInput=*/0, /*spendable=*/true);
    1106             : 
    1107             :     // Setup coin control to select from the given coins (!m_allow_other_inputs) *but* consume as little
    1108             :     // as possible (!fRequireAllInputs) and select our coins.
    1109             :     CCoinControl coin_control{};
    1110             :     coin_control.m_allow_other_inputs = false;
    1111             :     coin_control.fRequireAllInputs = false;
    1112             :     for (const auto& coin : available_coins.all()) {
    1113             :         coin_control.Select(coin.outpoint);
    1114             :     }
    1115             : 
    1116             :     // Select coins
    1117             :     FastRandomContext rand{};
    1118             :     CoinSelectionParams coin_selection_params{
    1119             :         rand,
    1120             :         /*change_output_size=*/0,
    1121             :         /*change_spend_size=*/0,
    1122             :         /*min_change_target=*/CENT,
    1123             :         /*effective_feerate=*/CFeeRate(0),
    1124             :         /*long_term_feerate=*/CFeeRate(0),
    1125             :         /*discard_feerate=*/CFeeRate(0),
    1126             :         /*tx_noinputs_size=*/0,
    1127             :         /*avoid_partial=*/false,
    1128             :     };
    1129             :     auto result = SelectCoins(*wallet, available_coins, target, coin_control, coin_selection_params);
    1130             :     BOOST_REQUIRE(result);
    1131             : 
    1132             :     // Should consume only the first two coins (9 + 16) >= 25 and account correctly
    1133             :     BOOST_CHECK_EQUAL(result->GetInputSet().size(), 2);
    1134             :     BOOST_CHECK_EQUAL(result->GetSelectedValue(), 25 * COIN);
    1135           0 : }
    1136             : 
    1137         146 : BOOST_AUTO_TEST_SUITE_END()
    1138             : } // namespace wallet

Generated by: LCOV version 1.16