Skip to content

Commit 4c8dc9b

Browse files
authored
Merge pull request #30 from jp1ac4/fix-lowestfee-bound
Loosen LowestFee metric bound
2 parents c22b30b + af1333b commit 4c8dc9b

File tree

5 files changed

+58
-6
lines changed

5 files changed

+58
-6
lines changed

src/coin_selector.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,15 @@ impl<'a> CoinSelector<'a> {
200200
- self.implied_fee_from_feerate(target, drain.weights) as i64
201201
}
202202

203+
/// Same as [rate_excess](Self::rate_excess) except `target.fee.rate` is applied to the
204+
/// implied transaction's weight units directly without any conversion to vbytes.
205+
pub fn rate_excess_wu(&self, target: Target, drain: Drain) -> i64 {
206+
self.selected_value() as i64
207+
- target.value() as i64
208+
- drain.value as i64
209+
- self.implied_fee_from_feerate_wu(target, drain.weights) as i64
210+
}
211+
203212
/// How much the current selection overshoots the value needed to satisfy RBF's rule 4.
204213
pub fn replacement_excess(&self, target: Target, drain: Drain) -> i64 {
205214
let mut replacement_excess_needed = 0;
@@ -213,6 +222,20 @@ impl<'a> CoinSelector<'a> {
213222
- replacement_excess_needed as i64
214223
}
215224

225+
/// Same as [replacement_excess](Self::replacement_excess) except the replacement fee
226+
/// is calculated using weight units directly without any conversion to vbytes.
227+
pub fn replacement_excess_wu(&self, target: Target, drain: Drain) -> i64 {
228+
let mut replacement_excess_needed = 0;
229+
if let Some(replace) = target.fee.replace {
230+
replacement_excess_needed =
231+
replace.min_fee_to_do_replacement_wu(self.weight(target.outputs, drain.weights))
232+
}
233+
self.selected_value() as i64
234+
- target.value() as i64
235+
- drain.value as i64
236+
- replacement_excess_needed as i64
237+
}
238+
216239
/// The feerate the transaction would have if we were to use this selection of inputs to achieve
217240
/// the `target`'s value and weight. It is essentially telling you what target feerate you currently have.
218241
///
@@ -253,6 +276,13 @@ impl<'a> CoinSelector<'a> {
253276
.implied_fee(self.weight(target.outputs, drain_weights))
254277
}
255278

279+
fn implied_fee_from_feerate_wu(&self, target: Target, drain_weights: DrainWeights) -> u64 {
280+
target
281+
.fee
282+
.rate
283+
.implied_fee_wu(self.weight(target.outputs, drain_weights))
284+
}
285+
256286
/// The actual fee the selection would pay if it was used in a transaction that had
257287
/// `target_value` value for outputs and change output of `drain_value`.
258288
///

src/feerate.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,11 +79,16 @@ impl FeeRate {
7979
self.0 .0
8080
}
8181

82-
/// The fee that the transaction with weight `tx_weight` should pay in order to satisfy the fee rate given by `self`.
82+
/// The fee that the transaction with weight `tx_weight` should pay in order to satisfy the fee rate given by `self`,
83+
/// where the fee rate is applied to the rounded-up vbytes obtained from `tx_weight`.
8384
pub fn implied_fee(&self, tx_weight: u64) -> u64 {
84-
// The fee rate is applied to the rounded-up vbytes.
8585
((tx_weight as f32 / 4.0).ceil() * self.as_sat_vb()).ceil() as u64
8686
}
87+
88+
/// Same as [implied_fee](Self::implied_fee) except the fee rate given by `self` is applied to `tx_weight` directly.
89+
pub fn implied_fee_wu(&self, tx_weight: u64) -> u64 {
90+
(tx_weight as f32 * self.spwu()).ceil() as u64
91+
}
8792
}
8893

8994
impl Add<FeeRate> for FeeRate {

src/metrics/lowest_fee.rs

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,10 @@ impl BnbMetric for LowestFee {
115115
.select_iter()
116116
.find(|(cs, _, _)| cs.is_target_met(self.target))?;
117117

118+
// If this selection is already perfect, return its score directly.
119+
if cs.excess(self.target, Drain::NONE) == 0 {
120+
return Some(self.score(&cs).unwrap());
121+
};
118122
cs.deselect(resize_index);
119123

120124
// We need to find the minimum fee we'd pay if we satisfy the feerate constraint. We do
@@ -131,7 +135,10 @@ impl BnbMetric for LowestFee {
131135
// scale = remaining_value_to_reach_feerate / effective_value_of_resized_input
132136
//
133137
// This should be intutive since we're finding out how to scale the input we're resizing to get the effective value we need.
134-
let rate_excess = cs.rate_excess(self.target, Drain::NONE) as f32;
138+
//
139+
// In the perfect scenario, no additional fee would be required to pay for rounding up when converting from weight units to
140+
// vbytes and so all fee calculations below are performed on weight units directly.
141+
let rate_excess = cs.rate_excess_wu(self.target, Drain::NONE) as f32;
135142
let mut scale = Ordf32(0.0);
136143

137144
if rate_excess < 0.0 {
@@ -150,7 +157,7 @@ impl BnbMetric for LowestFee {
150157
// We can use the same approach for replacement we just have to use the
151158
// incremental_relay_feerate.
152159
if let Some(replace) = self.target.fee.replace {
153-
let replace_excess = cs.replacement_excess(self.target, Drain::NONE) as f32;
160+
let replace_excess = cs.replacement_excess_wu(self.target, Drain::NONE) as f32;
154161
if replace_excess < 0.0 {
155162
let remaining_value_to_reach_feerate = replace_excess.abs();
156163
let effective_value_of_resized_input =
@@ -164,8 +171,8 @@ impl BnbMetric for LowestFee {
164171
}
165172
}
166173
}
167-
168-
assert!(scale.0 > 0.0);
174+
// `scale` could be 0 even if `is_target_met` is `false` due to the latter being based on
175+
// rounded-up vbytes.
169176
let ideal_fee = scale.0 * to_resize.value as f32 + cs.selected_value() as f32
170177
- self.target.value() as f32;
171178
assert!(ideal_fee >= 0.0);

src/target.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,4 +141,13 @@ impl Replace {
141141
.incremental_relay_feerate
142142
.implied_fee(replacing_tx_weight)
143143
}
144+
145+
/// Same as (min_fee_to_do_replacement)[Self::min_fee_to_do_replacement] except the additional fee
146+
/// is calculated using `replacing_tx_weight` directly without any conversion to vbytes.
147+
pub fn min_fee_to_do_replacement_wu(&self, replacing_tx_weight: u64) -> u64 {
148+
self.fee
149+
+ self
150+
.incremental_relay_feerate
151+
.implied_fee_wu(replacing_tx_weight)
152+
}
144153
}

tests/lowest_fee.proptest-regressions

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@ cc c580ee452624915fc710d5fe724c7a9347472ccd178f66c9db9479cfc6168f48 # shrinks to
1010
cc 850e0115aeeb7ed50235fdb4b5183eb5bf8309a45874dc261e3d3fd2d8c84660 # shrinks to n_candidates = 8, target_value = 444541, base_weight = 253, min_fee = 0, feerate = 55.98181, feerate_lt_diff = 36.874306, drain_weight = 490, drain_spend_weight = 1779, drain_dust = 100
1111
cc ca9831dfe4ad27fc705ae4af201b9b50739404bda0e1e130072858634f8d25d9 # added by llfourn from ci: has a case where the lowest fee metric would benefit by adding change
1212
cc 85d9dc968a690553484d0f166f3d778c3e0ec7d9059809c2818b62d6609853c1
13+
cc b32bb279f62e2300a14c9053cff09f6a953bf1020b878958cb510258ffe65028 # added by jp1ac4 from ci

0 commit comments

Comments
 (0)