@@ -1417,8 +1417,33 @@ impl Wallet {
14171417
14181418 fee_amount += fee_rate * tx. weight ( ) ;
14191419
1420- let ( required_utxos, optional_utxos) =
1421- self . preselect_utxos ( & params, Some ( current_height. to_consensus_u32 ( ) ) ) ;
1420+ let ( required_utxos, optional_utxos) = {
1421+ // NOTE: manual selection overrides unspendable
1422+ let mut required: Vec < WeightedUtxo > = params
1423+ . utxos
1424+ . clone ( )
1425+ . into_iter ( )
1426+ // transform required UTxOs into a WeightedUtxo
1427+ . map ( |utxo| WeightedUtxo {
1428+ satisfaction_weight : self
1429+ . public_descriptor ( utxo. keychain )
1430+ . max_weight_to_satisfy ( )
1431+ . unwrap ( ) ,
1432+ utxo : Utxo :: Local ( utxo) ,
1433+ } )
1434+ // include foreign UTxOs as required
1435+ . chain ( params. foreign_utxos . clone ( ) )
1436+ . collect ( ) ;
1437+ let optional = self . filter_utxos ( & params, current_height. to_consensus_u32 ( ) ) ;
1438+
1439+ // if drain_wallet is true, all UTxOs are required
1440+ if params. drain_wallet {
1441+ required. extend ( optional) ;
1442+ ( required, vec ! [ ] )
1443+ } else {
1444+ ( required, optional)
1445+ }
1446+ } ;
14221447
14231448 // get drain script
14241449 let mut drain_index = Option :: < ( KeychainKind , u32 ) > :: None ;
@@ -1618,60 +1643,64 @@ impl Wallet {
16181643 . map_err ( |_| BuildFeeBumpError :: FeeRateUnavailable ) ?;
16191644
16201645 // 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 {
1646+ let ( local, foreign) : ( HashSet < LocalOutput > , Vec < WeightedUtxo > ) =
1647+ tx. input . drain ( ..) . try_fold (
1648+ ( HashSet :: < LocalOutput > :: new ( ) , Vec :: < WeightedUtxo > :: new ( ) ) ,
1649+ |( mut local, mut foreign) , txin| -> Result < _ , BuildFeeBumpError > {
1650+ let prev_tx = graph
1651+ . get_tx ( txin. previous_output . txid )
1652+ . ok_or ( BuildFeeBumpError :: UnknownUtxo ( txin. previous_output ) ) ?;
1653+ let txout = & prev_tx. output [ txin. previous_output . vout as usize ] ;
1654+
1655+ let chain_position =
1656+ chain_positions
1657+ . get ( & txin. previous_output . txid )
1658+ . cloned ( )
1659+ . ok_or ( BuildFeeBumpError :: UnknownUtxo ( txin. previous_output ) ) ?;
1660+
1661+ match txout_index. index_of_spk ( txout. script_pubkey . clone ( ) ) {
1662+ Some ( & ( keychain, derivation_index) ) => local. insert ( LocalOutput {
1663+ outpoint : txin. previous_output ,
1664+ txout : txout. clone ( ) ,
1665+ keychain,
1666+ is_spent : true ,
1667+ derivation_index,
1668+ chain_position,
1669+ } ) ,
1670+ None => {
1671+ let satisfaction_weight = Weight :: from_wu_usize (
1672+ serialize ( & txin. script_sig ) . len ( ) * 4
1673+ + serialize ( & txin. witness ) . len ( ) ,
1674+ ) ;
1675+
1676+ let psbt_input_data = if txout. script_pubkey . witness_version ( ) . is_some ( )
1677+ {
1678+ Box :: new ( psbt:: Input {
16621679 witness_utxo : Some ( txout. clone ( ) ) ,
16631680 non_witness_utxo : Some ( prev_tx. as_ref ( ) . clone ( ) ) ,
16641681 ..Default :: default ( )
1665- } ) ,
1666- } ,
1667- satisfaction_weight,
1682+ } )
1683+ } else {
1684+ Box :: new ( psbt:: Input {
1685+ non_witness_utxo : Some ( prev_tx. as_ref ( ) . clone ( ) ) ,
1686+ ..Default :: default ( )
1687+ } )
1688+ } ;
1689+ foreign. push ( WeightedUtxo {
1690+ utxo : Utxo :: Foreign {
1691+ outpoint : txin. previous_output ,
1692+ sequence : txin. sequence ,
1693+ psbt_input : psbt_input_data,
1694+ } ,
1695+ satisfaction_weight,
1696+ } ) ;
1697+ true
16681698 }
1669- }
1670- } ;
1699+ } ;
16711700
1672- Ok ( weighted_utxo )
1673- } )
1674- . collect :: < Result < Vec < _ > , _ > > ( ) ?;
1701+ Ok ( ( local , foreign ) )
1702+ } ,
1703+ ) ?;
16751704
16761705 if tx. output . len ( ) > 1 {
16771706 let mut change_index = None ;
@@ -1698,7 +1727,8 @@ impl Wallet {
16981727 . into_iter ( )
16991728 . map ( |txout| ( txout. script_pubkey , txout. value ) )
17001729 . collect ( ) ,
1701- utxos : original_utxos,
1730+ utxos : local,
1731+ foreign_utxos : foreign,
17021732 bumping_fee : Some ( tx_builder:: PreviousFee {
17031733 absolute : fee,
17041734 rate : fee_rate,
@@ -1976,116 +2006,56 @@ impl Wallet {
19762006 descriptor. at_derivation_index ( child) . ok ( )
19772007 }
19782008
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-
19922009 /// Given the options returns the list of utxos that must be used to form the
19932010 /// transaction and any further that may be used if needed.
1994- fn preselect_utxos (
1995- & self ,
1996- params : & TxParams ,
1997- current_height : Option < u32 > ,
1998- ) -> ( 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- }
2062- }
2063- spendable
2064- } )
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) ,
2081- } )
2082- . collect ( ) ;
2083-
2084- if must_use_all_available {
2085- must_spend. append ( & mut may_spend) ;
2011+ fn filter_utxos ( & self , params : & TxParams , current_height : u32 ) -> Vec < WeightedUtxo > {
2012+ if params. manually_selected_only {
2013+ vec ! [ ]
2014+ // only process optional UTxOs if manually_selected_only is false
2015+ } else {
2016+ self
2017+ // get all unspent UTxOs from wallet
2018+ . list_unspent ( )
2019+ // only process UTxOs not selected manually, they will be considered later in the chain
2020+ // NOTE: this avoid UTxOs in both required and optional list
2021+ . filter ( |may_spend| !params. utxos . contains ( may_spend) )
2022+ // only add to optional UTxOs those which satisfy the change policy if we reuse change
2023+ . filter ( |local_output| {
2024+ self . keychains ( ) . count ( ) == 1
2025+ || params. change_policy . is_satisfied_by ( local_output)
2026+ } )
2027+ // only add to optional UTxOs those marked as spendable
2028+ . filter ( |local_output| !params. unspendable . contains ( & local_output. outpoint ) )
2029+ // if bumping fees only add to optional UTxOs those confirmed
2030+ . filter ( |local_output| {
2031+ params. bumping_fee . is_none ( ) || local_output. chain_position . is_confirmed ( )
2032+ } )
2033+ // only use UTxOs we posess and are mature if the origin tx is coinbase
2034+ . filter ( move |local_output| {
2035+ self . indexed_graph
2036+ . graph ( )
2037+ . get_tx ( local_output. outpoint . txid )
2038+ . is_some_and ( |tx| {
2039+ !tx. is_coinbase ( )
2040+ || ( local_output. chain_position . is_confirmed ( )
2041+ && local_output
2042+ . chain_position
2043+ . confirmation_height_upper_bound ( )
2044+ . is_some_and ( |local_output_height| {
2045+ current_height. saturating_sub ( local_output_height)
2046+ >= COINBASE_MATURITY
2047+ } ) )
2048+ } )
2049+ } )
2050+ . map ( |utxo| WeightedUtxo {
2051+ satisfaction_weight : self
2052+ . public_descriptor ( utxo. keychain )
2053+ . max_weight_to_satisfy ( )
2054+ . unwrap ( ) ,
2055+ utxo : Utxo :: Local ( utxo) ,
2056+ } )
2057+ . collect ( )
20862058 }
2087-
2088- ( must_spend, may_spend)
20892059 }
20902060
20912061 fn complete_transaction (
0 commit comments