Skip to content

Commit

Permalink
Refactor big UTXO pool knapsack tests
Browse files Browse the repository at this point in the history
  • Loading branch information
murchandamus committed Nov 1, 2023
1 parent 2bca819 commit ee4d637
Show file tree
Hide file tree
Showing 2 changed files with 62 additions and 58 deletions.
63 changes: 62 additions & 1 deletion src/wallet/test/coinselection_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ static void TestKnapsackFail(std::string test_title, std::vector<COutput>& 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<COutput> utxo_pool;

Expand Down Expand Up @@ -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<CoinSet::iterator, CoinSet::iterator> ret = std::mismatch(a.GetInputSet().begin(), a.GetInputSet().end(), b.GetInputSet().begin(),
[](const std::shared_ptr<COutput>& a, const std::shared_ptr<COutput>& 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<COutput> 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<SelectionResult> SelectCoinsBnB(std::vector<COutput>& 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)};
Expand Down
57 changes: 0 additions & 57 deletions src/wallet/test/coinselector_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down

0 comments on commit ee4d637

Please sign in to comment.