use super::change_lower_bound;
use crate::{
bnb::BnbMetric, float::Ordf32, Candidate, ChangePolicy, CoinSelector, Drain, FeeRate, Target,
};
#[derive(Clone, Copy, Debug)]
pub struct Waste {
pub target: Target,
pub long_term_feerate: FeeRate,
pub change_policy: ChangePolicy,
}
impl BnbMetric for Waste {
fn score(&mut self, cs: &CoinSelector<'_>) -> Option<Ordf32> {
let drain = cs.drain(self.target, self.change_policy);
if !cs.is_target_met_with_drain(self.target, drain) {
return None;
}
let score = cs.waste(self.target, self.long_term_feerate, drain, 1.0);
Some(Ordf32(score))
}
fn bound(&mut self, cs: &CoinSelector<'_>) -> Option<Ordf32> {
let rate_diff = self.target.feerate.spwu() - self.long_term_feerate.spwu();
let change_lower_bound = change_lower_bound(cs, self.target, self.change_policy);
const IGNORE_EXCESS: f32 = 0.0;
const INCLUDE_EXCESS: f32 = 1.0;
if rate_diff >= 0.0 {
if cs.is_target_met_with_drain(self.target, change_lower_bound) {
let current_change = cs.drain(self.target, self.change_policy);
let mut lower_bound = cs.waste(
self.target,
self.long_term_feerate,
current_change,
INCLUDE_EXCESS,
);
let should_explore_changeless = change_lower_bound.is_none();
if should_explore_changeless {
let selection_with_as_much_negative_ev_as_possible = cs
.clone()
.select_iter()
.rev()
.take_while(|(cs, _, wv)| {
wv.effective_value(self.target.feerate) < 0.0
&& cs.is_target_met(self.target)
})
.last();
if let Some((cs, _, _)) = selection_with_as_much_negative_ev_as_possible {
let can_do_better_by_slurping =
cs.unselected().next_back().and_then(|(_, wv)| {
if wv.effective_value(self.target.feerate) < 0.0 {
Some(wv)
} else {
None
}
});
let lower_bound_without_change = match can_do_better_by_slurping {
Some(finishing_input) => {
let value_to_slurp = -cs.rate_excess(self.target, Drain::none());
let weight_to_extinguish_excess =
slurp_wv(finishing_input, value_to_slurp, self.target.feerate);
let waste_to_extinguish_excess =
weight_to_extinguish_excess * rate_diff;
cs.waste(
self.target,
self.long_term_feerate,
Drain::none(),
IGNORE_EXCESS,
) + waste_to_extinguish_excess
}
None => cs.waste(
self.target,
self.long_term_feerate,
Drain::none(),
INCLUDE_EXCESS,
),
};
lower_bound = lower_bound.min(lower_bound_without_change);
}
}
Some(Ordf32(lower_bound))
} else {
let (mut cs, slurp_index, to_slurp) =
cs.clone().select_iter().find(|(cs, _, _)| {
cs.is_target_met_with_drain(self.target, change_lower_bound)
})?;
cs.deselect(slurp_index);
let ideal_next_weight = {
let remaining_rate = cs.rate_excess(self.target, change_lower_bound);
let remaining_abs = cs.absolute_excess(self.target, change_lower_bound);
let weight_to_satisfy_abs = remaining_abs.min(0) as f32 / to_slurp.value_pwu();
let weight_to_satisfy_rate =
slurp_wv(to_slurp, remaining_rate.min(0), self.target.feerate);
let weight_to_satisfy = weight_to_satisfy_abs.max(weight_to_satisfy_rate);
debug_assert!(weight_to_satisfy <= to_slurp.weight as f32);
weight_to_satisfy
};
let weight_lower_bound = cs.input_weight() as f32 + ideal_next_weight;
let mut waste = weight_lower_bound * rate_diff;
waste += change_lower_bound.waste(self.target.feerate, self.long_term_feerate);
Some(Ordf32(waste))
}
} else {
let mut lower_bound = {
let mut cs = cs.clone();
cs.select_all_effective(self.target.feerate);
if !cs.is_target_met(self.target) {
return None;
}
let change_at_value_optimum = cs.drain(self.target, self.change_policy);
cs.select_all();
cs.waste(
self.target,
self.long_term_feerate,
change_at_value_optimum,
IGNORE_EXCESS,
)
};
let look_for_changeless_solution = change_lower_bound.is_none();
if look_for_changeless_solution {
let highest_weight_selection_without_change = cs
.clone()
.select_iter()
.rev()
.take_while(|(cs, _, wv)| {
wv.effective_value(self.target.feerate) < 0.0
|| cs.drain_value(self.target, self.change_policy).is_none()
})
.last();
if let Some((cs, _, _)) = highest_weight_selection_without_change {
let no_change_waste = cs.waste(
self.target,
self.long_term_feerate,
Drain::none(),
IGNORE_EXCESS,
);
lower_bound = lower_bound.min(no_change_waste)
}
}
Some(Ordf32(lower_bound))
}
}
fn requires_ordering_by_descending_value_pwu(&self) -> bool {
true
}
}
fn slurp_wv(candidate: Candidate, value_to_slurp: i64, feerate: FeeRate) -> f32 {
let value_per_wu = (candidate.value as f32 / candidate.weight as f32) - feerate.spwu();
let weight_needed = value_to_slurp as f32 / value_per_wu;
debug_assert!(weight_needed <= candidate.weight as f32);
weight_needed.min(0.0)
}