@@ -1618,60 +1618,54 @@ impl Wallet {
16181618 . map_err ( |_| BuildFeeBumpError :: FeeRateUnavailable ) ?;
16191619
16201620 // remove the inputs from the tx and process them
1621- let original_txin = tx. input . drain ( ..) . collect :: < Vec < _ > > ( ) ;
1622- let original_utxos = original_txin
1623- . iter ( )
1624- . map ( |txin| -> Result < _ , BuildFeeBumpError > {
1625- let prev_tx = graph
1626- . get_tx ( txin. previous_output . txid )
1627- . ok_or ( BuildFeeBumpError :: UnknownUtxo ( txin. previous_output ) ) ?;
1628- let txout = & prev_tx. output [ txin. previous_output . vout as usize ] ;
1629-
1630- let chain_position = chain_positions
1631- . get ( & txin. previous_output . txid )
1632- . cloned ( )
1633- . ok_or ( BuildFeeBumpError :: UnknownUtxo ( txin. previous_output ) ) ?;
1634-
1635- let weighted_utxo = match txout_index. index_of_spk ( txout. script_pubkey . clone ( ) ) {
1636- Some ( & ( keychain, derivation_index) ) => {
1637- let satisfaction_weight = self
1638- . public_descriptor ( keychain)
1639- . max_weight_to_satisfy ( )
1640- . unwrap ( ) ;
1641- WeightedUtxo {
1642- utxo : Utxo :: Local ( LocalOutput {
1643- outpoint : txin. previous_output ,
1644- txout : txout. clone ( ) ,
1645- keychain,
1646- is_spent : true ,
1647- derivation_index,
1648- chain_position,
1649- } ) ,
1650- satisfaction_weight,
1651- }
1652- }
1653- None => {
1654- let satisfaction_weight = Weight :: from_wu_usize (
1655- serialize ( & txin. script_sig ) . len ( ) * 4 + serialize ( & txin. witness ) . len ( ) ,
1656- ) ;
1657- WeightedUtxo {
1658- utxo : Utxo :: Foreign {
1659- outpoint : txin. previous_output ,
1660- sequence : txin. sequence ,
1661- psbt_input : Box :: new ( psbt:: Input {
1662- witness_utxo : Some ( txout. clone ( ) ) ,
1663- non_witness_utxo : Some ( prev_tx. as_ref ( ) . clone ( ) ) ,
1664- ..Default :: default ( )
1665- } ) ,
1666- } ,
1667- satisfaction_weight,
1621+ let ( local, foreign) : ( HashSet < LocalOutput > , Vec < WeightedUtxo > ) =
1622+ tx. input . drain ( ..) . try_fold (
1623+ ( HashSet :: < LocalOutput > :: new ( ) , Vec :: < WeightedUtxo > :: new ( ) ) ,
1624+ |( mut local, mut foreign) , txin| -> Result < _ , BuildFeeBumpError > {
1625+ let prev_tx = graph
1626+ . get_tx ( txin. previous_output . txid )
1627+ . ok_or ( BuildFeeBumpError :: UnknownUtxo ( txin. previous_output ) ) ?;
1628+ let txout = & prev_tx. output [ txin. previous_output . vout as usize ] ;
1629+
1630+ let chain_position =
1631+ chain_positions
1632+ . get ( & txin. previous_output . txid )
1633+ . cloned ( )
1634+ . ok_or ( BuildFeeBumpError :: UnknownUtxo ( txin. previous_output ) ) ?;
1635+
1636+ match txout_index. index_of_spk ( txout. script_pubkey . clone ( ) ) {
1637+ Some ( & ( keychain, derivation_index) ) => local. insert ( LocalOutput {
1638+ outpoint : txin. previous_output ,
1639+ txout : txout. clone ( ) ,
1640+ keychain,
1641+ is_spent : true ,
1642+ derivation_index,
1643+ chain_position,
1644+ } ) ,
1645+ None => {
1646+ let satisfaction_weight = Weight :: from_wu_usize (
1647+ serialize ( & txin. script_sig ) . len ( ) * 4
1648+ + serialize ( & txin. witness ) . len ( ) ,
1649+ ) ;
1650+ foreign. push ( WeightedUtxo {
1651+ utxo : Utxo :: Foreign {
1652+ outpoint : txin. previous_output ,
1653+ sequence : txin. sequence ,
1654+ psbt_input : Box :: new ( psbt:: Input {
1655+ witness_utxo : Some ( txout. clone ( ) ) ,
1656+ non_witness_utxo : Some ( prev_tx. as_ref ( ) . clone ( ) ) ,
1657+ ..Default :: default ( )
1658+ } ) ,
1659+ } ,
1660+ satisfaction_weight,
1661+ } ) ;
1662+ true
16681663 }
1669- }
1670- } ;
1664+ } ;
16711665
1672- Ok ( weighted_utxo )
1673- } )
1674- . collect :: < Result < Vec < _ > , _ > > ( ) ?;
1666+ Ok ( ( local , foreign ) )
1667+ } ,
1668+ ) ?;
16751669
16761670 if tx. output . len ( ) > 1 {
16771671 let mut change_index = None ;
@@ -1698,7 +1692,8 @@ impl Wallet {
16981692 . into_iter ( )
16991693 . map ( |txout| ( txout. script_pubkey , txout. value ) )
17001694 . collect ( ) ,
1701- utxos : original_utxos,
1695+ utxos : local,
1696+ foreign_utxos : foreign,
17021697 bumping_fee : Some ( tx_builder:: PreviousFee {
17031698 absolute : fee,
17041699 rate : fee_rate,
@@ -1976,116 +1971,85 @@ impl Wallet {
19761971 descriptor. at_derivation_index ( child) . ok ( )
19771972 }
19781973
1979- fn get_available_utxos ( & self ) -> Vec < ( LocalOutput , Weight ) > {
1980- self . list_unspent ( )
1981- . map ( |utxo| {
1982- let keychain = utxo. keychain ;
1983- ( utxo, {
1984- self . public_descriptor ( keychain)
1985- . max_weight_to_satisfy ( )
1986- . unwrap ( )
1987- } )
1988- } )
1989- . collect ( )
1990- }
1991-
19921974 /// Given the options returns the list of utxos that must be used to form the
19931975 /// transaction and any further that may be used if needed.
19941976 fn preselect_utxos (
19951977 & self ,
19961978 params : & TxParams ,
19971979 current_height : Option < u32 > ,
19981980 ) -> ( Vec < WeightedUtxo > , Vec < WeightedUtxo > ) {
1999- let TxParams {
2000- change_policy,
2001- unspendable,
2002- utxos,
2003- drain_wallet,
2004- manually_selected_only,
2005- bumping_fee,
2006- ..
2007- } = params;
2008-
2009- let manually_selected = utxos. clone ( ) ;
2010- // we mandate confirmed transactions if we're bumping the fee
2011- let must_only_use_confirmed_tx = bumping_fee. is_some ( ) ;
2012- let must_use_all_available = * drain_wallet;
2013-
2014- // must_spend <- manually selected utxos
2015- // may_spend <- all other available utxos
2016- let mut may_spend = self . get_available_utxos ( ) ;
2017-
2018- may_spend. retain ( |may_spend| {
2019- !manually_selected
2020- . iter ( )
2021- . any ( |manually_selected| manually_selected. utxo . outpoint ( ) == may_spend. 0 . outpoint )
2022- } ) ;
2023- let mut must_spend = manually_selected;
2024-
2025- // NOTE: we are intentionally ignoring `unspendable` here. i.e manual
2026- // selection overrides unspendable.
2027- if * manually_selected_only {
2028- return ( must_spend, vec ! [ ] ) ;
2029- }
2030-
2031- let satisfies_confirmed = may_spend
2032- . iter ( )
2033- . map ( |u| -> bool {
2034- let txid = u. 0 . outpoint . txid ;
2035- let tx = match self . indexed_graph . graph ( ) . get_tx ( txid) {
2036- Some ( tx) => tx,
2037- None => return false ,
2038- } ;
2039-
2040- // Whether the UTXO is mature and, if needed, confirmed
2041- let mut spendable = true ;
2042- let chain_position = u. 0 . chain_position ;
2043- if must_only_use_confirmed_tx && !chain_position. is_confirmed ( ) {
2044- return false ;
2045- }
2046- if tx. is_coinbase ( ) {
2047- debug_assert ! (
2048- chain_position. is_confirmed( ) ,
2049- "coinbase must always be confirmed"
2050- ) ;
2051- if let Some ( current_height) = current_height {
2052- match chain_position {
2053- ChainPosition :: Confirmed { anchor, .. } => {
2054- // https://github.com/bitcoin/bitcoin/blob/c5e67be03bb06a5d7885c55db1f016fbf2333fe3/src/validation.cpp#L373-L375
2055- spendable &= ( current_height
2056- . saturating_sub ( anchor. block_id . height ) )
2057- >= COINBASE_MATURITY ;
2058- }
2059- ChainPosition :: Unconfirmed { .. } => spendable = false ,
2060- }
2061- }
1981+ self
1982+ // get all unspent UTxOs from wallet
1983+ . list_unspent ( )
1984+ // only process UTxOs not selected manually, they will be considered later in the chain
1985+ // NOTE: this avoid UTxOs in both required and optional list
1986+ . filter ( |may_spend| !params. utxos . contains ( may_spend) )
1987+ // only process optional UTxOs if manually_selected_only is false
1988+ . take_while ( |_| !params. manually_selected_only )
1989+ // only add to optional UTxOs those which satisfy the change policy if we reuse change
1990+ . filter ( |local_output| {
1991+ self . keychains ( ) . count ( ) == 1 || params. change_policy . is_satisfied_by ( local_output)
1992+ } )
1993+ // only add to optional UTxOs those marked as spendable
1994+ . filter ( |local_output| !params. unspendable . contains ( & local_output. outpoint ) )
1995+ // if bumping fees only add to optional UTxOs those confirmed
1996+ . filter ( |local_output| {
1997+ params. bumping_fee . is_none ( ) || local_output. chain_position . is_confirmed ( )
1998+ } )
1999+ // only use UTxOs we posess and are mature if the origin tx is coinbase
2000+ . filter ( |local_output| {
2001+ self . indexed_graph
2002+ . graph ( )
2003+ . get_tx ( local_output. outpoint . txid )
2004+ . is_some_and ( |tx| {
2005+ !tx. is_coinbase ( )
2006+ || ( local_output. chain_position . is_confirmed ( )
2007+ && current_height. is_some_and ( |current_height| {
2008+ local_output
2009+ . chain_position
2010+ . confirmation_height_upper_bound ( )
2011+ . is_some_and ( |local_output_height| {
2012+ current_height. saturating_sub ( local_output_height)
2013+ >= COINBASE_MATURITY
2014+ } )
2015+ } ) )
2016+ } )
2017+ } )
2018+ // combine optional UTxOs with manually selected UTxOs
2019+ // NOTE: if manually_selected_only is true, then the previous iterator in the chain is
2020+ // empty and only manually selected UTxOs will be considered
2021+ // NOTE: manual selection overrides unspendable
2022+ . chain ( params. utxos . clone ( ) )
2023+ // transform every UTxO into a WeightedUtxo
2024+ . map ( |utxo| {
2025+ let keychain = utxo. keychain ;
2026+ WeightedUtxo {
2027+ satisfaction_weight : self
2028+ . public_descriptor ( keychain)
2029+ . max_weight_to_satisfy ( )
2030+ . unwrap ( ) ,
2031+ utxo : Utxo :: Local ( utxo) ,
20622032 }
2063- spendable
20642033 } )
2065- . collect :: < Vec < _ > > ( ) ;
2066-
2067- let mut i = 0 ;
2068- may_spend. retain ( |u| {
2069- let retain = ( self . keychains ( ) . count ( ) == 1 || change_policy. is_satisfied_by ( & u. 0 ) )
2070- && !unspendable. contains ( & u. 0 . outpoint )
2071- && satisfies_confirmed[ i] ;
2072- i += 1 ;
2073- retain
2074- } ) ;
2075-
2076- let mut may_spend = may_spend
2077- . into_iter ( )
2078- . map ( |( local_utxo, satisfaction_weight) | WeightedUtxo {
2079- satisfaction_weight,
2080- utxo : Utxo :: Local ( local_utxo) ,
2034+ // include foreign UTxOs
2035+ . chain ( params. foreign_utxos . clone ( ) )
2036+ // TODO: here preselect_utxos could be splitted further into two functions to allow the
2037+ // addition of extra filters for inputs based on properties of the WeightedUtxos
2038+ //
2039+ // split UTxOs in required and optional
2040+ // if drain_wallet is true, all UTxOs are required, if false, only manually selected
2041+ // and foreign are required
2042+ // TODO: collection could be kept separate from selection
2043+ . partition :: < Vec < WeightedUtxo > , _ > ( |x| {
2044+ params. drain_wallet
2045+ // keep UTxO in required UTxOs if
2046+ || match x. utxo {
2047+ // it is in manually selected group
2048+ Utxo :: Local ( ref lo) => params. utxos . contains ( lo) ,
2049+ // it is foreign
2050+ Utxo :: Foreign { ..} => true ,
2051+ }
20812052 } )
2082- . collect ( ) ;
2083-
2084- if must_use_all_available {
2085- must_spend. append ( & mut may_spend) ;
2086- }
2087-
2088- ( must_spend, may_spend)
20892053 }
20902054
20912055 fn complete_transaction (
0 commit comments