mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-02-12 11:19:08 -05: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);
|
SelectionResult result(selection_target);
|
||||||
CAmount curr_value = 0;
|
CAmount curr_value = 0;
|
||||||
|
std::vector<size_t> curr_selection; // selected utxo indexes
|
||||||
std::vector<bool> curr_selection; // select the utxo at this index
|
|
||||||
curr_selection.reserve(utxo_pool.size());
|
|
||||||
|
|
||||||
// Calculate curr_available_value
|
// Calculate curr_available_value
|
||||||
CAmount curr_available_value = 0;
|
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);
|
std::sort(utxo_pool.begin(), utxo_pool.end(), descending);
|
||||||
|
|
||||||
CAmount curr_waste = 0;
|
CAmount curr_waste = 0;
|
||||||
std::vector<bool> best_selection;
|
std::vector<size_t> best_selection;
|
||||||
CAmount best_waste = MAX_MONEY;
|
CAmount best_waste = MAX_MONEY;
|
||||||
|
|
||||||
// Depth First search loop for choosing the UTXOs
|
// 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
|
// Conditions for starting a backtrack
|
||||||
bool backtrack = false;
|
bool backtrack = false;
|
||||||
if (curr_value + curr_available_value < selection_target || // Cannot possibly reach target with the amount remaining in the curr_available_value.
|
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.
|
// explore any more UTXOs to avoid burning money like that.
|
||||||
if (curr_waste <= best_waste) {
|
if (curr_waste <= best_waste) {
|
||||||
best_selection = curr_selection;
|
best_selection = curr_selection;
|
||||||
best_selection.resize(utxo_pool.size());
|
|
||||||
best_waste = curr_waste;
|
best_waste = curr_waste;
|
||||||
if (best_waste == 0) {
|
if (best_waste == 0) {
|
||||||
break;
|
break;
|
||||||
|
@ -116,36 +113,37 @@ std::optional<SelectionResult> SelectCoinsBnB(std::vector<OutputGroup>& utxo_poo
|
||||||
|
|
||||||
// Backtracking, moving backwards
|
// Backtracking, moving backwards
|
||||||
if (backtrack) {
|
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
|
if (curr_selection.empty()) { // We have walked back to the first utxo and no branch is untraversed. All solutions searched
|
||||||
break;
|
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.
|
// Output was included on previous iterations, try excluding now.
|
||||||
curr_selection.back() = false;
|
assert(utxo_pool_index == curr_selection.back());
|
||||||
OutputGroup& utxo = utxo_pool.at(curr_selection.size() - 1);
|
OutputGroup& utxo = utxo_pool.at(utxo_pool_index);
|
||||||
curr_value -= utxo.GetSelectionAmount();
|
curr_value -= utxo.GetSelectionAmount();
|
||||||
curr_waste -= utxo.fee - utxo.long_term_fee;
|
curr_waste -= utxo.fee - utxo.long_term_fee;
|
||||||
|
curr_selection.pop_back();
|
||||||
} else { // Moving forwards, continuing down this branch
|
} 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
|
// Remove this utxo from the curr_available_value utxo amount
|
||||||
curr_available_value -= utxo.GetSelectionAmount();
|
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
|
// 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.
|
// 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() &&
|
if (curr_selection.empty() ||
|
||||||
utxo.GetSelectionAmount() == utxo_pool.at(curr_selection.size() - 1).GetSelectionAmount() &&
|
// The previous index is included and therefore not relevant for exclusion shortcut
|
||||||
utxo.fee == utxo_pool.at(curr_selection.size() - 1).fee) {
|
(utxo_pool_index - 1) == curr_selection.back() ||
|
||||||
curr_selection.push_back(false);
|
utxo.GetSelectionAmount() != utxo_pool.at(utxo_pool_index - 1).GetSelectionAmount() ||
|
||||||
} else {
|
utxo.fee != utxo_pool.at(utxo_pool_index - 1).fee)
|
||||||
|
{
|
||||||
// Inclusion branch first (Largest First Exploration)
|
// Inclusion branch first (Largest First Exploration)
|
||||||
curr_selection.push_back(true);
|
curr_selection.push_back(utxo_pool_index);
|
||||||
curr_value += utxo.GetSelectionAmount();
|
curr_value += utxo.GetSelectionAmount();
|
||||||
curr_waste += utxo.fee - utxo.long_term_fee;
|
curr_waste += utxo.fee - utxo.long_term_fee;
|
||||||
}
|
}
|
||||||
|
@ -158,10 +156,8 @@ std::optional<SelectionResult> SelectCoinsBnB(std::vector<OutputGroup>& utxo_poo
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set output set
|
// Set output set
|
||||||
for (size_t i = 0; i < best_selection.size(); ++i) {
|
for (const size_t& i : best_selection) {
|
||||||
if (best_selection.at(i)) {
|
result.AddInput(utxo_pool.at(i));
|
||||||
result.AddInput(utxo_pool.at(i));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
|
Loading…
Add table
Reference in a new issue