coinselector_tests.cpp raw

   1  // Copyright (c) 2017-2022 The Limenka 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 <consensus/amount.h>
   6  #include <node/context.h>
   7  #include <policy/policy.h>
   8  #include <primitives/transaction.h>
   9  #include <random.h>
  10  #include <test/util/setup_common.h>
  11  #include <util/translation.h>
  12  #include <wallet/coincontrol.h>
  13  #include <wallet/coinselection.h>
  14  #include <wallet/spend.h>
  15  #include <wallet/test/util.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 <test/util/boost_no_print_int128.h>
  22  #include <random>
  23  
  24  namespace wallet {
  25  BOOST_FIXTURE_TEST_SUITE(coinselector_tests, WalletTestingSetup)
  26  
  27  // how many times to run all the tests to have a chance to catch errors that only show up with particular random shuffles
  28  #define RUN_TESTS 100
  29  
  30  // some tests fail 1% of the time due to bad luck.
  31  // we repeat those tests this many times and only complain if all iterations of the test fail
  32  #define RANDOM_REPEATS 5
  33  
  34  typedef std::set<std::shared_ptr<COutput>> CoinSet;
  35  
  36  static const CoinEligibilityFilter filter_standard(1, 6, 0);
  37  static const CoinEligibilityFilter filter_confirmed(1, 1, 0);
  38  static const CoinEligibilityFilter filter_standard_extra(6, 6, 0);
  39  static int nextLockTime = 0;
  40  
  41  static void add_coin(const CAmount& nValue, int nInput, std::vector<COutput>& set)
  42  {
  43      CMutableTransaction tx;
  44      tx.vout.resize(nInput + 1);
  45      tx.vout[nInput].nValue = nValue;
  46      tx.nLockTime = nextLockTime++;        // so all transactions get different hashes
  47      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);
  48  }
  49  
  50  static void add_coin(const CAmount& nValue, int nInput, SelectionResult& result)
  51  {
  52      CMutableTransaction tx;
  53      tx.vout.resize(nInput + 1);
  54      tx.vout[nInput].nValue = nValue;
  55      tx.nLockTime = nextLockTime++;        // so all transactions get different hashes
  56      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);
  57      OutputGroup group;
  58      group.Insert(std::make_shared<COutput>(output), /*ancestors=*/ 0, /*descendants=*/ 0);
  59      result.AddInput(group);
  60  }
  61  
  62  static void add_coin(const CAmount& nValue, int nInput, SelectionResult& result, CAmount fee, CAmount long_term_fee)
  63  {
  64      CMutableTransaction tx;
  65      tx.vout.resize(nInput + 1);
  66      tx.vout[nInput].nValue = nValue;
  67      tx.nLockTime = nextLockTime++;        // so all transactions get different hashes
  68      std::shared_ptr<COutput> coin = std::make_shared<COutput>(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);
  69      OutputGroup group;
  70      group.Insert(coin, /*ancestors=*/ 0, /*descendants=*/ 0);
  71      coin->long_term_fee = long_term_fee; // group.Insert() will modify long_term_fee, so we need to set it afterwards
  72      result.AddInput(group);
  73  }
  74  
  75  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)
  76  {
  77      CMutableTransaction tx;
  78      tx.nLockTime = nextLockTime++;        // so all transactions get different hashes
  79      tx.vout.resize(nInput + 1);
  80      tx.vout[nInput].nValue = nValue;
  81      if (spendable) {
  82          tx.vout[nInput].scriptPubKey = GetScriptForDestination(*Assert(wallet.GetNewDestination(OutputType::BECH32, "")));
  83      }
  84      uint256 txid = tx.GetHash();
  85  
  86      LOCK(wallet.cs_wallet);
  87      auto ret = wallet.mapWallet.emplace(std::piecewise_construct, std::forward_as_tuple(txid), std::forward_as_tuple(MakeTransactionRef(std::move(tx)), TxStateInactive{}));
  88      assert(ret.second);
  89      CWalletTx& wtx = (*ret.first).second;
  90      const auto& txout = wtx.tx->vout.at(nInput);
  91      available_coins.Add(OutputType::BECH32, {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});
  92  }
  93  
  94  // Helpers
  95  std::optional<SelectionResult> KnapsackSolver(std::vector<OutputGroup>& groups, const CAmount& nTargetValue,
  96                                                CAmount change_target, FastRandomContext& rng)
  97  {
  98      auto res{KnapsackSolver(groups, nTargetValue, change_target, rng, MAX_STANDARD_TX_WEIGHT)};
  99      return res ? std::optional<SelectionResult>(*res) : std::nullopt;
 100  }
 101  
 102  std::optional<SelectionResult> SelectCoinsBnB(std::vector<OutputGroup>& utxo_pool, const CAmount& selection_target, const CAmount& cost_of_change)
 103  {
 104      auto res{SelectCoinsBnB(utxo_pool, selection_target, cost_of_change, MAX_STANDARD_TX_WEIGHT)};
 105      return res ? std::optional<SelectionResult>(*res) : std::nullopt;
 106  }
 107  
 108  /** Check if SelectionResult a is equivalent to SelectionResult b.
 109   * Equivalent means same input values, but maybe different inputs (i.e. same value, different prevout) */
 110  static bool EquivalentResult(const SelectionResult& a, const SelectionResult& b)
 111  {
 112      std::vector<CAmount> a_amts;
 113      std::vector<CAmount> b_amts;
 114      for (const auto& coin : a.GetInputSet()) {
 115          a_amts.push_back(coin->txout.nValue);
 116      }
 117      for (const auto& coin : b.GetInputSet()) {
 118          b_amts.push_back(coin->txout.nValue);
 119      }
 120      std::sort(a_amts.begin(), a_amts.end());
 121      std::sort(b_amts.begin(), b_amts.end());
 122  
 123      std::pair<std::vector<CAmount>::iterator, std::vector<CAmount>::iterator> ret = std::mismatch(a_amts.begin(), a_amts.end(), b_amts.begin());
 124      return ret.first == a_amts.end() && ret.second == b_amts.end();
 125  }
 126  
 127  /** Check if this selection is equal to another one. Equal means same inputs (i.e same value and prevout) */
 128  static bool EqualResult(const SelectionResult& a, const SelectionResult& b)
 129  {
 130      std::pair<CoinSet::iterator, CoinSet::iterator> ret = std::mismatch(a.GetInputSet().begin(), a.GetInputSet().end(), b.GetInputSet().begin(),
 131          [](const std::shared_ptr<COutput>& a, const std::shared_ptr<COutput>& b) {
 132              return a->outpoint == b->outpoint;
 133          });
 134      return ret.first == a.GetInputSet().end() && ret.second == b.GetInputSet().end();
 135  }
 136  
 137  static CAmount make_hard_case(int utxos, std::vector<COutput>& utxo_pool)
 138  {
 139      utxo_pool.clear();
 140      CAmount target = 0;
 141      for (int i = 0; i < utxos; ++i) {
 142          target += CAmount{1} << (utxos+i);
 143          add_coin(CAmount{1} << (utxos+i), 2*i, utxo_pool);
 144          add_coin((CAmount{1} << (utxos+i)) + (CAmount{1} << (utxos-1-i)), 2*i + 1, utxo_pool);
 145      }
 146      return target;
 147  }
 148  
 149  inline std::vector<OutputGroup>& GroupCoins(const std::vector<COutput>& available_coins, bool subtract_fee_outputs = false)
 150  {
 151      static std::vector<OutputGroup> static_groups;
 152      static_groups.clear();
 153      for (auto& coin : available_coins) {
 154          static_groups.emplace_back();
 155          OutputGroup& group = static_groups.back();
 156          group.Insert(std::make_shared<COutput>(coin), /*ancestors=*/ 0, /*descendants=*/ 0);
 157          group.m_subtract_fee_outputs = subtract_fee_outputs;
 158      }
 159      return static_groups;
 160  }
 161  
 162  inline std::vector<OutputGroup>& KnapsackGroupOutputs(const CoinsResult& available_coins, CWallet& wallet, const CoinEligibilityFilter& filter)
 163  {
 164      FastRandomContext rand{};
 165      CoinSelectionParams coin_selection_params{
 166          rand,
 167          /*change_output_size=*/ 0,
 168          /*change_spend_size=*/ 0,
 169          /*min_change_target=*/ CENT,
 170          /*effective_feerate=*/ CFeeRate(0),
 171          /*long_term_feerate=*/ CFeeRate(0),
 172          /*discard_feerate=*/ CFeeRate(0),
 173          /*tx_noinputs_size=*/ 0,
 174          /*avoid_partial=*/ false,
 175      };
 176      static OutputGroupTypeMap static_groups;
 177      static_groups = GroupOutputs(wallet, available_coins, coin_selection_params, {{filter}})[filter];
 178      return static_groups.all_groups.mixed_group;
 179  }
 180  
 181  static std::unique_ptr<CWallet> NewWallet(const node::NodeContext& m_node, const std::string& wallet_name = "")
 182  {
 183      std::unique_ptr<CWallet> wallet = std::make_unique<CWallet>(m_node.chain.get(), wallet_name, CreateMockableWalletDatabase());
 184      BOOST_CHECK(wallet->LoadWallet() == DBErrors::LOAD_OK);
 185      LOCK(wallet->cs_wallet);
 186      wallet->SetWalletFlag(WALLET_FLAG_DESCRIPTORS);
 187      wallet->SetupDescriptorScriptPubKeyMans();
 188      return wallet;
 189  }
 190  
 191  // Branch and bound coin selection tests
 192  BOOST_AUTO_TEST_CASE(bnb_search_test)
 193  {
 194      FastRandomContext rand{};
 195      // Setup
 196      std::vector<COutput> utxo_pool;
 197      SelectionResult expected_result(CAmount(0), SelectionAlgorithm::BNB);
 198  
 199      /////////////////////////
 200      // Known Outcome tests //
 201      /////////////////////////
 202  
 203      // Empty utxo pool
 204      BOOST_CHECK(!SelectCoinsBnB(GroupCoins(utxo_pool), 1 * CENT, 0.5 * CENT));
 205  
 206      // Add utxos
 207      add_coin(1 * CENT, 1, utxo_pool);
 208      add_coin(2 * CENT, 2, utxo_pool);
 209      add_coin(3 * CENT, 3, utxo_pool);
 210      add_coin(4 * CENT, 4, utxo_pool);
 211  
 212      // Select 1 Cent
 213      add_coin(1 * CENT, 1, expected_result);
 214      const auto result1 = SelectCoinsBnB(GroupCoins(utxo_pool), 1 * CENT, 0.5 * CENT);
 215      BOOST_CHECK(result1);
 216      BOOST_CHECK(EquivalentResult(expected_result, *result1));
 217      BOOST_CHECK_EQUAL(result1->GetSelectedValue(), 1 * CENT);
 218      expected_result.Clear();
 219  
 220      // Select 2 Cent
 221      add_coin(2 * CENT, 2, expected_result);
 222      const auto result2 = SelectCoinsBnB(GroupCoins(utxo_pool), 2 * CENT, 0.5 * CENT);
 223      BOOST_CHECK(result2);
 224      BOOST_CHECK(EquivalentResult(expected_result, *result2));
 225      BOOST_CHECK_EQUAL(result2->GetSelectedValue(), 2 * CENT);
 226      expected_result.Clear();
 227  
 228      // Select 5 Cent
 229      add_coin(3 * CENT, 3, expected_result);
 230      add_coin(2 * CENT, 2, expected_result);
 231      const auto result3 = SelectCoinsBnB(GroupCoins(utxo_pool), 5 * CENT, 0.5 * CENT);
 232      BOOST_CHECK(result3);
 233      BOOST_CHECK(EquivalentResult(expected_result, *result3));
 234      BOOST_CHECK_EQUAL(result3->GetSelectedValue(), 5 * CENT);
 235      expected_result.Clear();
 236  
 237      // Select 11 Cent, not possible
 238      BOOST_CHECK(!SelectCoinsBnB(GroupCoins(utxo_pool), 11 * CENT, 0.5 * CENT));
 239      expected_result.Clear();
 240  
 241      // Cost of change is greater than the difference between target value and utxo sum
 242      add_coin(1 * CENT, 1, expected_result);
 243      const auto result4 = SelectCoinsBnB(GroupCoins(utxo_pool), 0.9 * CENT, 0.5 * CENT);
 244      BOOST_CHECK(result4);
 245      BOOST_CHECK_EQUAL(result4->GetSelectedValue(), 1 * CENT);
 246      BOOST_CHECK(EquivalentResult(expected_result, *result4));
 247      expected_result.Clear();
 248  
 249      // Cost of change is less than the difference between target value and utxo sum
 250      BOOST_CHECK(!SelectCoinsBnB(GroupCoins(utxo_pool), 0.9 * CENT, 0));
 251      expected_result.Clear();
 252  
 253      // Select 10 Cent
 254      add_coin(5 * CENT, 5, utxo_pool);
 255      add_coin(4 * CENT, 4, expected_result);
 256      add_coin(3 * CENT, 3, expected_result);
 257      add_coin(2 * CENT, 2, expected_result);
 258      add_coin(1 * CENT, 1, expected_result);
 259      const auto result5 = SelectCoinsBnB(GroupCoins(utxo_pool), 10 * CENT, 0.5 * CENT);
 260      BOOST_CHECK(result5);
 261      BOOST_CHECK(EquivalentResult(expected_result, *result5));
 262      BOOST_CHECK_EQUAL(result5->GetSelectedValue(), 10 * CENT);
 263      expected_result.Clear();
 264  
 265      // Select 0.25 Cent, not possible
 266      BOOST_CHECK(!SelectCoinsBnB(GroupCoins(utxo_pool), 0.25 * CENT, 0.5 * CENT));
 267      expected_result.Clear();
 268  
 269      // Iteration exhaustion test
 270      CAmount target = make_hard_case(17, utxo_pool);
 271      BOOST_CHECK(!SelectCoinsBnB(GroupCoins(utxo_pool), target, 1)); // Should exhaust
 272      target = make_hard_case(14, utxo_pool);
 273      const auto result7 = SelectCoinsBnB(GroupCoins(utxo_pool), target, 1); // Should not exhaust
 274      BOOST_CHECK(result7);
 275  
 276      // Test same value early bailout optimization
 277      utxo_pool.clear();
 278      add_coin(7 * CENT, 7, expected_result);
 279      add_coin(7 * CENT, 7, expected_result);
 280      add_coin(7 * CENT, 7, expected_result);
 281      add_coin(7 * CENT, 7, expected_result);
 282      add_coin(2 * CENT, 7, expected_result);
 283      add_coin(7 * CENT, 7, utxo_pool);
 284      add_coin(7 * CENT, 7, utxo_pool);
 285      add_coin(7 * CENT, 7, utxo_pool);
 286      add_coin(7 * CENT, 7, utxo_pool);
 287      add_coin(2 * CENT, 7, utxo_pool);
 288      for (int i = 0; i < 50000; ++i) {
 289          add_coin(5 * CENT, 7, utxo_pool);
 290      }
 291      const auto result8 = SelectCoinsBnB(GroupCoins(utxo_pool), 30 * CENT, 5000);
 292      BOOST_CHECK(result8);
 293      BOOST_CHECK_EQUAL(result8->GetSelectedValue(), 30 * CENT);
 294      BOOST_CHECK(EquivalentResult(expected_result, *result8));
 295  
 296      ////////////////////
 297      // Behavior tests //
 298      ////////////////////
 299      // Select 1 Cent with pool of only greater than 5 Cent
 300      utxo_pool.clear();
 301      for (int i = 5; i <= 20; ++i) {
 302          add_coin(i * CENT, i, utxo_pool);
 303      }
 304      // Run 100 times, to make sure it is never finding a solution
 305      for (int i = 0; i < 100; ++i) {
 306          BOOST_CHECK(!SelectCoinsBnB(GroupCoins(utxo_pool), 1 * CENT, 2 * CENT));
 307      }
 308  
 309      // Make sure that effective value is working in AttemptSelection when BnB is used
 310      CoinSelectionParams coin_selection_params_bnb{
 311          rand,
 312          /*change_output_size=*/ 31,
 313          /*change_spend_size=*/ 68,
 314          /*min_change_target=*/ 0,
 315          /*effective_feerate=*/ CFeeRate(3000),
 316          /*long_term_feerate=*/ CFeeRate(1000),
 317          /*discard_feerate=*/ CFeeRate(1000),
 318          /*tx_noinputs_size=*/ 0,
 319          /*avoid_partial=*/ false,
 320      };
 321      coin_selection_params_bnb.m_change_fee = coin_selection_params_bnb.m_effective_feerate.GetFee(coin_selection_params_bnb.change_output_size);
 322      coin_selection_params_bnb.m_cost_of_change = coin_selection_params_bnb.m_effective_feerate.GetFee(coin_selection_params_bnb.change_spend_size) + coin_selection_params_bnb.m_change_fee;
 323      coin_selection_params_bnb.min_viable_change = coin_selection_params_bnb.m_effective_feerate.GetFee(coin_selection_params_bnb.change_spend_size);
 324  
 325      {
 326          std::unique_ptr<CWallet> wallet = NewWallet(m_node);
 327  
 328          CoinsResult available_coins;
 329  
 330          add_coin(available_coins, *wallet, 1, coin_selection_params_bnb.m_effective_feerate);
 331          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
 332          BOOST_CHECK(!SelectCoinsBnB(GroupCoins(available_coins.All()), 1 * CENT, coin_selection_params_bnb.m_cost_of_change));
 333  
 334          // Test fees subtracted from output:
 335          available_coins.Clear();
 336          add_coin(available_coins, *wallet, 1 * CENT, coin_selection_params_bnb.m_effective_feerate);
 337          available_coins.All().at(0).input_bytes = 40;
 338          const auto result9 = SelectCoinsBnB(GroupCoins(available_coins.All()), 1 * CENT, coin_selection_params_bnb.m_cost_of_change);
 339          BOOST_CHECK(result9);
 340          BOOST_CHECK_EQUAL(result9->GetSelectedValue(), 1 * CENT);
 341      }
 342  
 343      {
 344          std::unique_ptr<CWallet> wallet = NewWallet(m_node);
 345  
 346          CoinsResult available_coins;
 347  
 348          coin_selection_params_bnb.m_effective_feerate = CFeeRate(0);
 349          add_coin(available_coins, *wallet, 5 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true);
 350          add_coin(available_coins, *wallet, 3 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true);
 351          add_coin(available_coins, *wallet, 2 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true);
 352          CCoinControl coin_control;
 353          coin_control.m_allow_other_inputs = true;
 354          COutput select_coin = available_coins.All().at(0);
 355          coin_control.Select(select_coin.outpoint);
 356          PreSelectedInputs selected_input;
 357          selected_input.Insert(select_coin, coin_selection_params_bnb.m_subtract_fee_outputs);
 358          available_coins.Erase({available_coins.coins[OutputType::BECH32].begin()->outpoint});
 359  
 360          LOCK(wallet->cs_wallet);
 361          const auto result10 = SelectCoins(*wallet, available_coins, selected_input, 10 * CENT, coin_control, coin_selection_params_bnb);
 362          BOOST_CHECK(result10);
 363      }
 364      {
 365          std::unique_ptr<CWallet> wallet = NewWallet(m_node);
 366          LOCK(wallet->cs_wallet); // Every 'SelectCoins' call requires it
 367  
 368          CoinsResult available_coins;
 369  
 370          // single coin should be selected when effective fee > long term fee
 371          coin_selection_params_bnb.m_effective_feerate = CFeeRate(5000);
 372          coin_selection_params_bnb.m_long_term_feerate = CFeeRate(3000);
 373  
 374          // Add selectable outputs, increasing their raw amounts by their input fee to make the effective value equal to the raw amount
 375          CAmount input_fee = coin_selection_params_bnb.m_effective_feerate.GetFee(/*num_bytes=*/68); // bech32 input size (default test output type)
 376          add_coin(available_coins, *wallet, 10 * CENT + input_fee, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true);
 377          add_coin(available_coins, *wallet, 9 * CENT + input_fee, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true);
 378          add_coin(available_coins, *wallet, 1 * CENT + input_fee, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true);
 379  
 380          expected_result.Clear();
 381          add_coin(10 * CENT + input_fee, 2, expected_result);
 382          CCoinControl coin_control;
 383          const auto result11 = SelectCoins(*wallet, available_coins, /*pre_set_inputs=*/{}, 10 * CENT, coin_control, coin_selection_params_bnb);
 384          BOOST_CHECK(EquivalentResult(expected_result, *result11));
 385          available_coins.Clear();
 386  
 387          // more coins should be selected when effective fee < long term fee
 388          coin_selection_params_bnb.m_effective_feerate = CFeeRate(3000);
 389          coin_selection_params_bnb.m_long_term_feerate = CFeeRate(5000);
 390  
 391          // Add selectable outputs, increasing their raw amounts by their input fee to make the effective value equal to the raw amount
 392          input_fee = coin_selection_params_bnb.m_effective_feerate.GetFee(/*num_bytes=*/68); // bech32 input size (default test output type)
 393          add_coin(available_coins, *wallet, 10 * CENT + input_fee, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true);
 394          add_coin(available_coins, *wallet, 9 * CENT + input_fee, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true);
 395          add_coin(available_coins, *wallet, 1 * CENT + input_fee, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true);
 396  
 397          expected_result.Clear();
 398          add_coin(9 * CENT + input_fee, 2, expected_result);
 399          add_coin(1 * CENT + input_fee, 2, expected_result);
 400          const auto result12 = SelectCoins(*wallet, available_coins, /*pre_set_inputs=*/{}, 10 * CENT, coin_control, coin_selection_params_bnb);
 401          BOOST_CHECK(EquivalentResult(expected_result, *result12));
 402          available_coins.Clear();
 403  
 404          // pre selected coin should be selected even if disadvantageous
 405          coin_selection_params_bnb.m_effective_feerate = CFeeRate(5000);
 406          coin_selection_params_bnb.m_long_term_feerate = CFeeRate(3000);
 407  
 408          // Add selectable outputs, increasing their raw amounts by their input fee to make the effective value equal to the raw amount
 409          input_fee = coin_selection_params_bnb.m_effective_feerate.GetFee(/*num_bytes=*/68); // bech32 input size (default test output type)
 410          add_coin(available_coins, *wallet, 10 * CENT + input_fee, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true);
 411          add_coin(available_coins, *wallet, 9 * CENT + input_fee, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true);
 412          add_coin(available_coins, *wallet, 1 * CENT + input_fee, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true);
 413  
 414          expected_result.Clear();
 415          add_coin(9 * CENT + input_fee, 2, expected_result);
 416          add_coin(1 * CENT + input_fee, 2, expected_result);
 417          coin_control.m_allow_other_inputs = true;
 418          COutput select_coin = available_coins.All().at(1); // pre select 9 coin
 419          coin_control.Select(select_coin.outpoint);
 420          PreSelectedInputs selected_input;
 421          selected_input.Insert(select_coin, coin_selection_params_bnb.m_subtract_fee_outputs);
 422          available_coins.Erase({(++available_coins.coins[OutputType::BECH32].begin())->outpoint});
 423          const auto result13 = SelectCoins(*wallet, available_coins, selected_input, 10 * CENT, coin_control, coin_selection_params_bnb);
 424          BOOST_CHECK(EquivalentResult(expected_result, *result13));
 425      }
 426  
 427      {
 428          // Test bnb max weight exceeded
 429          // Inputs set [10, 9, 8, 5, 3, 1], Selection Target = 16 and coin 5 exceeding the max weight.
 430  
 431          std::unique_ptr<CWallet> wallet = NewWallet(m_node);
 432  
 433          CoinsResult available_coins;
 434          add_coin(available_coins, *wallet, 10 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true);
 435          add_coin(available_coins, *wallet, 9 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true);
 436          add_coin(available_coins, *wallet, 8 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true);
 437          add_coin(available_coins, *wallet, 5 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true, /*custom_size=*/MAX_STANDARD_TX_WEIGHT);
 438          add_coin(available_coins, *wallet, 3 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true);
 439          add_coin(available_coins, *wallet, 1 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true);
 440  
 441          CAmount selection_target = 16 * CENT;
 442          const auto& no_res = SelectCoinsBnB(GroupCoins(available_coins.All(), /*subtract_fee_outputs*/true),
 443                                              selection_target, /*cost_of_change=*/0, MAX_STANDARD_TX_WEIGHT);
 444          BOOST_REQUIRE(!no_res);
 445          BOOST_CHECK(util::ErrorString(no_res).original.find("The inputs size exceeds the maximum weight") != std::string::npos);
 446  
 447          // Now add same coin value with a good size and check that it gets selected
 448          add_coin(available_coins, *wallet, 5 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true);
 449          const auto& res = SelectCoinsBnB(GroupCoins(available_coins.All(), /*subtract_fee_outputs*/true), selection_target, /*cost_of_change=*/0);
 450  
 451          expected_result.Clear();
 452          add_coin(8 * CENT, 2, expected_result);
 453          add_coin(5 * CENT, 2, expected_result);
 454          add_coin(3 * CENT, 2, expected_result);
 455          BOOST_CHECK(EquivalentResult(expected_result, *res));
 456      }
 457  }
 458  
 459  BOOST_AUTO_TEST_CASE(bnb_sffo_restriction)
 460  {
 461      // Verify the coin selection process does not produce a BnB solution when SFFO is enabled.
 462      // This is currently problematic because it could require a change output. And BnB is specialized on changeless solutions.
 463      std::unique_ptr<CWallet> wallet = NewWallet(m_node);
 464      WITH_LOCK(wallet->cs_wallet, wallet->SetLastBlockProcessed(300, uint256{})); // set a high block so internal UTXOs are selectable
 465  
 466      FastRandomContext rand{};
 467      CoinSelectionParams params{
 468              rand,
 469              /*change_output_size=*/ 31,  // unused value, p2wpkh output size (wallet default change type)
 470              /*change_spend_size=*/ 68,   // unused value, p2wpkh input size (high-r signature)
 471              /*min_change_target=*/ 0,    // dummy, set later
 472              /*effective_feerate=*/ CFeeRate(3000),
 473              /*long_term_feerate=*/ CFeeRate(1000),
 474              /*discard_feerate=*/ CFeeRate(1000),
 475              /*tx_noinputs_size=*/ 0,
 476              /*avoid_partial=*/ false,
 477      };
 478      params.m_subtract_fee_outputs = true;
 479      params.m_change_fee = params.m_effective_feerate.GetFee(params.change_output_size);
 480      params.m_cost_of_change = params.m_discard_feerate.GetFee(params.change_spend_size) + params.m_change_fee;
 481      params.m_min_change_target = params.m_cost_of_change + 1;
 482      // Add spendable coin at the BnB selection upper bound
 483      CoinsResult available_coins;
 484      add_coin(available_coins, *wallet, COIN + params.m_cost_of_change, /*feerate=*/params.m_effective_feerate, /*nAge=*/6, /*fIsFromMe=*/true, /*nInput=*/0, /*spendable=*/true);
 485      add_coin(available_coins, *wallet, 0.5 * COIN + params.m_cost_of_change, /*feerate=*/params.m_effective_feerate, /*nAge=*/6, /*fIsFromMe=*/true, /*nInput=*/0, /*spendable=*/true);
 486      add_coin(available_coins, *wallet, 0.5 * COIN, /*feerate=*/params.m_effective_feerate, /*nAge=*/6, /*fIsFromMe=*/true, /*nInput=*/0, /*spendable=*/true);
 487      // Knapsack will only find a changeless solution on an exact match to the satoshi, SRD doesn’t look for changeless
 488      // If BnB were run, it would produce a single input solution with the best waste score
 489      auto result = WITH_LOCK(wallet->cs_wallet, return SelectCoins(*wallet, available_coins, /*pre_set_inputs=*/{}, COIN, /*coin_control=*/{}, params));
 490      BOOST_CHECK(result.has_value());
 491      BOOST_CHECK_NE(result->GetAlgo(), SelectionAlgorithm::BNB);
 492      BOOST_CHECK(result->GetInputSet().size() == 2);
 493      // We have only considered BnB, SRD, and Knapsack. Test needs to be reevaluated if new algo is added
 494      BOOST_CHECK(result->GetAlgo() == SelectionAlgorithm::SRD || result->GetAlgo() == SelectionAlgorithm::KNAPSACK);
 495  }
 496  
 497  BOOST_AUTO_TEST_CASE(knapsack_solver_test)
 498  {
 499      FastRandomContext rand{};
 500      const auto temp1{[&rand](std::vector<OutputGroup>& g, const CAmount& v, CAmount c) { return KnapsackSolver(g, v, c, rand); }};
 501      const auto KnapsackSolver{temp1};
 502      std::unique_ptr<CWallet> wallet = NewWallet(m_node);
 503  
 504      CoinsResult available_coins;
 505  
 506      // test multiple times to allow for differences in the shuffle order
 507      for (int i = 0; i < RUN_TESTS; i++)
 508      {
 509          available_coins.Clear();
 510  
 511          // with an empty wallet we can't even pay one cent
 512          BOOST_CHECK(!KnapsackSolver(KnapsackGroupOutputs(available_coins, *wallet, filter_standard), 1 * CENT, CENT));
 513  
 514          add_coin(available_coins, *wallet, 1*CENT, CFeeRate(0), 4);        // add a new 1 cent coin
 515  
 516          // with a new 1 cent coin, we still can't find a mature 1 cent
 517          BOOST_CHECK(!KnapsackSolver(KnapsackGroupOutputs(available_coins, *wallet, filter_standard), 1 * CENT, CENT));
 518  
 519          // but we can find a new 1 cent
 520          const auto result1 = KnapsackSolver(KnapsackGroupOutputs(available_coins, *wallet, filter_confirmed), 1 * CENT, CENT);
 521          BOOST_CHECK(result1);
 522          BOOST_CHECK_EQUAL(result1->GetSelectedValue(), 1 * CENT);
 523  
 524          add_coin(available_coins, *wallet, 2*CENT);           // add a mature 2 cent coin
 525  
 526          // we can't make 3 cents of mature coins
 527          BOOST_CHECK(!KnapsackSolver(KnapsackGroupOutputs(available_coins, *wallet, filter_standard), 3 * CENT, CENT));
 528  
 529          // we can make 3 cents of new coins
 530          const auto result2 = KnapsackSolver(KnapsackGroupOutputs(available_coins, *wallet, filter_confirmed), 3 * CENT, CENT);
 531          BOOST_CHECK(result2);
 532          BOOST_CHECK_EQUAL(result2->GetSelectedValue(), 3 * CENT);
 533  
 534          add_coin(available_coins, *wallet, 5*CENT);           // add a mature 5 cent coin,
 535          add_coin(available_coins, *wallet, 10*CENT, CFeeRate(0), 3, true); // a new 10 cent coin sent from one of our own addresses
 536          add_coin(available_coins, *wallet, 20*CENT);          // and a mature 20 cent coin
 537  
 538          // now we have new: 1+10=11 (of which 10 was self-sent), and mature: 2+5+20=27.  total = 38
 539  
 540          // we can't make 38 cents only if we disallow new coins:
 541          BOOST_CHECK(!KnapsackSolver(KnapsackGroupOutputs(available_coins, *wallet, filter_standard), 38 * CENT, CENT));
 542          // we can't even make 37 cents if we don't allow new coins even if they're from us
 543          BOOST_CHECK(!KnapsackSolver(KnapsackGroupOutputs(available_coins, *wallet, filter_standard_extra), 38 * CENT, CENT));
 544          // but we can make 37 cents if we accept new coins from ourself
 545          const auto result3 = KnapsackSolver(KnapsackGroupOutputs(available_coins, *wallet, filter_standard), 37 * CENT, CENT);
 546          BOOST_CHECK(result3);
 547          BOOST_CHECK_EQUAL(result3->GetSelectedValue(), 37 * CENT);
 548          // and we can make 38 cents if we accept all new coins
 549          const auto result4 = KnapsackSolver(KnapsackGroupOutputs(available_coins, *wallet, filter_confirmed), 38 * CENT, CENT);
 550          BOOST_CHECK(result4);
 551          BOOST_CHECK_EQUAL(result4->GetSelectedValue(), 38 * CENT);
 552  
 553          // try making 34 cents from 1,2,5,10,20 - we can't do it exactly
 554          const auto result5 = KnapsackSolver(KnapsackGroupOutputs(available_coins, *wallet, filter_confirmed), 34 * CENT, CENT);
 555          BOOST_CHECK(result5);
 556          BOOST_CHECK_EQUAL(result5->GetSelectedValue(), 35 * CENT);       // but 35 cents is closest
 557          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)
 558  
 559          // when we try making 7 cents, the smaller coins (1,2,5) are enough.  We should see just 2+5
 560          const auto result6 = KnapsackSolver(KnapsackGroupOutputs(available_coins, *wallet, filter_confirmed), 7 * CENT, CENT);
 561          BOOST_CHECK(result6);
 562          BOOST_CHECK_EQUAL(result6->GetSelectedValue(), 7 * CENT);
 563          BOOST_CHECK_EQUAL(result6->GetInputSet().size(), 2U);
 564  
 565          // when we try making 8 cents, the smaller coins (1,2,5) are exactly enough.
 566          const auto result7 = KnapsackSolver(KnapsackGroupOutputs(available_coins, *wallet, filter_confirmed), 8 * CENT, CENT);
 567          BOOST_CHECK(result7);
 568          BOOST_CHECK(result7->GetSelectedValue() == 8 * CENT);
 569          BOOST_CHECK_EQUAL(result7->GetInputSet().size(), 3U);
 570  
 571          // when we try making 9 cents, no subset of smaller coins is enough, and we get the next bigger coin (10)
 572          const auto result8 = KnapsackSolver(KnapsackGroupOutputs(available_coins, *wallet, filter_confirmed), 9 * CENT, CENT);
 573          BOOST_CHECK(result8);
 574          BOOST_CHECK_EQUAL(result8->GetSelectedValue(), 10 * CENT);
 575          BOOST_CHECK_EQUAL(result8->GetInputSet().size(), 1U);
 576  
 577          // now clear out the wallet and start again to test choosing between subsets of smaller coins and the next biggest coin
 578          available_coins.Clear();
 579  
 580          add_coin(available_coins, *wallet,  6*CENT);
 581          add_coin(available_coins, *wallet,  7*CENT);
 582          add_coin(available_coins, *wallet,  8*CENT);
 583          add_coin(available_coins, *wallet, 20*CENT);
 584          add_coin(available_coins, *wallet, 30*CENT); // now we have 6+7+8+20+30 = 71 cents total
 585  
 586          // check that we have 71 and not 72
 587          const auto result9 = KnapsackSolver(KnapsackGroupOutputs(available_coins, *wallet, filter_confirmed), 71 * CENT, CENT);
 588          BOOST_CHECK(result9);
 589          BOOST_CHECK(!KnapsackSolver(KnapsackGroupOutputs(available_coins, *wallet, filter_confirmed), 72 * CENT, CENT));
 590  
 591          // 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
 592          const auto result10 = KnapsackSolver(KnapsackGroupOutputs(available_coins, *wallet, filter_confirmed), 16 * CENT, CENT);
 593          BOOST_CHECK(result10);
 594          BOOST_CHECK_EQUAL(result10->GetSelectedValue(), 20 * CENT); // we should get 20 in one coin
 595          BOOST_CHECK_EQUAL(result10->GetInputSet().size(), 1U);
 596  
 597          add_coin(available_coins, *wallet,  5*CENT); // now we have 5+6+7+8+20+30 = 75 cents total
 598  
 599          // 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
 600          const auto result11 = KnapsackSolver(KnapsackGroupOutputs(available_coins, *wallet, filter_confirmed), 16 * CENT, CENT);
 601          BOOST_CHECK(result11);
 602          BOOST_CHECK_EQUAL(result11->GetSelectedValue(), 18 * CENT); // we should get 18 in 3 coins
 603          BOOST_CHECK_EQUAL(result11->GetInputSet().size(), 3U);
 604  
 605          add_coin(available_coins, *wallet,  18*CENT); // now we have 5+6+7+8+18+20+30
 606  
 607          // 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
 608          const auto result12 = KnapsackSolver(KnapsackGroupOutputs(available_coins, *wallet, filter_confirmed), 16 * CENT, CENT);
 609          BOOST_CHECK(result12);
 610          BOOST_CHECK_EQUAL(result12->GetSelectedValue(), 18 * CENT);  // we should get 18 in 1 coin
 611          BOOST_CHECK_EQUAL(result12->GetInputSet().size(), 1U); // because in the event of a tie, the biggest coin wins
 612  
 613          // now try making 11 cents.  we should get 5+6
 614          const auto result13 = KnapsackSolver(KnapsackGroupOutputs(available_coins, *wallet, filter_confirmed), 11 * CENT, CENT);
 615          BOOST_CHECK(result13);
 616          BOOST_CHECK_EQUAL(result13->GetSelectedValue(), 11 * CENT);
 617          BOOST_CHECK_EQUAL(result13->GetInputSet().size(), 2U);
 618  
 619          // check that the smallest bigger coin is used
 620          add_coin(available_coins, *wallet,  1*COIN);
 621          add_coin(available_coins, *wallet,  2*COIN);
 622          add_coin(available_coins, *wallet,  3*COIN);
 623          add_coin(available_coins, *wallet,  4*COIN); // now we have 5+6+7+8+18+20+30+100+200+300+400 = 1094 cents
 624          const auto result14 = KnapsackSolver(KnapsackGroupOutputs(available_coins, *wallet, filter_confirmed), 95 * CENT, CENT);
 625          BOOST_CHECK(result14);
 626          BOOST_CHECK_EQUAL(result14->GetSelectedValue(), 1 * COIN);  // we should get 1 BTC in 1 coin
 627          BOOST_CHECK_EQUAL(result14->GetInputSet().size(), 1U);
 628  
 629          const auto result15 = KnapsackSolver(KnapsackGroupOutputs(available_coins, *wallet, filter_confirmed), 195 * CENT, CENT);
 630          BOOST_CHECK(result15);
 631          BOOST_CHECK_EQUAL(result15->GetSelectedValue(), 2 * COIN);  // we should get 2 BTC in 1 coin
 632          BOOST_CHECK_EQUAL(result15->GetInputSet().size(), 1U);
 633  
 634          // empty the wallet and start again, now with fractions of a cent, to test small change avoidance
 635  
 636          available_coins.Clear();
 637          add_coin(available_coins, *wallet, CENT * 1 / 10);
 638          add_coin(available_coins, *wallet, CENT * 2 / 10);
 639          add_coin(available_coins, *wallet, CENT * 3 / 10);
 640          add_coin(available_coins, *wallet, CENT * 4 / 10);
 641          add_coin(available_coins, *wallet, CENT * 5 / 10);
 642  
 643          // try making 1 * CENT from the 1.5 * CENT
 644          // we'll get change smaller than CENT whatever happens, so can expect CENT exactly
 645          const auto result16 = KnapsackSolver(KnapsackGroupOutputs(available_coins, *wallet, filter_confirmed), CENT, CENT);
 646          BOOST_CHECK(result16);
 647          BOOST_CHECK_EQUAL(result16->GetSelectedValue(), CENT);
 648  
 649          // but if we add a bigger coin, small change is avoided
 650          add_coin(available_coins, *wallet, 1111*CENT);
 651  
 652          // try making 1 from 0.1 + 0.2 + 0.3 + 0.4 + 0.5 + 1111 = 1112.5
 653          const auto result17 = KnapsackSolver(KnapsackGroupOutputs(available_coins, *wallet, filter_confirmed), 1 * CENT, CENT);
 654          BOOST_CHECK(result17);
 655          BOOST_CHECK_EQUAL(result17->GetSelectedValue(), 1 * CENT); // we should get the exact amount
 656  
 657          // if we add more small coins:
 658          add_coin(available_coins, *wallet, CENT * 6 / 10);
 659          add_coin(available_coins, *wallet, CENT * 7 / 10);
 660  
 661          // and try again to make 1.0 * CENT
 662          const auto result18 = KnapsackSolver(KnapsackGroupOutputs(available_coins, *wallet, filter_confirmed), 1 * CENT, CENT);
 663          BOOST_CHECK(result18);
 664          BOOST_CHECK_EQUAL(result18->GetSelectedValue(), 1 * CENT); // we should get the exact amount
 665  
 666          // run the 'mtgox' test (see https://blockexplorer.com/tx/29a3efd3ef04f9153d47a990bd7b048a4b2d213daaa5fb8ed670fb85f13bdbcf)
 667          // they tried to consolidate 10 50k coins into one 500k coin, and ended up with 50k in change
 668          available_coins.Clear();
 669          for (int j = 0; j < 20; j++)
 670              add_coin(available_coins, *wallet, 50000 * COIN);
 671  
 672          const auto result19 = KnapsackSolver(KnapsackGroupOutputs(available_coins, *wallet, filter_confirmed), 500000 * COIN, CENT);
 673          BOOST_CHECK(result19);
 674          BOOST_CHECK_EQUAL(result19->GetSelectedValue(), 500000 * COIN); // we should get the exact amount
 675          BOOST_CHECK_EQUAL(result19->GetInputSet().size(), 10U); // in ten coins
 676  
 677          // 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),
 678          // we need to try finding an exact subset anyway
 679  
 680          // sometimes it will fail, and so we use the next biggest coin:
 681          available_coins.Clear();
 682          add_coin(available_coins, *wallet, CENT * 5 / 10);
 683          add_coin(available_coins, *wallet, CENT * 6 / 10);
 684          add_coin(available_coins, *wallet, CENT * 7 / 10);
 685          add_coin(available_coins, *wallet, 1111 * CENT);
 686          const auto result20 = KnapsackSolver(KnapsackGroupOutputs(available_coins, *wallet, filter_confirmed), 1 * CENT, CENT);
 687          BOOST_CHECK(result20);
 688          BOOST_CHECK_EQUAL(result20->GetSelectedValue(), 1111 * CENT); // we get the bigger coin
 689          BOOST_CHECK_EQUAL(result20->GetInputSet().size(), 1U);
 690  
 691          // but sometimes it's possible, and we use an exact subset (0.4 + 0.6 = 1.0)
 692          available_coins.Clear();
 693          add_coin(available_coins, *wallet, CENT * 4 / 10);
 694          add_coin(available_coins, *wallet, CENT * 6 / 10);
 695          add_coin(available_coins, *wallet, CENT * 8 / 10);
 696          add_coin(available_coins, *wallet, 1111 * CENT);
 697          const auto result21 = KnapsackSolver(KnapsackGroupOutputs(available_coins, *wallet, filter_confirmed), CENT, CENT);
 698          BOOST_CHECK(result21);
 699          BOOST_CHECK_EQUAL(result21->GetSelectedValue(), CENT);   // we should get the exact amount
 700          BOOST_CHECK_EQUAL(result21->GetInputSet().size(), 2U); // in two coins 0.4+0.6
 701  
 702          // test avoiding small change
 703          available_coins.Clear();
 704          add_coin(available_coins, *wallet, CENT * 5 / 100);
 705          add_coin(available_coins, *wallet, CENT * 1);
 706          add_coin(available_coins, *wallet, CENT * 100);
 707  
 708          // trying to make 100.01 from these three coins
 709          const auto result22 = KnapsackSolver(KnapsackGroupOutputs(available_coins, *wallet, filter_confirmed), CENT * 10001 / 100, CENT);
 710          BOOST_CHECK(result22);
 711          BOOST_CHECK_EQUAL(result22->GetSelectedValue(), CENT * 10105 / 100); // we should get all coins
 712          BOOST_CHECK_EQUAL(result22->GetInputSet().size(), 3U);
 713  
 714          // but if we try to make 99.9, we should take the bigger of the two small coins to avoid small change
 715          const auto result23 = KnapsackSolver(KnapsackGroupOutputs(available_coins, *wallet, filter_confirmed), CENT * 9990 / 100, CENT);
 716          BOOST_CHECK(result23);
 717          BOOST_CHECK_EQUAL(result23->GetSelectedValue(), 101 * CENT);
 718          BOOST_CHECK_EQUAL(result23->GetInputSet().size(), 2U);
 719      }
 720  
 721      // test with many inputs
 722      for (CAmount amt=1500; amt < COIN; amt*=10) {
 723          available_coins.Clear();
 724          // Create 676 inputs (=  (old MAX_STANDARD_TX_SIZE == 100000)  / 148 bytes per input)
 725          for (uint16_t j = 0; j < 676; j++)
 726              add_coin(available_coins, *wallet, amt);
 727  
 728          // We only create the wallet once to save time, but we still run the coin selection RUN_TESTS times.
 729          for (int i = 0; i < RUN_TESTS; i++) {
 730              const auto result24 = KnapsackSolver(KnapsackGroupOutputs(available_coins, *wallet, filter_confirmed), 2000, CENT);
 731              BOOST_CHECK(result24);
 732  
 733              if (amt - 2000 < CENT) {
 734                  // needs more than one input:
 735                  uint16_t returnSize = std::ceil((2000.0 + CENT)/amt);
 736                  CAmount returnValue = amt * returnSize;
 737                  BOOST_CHECK_EQUAL(result24->GetSelectedValue(), returnValue);
 738                  BOOST_CHECK_EQUAL(result24->GetInputSet().size(), returnSize);
 739              } else {
 740                  // one input is sufficient:
 741                  BOOST_CHECK_EQUAL(result24->GetSelectedValue(), amt);
 742                  BOOST_CHECK_EQUAL(result24->GetInputSet().size(), 1U);
 743              }
 744          }
 745      }
 746  
 747      // test randomness
 748      {
 749          available_coins.Clear();
 750          for (int i2 = 0; i2 < 100; i2++)
 751              add_coin(available_coins, *wallet, COIN);
 752  
 753          // Again, we only create the wallet once to save time, but we still run the coin selection RUN_TESTS times.
 754          for (int i = 0; i < RUN_TESTS; i++) {
 755              // picking 50 from 100 coins doesn't depend on the shuffle,
 756              // but does depend on randomness in the stochastic approximation code
 757              const auto result25 = KnapsackSolver(GroupCoins(available_coins.All()), 50 * COIN, CENT);
 758              BOOST_CHECK(result25);
 759              const auto result26 = KnapsackSolver(GroupCoins(available_coins.All()), 50 * COIN, CENT);
 760              BOOST_CHECK(result26);
 761              BOOST_CHECK(!EqualResult(*result25, *result26));
 762  
 763              int fails = 0;
 764              for (int j = 0; j < RANDOM_REPEATS; j++)
 765              {
 766                  // Test that the KnapsackSolver selects randomly from equivalent coins (same value and same input size).
 767                  // When choosing 1 from 100 identical coins, 1% of the time, this test will choose the same coin twice
 768                  // which will cause it to fail.
 769                  // To avoid that issue, run the test RANDOM_REPEATS times and only complain if all of them fail
 770                  const auto result27 = KnapsackSolver(GroupCoins(available_coins.All()), COIN, CENT);
 771                  BOOST_CHECK(result27);
 772                  const auto result28 = KnapsackSolver(GroupCoins(available_coins.All()), COIN, CENT);
 773                  BOOST_CHECK(result28);
 774                  if (EqualResult(*result27, *result28))
 775                      fails++;
 776              }
 777              BOOST_CHECK_NE(fails, RANDOM_REPEATS);
 778          }
 779  
 780          // add 75 cents in small change.  not enough to make 90 cents,
 781          // then try making 90 cents.  there are multiple competing "smallest bigger" coins,
 782          // one of which should be picked at random
 783          add_coin(available_coins, *wallet, 5 * CENT);
 784          add_coin(available_coins, *wallet, 10 * CENT);
 785          add_coin(available_coins, *wallet, 15 * CENT);
 786          add_coin(available_coins, *wallet, 20 * CENT);
 787          add_coin(available_coins, *wallet, 25 * CENT);
 788  
 789          for (int i = 0; i < RUN_TESTS; i++) {
 790              int fails = 0;
 791              for (int j = 0; j < RANDOM_REPEATS; j++)
 792              {
 793                  const auto result29 = KnapsackSolver(GroupCoins(available_coins.All()), 90 * CENT, CENT);
 794                  BOOST_CHECK(result29);
 795                  const auto result30 = KnapsackSolver(GroupCoins(available_coins.All()), 90 * CENT, CENT);
 796                  BOOST_CHECK(result30);
 797                  if (EqualResult(*result29, *result30))
 798                      fails++;
 799              }
 800              BOOST_CHECK_NE(fails, RANDOM_REPEATS);
 801          }
 802      }
 803  }
 804  
 805  BOOST_AUTO_TEST_CASE(ApproximateBestSubset)
 806  {
 807      FastRandomContext rand{};
 808      std::unique_ptr<CWallet> wallet = NewWallet(m_node);
 809  
 810      CoinsResult available_coins;
 811  
 812      // Test vValue sort order
 813      for (int i = 0; i < 1000; i++)
 814          add_coin(available_coins, *wallet, 1000 * COIN);
 815      add_coin(available_coins, *wallet, 3 * COIN);
 816  
 817      const auto result = KnapsackSolver(KnapsackGroupOutputs(available_coins, *wallet, filter_standard), 1003 * COIN, CENT, rand);
 818      BOOST_CHECK(result);
 819      BOOST_CHECK_EQUAL(result->GetSelectedValue(), 1003 * COIN);
 820      BOOST_CHECK_EQUAL(result->GetInputSet().size(), 2U);
 821  }
 822  
 823  // Tests that with the ideal conditions, the coin selector will always be able to find a solution that can pay the target value
 824  BOOST_AUTO_TEST_CASE(SelectCoins_test)
 825  {
 826      std::unique_ptr<CWallet> wallet = NewWallet(m_node);
 827      LOCK(wallet->cs_wallet); // Every 'SelectCoins' call requires it
 828  
 829      // Random generator stuff
 830      std::default_random_engine generator;
 831      std::exponential_distribution<double> distribution (100);
 832      FastRandomContext rand;
 833  
 834      // Run this test 100 times
 835      for (int i = 0; i < 100; ++i)
 836      {
 837          CoinsResult available_coins;
 838          CAmount balance{0};
 839  
 840          // Make a wallet with 1000 exponentially distributed random inputs
 841          for (int j = 0; j < 1000; ++j)
 842          {
 843              CAmount val = distribution(generator)*10000000;
 844              add_coin(available_coins, *wallet, val);
 845              balance += val;
 846          }
 847  
 848          // Generate a random fee rate in the range of 100 - 400
 849          CFeeRate rate(rand.randrange(300) + 100);
 850  
 851          // Generate a random target value between 1000 and wallet balance
 852          CAmount target = rand.randrange(static_cast<int64_t>(balance - 1000)) + 1000;
 853  
 854          // Perform selection
 855          CoinSelectionParams cs_params{
 856              rand,
 857              /*change_output_size=*/ 34,
 858              /*change_spend_size=*/ 148,
 859              /*min_change_target=*/ CENT,
 860              /*effective_feerate=*/ CFeeRate(0),
 861              /*long_term_feerate=*/ CFeeRate(0),
 862              /*discard_feerate=*/ CFeeRate(0),
 863              /*tx_noinputs_size=*/ 0,
 864              /*avoid_partial=*/ false,
 865          };
 866          cs_params.m_cost_of_change = 1;
 867          cs_params.min_viable_change = 1;
 868          CCoinControl cc;
 869          const auto result = SelectCoins(*wallet, available_coins, /*pre_set_inputs=*/{}, target, cc, cs_params);
 870          BOOST_CHECK(result);
 871          BOOST_CHECK_GE(result->GetSelectedValue(), target);
 872      }
 873  }
 874  
 875  BOOST_AUTO_TEST_CASE(waste_test)
 876  {
 877      const CAmount fee{100};
 878      const CAmount min_viable_change{300};
 879      const CAmount change_cost{125};
 880      const CAmount change_fee{30};
 881      const CAmount fee_diff{40};
 882      const CAmount in_amt{3 * COIN};
 883      const CAmount target{2 * COIN};
 884      const CAmount excess{80};
 885      const CAmount exact_target{in_amt - fee * 2}; // Maximum spendable amount after fees: no change, no excess
 886  
 887      // In the following, we test that the waste is calculated correctly in various scenarios.
 888      // Usually, RecalculateWaste would compute change_fee and change_cost on basis of the
 889      // change output type, current feerate, and discard_feerate, but we use fixed values
 890      // across this test to make the test easier to understand.
 891      {
 892          // Waste with change is the change cost and difference between fee and long term fee
 893          SelectionResult selection1{target, SelectionAlgorithm::MANUAL};
 894          add_coin(1 * COIN, 1, selection1, /*fee=*/fee, /*long_term_fee=*/fee - fee_diff);
 895          add_coin(2 * COIN, 2, selection1, fee, fee - fee_diff);
 896          selection1.RecalculateWaste(min_viable_change, change_cost, change_fee);
 897          BOOST_CHECK_EQUAL(fee_diff * 2 + change_cost, selection1.GetWaste());
 898  
 899          // Waste will be greater when fee is greater, but long term fee is the same
 900          SelectionResult selection2{target, SelectionAlgorithm::MANUAL};
 901          add_coin(1 * COIN, 1, selection2, fee * 2, fee - fee_diff);
 902          add_coin(2 * COIN, 2, selection2, fee * 2, fee - fee_diff);
 903          selection2.RecalculateWaste(min_viable_change, change_cost, change_fee);
 904          BOOST_CHECK_GT(selection2.GetWaste(), selection1.GetWaste());
 905  
 906          // Waste with change is the change cost and difference between fee and long term fee
 907          // With long term fee greater than fee, waste should be less than when long term fee is less than fee
 908          SelectionResult selection3{target, SelectionAlgorithm::MANUAL};
 909          add_coin(1 * COIN, 1, selection3, fee, fee + fee_diff);
 910          add_coin(2 * COIN, 2, selection3, fee, fee + fee_diff);
 911          selection3.RecalculateWaste(min_viable_change, change_cost, change_fee);
 912          BOOST_CHECK_EQUAL(fee_diff * -2 + change_cost, selection3.GetWaste());
 913          BOOST_CHECK_LT(selection3.GetWaste(), selection1.GetWaste());
 914      }
 915  
 916      {
 917          // Waste without change is the excess and difference between fee and long term fee
 918          SelectionResult selection_nochange1{exact_target - excess, SelectionAlgorithm::MANUAL};
 919          add_coin(1 * COIN, 1, selection_nochange1, fee, fee - fee_diff);
 920          add_coin(2 * COIN, 2, selection_nochange1, fee, fee - fee_diff);
 921          selection_nochange1.RecalculateWaste(min_viable_change, change_cost, change_fee);
 922          BOOST_CHECK_EQUAL(fee_diff * 2 + excess, selection_nochange1.GetWaste());
 923  
 924          // Waste without change is the excess and difference between fee and long term fee
 925          // With long term fee greater than fee, waste should be less than when long term fee is less than fee
 926          SelectionResult selection_nochange2{exact_target - excess, SelectionAlgorithm::MANUAL};
 927          add_coin(1 * COIN, 1, selection_nochange2, fee, fee + fee_diff);
 928          add_coin(2 * COIN, 2, selection_nochange2, fee, fee + fee_diff);
 929          selection_nochange2.RecalculateWaste(min_viable_change, change_cost, change_fee);
 930          BOOST_CHECK_EQUAL(fee_diff * -2 + excess, selection_nochange2.GetWaste());
 931          BOOST_CHECK_LT(selection_nochange2.GetWaste(), selection_nochange1.GetWaste());
 932      }
 933  
 934      {
 935          // Waste with change and fee == long term fee is just cost of change
 936          SelectionResult selection{target, SelectionAlgorithm::MANUAL};
 937          add_coin(1 * COIN, 1, selection, fee, fee);
 938          add_coin(2 * COIN, 2, selection, fee, fee);
 939          selection.RecalculateWaste(min_viable_change, change_cost, change_fee);
 940          BOOST_CHECK_EQUAL(change_cost, selection.GetWaste());
 941      }
 942  
 943      {
 944          // Waste without change and fee == long term fee is just the excess
 945          SelectionResult selection{exact_target - excess, SelectionAlgorithm::MANUAL};
 946          add_coin(1 * COIN, 1, selection, fee, fee);
 947          add_coin(2 * COIN, 2, selection, fee, fee);
 948          selection.RecalculateWaste(min_viable_change, change_cost, change_fee);
 949          BOOST_CHECK_EQUAL(excess, selection.GetWaste());
 950      }
 951  
 952      {
 953          // Waste is 0 when fee == long_term_fee, no change, and no excess
 954          SelectionResult selection{exact_target, SelectionAlgorithm::MANUAL};
 955          add_coin(1 * COIN, 1, selection, fee, fee);
 956          add_coin(2 * COIN, 2, selection, fee, fee);
 957          selection.RecalculateWaste(min_viable_change, change_cost , change_fee);
 958          BOOST_CHECK_EQUAL(0, selection.GetWaste());
 959      }
 960  
 961      {
 962          // Waste is 0 when (fee - long_term_fee) == (-cost_of_change), and no excess
 963          SelectionResult selection{target, SelectionAlgorithm::MANUAL};
 964          add_coin(1 * COIN, 1, selection, fee, fee + fee_diff);
 965          add_coin(2 * COIN, 2, selection, fee, fee + fee_diff);
 966          selection.RecalculateWaste(min_viable_change, /*change_cost=*/fee_diff * 2, change_fee);
 967          BOOST_CHECK_EQUAL(0, selection.GetWaste());
 968      }
 969  
 970      {
 971          // Waste is 0 when (fee - long_term_fee) == (-excess), no change cost
 972          const CAmount new_target{exact_target - /*excess=*/fee_diff * 2};
 973          SelectionResult selection{new_target, SelectionAlgorithm::MANUAL};
 974          add_coin(1 * COIN, 1, selection, fee, fee + fee_diff);
 975          add_coin(2 * COIN, 2, selection, fee, fee + fee_diff);
 976          selection.RecalculateWaste(min_viable_change, change_cost, change_fee);
 977          BOOST_CHECK_EQUAL(0, selection.GetWaste());
 978      }
 979  
 980      {
 981          // Negative waste when the long term fee is greater than the current fee and the selected value == target
 982          SelectionResult selection{exact_target, SelectionAlgorithm::MANUAL};
 983          const CAmount target_waste1{-2 * fee_diff}; // = (2 * fee) - (2 * (fee + fee_diff))
 984          add_coin(1 * COIN, 1, selection, fee, fee + fee_diff);
 985          add_coin(2 * COIN, 2, selection, fee, fee + fee_diff);
 986          selection.RecalculateWaste(min_viable_change, change_cost, change_fee);
 987          BOOST_CHECK_EQUAL(target_waste1, selection.GetWaste());
 988      }
 989  
 990      {
 991          // Negative waste when the long term fee is greater than the current fee and change_cost < - (inputs * (fee - long_term_fee))
 992          SelectionResult selection{target, SelectionAlgorithm::MANUAL};
 993          const CAmount large_fee_diff{90};
 994          const CAmount target_waste2{-2 * large_fee_diff + change_cost};
 995          // = (2 * fee) - (2 * (fee + large_fee_diff)) + change_cost
 996          // = (2 * 100) - (2 * (100 + 90)) + 125
 997          // = 200 - 380 + 125 = -55
 998          assert(target_waste2 == -55);
 999          add_coin(1 * COIN, 1, selection, fee, fee + large_fee_diff);
1000          add_coin(2 * COIN, 2, selection, fee, fee + large_fee_diff);
1001          selection.RecalculateWaste(min_viable_change, change_cost, change_fee);
1002          BOOST_CHECK_EQUAL(target_waste2, selection.GetWaste());
1003      }
1004  }
1005  
1006  
1007  BOOST_AUTO_TEST_CASE(bump_fee_test)
1008  {
1009      const CAmount fee{100};
1010      const CAmount min_viable_change{200};
1011      const CAmount change_cost{125};
1012      const CAmount change_fee{35};
1013      const CAmount fee_diff{40};
1014      const CAmount target{2 * COIN};
1015  
1016      {
1017          SelectionResult selection{target, SelectionAlgorithm::MANUAL};
1018          add_coin(1 * COIN, 1, selection, /*fee=*/fee, /*long_term_fee=*/fee + fee_diff);
1019          add_coin(2 * COIN, 2, selection, fee, fee + fee_diff);
1020          const std::vector<std::shared_ptr<COutput>> inputs = selection.GetShuffledInputVector();
1021  
1022          for (size_t i = 0; i < inputs.size(); ++i) {
1023              inputs[i]->ApplyBumpFee(20*(i+1));
1024          }
1025  
1026          selection.RecalculateWaste(min_viable_change, change_cost, change_fee);
1027          CAmount expected_waste = fee_diff * -2 + change_cost + /*bump_fees=*/60;
1028          BOOST_CHECK_EQUAL(expected_waste, selection.GetWaste());
1029  
1030          selection.SetBumpFeeDiscount(30);
1031          selection.RecalculateWaste(min_viable_change, change_cost, change_fee);
1032          expected_waste = fee_diff * -2 + change_cost + /*bump_fees=*/60 - /*group_discount=*/30;
1033          BOOST_CHECK_EQUAL(expected_waste, selection.GetWaste());
1034      }
1035  
1036      {
1037          // Test with changeless transaction
1038          //
1039          // Bump fees and excess both contribute fully to the waste score,
1040          // therefore, a bump fee group discount will not change the waste
1041          // score as long as we do not create change in both instances.
1042          CAmount changeless_target = 3 * COIN - 2 * fee - 100;
1043          SelectionResult selection{changeless_target, SelectionAlgorithm::MANUAL};
1044          add_coin(1 * COIN, 1, selection, /*fee=*/fee, /*long_term_fee=*/fee + fee_diff);
1045          add_coin(2 * COIN, 2, selection, fee, fee + fee_diff);
1046          const std::vector<std::shared_ptr<COutput>> inputs = selection.GetShuffledInputVector();
1047  
1048          for (size_t i = 0; i < inputs.size(); ++i) {
1049              inputs[i]->ApplyBumpFee(20*(i+1));
1050          }
1051  
1052          selection.RecalculateWaste(min_viable_change, change_cost, change_fee);
1053          CAmount expected_waste = fee_diff * -2 + /*bump_fees=*/60 + /*excess = 100 - bump_fees*/40;
1054          BOOST_CHECK_EQUAL(expected_waste, selection.GetWaste());
1055  
1056          selection.SetBumpFeeDiscount(30);
1057          selection.RecalculateWaste(min_viable_change, change_cost, change_fee);
1058          expected_waste = fee_diff * -2 + /*bump_fees=*/60 - /*group_discount=*/30 + /*excess = 100 - bump_fees + group_discount*/70;
1059          BOOST_CHECK_EQUAL(expected_waste, selection.GetWaste());
1060      }
1061  }
1062  
1063  BOOST_AUTO_TEST_CASE(effective_value_test)
1064  {
1065      const int input_bytes = 148;
1066      const CFeeRate feerate(1000);
1067      const CAmount nValue = 10000;
1068      const int nInput = 0;
1069  
1070      CMutableTransaction tx;
1071      tx.vout.resize(1);
1072      tx.vout[nInput].nValue = nValue;
1073  
1074      // standard case, pass feerate in constructor
1075      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);
1076      const CAmount expected_ev1 = 9852; // 10000 - 148
1077      BOOST_CHECK_EQUAL(output1.GetEffectiveValue(), expected_ev1);
1078  
1079      // input bytes unknown (input_bytes = -1), pass feerate in constructor
1080      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);
1081      BOOST_CHECK_EQUAL(output2.GetEffectiveValue(), nValue); // The effective value should be equal to the absolute value if input_bytes is -1
1082  
1083      // negative effective value, pass feerate in constructor
1084      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));
1085      const CAmount expected_ev3 = -4800; // 10000 - 14800
1086      BOOST_CHECK_EQUAL(output3.GetEffectiveValue(), expected_ev3);
1087  
1088      // standard case, pass fees in constructor
1089      const CAmount fees = 148;
1090      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);
1091      BOOST_CHECK_EQUAL(output4.GetEffectiveValue(), expected_ev1);
1092  
1093      // input bytes unknown (input_bytes = -1), pass fees in constructor
1094      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);
1095      BOOST_CHECK_EQUAL(output5.GetEffectiveValue(), nValue); // The effective value should be equal to the absolute value if input_bytes is -1
1096  }
1097  
1098  static util::Result<SelectionResult> CoinGrinder(const CAmount& target,
1099                                                      const CoinSelectionParams& cs_params,
1100                                                      const node::NodeContext& m_node,
1101                                                      int max_selection_weight,
1102                                                      std::function<CoinsResult(CWallet&)> coin_setup)
1103  {
1104      std::unique_ptr<CWallet> wallet = NewWallet(m_node);
1105      CoinEligibilityFilter filter(0, 0, 0); // accept all coins without ancestors
1106      Groups group = GroupOutputs(*wallet, coin_setup(*wallet), cs_params, {{filter}})[filter].all_groups;
1107      return CoinGrinder(group.positive_group, target, cs_params.m_min_change_target, max_selection_weight);
1108  }
1109  
1110  BOOST_AUTO_TEST_CASE(coin_grinder_tests)
1111  {
1112      // Test Coin Grinder:
1113      // 1) Insufficient funds, select all provided coins and fail.
1114      // 2) Exceeded max weight, coin selection always surpasses the max allowed weight.
1115      // 3) Select coins without surpassing the max weight (some coins surpasses the max allowed weight, some others not)
1116      // 4) Test that two less valuable UTXOs with a combined lower weight are preferred over a more valuable heavier UTXO
1117      // 5) Test finding a solution in a UTXO pool with mixed weights
1118      // 6) Test that the lightest solution among many clones is found
1119      // 7) Test that lots of tiny UTXOs can be skipped if they are too heavy while there are enough funds in lookahead
1120  
1121      FastRandomContext rand;
1122      CoinSelectionParams dummy_params{ // Only used to provide the 'avoid_partial' flag.
1123              rand,
1124              /*change_output_size=*/34,
1125              /*change_spend_size=*/68,
1126              /*min_change_target=*/CENT,
1127              /*effective_feerate=*/CFeeRate(5000),
1128              /*long_term_feerate=*/CFeeRate(2000),
1129              /*discard_feerate=*/CFeeRate(1000),
1130              /*tx_noinputs_size=*/10 + 34, // static header size + output size
1131              /*avoid_partial=*/false,
1132      };
1133  
1134      {
1135          // #########################################################
1136          // 1) Insufficient funds, select all provided coins and fail
1137          // #########################################################
1138          CAmount target = 49.5L * COIN;
1139          int max_selection_weight = 10'000; // high enough to not fail for this reason.
1140          const auto& res = CoinGrinder(target, dummy_params, m_node, max_selection_weight, [&](CWallet& wallet) {
1141              CoinsResult available_coins;
1142              for (int j = 0; j < 10; ++j) {
1143                  add_coin(available_coins, wallet, CAmount(1 * COIN));
1144                  add_coin(available_coins, wallet, CAmount(2 * COIN));
1145              }
1146              return available_coins;
1147          });
1148          BOOST_CHECK(!res);
1149          BOOST_CHECK(util::ErrorString(res).empty()); // empty means "insufficient funds"
1150      }
1151  
1152      {
1153          // ###########################
1154          // 2) Test max weight exceeded
1155          // ###########################
1156          CAmount target = 29.5L * COIN;
1157          int max_selection_weight = 3000;
1158          const auto& res = CoinGrinder(target, dummy_params, m_node, max_selection_weight, [&](CWallet& wallet) {
1159              CoinsResult available_coins;
1160              for (int j = 0; j < 10; ++j) {
1161                  add_coin(available_coins, wallet, CAmount(1 * COIN), CFeeRate(5000), 144, false, 0, true);
1162                  add_coin(available_coins, wallet, CAmount(2 * COIN), CFeeRate(5000), 144, false, 0, true);
1163              }
1164              return available_coins;
1165          });
1166          BOOST_CHECK(!res);
1167          BOOST_CHECK(util::ErrorString(res).original.find("The inputs size exceeds the maximum weight") != std::string::npos);
1168      }
1169  
1170      {
1171          // ###############################################################################################################
1172          // 3) Test selection when some coins surpass the max allowed weight while others not. --> must find a good solution
1173          // ################################################################################################################
1174          CAmount target = 25.33L * COIN;
1175          int max_selection_weight = 10'000; // WU
1176          const auto& res = CoinGrinder(target, dummy_params, m_node, max_selection_weight, [&](CWallet& wallet) {
1177              CoinsResult available_coins;
1178              for (int j = 0; j < 60; ++j) { // 60 UTXO --> 19,8 BTC total --> 60 × 272 WU = 16320 WU
1179                  add_coin(available_coins, wallet, CAmount(0.33 * COIN), CFeeRate(5000), 144, false, 0, true);
1180              }
1181              for (int i = 0; i < 10; i++) { // 10 UTXO --> 20 BTC total --> 10 × 272 WU = 2720 WU
1182                  add_coin(available_coins, wallet, CAmount(2 * COIN), CFeeRate(5000), 144, false, 0, true);
1183              }
1184              return available_coins;
1185          });
1186          BOOST_CHECK(res);
1187          // Demonstrate how following improvements reduce iteration count and catch any regressions in the future.
1188          size_t expected_attempts = 37;
1189          BOOST_CHECK_MESSAGE(res->GetSelectionsEvaluated() == expected_attempts, strprintf("Expected %i attempts, but got %i", expected_attempts, res->GetSelectionsEvaluated()));
1190      }
1191  
1192      {
1193          // #################################################################################################################
1194          // 4) Test that two less valuable UTXOs with a combined lower weight are preferred over a more valuable heavier UTXO
1195          // #################################################################################################################
1196          CAmount target =  1.9L * COIN;
1197          int max_selection_weight = 400'000; // WU
1198          const auto& res = CoinGrinder(target, dummy_params, m_node, max_selection_weight, [&](CWallet& wallet) {
1199              CoinsResult available_coins;
1200              add_coin(available_coins, wallet, CAmount(2 * COIN), CFeeRate(5000), 144, false, 0, true, 148);
1201              add_coin(available_coins, wallet, CAmount(1 * COIN), CFeeRate(5000), 144, false, 0, true, 68);
1202              add_coin(available_coins, wallet, CAmount(1 * COIN), CFeeRate(5000), 144, false, 0, true, 68);
1203              return available_coins;
1204          });
1205          SelectionResult expected_result(CAmount(0), SelectionAlgorithm::CG);
1206          add_coin(1 * COIN, 1, expected_result);
1207          add_coin(1 * COIN, 2, expected_result);
1208          BOOST_CHECK(EquivalentResult(expected_result, *res));
1209          // Demonstrate how following improvements reduce iteration count and catch any regressions in the future.
1210          size_t expected_attempts = 3;
1211          BOOST_CHECK_MESSAGE(res->GetSelectionsEvaluated() == expected_attempts, strprintf("Expected %i attempts, but got %i", expected_attempts, res->GetSelectionsEvaluated()));
1212      }
1213  
1214      {
1215          // ###############################################################################################################
1216          // 5) Test finding a solution in a UTXO pool with mixed weights
1217          // ################################################################################################################
1218          CAmount target = 30L * COIN;
1219          int max_selection_weight = 400'000; // WU
1220          const auto& res = CoinGrinder(target, dummy_params, m_node, max_selection_weight, [&](CWallet& wallet) {
1221              CoinsResult available_coins;
1222              for (int j = 0; j < 5; ++j) {
1223                  // Add heavy coins {3, 6, 9, 12, 15}
1224                  add_coin(available_coins, wallet, CAmount((3 + 3 * j) * COIN), CFeeRate(5000), 144, false, 0, true, 350);
1225                  // Add medium coins {2, 5, 8, 11, 14}
1226                  add_coin(available_coins, wallet, CAmount((2 + 3 * j) * COIN), CFeeRate(5000), 144, false, 0, true, 250);
1227                  // Add light coins {1, 4, 7, 10, 13}
1228                  add_coin(available_coins, wallet, CAmount((1 + 3 * j) * COIN), CFeeRate(5000), 144, false, 0, true, 150);
1229              }
1230              return available_coins;
1231          });
1232          BOOST_CHECK(res);
1233          SelectionResult expected_result(CAmount(0), SelectionAlgorithm::CG);
1234          add_coin(14 * COIN, 1, expected_result);
1235          add_coin(13 * COIN, 2, expected_result);
1236          add_coin(4 * COIN, 3, expected_result);
1237          BOOST_CHECK(EquivalentResult(expected_result, *res));
1238          // Demonstrate how following improvements reduce iteration count and catch any regressions in the future.
1239          size_t expected_attempts = 92;
1240          BOOST_CHECK_MESSAGE(res->GetSelectionsEvaluated() == expected_attempts, strprintf("Expected %i attempts, but got %i", expected_attempts, res->GetSelectionsEvaluated()));
1241      }
1242  
1243      {
1244          // #################################################################################################################
1245          // 6) Test that the lightest solution among many clones is found
1246          // #################################################################################################################
1247          CAmount target =  9.9L * COIN;
1248          int max_selection_weight = 400'000; // WU
1249          const auto& res = CoinGrinder(target, dummy_params, m_node, max_selection_weight, [&](CWallet& wallet) {
1250              CoinsResult available_coins;
1251              // Expected Result: 4 + 3 + 2 + 1 = 10 BTC at 400 vB
1252              add_coin(available_coins, wallet, CAmount(4 * COIN), CFeeRate(5000), 144, false, 0, true, 100);
1253              add_coin(available_coins, wallet, CAmount(3 * COIN), CFeeRate(5000), 144, false, 0, true, 100);
1254              add_coin(available_coins, wallet, CAmount(2 * COIN), CFeeRate(5000), 144, false, 0, true, 100);
1255              add_coin(available_coins, wallet, CAmount(1 * COIN), CFeeRate(5000), 144, false, 0, true, 100);
1256              // Distracting clones:
1257              for (int j = 0; j < 100; ++j) {
1258                  add_coin(available_coins, wallet, CAmount(8 * COIN), CFeeRate(5000), 144, false, 0, true, 1000);
1259              }
1260              for (int j = 0; j < 100; ++j) {
1261                  add_coin(available_coins, wallet, CAmount(7 * COIN), CFeeRate(5000), 144, false, 0, true, 800);
1262              }
1263              for (int j = 0; j < 100; ++j) {
1264                  add_coin(available_coins, wallet, CAmount(6 * COIN), CFeeRate(5000), 144, false, 0, true, 600);
1265              }
1266              for (int j = 0; j < 100; ++j) {
1267                  add_coin(available_coins, wallet, CAmount(5 * COIN), CFeeRate(5000), 144, false, 0, true, 400);
1268              }
1269              return available_coins;
1270          });
1271          SelectionResult expected_result(CAmount(0), SelectionAlgorithm::CG);
1272          add_coin(4 * COIN, 0, expected_result);
1273          add_coin(3 * COIN, 0, expected_result);
1274          add_coin(2 * COIN, 0, expected_result);
1275          add_coin(1 * COIN, 0, expected_result);
1276          BOOST_CHECK(EquivalentResult(expected_result, *res));
1277          // Demonstrate how following improvements reduce iteration count and catch any regressions in the future.
1278          size_t expected_attempts = 38;
1279          BOOST_CHECK_MESSAGE(res->GetSelectionsEvaluated() == expected_attempts, strprintf("Expected %i attempts, but got %i", expected_attempts, res->GetSelectionsEvaluated()));
1280      }
1281  
1282      {
1283          // #################################################################################################################
1284          // 7) Test that lots of tiny UTXOs can be skipped if they are too heavy while there are enough funds in lookahead
1285          // #################################################################################################################
1286          CAmount target =  1.9L * COIN;
1287          int max_selection_weight = 40000; // WU
1288          const auto& res = CoinGrinder(target, dummy_params, m_node, max_selection_weight, [&](CWallet& wallet) {
1289              CoinsResult available_coins;
1290              add_coin(available_coins, wallet, CAmount(1.8 * COIN), CFeeRate(5000), 144, false, 0, true, 2500);
1291              add_coin(available_coins, wallet, CAmount(1 * COIN), CFeeRate(5000), 144, false, 0, true, 1000);
1292              add_coin(available_coins, wallet, CAmount(1 * COIN), CFeeRate(5000), 144, false, 0, true, 1000);
1293              for (int j = 0; j < 100; ++j) {
1294                  // make a 100 unique coins only differing by one sat
1295                  add_coin(available_coins, wallet, CAmount(0.01 * COIN + j), CFeeRate(5000), 144, false, 0, true, 110);
1296              }
1297              return available_coins;
1298          });
1299          SelectionResult expected_result(CAmount(0), SelectionAlgorithm::CG);
1300          add_coin(1 * COIN, 1, expected_result);
1301          add_coin(1 * COIN, 2, expected_result);
1302          BOOST_CHECK(EquivalentResult(expected_result, *res));
1303          // Demonstrate how following improvements reduce iteration count and catch any regressions in the future.
1304          size_t expected_attempts = 7;
1305          BOOST_CHECK_MESSAGE(res->GetSelectionsEvaluated() == expected_attempts, strprintf("Expected %i attempts, but got %i", expected_attempts, res->GetSelectionsEvaluated()));
1306      }
1307  }
1308  
1309  static util::Result<SelectionResult> SelectCoinsSRD(const CAmount& target,
1310                                                      const CoinSelectionParams& cs_params,
1311                                                      const node::NodeContext& m_node,
1312                                                      int max_selection_weight,
1313                                                      std::function<CoinsResult(CWallet&)> coin_setup)
1314  {
1315      std::unique_ptr<CWallet> wallet = NewWallet(m_node);
1316      CoinEligibilityFilter filter(0, 0, 0); // accept all coins without ancestors
1317      Groups group = GroupOutputs(*wallet, coin_setup(*wallet), cs_params, {{filter}})[filter].all_groups;
1318      return SelectCoinsSRD(group.positive_group, target, cs_params.m_change_fee, cs_params.rng_fast, max_selection_weight);
1319  }
1320  
1321  BOOST_AUTO_TEST_CASE(srd_tests)
1322  {
1323      // Test SRD:
1324      // 1) Insufficient funds, select all provided coins and fail.
1325      // 2) Exceeded max weight, coin selection always surpasses the max allowed weight.
1326      // 3) Select coins without surpassing the max weight (some coins surpasses the max allowed weight, some others not)
1327  
1328      FastRandomContext rand;
1329      CoinSelectionParams dummy_params{ // Only used to provide the 'avoid_partial' flag.
1330              rand,
1331              /*change_output_size=*/34,
1332              /*change_spend_size=*/68,
1333              /*min_change_target=*/CENT,
1334              /*effective_feerate=*/CFeeRate(0),
1335              /*long_term_feerate=*/CFeeRate(0),
1336              /*discard_feerate=*/CFeeRate(0),
1337              /*tx_noinputs_size=*/10 + 34, // static header size + output size
1338              /*avoid_partial=*/false,
1339      };
1340  
1341      {
1342          // #########################################################
1343          // 1) Insufficient funds, select all provided coins and fail
1344          // #########################################################
1345          CAmount target = 49.5L * COIN;
1346          int max_selection_weight = 10000; // high enough to not fail for this reason.
1347          const auto& res = SelectCoinsSRD(target, dummy_params, m_node, max_selection_weight, [&](CWallet& wallet) {
1348              CoinsResult available_coins;
1349              for (int j = 0; j < 10; ++j) {
1350                  add_coin(available_coins, wallet, CAmount(1 * COIN));
1351                  add_coin(available_coins, wallet, CAmount(2 * COIN));
1352              }
1353              return available_coins;
1354          });
1355          BOOST_CHECK(!res);
1356          BOOST_CHECK(util::ErrorString(res).empty()); // empty means "insufficient funds"
1357      }
1358  
1359      {
1360          // ###########################
1361          // 2) Test max weight exceeded
1362          // ###########################
1363          CAmount target = 49.5L * COIN;
1364          int max_selection_weight = 3000;
1365          const auto& res = SelectCoinsSRD(target, dummy_params, m_node, max_selection_weight, [&](CWallet& wallet) {
1366              CoinsResult available_coins;
1367              for (int j = 0; j < 10; ++j) {
1368                  /* 10 × 1 BTC + 10 × 2 BTC = 30 BTC. 20 × 272 WU = 5440 WU */
1369                  add_coin(available_coins, wallet, CAmount(1 * COIN), CFeeRate(0), 144, false, 0, true);
1370                  add_coin(available_coins, wallet, CAmount(2 * COIN), CFeeRate(0), 144, false, 0, true);
1371              }
1372              return available_coins;
1373          });
1374          BOOST_CHECK(!res);
1375          BOOST_CHECK(util::ErrorString(res).original.find("The inputs size exceeds the maximum weight") != std::string::npos);
1376      }
1377  
1378      {
1379          // ################################################################################################################
1380          // 3) Test selection when some coins surpass the max allowed weight while others not. --> must find a good solution
1381          // ################################################################################################################
1382          CAmount target = 25.33L * COIN;
1383          int max_selection_weight = 10000; // WU
1384          const auto& res = SelectCoinsSRD(target, dummy_params, m_node, max_selection_weight, [&](CWallet& wallet) {
1385              CoinsResult available_coins;
1386              for (int j = 0; j < 60; ++j) { // 60 UTXO --> 19,8 BTC total --> 60 × 272 WU = 16320 WU
1387                  add_coin(available_coins, wallet, CAmount(0.33 * COIN), CFeeRate(0), 144, false, 0, true);
1388              }
1389              for (int i = 0; i < 10; i++) { // 10 UTXO --> 20 BTC total --> 10 × 272 WU = 2720 WU
1390                  add_coin(available_coins, wallet, CAmount(2 * COIN), CFeeRate(0), 144, false, 0, true);
1391              }
1392              return available_coins;
1393          });
1394          BOOST_CHECK(res);
1395      }
1396  }
1397  
1398  static util::Result<SelectionResult> select_coins(const CAmount& target, const CoinSelectionParams& cs_params, const CCoinControl& cc, std::function<CoinsResult(CWallet&)> coin_setup, const node::NodeContext& m_node)
1399  {
1400      std::unique_ptr<CWallet> wallet = NewWallet(m_node);
1401      auto available_coins = coin_setup(*wallet);
1402  
1403      LOCK(wallet->cs_wallet);
1404      auto result = SelectCoins(*wallet, available_coins, /*pre_set_inputs=*/ {}, target, cc, cs_params);
1405      if (result) {
1406          const auto signedTxSize = 10 + 34 + 68 * result->GetInputSet().size(); // static header size + output size + inputs size (P2WPKH)
1407          BOOST_CHECK_LE(signedTxSize * WITNESS_SCALE_FACTOR, MAX_STANDARD_TX_WEIGHT);
1408  
1409          BOOST_CHECK_GE(result->GetSelectedValue(), target);
1410      }
1411      return result;
1412  }
1413  
1414  static bool has_coin(const CoinSet& set, CAmount amount)
1415  {
1416      return std::any_of(set.begin(), set.end(), [&](const auto& coin) { return coin->GetEffectiveValue() == amount; });
1417  }
1418  
1419  BOOST_AUTO_TEST_CASE(check_max_selection_weight)
1420  {
1421      const CAmount target = 49.5L * COIN;
1422      CCoinControl cc;
1423  
1424      FastRandomContext rand;
1425      CoinSelectionParams cs_params{
1426          rand,
1427          /*change_output_size=*/34,
1428          /*change_spend_size=*/68,
1429          /*min_change_target=*/CENT,
1430          /*effective_feerate=*/CFeeRate(0),
1431          /*long_term_feerate=*/CFeeRate(0),
1432          /*discard_feerate=*/CFeeRate(0),
1433          /*tx_noinputs_size=*/10 + 34, // static header size + output size
1434          /*avoid_partial=*/false,
1435      };
1436  
1437      int max_weight = MAX_STANDARD_TX_WEIGHT - WITNESS_SCALE_FACTOR * (cs_params.tx_noinputs_size + cs_params.change_output_size);
1438      {
1439          // Scenario 1:
1440          // The actor starts with 1x 50.0 BTC and 1515x 0.033 BTC (~100.0 BTC total) unspent outputs
1441          // Then tries to spend 49.5 BTC
1442          // The 50.0 BTC output should be selected, because the transaction would otherwise be too large
1443  
1444          // Perform selection
1445  
1446          const auto result = select_coins(
1447              target, cs_params, cc, [&](CWallet& wallet) {
1448                  CoinsResult available_coins;
1449                  for (int j = 0; j < 1515; ++j) {
1450                      add_coin(available_coins, wallet, CAmount(0.033 * COIN), CFeeRate(0), 144, false, 0, true);
1451                  }
1452  
1453                  add_coin(available_coins, wallet, CAmount(50 * COIN), CFeeRate(0), 144, false, 0, true);
1454                  return available_coins;
1455              },
1456              m_node);
1457  
1458          BOOST_CHECK(result);
1459          // Verify that the 50 BTC UTXO was selected, and result is below max_weight
1460          BOOST_CHECK(has_coin(result->GetInputSet(), CAmount(50 * COIN)));
1461          BOOST_CHECK_LE(result->GetWeight(), max_weight);
1462      }
1463  
1464      {
1465          // Scenario 2:
1466  
1467          // The actor starts with 400x 0.0625 BTC and 2000x 0.025 BTC (75.0 BTC total) unspent outputs
1468          // Then tries to spend 49.5 BTC
1469          // A combination of coins should be selected, such that the created transaction is not too large
1470  
1471          // Perform selection
1472          const auto result = select_coins(
1473              target, cs_params, cc, [&](CWallet& wallet) {
1474                  CoinsResult available_coins;
1475                  for (int j = 0; j < 400; ++j) {
1476                      add_coin(available_coins, wallet, CAmount(0.0625 * COIN), CFeeRate(0), 144, false, 0, true);
1477                  }
1478                  for (int j = 0; j < 2000; ++j) {
1479                      add_coin(available_coins, wallet, CAmount(0.025 * COIN), CFeeRate(0), 144, false, 0, true);
1480                  }
1481                  return available_coins;
1482              },
1483              m_node);
1484  
1485          BOOST_CHECK(has_coin(result->GetInputSet(), CAmount(0.0625 * COIN)));
1486          BOOST_CHECK(has_coin(result->GetInputSet(), CAmount(0.025 * COIN)));
1487          BOOST_CHECK_LE(result->GetWeight(), max_weight);
1488      }
1489  
1490      {
1491          // Scenario 3:
1492  
1493          // The actor starts with 1515x 0.033 BTC (49.995 BTC total) unspent outputs
1494          // No results should be returned, because the transaction would be too large
1495  
1496          // Perform selection
1497          const auto result = select_coins(
1498              target, cs_params, cc, [&](CWallet& wallet) {
1499                  CoinsResult available_coins;
1500                  for (int j = 0; j < 1515; ++j) {
1501                      add_coin(available_coins, wallet, CAmount(0.033 * COIN), CFeeRate(0), 144, false, 0, true);
1502                  }
1503                  return available_coins;
1504              },
1505              m_node);
1506  
1507          // No results
1508          // 1515 inputs * 68 bytes = 103,020 bytes
1509          // 103,020 bytes * 4 = 412,080 weight, which is above the MAX_STANDARD_TX_WEIGHT of 400,000
1510          BOOST_CHECK(!result);
1511      }
1512  }
1513  
1514  BOOST_AUTO_TEST_CASE(SelectCoins_effective_value_test)
1515  {
1516      // Test that the effective value is used to check whether preset inputs provide sufficient funds when subtract_fee_outputs is not used.
1517      // This test creates a coin whose value is higher than the target but whose effective value is lower than the target.
1518      // The coin is selected using coin control, with m_allow_other_inputs = false. SelectCoins should fail due to insufficient funds.
1519  
1520      std::unique_ptr<CWallet> wallet = NewWallet(m_node);
1521  
1522      CoinsResult available_coins;
1523      {
1524          std::unique_ptr<CWallet> dummyWallet = NewWallet(m_node, /*wallet_name=*/"dummy");
1525          add_coin(available_coins, *dummyWallet, 100000); // 0.001 BTC
1526      }
1527  
1528      CAmount target{99900}; // 0.000999 BTC
1529  
1530      FastRandomContext rand;
1531      CoinSelectionParams cs_params{
1532          rand,
1533          /*change_output_size=*/34,
1534          /*change_spend_size=*/148,
1535          /*min_change_target=*/1000,
1536          /*effective_feerate=*/CFeeRate(3000),
1537          /*long_term_feerate=*/CFeeRate(1000),
1538          /*discard_feerate=*/CFeeRate(1000),
1539          /*tx_noinputs_size=*/0,
1540          /*avoid_partial=*/false,
1541      };
1542      CCoinControl cc;
1543      cc.m_allow_other_inputs = false;
1544      COutput output = available_coins.All().at(0);
1545      cc.SetInputWeight(output.outpoint, 148);
1546      cc.Select(output.outpoint).SetTxOut(output.txout);
1547  
1548      LOCK(wallet->cs_wallet);
1549      const auto preset_inputs = *Assert(FetchSelectedInputs(*wallet, cc, cs_params));
1550      available_coins.Erase({available_coins.coins[OutputType::BECH32].begin()->outpoint});
1551  
1552      const auto result = SelectCoins(*wallet, available_coins, preset_inputs, target, cc, cs_params);
1553      BOOST_CHECK(!result);
1554  }
1555  
1556  BOOST_FIXTURE_TEST_CASE(wallet_coinsresult_test, BasicTestingSetup)
1557  {
1558      // Test case to verify CoinsResult object sanity.
1559      CoinsResult available_coins;
1560      {
1561          std::unique_ptr<CWallet> dummyWallet = NewWallet(m_node, /*wallet_name=*/"dummy");
1562  
1563          // Add some coins to 'available_coins'
1564          for (int i=0; i<10; i++) {
1565              add_coin(available_coins, *dummyWallet, 1 * COIN);
1566          }
1567      }
1568  
1569      {
1570          // First test case, check that 'CoinsResult::Erase' function works as expected.
1571          // By trying to erase two elements from the 'available_coins' object.
1572          std::unordered_set<COutPoint, SaltedOutpointHasher> outs_to_remove;
1573          const auto& coins = available_coins.All();
1574          for (int i = 0; i < 2; i++) {
1575              outs_to_remove.emplace(coins[i].outpoint);
1576          }
1577          available_coins.Erase(outs_to_remove);
1578  
1579          // Check that the elements were actually removed.
1580          const auto& updated_coins = available_coins.All();
1581          for (const auto& out: outs_to_remove) {
1582              auto it = std::find_if(updated_coins.begin(), updated_coins.end(), [&out](const COutput &coin) {
1583                  return coin.outpoint == out;
1584              });
1585              BOOST_CHECK(it == updated_coins.end());
1586          }
1587          // And verify that no extra element were removed
1588          BOOST_CHECK_EQUAL(available_coins.Size(), 8);
1589      }
1590  }
1591  
1592  BOOST_AUTO_TEST_SUITE_END()
1593  } // namespace wallet
1594