Skip to content

Commit 7cb0243

Browse files
committed
fix(wallet): ensure there are not duplicated foreign utxos
1 parent 905c8dd commit 7cb0243

File tree

3 files changed

+40
-7
lines changed

3 files changed

+40
-7
lines changed

crates/wallet/src/types.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ pub struct LocalOutput {
6666
}
6767

6868
/// A [`Utxo`] with its `satisfaction_weight`.
69-
#[derive(Debug, Clone, PartialEq, Eq)]
69+
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
7070
pub struct WeightedUtxo {
7171
/// The weight of the witness data and `scriptSig` expressed in [weight units]. This is used to
7272
/// properly maintain the feerate when adding this input to a transaction during coin selection.
@@ -77,7 +77,7 @@ pub struct WeightedUtxo {
7777
pub utxo: Utxo,
7878
}
7979

80-
#[derive(Debug, Clone, PartialEq, Eq)]
80+
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
8181
/// An unspent transaction output (UTXO).
8282
pub enum Utxo {
8383
/// A UTXO owned by the local wallet.

crates/wallet/src/wallet/mod.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1640,9 +1640,12 @@ impl Wallet {
16401640
.map_err(|_| BuildFeeBumpError::FeeRateUnavailable)?;
16411641

16421642
// remove the inputs from the tx and process them
1643-
let (local, foreign): (HashSet<LocalOutput>, Vec<WeightedUtxo>) =
1643+
let (local, foreign): (HashSet<LocalOutput>, HashSet<WeightedUtxo>) =
16441644
tx.input.drain(..).try_fold(
1645-
(HashSet::<LocalOutput>::new(), Vec::<WeightedUtxo>::new()),
1645+
(
1646+
HashSet::<LocalOutput>::new(),
1647+
HashSet::<WeightedUtxo>::new(),
1648+
),
16461649
|(mut local, mut foreign), txin| -> Result<_, BuildFeeBumpError> {
16471650
let prev_tx = graph
16481651
.get_tx(txin.previous_output.txid)
@@ -1683,7 +1686,7 @@ impl Wallet {
16831686
..Default::default()
16841687
})
16851688
};
1686-
foreign.push(WeightedUtxo {
1689+
foreign.insert(WeightedUtxo {
16871690
utxo: Utxo::Foreign {
16881691
outpoint: txin.previous_output,
16891692
sequence: txin.sequence,

crates/wallet/src/wallet/tx_builder.rs

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -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: HashSet<WeightedUtxo>,
130130
pub(crate) unspendable: HashSet<OutPoint>,
131131
pub(crate) manually_selected_only: bool,
132132
pub(crate) sighash: Option<psbt::PsbtSighashType>,
@@ -382,7 +382,7 @@ impl<'a, Cs> TxBuilder<'a, Cs> {
382382
}
383383
}
384384

385-
self.params.foreign_utxos.push(WeightedUtxo {
385+
self.params.foreign_utxos.insert(WeightedUtxo {
386386
satisfaction_weight,
387387
utxo: Utxo::Foreign {
388388
outpoint,
@@ -1100,4 +1100,34 @@ mod test {
11001100
params.utxos.into_iter().collect::<Vec<_>>()
11011101
);
11021102
}
1103+
1104+
#[test]
1105+
fn not_duplicated_foreign_utxos() {
1106+
use crate::test_utils::get_funded_wallet_wpkh;
1107+
// Use two different wallets to avoid adding local utxos
1108+
let (wallet1, txid1) = get_funded_wallet_wpkh();
1109+
let (mut wallet2, _) = get_funded_wallet_wpkh();
1110+
1111+
let utxo1 = wallet1.list_unspent().next().unwrap();
1112+
let tx1 = wallet1.get_tx(txid1).unwrap().tx_node.tx.clone();
1113+
1114+
let satisfaction_weight = wallet1
1115+
.public_descriptor(KeychainKind::External)
1116+
.max_weight_to_satisfy()
1117+
.unwrap();
1118+
1119+
let mut builder = wallet2.build_tx();
1120+
for _ in 0..2 {
1121+
let _ = builder.add_foreign_utxo(
1122+
utxo1.outpoint,
1123+
psbt::Input {
1124+
non_witness_utxo: Some(tx1.as_ref().clone()),
1125+
..Default::default()
1126+
},
1127+
satisfaction_weight,
1128+
);
1129+
}
1130+
1131+
assert_eq!(builder.params.foreign_utxos.len(), 1);
1132+
}
11031133
}

0 commit comments

Comments
 (0)