use crate::{
change_policy::ChangePolicy, float::Ordf32, BnbMetric, Candidate, CoinSelector, Drain, FeeRate,
Target,
};
#[derive(Clone, Copy)]
pub struct LowestFee {
pub target: Target,
pub long_term_feerate: FeeRate,
pub change_policy: ChangePolicy,
}
impl BnbMetric for LowestFee {
fn score(&mut self, cs: &CoinSelector<'_>) -> Option<Ordf32> {
if !cs.is_target_met(self.target) {
return None;
}
let long_term_fee = {
let drain = cs.drain(self.target, self.change_policy);
let fee_for_the_tx = cs.fee(self.target.value, drain.value);
assert!(
fee_for_the_tx > 0,
"must not be called unless selection has met target"
);
let fee_for_spending_drain = drain.weights.spend_fee(self.long_term_feerate);
fee_for_the_tx as u64 + fee_for_spending_drain
};
Some(Ordf32(long_term_fee as f32))
}
fn bound(&mut self, cs: &CoinSelector<'_>) -> Option<Ordf32> {
if cs.is_target_met(self.target) {
let current_score = self.score(cs).unwrap();
let drain_value = cs.drain_value(self.target, self.change_policy);
if let Some(drain_value) = drain_value {
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.feerate);
if ev < -0.0 {
let value_per_negative_effective_value =
low_sats_per_wu_candidate.value as f32 / ev.abs();
let extra_value_needed_to_get_rid_of_change = amount_above_change_threshold
as f32
* value_per_negative_effective_value;
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.feerate, 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);
}
}
}
}
Some(current_score)
} else {
let (mut cs, slurp_index, to_slurp) = cs
.clone()
.select_iter()
.find(|(cs, _, _)| cs.is_target_met(self.target))?;
cs.deselect(slurp_index);
let ideal_next_weight = {
let remaining_rate = cs.rate_excess(self.target, Drain::none());
slurp_wv(to_slurp, remaining_rate.min(0), self.target.feerate)
};
let input_weight_lower_bound = cs.input_weight() as f32 + ideal_next_weight;
let ideal_fee_by_feerate =
(cs.base_weight() as f32 + input_weight_lower_bound) * self.target.feerate.spwu();
let ideal_fee = ideal_fee_by_feerate.max(self.target.min_fee as f32);
Some(Ordf32(ideal_fee))
}
}
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)
}