|
| 1 | +// Copyright (c) 2017 The Bitcoin Core developers |
| 2 | +// Distributed under the MIT software license, see the accompanying |
| 3 | +// file COPYING or http://www.opensource.org/licenses/mit-license.php. |
| 4 | + |
| 5 | +#include "wallet/wallet.h" |
| 6 | +#include "wallet/coinselection.h" |
| 7 | +#include "amount.h" |
| 8 | +#include "primitives/transaction.h" |
| 9 | +#include "random.h" |
| 10 | +#include "test/test_bitcoin.h" |
| 11 | +#include "wallet/test/wallet_test_fixture.h" |
| 12 | + |
| 13 | +#include <boost/test/unit_test.hpp> |
| 14 | +#include <random> |
| 15 | + |
| 16 | +BOOST_FIXTURE_TEST_SUITE(coin_selection_tests, WalletTestingSetup) |
| 17 | + |
| 18 | +typedef std::set<CInputCoin> CoinSet; |
| 19 | + |
| 20 | +static std::vector<COutput> vCoins; |
| 21 | +static const CWallet testWallet("dummy", CWalletDBWrapper::CreateDummy()); |
| 22 | + |
| 23 | +static void add_coin(const CAmount& nValue, int nInput, std::vector<CInputCoin>& set) |
| 24 | +{ |
| 25 | + CMutableTransaction tx; |
| 26 | + tx.vout.resize(nInput + 1); |
| 27 | + tx.vout[nInput].nValue = nValue; |
| 28 | + set.emplace_back(MakeTransactionRef(tx), nInput); |
| 29 | +} |
| 30 | + |
| 31 | +static void add_coin(const CAmount& nValue, int nInput, CoinSet& set) |
| 32 | +{ |
| 33 | + CMutableTransaction tx; |
| 34 | + tx.vout.resize(nInput + 1); |
| 35 | + tx.vout[nInput].nValue = nValue; |
| 36 | + set.emplace(MakeTransactionRef(tx), nInput); |
| 37 | +} |
| 38 | + |
| 39 | +static bool equal_sets(CoinSet a, CoinSet b) |
| 40 | +{ |
| 41 | + std::pair<CoinSet::iterator, CoinSet::iterator> ret = mismatch(a.begin(), a.end(), b.begin()); |
| 42 | + return ret.first == a.end() && ret.second == b.end(); |
| 43 | +} |
| 44 | + |
| 45 | +static CAmount make_hard_case(int utxos, std::vector<CInputCoin>& utxo_pool) |
| 46 | +{ |
| 47 | + utxo_pool.clear(); |
| 48 | + CAmount target = 0; |
| 49 | + for (int i = 0; i < utxos; ++i) { |
| 50 | + target += (CAmount)1 << (utxos+i); |
| 51 | + add_coin((CAmount)1 << (utxos+i), 2*i, utxo_pool); |
| 52 | + add_coin(((CAmount)1 << (utxos+i)) + ((CAmount)1 << (utxos-1-i)), 2*i + 1, utxo_pool); |
| 53 | + } |
| 54 | + return target; |
| 55 | +} |
| 56 | + |
| 57 | +// Branch and bound coin selection tests |
| 58 | +BOOST_AUTO_TEST_CASE(bnb_search_test) |
| 59 | +{ |
| 60 | + |
| 61 | + LOCK(testWallet.cs_wallet); |
| 62 | + |
| 63 | + // Setup |
| 64 | + std::vector<CInputCoin> utxo_pool; |
| 65 | + CoinSet selection; |
| 66 | + CoinSet actual_selection; |
| 67 | + CAmount value_ret = 0; |
| 68 | + CAmount not_input_fees = 0; |
| 69 | + |
| 70 | + ///////////////////////// |
| 71 | + // Known Outcome tests // |
| 72 | + ///////////////////////// |
| 73 | + BOOST_TEST_MESSAGE("Testing known outcomes"); |
| 74 | + |
| 75 | + // Empty utxo pool |
| 76 | + BOOST_CHECK(!SelectCoinsBnB(utxo_pool, 1 * CENT, 0.5 * CENT, selection, value_ret, not_input_fees)); |
| 77 | + selection.clear(); |
| 78 | + |
| 79 | + // Add utxos |
| 80 | + add_coin(1 * CENT, 1, utxo_pool); |
| 81 | + add_coin(2 * CENT, 2, utxo_pool); |
| 82 | + add_coin(3 * CENT, 3, utxo_pool); |
| 83 | + add_coin(4 * CENT, 4, utxo_pool); |
| 84 | + |
| 85 | + // Select 1 Cent |
| 86 | + add_coin(1 * CENT, 1, actual_selection); |
| 87 | + BOOST_CHECK(SelectCoinsBnB(utxo_pool, 1 * CENT, 0.5 * CENT, selection, value_ret, not_input_fees)); |
| 88 | + BOOST_CHECK(equal_sets(selection, actual_selection)); |
| 89 | + actual_selection.clear(); |
| 90 | + selection.clear(); |
| 91 | + |
| 92 | + // Select 2 Cent |
| 93 | + add_coin(2 * CENT, 2, actual_selection); |
| 94 | + BOOST_CHECK(SelectCoinsBnB(utxo_pool, 2 * CENT, 0.5 * CENT, selection, value_ret, not_input_fees)); |
| 95 | + BOOST_CHECK(equal_sets(selection, actual_selection)); |
| 96 | + actual_selection.clear(); |
| 97 | + selection.clear(); |
| 98 | + |
| 99 | + // Select 5 Cent |
| 100 | + add_coin(3 * CENT, 3, actual_selection); |
| 101 | + add_coin(2 * CENT, 2, actual_selection); |
| 102 | + BOOST_CHECK(SelectCoinsBnB(utxo_pool, 5 * CENT, 0.5 * CENT, selection, value_ret, not_input_fees)); |
| 103 | + BOOST_CHECK(equal_sets(selection, actual_selection)); |
| 104 | + actual_selection.clear(); |
| 105 | + selection.clear(); |
| 106 | + |
| 107 | + // Select 11 Cent, not possible |
| 108 | + BOOST_CHECK(!SelectCoinsBnB(utxo_pool, 11 * CENT, 0.5 * CENT, selection, value_ret, not_input_fees)); |
| 109 | + actual_selection.clear(); |
| 110 | + selection.clear(); |
| 111 | + |
| 112 | + // Select 10 Cent |
| 113 | + add_coin(5 * CENT, 5, utxo_pool); |
| 114 | + add_coin(4 * CENT, 4, actual_selection); |
| 115 | + add_coin(3 * CENT, 3, actual_selection); |
| 116 | + add_coin(2 * CENT, 2, actual_selection); |
| 117 | + add_coin(1 * CENT, 1, actual_selection); |
| 118 | + BOOST_CHECK(SelectCoinsBnB(utxo_pool, 10 * CENT, 0.5 * CENT, selection, value_ret, not_input_fees)); |
| 119 | + BOOST_CHECK(equal_sets(selection, actual_selection)); |
| 120 | + actual_selection.clear(); |
| 121 | + selection.clear(); |
| 122 | + |
| 123 | + // Negative effective value |
| 124 | + // Select 10 Cent but have 1 Cent not be possible because too small |
| 125 | + add_coin(5 * CENT, 5, actual_selection); |
| 126 | + add_coin(3 * CENT, 3, actual_selection); |
| 127 | + add_coin(2 * CENT, 2, actual_selection); |
| 128 | + BOOST_CHECK(SelectCoinsBnB(utxo_pool, 10 * CENT, 5000, selection, value_ret, not_input_fees)); |
| 129 | + |
| 130 | + // Select 0.25 Cent, not possible |
| 131 | + BOOST_CHECK(!SelectCoinsBnB(utxo_pool, 0.25 * CENT, 0.5 * CENT, selection, value_ret, not_input_fees)); |
| 132 | + actual_selection.clear(); |
| 133 | + selection.clear(); |
| 134 | + |
| 135 | + // Iteration exhaustion test |
| 136 | + CAmount target = make_hard_case(17, utxo_pool); |
| 137 | + BOOST_CHECK(!SelectCoinsBnB(utxo_pool, target, 0, selection, value_ret, not_input_fees)); // Should exhaust |
| 138 | + target = make_hard_case(14, utxo_pool); |
| 139 | + BOOST_CHECK(SelectCoinsBnB(utxo_pool, target, 0, selection, value_ret, not_input_fees)); // Should not exhaust |
| 140 | + |
| 141 | + // Test same value early bailout optimization |
| 142 | + add_coin(7 * CENT, 7, actual_selection); |
| 143 | + add_coin(7 * CENT, 7, actual_selection); |
| 144 | + add_coin(7 * CENT, 7, actual_selection); |
| 145 | + add_coin(7 * CENT, 7, actual_selection); |
| 146 | + add_coin(2 * CENT, 7, actual_selection); |
| 147 | + add_coin(7 * CENT, 7, utxo_pool); |
| 148 | + add_coin(7 * CENT, 7, utxo_pool); |
| 149 | + add_coin(7 * CENT, 7, utxo_pool); |
| 150 | + add_coin(7 * CENT, 7, utxo_pool); |
| 151 | + add_coin(2 * CENT, 7, utxo_pool); |
| 152 | + for (int i = 0; i < 50000; ++i) { |
| 153 | + add_coin(5 * CENT, 7, utxo_pool); |
| 154 | + } |
| 155 | + BOOST_CHECK(SelectCoinsBnB(utxo_pool, 30 * CENT, 5000, selection, value_ret, not_input_fees)); |
| 156 | + |
| 157 | + //////////////////// |
| 158 | + // Behavior tests // |
| 159 | + //////////////////// |
| 160 | + // Select 1 Cent with pool of only greater than 5 Cent |
| 161 | + utxo_pool.clear(); |
| 162 | + for (int i = 5; i <= 20; ++i) { |
| 163 | + add_coin(i * CENT, i, utxo_pool); |
| 164 | + } |
| 165 | + // Run 100 times, to make sure it is never finding a solution |
| 166 | + for (int i = 0; i < 100; ++i) { |
| 167 | + BOOST_CHECK(!SelectCoinsBnB(utxo_pool, 1 * CENT, 2 * CENT, selection, value_ret, not_input_fees)); |
| 168 | + } |
| 169 | +} |
| 170 | + |
| 171 | +BOOST_AUTO_TEST_SUITE_END() |
0 commit comments