66
66
//! });
67
67
//! }
68
68
//!
69
- //! let calculated_waste = Some(
70
- //! calculate_waste(
71
- //! &all_utxos_selected,
72
- //! None,
73
- //! amount_needed,
74
- //! fee_rate,
75
- //! FeeRate::from_sat_per_vb(10.0),
76
- //! )
77
- //! .unwrap(),
78
- //! );
69
+ //! let calculated_waste = Waste::calculate(
70
+ //! &all_utxos_selected,
71
+ //! None,
72
+ //! amount_needed,
73
+ //! fee_rate,
74
+ //! FeeRate::from_sat_per_vb(10.0),
75
+ //! )?;
79
76
//!
80
77
//! Ok(CoinSelectionResult::new(
81
78
//! all_utxos_selected.into_iter().map(|u| u.utxo).collect(),
@@ -109,7 +106,6 @@ use rand::seq::SliceRandom;
109
106
use rand:: thread_rng;
110
107
#[ cfg( test) ]
111
108
use rand:: { rngs:: StdRng , SeedableRng } ;
112
- use std:: cell:: Cell ;
113
109
use std:: convert:: TryInto ;
114
110
115
111
/// Default coin selection algorithm used by [`TxBuilder`](super::tx_builder::TxBuilder) if not
@@ -123,7 +119,6 @@ pub type DefaultCoinSelectionAlgorithm = LargestFirstCoinSelection; // make the
123
119
// prev_txid (32 bytes) + prev_vout (4 bytes) + sequence (4 bytes) + script_len (1 bytes)
124
120
pub ( crate ) const TXIN_BASE_WEIGHT : usize = ( 32 + 4 + 4 + 1 ) * 4 ;
125
121
126
- pub type Waste = i64 ;
127
122
/// Result of a successful coin selection
128
123
#[ derive( Debug ) ]
129
124
pub struct CoinSelectionResult {
@@ -132,16 +127,16 @@ pub struct CoinSelectionResult {
132
127
/// Total fee amount in satoshi
133
128
pub fee_amount : u64 ,
134
129
/// Waste value of current coin selection
135
- waste : Cell < Option < Waste > > ,
130
+ pub waste : Waste ,
136
131
}
137
132
138
133
impl CoinSelectionResult {
139
134
/// Create new CoinSelectionResult
140
- pub fn new ( selected_utxos : Vec < Utxo > , fee_amount : u64 , selection_waste : Option < Waste > ) -> Self {
135
+ pub fn new ( selected_utxos : Vec < Utxo > , fee_amount : u64 , selection_waste : Waste ) -> Self {
141
136
CoinSelectionResult {
142
137
selected : selected_utxos,
143
138
fee_amount,
144
- waste : Cell :: new ( selection_waste) ,
139
+ waste : selection_waste,
145
140
}
146
141
}
147
142
@@ -160,78 +155,59 @@ impl CoinSelectionResult {
160
155
} )
161
156
. sum ( )
162
157
}
158
+ }
163
159
164
- pub fn get_waste (
165
- & self ,
160
+ #[ derive( Debug , PartialEq , Eq , PartialOrd , Ord ) ]
161
+ pub struct Waste ( pub i64 ) ;
162
+
163
+ impl Waste {
164
+ pub fn calculate (
166
165
selected : & [ WeightedUtxo ] ,
167
166
cost_of_change : Option < u64 > ,
168
167
target : u64 ,
169
168
fee_rate : FeeRate ,
169
+ // NOTE: The value of long_term_fee_rate should be set to 10sat/vb.
170
170
long_term_fee_rate : FeeRate ,
171
- ) -> Waste {
172
- match self . waste . get ( ) {
173
- Some ( waste) => waste,
174
- None => {
175
- let calculated_waste = calculate_waste (
176
- selected,
177
- cost_of_change,
178
- target,
179
- fee_rate,
180
- long_term_fee_rate,
181
- )
182
- . unwrap ( ) ;
183
- self . waste . set ( Some ( calculated_waste) ) ;
184
- calculated_waste
185
- }
186
- }
187
- }
188
- }
171
+ ) -> Result < Waste , Error > {
172
+ // Always consider the cost of spending an input now vs in the future.
173
+ let utxo_groups: Vec < _ > = selected
174
+ . iter ( )
175
+ . map ( |u| OutputGroup :: new ( u, fee_rate) )
176
+ . collect ( ) ;
177
+
178
+ // If fee_rate < long_term_fee_rate timing cost can be negative
179
+ let timing_cost: i64 = utxo_groups. iter ( ) . fold ( 0 , |acc, utxo| {
180
+ let fee: i64 = utxo. fee as i64 ;
181
+ let long_term_fee: i64 = long_term_fee_rate
182
+ . fee_wu ( TXIN_BASE_WEIGHT + utxo. weighted_utxo . satisfaction_weight )
183
+ as i64 ;
189
184
190
- pub fn calculate_waste (
191
- selected : & [ WeightedUtxo ] ,
192
- cost_of_change : Option < u64 > ,
193
- target : u64 ,
194
- fee_rate : FeeRate ,
195
- long_term_fee_rate : FeeRate ,
196
- ) -> Result < Waste , Error > {
197
- // Always consider the cost of spending an input now vs in the future.
198
- let utxo_groups: Vec < _ > = selected
199
- . iter ( )
200
- . map ( |u| OutputGroup :: new ( u, fee_rate) )
201
- . collect ( ) ;
202
-
203
- // If fee_rate < long_term_fee_rate timing cost can be negative
204
- let timing_cost: i64 = utxo_groups. iter ( ) . fold ( 0 , |acc, utxo| {
205
- let fee: i64 = utxo. fee as i64 ;
206
- let long_term_fee: i64 = long_term_fee_rate
207
- . fee_wu ( TXIN_BASE_WEIGHT + utxo. weighted_utxo . satisfaction_weight )
208
- as i64 ;
209
-
210
- acc + fee - long_term_fee
211
- } ) ;
212
-
213
- let selected_effective_value: i64 = utxo_groups
214
- . iter ( )
215
- . fold ( 0 , |acc, utxo| acc + utxo. effective_value ) ;
216
-
217
- let creation_cost = match cost_of_change {
218
- Some ( change_cost) => {
219
- // Consider the cost of making change and spending it in the future
220
- // If we aren't making change, the caller should've set change_cost to None
221
- if change_cost == 0 {
222
- return Err ( Error :: Generic (
223
- "if there is not cost_of_change, set to None, not zero" . to_string ( ) ,
224
- ) ) ;
185
+ acc + fee - long_term_fee
186
+ } ) ;
187
+
188
+ let selected_effective_value: i64 = utxo_groups
189
+ . iter ( )
190
+ . fold ( 0 , |acc, utxo| acc + utxo. effective_value ) ;
191
+
192
+ let creation_cost = match cost_of_change {
193
+ Some ( change_cost) => {
194
+ // Consider the cost of making change and spending it in the future
195
+ // If we aren't making change, the caller should've set cost_of_change to None
196
+ if change_cost == 0 {
197
+ return Err ( Error :: Generic (
198
+ "if there is not cost_of_change, set to None, not zero" . to_string ( ) ,
199
+ ) ) ;
200
+ }
201
+ change_cost as i64
225
202
}
226
- change_cost as i64
227
- }
228
- None => {
229
- // When we are not making change, consider the excess we are throwing away to fees
230
- selected_effective_value - target as i64
231
- }
232
- } ;
203
+ None => {
204
+ // When we are not making change, consider the excess we are throwing away to fees
205
+ selected_effective_value - target as i64
206
+ }
207
+ } ;
233
208
234
- Ok ( timing_cost + creation_cost)
209
+ Ok ( Waste ( timing_cost + creation_cost) )
210
+ }
235
211
}
236
212
237
213
/// Trait for generalized coin selection algorithms
@@ -332,7 +308,8 @@ impl<D: Database> CoinSelectionAlgorithm<D> for LargestFirstCoinSelection {
332
308
} ) ;
333
309
}
334
310
335
- Ok ( CoinSelectionResult :: new ( selected, fee_amount, None ) )
311
+ // TODO: Calculate waste metric for selected coins
312
+ Ok ( CoinSelectionResult :: new ( selected, fee_amount, Waste ( 0 ) ) )
336
313
}
337
314
}
338
315
@@ -621,7 +598,8 @@ impl BranchAndBoundCoinSelection {
621
598
. map ( |u| u. weighted_utxo . utxo . clone ( ) )
622
599
. collect :: < Vec < _ > > ( ) ;
623
600
624
- CoinSelectionResult :: new ( selected, fee_amount, None )
601
+ // TODO: Calculate waste metric for selected coins
602
+ CoinSelectionResult :: new ( selected, fee_amount, Waste ( 0 ) )
625
603
}
626
604
}
627
605
@@ -1026,7 +1004,8 @@ mod test {
1026
1004
#[ should_panic( expected = "BnBNoExactMatch" ) ]
1027
1005
fn test_bnb_function_no_exact_match ( ) {
1028
1006
let fee_rate = FeeRate :: from_sat_per_vb ( 10.0 ) ;
1029
- let utxos: Vec < OutputGroup < ' _ > > = get_test_utxos ( )
1007
+ let test_utxos = get_test_utxos ( ) ;
1008
+ let utxos: Vec < OutputGroup < ' _ > > = test_utxos
1030
1009
. iter ( )
1031
1010
. map ( |u| OutputGroup :: new ( u, fee_rate) )
1032
1011
. collect ( ) ;
@@ -1052,7 +1031,8 @@ mod test {
1052
1031
#[ should_panic( expected = "BnBTotalTriesExceeded" ) ]
1053
1032
fn test_bnb_function_tries_exceeded ( ) {
1054
1033
let fee_rate = FeeRate :: from_sat_per_vb ( 10.0 ) ;
1055
- let utxos: Vec < OutputGroup > = generate_same_value_utxos ( 100_000 , 100_000 )
1034
+ let same_value_utxos = generate_same_value_utxos ( 100_000 , 100_000 ) ;
1035
+ let utxos: Vec < OutputGroup > = same_value_utxos
1056
1036
. iter ( )
1057
1037
. map ( |u| OutputGroup :: new ( u, fee_rate) )
1058
1038
. collect ( ) ;
@@ -1082,7 +1062,8 @@ mod test {
1082
1062
let size_of_change = 31 ;
1083
1063
let cost_of_change = size_of_change as f32 * fee_rate. as_sat_vb ( ) ;
1084
1064
1085
- let utxos: Vec < _ > = generate_same_value_utxos ( 50_000 , 10 )
1065
+ let same_value_utxos = generate_same_value_utxos ( 50_000 , 10 ) ;
1066
+ let utxos: Vec < _ > = same_value_utxos
1086
1067
. iter ( )
1087
1068
. map ( |u| OutputGroup :: new ( u, fee_rate) )
1088
1069
. collect ( ) ;
@@ -1118,7 +1099,8 @@ mod test {
1118
1099
let fee_rate = FeeRate :: from_sat_per_vb ( 0.0 ) ;
1119
1100
1120
1101
for _ in 0 ..200 {
1121
- let optional_utxos: Vec < _ > = generate_random_utxos ( & mut rng, 40 )
1102
+ let random_utxos = generate_random_utxos ( & mut rng, 40 ) ;
1103
+ let optional_utxos: Vec < _ > = random_utxos
1122
1104
. iter ( )
1123
1105
. map ( |u| OutputGroup :: new ( u, fee_rate) )
1124
1106
. collect ( ) ;
@@ -1155,8 +1137,8 @@ mod test {
1155
1137
let target_amount = sum_random_utxos ( & mut rng, & mut utxos) ;
1156
1138
1157
1139
let fee_rate = FeeRate :: from_sat_per_vb ( 1.0 ) ;
1158
- let utxos: Vec < OutputGroup > = utxos
1159
- . into_iter ( )
1140
+ let utxos: Vec < OutputGroup < ' _ > > = utxos
1141
+ . iter ( )
1160
1142
. map ( |u| OutputGroup :: new ( u, fee_rate) )
1161
1143
. collect ( ) ;
1162
1144
@@ -1179,9 +1161,8 @@ mod test {
1179
1161
let selected = generate_utxos_of_values ( vec ! [ 100_000_000 , 200_000_000 ] ) ;
1180
1162
let amount_needed = 200_000_000 ;
1181
1163
1182
- let utxos: Vec < OutputGroup > = selected
1183
- . clone ( )
1184
- . into_iter ( )
1164
+ let utxos: Vec < OutputGroup < ' _ > > = selected
1165
+ . iter ( )
1185
1166
. map ( |u| OutputGroup :: new ( u, fee_rate) )
1186
1167
. collect ( ) ;
1187
1168
@@ -1198,15 +1179,15 @@ mod test {
1198
1179
} ) ;
1199
1180
1200
1181
// Waste with change is the change cost and difference between fee and long term fee
1201
- let waste = calculate_waste (
1202
- selected,
1182
+ let waste = Waste :: calculate (
1183
+ & selected,
1203
1184
Some ( cost_of_change) ,
1204
1185
amount_needed,
1205
1186
fee_rate,
1206
1187
long_term_fee_rate,
1207
1188
) ;
1208
1189
1209
- assert_eq ! ( waste. unwrap( ) , ( utxo_fee_diff + cost_of_change as i64 ) ) ;
1190
+ assert_eq ! ( waste. unwrap( ) , Waste ( utxo_fee_diff + cost_of_change as i64 ) ) ;
1210
1191
}
1211
1192
1212
1193
#[ test]
@@ -1218,9 +1199,8 @@ mod test {
1218
1199
let available_value: u64 = utxo_values. iter ( ) . sum ( ) ;
1219
1200
let amount_needed = 200_000_000 ;
1220
1201
1221
- let utxos: Vec < OutputGroup > = selected
1222
- . clone ( )
1223
- . into_iter ( )
1202
+ let utxos: Vec < OutputGroup < ' _ > > = selected
1203
+ . iter ( )
1224
1204
. map ( |u| OutputGroup :: new ( u, fee_rate) )
1225
1205
. collect ( ) ;
1226
1206
@@ -1238,9 +1218,9 @@ mod test {
1238
1218
let excess = available_value - utxos_fee - amount_needed;
1239
1219
1240
1220
// Waste without change is the excess and difference between fee and long term fee
1241
- let waste = calculate_waste ( selected, None , amount_needed, fee_rate, long_term_fee_rate) ;
1221
+ let waste = Waste :: calculate ( & selected, None , amount_needed, fee_rate, long_term_fee_rate) ;
1242
1222
1243
- assert_eq ! ( waste. unwrap( ) , ( utxo_fee_diff + excess as i64 ) ) ;
1223
+ assert_eq ! ( waste. unwrap( ) , Waste ( utxo_fee_diff + excess as i64 ) ) ;
1244
1224
}
1245
1225
1246
1226
#[ test]
@@ -1252,8 +1232,8 @@ mod test {
1252
1232
let amount_needed = 200_000_000 ;
1253
1233
1254
1234
// Waste without change is the excess and difference between fee and long term fee
1255
- let waste = calculate_waste (
1256
- selected,
1235
+ let waste = Waste :: calculate (
1236
+ & selected,
1257
1237
Some ( 0 ) ,
1258
1238
amount_needed,
1259
1239
fee_rate,
@@ -1283,9 +1263,8 @@ mod test {
1283
1263
let available_value: u64 = utxo_values. iter ( ) . sum ( ) ;
1284
1264
let amount_needed = 200_000_000 ;
1285
1265
1286
- let utxos: Vec < OutputGroup > = selected
1287
- . clone ( )
1288
- . into_iter ( )
1266
+ let utxos: Vec < OutputGroup < ' _ > > = selected
1267
+ . iter ( )
1289
1268
. map ( |u| OutputGroup :: new ( u, fee_rate) )
1290
1269
. collect ( ) ;
1291
1270
@@ -1303,9 +1282,9 @@ mod test {
1303
1282
let excess = available_value - utxos_fee - amount_needed;
1304
1283
1305
1284
// Waste without change is the excess and difference between fee and long term fee
1306
- let waste = calculate_waste ( selected, None , amount_needed, fee_rate, long_term_fee_rate) ;
1285
+ let waste = Waste :: calculate ( & selected, None , amount_needed, fee_rate, long_term_fee_rate) ;
1307
1286
1308
- assert_eq ! ( waste. unwrap( ) , ( utxo_fee_diff + excess as i64 ) ) ;
1287
+ assert_eq ! ( waste. unwrap( ) , Waste ( utxo_fee_diff + excess as i64 ) ) ;
1309
1288
}
1310
1289
1311
1290
#[ test]
@@ -1321,10 +1300,10 @@ mod test {
1321
1300
let amount_needed = utxo_values[ 0 ] - utxos_fee;
1322
1301
1323
1302
// Waste without change is the excess and difference between fee and long term fee
1324
- let waste = calculate_waste ( selected, None , amount_needed, fee_rate, long_term_fee_rate) ;
1303
+ let waste = Waste :: calculate ( & selected, None , amount_needed, fee_rate, long_term_fee_rate) ;
1325
1304
1326
1305
// There shouldn't be any waste if there is no
1327
1306
// timing_cost nor creation_cost
1328
- assert_eq ! ( waste. unwrap( ) , 0 ) ;
1307
+ assert_eq ! ( waste. unwrap( ) , Waste ( 0 ) ) ;
1329
1308
}
1330
1309
}
0 commit comments