Skip to content

Commit 78fda32

Browse files
evanlinjinclaude
andcommitted
perf: make LowestFee's BnB bound max_weight-aware
The bound was cap-blind: admissible but loose, crediting improvements the weight budget can't actually afford. Tighten it in the cap-binding regime: - Not-funded ("slurp") branch: reaching the feerate needs a perfect input weighing `scale * to_resize.weight`. `to_resize` is the best value-per-weight input available, so if even that can't fit the remaining budget, nothing down this branch reaches the target within the cap -> prune. It's a fractional relaxation, so it never prunes a branch with an integer solution. - Changeless "recover value by adding change" branch: only credit the improvement if a change output fits the cap. Clearing dust needs heavier inputs and the change output adds weight, so if change doesn't fit now it never will -> keep `current_score`. Both stay admissible (`ensure_bound_is_not_too_tight`) and BnB still finds the optimum (`can_eventually_find_best_solution`). No effect unless `max_weight` is set. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent e378e88 commit 78fda32

1 file changed

Lines changed: 24 additions & 1 deletion

File tree

src/metrics/lowest_fee.rs

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,17 @@ impl BnbMetric for LowestFee {
161161

162162
let best_score_with_change =
163163
Ordf32(current_score.0 - cost_of_no_change as f32 + cost_of_adding_change);
164-
if best_score_with_change < current_score {
164+
// max_weight-aware: recovering that value requires a change output (and more
165+
// inputs to clear the dust threshold), which only makes the tx heavier. If a change
166+
// output can't fit the cap now it never will down this branch, so don't credit the
167+
// improvement — keep `current_score` (a tighter, still-admissible bound).
168+
let change_output = Drain {
169+
weights: self.drain_weights,
170+
value: 0,
171+
};
172+
if cs.is_within_max_weight(target, change_output)
173+
&& best_score_with_change < current_score
174+
{
165175
return Some(best_score_with_change);
166176
}
167177
}
@@ -243,6 +253,19 @@ impl BnbMetric for LowestFee {
243253
}
244254
}
245255

256+
// max_weight-aware: reaching the feerate needs a perfect input weighing
257+
// `scale * to_resize.weight`. `to_resize` is the best value-per-weight input available,
258+
// so if even that (fractionally) can't fit the remaining weight budget, no within-cap
259+
// selection down this branch reaches the target -> prune. This is the fractional
260+
// relaxation, so it never prunes a branch that has an (integer) within-cap solution.
261+
if let Some(max_weight) = target.max_weight {
262+
let budget =
263+
max_weight.saturating_sub(cs.weight(target.outputs, DrainWeights::NONE));
264+
if scale.0 * to_resize.weight as f32 > budget as f32 {
265+
return None;
266+
}
267+
}
268+
246269
// `scale` could be 0 even if `is_funded` is `false` due to the latter being based on
247270
// rounded-up vbytes.
248271
let ideal_fee = scale.0 * to_resize.value as f32 + cs.selected_value() as f32

0 commit comments

Comments
 (0)