Skip to content

Commit

Permalink
opt: Skip evaluation of equivalent input sets
Browse files Browse the repository at this point in the history
When two successive UTXOs match in effective value and weight, we can
skip the second if the prior is not selected: adding it would create an
equivalent input set to a previously evaluated.

E.g. if we have three UTXOs with effective values {5, 3, 3} of the same
weight each, we want to evaluate
{5, _, _}, {5, 3, _}, {5, 3, 3}, {_, 3, _}, {_, 3, 3},
but skip {5, _, 3}, and {_, _, 3}, because the first 3 is not selected,
and we therefore do not need to evaluate the second 3 at the same
position in the input set.

If we reach the end of the branch, we must SHIFT the previously selected
UTXO group instead.
  • Loading branch information
murchandamus committed Feb 9, 2024
1 parent 407b1e3 commit 451be19
Show file tree
Hide file tree
Showing 2 changed files with 22 additions and 5 deletions.
21 changes: 19 additions & 2 deletions src/wallet/coinselection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -401,8 +401,9 @@ util::Result<SelectionResult> CoinGrinder(std::vector<OutputGroup>& utxo_pool, c
};

SelectionResult result(selection_target, SelectionAlgorithm::CG);
bool is_done = false;
size_t curr_try = 0;
while (true) {
while (!is_done) {
bool should_shift{false}, should_cut{false};
// Select `next_utxo`
OutputGroup& utxo = utxo_pool[next_utxo];
Expand Down Expand Up @@ -454,16 +455,32 @@ util::Result<SelectionResult> CoinGrinder(std::vector<OutputGroup>& utxo_pool, c
should_shift = true;
}

if (should_shift) {
while (should_shift) {
// Set `next_utxo` to one after last selected, then deselect last selected UTXO
if (curr_selection.empty()) {
// Exhausted search space before running into attempt limit
is_done = true;
result.SetAlgoCompleted(true);
break;
}
next_utxo = curr_selection.back() + 1;
deselect_last();
should_shift = false;

// After SHIFTing to an omission branch, the `next_utxo` might have the same value and same weight as the
// UTXO we just omitted (i.e. it is a "clone"). If so, selecting `next_utxo` would produce an equivalent
// selection as one we previously evaluated. In that case, increment `next_utxo` until we find a UTXO with a
// differing amount or weight.
while (utxo_pool[next_utxo - 1].GetSelectionAmount() == utxo_pool[next_utxo].GetSelectionAmount()
&& utxo_pool[next_utxo - 1].m_weight == utxo_pool[next_utxo].m_weight) {
if (next_utxo >= utxo_pool.size() - 1) {
// Reached end of UTXO pool skipping clones: SHIFT instead
should_shift = true;
break;
}
// Skip clone: previous UTXO is equivalent and unselected
++next_utxo;
}
}
}

Expand Down
6 changes: 3 additions & 3 deletions src/wallet/test/coinselector_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1180,7 +1180,7 @@ BOOST_AUTO_TEST_CASE(coin_grinder_tests)
});
BOOST_CHECK(res);
// Demonstrate how following improvements reduce iteration count and catch any regressions in the future.
size_t expected_attempts = 100'000;
size_t expected_attempts = 184;
BOOST_CHECK_MESSAGE(res->GetSelectionsEvaluated() == expected_attempts, strprintf("Expected %i attempts, but got %i", expected_attempts, res->GetSelectionsEvaluated()));
}

Expand All @@ -1202,7 +1202,7 @@ BOOST_AUTO_TEST_CASE(coin_grinder_tests)
add_coin(1 * COIN, 2, expected_result);
BOOST_CHECK(EquivalentResult(expected_result, *res));
// Demonstrate how following improvements reduce iteration count and catch any regressions in the future.
size_t expected_attempts = 4;
size_t expected_attempts = 3;
BOOST_CHECK_MESSAGE(res->GetSelectionsEvaluated() == expected_attempts, strprintf("Expected %i attempts, but got %i", expected_attempts, res->GetSelectionsEvaluated()));
}

Expand Down Expand Up @@ -1271,7 +1271,7 @@ BOOST_AUTO_TEST_CASE(coin_grinder_tests)
BOOST_CHECK(EquivalentResult(expected_result, *res));
// Demonstrate how following improvements reduce iteration count and catch any regressions in the future.
// If this takes more attempts, the implementation has regressed
size_t expected_attempts = 82'407;
size_t expected_attempts = 43;
BOOST_CHECK_MESSAGE(res->GetSelectionsEvaluated() == expected_attempts, strprintf("Expected %i attempts, but got %i", expected_attempts, res->GetSelectionsEvaluated()));
}

Expand Down

0 comments on commit 451be19

Please sign in to comment.