Skip to content

Commit

Permalink
opt: Cut if last addition was minimal weight
Browse files Browse the repository at this point in the history
In situations where we have UTXO groups of various weight, we can CUT
rather than SHIFT when we exceeded the max_weight or the best
selection’s weight while the last step was equal to the minimum weight
in the lookahead.
  • Loading branch information
murchandamus committed Feb 6, 2024
1 parent 6bdcc64 commit 752dc5c
Show file tree
Hide file tree
Showing 2 changed files with 23 additions and 8 deletions.
27 changes: 21 additions & 6 deletions src/wallet/coinselection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -327,15 +327,20 @@ util::Result<SelectionResult> CoinGrinder(std::vector<OutputGroup>& utxo_pool, c
std::sort(utxo_pool.begin(), utxo_pool.end(), descending_effval_weight);
// The sum of UTXO amounts after this UTXO index, e.g. lookahead[5] = Σ(UTXO[6+].amount)
std::vector<CAmount> lookahead(utxo_pool.size());
// The minimum UTXO weight among the remaining UTXOs after this UTXO index, e.g. min_tail_weight[5] = min(UTXO[6+].weight)
std::vector<int> min_tail_weight(utxo_pool.size());

// Calculate lookahead values, and check that there are sufficient funds
// Calculate lookahead values, min_tail_weights, and check that there are sufficient funds
CAmount total_available = 0;
int min_group_weight = std::numeric_limits<int>::max();
for (size_t i = 0; i < utxo_pool.size(); ++i) {
size_t index = utxo_pool.size() - 1 - i; // Loop over every element in reverse order
lookahead[index] = total_available;
min_tail_weight[index] = min_group_weight;
// UTXOs with non-positive effective value must have been filtered
Assume(utxo_pool[index].GetSelectionAmount() > 0);
total_available += utxo_pool[index].GetSelectionAmount();
min_group_weight = std::min(min_group_weight, utxo_pool[index].m_weight);
}

const CAmount total_target = selection_target + change_target;
Expand Down Expand Up @@ -426,16 +431,26 @@ util::Result<SelectionResult> CoinGrinder(std::vector<OutputGroup>& utxo_pool, c
++curr_try;

// EVALUATE current selection: check for solutions and see whether we can CUT or SHIFT before EXPLORING further
if (curr_amount + lookahead[curr_selection.back()] < total_target) {
auto curr_tail = curr_selection.back();
if (curr_amount + lookahead[curr_tail] < total_target) {
// Insufficient funds with lookahead: CUT
should_cut = true;
} else if (curr_weight > max_weight) {
// max_weight exceeded: SHIFT
// max_weight exceeded: CUT if last selected group had minimal weight, else SHIFT
max_tx_weight_exceeded = true;
should_shift = true;
if (utxo_pool[curr_tail].m_weight <= min_tail_weight[curr_tail]) {
should_cut = true;
} else {
should_shift = true;
}
} else if (curr_weight > best_selection_weight) {
// Worse weight than best solution. More UTXOs only increase weight: SHIFT
should_shift = true;
// Worse weight than best solution. More UTXOs only increase weight:
// CUT if last selected group had minimal weight, else SHIFT
if (utxo_pool[curr_tail].m_weight <= min_tail_weight[curr_tail]) {
should_cut = true;
} else {
should_shift = true;
}
} else if (curr_amount >= total_target) {
// Success, adding more weight cannot be better: SHIFT
should_shift = true;
Expand Down
4 changes: 2 additions & 2 deletions src/wallet/test/coinselector_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1226,7 +1226,7 @@ BOOST_AUTO_TEST_CASE(coin_grinder_tests)
});
BOOST_CHECK(res);
// If this takes more attempts, the implementation has regressed
size_t expected_attempts = 281;
size_t expected_attempts = 218;
BOOST_CHECK_MESSAGE(res->GetSelectionsEvaluated() == expected_attempts, strprintf("Expected %i attempts, but got %i", expected_attempts, res->GetSelectionsEvaluated()));
}

Expand Down Expand Up @@ -1265,7 +1265,7 @@ BOOST_AUTO_TEST_CASE(coin_grinder_tests)
add_coin(1 * COIN, 0, expected_result);
BOOST_CHECK(EquivalentResult(expected_result, *res));
// If this takes more attempts, the implementation has regressed
size_t expected_attempts = 43;
size_t expected_attempts = 42;
BOOST_CHECK_MESSAGE(res->GetSelectionsEvaluated() == expected_attempts, strprintf("Expected %i attempts, but got %i", expected_attempts, res->GetSelectionsEvaluated()));
}

Expand Down

0 comments on commit 752dc5c

Please sign in to comment.