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