Skip to content

Commit 4566ab7

Browse files
committed
Add tests for the Branch and Bound algorithm
1 parent 4b2716d commit 4566ab7

File tree

2 files changed

+173
-1
lines changed

2 files changed

+173
-1
lines changed

src/Makefile.test.include

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,8 @@ BITCOIN_TESTS += \
9494
wallet/test/wallet_test_fixture.h \
9595
wallet/test/accounting_tests.cpp \
9696
wallet/test/wallet_tests.cpp \
97-
wallet/test/crypto_tests.cpp
97+
wallet/test/crypto_tests.cpp \
98+
wallet/test/coinselector_tests.cpp
9899
endif
99100

100101
test_test_bitcoin_SOURCES = $(BITCOIN_TESTS) $(JSON_TEST_FILES) $(RAW_TEST_FILES)
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
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

Comments
 (0)