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