@@ -36,7 +36,7 @@ use bdk_chain::{
3636use bitcoin:: {
3737 absolute,
3838 consensus:: encode:: serialize,
39- constants:: { genesis_block, COINBASE_MATURITY } ,
39+ constants:: genesis_block,
4040 psbt,
4141 secp256k1:: Secp256k1 ,
4242 sighash:: { EcdsaSighashType , TapSighashType } ,
@@ -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,117 +2006,52 @@ 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- let spend_height = current_height + 1 ;
2056- let coin_age_at_spend_height =
2057- spend_height. saturating_sub ( anchor. block_id . height ) ;
2058- spendable &= coin_age_at_spend_height >= COINBASE_MATURITY ;
2059- }
2060- ChainPosition :: Unconfirmed { .. } => spendable = false ,
2061- }
2062- }
2063- }
2064- spendable
2065- } )
2066- . collect :: < Vec < _ > > ( ) ;
2067-
2068- let mut i = 0 ;
2069- may_spend. retain ( |u| {
2070- let retain = ( self . keychains ( ) . count ( ) == 1 || change_policy. is_satisfied_by ( & u. 0 ) )
2071- && !unspendable. contains ( & u. 0 . outpoint )
2072- && satisfies_confirmed[ i] ;
2073- i += 1 ;
2074- retain
2075- } ) ;
2076-
2077- let mut may_spend = may_spend
2078- . into_iter ( )
2079- . map ( |( local_utxo, satisfaction_weight) | WeightedUtxo {
2080- satisfaction_weight,
2081- utxo : Utxo :: Local ( local_utxo) ,
2082- } )
2083- . collect ( ) ;
2084-
2085- if must_use_all_available {
2086- 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 . indexed_graph
2017+ . graph ( )
2018+ // get all unspent UTxOs from wallet
2019+ // NOTE: the UTxOs returned by the following method already belong to wallet as the
2020+ // call chain uses get_tx_node infallibly
2021+ . filter_chain_unspents (
2022+ & self . chain ,
2023+ self . chain . tip ( ) . block_id ( ) ,
2024+ self . indexed_graph . index . outpoints ( ) . iter ( ) . cloned ( ) ,
2025+ )
2026+ // only create LocalOutput if UTxO is mature
2027+ . filter_map ( move |( ( k, i) , full_txo) | {
2028+ full_txo
2029+ . is_mature ( current_height)
2030+ . then ( || new_local_utxo ( k, i, full_txo) )
2031+ } )
2032+ // only process UTxOs not selected manually, they will be considered later in the chain
2033+ // NOTE: this avoid UTxOs in both required and optional list
2034+ . filter ( |may_spend| !params. utxos . contains ( may_spend) )
2035+ // only add to optional UTxOs those which satisfy the change policy if we reuse change
2036+ . filter ( |local_output| {
2037+ self . keychains ( ) . count ( ) == 1
2038+ || params. change_policy . is_satisfied_by ( local_output)
2039+ } )
2040+ // only add to optional UTxOs those marked as spendable
2041+ . filter ( |local_output| !params. unspendable . contains ( & local_output. outpoint ) )
2042+ // if bumping fees only add to optional UTxOs those confirmed
2043+ . filter ( |local_output| {
2044+ params. bumping_fee . is_none ( ) || local_output. chain_position . is_confirmed ( )
2045+ } )
2046+ . map ( |utxo| WeightedUtxo {
2047+ satisfaction_weight : self
2048+ . public_descriptor ( utxo. keychain )
2049+ . max_weight_to_satisfy ( )
2050+ . unwrap ( ) ,
2051+ utxo : Utxo :: Local ( utxo) ,
2052+ } )
2053+ . collect ( )
20872054 }
2088-
2089- ( must_spend, may_spend)
20902055 }
20912056
20922057 fn complete_transaction (
0 commit comments