Skip to content

Commit 0aef6ff

Browse files
committed
Make lowest fee test fail by implementing score correctly
1 parent 0c66696 commit 0aef6ff

File tree

2 files changed

+32
-9
lines changed

2 files changed

+32
-9
lines changed

src/coin_selector.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,14 @@ impl<'a> CoinSelector<'a> {
264264
(self.weight(drain_weight) as f32 * feerate.spwu()).ceil() as u64
265265
}
266266

267+
/// The actual fee the selection would pay if it was used in a transaction that had
268+
/// `target_value` value for outputs and change output of `drain_value`.
269+
///
270+
/// This can be negative when the selection is invalid (outputs are greater than inputs).
271+
pub fn fee(&self, target_value: u64, drain_value: u64) -> i64 {
272+
self.selected_value() as i64 - target_value as i64 - drain_value as i64
273+
}
274+
267275
/// The value of the current selected inputs minus the fee needed to pay for the selected inputs
268276
pub fn effective_value(&self, feerate: FeeRate) -> i64 {
269277
self.selected_value() as i64 - (self.input_weight() as f32 * feerate.spwu()).ceil() as i64
@@ -656,6 +664,11 @@ impl DrainWeights {
656664
+ self.spend_weight as f32 * long_term_feerate.spwu()
657665
}
658666

667+
/// The the fee you will pay to spend this otuput in the future.
668+
pub fn spend_fee(&self, long_term_feerate: FeeRate) -> u64 {
669+
(self.spend_weight as f32 * long_term_feerate.spwu()).ceil() as u64
670+
}
671+
659672
/// Create [`DrainWeights`] that represents a drain output with a taproot keyspend.
660673
pub fn new_tr_keyspend() -> Self {
661674
Self {

src/metrics/lowest_fee.rs

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,15 @@ use crate::{
66
/// Metric that aims to minimize transaction fees. The future fee for spending the change output is
77
/// included in this calculation.
88
///
9-
/// The scoring function for changeless solutions is:
10-
/// > input_weight * feerate + excess
9+
/// The fee is simply:
1110
///
12-
/// The scoring function for solutions with change:
13-
/// > (input_weight + change_output_weight) * feerate + change_spend_weight * long_term_feerate
11+
/// > `inputs - outputs` where `outputs = target.value + change_value`
12+
///
13+
/// But the total value includes the cost of spending the change output if it exists:
14+
///
15+
/// > `change_spend_weight * long_term_feerate`
16+
///
17+
/// The `change_spend_weight` and `change_value` are determined by the `change_policy`
1418
#[derive(Clone, Copy)]
1519
pub struct LowestFee {
1620
/// The target parameters for the resultant selection.
@@ -55,13 +59,19 @@ impl BnbMetric for LowestFee {
5559
return None;
5660
}
5761

58-
let drain_weights = if drain.is_some() {
59-
Some(drain.weights)
60-
} else {
61-
None
62+
let long_term_fee = {
63+
let fee_for_the_tx = cs.fee(self.target.value, drain.value);
64+
assert!(
65+
fee_for_the_tx > 0,
66+
"must not be called unless selection has met target"
67+
);
68+
// Why `spend_fee` rounds up here. We could use floats but I felt it was just better to
69+
// accept the extra 1 sat penality to having a change output
70+
let fee_for_spending_drain = drain.weights.spend_fee(self.long_term_feerate);
71+
fee_for_the_tx as u64 + fee_for_spending_drain
6272
};
6373

64-
Some(Ordf32(self.calc_metric(cs, drain_weights)))
74+
Some(Ordf32(long_term_fee as f32))
6575
}
6676

6777
fn bound(&mut self, cs: &CoinSelector<'_>) -> Option<Ordf32> {

0 commit comments

Comments
 (0)