@@ -320,7 +320,6 @@ BOOST_AUTO_TEST_CASE(bnb_search_test)
320320 coin_selection_params_bnb.m_change_fee = coin_selection_params_bnb.m_effective_feerate .GetFee (coin_selection_params_bnb.change_output_size );
321321 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 ;
322322 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 ;
324323
325324 {
326325 std::unique_ptr<CWallet> wallet = NewWallet (m_node);
@@ -345,6 +344,7 @@ BOOST_AUTO_TEST_CASE(bnb_search_test)
345344
346345 CoinsResult available_coins;
347346
347+ coin_selection_params_bnb.m_effective_feerate = CFeeRate (0 );
348348 add_coin (available_coins, *wallet, 5 * CENT, coin_selection_params_bnb.m_effective_feerate , 6 * 24 , false , 0 , true );
349349 add_coin (available_coins, *wallet, 3 * CENT, coin_selection_params_bnb.m_effective_feerate , 6 * 24 , false , 0 , true );
350350 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)
355355 PreSelectedInputs selected_input;
356356 selected_input.Insert (select_coin, coin_selection_params_bnb.m_subtract_fee_outputs );
357357 available_coins.Erase ({available_coins.coins [OutputType::BECH32].begin ()->outpoint });
358- coin_selection_params_bnb. m_effective_feerate = CFeeRate ( 0 );
358+
359359 LOCK (wallet->cs_wallet );
360360 const auto result10 = SelectCoins (*wallet, available_coins, selected_input, 10 * CENT, coin_control, coin_selection_params_bnb);
361361 BOOST_CHECK (result10);
@@ -370,12 +370,14 @@ BOOST_AUTO_TEST_CASE(bnb_search_test)
370370 coin_selection_params_bnb.m_effective_feerate = CFeeRate (5000 );
371371 coin_selection_params_bnb.m_long_term_feerate = CFeeRate (3000 );
372372
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 );
376378
377379 expected_result.Clear ();
378- add_coin (10 * CENT, 2 , expected_result);
380+ add_coin (10 * CENT + input_fee , 2 , expected_result);
379381 CCoinControl coin_control;
380382 const auto result11 = SelectCoins (*wallet, available_coins, /* pre_set_inputs=*/ {}, 10 * CENT, coin_control, coin_selection_params_bnb);
381383 BOOST_CHECK (EquivalentResult (expected_result, *result11));
@@ -385,13 +387,15 @@ BOOST_AUTO_TEST_CASE(bnb_search_test)
385387 coin_selection_params_bnb.m_effective_feerate = CFeeRate (3000 );
386388 coin_selection_params_bnb.m_long_term_feerate = CFeeRate (5000 );
387389
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 );
391395
392396 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);
395399 const auto result12 = SelectCoins (*wallet, available_coins, /* pre_set_inputs=*/ {}, 10 * CENT, coin_control, coin_selection_params_bnb);
396400 BOOST_CHECK (EquivalentResult (expected_result, *result12));
397401 available_coins.Clear ();
@@ -400,13 +404,15 @@ BOOST_AUTO_TEST_CASE(bnb_search_test)
400404 coin_selection_params_bnb.m_effective_feerate = CFeeRate (5000 );
401405 coin_selection_params_bnb.m_long_term_feerate = CFeeRate (3000 );
402406
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 );
406412
407413 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);
410416 coin_control.m_allow_other_inputs = true ;
411417 COutput select_coin = available_coins.All ().at (1 ); // pre select 9 coin
412418 coin_control.Select (select_coin.outpoint );
@@ -449,6 +455,44 @@ BOOST_AUTO_TEST_CASE(bnb_search_test)
449455 }
450456}
451457
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+
452496BOOST_AUTO_TEST_CASE (knapsack_solver_test)
453497{
454498 FastRandomContext rand{};
0 commit comments