wallet: ensure m_min_change_target always covers change fee

This commit is contained in:
S3RK 2022-08-15 09:30:31 +02:00
parent dc9d662683
commit c8cf08ea74
4 changed files with 11 additions and 6 deletions

View File

@ -389,14 +389,14 @@ CAmount GetSelectionWaste(const std::set<COutput>& 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;
}
}

View File

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

View File

@ -794,7 +794,6 @@ static util::Result<CreatedTransactionResult> 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<CreatedTransactionResult> 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)

View File

@ -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<int>(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);