Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ChangePolicy should be decided by metrics in branch and bound #20

Open
LLFourn opened this issue Jan 24, 2024 · 0 comments
Open

ChangePolicy should be decided by metrics in branch and bound #20

LLFourn opened this issue Jan 24, 2024 · 0 comments

Comments

@LLFourn
Copy link
Collaborator

LLFourn commented Jan 24, 2024

Right now we pass in a change policy when creating the metric type:

#[derive(Clone, Copy)]
pub struct LowestFee {
    /// The target parameters for the resultant selection.
    pub target: Target,
    /// The estimated feerate needed to spend our change output later.
    pub long_term_feerate: FeeRate,
    /// Policy to determine the change output (if any) of a given selection.
    pub change_policy: ChangePolicy,
}

But what if we pass in a really wacky change_policy that doesn't try and lower your fees? This means a whole lot of complicated logic is needed within the bound function of the metric to try and still get the correct bound with a ChangePolicy that is working against the metric!

This would allow us to remove this entire if/else statement:

if let Some(drain_value) = drain_value {
// it's possible that adding another input might reduce your long term fee if it
// gets rid of an expensive change output. Our strategy is to take the lowest sat
// per value candidate we have and use it as a benchmark. We imagine it has the
// perfect value (but the same sats per weight unit) to get rid of the change output
// by adding negative effective value (i.e. perfectly reducing excess to the point
// where change wouldn't be added according to the policy).
//
// TODO: This metric could be tighter by being more complicated but this seems to be
// good enough for now.
let amount_above_change_threshold = drain_value - self.change_policy.min_value;
if let Some((_, low_sats_per_wu_candidate)) = cs.unselected().next_back() {
let ev = low_sats_per_wu_candidate.effective_value(self.target.fee.rate);
// we can only reduce excess if ev is negative
if ev < -0.0 {
let value_per_negative_effective_value =
low_sats_per_wu_candidate.value as f32 / ev.abs();
// this is how much abosolute value we have to add to cancel out the excess
let extra_value_needed_to_get_rid_of_change = amount_above_change_threshold
as f32
* value_per_negative_effective_value;
// NOTE: the drain_value goes to fees if we get rid of it so it's part of
// the cost of removing the change output
let cost_of_getting_rid_of_change =
extra_value_needed_to_get_rid_of_change + drain_value as f32;
let cost_of_change = self
.change_policy
.drain_weights
.waste(self.target.fee.rate, self.long_term_feerate);
let best_score_without_change = Ordf32(
current_score.0 + cost_of_getting_rid_of_change - cost_of_change,
);
if best_score_without_change < current_score {
return Some(best_score_without_change);
}
}
}
} else {
// Ok but maybe adding change could improve the metric?
let cost_of_adding_change = self
.change_policy
.drain_weights
.waste(self.target.fee.rate, self.long_term_feerate);
let cost_of_no_change = cs.excess(self.target, Drain::none());
let best_score_with_change =
Ordf32(current_score.0 - cost_of_no_change as f32 + cost_of_adding_change);
if best_score_with_change < current_score {
return Some(best_score_with_change);
}
}

Can anyone think of a reason you'd want a change policy that is not inline with the metric you are trying to optimize for?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant