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,92 @@ 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
+ //
64
+ // TODO: This check could be tighter by being more complicated but this seems to be
65
+ // good enough for now.
66
+ let amount_above_change_threshold = drain_value - self . change_policy . min_value ;
67
+
68
+ if let Some ( ( _, low_sats_per_wu_candidate) ) = cs. unselected ( ) . next_back ( ) {
69
+ let ev = low_sats_per_wu_candidate. effective_value ( self . target . feerate ) ;
70
+ if ev < 0.0 {
71
+ // we can only reduce excess if ev is negative
72
+ let value_per_negative_effective_value =
73
+ low_sats_per_wu_candidate. value_pwu ( ) / ev. abs ( ) ;
74
+ let extra_value_needed_to_get_rid_of_change = amount_above_change_threshold
75
+ as f32
76
+ * value_per_negative_effective_value;
77
+ let cost_of_getting_rid_of_change =
78
+ extra_value_needed_to_get_rid_of_change + drain_value as f32 ;
79
+ let cost_of_change = self
80
+ . change_policy
81
+ . drain_weights
82
+ . waste ( self . target . feerate , self . long_term_feerate ) ;
83
+ let best_score_without_change = Ordf32 (
84
+ current_score. 0 - cost_of_change + cost_of_getting_rid_of_change,
85
+ ) ;
86
+ if best_score_without_change < current_score {
87
+ return Some ( best_score_without_change) ;
130
88
}
131
- None => self . calc_metric ( & cs, None ) ,
132
- } ;
133
-
134
- lower_bound = lower_bound. min ( lower_bound_changeless)
89
+ }
135
90
}
136
91
}
137
92
138
- return Some ( Ordf32 ( lower_bound) ) ;
93
+ Some ( current_score)
94
+ } else {
95
+ // Step 1: select everything up until the input that hits the target.
96
+ let ( mut cs, slurp_index, to_slurp) = cs
97
+ . clone ( )
98
+ . select_iter ( )
99
+ . find ( |( cs, _, _) | cs. is_target_met ( self . target ) ) ?;
100
+
101
+ cs. deselect ( slurp_index) ;
102
+
103
+ // Step 2: We pretend that the final input exactly cancels out the remaining excess
104
+ // by taking whatever value we want from it but at the value per weight of the real
105
+ // input.
106
+ let ideal_next_weight = {
107
+ let remaining_rate = cs. rate_excess ( self . target , Drain :: none ( ) ) ;
108
+
109
+ slurp_wv ( to_slurp, remaining_rate. min ( 0 ) , self . target . feerate )
110
+ } ;
111
+ let input_weight_lower_bound = cs. input_weight ( ) as f32 + ideal_next_weight;
112
+ let ideal_fee_by_feerate =
113
+ ( cs. base_weight ( ) as f32 + input_weight_lower_bound) * self . target . feerate . spwu ( ) ;
114
+ let ideal_fee = ideal_fee_by_feerate. max ( self . target . min_fee as f32 ) ;
115
+
116
+ Some ( Ordf32 ( ideal_fee) )
139
117
}
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
118
}
162
119
163
120
fn requires_ordering_by_descending_value_pwu ( & self ) -> bool {
164
121
true
165
122
}
166
123
}
167
124
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 )
125
+ /// Returns the "perfect weight" for this candidate to slurp up a given value with `feerate` while
126
+ /// not changing the candidate's value/weight ratio.
127
+ ///
128
+ /// Used to pretend that a candidate had precisely `value_to_slurp` + fee needed to include it. It
129
+ /// tells you how much weight such a perfect candidate would have if it had the same value per
130
+ /// weight unit as `candidate`. This is useful for estimating a lower weight bound for a perfect
131
+ /// match.
132
+ fn slurp_wv ( candidate : Candidate , value_to_slurp : i64 , feerate : FeeRate ) -> f32 {
133
+ // the value per weight unit this candidate offers at feerate
134
+ let value_per_wu = ( candidate. value as f32 / candidate. weight as f32 ) - feerate. spwu ( ) ;
135
+ // return how much weight we need
136
+ let weight_needed = value_to_slurp as f32 / value_per_wu;
137
+ debug_assert ! ( weight_needed <= candidate. weight as f32 ) ;
138
+ weight_needed. min ( 0.0 )
172
139
}
0 commit comments