@@ -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