diff --git a/src/wallet/test/coinselection_tests.cpp b/src/wallet/test/coinselection_tests.cpp index 3f5969c338f4c9..f571f95fa56695 100644 --- a/src/wallet/test/coinselection_tests.cpp +++ b/src/wallet/test/coinselection_tests.cpp @@ -134,7 +134,7 @@ static void TestKnapsackFail(std::string test_title, std::vector& utxo_ BOOST_CHECK_MESSAGE(!SelectCoinsKnapsack(utxo_pool, selection_target), "Knapsack-Fail: " + test_title); } -BOOST_AUTO_TEST_CASE(knapsack_test) +BOOST_AUTO_TEST_CASE(knapsack_predictable_test) { std::vector utxo_pool; @@ -162,6 +162,67 @@ BOOST_AUTO_TEST_CASE(knapsack_test) TestKnapsackMatch("Select more to get min_change", utxo_pool, /*selection_target=*/ 9.976 * CENT, /*expected_input_amounts=*/ {11 * CENT}); } +/** Check if this selection is equal to another one. Equal means same inputs (i.e same value and prevout) */ +static bool EqualResult(const SelectionResult& a, const SelectionResult& b) +{ + std::pair ret = std::mismatch(a.GetInputSet().begin(), a.GetInputSet().end(), b.GetInputSet().begin(), + [](const std::shared_ptr& a, const std::shared_ptr& b) { + return a->outpoint == b->outpoint; + }); + return ret.first == a.GetInputSet().end() && ret.second == b.GetInputSet().end(); +} + +BOOST_AUTO_TEST_CASE(knapsack_randomness_test) +{ + std::vector clone_pool; + AddDuplicateCoins(clone_pool, 10'000, 1 * COIN); + { + // Select 50 from 10000 coins twice, input sets must differ + const auto one_result = KnapsackSolver(/*utxos=*/ GroupCoins(clone_pool), /*selection_target*/ 50 * COIN); + BOOST_CHECK(one_result); + + const auto another_result = KnapsackSolver(/*utxos=*/ GroupCoins(clone_pool), /*selection_target*/ 50 * COIN); + BOOST_CHECK(another_result); + BOOST_CHECK_MESSAGE(!EqualResult(*one_result, *another_result), "Knapsack-Success: Select different input sets from clones on exact match"); + } + + { + // Select 1 from 10000 coins up to twenty times until they differ + SelectionResult one_result = KnapsackSolver(/*utxos=*/ GroupCoins(clone_pool), /*selection_target*/ 1 * COIN); + BOOST_CHECK(previous); + for (int i = 0; i < 20; i++) { + const auto another_result = KnapsackSolver(/*utxos=*/ GroupCoins(clone_pool), /*selection_target*/ 1 * COIN); + BOOST_CHECK(another_result); + if (EqualResult(*one_result, *another_result)) { + // Randomly selected the same single input from 10'000. Select another. + continue; + } + break; + } + // If we select the same 20 times from 10'000, something is wrong + BOOST_CHECK_MESSAGE(!EqualResult(*one_result, *another_result), "Knapsack-Success: Select different single exact match inputs from clones"); + } + + { + // Select different lowest larger UTXO on repetition + AddDuplicateCoins(clone_pool, 100, 50 * CENT); + SelectionResult one_result = KnapsackSolver(/*utxos=*/ GroupCoins(clone_pool), /*selection_target*/ 0.7 * COIN); + BOOST_CHECK(previous); + for (int i = 0; i < 20; i++) { + const auto another_result = KnapsackSolver(/*utxos=*/ GroupCoins(clone_pool), /*selection_target*/ 0.7 * COIN); + BOOST_CHECK(another_result); + if (EqualResult(*one_result, *another_result)) { + // Randomly selected the same lowest larger from 10'000. Select another. + continue; + } + break; + } + // If we select the same lowest larger 20 times from 10'000, something is wrong + BOOST_CHECK_MESSAGE(!EqualResult(*one_result, *another_result), "Knapsack-Success: Select different lowest larger inputs from clones"); + } +} + + std::optional SelectCoinsBnB(std::vector& utxo_pool, const CAmount& selection_target, const CAmount& cost_of_change = default_cs_params.m_cost_of_change, const int& max_weight = MAX_STANDARD_TX_WEIGHT) { auto res{SelectCoinsBnB(GroupCoins(utxo_pool), selection_target, cost_of_change, max_weight)}; diff --git a/src/wallet/test/coinselector_tests.cpp b/src/wallet/test/coinselector_tests.cpp index 5fe9a464367286..2975f88a679091 100644 --- a/src/wallet/test/coinselector_tests.cpp +++ b/src/wallet/test/coinselector_tests.cpp @@ -365,63 +365,6 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test) } } } - - // test randomness - { - available_coins.Clear(); - for (int i2 = 0; i2 < 100; i2++) - add_coin(available_coins, *wallet, COIN); - - // Again, we only create the wallet once to save time, but we still run the coin selection RUN_TESTS times. - for (int i = 0; i < RUN_TESTS; i++) { - // picking 50 from 100 coins doesn't depend on the shuffle, - // but does depend on randomness in the stochastic approximation code - const auto result25 = KnapsackSolver(GroupCoins(available_coins.All()), 50 * COIN, CENT); - BOOST_CHECK(result25); - const auto result26 = KnapsackSolver(GroupCoins(available_coins.All()), 50 * COIN, CENT); - BOOST_CHECK(result26); - BOOST_CHECK(!EqualResult(*result25, *result26)); - - int fails = 0; - for (int j = 0; j < RANDOM_REPEATS; j++) - { - // Test that the KnapsackSolver selects randomly from equivalent coins (same value and same input size). - // When choosing 1 from 100 identical coins, 1% of the time, this test will choose the same coin twice - // which will cause it to fail. - // To avoid that issue, run the test RANDOM_REPEATS times and only complain if all of them fail - const auto result27 = KnapsackSolver(GroupCoins(available_coins.All()), COIN, CENT); - BOOST_CHECK(result27); - const auto result28 = KnapsackSolver(GroupCoins(available_coins.All()), COIN, CENT); - BOOST_CHECK(result28); - if (EqualResult(*result27, *result28)) - fails++; - } - BOOST_CHECK_NE(fails, RANDOM_REPEATS); - } - - // add 75 cents in small change. not enough to make 90 cents, - // then try making 90 cents. there are multiple competing "smallest bigger" coins, - // one of which should be picked at random - add_coin(available_coins, *wallet, 5 * CENT); - add_coin(available_coins, *wallet, 10 * CENT); - add_coin(available_coins, *wallet, 15 * CENT); - add_coin(available_coins, *wallet, 20 * CENT); - add_coin(available_coins, *wallet, 25 * CENT); - - for (int i = 0; i < RUN_TESTS; i++) { - int fails = 0; - for (int j = 0; j < RANDOM_REPEATS; j++) - { - const auto result29 = KnapsackSolver(GroupCoins(available_coins.All()), 90 * CENT, CENT); - BOOST_CHECK(result29); - const auto result30 = KnapsackSolver(GroupCoins(available_coins.All()), 90 * CENT, CENT); - BOOST_CHECK(result30); - if (EqualResult(*result29, *result30)) - fails++; - } - BOOST_CHECK_NE(fails, RANDOM_REPEATS); - } - } } BOOST_AUTO_TEST_CASE(ApproximateBestSubset)