Skip to content

Commit 6d3552c

Browse files
committed
Refactor calculate_waste inside Waste struct
1 parent d2bf84a commit 6d3552c

File tree

1 file changed

+85
-106
lines changed

1 file changed

+85
-106
lines changed

src/wallet/coin_selection.rs

+85-106
Original file line numberDiff line numberDiff line change
@@ -66,16 +66,13 @@
6666
//! });
6767
//! }
6868
//!
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+
//! )?;
7976
//!
8077
//! Ok(CoinSelectionResult::new(
8178
//! all_utxos_selected.into_iter().map(|u| u.utxo).collect(),
@@ -109,7 +106,6 @@ use rand::seq::SliceRandom;
109106
use rand::thread_rng;
110107
#[cfg(test)]
111108
use rand::{rngs::StdRng, SeedableRng};
112-
use std::cell::Cell;
113109
use std::convert::TryInto;
114110

115111
/// Default coin selection algorithm used by [`TxBuilder`](super::tx_builder::TxBuilder) if not
@@ -123,7 +119,6 @@ pub type DefaultCoinSelectionAlgorithm = LargestFirstCoinSelection; // make the
123119
// prev_txid (32 bytes) + prev_vout (4 bytes) + sequence (4 bytes) + script_len (1 bytes)
124120
pub(crate) const TXIN_BASE_WEIGHT: usize = (32 + 4 + 4 + 1) * 4;
125121

126-
pub type Waste = i64;
127122
/// Result of a successful coin selection
128123
#[derive(Debug)]
129124
pub struct CoinSelectionResult {
@@ -132,16 +127,16 @@ pub struct CoinSelectionResult {
132127
/// Total fee amount in satoshi
133128
pub fee_amount: u64,
134129
/// Waste value of current coin selection
135-
waste: Cell<Option<Waste>>,
130+
pub waste: Waste,
136131
}
137132

138133
impl CoinSelectionResult {
139134
/// 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 {
141136
CoinSelectionResult {
142137
selected: selected_utxos,
143138
fee_amount,
144-
waste: Cell::new(selection_waste),
139+
waste: selection_waste,
145140
}
146141
}
147142

@@ -160,78 +155,59 @@ impl CoinSelectionResult {
160155
})
161156
.sum()
162157
}
158+
}
163159

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(
166165
selected: &[WeightedUtxo],
167166
cost_of_change: Option<u64>,
168167
target: u64,
169168
fee_rate: FeeRate,
169+
// NOTE: The value of long_term_fee_rate should be set to 10sat/vb.
170170
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;
189184

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
225202
}
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+
};
233208

234-
Ok(timing_cost + creation_cost)
209+
Ok(Waste(timing_cost + creation_cost))
210+
}
235211
}
236212

237213
/// Trait for generalized coin selection algorithms
@@ -332,7 +308,8 @@ impl<D: Database> CoinSelectionAlgorithm<D> for LargestFirstCoinSelection {
332308
});
333309
}
334310

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)))
336313
}
337314
}
338315

@@ -621,7 +598,8 @@ impl BranchAndBoundCoinSelection {
621598
.map(|u| u.weighted_utxo.utxo.clone())
622599
.collect::<Vec<_>>();
623600

624-
CoinSelectionResult::new(selected, fee_amount, None)
601+
// TODO: Calculate waste metric for selected coins
602+
CoinSelectionResult::new(selected, fee_amount, Waste(0))
625603
}
626604
}
627605

@@ -1026,7 +1004,8 @@ mod test {
10261004
#[should_panic(expected = "BnBNoExactMatch")]
10271005
fn test_bnb_function_no_exact_match() {
10281006
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
10301009
.iter()
10311010
.map(|u| OutputGroup::new(u, fee_rate))
10321011
.collect();
@@ -1052,7 +1031,8 @@ mod test {
10521031
#[should_panic(expected = "BnBTotalTriesExceeded")]
10531032
fn test_bnb_function_tries_exceeded() {
10541033
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
10561036
.iter()
10571037
.map(|u| OutputGroup::new(u, fee_rate))
10581038
.collect();
@@ -1082,7 +1062,8 @@ mod test {
10821062
let size_of_change = 31;
10831063
let cost_of_change = size_of_change as f32 * fee_rate.as_sat_vb();
10841064

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
10861067
.iter()
10871068
.map(|u| OutputGroup::new(u, fee_rate))
10881069
.collect();
@@ -1118,7 +1099,8 @@ mod test {
11181099
let fee_rate = FeeRate::from_sat_per_vb(0.0);
11191100

11201101
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
11221104
.iter()
11231105
.map(|u| OutputGroup::new(u, fee_rate))
11241106
.collect();
@@ -1155,8 +1137,8 @@ mod test {
11551137
let target_amount = sum_random_utxos(&mut rng, &mut utxos);
11561138

11571139
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()
11601142
.map(|u| OutputGroup::new(u, fee_rate))
11611143
.collect();
11621144

@@ -1179,9 +1161,8 @@ mod test {
11791161
let selected = generate_utxos_of_values(vec![100_000_000, 200_000_000]);
11801162
let amount_needed = 200_000_000;
11811163

1182-
let utxos: Vec<OutputGroup> = selected
1183-
.clone()
1184-
.into_iter()
1164+
let utxos: Vec<OutputGroup<'_>> = selected
1165+
.iter()
11851166
.map(|u| OutputGroup::new(u, fee_rate))
11861167
.collect();
11871168

@@ -1198,15 +1179,15 @@ mod test {
11981179
});
11991180

12001181
// 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,
12031184
Some(cost_of_change),
12041185
amount_needed,
12051186
fee_rate,
12061187
long_term_fee_rate,
12071188
);
12081189

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));
12101191
}
12111192

12121193
#[test]
@@ -1218,9 +1199,8 @@ mod test {
12181199
let available_value: u64 = utxo_values.iter().sum();
12191200
let amount_needed = 200_000_000;
12201201

1221-
let utxos: Vec<OutputGroup> = selected
1222-
.clone()
1223-
.into_iter()
1202+
let utxos: Vec<OutputGroup<'_>> = selected
1203+
.iter()
12241204
.map(|u| OutputGroup::new(u, fee_rate))
12251205
.collect();
12261206

@@ -1238,9 +1218,9 @@ mod test {
12381218
let excess = available_value - utxos_fee - amount_needed;
12391219

12401220
// 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);
12421222

1243-
assert_eq!(waste.unwrap(), (utxo_fee_diff + excess as i64));
1223+
assert_eq!(waste.unwrap(), Waste(utxo_fee_diff + excess as i64));
12441224
}
12451225

12461226
#[test]
@@ -1252,8 +1232,8 @@ mod test {
12521232
let amount_needed = 200_000_000;
12531233

12541234
// 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,
12571237
Some(0),
12581238
amount_needed,
12591239
fee_rate,
@@ -1283,9 +1263,8 @@ mod test {
12831263
let available_value: u64 = utxo_values.iter().sum();
12841264
let amount_needed = 200_000_000;
12851265

1286-
let utxos: Vec<OutputGroup> = selected
1287-
.clone()
1288-
.into_iter()
1266+
let utxos: Vec<OutputGroup<'_>> = selected
1267+
.iter()
12891268
.map(|u| OutputGroup::new(u, fee_rate))
12901269
.collect();
12911270

@@ -1303,9 +1282,9 @@ mod test {
13031282
let excess = available_value - utxos_fee - amount_needed;
13041283

13051284
// 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);
13071286

1308-
assert_eq!(waste.unwrap(), (utxo_fee_diff + excess as i64));
1287+
assert_eq!(waste.unwrap(), Waste(utxo_fee_diff + excess as i64));
13091288
}
13101289

13111290
#[test]
@@ -1321,10 +1300,10 @@ mod test {
13211300
let amount_needed = utxo_values[0] - utxos_fee;
13221301

13231302
// 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);
13251304

13261305
// There shouldn't be any waste if there is no
13271306
// timing_cost nor creation_cost
1328-
assert_eq!(waste.unwrap(), 0);
1307+
assert_eq!(waste.unwrap(), Waste(0));
13291308
}
13301309
}

0 commit comments

Comments
 (0)