Skip to content

Commit d610951

Browse files
committed
test: Recreate BnB iteration exhaustion test
1 parent 2a1b275 commit d610951

File tree

2 files changed

+36
-32
lines changed

2 files changed

+36
-32
lines changed

src/wallet/test/coinselection_tests.cpp

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,42 @@ BOOST_AUTO_TEST_CASE(bnb_test)
146146
AddCoins(clone_pool, {2 * CENT, 7 * CENT, 7 * CENT});
147147
AddDuplicateCoins(clone_pool, 50'000, 5 * CENT);
148148
TestBnBSuccess("Skip equivalent input sets", clone_pool, /*selection_target=*/16 * CENT, /*expected_input_amounts=*/{2 * CENT, 7 * CENT, 7 * CENT});
149+
150+
/* Test BnB attempt limit (`TOTAL_TRIES`)
151+
*
152+
* Generally, on a diverse UTXO pool BnB will quickly pass over UTXOs bigger than the target and then start
153+
* combining small counts of UTXOs that in sum remain under the selection_target+cost_of_change. When there are
154+
* multiple UTXOs that have matching amount and cost, combinations with equivalent input sets are skipped. The UTXO
155+
* pool for this test is specifically crafted to create as much branching as possible. The selection target is
156+
* 8 CENT while all UTXOs are slightly bigger than 1 CENT. The smallest eight are 100,000…100,007 sats, while the larger
157+
* nine are 100,368…100,375 (i.e., 100,008…100,016 sats plus cost_of_change (359 sats)).
158+
*
159+
* Because BnB will only select input sets that fall between selection_target and selection_target + cost_of_change,
160+
* and the search traverses the UTXO pool from large to small amounts, the search will visit every single
161+
* combination of eight inputs. All except the last combination will overshoot by more than cost_of_change on the
162+
* eighth input, because the larger nine inputs each exceed 1 CENT by more than cost_of_change. Only the last
163+
* combination consisting of the eight smallest UTXOs falls into the target window.
164+
*/
165+
std::vector<OutputGroup> doppelganger_pool;
166+
std::vector<CAmount> doppelgangers;
167+
std::vector<CAmount> expected_inputs;
168+
for (int i = 0; i < 17; ++i) {
169+
if (i < 8) {
170+
// The eight smallest UTXOs can be combined to create expected_result
171+
doppelgangers.push_back(1 * CENT + i);
172+
expected_inputs.push_back(doppelgangers[i]);
173+
} else {
174+
// Any eight UTXOs including at least one UTXO with the added cost_of_change will exceed target window
175+
doppelgangers.push_back(1 * CENT + default_cs_params.m_cost_of_change + i);
176+
}
177+
}
178+
AddCoins(doppelganger_pool, doppelgangers);
179+
// Among up to 17 unique UTXOs of similar effective value we will find a solution composed of the eight smallest UTXOs
180+
TestBnBSuccess("Combine smallest 8 of 17 unique UTXOs", doppelganger_pool, /*selection_target=*/8 * CENT, /*expected_input_amounts=*/expected_inputs);
181+
182+
// Starting with 18 unique UTXOs of similar effective value we will not find the solution due to exceeding the attempt limit
183+
AddCoins(doppelganger_pool, {1 * CENT + default_cs_params.m_cost_of_change + 17});
184+
TestBnBFail("Exhaust looking for smallest 8 of 18 unique UTXOs", doppelganger_pool, /*selection_target=*/8 * CENT);
149185
}
150186

151187
BOOST_AUTO_TEST_CASE(bnb_feerate_sensitivity_test)

src/wallet/test/coinselector_tests.cpp

Lines changed: 0 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -37,15 +37,6 @@ static const CoinEligibilityFilter filter_confirmed(1, 1, 0);
3737
static const CoinEligibilityFilter filter_standard_extra(6, 6, 0);
3838
static int nextLockTime = 0;
3939

40-
static void add_coin(const CAmount& nValue, int nInput, std::vector<COutput>& set)
41-
{
42-
CMutableTransaction tx;
43-
tx.vout.resize(nInput + 1);
44-
tx.vout[nInput].nValue = nValue;
45-
tx.nLockTime = nextLockTime++; // so all transactions get different hashes
46-
set.emplace_back(COutPoint(tx.GetHash(), nInput), tx.vout.at(nInput), /*depth=*/ 1, /*input_bytes=*/ -1, /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, /*time=*/ 0, /*from_me=*/ false, /*fees=*/ 0);
47-
}
48-
4940
static void add_coin(const CAmount& nValue, int nInput, SelectionResult& result)
5041
{
5142
CMutableTransaction tx;
@@ -133,18 +124,6 @@ static bool EqualResult(const SelectionResult& a, const SelectionResult& b)
133124
return ret.first == a.GetInputSet().end() && ret.second == b.GetInputSet().end();
134125
}
135126

136-
static CAmount make_hard_case(int utxos, std::vector<COutput>& utxo_pool)
137-
{
138-
utxo_pool.clear();
139-
CAmount target = 0;
140-
for (int i = 0; i < utxos; ++i) {
141-
target += CAmount{1} << (utxos+i);
142-
add_coin(CAmount{1} << (utxos+i), 2*i, utxo_pool);
143-
add_coin((CAmount{1} << (utxos+i)) + (CAmount{1} << (utxos-1-i)), 2*i + 1, utxo_pool);
144-
}
145-
return target;
146-
}
147-
148127
inline std::vector<OutputGroup>& GroupCoins(const std::vector<COutput>& available_coins, bool subtract_fee_outputs = false)
149128
{
150129
static std::vector<OutputGroup> static_groups;
@@ -195,17 +174,6 @@ BOOST_AUTO_TEST_CASE(bnb_search_test)
195174
std::vector<COutput> utxo_pool;
196175
SelectionResult expected_result(CAmount(0), SelectionAlgorithm::BNB);
197176

198-
/////////////////////////
199-
// Known Outcome tests //
200-
/////////////////////////
201-
202-
// Iteration exhaustion test
203-
CAmount target = make_hard_case(17, utxo_pool);
204-
BOOST_CHECK(!SelectCoinsBnB(GroupCoins(utxo_pool), target, 1)); // Should exhaust
205-
target = make_hard_case(14, utxo_pool);
206-
const auto result7 = SelectCoinsBnB(GroupCoins(utxo_pool), target, 1); // Should not exhaust
207-
BOOST_CHECK(result7);
208-
209177
////////////////////
210178
// Behavior tests //
211179
////////////////////

0 commit comments

Comments
 (0)