Skip to content

Commit 8046759

Browse files
committed
Merge bitcoin#31870: fuzz: split coinselection harness
ba82240 fuzz: split `coinselection` harness (brunoerg) Pull request description: This PR splits the `coinselection` fuzz harness into 3 targets (`coinselection_bnb`, `coinselection_knapsack`, `coinselection_srd`). The goal is to be able to fuzz each algorithm separately (to avoid performance issues) and also all of them together. ACKs for top commit: janb84: Tested ACK [ba82240](bitcoin@ba82240) maflcko: review ACK ba82240 👐 marcofleon: reACK ba82240 zaidmstrr: reACK [ba82240](bitcoin@ba82240) Tree-SHA512: 277cffd524e57d286dbbbcb2aa0a9f1d720b4c56331dfb0f4425e1666246330616508e47977da23f28a72705aa142bbaf536e2cf7fe4703a2cd2e4b2fd441d9d
2 parents 2db0027 + ba82240 commit 8046759

File tree

1 file changed

+71
-47
lines changed

1 file changed

+71
-47
lines changed

src/wallet/test/fuzz/coinselection.cpp

+71-47
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include <wallet/coinselection.h>
1313

1414
#include <numeric>
15+
#include <span>
1516
#include <vector>
1617

1718
namespace wallet {
@@ -216,8 +217,14 @@ FUZZ_TARGET(coin_grinder_is_optimal)
216217
assert(!result_cg);
217218
}
218219

219-
FUZZ_TARGET(coinselection)
220-
{
220+
enum class CoinSelectionAlgorithm {
221+
BNB,
222+
SRD,
223+
KNAPSACK,
224+
};
225+
226+
template<CoinSelectionAlgorithm Algorithm>
227+
void FuzzCoinSelectionAlgorithm(std::span<const uint8_t> buffer) {
221228
SeedRandomStateForTest(SeedRand::ZEROS);
222229
FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
223230
std::vector<COutput> utxo_pool;
@@ -249,66 +256,72 @@ FUZZ_TARGET(coinselection)
249256

250257
std::vector<OutputGroup> group_pos;
251258
GroupCoins(fuzzed_data_provider, utxo_pool, coin_params, /*positive_only=*/true, group_pos);
252-
std::vector<OutputGroup> group_all;
253-
GroupCoins(fuzzed_data_provider, utxo_pool, coin_params, /*positive_only=*/false, group_all);
254-
255-
for (const OutputGroup& group : group_all) {
256-
const CoinEligibilityFilter filter{fuzzed_data_provider.ConsumeIntegral<int>(), fuzzed_data_provider.ConsumeIntegral<int>(), fuzzed_data_provider.ConsumeIntegral<uint64_t>()};
257-
(void)group.EligibleForSpending(filter);
258-
}
259259

260260
int max_selection_weight = fuzzed_data_provider.ConsumeIntegralInRange<int>(0, std::numeric_limits<int>::max());
261261

262-
// Run coinselection algorithms
263-
auto result_bnb = coin_params.m_subtract_fee_outputs ? util::Error{Untranslated("BnB disabled when SFFO is enabled")} :
264-
SelectCoinsBnB(group_pos, target, coin_params.m_cost_of_change, max_selection_weight);
265-
if (result_bnb) {
266-
assert(result_bnb->GetChange(coin_params.min_viable_change, coin_params.m_change_fee) == 0);
267-
assert(result_bnb->GetSelectedValue() >= target);
268-
assert(result_bnb->GetWeight() <= max_selection_weight);
269-
(void)result_bnb->GetShuffledInputVector();
270-
(void)result_bnb->GetInputSet();
262+
std::optional<SelectionResult> result;
263+
264+
if constexpr (Algorithm == CoinSelectionAlgorithm::BNB) {
265+
if (!coin_params.m_subtract_fee_outputs) {
266+
auto result_bnb = SelectCoinsBnB(group_pos, target, coin_params.m_cost_of_change, max_selection_weight);
267+
if (result_bnb) {
268+
result = *result_bnb;
269+
assert(result_bnb->GetChange(coin_params.min_viable_change, coin_params.m_change_fee) == 0);
270+
assert(result_bnb->GetSelectedValue() >= target);
271+
assert(result_bnb->GetWeight() <= max_selection_weight);
272+
(void)result_bnb->GetShuffledInputVector();
273+
(void)result_bnb->GetInputSet();
274+
}
275+
}
271276
}
272277

273-
auto result_srd = SelectCoinsSRD(group_pos, target, coin_params.m_change_fee, fast_random_context, max_selection_weight);
274-
if (result_srd) {
275-
assert(result_srd->GetSelectedValue() >= target);
276-
assert(result_srd->GetChange(CHANGE_LOWER, coin_params.m_change_fee) > 0); // Demonstrate that SRD creates change of at least CHANGE_LOWER
277-
assert(result_srd->GetWeight() <= max_selection_weight);
278-
result_srd->RecalculateWaste(coin_params.min_viable_change, coin_params.m_cost_of_change, coin_params.m_change_fee);
279-
(void)result_srd->GetShuffledInputVector();
280-
(void)result_srd->GetInputSet();
278+
if constexpr (Algorithm == CoinSelectionAlgorithm::SRD) {
279+
auto result_srd = SelectCoinsSRD(group_pos, target, coin_params.m_change_fee, fast_random_context, max_selection_weight);
280+
if (result_srd) {
281+
result = *result_srd;
282+
assert(result_srd->GetSelectedValue() >= target);
283+
assert(result_srd->GetChange(CHANGE_LOWER, coin_params.m_change_fee) > 0);
284+
assert(result_srd->GetWeight() <= max_selection_weight);
285+
result_srd->RecalculateWaste(coin_params.min_viable_change, coin_params.m_cost_of_change, coin_params.m_change_fee);
286+
(void)result_srd->GetShuffledInputVector();
287+
(void)result_srd->GetInputSet();
288+
}
281289
}
282290

283-
CAmount change_target{GenerateChangeTarget(target, coin_params.m_change_fee, fast_random_context)};
284-
auto result_knapsack = KnapsackSolver(group_all, target, change_target, fast_random_context, max_selection_weight);
285-
if (result_knapsack) {
286-
assert(result_knapsack->GetSelectedValue() >= target);
287-
assert(result_knapsack->GetWeight() <= max_selection_weight);
288-
result_knapsack->RecalculateWaste(coin_params.min_viable_change, coin_params.m_cost_of_change, coin_params.m_change_fee);
289-
(void)result_knapsack->GetShuffledInputVector();
290-
(void)result_knapsack->GetInputSet();
291-
}
291+
if constexpr (Algorithm == CoinSelectionAlgorithm::KNAPSACK) {
292+
std::vector<OutputGroup> group_all;
293+
GroupCoins(fuzzed_data_provider, utxo_pool, coin_params, /*positive_only=*/false, group_all);
292294

293-
// If the total balance is sufficient for the target and we are not using
294-
// effective values, Knapsack should always find a solution (unless the selection exceeded the max tx weight).
295-
if (total_balance >= target && subtract_fee_outputs && !HasErrorMsg(result_knapsack)) {
296-
assert(result_knapsack);
295+
for (const OutputGroup& group : group_all) {
296+
const CoinEligibilityFilter filter{fuzzed_data_provider.ConsumeIntegral<int>(), fuzzed_data_provider.ConsumeIntegral<int>(), fuzzed_data_provider.ConsumeIntegral<uint64_t>()};
297+
(void)group.EligibleForSpending(filter);
298+
}
299+
300+
CAmount change_target{GenerateChangeTarget(target, coin_params.m_change_fee, fast_random_context)};
301+
auto result_knapsack = KnapsackSolver(group_all, target, change_target, fast_random_context, max_selection_weight);
302+
// If the total balance is sufficient for the target and we are not using
303+
// effective values, Knapsack should always find a solution (unless the selection exceeded the max tx weight).
304+
if (total_balance >= target && subtract_fee_outputs && !HasErrorMsg(result_knapsack)) {
305+
assert(result_knapsack);
306+
}
307+
if (result_knapsack) {
308+
result = *result_knapsack;
309+
assert(result_knapsack->GetSelectedValue() >= target);
310+
assert(result_knapsack->GetWeight() <= max_selection_weight);
311+
result_knapsack->RecalculateWaste(coin_params.min_viable_change, coin_params.m_cost_of_change, coin_params.m_change_fee);
312+
(void)result_knapsack->GetShuffledInputVector();
313+
(void)result_knapsack->GetInputSet();
314+
}
297315
}
298316

299317
std::vector<COutput> utxos;
300-
std::vector<util::Result<SelectionResult>> results;
301-
results.emplace_back(std::move(result_srd));
302-
results.emplace_back(std::move(result_knapsack));
303-
results.emplace_back(std::move(result_bnb));
304318
CAmount new_total_balance{CreateCoins(fuzzed_data_provider, utxos, coin_params, next_locktime)};
305319
if (new_total_balance > 0) {
306320
std::set<std::shared_ptr<COutput>> new_utxo_pool;
307321
for (const auto& utxo : utxos) {
308322
new_utxo_pool.insert(std::make_shared<COutput>(utxo));
309323
}
310-
for (auto& result : results) {
311-
if (!result) continue;
324+
if (result) {
312325
const auto weight{result->GetWeight()};
313326
result->AddInputs(new_utxo_pool, subtract_fee_outputs);
314327
assert(result->GetWeight() > weight);
@@ -319,8 +332,7 @@ FUZZ_TARGET(coinselection)
319332
CAmount manual_balance{CreateCoins(fuzzed_data_provider, manual_inputs, coin_params, next_locktime)};
320333
if (manual_balance == 0) return;
321334
auto manual_selection{ManualSelection(manual_inputs, manual_balance, coin_params.m_subtract_fee_outputs)};
322-
for (auto& result : results) {
323-
if (!result) continue;
335+
if (result) {
324336
const CAmount old_target{result->GetTarget()};
325337
const std::set<std::shared_ptr<COutput>> input_set{result->GetInputSet()};
326338
const int old_weight{result->GetWeight()};
@@ -331,4 +343,16 @@ FUZZ_TARGET(coinselection)
331343
}
332344
}
333345

346+
FUZZ_TARGET(coinselection_bnb) {
347+
FuzzCoinSelectionAlgorithm<CoinSelectionAlgorithm::BNB>(buffer);
348+
}
349+
350+
FUZZ_TARGET(coinselection_srd) {
351+
FuzzCoinSelectionAlgorithm<CoinSelectionAlgorithm::SRD>(buffer);
352+
}
353+
354+
FUZZ_TARGET(coinselection_knapsack) {
355+
FuzzCoinSelectionAlgorithm<CoinSelectionAlgorithm::KNAPSACK>(buffer);
356+
}
357+
334358
} // namespace wallet

0 commit comments

Comments
 (0)