Skip to content

Commit 7a5b38b

Browse files
committed
draft: move weighted_utxo to seperate module
1 parent ca2a659 commit 7a5b38b

File tree

4 files changed

+108
-98
lines changed

4 files changed

+108
-98
lines changed

src/branch_and_bound.rs

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -171,8 +171,11 @@ pub fn branch_and_bound<'a, T: IntoIterator<Item = &'a WeightedUtxo> + std::mark
171171
let mut weighted_utxos: Vec<_> = weighted_utxos.into_iter().collect();
172172

173173
// descending sort by effective_value, ascending sort by waste.
174-
weighted_utxos
175-
.sort_by(|a, b| b.effective_value().cmp(&a.effective_value()).then(a.waste.cmp(&b.waste)));
174+
weighted_utxos.sort_by(|a, b| {
175+
b.effective_value_raw()
176+
.cmp(&a.effective_value_raw())
177+
.then(a.waste_raw().cmp(&b.waste_raw()))
178+
});
176179

177180
if available_value < target {
178181
return Err(InsufficentFunds);
@@ -247,13 +250,13 @@ pub fn branch_and_bound<'a, T: IntoIterator<Item = &'a WeightedUtxo> + std::mark
247250
break;
248251
}
249252

250-
let eff_value = weighted_utxos[index].effective_value;
253+
let eff_value = weighted_utxos[index].effective_value_raw();
251254
available_value += eff_value;
252255
}
253256

254257
assert_eq!(index, *index_selection.last().unwrap());
255-
let eff_value = weighted_utxos[index].effective_value;
256-
let utxo_waste = weighted_utxos[index].waste;
258+
let eff_value = weighted_utxos[index].effective_value_raw();
259+
let utxo_waste = weighted_utxos[index].waste_raw();
257260
let utxo_weight = weighted_utxos[index].weight();
258261
current_waste = current_waste.checked_sub(utxo_waste).ok_or(Overflow(Subtraction))?;
259262
value = value.checked_sub(eff_value).ok_or(Overflow(Addition))?;
@@ -262,9 +265,9 @@ pub fn branch_and_bound<'a, T: IntoIterator<Item = &'a WeightedUtxo> + std::mark
262265
}
263266
// * Add next node to the inclusion branch.
264267
else {
265-
let eff_value = weighted_utxos[index].effective_value;
268+
let eff_value = weighted_utxos[index].effective_value_raw();
266269
let utxo_weight = weighted_utxos[index].weight();
267-
let utxo_waste = weighted_utxos[index].waste;
270+
let utxo_waste = weighted_utxos[index].waste_raw();
268271

269272
// unchecked sub is used her for performance.
270273
// The bounds for available_value are at most the sum of utxos
@@ -277,7 +280,7 @@ pub fn branch_and_bound<'a, T: IntoIterator<Item = &'a WeightedUtxo> + std::mark
277280
// Check if the previous UTXO was included.
278281
|| index - 1 == *index_selection.last().unwrap()
279282
// Check if the previous UTXO has the same value has the previous one.
280-
|| weighted_utxos[index].effective_value != weighted_utxos[index - 1].effective_value
283+
|| weighted_utxos[index].effective_value_raw() != weighted_utxos[index - 1].effective_value_raw()
281284
{
282285
index_selection.push(index);
283286
current_waste = current_waste.checked_add(utxo_waste).ok_or(Overflow(Addition))?;

src/coin_grinder.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -672,7 +672,7 @@ mod tests {
672672
let w = u.int_in_range::<u64>(1..=Weight::MAX.to_wu()).unwrap();
673673
let wu = Weight::from_wu(w);
674674
WeightedUtxo::new(
675-
utxo.value,
675+
utxo.value(),
676676
wu,
677677
exclusion_set.fee_rate,
678678
exclusion_set.long_term_fee_rate,
@@ -685,14 +685,14 @@ mod tests {
685685
.iter()
686686
.map(|utxo| {
687687
WeightedUtxo::new(
688-
utxo.value,
688+
utxo.value(),
689689
Weight::ZERO,
690690
inclusion_set.fee_rate,
691691
inclusion_set.long_term_fee_rate,
692692
)
693693
.unwrap()
694694
})
695-
.filter(|utxo| utxo.value == Amount::ZERO)
695+
.filter(|utxo| utxo.value() == Amount::ZERO)
696696
.collect();
697697

698698
if let Some(target) = weightless_pool.iter().map(|utxo| utxo.value()).checked_sum() {

src/lib.rs

Lines changed: 4 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,13 @@ mod branch_and_bound;
1515
mod coin_grinder;
1616
mod single_random_draw;
1717

18+
mod weighted_utxo;
19+
20+
pub use crate::weighted_utxo::WeightedUtxo;
21+
1822
/// Possible returned error types if no match is found.
1923
pub mod errors;
2024

21-
use std::cmp::Ordering;
22-
2325
use bitcoin_units::{Amount, FeeRate, SignedAmount, Weight};
2426
#[cfg(feature = "rand")]
2527
#[cfg_attr(docsrs, doc(cfg(feature = "rand")))]
@@ -60,91 +62,6 @@ pub(crate) fn effective_value(
6062
Some(eff_value)
6163
}
6264

63-
#[derive(Debug, Clone, PartialEq, Eq)]
64-
/// Represents the spendable conditions of a `UTXO`.
65-
pub struct WeightedUtxo {
66-
/// The `Amount` that the output contributes towards the selection target.
67-
value: Amount,
68-
/// The estimated `Weight` (satisfaction weight + base weight) of the output.
69-
weight: Weight,
70-
/// The positive effective value `(value - fee)`. This value is stored as a `u64` for
71-
/// better performance.
72-
effective_value: u64,
73-
/// The `SignedAmount` required to spend the output at the given `fee_rate`.
74-
fee: SignedAmount,
75-
/// The `SignedAmount` required to spend the output at the given `long_term_fee_rate`.
76-
long_term_fee: SignedAmount,
77-
/// A metric for how wasteful it is to spend this `WeightedUtxo` given the current fee
78-
/// environment.
79-
waste: i64,
80-
}
81-
82-
impl WeightedUtxo {
83-
/// Creates a new `WeightedUtxo`.
84-
pub fn new(
85-
value: Amount,
86-
weight: Weight,
87-
fee_rate: FeeRate,
88-
long_term_fee_rate: FeeRate,
89-
) -> Option<WeightedUtxo> {
90-
let positive_effective_value = Self::positive_effective_value(fee_rate, weight, value);
91-
92-
if let Some(effective_value) = positive_effective_value {
93-
let fee = fee_rate.fee_wu(weight)?.to_signed();
94-
let long_term_fee: SignedAmount = long_term_fee_rate.fee_wu(weight)?.to_signed();
95-
let waste = Self::calculate_waste(fee, long_term_fee);
96-
return Some(Self { value, weight, effective_value, fee, long_term_fee, waste });
97-
}
98-
99-
None
100-
}
101-
102-
/// Calculates if the current fee environment is expensive.
103-
pub fn is_fee_expensive(&self) -> bool { self.fee > self.long_term_fee }
104-
105-
/// Returns the associated value.
106-
pub fn value(&self) -> Amount { self.value }
107-
108-
/// Returns the associated weight.
109-
pub fn weight(&self) -> Weight { self.weight }
110-
111-
/// Returns the associated waste.
112-
pub fn waste(&self) -> SignedAmount { SignedAmount::from_sat(self.waste).unwrap() }
113-
114-
/// Returns the calculated effective value.
115-
pub fn effective_value(&self) -> Amount { Amount::from_sat(self.effective_value).unwrap() }
116-
117-
/// Returns the calculated effective value using the underlying type without transformation.
118-
pub fn effective_value_raw(&self) -> u64 { self.effective_value }
119-
120-
/// Returns the calculated waste using the underlying type without transformation.
121-
pub fn waste_raw(&self) -> i64 { self.waste }
122-
123-
fn positive_effective_value(fee_rate: FeeRate, weight: Weight, value: Amount) -> Option<u64> {
124-
if let Some(eff_value) = effective_value(fee_rate, weight, value) {
125-
if let Ok(unsigned) = eff_value.to_unsigned() {
126-
return Some(unsigned.to_sat());
127-
}
128-
}
129-
130-
None
131-
}
132-
133-
fn calculate_waste(fee: SignedAmount, long_term_fee: SignedAmount) -> i64 {
134-
fee.to_sat() - long_term_fee.to_sat()
135-
}
136-
}
137-
138-
impl Ord for WeightedUtxo {
139-
fn cmp(&self, other: &Self) -> Ordering {
140-
other.effective_value.cmp(&self.effective_value).then(self.weight.cmp(&other.weight))
141-
}
142-
}
143-
144-
impl PartialOrd for WeightedUtxo {
145-
fn partial_cmp(&self, other: &Self) -> Option<Ordering> { Some(self.cmp(other)) }
146-
}
147-
14865
/// Attempt a match with [`branch_and_bound`] falling back to [`single_random_draw`].
14966
///
15067
/// If [`branch_and_bound`] fails to find a changeless solution (basically, an exact match), then

src/weighted_utxo.rs

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
use std::cmp::Ordering;
2+
3+
use bitcoin_units::{Amount, FeeRate, SignedAmount, Weight};
4+
5+
use crate::effective_value;
6+
7+
#[derive(Debug, Clone, PartialEq, Eq)]
8+
/// Represents the spendable conditions of a `UTXO`.
9+
pub struct WeightedUtxo {
10+
/// The `Amount` that the output contributes towards the selection target.
11+
value: Amount,
12+
/// The estimated `Weight` (satisfaction weight + base weight) of the output.
13+
weight: Weight,
14+
/// The positive effective value `(value - fee)`. This value is stored as a `u64` for
15+
/// better performance.
16+
effective_value: u64,
17+
/// The `SignedAmount` required to spend the output at the given `fee_rate`.
18+
fee: SignedAmount,
19+
/// The `SignedAmount` required to spend the output at the given `long_term_fee_rate`.
20+
long_term_fee: SignedAmount,
21+
/// A metric for how wasteful it is to spend this `WeightedUtxo` given the current fee
22+
/// environment.
23+
waste: i64,
24+
}
25+
26+
impl WeightedUtxo {
27+
/// Creates a new `WeightedUtxo`.
28+
pub fn new(
29+
value: Amount,
30+
weight: Weight,
31+
fee_rate: FeeRate,
32+
long_term_fee_rate: FeeRate,
33+
) -> Option<WeightedUtxo> {
34+
let positive_effective_value = Self::positive_effective_value(fee_rate, weight, value);
35+
36+
if let Some(effective_value) = positive_effective_value {
37+
let fee = fee_rate.fee_wu(weight)?.to_signed();
38+
let long_term_fee: SignedAmount = long_term_fee_rate.fee_wu(weight)?.to_signed();
39+
let waste = Self::calculate_waste(fee, long_term_fee);
40+
return Some(Self { value, weight, effective_value, fee, long_term_fee, waste });
41+
}
42+
43+
None
44+
}
45+
46+
/// Calculates if the current fee environment is expensive.
47+
pub fn is_fee_expensive(&self) -> bool { self.fee > self.long_term_fee }
48+
49+
/// Returns the associated value.
50+
pub fn value(&self) -> Amount { self.value }
51+
52+
/// Returns the associated weight.
53+
pub fn weight(&self) -> Weight { self.weight }
54+
55+
/// Returns the associated waste.
56+
pub fn waste(&self) -> SignedAmount { SignedAmount::from_sat(self.waste).unwrap() }
57+
58+
/// Returns the calculated effective value.
59+
pub fn effective_value(&self) -> Amount { Amount::from_sat(self.effective_value).unwrap() }
60+
61+
/// Returns the calculated effective value using the underlying type without transformation.
62+
pub fn effective_value_raw(&self) -> u64 { self.effective_value }
63+
64+
/// Returns the calculated waste using the underlying type without transformation.
65+
pub fn waste_raw(&self) -> i64 { self.waste }
66+
67+
fn positive_effective_value(fee_rate: FeeRate, weight: Weight, value: Amount) -> Option<u64> {
68+
if let Some(eff_value) = effective_value(fee_rate, weight, value) {
69+
if let Ok(unsigned) = eff_value.to_unsigned() {
70+
return Some(unsigned.to_sat());
71+
}
72+
}
73+
74+
None
75+
}
76+
77+
fn calculate_waste(fee: SignedAmount, long_term_fee: SignedAmount) -> i64 {
78+
fee.to_sat() - long_term_fee.to_sat()
79+
}
80+
}
81+
82+
impl Ord for WeightedUtxo {
83+
fn cmp(&self, other: &Self) -> Ordering {
84+
other.effective_value.cmp(&self.effective_value).then(self.weight.cmp(&other.weight))
85+
}
86+
}
87+
88+
impl PartialOrd for WeightedUtxo {
89+
fn partial_cmp(&self, other: &Self) -> Option<Ordering> { Some(self.cmp(other)) }
90+
}

0 commit comments

Comments
 (0)