diff --git a/src/wallet/coinselection.cpp b/src/wallet/coinselection.cpp index 49e6bac462..37cc7e37c8 100644 --- a/src/wallet/coinselection.cpp +++ b/src/wallet/coinselection.cpp @@ -389,14 +389,14 @@ CAmount GetSelectionWaste(const std::set& inputs, CAmount change_cost, return waste; } -CAmount GenerateChangeTarget(CAmount payment_value, FastRandomContext& rng) +CAmount GenerateChangeTarget(const CAmount payment_value, const CAmount change_fee, FastRandomContext& rng) { if (payment_value <= CHANGE_LOWER / 2) { - return CHANGE_LOWER; + return change_fee + CHANGE_LOWER; } else { // random value between 50ksat and min (payment_value * 2, 1milsat) const auto upper_bound = std::min(payment_value * 2, CHANGE_UPPER); - return rng.randrange(upper_bound - CHANGE_LOWER) + CHANGE_LOWER; + return change_fee + rng.randrange(upper_bound - CHANGE_LOWER) + CHANGE_LOWER; } } diff --git a/src/wallet/coinselection.h b/src/wallet/coinselection.h index e257d3f7c7..191476779f 100644 --- a/src/wallet/coinselection.h +++ b/src/wallet/coinselection.h @@ -252,6 +252,7 @@ struct OutputGroup /** Choose a random change target for each transaction to make it harder to fingerprint the Core * wallet based on the change output values of transactions it creates. + * Change target covers at least change fees and adds a random value on top of it. * The random value is between 50ksat and min(2 * payment_value, 1milsat) * When payment_value <= 25ksat, the value is just 50ksat. * @@ -261,8 +262,9 @@ struct OutputGroup * coins selected are just sufficient to cover the payment amount ("unnecessary input" heuristic). * * @param[in] payment_value Average payment value of the transaction output(s). + * @param[in] change_fee Fee for creating a change output. */ -[[nodiscard]] CAmount GenerateChangeTarget(CAmount payment_value, FastRandomContext& rng); +[[nodiscard]] CAmount GenerateChangeTarget(const CAmount payment_value, const CAmount change_fee, FastRandomContext& rng); enum class SelectionAlgorithm : uint8_t { diff --git a/src/wallet/spend.cpp b/src/wallet/spend.cpp index 61d86844df..9babb5e04e 100644 --- a/src/wallet/spend.cpp +++ b/src/wallet/spend.cpp @@ -794,7 +794,6 @@ static util::Result CreateTransactionInternal( coin_selection_params.m_subtract_fee_outputs = true; } } - coin_selection_params.m_min_change_target = GenerateChangeTarget(std::floor(recipients_sum / vecSend.size()), rng_fast); // Create change script that will be used if we need change CScript scriptChange; @@ -863,6 +862,8 @@ static util::Result CreateTransactionInternal( coin_selection_params.m_change_fee = coin_selection_params.m_effective_feerate.GetFee(coin_selection_params.change_output_size); coin_selection_params.m_cost_of_change = coin_selection_params.m_discard_feerate.GetFee(coin_selection_params.change_spend_size) + coin_selection_params.m_change_fee; + coin_selection_params.m_min_change_target = GenerateChangeTarget(std::floor(recipients_sum / vecSend.size()), coin_selection_params.m_change_fee, rng_fast); + // vouts to the payees if (!coin_selection_params.m_subtract_fee_outputs) { coin_selection_params.tx_noinputs_size = 10; // Static vsize overhead + outputs vsize. 4 nVersion, 4 nLocktime, 1 input count, 1 witness overhead (dummy, flag, stack size) diff --git a/src/wallet/test/fuzz/coinselection.cpp b/src/wallet/test/fuzz/coinselection.cpp index 3465f2f331..52bd74126d 100644 --- a/src/wallet/test/fuzz/coinselection.cpp +++ b/src/wallet/test/fuzz/coinselection.cpp @@ -58,6 +58,8 @@ FUZZ_TARGET(coinselection) coin_params.m_subtract_fee_outputs = subtract_fee_outputs; coin_params.m_long_term_feerate = long_term_fee_rate; coin_params.m_effective_feerate = effective_fee_rate; + coin_params.change_output_size = fuzzed_data_provider.ConsumeIntegralInRange(10, 1000); + coin_params.m_change_fee = effective_fee_rate.GetFee(coin_params.change_output_size); // Create some coins CAmount total_balance{0}; @@ -85,7 +87,7 @@ FUZZ_TARGET(coinselection) auto result_srd = SelectCoinsSRD(group_pos, target, fast_random_context); if (result_srd) result_srd->ComputeAndSetWaste(cost_of_change); - CAmount change_target{GenerateChangeTarget(target, fast_random_context)}; + CAmount change_target{GenerateChangeTarget(target, coin_params.m_change_fee, fast_random_context)}; auto result_knapsack = KnapsackSolver(group_all, target, change_target, fast_random_context); if (result_knapsack) result_knapsack->ComputeAndSetWaste(cost_of_change);