Skip to content

Commit 175970e

Browse files
committed
merge bitcoin#13226: Optimize SelectCoinsBnB by tracking the selection by index rather than by position
1 parent 27933a7 commit 175970e

File tree

1 file changed

+28
-32
lines changed

1 file changed

+28
-32
lines changed

src/wallet/coinselection.cpp

Lines changed: 28 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -69,14 +69,13 @@ std::optional<SelectionResult> SelectCoinsBnB(std::vector<OutputGroup>& utxo_poo
6969

7070
SelectionResult result(selection_target, SelectionAlgorithm::BNB);
7171
CAmount curr_value = 0;
72-
73-
std::vector<bool> curr_selection; // select the utxo at this index
74-
curr_selection.reserve(utxo_pool.size());
72+
std::vector<size_t> curr_selection; // selected utxo indexes
7573

7674
// Calculate curr_available_value
7775
CAmount curr_available_value = 0;
7876
for (const OutputGroup& utxo : utxo_pool) {
79-
// Assert that this utxo is not negative. It should never be negative, effective value calculation should have removed it
77+
// Assert that this utxo is not negative. It should never be negative,
78+
// effective value calculation should have removed it
8079
assert(utxo.GetSelectionAmount() > 0);
8180
curr_available_value += utxo.GetSelectionAmount();
8281
}
@@ -88,15 +87,15 @@ std::optional<SelectionResult> SelectCoinsBnB(std::vector<OutputGroup>& utxo_poo
8887
std::sort(utxo_pool.begin(), utxo_pool.end(), descending);
8988

9089
CAmount curr_waste = 0;
91-
std::vector<bool> best_selection;
90+
std::vector<size_t> best_selection;
9291
CAmount best_waste = MAX_MONEY;
9392

9493
// Depth First search loop for choosing the UTXOs
95-
for (size_t i = 0; i < TOTAL_TRIES; ++i) {
94+
for (size_t curr_try = 0, utxo_pool_index = 0; curr_try < TOTAL_TRIES; ++curr_try, ++utxo_pool_index) {
9695
// Conditions for starting a backtrack
9796
bool backtrack = false;
98-
if (curr_value + curr_available_value < selection_target || // Cannot possibly reach target with the amount remaining in the curr_available_value.
99-
curr_value > selection_target + cost_of_change || // Selected value is out of range, go back and try other branch
97+
if (curr_value + curr_available_value < selection_target || // Cannot possibly reach target with the amount remaining in the curr_available_value.
98+
curr_value > selection_target + cost_of_change || // Selected value is out of range, go back and try other branch
10099
(curr_waste > best_waste && (utxo_pool.at(0).fee - utxo_pool.at(0).long_term_fee) > 0)) { // Don't select things which we know will be more wasteful if the waste is increasing
101100
backtrack = true;
102101
} else if (curr_value >= selection_target) { // Selected value is within range
@@ -107,45 +106,44 @@ std::optional<SelectionResult> SelectCoinsBnB(std::vector<OutputGroup>& utxo_poo
107106
// explore any more UTXOs to avoid burning money like that.
108107
if (curr_waste <= best_waste) {
109108
best_selection = curr_selection;
110-
best_selection.resize(utxo_pool.size());
111109
best_waste = curr_waste;
112110
}
113111
curr_waste -= (curr_value - selection_target); // Remove the excess value as we will be selecting different coins now
114112
backtrack = true;
115113
}
116114

117-
// Backtracking, moving backwards
118-
if (backtrack) {
119-
// Walk backwards to find the last included UTXO that still needs to have its omission branch traversed.
120-
while (!curr_selection.empty() && !curr_selection.back()) {
121-
curr_selection.pop_back();
122-
curr_available_value += utxo_pool.at(curr_selection.size()).GetSelectionAmount();
123-
}
124-
115+
if (backtrack) { // Backtracking, moving backwards
125116
if (curr_selection.empty()) { // We have walked back to the first utxo and no branch is untraversed. All solutions searched
126117
break;
127118
}
128119

120+
// Add omitted UTXOs back to lookahead before traversing the omission branch of last included UTXO.
121+
for (--utxo_pool_index; utxo_pool_index > curr_selection.back(); --utxo_pool_index) {
122+
curr_available_value += utxo_pool.at(utxo_pool_index).GetSelectionAmount();
123+
}
124+
129125
// Output was included on previous iterations, try excluding now.
130-
curr_selection.back() = false;
131-
OutputGroup& utxo = utxo_pool.at(curr_selection.size() - 1);
126+
assert(utxo_pool_index == curr_selection.back());
127+
OutputGroup& utxo = utxo_pool.at(utxo_pool_index);
132128
curr_value -= utxo.GetSelectionAmount();
133129
curr_waste -= utxo.fee - utxo.long_term_fee;
130+
curr_selection.pop_back();
134131
} else { // Moving forwards, continuing down this branch
135-
OutputGroup& utxo = utxo_pool.at(curr_selection.size());
132+
OutputGroup& utxo = utxo_pool.at(utxo_pool_index);
136133

137134
// Remove this utxo from the curr_available_value utxo amount
138135
curr_available_value -= utxo.GetSelectionAmount();
139136

140-
// Avoid searching a branch if the previous UTXO has the same value and same waste and was excluded. Since the ratio of fee to
141-
// long term fee is the same, we only need to check if one of those values match in order to know that the waste is the same.
142-
if (!curr_selection.empty() && !curr_selection.back() &&
143-
utxo.GetSelectionAmount() == utxo_pool.at(curr_selection.size() - 1).GetSelectionAmount() &&
144-
utxo.fee == utxo_pool.at(curr_selection.size() - 1).fee) {
145-
curr_selection.push_back(false);
146-
} else {
137+
if (curr_selection.empty() ||
138+
// The previous index is included and therefore not relevant for exclusion shortcut
139+
(utxo_pool_index - 1) == curr_selection.back() ||
140+
// Avoid searching a branch if the previous UTXO has the same value and same waste and was excluded.
141+
// Since the ratio of fee to long term fee is the same, we only need to check if one of those values match in order to know that the waste is the same.
142+
utxo.GetSelectionAmount() != utxo_pool.at(utxo_pool_index - 1).GetSelectionAmount() ||
143+
utxo.fee != utxo_pool.at(utxo_pool_index - 1).fee)
144+
{
147145
// Inclusion branch first (Largest First Exploration)
148-
curr_selection.push_back(true);
146+
curr_selection.push_back(utxo_pool_index);
149147
curr_value += utxo.GetSelectionAmount();
150148
curr_waste += utxo.fee - utxo.long_term_fee;
151149
}
@@ -158,10 +156,8 @@ std::optional<SelectionResult> SelectCoinsBnB(std::vector<OutputGroup>& utxo_poo
158156
}
159157

160158
// Set output set
161-
for (size_t i = 0; i < best_selection.size(); ++i) {
162-
if (best_selection.at(i)) {
163-
result.AddInput(utxo_pool.at(i));
164-
}
159+
for (const size_t& i : best_selection) {
160+
result.AddInput(utxo_pool.at(i));
165161
}
166162
result.ComputeAndSetWaste(CAmount{0});
167163
assert(best_waste == result.GetWaste());

0 commit comments

Comments
 (0)