Skip to content

Commit 9e038df

Browse files
evanlinjinclaude
andcommitted
refactor(metrics): extract shared resize_bound helper
`LowestFee::bound` and `ChangelessWaste::bound` both used the same "resize trick" to lower-bound a monotone quantity of any target-meeting descendant: walk the value_pwu-sorted unselected list until the target is crossed, then fractionally resize the crossing input to hit the target with zero excess. Extract it into a private `resize_bound` helper in `metrics.rs`, returning a `ResizeBound` enum so each caller keeps its own exact-match computation (`LowestFee` returns `score`, `ChangelessWaste` returns `waste`) while sharing the find + deselect + scale mechanism. No behavior change; all tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 9dd8f8f commit 9e038df

3 files changed

Lines changed: 116 additions & 131 deletions

File tree

src/metrics.rs

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,94 @@ mod changeless;
99
pub use changeless::*;
1010
mod changeless_waste;
1111
pub use changeless_waste::*;
12+
13+
use crate::{Candidate, CoinSelector, Drain, Target};
14+
15+
/// Outcome of the "resize trick" (see [`resize_bound`]).
16+
enum ResizeBound<'a> {
17+
/// The crossing selection hit the target with exactly zero excess, so it is itself the
18+
/// minimum-cost target-meeting descendant. Holds that selection.
19+
Exact(CoinSelector<'a>),
20+
/// The crossing input must be fractionally resized. Holds the selection with the crossing input
21+
/// deselected, the crossing candidate, and the `scale ∈ [0, 1]` of it that satisfies every fee
22+
/// constraint with exactly zero excess.
23+
Resize(CoinSelector<'a>, Candidate, f32),
24+
}
25+
26+
/// Shared "resize trick" used by bounds that need a tight lower bound on some monotone quantity
27+
/// (fee, `input_weight`, …) of any target-meeting descendant `D ⊇ cs`, for the case where `cs`
28+
/// does not yet meet the target.
29+
///
30+
/// Walk the `value_pwu`-sorted unselected list until the target is first crossed, then represent
31+
/// the crossing candidate as a fractional `scale ∈ [0, 1]` that satisfies each fee constraint
32+
/// (rate, replacement, absolute) with exactly zero excess. Among all subsets of unselected that
33+
/// reach the target, the highest-`value_pwu` candidates are the most efficient, so the
34+
/// resize-scaled prefix is a valid lower bound on any target-meeting descendant.
35+
///
36+
/// Callers compute their own metric value from the outcome, e.g.
37+
/// ```ignore
38+
/// let ideal_fee = scale * to_resize.value as f32 + cs.selected_value() as f32 - target.value() as f32;
39+
/// let ideal_iw = cs.input_weight() as f32 + scale * to_resize.weight as f32;
40+
/// ```
41+
///
42+
/// Returns `None` if no target-meeting descendant exists (a fee constraint cannot be satisfied by
43+
/// any available candidate).
44+
fn resize_bound<'a>(cs: &CoinSelector<'a>, target: Target) -> Option<ResizeBound<'a>> {
45+
// Step 1: select everything up until the input that first hits the target.
46+
let (mut cs, resize_index, to_resize) = cs
47+
.clone()
48+
.select_iter()
49+
.find(|(cs, _, _)| cs.is_target_met(target))?;
50+
51+
// If this selection is already perfect, it is the minimum-cost target-meeting descendant.
52+
if cs.excess(target, Drain::NONE) == 0 {
53+
return Some(ResizeBound::Exact(cs));
54+
}
55+
cs.deselect(resize_index);
56+
57+
// Find the smallest `scale` of `to_resize` that satisfies every fee constraint. We imagine a
58+
// perfect input that hits the target with zero excess: for a feerate constraint,
59+
//
60+
// scale = remaining_value_to_reach_feerate / effective_value_of_resized_input
61+
//
62+
// In this perfect scenario no extra fee is needed for weight-unit-to-vbyte rounding, so all
63+
// computations are on weight units directly.
64+
let mut scale = 0.0_f32;
65+
66+
let rate_excess = cs.rate_excess_wu(target, Drain::NONE) as f32;
67+
if rate_excess < 0.0 {
68+
let remaining = rate_excess.abs();
69+
let ev_resized = to_resize.effective_value(target.fee.rate);
70+
if ev_resized > 0.0 {
71+
scale = scale.max(remaining / ev_resized);
72+
} else {
73+
return None; // we can never satisfy the constraint
74+
}
75+
}
76+
// Replacement uses the same approach with the incremental relay feerate.
77+
if let Some(replace) = target.fee.replace {
78+
let replace_excess = cs.replacement_excess_wu(target, Drain::NONE) as f32;
79+
if replace_excess < 0.0 {
80+
let remaining = replace_excess.abs();
81+
let ev_resized = to_resize.effective_value(replace.incremental_relay_feerate);
82+
if ev_resized > 0.0 {
83+
scale = scale.max(remaining / ev_resized);
84+
} else {
85+
return None; // we can never satisfy the constraint
86+
}
87+
}
88+
}
89+
// The absolute fee is a fixed amount (not weight-proportional), so we just need enough raw
90+
// value to cover the gap.
91+
let absolute_excess = cs.absolute_excess(target, Drain::NONE) as f32;
92+
if absolute_excess < 0.0 {
93+
let remaining = absolute_excess.abs();
94+
if to_resize.value > 0 {
95+
scale = scale.max(remaining / to_resize.value as f32);
96+
} else {
97+
return None; // we can never satisfy the constraint
98+
}
99+
}
100+
101+
Some(ResizeBound::Resize(cs, to_resize, scale))
102+
}

src/metrics/changeless_waste.rs

Lines changed: 12 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -240,63 +240,24 @@ impl BnbMetric for ChangelessWaste {
240240
return Some(Ordf32(cs.input_weight() as f32 * rate_diff));
241241
}
242242

243-
// Target not met. Same resize trick as `LowestFee::bound`: walk the sorted unselected
244-
// list until we cross the target, then pretend the crossing input was perfectly
245-
// scaled so the target is hit with zero excess. Among all subsets of unselected that
246-
// reach target, the highest-`value_pwu` candidates are the most weight-efficient — so
247-
// the resize-scaled prefix is a valid lower bound on any target-meeting descendant's
248-
// input_weight.
249-
let (mut cs, resize_index, to_resize) = cs
250-
.clone()
251-
.select_iter()
252-
.find(|(cs, _, _)| cs.is_target_met(target))?;
253-
254-
if cs.excess(target, Drain::NONE) == 0 {
255-
return Some(Ordf32(cs.waste(
243+
// Target not met. Same resize trick as `LowestFee::bound`: walk the sorted unselected list
244+
// until we cross the target, then pretend the crossing input was perfectly scaled so the
245+
// target is hit with zero excess. The resize-scaled prefix is a valid lower bound on any
246+
// target-meeting descendant's `input_weight` (see `resize_bound`).
247+
match super::resize_bound(cs, target)? {
248+
// The crossing selection is already the minimum-weight target-meeting subset; its bound
249+
// is its own waste (with `Drain::NONE`).
250+
super::ResizeBound::Exact(cs) => Some(Ordf32(cs.waste(
256251
target,
257252
self.long_term_feerate,
258253
Drain::NONE,
259254
1.0,
260-
)));
261-
}
262-
cs.deselect(resize_index);
263-
264-
let mut scale = Ordf32(0.0);
265-
266-
let rate_excess = cs.rate_excess_wu(target, Drain::NONE) as f32;
267-
if rate_excess < 0.0 {
268-
let remaining = rate_excess.abs();
269-
let ev_resized = to_resize.effective_value(target.fee.rate);
270-
if ev_resized > 0.0 {
271-
scale = scale.max(Ordf32(remaining / ev_resized));
272-
} else {
273-
return None;
255+
))),
256+
super::ResizeBound::Resize(cs, to_resize, scale) => {
257+
let ideal_input_weight = cs.input_weight() as f32 + scale * to_resize.weight as f32;
258+
Some(Ordf32(ideal_input_weight * rate_diff))
274259
}
275260
}
276-
if let Some(replace) = target.fee.replace {
277-
let replace_excess = cs.replacement_excess_wu(target, Drain::NONE) as f32;
278-
if replace_excess < 0.0 {
279-
let remaining = replace_excess.abs();
280-
let ev_resized = to_resize.effective_value(replace.incremental_relay_feerate);
281-
if ev_resized > 0.0 {
282-
scale = scale.max(Ordf32(remaining / ev_resized));
283-
} else {
284-
return None;
285-
}
286-
}
287-
}
288-
let absolute_excess = cs.absolute_excess(target, Drain::NONE) as f32;
289-
if absolute_excess < 0.0 {
290-
let remaining = absolute_excess.abs();
291-
if to_resize.value > 0 {
292-
scale = scale.max(Ordf32(remaining / to_resize.value as f32));
293-
} else {
294-
return None;
295-
}
296-
}
297-
298-
let ideal_input_weight = cs.input_weight() as f32 + scale.0 * to_resize.weight as f32;
299-
Some(Ordf32(ideal_input_weight * rate_diff))
300261
}
301262

302263
fn requires_ordering_by_descending_value_pwu(&self) -> bool {

src/metrics/lowest_fee.rs

Lines changed: 13 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -132,88 +132,21 @@ impl BnbMetric for LowestFee {
132132

133133
Some(current_score)
134134
} else {
135-
// Step 1: select everything up until the input that hits the target.
136-
let (mut cs, resize_index, to_resize) = cs
137-
.clone()
138-
.select_iter()
139-
.find(|(cs, _, _)| cs.is_target_met(target))?;
140-
141-
// If this selection is already perfect, return its score directly.
142-
if cs.excess(target, Drain::NONE) == 0 {
143-
return Some(self.score(&cs, target).unwrap());
144-
};
145-
cs.deselect(resize_index);
146-
147-
// We need to find the minimum fee we'd pay if we satisfy the feerate constraint. We do
148-
// this by imagining we had a perfect input that perfectly hit the target. The sats per
149-
// weight unit of this perfect input is the one at `slurp_index` but we'll do a scaled
150-
// resize of it to fit perfectly.
151-
//
152-
// Here's the formaula:
153-
//
154-
// target_feerate = (current_input_value - current_output_value + scale * value_resized_input) / (current_weight + scale * weight_resized_input)
155-
//
156-
// Rearranging to find `scale` we find that:
157-
//
158-
// scale = remaining_value_to_reach_feerate / effective_value_of_resized_input
159-
//
160-
// This should be intutive since we're finding out how to scale the input we're resizing to get the effective value we need.
161-
//
162-
// In the perfect scenario, no additional fee would be required to pay for rounding up when converting from weight units to
163-
// vbytes and so all fee calculations below are performed on weight units directly.
164-
let rate_excess = cs.rate_excess_wu(target, Drain::NONE) as f32;
165-
let mut scale = Ordf32(0.0);
166-
167-
if rate_excess < 0.0 {
168-
let remaining_value_to_reach_feerate = rate_excess.abs();
169-
let effective_value_of_resized_input = to_resize.effective_value(target.fee.rate);
170-
if effective_value_of_resized_input > 0.0 {
171-
let feerate_scale =
172-
remaining_value_to_reach_feerate / effective_value_of_resized_input;
173-
scale = scale.max(Ordf32(feerate_scale));
174-
} else {
175-
return None; // we can never satisfy the constraint
176-
}
177-
}
178-
179-
// We can use the same approach for replacement we just have to use the
180-
// incremental_relay_feerate.
181-
if let Some(replace) = target.fee.replace {
182-
let replace_excess = cs.replacement_excess_wu(target, Drain::NONE) as f32;
183-
if replace_excess < 0.0 {
184-
let remaining_value_to_reach_feerate = replace_excess.abs();
185-
let effective_value_of_resized_input =
186-
to_resize.effective_value(replace.incremental_relay_feerate);
187-
if effective_value_of_resized_input > 0.0 {
188-
let replace_scale =
189-
remaining_value_to_reach_feerate / effective_value_of_resized_input;
190-
scale = scale.max(Ordf32(replace_scale));
191-
} else {
192-
return None; // we can never satisfy the constraint
193-
}
135+
// Walk the sorted unselected list until we cross the target, resizing the crossing
136+
// input to hit the target with zero excess (see `resize_bound`).
137+
match super::resize_bound(cs, target)? {
138+
// The crossing selection is already perfect: return its score directly.
139+
super::ResizeBound::Exact(cs) => Some(self.score(&cs, target).unwrap()),
140+
super::ResizeBound::Resize(cs, to_resize, scale) => {
141+
// `scale` could be 0 even if `is_target_met` is `false` due to the latter being
142+
// based on rounded-up vbytes.
143+
let ideal_fee = scale * to_resize.value as f32 + cs.selected_value() as f32
144+
- target.value() as f32;
145+
assert!(ideal_fee >= 0.0);
146+
147+
Some(Ordf32(ideal_fee))
194148
}
195149
}
196-
// Handle absolute fee constraint. Unlike feerate and replacement, the
197-
// absolute fee is a fixed amount (not weight-proportional), so we just
198-
// need enough raw value to cover the gap.
199-
let absolute_excess = cs.absolute_excess(target, Drain::NONE) as f32;
200-
if absolute_excess < 0.0 {
201-
let remaining = absolute_excess.abs();
202-
if to_resize.value > 0 {
203-
let absolute_scale = remaining / to_resize.value as f32;
204-
scale = scale.max(Ordf32(absolute_scale));
205-
} else {
206-
return None; // we can never satisfy the constraint
207-
}
208-
}
209-
210-
// `scale` could be 0 even if `is_target_met` is `false` due to the latter being based on
211-
// rounded-up vbytes.
212-
let ideal_fee = scale.0 * to_resize.value as f32 + cs.selected_value() as f32
213-
- target.value() as f32;
214-
assert!(ideal_fee >= 0.0);
215-
216-
Some(Ordf32(ideal_fee))
217150
}
218151
}
219152

0 commit comments

Comments
 (0)