Skip to content

Commit 938881e

Browse files
committed
port 236
1 parent 1cb3de6 commit 938881e

2 files changed

Lines changed: 34 additions & 73 deletions

File tree

src/lib.rs

Lines changed: 24 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,9 @@ use crate::errors::{OverflowError, SelectionError};
3434
#[cfg_attr(docsrs, doc(cfg(feature = "rand")))]
3535
pub use crate::single_random_draw::single_random_draw;
3636

37-
pub(crate) type Return<'a> = Result<(u32, Vec<&'a WeightedUtxo>), SelectionError>;
37+
use bitcoin_units::NumOpResult;
3838

39-
// https://github.com/bitcoin/bitcoin/blob/f722a9bd132222d9d5cd503b5af25c905b205cdb/src/wallet/coinselection.h#L20
40-
const CHANGE_LOWER: Amount = Amount::from_sat_u32(50_000);
39+
pub(crate) type Return<'a> = Result<(u32, Vec<&'a WeightedUtxo>), SelectionError>;
4140

4241
/// Computes the value of an output accounting for the cost to spend it.
4342
///
@@ -251,7 +250,7 @@ mod tests {
251250
#[test]
252251
fn select_coins_no_solution() {
253252
// Test the code branch where both SRD and BnB fail.
254-
let target = Amount::from_sat_u32(255432);
253+
let target = Amount::from_sat_u32(262644);
255254
let cost_of_change = Amount::ZERO;
256255
let max_weight = Weight::from_wu(40_000);
257256
let pool = build_pool(); // eff value sum 262643
@@ -264,23 +263,6 @@ mod tests {
264263
}
265264
}
266265

267-
#[test]
268-
fn select_coins_srd_solution() {
269-
let target = (Amount::from_sat_u32(255432) - CHANGE_LOWER).unwrap();
270-
let cost_of_change = Amount::ZERO;
271-
let max_weight = Weight::from_wu(40_000);
272-
let pool = build_pool();
273-
274-
let result = select_coins(target, cost_of_change, max_weight, &pool);
275-
let (_iterations, utxos) = result.unwrap();
276-
let sum: Amount = utxos
277-
.into_iter()
278-
.map(|u| u.value())
279-
.try_fold(Amount::ZERO, Amount::checked_add)
280-
.unwrap();
281-
assert!(sum > target);
282-
}
283-
284266
#[test]
285267
fn select_coins_bnb_solution() {
286268
let target = Amount::from_sat_u32(255432);
@@ -305,6 +287,25 @@ mod tests {
305287
assert_eq!(16, iterations);
306288
}
307289

290+
#[test]
291+
fn select_coins_srd_solution() {
292+
let fee_rate = FeeRate::from_sat_per_vb(10);
293+
let target = Amount::from_sat_u32(50_000);
294+
let utxo_amt = Amount::from_sat_u32(100_000);
295+
let weight = Weight::from_wu(230); // TR output size
296+
let w_utxo = WeightedUtxo::new(utxo_amt, weight, fee_rate, fee_rate).unwrap();
297+
let utxo_pool = vec![w_utxo];
298+
let cost_of_change = Amount::from_sat_u32(678);
299+
300+
let (iterations, utxos) =
301+
select_coins(target, cost_of_change, Weight::MAX, &utxo_pool).unwrap();
302+
303+
let sum: Amount = utxos.into_iter().map(|u| u.value()).map(NumOpResult::from).sum::<NumOpResult<Amount>>().unwrap();
304+
305+
assert!(sum > target);
306+
assert_eq!(1, iterations);
307+
}
308+
308309
#[test]
309310
fn select_coins_proptest() {
310311
arbtest(|u| {
@@ -323,15 +324,13 @@ mod tests {
323324
}
324325
Err(InsufficentFunds) => {
325326
let available_value = candidate_selection.available_value().unwrap();
326-
assert!(available_value < (target + CHANGE_LOWER).unwrap());
327+
assert!(available_value < target);
327328
}
328329
Err(Overflow(_)) => {
329330
let available_value = candidate_selection.available_value();
330331
let weight_total = candidate_selection.weight_total();
331332
assert!(
332-
available_value.is_none()
333-
|| weight_total.is_none()
334-
|| target.checked_add(CHANGE_LOWER).is_none()
333+
available_value.is_none() || weight_total.is_none()
335334
);
336335
}
337336
Err(ProgramError) => panic!("un-expected program error"),

src/single_random_draw.rs

Lines changed: 10 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use rand::seq::SliceRandom;
1313

1414
use crate::OverflowError::Addition;
1515
use crate::SelectionError::{InsufficentFunds, MaxWeightExceeded, Overflow, ProgramError};
16-
use crate::{Return, WeightedUtxo, CHANGE_LOWER};
16+
use crate::{Return, WeightedUtxo};
1717

1818
/// Select coins by Single Random Draw (SRD).
1919
///
@@ -59,8 +59,7 @@ pub fn single_random_draw<
5959
.try_fold(Amount::ZERO, Amount::checked_add)
6060
.ok_or(Overflow(Addition))?;
6161

62-
let threshold = target.checked_add(CHANGE_LOWER).ok_or(Overflow(Addition))?;
63-
if available_value < threshold {
62+
if available_value < target {
6463
return Err(InsufficentFunds);
6564
}
6665

@@ -69,7 +68,6 @@ pub fn single_random_draw<
6968
let mut heap: BinaryHeap<&WeightedUtxo> = BinaryHeap::new();
7069

7170
let mut value = Amount::ZERO;
72-
7371
let mut iteration = 0;
7472
let mut max_tx_weight_exceeded = false;
7573
let mut weight_total = Weight::ZERO;
@@ -93,7 +91,7 @@ pub fn single_random_draw<
9391
};
9492
}
9593

96-
if value >= threshold {
94+
if value >= target {
9795
let result: Vec<_> = heap.into_sorted_vec();
9896
return Ok((iteration, result));
9997
}
@@ -233,55 +231,21 @@ mod tests {
233231
.assert();
234232
}
235233

236-
#[test]
237-
fn select_coins_srd_change_output_too_small() {
238-
// The resulting change must be greater than CHANGE_LOWER
239-
// therefore, an exact match will fail.
240-
TestSRD {
241-
target: "3 cBTC",
242-
fee_rate: "10 sat/kwu",
243-
max_weight: "40000 wu",
244-
weighted_utxos: &["e(1 cBTC)/68 vB", "e(2 cBTC)/68 vB"],
245-
expected_utxos: &[],
246-
expected_error: Some(InsufficentFunds),
247-
expected_iterations: 0,
248-
}
249-
.assert();
250-
}
251-
252234
#[test]
253235
fn select_coins_srd_with_high_fee() {
254-
// Although the first selected UTXO valued at 2050000 meets the
255-
// target and meets the threshold of target + CHANGE, the value
256-
// is not enough since when the effective value is calculated,
257-
// it falls bellow the threshold. Therefore multiple UTXOs are
258-
// selected.
236+
// Both UTXOs are selected since neither has enough effective_value individually
259237
TestSRD {
260238
target: "2 cBTC",
261239
fee_rate: "10 sat/kwu",
262240
max_weight: "40000 wu",
263-
weighted_utxos: &["1 cBTC/68 vB", "2050000 sats/68 vB"],
264-
expected_utxos: &["2050000 sats/68 vB", "1 cBTC/68 vB"],
241+
weighted_utxos: &["1 cBTC/68 vB", "2 cBTC/68 vB"],
242+
expected_utxos: &["2 cBTC/68 vB", "1 cBTC/68 vB"],
265243
expected_error: None,
266244
expected_iterations: 2,
267245
}
268246
.assert();
269247
}
270248

271-
#[test]
272-
fn select_coins_srd_threshold_overflow() {
273-
TestSRD {
274-
target: "2100000000000000 sat", // Amount::MAX
275-
fee_rate: "10 sat/kwu",
276-
max_weight: "40000 wu",
277-
weighted_utxos: &["1 cBTC/68 vB"],
278-
expected_utxos: &[],
279-
expected_error: Some(Overflow(Addition)),
280-
expected_iterations: 0,
281-
}
282-
.assert();
283-
}
284-
285249
#[test]
286250
fn select_coins_srd_utxo_pool_sum_overflow() {
287251
TestSRD {
@@ -328,7 +292,7 @@ mod tests {
328292
#[test]
329293
fn select_coins_srd_max_weight_eff_value() {
330294
TestSRD {
331-
target: "10000 sats", //threshold = 60k sats
295+
target: "60000 sats",
332296
fee_rate: "10 sat/kwu",
333297
max_weight: "1000 wu",
334298
// after rand: [30k sats/500 wu, 29,999 sats/700 wu, 30k sats/500 wu]
@@ -347,7 +311,7 @@ mod tests {
347311
#[test]
348312
fn select_coins_srd_max_weight_eff_value_tie() {
349313
TestSRD {
350-
target: "10000 sats", // threshold = 60k sats
314+
target: "60000 sats",
351315
fee_rate: "10 sat/kwu",
352316
max_weight: "1000 wu",
353317
// after rand: [30k sats/500 wu, 30k sats/700 wu, 30k sats/500 wu]
@@ -381,7 +345,7 @@ mod tests {
381345
}
382346
Err(InsufficentFunds) => {
383347
let available_value = candidate.available_value().unwrap();
384-
assert!(available_value < (target + CHANGE_LOWER).unwrap());
348+
assert!(available_value < target);
385349
}
386350
Err(crate::SelectionError::IterationLimitReached) => panic!("un-expected result"),
387351
Err(MaxWeightExceeded) => {
@@ -392,9 +356,7 @@ mod tests {
392356
let available_value = candidate.available_value();
393357
let weight_total = candidate.weight_total();
394358
assert!(
395-
available_value.is_none()
396-
|| weight_total.is_none()
397-
|| target.checked_add(CHANGE_LOWER).is_none()
359+
available_value.is_none() || weight_total.is_none()
398360
);
399361
}
400362
Err(ProgramError) => panic!("un-expected program error"),

0 commit comments

Comments
 (0)