diff --git a/src/wallet/coinselection.cpp b/src/wallet/coinselection.cpp index b9c305c53d1e12..a7764f558aabef 100644 --- a/src/wallet/coinselection.cpp +++ b/src/wallet/coinselection.cpp @@ -301,8 +301,8 @@ util::Result CoinGrinder(std::vector& utxo_pool, c curr_amount += utxo.GetSelectionAmount(); curr_weight += utxo.m_weight; curr_selection.push_back(next_utxo); - ++curr_try; ++next_utxo; + ++curr_try; // EVALUATE current selection, default to exploring the inclusion branch further, else do exactly one SHIFT or CUT. if (curr_amount + lookahead[curr_selection.back()] < selection_target + change_target) { @@ -340,42 +340,45 @@ util::Result CoinGrinder(std::vector& utxo_pool, c } if (should_cut) { - // Redirect to exploring Omission branch of second to last selected UTXO (i.e. deselect last selected UTXO, then SHIFT) + + // Neither adding to the current selection nor exploring the omission branch of the last selected UTXO can + // find any solutions. Redirect to exploring Omission branch of the penultimate selected UTXO (i.e. deselect + // last selected UTXO, then SHIFT) + should_cut = false; deselect_last(); should_shift = true; } - if (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 - result.SetAlgoCompleted(true); - break; - } - next_utxo = curr_selection.back() + 1; - deselect_last(); - should_shift = false; - } - - // Find next undecided UTXO that does not produce equivalent prefix - while (next_utxo > 0 - && (curr_selection.empty() || curr_selection.back() != next_utxo - 1) - && utxo_pool[next_utxo - 1].GetSelectionAmount() == utxo_pool[next_utxo].GetSelectionAmount() - && utxo_pool[next_utxo - 1].fee == utxo_pool[next_utxo].fee) { - if (next_utxo < utxo_pool.size() - 1) { - // Skip if previous UTXO is equivalent and unselected - ++next_utxo; - } else { - // "Skipping" end of branch: SHIFT instead + while (!is_done && should_shift) { + if (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 limit + // 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 duplicate). 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 (!should_shift && next_utxo > 0 + && (curr_selection.empty() || curr_selection.back() != next_utxo - 1) + && utxo_pool[next_utxo - 1].GetSelectionAmount() == utxo_pool[next_utxo].GetSelectionAmount() + && utxo_pool[next_utxo - 1].fee == utxo_pool[next_utxo].fee) { + if (next_utxo < utxo_pool.size() - 1) { + // Skip clone: previous UTXO is equivalent and unselected + ++next_utxo; + } else { + // Reached end of UTXO pool skipping clones: SHIFT instead + should_shift = true; + } } } }