Skip to content

Commit

Permalink
Add Gutter Guard Selector
Browse files Browse the repository at this point in the history
Does a random selection limited to three more output groups than
largest-first selection would select to fund the transaction. If the
limit is exceeded during selection, the output group with the lowest
effective value is discarded.
  • Loading branch information
murchandamus committed Nov 30, 2023
1 parent ffb0216 commit bd3f3cd
Show file tree
Hide file tree
Showing 3 changed files with 95 additions and 1 deletion.
74 changes: 74 additions & 0 deletions src/wallet/coinselection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,79 @@ util::Result<SelectionResult> SelectCoinsSRD(const std::vector<OutputGroup>& utx
return max_tx_weight_exceeded ? ErrorMaxWeightExceeded() : util::Error();
}

util::Result<SelectionResult> SelectCoinsGG(std::vector<OutputGroup>& utxo_pool, CAmount target_value, CAmount change_fee, FastRandomContext& rng,
int max_weight)
{
SelectionResult result(target_value, SelectionAlgorithm::PF);
std::priority_queue<OutputGroup, std::vector<OutputGroup>, MinOutputGroupComparator> heap;

// Include change for PF as we want to avoid making really small change if the selection just
// barely meets the target.
target_value += CHANGE_LOWER + change_fee;

std::sort(utxo_pool.begin(), utxo_pool.end(), descending);
CAmount selected_lf_amount = 0;
size_t lf_group_count = 0;

for (const OutputGroup& group : utxo_pool) {
selected_lf_amount += group.GetSelectionAmount();
lf_group_count++;
if (selected_lf_amount >= target_value) {
break;
}
}

size_t gg_group_limit = lf_group_count + 3;

std::vector<size_t> indexes;
indexes.resize(utxo_pool.size());
std::iota(indexes.begin(), indexes.end(), 0);
Shuffle(indexes.begin(), indexes.end(), rng);

CAmount selected_eff_value = 0;
int weight = 0;
bool max_tx_weight_exceeded = false;
for (const size_t i : indexes) {
const OutputGroup& group = utxo_pool.at(i);
Assume(group.GetSelectionAmount() > 0);

// Add group to selection
heap.push(group);
selected_eff_value += group.GetSelectionAmount();
weight += group.m_weight;

// If the selection weight exceeds the maximum allowed size, remove the least valuable inputs until we
// are below max weight.
if (weight > max_weight) {
max_tx_weight_exceeded = true; // mark it in case we don't find any useful result.
do {
const OutputGroup& to_remove_group = heap.top();
selected_eff_value -= to_remove_group.GetSelectionAmount();
weight -= to_remove_group.m_weight;
heap.pop();
} while (!heap.empty() && weight > max_weight);
}

while (!heap.empty() && heap.size() > gg_group_limit) {
const OutputGroup& to_remove_group = heap.top();
selected_eff_value -= to_remove_group.GetSelectionAmount();
weight -= to_remove_group.m_weight;
heap.pop();
}

// Now check if we are above the target
if (selected_eff_value >= target_value) {
// Result found, add it.
while (!heap.empty()) {
result.AddInput(heap.top());
heap.pop();
}
return result;
}
}
return max_tx_weight_exceeded ? ErrorMaxWeightExceeded() : util::Error();
}

/** Find a subset of the OutputGroups that is at least as large as, but as close as possible to, the
* target amount; solve subset sum.
* param@[in] groups OutputGroups to choose from, sorted by value in descending order.
Expand Down Expand Up @@ -602,6 +675,7 @@ std::string GetAlgorithmName(const SelectionAlgorithm algo)
case SelectionAlgorithm::BNB: return "bnb";
case SelectionAlgorithm::KNAPSACK: return "knapsack";
case SelectionAlgorithm::SRD: return "srd";
case SelectionAlgorithm::PF: return "pf";
case SelectionAlgorithm::MANUAL: return "manual";
// No default case to allow for compiler to warn
}
Expand Down
18 changes: 17 additions & 1 deletion src/wallet/coinselection.h
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,8 @@ enum class SelectionAlgorithm : uint8_t
BNB = 0,
KNAPSACK = 1,
SRD = 2,
MANUAL = 3,
GG = 3,
MANUAL = 4,
};

std::string GetAlgorithmName(const SelectionAlgorithm algo);
Expand Down Expand Up @@ -442,6 +443,21 @@ util::Result<SelectionResult> SelectCoinsBnB(std::vector<OutputGroup>& utxo_pool
util::Result<SelectionResult> SelectCoinsSRD(const std::vector<OutputGroup>& utxo_pool, CAmount target_value, CAmount change_fee, FastRandomContext& rng,
int max_weight);

/** Select coins by Gutter Guard Selector. Limits the number of unnecessary
* OutputGroups to three. OutputGroups are selected randomly from the eligible
* output groups until the target is satisfied, if the permitted count of
* OutputGroups is exceeded, the output group with the smallest amount is
* dropped from the input set.
*
* @param[in] utxo_pool The positive effective value OutputGroups eligible for selection
* @param[in] target_value The target value to select for
* @param[in] rng The randomness source to shuffle coins
* @param[in] max_weight The maximum allowed weight for a selection result to be valid
* @returns If successful, a valid SelectionResult, otherwise, util::Error
*/
util::Result<SelectionResult> SelectCoinsGG(std::vector<OutputGroup>& utxo_pool, CAmount target_value, CAmount change_fee, FastRandomContext& rng,
int max_weight);

// Original coin selection algorithm as a fallback
util::Result<SelectionResult> KnapsackSolver(std::vector<OutputGroup>& groups, const CAmount& nTargetValue,
CAmount change_target, FastRandomContext& rng, int max_weight);
Expand Down
4 changes: 4 additions & 0 deletions src/wallet/spend.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -709,6 +709,10 @@ util::Result<SelectionResult> ChooseSelectionResult(interfaces::Chain& chain, co
results.push_back(*srd_result);
} else append_error(srd_result);

if (auto gg_result{SelectCoinsGG(groups.positive_group, nTargetValue, coin_selection_params.m_change_fee, coin_selection_params.rng_fast, max_inputs_weight)}) {
results.push_back(*gg_result);
} else append_error(gg_result);

if (results.empty()) {
// No solution found, retrieve the first explicit error (if any).
// future: add 'severity level' to errors so the worst one can be retrieved instead of the first one.
Expand Down

0 comments on commit bd3f3cd

Please sign in to comment.