12
12
#include < wallet/coinselection.h>
13
13
14
14
#include < numeric>
15
+ #include < span>
15
16
#include < vector>
16
17
17
18
namespace wallet {
@@ -216,8 +217,14 @@ FUZZ_TARGET(coin_grinder_is_optimal)
216
217
assert (!result_cg);
217
218
}
218
219
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) {
221
228
SeedRandomStateForTest (SeedRand::ZEROS);
222
229
FuzzedDataProvider fuzzed_data_provider{buffer.data (), buffer.size ()};
223
230
std::vector<COutput> utxo_pool;
@@ -249,66 +256,72 @@ FUZZ_TARGET(coinselection)
249
256
250
257
std::vector<OutputGroup> group_pos;
251
258
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
- }
259
259
260
260
int max_selection_weight = fuzzed_data_provider.ConsumeIntegralInRange <int >(0 , std::numeric_limits<int >::max ());
261
261
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
+ }
271
276
}
272
277
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
+ }
281
289
}
282
290
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);
292
294
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
+ }
297
315
}
298
316
299
317
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));
304
318
CAmount new_total_balance{CreateCoins (fuzzed_data_provider, utxos, coin_params, next_locktime)};
305
319
if (new_total_balance > 0 ) {
306
320
std::set<std::shared_ptr<COutput>> new_utxo_pool;
307
321
for (const auto & utxo : utxos) {
308
322
new_utxo_pool.insert (std::make_shared<COutput>(utxo));
309
323
}
310
- for (auto & result : results) {
311
- if (!result) continue ;
324
+ if (result) {
312
325
const auto weight{result->GetWeight ()};
313
326
result->AddInputs (new_utxo_pool, subtract_fee_outputs);
314
327
assert (result->GetWeight () > weight);
@@ -319,8 +332,7 @@ FUZZ_TARGET(coinselection)
319
332
CAmount manual_balance{CreateCoins (fuzzed_data_provider, manual_inputs, coin_params, next_locktime)};
320
333
if (manual_balance == 0 ) return ;
321
334
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) {
324
336
const CAmount old_target{result->GetTarget ()};
325
337
const std::set<std::shared_ptr<COutput>> input_set{result->GetInputSet ()};
326
338
const int old_weight{result->GetWeight ()};
@@ -331,4 +343,16 @@ FUZZ_TARGET(coinselection)
331
343
}
332
344
}
333
345
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
+
334
358
} // namespace wallet
0 commit comments