Improve Coin Selection API
Right now our only coin selection is try_preserving_privacy, which is supposed to attempt to avoid UIH and if not use the first output. In practice, the select_first_candidate only runs on sweeps, since avoid_uihthrows an error if there are 2 outputs in the original psbt.
In practice, sometimes you want to select the first candidate no matter once since you have a discounted opportunity to conolidate a receiver input.
@SatoshiPortal suggested a possible "consolidation" selection algorithm that tried to optimize for consolidation that could be enabled when fees were below a certain threshold. We can support something like this.
I think this is something @0xBEEFCAF3 is interested in
I'm interested in taking this on. I want to ensure I fully understand the requirements. It seems we want to support a selection strategy mechanism with two distinct approaches:
- try_preserving_privacy: Focuses on maintaining privacy by avoiding heuristics like UIH (this is our current implementation).
- try_consolidate: Optimized for consolidating receiver inputs, which would only be activated when fee_rates are below a certain threshold.
I have a couple of questions:
- Why would a sender agree to receiver consolidation if it might compromise their own privacy?
- When consolidating, is the goal to merge multiple UTXOs from the receiver, or just a single one?
I will be working here: https://github.com/payjoin/rust-payjoin/pull/557
Why would a sender agree to receiver consolidation if it might compromise their own privacy?
Consolidation is necessary to spend amounts greater than individual TXO values. If it can be done while the sender pays for some overhead + additional input, payjoin is an opportunity to do it less expensively than if done outside of a payjoin, and privacy wouldn't really be further compromised from the perspective of a third party. Even the second party sender could depend on CIH to see a consolidation is a consolidation outside of a payjoin.
When consolidating, is the goal to merge multiple UTXOs from the receiver, or just a single one?
"Consolidation" here means multiple UTXOs from the receiver since the canonical payjoin behavior is a consolidation of just one input with the incoming payment, and this issue is about something more.
Regarding the privacy issue, I was initially confused, but I now have a clearer understanding of it.
Concerning the incentive for the sender to accept receiver consolidation, I now see that the receiver could cover the weight difference by subtracting that amount from his change.
On the topic of consolidation, makes sense.
Now, my doubts focus on how to implement the consolidation effectively. Here are my current thoughts:
-
Expose a Strategy Selection Function:
We could provide a function that selects the input selection strategy and passes additional parameters as needed. For example:fn select_inputs(candidate_inputs, strategy, /* additional params */) -
We should also have the consolidation function with a set of configurable parameters. For example something like this:
fn try_consolidate(
candidate_inputs,
dust_limit = DEFAULT_DUST_LIMIT,
max_inputs = DEFAULT_MAX_INPUTS,
target_value = null,
fee_rate_threshold = null
):
# Step 1: Check if the current fee rate is acceptable.
estimated_fee_rate = estimate_fee_rate(...) # estimates fee_rate from the psbt fees and weight
return null if fee_rate_threshold is not null and estimated_fee_rate > fee_rate_threshold:
# Step 2: Filter out UTXOs below the dust limit.
valid_inputs = []
for input in candidate_inputs:
if input.value >= dust_limit:
valid_inputs.append(input)
# Step 3: Sort the valid inputs by ascending value.
valid_inputs.sort_by(input.value)
# Step 4: Select inputs until reaching max_inputs or target_value.
selected_inputs = []
total_value = 0
for input in valid_inputs:
if length(selected_inputs) >= max_inputs:
break
selected_inputs.append(input)
total_value = total_value + input.value
if target_value is not null and total_value >= target_value:
break
return selected_inputs
How customizable this function should be? While providing numerous parameters could offer flexibility, it might also lead to unnecessary complexity. We need to strike the right balance between configurability and simplicity.