Skip to content

Commit e2eb208

Browse files
committed
feat(coin_select): add min_value_and_waste change policy
This is a better default policy as it minimizes waste without introducing a change output with a dust value.
1 parent eb079d5 commit e2eb208

File tree

2 files changed

+41
-12
lines changed

2 files changed

+41
-12
lines changed

example-crates/example_cli/src/lib.rs

+10-12
Original file line numberDiff line numberDiff line change
@@ -466,7 +466,7 @@ where
466466
};
467467

468468
let target = bdk_coin_select::Target {
469-
feerate: bdk_coin_select::FeeRate::from_sat_per_vb(1.0),
469+
feerate: bdk_coin_select::FeeRate::from_sat_per_vb(5.0),
470470
min_fee: 0,
471471
value: transaction.output.iter().map(|txo| txo.value).sum(),
472472
};
@@ -489,8 +489,12 @@ where
489489
value: 0,
490490
spend_weight: change_plan.expected_weight() as u32,
491491
};
492-
let long_term_feerate = bdk_coin_select::FeeRate::from_sat_per_wu(0.25);
493-
let drain_policy = bdk_coin_select::change_policy::min_waste(drain, long_term_feerate);
492+
let long_term_feerate = bdk_coin_select::FeeRate::from_sat_per_vb(1.0);
493+
let drain_policy = bdk_coin_select::change_policy::min_value_and_waste(
494+
drain,
495+
change_script.dust_value().to_sat(),
496+
long_term_feerate,
497+
);
494498

495499
let mut selector = CoinSelector::new(&candidates, transaction.weight().to_wu() as u32);
496500
match cs_algorithm {
@@ -503,16 +507,10 @@ where
503507
let (final_selection, _score) = selector
504508
.branch_and_bound(metric)
505509
.take(50_000)
506-
// we only process viable solutions
510+
// skip exclusion branches (as they are not scored)
507511
.flatten()
508-
.reduce(|(best_sol, best_score), (curr_sol, curr_score)| {
509-
// we are reducing waste
510-
if curr_score < best_score {
511-
(curr_sol, curr_score)
512-
} else {
513-
(best_sol, best_score)
514-
}
515-
})
512+
// the last result is always the best score
513+
.last()
516514
.ok_or(anyhow::format_err!("no bnb solution found"))?;
517515
selector = final_selection;
518516
}

nursery/coin_select/src/change_policy.rs

+31
Original file line numberDiff line numberDiff line change
@@ -51,3 +51,34 @@ pub fn min_waste(
5151
}
5252
}
5353
}
54+
55+
/// Add a change output if the change value is greater than or equal to `min_value` and if it would
56+
/// reduce the overall waste of the transaction.
57+
///
58+
/// Note that the value field of the `drain` is ignored. [`Drain`] is just used for the drain weight
59+
/// and drain spend weight.
60+
pub fn min_value_and_waste(
61+
mut drain: Drain,
62+
min_value: u64,
63+
long_term_feerate: FeeRate,
64+
) -> impl Fn(&CoinSelector, Target) -> Drain {
65+
debug_assert!(drain.is_some());
66+
drain.value = 0;
67+
68+
let min_value: i64 = min_value
69+
.try_into()
70+
.expect("dust_value is ridiculously large");
71+
72+
move |cs, target| {
73+
let excess = cs.excess(target, drain);
74+
if excess >= min_value
75+
&& excess > drain.waste(target.feerate, long_term_feerate).ceil() as i64
76+
{
77+
let mut drain = drain;
78+
drain.value = cs.excess(target, drain).try_into().expect("excess must be positive because excess is checked to be greater than waste and min_value");
79+
drain
80+
} else {
81+
Drain::none()
82+
}
83+
}
84+
}

0 commit comments

Comments
 (0)