Skip to content

Commit bd89626

Browse files
committed
wip
wip wip wip bump commit wip
1 parent 49f0705 commit bd89626

File tree

6 files changed

+140
-125
lines changed

6 files changed

+140
-125
lines changed

Cargo.toml

+9-9
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,11 @@ readme = "README.md"
1414
rust-version = "1.63.0"
1515

1616
[dependencies]
17-
bitcoin = { git = "https://github.com/rust-bitcoin/rust-bitcoin.git", rev = "7df5e7c1bcb4aaf3247f0b76591db9744f03425e" }
17+
bitcoin = { git = "https://github.com/rust-bitcoin/rust-bitcoin.git", rev = "52f9c13358c97c358543f3302b325f37ac49392f" }
1818
rand = { version = "0.8.5", default-features = false, optional = true }
1919

2020
[dev-dependencies]
21-
bitcoin = { git = "https://github.com/rust-bitcoin/rust-bitcoin.git", rev = "7df5e7c1bcb4aaf3247f0b76591db9744f03425e", features = ["arbitrary"] }
21+
bitcoin = { git = "https://github.com/rust-bitcoin/rust-bitcoin.git", rev = "52f9c13358c97c358543f3302b325f37ac49392f", features = ["arbitrary"] }
2222
criterion = "0.3"
2323
bitcoin-coin-selection = {path = ".", features = ["rand"]}
2424
rand = "0.8.5"
@@ -31,10 +31,10 @@ name = "coin_selection"
3131
harness = false
3232

3333
[patch.crates-io]
34-
bitcoin_hashes = { git = "https://github.com/rust-bitcoin/rust-bitcoin.git", rev = "7df5e7c1bcb4aaf3247f0b76591db9744f03425e" }
35-
base58ck = { git = "https://github.com/rust-bitcoin/rust-bitcoin.git", rev = "7df5e7c1bcb4aaf3247f0b76591db9744f03425e" }
36-
bitcoin-internals = { git = "https://github.com/rust-bitcoin/rust-bitcoin.git", rev = "7df5e7c1bcb4aaf3247f0b76591db9744f03425e" }
37-
bitcoin-io = { git = "https://github.com/rust-bitcoin/rust-bitcoin.git", rev = "7df5e7c1bcb4aaf3247f0b76591db9744f03425e" }
38-
bitcoin-primitives = { git = "https://github.com/rust-bitcoin/rust-bitcoin.git", rev = "7df5e7c1bcb4aaf3247f0b76591db9744f03425e" }
39-
bitcoin-addresses = { git = "https://github.com/rust-bitcoin/rust-bitcoin.git", rev = "7df5e7c1bcb4aaf3247f0b76591db9744f03425e" }
40-
bitcoin-units = { git = "https://github.com/rust-bitcoin/rust-bitcoin.git", rev = "7df5e7c1bcb4aaf3247f0b76591db9744f03425e" }
34+
bitcoin_hashes = { git = "https://github.com/rust-bitcoin/rust-bitcoin.git", rev = "52f9c13358c97c358543f3302b325f37ac49392f" }
35+
base58ck = { git = "https://github.com/rust-bitcoin/rust-bitcoin.git", rev = "52f9c13358c97c358543f3302b325f37ac49392f" }
36+
bitcoin-internals = { git = "https://github.com/rust-bitcoin/rust-bitcoin.git", rev = "52f9c13358c97c358543f3302b325f37ac49392f" }
37+
bitcoin-io = { git = "https://github.com/rust-bitcoin/rust-bitcoin.git", rev = "52f9c13358c97c358543f3302b325f37ac49392f" }
38+
bitcoin-primitives = { git = "https://github.com/rust-bitcoin/rust-bitcoin.git", rev = "52f9c13358c97c358543f3302b325f37ac49392f" }
39+
bitcoin-addresses = { git = "https://github.com/rust-bitcoin/rust-bitcoin.git", rev = "52f9c13358c97c358543f3302b325f37ac49392f" }
40+
bitcoin-units = { git = "https://github.com/rust-bitcoin/rust-bitcoin.git", rev = "52f9c13358c97c358543f3302b325f37ac49392f" }

benches/coin_selection.rs

+6-6
Original file line numberDiff line numberDiff line change
@@ -16,19 +16,19 @@ impl WeightedUtxo for Utxo {
1616

1717
pub fn criterion_benchmark(c: &mut Criterion) {
1818
// https://github.com/bitcoin/bitcoin/blob/f3bc1a72825fe2b51f4bc20e004cef464f05b965/src/wallet/coinselection.h#L18
19-
let cost_of_change = Amount::from_sat(50_000);
19+
let cost_of_change = Amount::from_sat(50_000).unwrap();
2020

2121
let one = Utxo {
22-
output: TxOut { value: Amount::from_sat(1_000), script_pubkey: ScriptBuf::new() },
22+
output: TxOut { value: Amount::from_sat(1_000).unwrap(), script_pubkey: ScriptBuf::new() },
2323
satisfaction_weight: Weight::ZERO,
2424
};
2525

2626
let two = Utxo {
27-
output: TxOut { value: Amount::from_sat(3), script_pubkey: ScriptBuf::new() },
27+
output: TxOut { value: Amount::from_sat(3).unwrap(), script_pubkey: ScriptBuf::new() },
2828
satisfaction_weight: Weight::ZERO,
2929
};
3030

31-
let target = Amount::from_sat(1_003);
31+
let target = Amount::from_sat(1_003).unwrap();
3232
let mut utxo_pool = vec![one; 1000];
3333
utxo_pool.push(two);
3434

@@ -45,8 +45,8 @@ pub fn criterion_benchmark(c: &mut Criterion) {
4545
assert_eq!(iteration_count, 100000);
4646

4747
assert_eq!(2, inputs.len());
48-
assert_eq!(Amount::from_sat(1_000), inputs[0].value());
49-
assert_eq!(Amount::from_sat(3), inputs[1].value());
48+
assert_eq!(Amount::from_sat(1_000).unwrap(), inputs[0].value());
49+
assert_eq!(Amount::from_sat(3).unwrap(), inputs[1].value());
5050
})
5151
});
5252
}

fuzz/Cargo.toml

+8-8
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ cargo-fuzz = true
1010
[dependencies]
1111
libfuzzer-sys = "0.4"
1212
rand = "0.8.5"
13-
bitcoin = { git = "https://github.com/rust-bitcoin/rust-bitcoin.git", rev = "7df5e7c1bcb4aaf3247f0b76591db9744f03425e", features = ["arbitrary"] }
13+
bitcoin = { git = "https://github.com/rust-bitcoin/rust-bitcoin.git", rev = "52f9c13358c97c358543f3302b325f37ac49392f", features = ["arbitrary"] }
1414
arbitrary = { version = "1", features = ["derive"] }
1515

1616
[dependencies.bitcoin-coin-selection]
@@ -39,10 +39,10 @@ doc = false
3939
bench = false
4040

4141
[patch.crates-io]
42-
bitcoin_hashes = { git = "https://github.com/rust-bitcoin/rust-bitcoin.git", rev = "7df5e7c1bcb4aaf3247f0b76591db9744f03425e" }
43-
base58ck = { git = "https://github.com/rust-bitcoin/rust-bitcoin.git", rev = "7df5e7c1bcb4aaf3247f0b76591db9744f03425e" }
44-
bitcoin-internals = { git = "https://github.com/rust-bitcoin/rust-bitcoin.git", rev = "7df5e7c1bcb4aaf3247f0b76591db9744f03425e" }
45-
bitcoin-io = { git = "https://github.com/rust-bitcoin/rust-bitcoin.git", rev = "7df5e7c1bcb4aaf3247f0b76591db9744f03425e" }
46-
bitcoin-primitives = { git = "https://github.com/rust-bitcoin/rust-bitcoin.git", rev = "7df5e7c1bcb4aaf3247f0b76591db9744f03425e" }
47-
bitcoin-addresses = { git = "https://github.com/rust-bitcoin/rust-bitcoin.git", rev = "7df5e7c1bcb4aaf3247f0b76591db9744f03425e" }
48-
bitcoin-units = { git = "https://github.com/rust-bitcoin/rust-bitcoin.git", rev = "7df5e7c1bcb4aaf3247f0b76591db9744f03425e" }
42+
bitcoin_hashes = { git = "https://github.com/rust-bitcoin/rust-bitcoin.git", rev = "52f9c13358c97c358543f3302b325f37ac49392f" }
43+
base58ck = { git = "https://github.com/rust-bitcoin/rust-bitcoin.git", rev = "52f9c13358c97c358543f3302b325f37ac49392f" }
44+
bitcoin-internals = { git = "https://github.com/rust-bitcoin/rust-bitcoin.git", rev = "52f9c13358c97c358543f3302b325f37ac49392f" }
45+
bitcoin-io = { git = "https://github.com/rust-bitcoin/rust-bitcoin.git", rev = "52f9c13358c97c358543f3302b325f37ac49392f" }
46+
bitcoin-primitives = { git = "https://github.com/rust-bitcoin/rust-bitcoin.git", rev = "52f9c13358c97c358543f3302b325f37ac49392f" }
47+
bitcoin-addresses = { git = "https://github.com/rust-bitcoin/rust-bitcoin.git", rev = "52f9c13358c97c358543f3302b325f37ac49392f" }
48+
bitcoin-units = { git = "https://github.com/rust-bitcoin/rust-bitcoin.git", rev = "52f9c13358c97c358543f3302b325f37ac49392f" }

src/branch_and_bound.rs

+44-52
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
//! This module introduces the Branch and Bound Coin-Selection Algorithm.
66
77
use bitcoin::amount::CheckedSum;
8-
use bitcoin::{Amount, FeeRate, SignedAmount};
8+
use bitcoin::{Amount, FeeRate};
99

1010
use crate::{Return, WeightedUtxo};
1111

@@ -161,18 +161,18 @@ pub fn select_coins_bnb<Utxo: WeightedUtxo>(
161161
let mut index = 0;
162162
let mut backtrack;
163163

164-
let mut value = Amount::ZERO;
164+
let mut value = 0;
165165

166-
let mut current_waste: SignedAmount = SignedAmount::ZERO;
167-
let mut best_waste = SignedAmount::MAX_MONEY;
166+
let mut current_waste: i64 = 0;
167+
let mut best_waste: i64 = Amount::MAX_MONEY.to_sat() as i64;
168168

169169
let mut index_selection: Vec<usize> = vec![];
170170
let mut best_selection: Vec<usize> = vec![];
171171

172172
let upper_bound = target.checked_add(cost_of_change)?;
173173

174174
// Creates a tuple of (effective_value, waste, weighted_utxo)
175-
let mut w_utxos: Vec<(Amount, SignedAmount, &Utxo)> = weighted_utxos
175+
let w_utxos = weighted_utxos
176176
.iter()
177177
// calculate effective_value and waste for each w_utxo.
178178
.map(|wu| (wu.effective_value(fee_rate), wu.waste(fee_rate, long_term_fee_rate), wu))
@@ -183,20 +183,25 @@ pub fn select_coins_bnb<Utxo: WeightedUtxo>(
183183
// filter out all effective_values that are negative.
184184
.filter(|(eff_val, _, _)| eff_val.is_positive())
185185
// all utxo effective_values are now positive (see previous step) - cast to unsigned.
186-
.map(|(eff_val, waste, wu)| (eff_val.to_unsigned().unwrap(), waste, wu))
187-
.collect();
186+
.map(|(eff_val, waste, wu)| (eff_val.to_unsigned().unwrap(), waste, wu));
188187

189-
// descending sort by effective_value using satisfaction weight as tie breaker.
190-
w_utxos.sort_by(|a, b| {
191-
b.0.cmp(&a.0).then(b.2.satisfaction_weight().cmp(&a.2.satisfaction_weight()))
192-
});
193-
194-
let mut available_value = w_utxos.clone().into_iter().map(|(ev, _, _)| ev).checked_sum()?;
188+
let available_value = w_utxos.clone().map(|(ev, _, _)| ev).checked_sum()?;
195189

196190
if available_value < target || target == Amount::ZERO {
197191
return None;
198192
}
199193

194+
let mut available_value = available_value.to_sat();
195+
196+
// cast from Amount/SignedAmount to u64/i64 for more performant operations.
197+
let mut w_utxos: Vec<(u64, i64, &Utxo)> = w_utxos.map(|(e, w, u)| (e.to_sat(), w.to_sat(), u)).collect();
198+
let target = target.to_sat();
199+
200+
// descending sort by effective_value using satisfaction weight as tie breaker.
201+
w_utxos.sort_by(|a, b| {
202+
b.0.cmp(&a.0).then(b.2.satisfaction_weight().cmp(&a.2.satisfaction_weight()))
203+
});
204+
200205
while iteration < ITERATION_LIMIT {
201206
backtrack = false;
202207

@@ -205,7 +210,7 @@ pub fn select_coins_bnb<Utxo: WeightedUtxo>(
205210
// unchecked_add is used here for performance. Before entering the search loop, all
206211
// utxos are summed and checked for overflow. Since there was no overflow then, any
207212
// subset of addition will not overflow.
208-
if available_value.unchecked_add(value) < target
213+
if available_value + value < target
209214
// Provides an upper bound on the excess value that is permissible.
210215
// Since value is lost when we create a change output due to increasing the size of the
211216
// transaction by an output (the change output), we accept solutions that may be
@@ -217,7 +222,7 @@ pub fn select_coins_bnb<Utxo: WeightedUtxo>(
217222
//
218223
// That is, the range includes solutions that exactly equal the target up to but not
219224
// including values greater than target + cost_of_change.
220-
|| value > upper_bound
225+
|| value > upper_bound.to_sat()
221226
// if current_waste > best_waste, then backtrack. However, only backtrack if
222227
// it's high fee_rate environment. During low fee environments, a utxo may
223228
// have negative waste, therefore adding more utxos in such an environment
@@ -231,9 +236,7 @@ pub fn select_coins_bnb<Utxo: WeightedUtxo>(
231236
else if value >= target {
232237
backtrack = true;
233238

234-
let v = value.to_signed().ok()?;
235-
let t = target.to_signed().ok()?;
236-
let waste: SignedAmount = v.checked_sub(t)?;
239+
let waste: i64 = (value as i64).checked_sub(target as i64)?;
237240
current_waste = current_waste.checked_add(waste)?;
238241

239242
// Check if index_selection is better than the previous known best, and
@@ -265,6 +268,7 @@ pub fn select_coins_bnb<Utxo: WeightedUtxo>(
265268
assert_eq!(index, *index_selection.last().unwrap());
266269
let (eff_value, utxo_waste, _) = w_utxos[index];
267270
current_waste = current_waste.checked_sub(utxo_waste)?;
271+
268272
value = value.checked_sub(eff_value)?;
269273
index_selection.pop().unwrap();
270274
}
@@ -275,7 +279,7 @@ pub fn select_coins_bnb<Utxo: WeightedUtxo>(
275279
// unchecked sub is used her for performance.
276280
// The bounds for available_value are at most the sum of utxos
277281
// and at least zero.
278-
available_value = available_value.unchecked_sub(eff_value);
282+
available_value = available_value - eff_value;
279283

280284
// Check if we can omit the currently selected depending on if the last
281285
// was omitted. Therefore, check if index_selection has a previous one.
@@ -290,7 +294,7 @@ pub fn select_coins_bnb<Utxo: WeightedUtxo>(
290294

291295
// unchecked add is used here for performance. Since the sum of all utxo values
292296
// did not overflow, then any positive subset of the sum will not overflow.
293-
value = value.unchecked_add(eff_value);
297+
value = value + eff_value;
294298
}
295299
}
296300

@@ -305,7 +309,7 @@ pub fn select_coins_bnb<Utxo: WeightedUtxo>(
305309
fn index_to_utxo_list<Utxo: WeightedUtxo>(
306310
iterations: u32,
307311
index_list: Vec<usize>,
308-
wu: Vec<(Amount, SignedAmount, &Utxo)>,
312+
wu: Vec<(u64, i64, &Utxo)>,
309313
) -> Return<Utxo> {
310314
let mut result: Vec<_> = Vec::new();
311315
let list = index_list;
@@ -330,7 +334,7 @@ mod tests {
330334
use arbitrary::{Arbitrary, Unstructured};
331335
use arbtest::arbtest;
332336
use bitcoin::transaction::effective_value;
333-
use bitcoin::{Amount, Weight};
337+
use bitcoin::{Amount, Weight, SignedAmount};
334338

335339
use super::*;
336340
use crate::tests::{assert_proptest_bnb, assert_ref_eq, parse_fee_rate, Utxo, UtxoPool};
@@ -392,7 +396,7 @@ mod tests {
392396
// see: https://github.com/rust-fuzz/arbitrary/pull/192
393397
fn arb_amount_in_range(u: &mut Unstructured, r: std::ops::RangeInclusive<u64>) -> Amount {
394398
let u = u.int_in_range::<u64>(r).unwrap();
395-
Amount::from_sat(u)
399+
Amount::from_sat(u).unwrap()
396400
}
397401

398402
// Use in place of arbitrary_in_range()
@@ -634,7 +638,7 @@ mod tests {
634638
cost_of_change: "0",
635639
fee_rate: "0",
636640
lt_fee_rate: "0",
637-
weighted_utxos: &["18446744073709551615 sats/68 vB", "1 sats/68 vB"], // [u64::MAX, 1 sat]
641+
weighted_utxos: &["2100000000000000 sats/68 vB", "1 sats/68 vB"], // [Amount::MAX, 1 sat]
638642
expected_utxos: None,
639643
expected_iterations: 0,
640644
}
@@ -645,7 +649,7 @@ mod tests {
645649
fn select_coins_bnb_upper_bound_overflow() {
646650
TestBnB {
647651
target: "1 sats",
648-
cost_of_change: "18446744073709551615 sats", // u64::MAX
652+
cost_of_change: "2100000000000000 sats", // Amount::MAX
649653
fee_rate: "0",
650654
lt_fee_rate: "0",
651655
weighted_utxos: &["1 sats/68 vB"],
@@ -655,20 +659,6 @@ mod tests {
655659
.assert();
656660
}
657661

658-
#[test]
659-
fn select_coins_bnb_utxo_greater_than_max_money() {
660-
TestBnB {
661-
target: "1 sats",
662-
cost_of_change: "18141417255681066410 sats",
663-
fee_rate: "1 sat/kwu",
664-
lt_fee_rate: "0",
665-
weighted_utxos: &["8740670712339394302 sats/68 vB"],
666-
expected_utxos: None,
667-
expected_iterations: 0,
668-
}
669-
.assert();
670-
}
671-
672662
#[test]
673663
fn select_coins_bnb_effective_value_tie_high_fee_rate() {
674664
// If the fee_rate is expensive prefer lower weight UTXOS
@@ -793,7 +783,7 @@ mod tests {
793783
// Takes 327,661 iterations to find a solution.
794784
let base: usize = 2;
795785
let alpha = (0..17).enumerate().map(|(i, _)| base.pow(17 + i as u32));
796-
let target = Amount::from_sat(alpha.clone().sum::<usize>() as u64);
786+
let target = Amount::from_sat(alpha.clone().sum::<usize>() as u64).unwrap();
797787

798788
let beta = (0..17).enumerate().map(|(i, _)| {
799789
let a = base.pow(17 + i as u32);
@@ -805,7 +795,7 @@ mod tests {
805795
// flatten requires iterable types.
806796
// use once() to make tuple iterable.
807797
.flat_map(|tup| once(tup.0).chain(once(tup.1)))
808-
.map(|a| Amount::from_sat(a as u64))
798+
.map(|a| Amount::from_sat(a as u64).unwrap())
809799
.collect();
810800

811801
let pool: Vec<_> = amts.into_iter().map(|a| Utxo::new(a, Weight::ZERO)).collect();
@@ -826,11 +816,11 @@ mod tests {
826816
vec![a, a + 2]
827817
});
828818

829-
let amts: Vec<_> = vals.map(Amount::from_sat).collect();
819+
let amts: Vec<_> = vals.map(|v| Amount::from_sat(v).unwrap()).collect();
830820
let pool: Vec<_> = amts.into_iter().map(|a| Utxo::new(a, Weight::ZERO)).collect();
831821

832822
let list = select_coins_bnb(
833-
Amount::from_sat(target),
823+
Amount::from_sat(target).unwrap(),
834824
Amount::ONE_SAT,
835825
FeeRate::ZERO,
836826
FeeRate::ZERO,
@@ -852,14 +842,14 @@ mod tests {
852842
vec![a, a + 2]
853843
});
854844

855-
let mut amts: Vec<_> = amts.map(Amount::from_sat).collect();
845+
let mut amts: Vec<_> = amts.map(|v| Amount::from_sat(v).unwrap()).collect();
856846

857847
// Add a value that will match the target before iteration exhaustion occurs.
858-
amts.push(Amount::from_sat(target));
848+
amts.push(Amount::from_sat(target).unwrap());
859849
let pool: Vec<_> = amts.into_iter().map(|a| Utxo::new(a, Weight::ZERO)).collect();
860850

861851
let (iterations, utxos) = select_coins_bnb(
862-
Amount::from_sat(target),
852+
Amount::from_sat(target).unwrap(),
863853
Amount::ONE_SAT,
864854
FeeRate::ZERO,
865855
FeeRate::ZERO,
@@ -868,7 +858,7 @@ mod tests {
868858
.unwrap();
869859

870860
assert_eq!(utxos.len(), 1);
871-
assert_eq!(utxos[0].value(), Amount::from_sat(target));
861+
assert_eq!(utxos[0].value(), Amount::from_sat(target).unwrap());
872862
assert_eq!(100000, iterations);
873863
}
874864

@@ -904,7 +894,7 @@ mod tests {
904894
if let Some(f) = max_fee_rate {
905895
let fee_rate = arb_fee_rate_in_range(u, 1..=f.to_sat_per_kwu());
906896

907-
//TODO update eff value interface
897+
// TODO update eff value interface
908898
let target_effective_value =
909899
effective_value(fee_rate, utxo.satisfaction_weight(), utxo.value()).unwrap();
910900

@@ -918,7 +908,8 @@ mod tests {
918908
effective_value(fee_rate, u.satisfaction_weight(), u.value())
919909
.unwrap()
920910
})
921-
.sum();
911+
.checked_sum()
912+
.unwrap();
922913
let amount_sum = sum.to_unsigned().unwrap();
923914
assert_eq!(amount_sum, target);
924915

@@ -998,7 +989,8 @@ mod tests {
998989
.to_unsigned()
999990
.unwrap()
1000991
})
1001-
.sum();
992+
.checked_sum()
993+
.unwrap();
1002994
assert_eq!(effective_value_sum, target);
1003995

1004996
// TODO checked_add not available in Weight
@@ -1039,9 +1031,9 @@ mod tests {
10391031

10401032
let result = select_coins_bnb(target, cost_of_change, fee_rate, lt_fee_rate, &utxos);
10411033

1042-
assert_proptest_bnb(target, cost_of_change, fee_rate, pool, result);
1034+
assert_proptest_bnb(target, cost_of_change, fee_rate, lt_fee_rate, pool, result);
10431035

10441036
Ok(())
1045-
});
1037+
}).seed(0xcde68a8900000060);
10461038
}
10471039
}

0 commit comments

Comments
 (0)