mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-01-25 10:43:19 -03:00
refactor: Track BnB selection by index
This is a performance optimization - rather than track all visited values in a bool vector, track the selected index in a vector. This results in a complexity reduction of O(utxo_size) to O(selection_size).
This commit is contained in:
parent
b71a07778f
commit
1dd0923677
1 changed files with 21 additions and 25 deletions
|
@ -66,9 +66,7 @@ std::optional<SelectionResult> SelectCoinsBnB(std::vector<OutputGroup>& utxo_poo
|
|||
{
|
||||
SelectionResult result(selection_target);
|
||||
CAmount curr_value = 0;
|
||||
|
||||
std::vector<bool> curr_selection; // select the utxo at this index
|
||||
curr_selection.reserve(utxo_pool.size());
|
||||
std::vector<size_t> curr_selection; // selected utxo indexes
|
||||
|
||||
// Calculate curr_available_value
|
||||
CAmount curr_available_value = 0;
|
||||
|
@ -85,11 +83,11 @@ std::optional<SelectionResult> SelectCoinsBnB(std::vector<OutputGroup>& utxo_poo
|
|||
std::sort(utxo_pool.begin(), utxo_pool.end(), descending);
|
||||
|
||||
CAmount curr_waste = 0;
|
||||
std::vector<bool> best_selection;
|
||||
std::vector<size_t> best_selection;
|
||||
CAmount best_waste = MAX_MONEY;
|
||||
|
||||
// Depth First search loop for choosing the UTXOs
|
||||
for (size_t i = 0; i < TOTAL_TRIES; ++i) {
|
||||
for (size_t i = 0, utxo_pool_index = 0; i < TOTAL_TRIES; ++i, ++utxo_pool_index) {
|
||||
// Conditions for starting a backtrack
|
||||
bool backtrack = false;
|
||||
if (curr_value + curr_available_value < selection_target || // Cannot possibly reach target with the amount remaining in the curr_available_value.
|
||||
|
@ -104,7 +102,6 @@ std::optional<SelectionResult> SelectCoinsBnB(std::vector<OutputGroup>& utxo_poo
|
|||
// explore any more UTXOs to avoid burning money like that.
|
||||
if (curr_waste <= best_waste) {
|
||||
best_selection = curr_selection;
|
||||
best_selection.resize(utxo_pool.size());
|
||||
best_waste = curr_waste;
|
||||
if (best_waste == 0) {
|
||||
break;
|
||||
|
@ -116,36 +113,37 @@ std::optional<SelectionResult> SelectCoinsBnB(std::vector<OutputGroup>& utxo_poo
|
|||
|
||||
// Backtracking, moving backwards
|
||||
if (backtrack) {
|
||||
// Walk backwards to find the last included UTXO that still needs to have its omission branch traversed.
|
||||
while (!curr_selection.empty() && !curr_selection.back()) {
|
||||
curr_selection.pop_back();
|
||||
curr_available_value += utxo_pool.at(curr_selection.size()).GetSelectionAmount();
|
||||
}
|
||||
|
||||
if (curr_selection.empty()) { // We have walked back to the first utxo and no branch is untraversed. All solutions searched
|
||||
break;
|
||||
}
|
||||
|
||||
// Walk backwards to find the last included UTXO that still needs to have its omission branch traversed.
|
||||
for (--utxo_pool_index; utxo_pool_index > curr_selection.back(); --utxo_pool_index) {
|
||||
curr_available_value += utxo_pool.at(utxo_pool_index).GetSelectionAmount();
|
||||
}
|
||||
|
||||
// Output was included on previous iterations, try excluding now.
|
||||
curr_selection.back() = false;
|
||||
OutputGroup& utxo = utxo_pool.at(curr_selection.size() - 1);
|
||||
assert(utxo_pool_index == curr_selection.back());
|
||||
OutputGroup& utxo = utxo_pool.at(utxo_pool_index);
|
||||
curr_value -= utxo.GetSelectionAmount();
|
||||
curr_waste -= utxo.fee - utxo.long_term_fee;
|
||||
curr_selection.pop_back();
|
||||
} else { // Moving forwards, continuing down this branch
|
||||
OutputGroup& utxo = utxo_pool.at(curr_selection.size());
|
||||
OutputGroup& utxo = utxo_pool.at(utxo_pool_index);
|
||||
|
||||
// Remove this utxo from the curr_available_value utxo amount
|
||||
curr_available_value -= utxo.GetSelectionAmount();
|
||||
|
||||
// Avoid searching a branch if the previous UTXO has the same value and same waste and was excluded. 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.
|
||||
if (!curr_selection.empty() && !curr_selection.back() &&
|
||||
utxo.GetSelectionAmount() == utxo_pool.at(curr_selection.size() - 1).GetSelectionAmount() &&
|
||||
utxo.fee == utxo_pool.at(curr_selection.size() - 1).fee) {
|
||||
curr_selection.push_back(false);
|
||||
} else {
|
||||
if (curr_selection.empty() ||
|
||||
// The previous index is included and therefore not relevant for exclusion shortcut
|
||||
(utxo_pool_index - 1) == curr_selection.back() ||
|
||||
utxo.GetSelectionAmount() != utxo_pool.at(utxo_pool_index - 1).GetSelectionAmount() ||
|
||||
utxo.fee != utxo_pool.at(utxo_pool_index - 1).fee)
|
||||
{
|
||||
// Inclusion branch first (Largest First Exploration)
|
||||
curr_selection.push_back(true);
|
||||
curr_selection.push_back(utxo_pool_index);
|
||||
curr_value += utxo.GetSelectionAmount();
|
||||
curr_waste += utxo.fee - utxo.long_term_fee;
|
||||
}
|
||||
|
@ -158,10 +156,8 @@ std::optional<SelectionResult> SelectCoinsBnB(std::vector<OutputGroup>& utxo_poo
|
|||
}
|
||||
|
||||
// Set output set
|
||||
for (size_t i = 0; i < best_selection.size(); ++i) {
|
||||
if (best_selection.at(i)) {
|
||||
result.AddInput(utxo_pool.at(i));
|
||||
}
|
||||
for (const size_t& i : best_selection) {
|
||||
result.AddInput(utxo_pool.at(i));
|
||||
}
|
||||
|
||||
return result;
|
||||
|
|
Loading…
Add table
Reference in a new issue