@@ -320,7 +320,6 @@ BOOST_AUTO_TEST_CASE(bnb_search_test)
320
320
coin_selection_params_bnb.m_change_fee = coin_selection_params_bnb.m_effective_feerate .GetFee (coin_selection_params_bnb.change_output_size );
321
321
coin_selection_params_bnb.m_cost_of_change = coin_selection_params_bnb.m_effective_feerate .GetFee (coin_selection_params_bnb.change_spend_size ) + coin_selection_params_bnb.m_change_fee ;
322
322
coin_selection_params_bnb.min_viable_change = coin_selection_params_bnb.m_effective_feerate .GetFee (coin_selection_params_bnb.change_spend_size );
323
- coin_selection_params_bnb.m_subtract_fee_outputs = true ;
324
323
325
324
{
326
325
std::unique_ptr<CWallet> wallet = NewWallet (m_node);
@@ -345,6 +344,7 @@ BOOST_AUTO_TEST_CASE(bnb_search_test)
345
344
346
345
CoinsResult available_coins;
347
346
347
+ coin_selection_params_bnb.m_effective_feerate = CFeeRate (0 );
348
348
add_coin (available_coins, *wallet, 5 * CENT, coin_selection_params_bnb.m_effective_feerate , 6 * 24 , false , 0 , true );
349
349
add_coin (available_coins, *wallet, 3 * CENT, coin_selection_params_bnb.m_effective_feerate , 6 * 24 , false , 0 , true );
350
350
add_coin (available_coins, *wallet, 2 * CENT, coin_selection_params_bnb.m_effective_feerate , 6 * 24 , false , 0 , true );
@@ -355,7 +355,7 @@ BOOST_AUTO_TEST_CASE(bnb_search_test)
355
355
PreSelectedInputs selected_input;
356
356
selected_input.Insert (select_coin, coin_selection_params_bnb.m_subtract_fee_outputs );
357
357
available_coins.Erase ({available_coins.coins [OutputType::BECH32].begin ()->outpoint });
358
- coin_selection_params_bnb. m_effective_feerate = CFeeRate ( 0 );
358
+
359
359
LOCK (wallet->cs_wallet );
360
360
const auto result10 = SelectCoins (*wallet, available_coins, selected_input, 10 * CENT, coin_control, coin_selection_params_bnb);
361
361
BOOST_CHECK (result10);
@@ -370,12 +370,14 @@ BOOST_AUTO_TEST_CASE(bnb_search_test)
370
370
coin_selection_params_bnb.m_effective_feerate = CFeeRate (5000 );
371
371
coin_selection_params_bnb.m_long_term_feerate = CFeeRate (3000 );
372
372
373
- add_coin (available_coins, *wallet, 10 * CENT, coin_selection_params_bnb.m_effective_feerate , 6 * 24 , false , 0 , true );
374
- add_coin (available_coins, *wallet, 9 * CENT, coin_selection_params_bnb.m_effective_feerate , 6 * 24 , false , 0 , true );
375
- add_coin (available_coins, *wallet, 1 * CENT, coin_selection_params_bnb.m_effective_feerate , 6 * 24 , false , 0 , true );
373
+ // Add selectable outputs, increasing their raw amounts by their input fee to make the effective value equal to the raw amount
374
+ CAmount input_fee = coin_selection_params_bnb.m_effective_feerate .GetFee (/* num_bytes=*/ 68 ); // bech32 input size (default test output type)
375
+ add_coin (available_coins, *wallet, 10 * CENT + input_fee, coin_selection_params_bnb.m_effective_feerate , 6 * 24 , false , 0 , true );
376
+ add_coin (available_coins, *wallet, 9 * CENT + input_fee, coin_selection_params_bnb.m_effective_feerate , 6 * 24 , false , 0 , true );
377
+ add_coin (available_coins, *wallet, 1 * CENT + input_fee, coin_selection_params_bnb.m_effective_feerate , 6 * 24 , false , 0 , true );
376
378
377
379
expected_result.Clear ();
378
- add_coin (10 * CENT, 2 , expected_result);
380
+ add_coin (10 * CENT + input_fee , 2 , expected_result);
379
381
CCoinControl coin_control;
380
382
const auto result11 = SelectCoins (*wallet, available_coins, /* pre_set_inputs=*/ {}, 10 * CENT, coin_control, coin_selection_params_bnb);
381
383
BOOST_CHECK (EquivalentResult (expected_result, *result11));
@@ -385,13 +387,15 @@ BOOST_AUTO_TEST_CASE(bnb_search_test)
385
387
coin_selection_params_bnb.m_effective_feerate = CFeeRate (3000 );
386
388
coin_selection_params_bnb.m_long_term_feerate = CFeeRate (5000 );
387
389
388
- add_coin (available_coins, *wallet, 10 * CENT, coin_selection_params_bnb.m_effective_feerate , 6 * 24 , false , 0 , true );
389
- add_coin (available_coins, *wallet, 9 * CENT, coin_selection_params_bnb.m_effective_feerate , 6 * 24 , false , 0 , true );
390
- add_coin (available_coins, *wallet, 1 * CENT, coin_selection_params_bnb.m_effective_feerate , 6 * 24 , false , 0 , true );
390
+ // Add selectable outputs, increasing their raw amounts by their input fee to make the effective value equal to the raw amount
391
+ input_fee = coin_selection_params_bnb.m_effective_feerate .GetFee (/* num_bytes=*/ 68 ); // bech32 input size (default test output type)
392
+ add_coin (available_coins, *wallet, 10 * CENT + input_fee, coin_selection_params_bnb.m_effective_feerate , 6 * 24 , false , 0 , true );
393
+ add_coin (available_coins, *wallet, 9 * CENT + input_fee, coin_selection_params_bnb.m_effective_feerate , 6 * 24 , false , 0 , true );
394
+ add_coin (available_coins, *wallet, 1 * CENT + input_fee, coin_selection_params_bnb.m_effective_feerate , 6 * 24 , false , 0 , true );
391
395
392
396
expected_result.Clear ();
393
- add_coin (9 * CENT, 2 , expected_result);
394
- add_coin (1 * CENT, 2 , expected_result);
397
+ add_coin (9 * CENT + input_fee , 2 , expected_result);
398
+ add_coin (1 * CENT + input_fee , 2 , expected_result);
395
399
const auto result12 = SelectCoins (*wallet, available_coins, /* pre_set_inputs=*/ {}, 10 * CENT, coin_control, coin_selection_params_bnb);
396
400
BOOST_CHECK (EquivalentResult (expected_result, *result12));
397
401
available_coins.Clear ();
@@ -400,13 +404,15 @@ BOOST_AUTO_TEST_CASE(bnb_search_test)
400
404
coin_selection_params_bnb.m_effective_feerate = CFeeRate (5000 );
401
405
coin_selection_params_bnb.m_long_term_feerate = CFeeRate (3000 );
402
406
403
- add_coin (available_coins, *wallet, 10 * CENT, coin_selection_params_bnb.m_effective_feerate , 6 * 24 , false , 0 , true );
404
- add_coin (available_coins, *wallet, 9 * CENT, coin_selection_params_bnb.m_effective_feerate , 6 * 24 , false , 0 , true );
405
- add_coin (available_coins, *wallet, 1 * CENT, coin_selection_params_bnb.m_effective_feerate , 6 * 24 , false , 0 , true );
407
+ // Add selectable outputs, increasing their raw amounts by their input fee to make the effective value equal to the raw amount
408
+ input_fee = coin_selection_params_bnb.m_effective_feerate .GetFee (/* num_bytes=*/ 68 ); // bech32 input size (default test output type)
409
+ add_coin (available_coins, *wallet, 10 * CENT + input_fee, coin_selection_params_bnb.m_effective_feerate , 6 * 24 , false , 0 , true );
410
+ add_coin (available_coins, *wallet, 9 * CENT + input_fee, coin_selection_params_bnb.m_effective_feerate , 6 * 24 , false , 0 , true );
411
+ add_coin (available_coins, *wallet, 1 * CENT + input_fee, coin_selection_params_bnb.m_effective_feerate , 6 * 24 , false , 0 , true );
406
412
407
413
expected_result.Clear ();
408
- add_coin (9 * CENT, 2 , expected_result);
409
- add_coin (1 * CENT, 2 , expected_result);
414
+ add_coin (9 * CENT + input_fee , 2 , expected_result);
415
+ add_coin (1 * CENT + input_fee , 2 , expected_result);
410
416
coin_control.m_allow_other_inputs = true ;
411
417
COutput select_coin = available_coins.All ().at (1 ); // pre select 9 coin
412
418
coin_control.Select (select_coin.outpoint );
@@ -449,6 +455,44 @@ BOOST_AUTO_TEST_CASE(bnb_search_test)
449
455
}
450
456
}
451
457
458
+ BOOST_AUTO_TEST_CASE (bnb_sffo_restriction)
459
+ {
460
+ // Verify the coin selection process does not produce a BnB solution when SFFO is enabled.
461
+ // This is currently problematic because it could require a change output. And BnB is specialized on changeless solutions.
462
+ std::unique_ptr<CWallet> wallet = NewWallet (m_node);
463
+ WITH_LOCK (wallet->cs_wallet , wallet->SetLastBlockProcessed (300 , uint256{})); // set a high block so internal UTXOs are selectable
464
+
465
+ FastRandomContext rand{};
466
+ CoinSelectionParams params{
467
+ rand,
468
+ /* change_output_size=*/ 31 , // unused value, p2wpkh output size (wallet default change type)
469
+ /* change_spend_size=*/ 68 , // unused value, p2wpkh input size (high-r signature)
470
+ /* min_change_target=*/ 0 , // dummy, set later
471
+ /* effective_feerate=*/ CFeeRate (3000 ),
472
+ /* long_term_feerate=*/ CFeeRate (1000 ),
473
+ /* discard_feerate=*/ CFeeRate (1000 ),
474
+ /* tx_noinputs_size=*/ 0 ,
475
+ /* avoid_partial=*/ false ,
476
+ };
477
+ params.m_subtract_fee_outputs = true ;
478
+ params.m_change_fee = params.m_effective_feerate .GetFee (params.change_output_size );
479
+ params.m_cost_of_change = params.m_discard_feerate .GetFee (params.change_spend_size ) + params.m_change_fee ;
480
+ params.m_min_change_target = params.m_cost_of_change + 1 ;
481
+ // Add spendable coin at the BnB selection upper bound
482
+ CoinsResult available_coins;
483
+ add_coin (available_coins, *wallet, COIN + params.m_cost_of_change , /* feerate=*/ params.m_effective_feerate , /* nAge=*/ 6 , /* fIsFromMe=*/ true , /* nInput=*/ 0 , /* spendable=*/ true );
484
+ add_coin (available_coins, *wallet, 0.5 * COIN + params.m_cost_of_change , /* feerate=*/ params.m_effective_feerate , /* nAge=*/ 6 , /* fIsFromMe=*/ true , /* nInput=*/ 0 , /* spendable=*/ true );
485
+ add_coin (available_coins, *wallet, 0.5 * COIN, /* feerate=*/ params.m_effective_feerate , /* nAge=*/ 6 , /* fIsFromMe=*/ true , /* nInput=*/ 0 , /* spendable=*/ true );
486
+ // Knapsack will only find a changeless solution on an exact match to the satoshi, SRD doesn’t look for changeless
487
+ // If BnB were run, it would produce a single input solution with the best waste score
488
+ auto result = WITH_LOCK (wallet->cs_wallet , return SelectCoins (*wallet, available_coins, /* pre_set_inputs=*/ {}, COIN, /* coin_control=*/ {}, params));
489
+ BOOST_CHECK (result.has_value ());
490
+ BOOST_CHECK_NE (result->GetAlgo (), SelectionAlgorithm::BNB);
491
+ BOOST_CHECK (result->GetInputSet ().size () == 2 );
492
+ // We have only considered BnB, SRD, and Knapsack. Test needs to be reevaluated if new algo is added
493
+ BOOST_CHECK (result->GetAlgo () == SelectionAlgorithm::SRD || result->GetAlgo () == SelectionAlgorithm::KNAPSACK);
494
+ }
495
+
452
496
BOOST_AUTO_TEST_CASE (knapsack_solver_test)
453
497
{
454
498
FastRandomContext rand{};
0 commit comments