Skip to content

Commit

Permalink
wallet: Refill utxo target buckets proactively
Browse files Browse the repository at this point in the history
When utxo targets are specified and a utxo target bucket is depleted, or fees are low, produce excess change that can be split to replenish the utxo target set. This is accomplished by including a large valued utxo as an input to coin selection.
  • Loading branch information
remyers committed Feb 16, 2024
1 parent f47a513 commit a214548
Show file tree
Hide file tree
Showing 2 changed files with 28 additions and 0 deletions.
25 changes: 25 additions & 0 deletions src/wallet/spend.cpp
Expand Up @@ -201,6 +201,20 @@ std::vector<COutput> CoinsResult::All() const
return all;
}

std::optional<COutput> CoinsResult::Max() const
{
std::optional<COutput> max_out;
for (const auto& it : coins) {
const auto out_it = std::max_element(it.second.begin(), it.second.end(), [max_out](const COutput& a, const COutput& b) {
return a.txout.nValue < b.txout.nValue;
});
if (out_it != it.second.end() && (!max_out || out_it->txout.nValue > max_out->txout.nValue)) {
max_out = *out_it;
}
}
return max_out;
}

void CoinsResult::Clear() {
coins.clear();
}
Expand Down Expand Up @@ -1214,12 +1228,14 @@ static util::Result<CreatedTransactionResult> CreateTransactionInternal(

// Load a json file that describes a target utxo set
std::vector<UtxoTarget> utxo_targets;
CFeeRate bucket_refill_feerate{0};
const fs::path utxo_targets_file_path{gArgs.GetPathArg("-utxotargetsfile", fs::path{})};
if (utxo_targets_file_path.empty() == false) {
std::map<std::string, common::SettingsValue> utxo_targets_json;
std::vector<std::string> read_errors;
common::ReadSettings(utxo_targets_file_path, utxo_targets_json, read_errors);
utxo_targets = UtxoTargetsFromJson(utxo_targets_json["buckets"], available_coins);
bucket_refill_feerate = CFeeRate(utxo_targets_json["bucket_refill_feerate"].getInt<CAmount>());
}

if (utxo_targets.size() > 0) {
Expand All @@ -1228,6 +1244,15 @@ static util::Result<CreatedTransactionResult> CreateTransactionInternal(
auto change_target = GenerateChangeTargetFromUtxoTargets(utxo_targets, coin_selection_params.m_change_fee, capacity, rng_fast);
if (change_target) {
coin_selection_params.m_min_change_target = change_target.value();
// Proactively refill target buckets when a utxo target bucket is very low, or fees are low.
bool refill = coin_selection_params.m_effective_feerate < bucket_refill_feerate;
if (capacity < 0.3 || (refill && capacity < 0.7)) {
// Adding our largest available UTXO creates extra change that can be split into target buckets.
auto largest_utxo = available_coins.Max();
if (largest_utxo) {
preset_inputs.Insert(largest_utxo.value(), coin_selection_params.m_subtract_fee_outputs);
}
}
}
}

Expand Down
3 changes: 3 additions & 0 deletions src/wallet/spend.h
Expand Up @@ -43,6 +43,9 @@ struct CoinsResult {
/** Concatenate and return all COutputs as one vector */
std::vector<COutput> All() const;

/** Return COutput with the largest value */
std::optional<COutput> Max() const;

/** The following methods are provided so that CoinsResult can mimic a vector,
* i.e., methods can work with individual OutputType vectors or on the entire object */
size_t Size() const;
Expand Down

0 comments on commit a214548

Please sign in to comment.