aboutsummaryrefslogtreecommitdiff
path: root/src/wallet/spend.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/wallet/spend.cpp')
-rw-r--r--src/wallet/spend.cpp50
1 files changed, 42 insertions, 8 deletions
diff --git a/src/wallet/spend.cpp b/src/wallet/spend.cpp
index fd7f279505..96c9a3dc16 100644
--- a/src/wallet/spend.cpp
+++ b/src/wallet/spend.cpp
@@ -259,6 +259,7 @@ util::Result<PreSelectedInputs> FetchSelectedInputs(const CWallet& wallet, const
{
PreSelectedInputs result;
const bool can_grind_r = wallet.CanGrindR();
+ std::map<COutPoint, CAmount> map_of_bump_fees = wallet.chain().CalculateIndividualBumpFees(coin_control.ListSelected(), coin_selection_params.m_effective_feerate);
for (const COutPoint& outpoint : coin_control.ListSelected()) {
int input_bytes = -1;
CTxOut txout;
@@ -294,6 +295,7 @@ util::Result<PreSelectedInputs> FetchSelectedInputs(const CWallet& wallet, const
/* Set some defaults for depth, spendable, solvable, safe, time, and from_me as these don't matter for preset inputs since no selection is being done. */
COutput output(outpoint, txout, /*depth=*/ 0, input_bytes, /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, /*time=*/ 0, /*from_me=*/ false, coin_selection_params.m_effective_feerate);
+ output.ApplyBumpFee(map_of_bump_fees.at(output.outpoint));
result.Insert(output, coin_selection_params.m_subtract_fee_outputs);
}
return result;
@@ -314,6 +316,7 @@ CoinsResult AvailableCoins(const CWallet& wallet,
const int max_depth = {coinControl ? coinControl->m_max_depth : DEFAULT_MAX_DEPTH};
const bool only_safe = {coinControl ? !coinControl->m_include_unsafe_inputs : true};
const bool can_grind_r = wallet.CanGrindR();
+ std::vector<COutPoint> outpoints;
std::set<uint256> trusted_parents;
for (const auto& entry : wallet.mapWallet)
@@ -433,6 +436,8 @@ CoinsResult AvailableCoins(const CWallet& wallet,
result.Add(GetOutputType(type, is_from_p2sh),
COutput(outpoint, output, nDepth, input_bytes, spendable, solvable, safeTx, wtx.GetTxTime(), tx_from_me, feerate));
+ outpoints.push_back(outpoint);
+
// Checks the sum amount of all UTXO's.
if (params.min_sum_amount != MAX_MONEY) {
if (result.GetTotalAmount() >= params.min_sum_amount) {
@@ -447,6 +452,16 @@ CoinsResult AvailableCoins(const CWallet& wallet,
}
}
+ if (feerate.has_value()) {
+ std::map<COutPoint, CAmount> map_of_bump_fees = wallet.chain().CalculateIndividualBumpFees(outpoints, feerate.value());
+
+ for (auto& [_, outputs] : result.coins) {
+ for (auto& output : outputs) {
+ output.ApplyBumpFee(map_of_bump_fees.at(output.outpoint));
+ }
+ }
+ }
+
return result;
}
@@ -628,13 +643,13 @@ FilteredOutputGroups GroupOutputs(const CWallet& wallet,
// Returns true if the result contains an error and the message is not empty
static bool HasErrorMsg(const util::Result<SelectionResult>& res) { return !util::ErrorString(res).empty(); }
-util::Result<SelectionResult> AttemptSelection(const CAmount& nTargetValue, OutputGroupTypeMap& groups,
+util::Result<SelectionResult> AttemptSelection(interfaces::Chain& chain, const CAmount& nTargetValue, OutputGroupTypeMap& groups,
const CoinSelectionParams& coin_selection_params, bool allow_mixed_output_types)
{
// Run coin selection on each OutputType and compute the Waste Metric
std::vector<SelectionResult> results;
for (auto& [type, group] : groups.groups_by_type) {
- auto result{ChooseSelectionResult(nTargetValue, group, coin_selection_params)};
+ auto result{ChooseSelectionResult(chain, nTargetValue, group, coin_selection_params)};
// If any specific error message appears here, then something particularly wrong happened.
if (HasErrorMsg(result)) return result; // So let's return the specific error.
// Append the favorable result.
@@ -648,14 +663,14 @@ util::Result<SelectionResult> AttemptSelection(const CAmount& nTargetValue, Outp
// over all available coins, which would allow mixing.
// If TypesCount() <= 1, there is nothing to mix.
if (allow_mixed_output_types && groups.TypesCount() > 1) {
- return ChooseSelectionResult(nTargetValue, groups.all_groups, coin_selection_params);
+ return ChooseSelectionResult(chain, nTargetValue, groups.all_groups, coin_selection_params);
}
// Either mixing is not allowed and we couldn't find a solution from any single OutputType, or mixing was allowed and we still couldn't
// find a solution using all available coins
return util::Error();
};
-util::Result<SelectionResult> ChooseSelectionResult(const CAmount& nTargetValue, Groups& groups, const CoinSelectionParams& coin_selection_params)
+util::Result<SelectionResult> ChooseSelectionResult(interfaces::Chain& chain, const CAmount& nTargetValue, Groups& groups, const CoinSelectionParams& coin_selection_params)
{
// Vector of results. We will choose the best one based on waste.
std::vector<SelectionResult> results;
@@ -680,12 +695,10 @@ util::Result<SelectionResult> ChooseSelectionResult(const CAmount& nTargetValue,
// The knapsack solver has some legacy behavior where it will spend dust outputs. We retain this behavior, so don't filter for positive only here.
if (auto knapsack_result{KnapsackSolver(groups.mixed_group, nTargetValue, coin_selection_params.m_min_change_target, coin_selection_params.rng_fast, max_inputs_weight)}) {
- knapsack_result->ComputeAndSetWaste(coin_selection_params.min_viable_change, coin_selection_params.m_cost_of_change, coin_selection_params.m_change_fee);
results.push_back(*knapsack_result);
} else append_error(knapsack_result);
if (auto srd_result{SelectCoinsSRD(groups.positive_group, nTargetValue, coin_selection_params.m_change_fee, coin_selection_params.rng_fast, max_inputs_weight)}) {
- srd_result->ComputeAndSetWaste(coin_selection_params.min_viable_change, coin_selection_params.m_cost_of_change, coin_selection_params.m_change_fee);
results.push_back(*srd_result);
} else append_error(srd_result);
@@ -695,6 +708,27 @@ util::Result<SelectionResult> ChooseSelectionResult(const CAmount& nTargetValue,
return errors.empty() ? util::Error() : errors.front();
}
+ // If the chosen input set has unconfirmed inputs, check for synergies from overlapping ancestry
+ for (auto& result : results) {
+ std::vector<COutPoint> outpoints;
+ std::set<std::shared_ptr<COutput>> coins = result.GetInputSet();
+ CAmount summed_bump_fees = 0;
+ for (auto& coin : coins) {
+ if (coin->depth > 0) continue; // Bump fees only exist for unconfirmed inputs
+ outpoints.push_back(coin->outpoint);
+ summed_bump_fees += coin->ancestor_bump_fees;
+ }
+ std::optional<CAmount> combined_bump_fee = chain.CalculateCombinedBumpFee(outpoints, coin_selection_params.m_effective_feerate);
+ if (!combined_bump_fee.has_value()) {
+ return util::Error{_("Failed to calculate bump fees, because unconfirmed UTXOs depend on enormous cluster of unconfirmed transactions.")};
+ }
+ CAmount bump_fee_overestimate = summed_bump_fees - combined_bump_fee.value();
+ if (bump_fee_overestimate) {
+ result.SetBumpFeeDiscount(bump_fee_overestimate);
+ }
+ result.ComputeAndSetWaste(coin_selection_params.min_viable_change, coin_selection_params.m_cost_of_change, coin_selection_params.m_change_fee);
+ }
+
// Choose the result with the least waste
// If the waste is the same, choose the one which spends more inputs.
return *std::min_element(results.begin(), results.end());
@@ -824,7 +858,7 @@ util::Result<SelectionResult> AutomaticCoinSelection(const CWallet& wallet, Coin
for (const auto& select_filter : ordered_filters) {
auto it = filtered_groups.find(select_filter.filter);
if (it == filtered_groups.end()) continue;
- if (auto res{AttemptSelection(value_to_select, it->second,
+ if (auto res{AttemptSelection(wallet.chain(), value_to_select, it->second,
coin_selection_params, select_filter.allow_mixed_output_types)}) {
return res; // result found
} else {
@@ -1120,7 +1154,7 @@ static util::Result<CreatedTransactionResult> CreateTransactionInternal(
if (nBytes == -1) {
return util::Error{_("Missing solving data for estimating transaction size")};
}
- CAmount fee_needed = coin_selection_params.m_effective_feerate.GetFee(nBytes);
+ CAmount fee_needed = coin_selection_params.m_effective_feerate.GetFee(nBytes) + result.GetTotalBumpFees();
const CAmount output_value = CalculateOutputValue(txNew);
Assume(recipients_sum + change_amount == output_value);
CAmount current_fee = result.GetSelectedValue() - output_value;