@@ -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
@@ -126,7 +126,7 @@ pub(crate) struct TxParams {
126126 pub ( crate ) internal_policy_path : Option < BTreeMap < String , Vec < usize > > > ,
127127 pub ( crate ) external_policy_path : Option < BTreeMap < String , Vec < usize > > > ,
128128 pub ( crate ) utxos : HashSet < LocalOutput > ,
129- pub ( crate ) foreign_utxos : Vec < WeightedUtxo > ,
129+ pub ( crate ) foreign_utxos : HashMap < OutPoint , WeightedUtxo > ,
130130 pub ( crate ) unspendable : HashSet < OutPoint > ,
131131 pub ( crate ) manually_selected_only : bool ,
132132 pub ( crate ) sighash : Option < psbt:: PsbtSighashType > ,
@@ -382,14 +382,17 @@ impl<'a, Cs> TxBuilder<'a, Cs> {
382382 }
383383 }
384384
385- self . params . foreign_utxos . push ( WeightedUtxo {
386- satisfaction_weight,
387- utxo : Utxo :: Foreign {
388- outpoint,
389- sequence,
390- psbt_input : Box :: new ( psbt_input) ,
385+ self . params . foreign_utxos . insert (
386+ outpoint,
387+ WeightedUtxo {
388+ satisfaction_weight,
389+ utxo : Utxo :: Foreign {
390+ outpoint,
391+ sequence,
392+ psbt_input : Box :: new ( psbt_input) ,
393+ } ,
391394 } ,
392- } ) ;
395+ ) ;
393396
394397 Ok ( self )
395398 }
@@ -1100,4 +1103,56 @@ mod test {
11001103 params. utxos. into_iter( ) . collect:: <Vec <_>>( )
11011104 ) ;
11021105 }
1106+
1107+ #[ test]
1108+ fn not_duplicated_foreign_utxos_with_same_outpoint_but_different_weight ( ) {
1109+ use crate :: test_utils:: { get_funded_wallet_single, get_funded_wallet_wpkh, get_test_wpkh} ;
1110+
1111+ // Use two different wallets to avoid adding local utxos
1112+ let ( wallet1, txid1) = get_funded_wallet_wpkh ( ) ;
1113+ let ( mut wallet2, txid2) = get_funded_wallet_single ( get_test_wpkh ( ) ) ;
1114+
1115+ // if the transactions were produced by the same wallet the following assert should fail
1116+ assert_ne ! ( txid1, txid2) ;
1117+
1118+ let utxo1 = wallet1. list_unspent ( ) . next ( ) . unwrap ( ) ;
1119+ let tx1 = wallet1. get_tx ( txid1) . unwrap ( ) . tx_node . tx . clone ( ) ;
1120+
1121+ let satisfaction_weight = wallet1
1122+ . public_descriptor ( KeychainKind :: External )
1123+ . max_weight_to_satisfy ( )
1124+ . unwrap ( ) ;
1125+
1126+ let mut builder = wallet2. build_tx ( ) ;
1127+
1128+ // add foreign utxo with satisfaction weight x
1129+ assert ! ( builder
1130+ . add_foreign_utxo(
1131+ utxo1. outpoint,
1132+ psbt:: Input {
1133+ non_witness_utxo: Some ( tx1. as_ref( ) . clone( ) ) ,
1134+ ..Default :: default ( )
1135+ } ,
1136+ satisfaction_weight,
1137+ )
1138+ . is_ok( ) ) ;
1139+
1140+ let modified_satisfaction_weight = satisfaction_weight - Weight :: from_wu ( 6 ) ;
1141+
1142+ assert_ne ! ( satisfaction_weight, modified_satisfaction_weight) ;
1143+
1144+ // add foreign utxo with same outpoint but satisfaction weight x - 6wu
1145+ assert ! ( builder
1146+ . add_foreign_utxo(
1147+ utxo1. outpoint,
1148+ psbt:: Input {
1149+ non_witness_utxo: Some ( tx1. as_ref( ) . clone( ) ) ,
1150+ ..Default :: default ( )
1151+ } ,
1152+ modified_satisfaction_weight,
1153+ )
1154+ . is_ok( ) ) ;
1155+
1156+ assert_eq ! ( builder. params. foreign_utxos. len( ) , 1 ) ;
1157+ }
11031158}
0 commit comments