1212#include < wallet/coinselection.h>
1313
1414#include < numeric>
15+ #include < span>
1516#include < vector>
1617
1718namespace 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