1
1
use crate :: {
2
- change_policy:: ChangePolicy , float:: Ordf32 , metrics :: change_lower_bound , BnbMetric , Candidate ,
3
- CoinSelector , Drain , DrainWeights , FeeRate , Target ,
2
+ change_policy:: ChangePolicy , float:: Ordf32 , BnbMetric , Candidate , CoinSelector , Drain , FeeRate ,
3
+ Target ,
4
4
} ;
5
5
6
6
/// Metric that aims to minimize transaction fees. The future fee for spending the change output is
@@ -25,41 +25,14 @@ pub struct LowestFee {
25
25
pub change_policy : ChangePolicy ,
26
26
}
27
27
28
- impl LowestFee {
29
- fn calc_metric ( & self , cs : & CoinSelector < ' _ > , drain_weights : Option < DrainWeights > ) -> f32 {
30
- self . calc_metric_lb ( cs, drain_weights)
31
- + match drain_weights {
32
- Some ( _) => {
33
- let selected_value = cs. selected_value ( ) ;
34
- assert ! ( selected_value >= self . target. value) ;
35
- ( cs. selected_value ( ) - self . target . value ) as f32
36
- }
37
- None => 0.0 ,
38
- }
39
- }
40
-
41
- fn calc_metric_lb ( & self , cs : & CoinSelector < ' _ > , drain_weights : Option < DrainWeights > ) -> f32 {
42
- match drain_weights {
43
- // with change
44
- Some ( drain_weights) => {
45
- ( cs. input_weight ( ) + drain_weights. output_weight ) as f32
46
- * self . target . feerate . spwu ( )
47
- + drain_weights. spend_weight as f32 * self . long_term_feerate . spwu ( )
48
- }
49
- // changeless
50
- None => cs. input_weight ( ) as f32 * self . target . feerate . spwu ( ) ,
51
- }
52
- }
53
- }
54
-
55
28
impl BnbMetric for LowestFee {
56
29
fn score ( & mut self , cs : & CoinSelector < ' _ > ) -> Option < Ordf32 > {
57
- let drain = cs. drain ( self . target , self . change_policy ) ;
58
- if !cs. is_target_met_with_drain ( self . target , drain) {
30
+ if !cs. is_target_met ( self . target ) {
59
31
return None ;
60
32
}
61
33
62
34
let long_term_fee = {
35
+ let drain = cs. drain ( self . target , self . change_policy ) ;
63
36
let fee_for_the_tx = cs. fee ( self . target . value , drain. value ) ;
64
37
assert ! (
65
38
fee_for_the_tx > 0 ,
@@ -75,98 +48,89 @@ impl BnbMetric for LowestFee {
75
48
}
76
49
77
50
fn bound ( & mut self , cs : & CoinSelector < ' _ > ) -> Option < Ordf32 > {
78
- // this either returns:
79
- // * None: change output may or may not exist
80
- // * Some: change output must exist from this branch onwards
81
- let change_lb = change_lower_bound ( cs, self . target , self . change_policy ) ;
82
- let change_lb_weights = if change_lb. is_some ( ) {
83
- Some ( change_lb. weights )
84
- } else {
85
- None
86
- } ;
87
- // println!("\tchange lb: {:?}", change_lb_weights);
88
-
89
- if cs. is_target_met_with_drain ( self . target , change_lb) {
90
- // Target is met, is it possible to add further inputs to remove drain output?
91
- // If we do, can we get a better score?
92
-
93
- // First lower bound candidate is just the selection itself (include excess).
94
- let mut lower_bound = self . calc_metric ( cs, change_lb_weights) ;
95
-
96
- if change_lb_weights. is_none ( ) {
97
- // Since a changeless solution may exist, we should try minimize the excess with by
98
- // adding as much -ev candidates as possible
99
- let selection_with_as_much_negative_ev_as_possible = cs
100
- . clone ( )
101
- . select_iter ( )
102
- . rev ( )
103
- . take_while ( |( cs, _, candidate) | {
104
- candidate. effective_value ( self . target . feerate ) . 0 < 0.0
105
- && cs. is_target_met_with_drain ( self . target , Drain :: none ( ) )
106
- } )
107
- . last ( )
108
- . map ( |( cs, _, _) | cs) ;
109
-
110
- if let Some ( cs) = selection_with_as_much_negative_ev_as_possible {
111
- // we have selected as much "real" inputs as possible, is it possible to select
112
- // one more with the perfect weight?
113
- let can_do_better_by_slurping =
114
- cs. unselected ( ) . next_back ( ) . and_then ( |( _, candidate) | {
115
- if candidate. effective_value ( self . target . feerate ) . 0 < 0.0 {
116
- Some ( candidate)
117
- } else {
118
- None
119
- }
120
- } ) ;
121
- let lower_bound_changeless = match can_do_better_by_slurping {
122
- Some ( finishing_input) => {
123
- let excess = cs. rate_excess ( self . target , Drain :: none ( ) ) ;
124
-
125
- // change the input's weight to make it's effective value match the excess
126
- let perfect_input_weight = slurp ( self . target , excess, finishing_input) ;
127
-
128
- ( cs. input_weight ( ) as f32 + perfect_input_weight)
129
- * self . target . feerate . spwu ( )
51
+ if cs. is_target_met ( self . target ) {
52
+ let current_score = self . score ( cs) . unwrap ( ) ;
53
+
54
+ let drain_value = cs. drain_value ( self . target , self . change_policy ) ;
55
+
56
+ if let Some ( drain_value) = drain_value {
57
+ // it's possible that adding another input might reduce your long term if it gets
58
+ // rid of an expensive change output. Our strategy is to take the lowest sat per
59
+ // value candidate we have and use it as a benchmark. We imagine it has the perfect
60
+ // value (but the same sats per weight unit) to get rid of the change output by
61
+ // adding negative effective value (i.e. perfectly reducing excess to the point
62
+ // where change wouldn't be added according to the policy).
63
+ let amount_above_change_threshold = drain_value - self . change_policy . min_value ;
64
+
65
+ if let Some ( ( _, low_sats_per_wu_candidate) ) = cs. unselected ( ) . next_back ( ) {
66
+ let ev = low_sats_per_wu_candidate. effective_value ( self . target . feerate ) ;
67
+ if ev < 0.0 {
68
+ // we can only reduce excess if ev is negative
69
+ let value_per_negative_effective_value =
70
+ low_sats_per_wu_candidate. value_pwu ( ) / ev. abs ( ) ;
71
+ let extra_value_needed_to_get_rid_of_change = amount_above_change_threshold
72
+ as f32
73
+ * value_per_negative_effective_value;
74
+ let cost_of_getting_rid_of_change =
75
+ extra_value_needed_to_get_rid_of_change + drain_value as f32 ;
76
+ let cost_of_change = self
77
+ . change_policy
78
+ . drain_weights
79
+ . waste ( self . target . feerate , self . long_term_feerate ) ;
80
+ let best_score_without_change = Ordf32 (
81
+ current_score. 0 - cost_of_change + cost_of_getting_rid_of_change,
82
+ ) ;
83
+ if best_score_without_change < current_score {
84
+ return Some ( best_score_without_change) ;
130
85
}
131
- None => self . calc_metric ( & cs, None ) ,
132
- } ;
133
-
134
- lower_bound = lower_bound. min ( lower_bound_changeless)
86
+ }
135
87
}
136
88
}
137
89
138
- return Some ( Ordf32 ( lower_bound) ) ;
90
+ Some ( current_score)
91
+ } else {
92
+ // Step 1: select everything up until the input that hits the target.
93
+ let ( mut cs, slurp_index, to_slurp) = cs
94
+ . clone ( )
95
+ . select_iter ( )
96
+ . find ( |( cs, _, _) | cs. is_target_met ( self . target ) ) ?;
97
+
98
+ cs. deselect ( slurp_index) ;
99
+
100
+ // Step 2: We pretend that the final input exactly cancels out the remaining excess
101
+ // by taking whatever value we want from it but at the value per weight of the real
102
+ // input.
103
+ let ideal_next_weight = {
104
+ let remaining_rate = cs. rate_excess ( self . target , Drain :: none ( ) ) ;
105
+
106
+ slurp_wv ( to_slurp, remaining_rate. min ( 0 ) , self . target . feerate )
107
+ } ;
108
+ let input_weight_lower_bound = cs. input_weight ( ) as f32 + ideal_next_weight;
109
+ let ideal_fee_by_feerate =
110
+ ( cs. base_weight ( ) as f32 + input_weight_lower_bound) * self . target . feerate . spwu ( ) ;
111
+ let ideal_fee = ideal_fee_by_feerate. max ( self . target . min_fee as f32 ) ;
112
+
113
+ Some ( Ordf32 ( ideal_fee) )
139
114
}
140
-
141
- // target is not met yet
142
- // select until we just exceed target, then we slurp the last selection
143
- let ( mut cs, slurp_index, candidate_to_slurp) = cs
144
- . clone ( )
145
- . select_iter ( )
146
- . find ( |( cs, _, _) | cs. is_target_met_with_drain ( self . target , change_lb) ) ?;
147
- cs. deselect ( slurp_index) ;
148
-
149
- let mut lower_bound = self . calc_metric_lb ( & cs, change_lb_weights) ;
150
-
151
- // find the max excess we need to rid of
152
- let perfect_excess = i64:: max (
153
- cs. rate_excess ( self . target , Drain :: none ( ) ) ,
154
- cs. absolute_excess ( self . target , Drain :: none ( ) ) ,
155
- ) ;
156
- // use the highest excess to find "perfect candidate weight"
157
- let perfect_input_weight = slurp ( self . target , perfect_excess, candidate_to_slurp) ;
158
- lower_bound += perfect_input_weight * self . target . feerate . spwu ( ) ;
159
-
160
- Some ( Ordf32 ( lower_bound) )
161
115
}
162
116
163
117
fn requires_ordering_by_descending_value_pwu ( & self ) -> bool {
164
118
true
165
119
}
166
120
}
167
121
168
- fn slurp ( target : Target , excess : i64 , candidate : Candidate ) -> f32 {
169
- let vpw = candidate. value_pwu ( ) . 0 ;
170
- let perfect_weight = -excess as f32 / ( vpw - target. feerate . spwu ( ) ) ;
171
- perfect_weight. max ( 0.0 )
122
+ /// Returns the "perfect weight" for this candidate to slurp up a given value with `feerate` while
123
+ /// not changing the candidate's value/weight ratio.
124
+ ///
125
+ /// Used to pretend that a candidate had precisely `value_to_slurp` + fee needed to include it. It
126
+ /// tells you how much weight such a perfect candidate would have if it had the same value per
127
+ /// weight unit as `candidate`. This is useful for estimating a lower weight bound for a perfect
128
+ /// match.
129
+ fn slurp_wv ( candidate : Candidate , value_to_slurp : i64 , feerate : FeeRate ) -> f32 {
130
+ // the value per weight unit this candidate offers at feerate
131
+ let value_per_wu = ( candidate. value as f32 / candidate. weight as f32 ) - feerate. spwu ( ) ;
132
+ // return how much weight we need
133
+ let weight_needed = value_to_slurp as f32 / value_per_wu;
134
+ debug_assert ! ( weight_needed <= candidate. weight as f32 ) ;
135
+ weight_needed. min ( 0.0 )
172
136
}
0 commit comments