@@ -3,8 +3,8 @@ use alloc::vec::Vec;
33use core:: fmt;
44
55use crate :: {
6- collections:: HashMap , input:: CoinbaseMismatch , CPFPError , CPFPSet , FromPsbtInputError , Input ,
7- RbfSet , TxStatus ,
6+ collections:: HashMap , input:: CoinbaseMismatch , CPFPError , CPFPSet ,
7+ FromPsbtInputError , Input , RbfSet , TxStatus ,
88} ;
99use bdk_chain:: TxGraph ;
1010use bitcoin:: { psbt, Amount , OutPoint , Sequence , Transaction , TxOut , Txid , Weight } ;
@@ -21,6 +21,22 @@ pub struct CanonicalUnspents {
2121 spends : HashMap < OutPoint , Txid > ,
2222}
2323
24+ fn select_upper_middle_output ( candidates : & [ ( OutPoint , & TxOut ) ] ) -> OutPoint {
25+ let len = candidates. len ( ) ;
26+
27+ let index = match len {
28+ 1 ..=2 => len - 1 , // select the largest for small sets
29+ 3 ..=5 => len - 2 , // select second largest for medium sets
30+ _ => {
31+ let upper_third_start = ( len * 2 ) / 3 ;
32+ let upper_third_end = len - 1 ;
33+ ( upper_third_start + upper_third_end) / 2
34+ }
35+ } ;
36+
37+ candidates[ index] . 0
38+ }
39+
2440impl CanonicalUnspents {
2541 /// Construct [`CanonicalUnspents`] from an iterator of txs with confirmation status.
2642 pub fn new < T > ( canonical_txs : impl IntoIterator < Item = TxWithStatus < T > > ) -> Self
@@ -128,11 +144,15 @@ impl CanonicalUnspents {
128144 }
129145
130146 /// Constructs a '[CPFPSet]' from a set of parent transaction IDs.
131- pub fn build_cpfp_set_from_txids (
147+ pub fn build_cpfp_set_from_txids < F > (
132148 & self ,
133149 parent_txids : impl IntoIterator < Item = Txid > ,
134150 graph : & TxGraph ,
135- ) -> Result < CPFPSet , CPFPError > {
151+ ownership_check : F ,
152+ ) -> Result < CPFPSet , CPFPError >
153+ where
154+ F : Fn ( OutPoint ) -> bool + Clone ,
155+ {
136156 let parent_txids: Vec < Txid > = parent_txids. into_iter ( ) . collect ( ) ;
137157
138158 const MAX_ANCESTORS : usize = 25 ;
@@ -148,8 +168,9 @@ impl CanonicalUnspents {
148168 weight += tx. weight ( ) ;
149169 fee += graph. calculate_fee ( tx) ?;
150170
151- let base_outpoint = self . select_largest_unspent_output ( tx. clone ( ) ) ?;
152- outpoints. push ( base_outpoint) ;
171+ let selected_outpoint =
172+ self . select_owned_unspent_output ( tx. clone ( ) , ownership_check. clone ( ) ) ?;
173+ outpoints. push ( selected_outpoint) ;
153174
154175 Ok ( ( weight, fee, outpoints) )
155176 } ,
@@ -158,13 +179,17 @@ impl CanonicalUnspents {
158179 Ok ( CPFPSet :: new ( parent_fee, parent_weight, selected_outpoints) )
159180 }
160181
161- /// Selects the largest unspent output from a given transaction
162- pub fn select_largest_unspent_output (
182+ /// Selects the unspent output from a given transaction
183+ pub fn select_owned_unspent_output < F > (
163184 & self ,
164185 tx : Arc < Transaction > ,
165- ) -> Result < OutPoint , CPFPError > {
186+ ownership_check : F ,
187+ ) -> Result < OutPoint , CPFPError >
188+ where
189+ F : Fn ( OutPoint ) -> bool ,
190+ {
166191 let txid = tx. compute_txid ( ) ;
167- let outpoint = tx
192+ let mut candidates : Vec < _ > = tx
168193 . output
169194 . iter ( )
170195 . enumerate ( )
@@ -177,12 +202,22 @@ impl CanonicalUnspents {
177202 txout,
178203 )
179204 } )
180- . filter ( |( op, _) | self . is_unspent ( * op) )
181- . max_by_key ( |( _, txout) | txout. value )
182- . map ( |( op, _) | op)
183- . ok_or ( CPFPError :: NoUnspentOutput ( txid) ) ?;
205+ . filter ( |( op, tx_out) | {
206+ // Must be unspent, owned and above dust threshold
207+ self . is_unspent ( * op)
208+ && ownership_check ( * op)
209+ && tx_out. value >= tx_out. script_pubkey . minimal_non_dust ( )
210+ } )
211+ . collect ( ) ;
212+
213+ if candidates. is_empty ( ) {
214+ return Err ( CPFPError :: NoUnspentOutput ( txid) ) ;
215+ }
216+
217+ candidates. sort_by_key ( |( _, txout) | txout. value ) ;
218+ let selected_outpoint = select_upper_middle_output ( & candidates) ;
184219
185- Ok ( outpoint )
220+ Ok ( selected_outpoint )
186221 }
187222
188223 /// Whether outpoint is a leaf (unspent).
0 commit comments