@@ -111,13 +111,19 @@ impl<'a> CoinSelector<'a> {
111111 self . selected . contains ( index)
112112 }
113113
114- /// Is meeting this `target` possible with the current selection with this `drain` ( i.e. change output).
115- /// Note this will respect [`ban`]ned candidates.
114+ /// Whether the candidates can cover this `target`'s **value** (net of input fees) — i.e. whether
115+ /// enough value is reachable for [`is_target_met`] to hold. Respects [`ban`]ned candidates.
116116 ///
117- /// This simply selects all effective inputs at the target's feerate and checks whether we have
118- /// enough value.
117+ /// Selecting *all* effective inputs maximizes the value available, so if that can't meet the
118+ /// target value, nothing can. Monotone, hence exact.
119+ ///
120+ /// NOTE: this does **not** account for [`Target::max_weight`] — a `true` result can still be
121+ /// infeasible under the weight cap. Use [`select_until_target_met`] or branch and bound (both of
122+ /// which enforce the cap) to actually build a selection.
119123 ///
120124 /// [`ban`]: Self::ban
125+ /// [`is_target_met`]: Self::is_target_met
126+ /// [`select_until_target_met`]: Self::select_until_target_met
121127 pub fn is_selection_possible ( & self , target : Target ) -> bool {
122128 let mut test = self . clone ( ) ;
123129 test. select_all_effective ( target. fee . rate ) ;
@@ -429,18 +435,36 @@ impl<'a> CoinSelector<'a> {
429435 self . unselected_indices ( ) . next ( ) . is_none ( )
430436 }
431437
432- /// Whether the constraints of `Target` have been met if we include a specific `drain` ouput .
438+ /// Whether the tx implied by the current selection and `drain` is within [`Target::max_weight`] .
433439 ///
434- /// Note if [`is_target_met`] is true and the `drain` is produced from the [`drain`] method then
435- /// this method will also always be true.
440+ /// Always `true` when `max_weight` is `None`. Note this is the *anti-monotone* half of
441+ /// feasibility (adding inputs adds weight), so it is kept separate from the monotone
442+ /// value-only [`is_target_met`](Self::is_target_met).
443+ pub fn is_within_max_weight ( & self , target : Target , drain : Drain ) -> bool {
444+ match target. max_weight {
445+ Some ( max_weight) => self . weight ( target. outputs , drain. weights ) <= max_weight,
446+ None => true ,
447+ }
448+ }
449+
450+ /// Whether the selection covers the target value (i.e. [`excess`](Self::excess) is
451+ /// non-negative), ignoring [`Target::max_weight`].
436452 ///
437- /// [`is_target_met`]: Self::is_target_met
438- /// [`drain`]: Self::drain
453+ /// This is **monotone**: selecting more never un-meets it. It deliberately does *not* include
454+ /// the weight cap — see [`is_within_max_weight`]( Self::is_within_max_weight).
439455 pub fn is_target_met_with_drain ( & self , target : Target , drain : Drain ) -> bool {
440456 self . excess ( target, drain) >= 0
441457 }
442458
443- /// Whether the constraints of `Target` have been met.
459+ /// Whether the selection covers the target **value** (net of input fees), i.e. [`excess`] is
460+ /// non-negative. **Monotone** (selecting more never un-meets it), and it deliberately does
461+ /// *not* check [`Target::max_weight`] — that is the separate, anti-monotone
462+ /// [`is_within_max_weight`]. See [`is_target_met_with_drain`] for the version that
463+ /// accounts for a specific `drain`.
464+ ///
465+ /// [`excess`]: Self::excess
466+ /// [`is_within_max_weight`]: Self::is_within_max_weight
467+ /// [`is_target_met_with_drain`]: Self::is_target_met_with_drain
444468 pub fn is_target_met ( & self , target : Target ) -> bool {
445469 self . is_target_met_with_drain ( target, Drain :: NONE )
446470 }
@@ -521,14 +545,25 @@ impl<'a> CoinSelector<'a> {
521545 }
522546 }
523547
524- /// Select candidates until `target` has been met assuming the `drain` output is attached.
548+ /// Select candidates until `target` has been met.
549+ ///
550+ /// # Errors
525551 ///
526- /// Returns an error if the target was unable to be met.
527- pub fn select_until_target_met ( & mut self , target : Target ) -> Result < ( ) , InsufficientFunds > {
552+ /// - [`SelectError::InsufficientFunds`] if the candidates can't cover the target value.
553+ /// - [`SelectError::MaxWeightExceeded`] if the value is met but the resulting selection exceeds
554+ /// [`Target::max_weight`]. Note this only reflects *this* in-order greedy selection; a
555+ /// different selection might still fit the cap (use branch and bound to search for one).
556+ pub fn select_until_target_met ( & mut self , target : Target ) -> Result < ( ) , SelectError > {
528557 self . select_until ( |cs| cs. is_target_met ( target) )
529- . ok_or_else ( || InsufficientFunds {
530- missing : self . excess ( target, Drain :: NONE ) . unsigned_abs ( ) ,
531- } )
558+ . ok_or_else ( || {
559+ SelectError :: InsufficientFunds ( InsufficientFunds {
560+ missing : self . excess ( target, Drain :: NONE ) . unsigned_abs ( ) ,
561+ } )
562+ } ) ?;
563+ if !self . is_within_max_weight ( target, Drain :: NONE ) {
564+ return Err ( SelectError :: MaxWeightExceeded ) ;
565+ }
566+ Ok ( ( ) )
532567 }
533568
534569 /// Select candidates until some predicate has been satisfied.
@@ -663,6 +698,38 @@ impl core::fmt::Display for InsufficientFunds {
663698#[ cfg( feature = "std" ) ]
664699impl std:: error:: Error for InsufficientFunds { }
665700
701+ /// Error returned by [`CoinSelector::select_until_target_met`].
702+ #[ derive( Clone , Debug , Copy , PartialEq , Eq ) ]
703+ pub enum SelectError {
704+ /// The candidates can't cover the target value.
705+ InsufficientFunds ( InsufficientFunds ) ,
706+ /// The value target is met, but the resulting selection exceeds [`Target::max_weight`].
707+ MaxWeightExceeded ,
708+ }
709+
710+ impl From < InsufficientFunds > for SelectError {
711+ fn from ( e : InsufficientFunds ) -> Self {
712+ SelectError :: InsufficientFunds ( e)
713+ }
714+ }
715+
716+ impl core:: fmt:: Display for SelectError {
717+ fn fmt ( & self , f : & mut core:: fmt:: Formatter ) -> core:: fmt:: Result {
718+ match self {
719+ SelectError :: InsufficientFunds ( e) => write ! ( f, "{}" , e) ,
720+ SelectError :: MaxWeightExceeded => {
721+ write ! (
722+ f,
723+ "Selection meets the target value but exceeds `max_weight`."
724+ )
725+ }
726+ }
727+ }
728+ }
729+
730+ #[ cfg( feature = "std" ) ]
731+ impl std:: error:: Error for SelectError { }
732+
666733/// Error type for when a solution cannot be found by branch-and-bound.
667734#[ derive( Debug , Clone , Copy , PartialEq , Eq ) ]
668735pub struct NoBnbSolution {
0 commit comments