@@ -274,25 +274,30 @@ impl<'a, Cs> TxBuilder<'a, Cs> {
274274 /// These have priority over the "unspendable" utxos, meaning that if a utxo is present both in
275275 /// the "utxos" and the "unspendable" list, it will be spent.
276276 pub fn add_utxos ( & mut self , outpoints : & [ OutPoint ] ) -> Result < & mut Self , AddUtxoError > {
277- {
278- let wallet = & mut self . wallet ;
279- let utxos = outpoints
280- . iter ( )
281- . map ( |outpoint| {
282- wallet
283- . get_utxo ( * outpoint)
284- . ok_or ( AddUtxoError :: UnknownUtxo ( * outpoint) )
285- } )
286- . collect :: < Result < Vec < _ > , _ > > ( ) ?;
287-
288- for utxo in utxos {
289- let descriptor = wallet. public_descriptor ( utxo. keychain ) ;
290- let satisfaction_weight = descriptor. max_weight_to_satisfy ( ) . unwrap ( ) ;
291- self . params . utxos . push ( WeightedUtxo {
292- satisfaction_weight,
293- utxo : Utxo :: Local ( utxo) ,
294- } ) ;
295- }
277+ let utxos = outpoints
278+ . iter ( )
279+ . map ( |outpoint| {
280+ self . wallet
281+ . get_utxo ( * outpoint)
282+ . or_else ( || {
283+ // allow selecting a spent output if we're bumping fee
284+ self . params
285+ . bumping_fee
286+ . and_then ( |_| self . wallet . get_output ( * outpoint) )
287+ } )
288+ . ok_or ( AddUtxoError :: UnknownUtxo ( * outpoint) )
289+ } )
290+ . collect :: < Result < Vec < _ > , _ > > ( ) ?;
291+
292+ for utxo in utxos {
293+ let descriptor = self . wallet . public_descriptor ( utxo. keychain ) ;
294+ let satisfaction_weight = descriptor
295+ . max_weight_to_satisfy ( )
296+ . expect ( "descriptor should be satisfiable" ) ;
297+ self . params . utxos . push ( WeightedUtxo {
298+ satisfaction_weight,
299+ utxo : Utxo :: Local ( utxo) ,
300+ } ) ;
296301 }
297302
298303 Ok ( self )
@@ -306,6 +311,120 @@ impl<'a, Cs> TxBuilder<'a, Cs> {
306311 self . add_utxos ( & [ outpoint] )
307312 }
308313
314+ /// Replace an unconfirmed transaction.
315+ ///
316+ /// This method attempts to create a replacement for the transaction with `txid` by
317+ /// looking for the largest input that is owned by this wallet and adding it to the
318+ /// list of UTXOs to spend.
319+ ///
320+ /// # Note
321+ ///
322+ /// Aside from reusing one of the inputs, the method makes no assumptions about the
323+ /// structure of the replacement, so if you need to reuse the original recipient(s)
324+ /// and/or change address, you should add them manually before [`finish`] is called.
325+ ///
326+ /// # Example
327+ ///
328+ /// Create a replacement for an unconfirmed wallet transaction
329+ ///
330+ /// ```rust,no_run
331+ /// # let mut wallet = bdk_wallet::doctest_wallet!();
332+ /// let wallet_txs = wallet.transactions().collect::<Vec<_>>();
333+ /// let tx = wallet_txs.first().expect("must have wallet tx");
334+ ///
335+ /// if !tx.chain_position.is_confirmed() {
336+ /// let txid = tx.tx_node.txid;
337+ /// let mut builder = wallet.build_tx();
338+ /// builder.replace_tx(txid).expect("should replace");
339+ ///
340+ /// // Continue building tx...
341+ ///
342+ /// let psbt = builder.finish()?;
343+ /// }
344+ /// # Ok::<_, anyhow::Error>(())
345+ /// ```
346+ ///
347+ /// # Errors
348+ ///
349+ /// - If the original transaction is not found in the tx graph
350+ /// - If the orginal transaction is confirmed
351+ /// - If none of the inputs are owned by this wallet
352+ ///
353+ /// [`finish`]: TxBuilder::finish
354+ pub fn replace_tx ( & mut self , txid : Txid ) -> Result < & mut Self , ReplaceTxError > {
355+ let tx = self
356+ . wallet
357+ . indexed_graph
358+ . graph ( )
359+ . get_tx ( txid)
360+ . ok_or ( ReplaceTxError :: MissingTransaction ) ?;
361+ if self
362+ . wallet
363+ . transactions ( )
364+ . find ( |c| c. tx_node . txid == txid)
365+ . map ( |c| c. chain_position . is_confirmed ( ) )
366+ . unwrap_or ( false )
367+ {
368+ return Err ( ReplaceTxError :: TransactionConfirmed ) ;
369+ }
370+ let outpoint = tx
371+ . input
372+ . iter ( )
373+ . filter_map ( |txin| {
374+ let prev_tx = self
375+ . wallet
376+ . indexed_graph
377+ . graph ( )
378+ . get_tx ( txin. previous_output . txid ) ?;
379+ let txout = & prev_tx. output [ txin. previous_output . vout as usize ] ;
380+ if self . wallet . is_mine ( txout. script_pubkey . clone ( ) ) {
381+ Some ( ( txin. previous_output , txout. value ) )
382+ } else {
383+ None
384+ }
385+ } )
386+ . max_by_key ( |( _, value) | * value)
387+ . map ( |( op, _) | op)
388+ . ok_or ( ReplaceTxError :: NonReplaceable ) ?;
389+
390+ // add previous fee
391+ let absolute = self . wallet . calculate_fee ( & tx) . unwrap_or_default ( ) ;
392+ let rate = absolute / tx. weight ( ) ;
393+ self . params . bumping_fee = Some ( PreviousFee { absolute, rate } ) ;
394+
395+ self . add_utxo ( outpoint) . expect ( "we must have the utxo" ) ;
396+
397+ // do not try to spend the outputs of the replaced tx including descendants
398+ core:: iter:: once ( ( txid, tx) )
399+ . chain (
400+ self . wallet
401+ . tx_graph ( )
402+ . walk_descendants ( txid, |_, descendant_txid| {
403+ let tx = self . wallet . tx_graph ( ) . get_tx ( txid) ?;
404+ Some ( ( descendant_txid, tx) )
405+ } ) ,
406+ )
407+ . for_each ( |( txid, tx) | {
408+ self . params
409+ . unspendable
410+ . extend ( ( 0 ..tx. output . len ( ) ) . map ( |vout| OutPoint :: new ( txid, vout as u32 ) ) ) ;
411+ } ) ;
412+
413+ Ok ( self )
414+ }
415+
416+ /// Get the previous fee and feerate, i.e. the fee of the tx being fee-bumped, if any.
417+ ///
418+ /// This method may be used in combination with either [`build_fee_bump`] or [`replace_tx`]
419+ /// and is useful for deciding what fee to attach to a transaction for the purpose of
420+ /// "replace-by-fee" (RBF).
421+ ///
422+ /// [`build_fee_bump`]: Wallet::build_fee_bump
423+ /// [`replace_tx`]: Self::replace_tx
424+ pub fn previous_fee ( & self ) -> Option < ( Amount , FeeRate ) > {
425+ self . params . bumping_fee . map ( |p| ( p. absolute , p. rate ) )
426+ }
427+
309428 /// Add a foreign UTXO i.e. a UTXO not owned by this wallet.
310429 ///
311430 /// At a minimum to add a foreign UTXO we need:
@@ -697,6 +816,30 @@ impl fmt::Display for AddUtxoError {
697816#[ cfg( feature = "std" ) ]
698817impl std:: error:: Error for AddUtxoError { }
699818
819+ /// Error returned by [`TxBuilder::replace_tx`].
820+ #[ derive( Debug ) ]
821+ pub enum ReplaceTxError {
822+ /// Transaction was not found in tx graph
823+ MissingTransaction ,
824+ /// Transaction can't be replaced by this wallet
825+ NonReplaceable ,
826+ /// Transaction is already confirmed
827+ TransactionConfirmed ,
828+ }
829+
830+ impl fmt:: Display for ReplaceTxError {
831+ fn fmt ( & self , f : & mut fmt:: Formatter < ' _ > ) -> fmt:: Result {
832+ match self {
833+ Self :: MissingTransaction => write ! ( f, "transaction not found in tx graph" ) ,
834+ Self :: NonReplaceable => write ! ( f, "no replaceable input found" ) ,
835+ Self :: TransactionConfirmed => write ! ( f, "cannot replace a confirmed tx" ) ,
836+ }
837+ }
838+ }
839+
840+ #[ cfg( feature = "std" ) ]
841+ impl std:: error:: Error for ReplaceTxError { }
842+
700843#[ derive( Debug ) ]
701844/// Error returned from [`TxBuilder::add_foreign_utxo`].
702845pub enum AddForeignUtxoError {
@@ -833,6 +976,7 @@ mod test {
833976 } ;
834977 }
835978
979+ use crate :: test_utils:: * ;
836980 use bitcoin:: consensus:: deserialize;
837981 use bitcoin:: hex:: FromHex ;
838982 use bitcoin:: TxOut ;
@@ -1098,4 +1242,134 @@ mod test {
10981242 builder. fee_rate ( FeeRate :: from_sat_per_kwu ( feerate + 250 ) ) ;
10991243 let _ = builder. finish ( ) . unwrap ( ) ;
11001244 }
1245+ #[ test]
1246+ fn replace_tx_allows_selecting_spent_outputs ( ) {
1247+ let ( mut wallet, txid_0) = get_funded_wallet_wpkh ( ) ;
1248+ let outpoint_1 = OutPoint :: new ( txid_0, 0 ) ;
1249+
1250+ // receive output 2
1251+ let outpoint_2 = receive_output_in_latest_block ( & mut wallet, 49_000 ) ;
1252+ assert_eq ! ( wallet. list_unspent( ) . count( ) , 2 ) ;
1253+ assert_eq ! ( wallet. balance( ) . total( ) . to_sat( ) , 99_000 ) ;
1254+
1255+ // create tx1: 2-in/1-out sending all to `recip`
1256+ let recip = ScriptBuf :: from_hex ( "0014446906a6560d8ad760db3156706e72e171f3a2aa" ) . unwrap ( ) ;
1257+ let mut builder = wallet. build_tx ( ) ;
1258+ builder. add_recipient ( recip. clone ( ) , Amount :: from_sat ( 98_800 ) ) ;
1259+ let psbt = builder. finish ( ) . unwrap ( ) ;
1260+ let tx1 = psbt. unsigned_tx ;
1261+ let txid1 = tx1. compute_txid ( ) ;
1262+ insert_tx ( & mut wallet, tx1) ;
1263+ assert ! ( wallet. list_unspent( ) . next( ) . is_none( ) ) ;
1264+
1265+ // now replace tx1 with a new transaction
1266+ let mut builder = wallet. build_tx ( ) ;
1267+ builder. replace_tx ( txid1) . expect ( "should replace input" ) ;
1268+ let prev_feerate = builder. previous_fee ( ) . unwrap ( ) . 1 ;
1269+ builder. add_recipient ( recip, Amount :: from_sat ( 98_500 ) ) ;
1270+ builder. fee_rate ( FeeRate :: from_sat_per_kwu (
1271+ prev_feerate. to_sat_per_kwu ( ) + 250 ,
1272+ ) ) ;
1273+
1274+ // Because outpoint 2 was spent in tx1, by default it won't be available for selection,
1275+ // but we can add it manually, with the caveat that the builder is in a bump-fee
1276+ // context.
1277+ builder. add_utxo ( outpoint_2) . expect ( "should add output" ) ;
1278+ let psbt = builder. finish ( ) . unwrap ( ) ;
1279+
1280+ assert ! ( psbt
1281+ . unsigned_tx
1282+ . input
1283+ . iter( )
1284+ . any( |txin| txin. previous_output == outpoint_1) ) ;
1285+ assert ! ( psbt
1286+ . unsigned_tx
1287+ . input
1288+ . iter( )
1289+ . any( |txin| txin. previous_output == outpoint_2) ) ;
1290+ }
1291+
1292+ #[ test]
1293+ fn test_replace_tx_unspendable_with_descendants ( ) {
1294+ use crate :: KeychainKind :: External ;
1295+
1296+ // Replacing a tx should mark the original txouts unspendable
1297+
1298+ let ( mut wallet, txid_0) = get_funded_wallet_wpkh ( ) ;
1299+ let outpoint_0 = OutPoint :: new ( txid_0, 0 ) ;
1300+ let balance = wallet. balance ( ) . total ( ) ;
1301+ let fee = Amount :: from_sat ( 256 ) ;
1302+
1303+ let mut previous_output = outpoint_0;
1304+
1305+ // apply 3 unconfirmed txs to wallet
1306+ for i in 1 ..=3 {
1307+ let tx = Transaction {
1308+ input : vec ! [ TxIn {
1309+ previous_output,
1310+ ..Default :: default ( )
1311+ } ] ,
1312+ output : vec ! [ TxOut {
1313+ script_pubkey: wallet. reveal_next_address( External ) . script_pubkey( ) ,
1314+ value: balance - fee * i as u64 ,
1315+ } ] ,
1316+ ..new_tx ( i)
1317+ } ;
1318+
1319+ let txid = tx. compute_txid ( ) ;
1320+ insert_tx ( & mut wallet, tx) ;
1321+ previous_output = OutPoint :: new ( txid, 0 ) ;
1322+ }
1323+
1324+ let unconfirmed_txs: Vec < _ > = wallet
1325+ . transactions ( )
1326+ . filter ( |c| !c. chain_position . is_confirmed ( ) )
1327+ . collect ( ) ;
1328+ let txid_1 = unconfirmed_txs
1329+ . iter ( )
1330+ . find ( |c| c. tx_node . input [ 0 ] . previous_output == outpoint_0)
1331+ . map ( |c| c. tx_node . txid )
1332+ . unwrap ( ) ;
1333+ let unconfirmed_txids: Vec < _ > = unconfirmed_txs. iter ( ) . map ( |c| c. tx_node . txid ) . collect ( ) ;
1334+ assert_eq ! ( unconfirmed_txids. len( ) , 3 ) ;
1335+
1336+ // replace tx1
1337+ let mut builder = wallet. build_tx ( ) ;
1338+ builder. replace_tx ( txid_1) . unwrap ( ) ;
1339+ assert_eq ! (
1340+ builder. params. utxos. first( ) . unwrap( ) . utxo. outpoint( ) ,
1341+ outpoint_0
1342+ ) ;
1343+ for txid in unconfirmed_txids {
1344+ assert ! ( builder. params. unspendable. contains( & OutPoint :: new( txid, 0 ) ) ) ;
1345+ }
1346+ }
1347+
1348+ #[ test]
1349+ fn test_replace_tx_error ( ) {
1350+ use bitcoin:: hashes:: Hash ;
1351+ let ( mut wallet, txid_0) = get_funded_wallet_wpkh ( ) ;
1352+
1353+ // tx does not exist
1354+ let mut builder = wallet. build_tx ( ) ;
1355+ let res = builder. replace_tx ( Txid :: all_zeros ( ) ) ;
1356+ assert ! ( matches!( res, Err ( ReplaceTxError :: MissingTransaction ) ) ) ;
1357+
1358+ // tx confirmed
1359+ let mut builder = wallet. build_tx ( ) ;
1360+ let res = builder. replace_tx ( txid_0) ;
1361+ assert ! ( matches!( res, Err ( ReplaceTxError :: TransactionConfirmed ) ) ) ;
1362+
1363+ // can't replace a foreign tx
1364+ let tx = Transaction {
1365+ input : vec ! [ TxIn :: default ( ) ] ,
1366+ output : vec ! [ TxOut :: NULL ] ,
1367+ ..new_tx ( 0 )
1368+ } ;
1369+ let txid = tx. compute_txid ( ) ;
1370+ insert_tx ( & mut wallet, tx) ;
1371+ let mut builder = wallet. build_tx ( ) ;
1372+ let res = builder. replace_tx ( txid) ;
1373+ assert ! ( matches!( res, Err ( ReplaceTxError :: NonReplaceable ) ) ) ;
1374+ }
11011375}
0 commit comments