Skip to content

Commit ebecafc

Browse files
committed
fix(wallet): ensure there are not duplicated foreign utxos
1 parent f24687f commit ebecafc

File tree

2 files changed

+72
-19
lines changed

2 files changed

+72
-19
lines changed

crates/wallet/src/wallet/mod.rs

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1432,7 +1432,7 @@ impl Wallet {
14321432
utxo: Utxo::Local(utxo),
14331433
})
14341434
// include foreign UTxOs as required
1435-
.chain(params.foreign_utxos.clone())
1435+
.chain(params.foreign_utxos.clone().into_values())
14361436
.collect();
14371437
let optional = self.filter_utxos(&params, current_height.to_consensus_u32());
14381438

@@ -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>, HashMap<OutPoint, WeightedUtxo>) =
16441644
tx.input.drain(..).try_fold(
1645-
(HashSet::<LocalOutput>::new(), Vec::<WeightedUtxo>::new()),
1645+
(
1646+
HashSet::<LocalOutput>::new(),
1647+
HashMap::<OutPoint, WeightedUtxo>::new(),
1648+
),
16461649
|(mut local, mut foreign), txin| -> Result<_, BuildFeeBumpError> {
16471650
let prev_tx = graph
16481651
.get_tx(txin.previous_output.txid)
@@ -1683,14 +1686,17 @@ impl Wallet {
16831686
..Default::default()
16841687
})
16851688
};
1686-
foreign.push(WeightedUtxo {
1687-
utxo: Utxo::Foreign {
1688-
outpoint: txin.previous_output,
1689-
sequence: txin.sequence,
1690-
psbt_input: psbt_input_data,
1689+
foreign.insert(
1690+
txin.previous_output,
1691+
WeightedUtxo {
1692+
utxo: Utxo::Foreign {
1693+
outpoint: txin.previous_output,
1694+
sequence: txin.sequence,
1695+
psbt_input: psbt_input_data,
1696+
},
1697+
satisfaction_weight,
16911698
},
1692-
satisfaction_weight,
1693-
});
1699+
);
16941700
true
16951701
}
16961702
};

crates/wallet/src/wallet/tx_builder.rs

Lines changed: 56 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ use rand_core::RngCore;
5252
use super::coin_selection::CoinSelectionAlgorithm;
5353
use super::utils::shuffle_slice;
5454
use super::{CreateTxError, Wallet};
55-
use crate::collections::{BTreeMap, HashSet};
55+
use crate::collections::{BTreeMap, HashMap, HashSet};
5656
use 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,48 @@ 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_wpkh;
1110+
// Use two different wallets to avoid adding local utxos
1111+
let (wallet1, txid1) = get_funded_wallet_wpkh();
1112+
let (mut wallet2, _) = get_funded_wallet_wpkh();
1113+
1114+
let utxo1 = wallet1.list_unspent().next().unwrap();
1115+
let tx1 = wallet1.get_tx(txid1).unwrap().tx_node.tx.clone();
1116+
1117+
let satisfaction_weight = wallet1
1118+
.public_descriptor(KeychainKind::External)
1119+
.max_weight_to_satisfy()
1120+
.unwrap();
1121+
1122+
let mut builder = wallet2.build_tx();
1123+
1124+
// add foreign utxo with satsifaction weight x
1125+
let _ = builder.add_foreign_utxo(
1126+
utxo1.outpoint,
1127+
psbt::Input {
1128+
non_witness_utxo: Some(tx1.as_ref().clone()),
1129+
..Default::default()
1130+
},
1131+
satisfaction_weight,
1132+
);
1133+
1134+
let modified_satisfaction_weight = satisfaction_weight - Weight::from_wu(6);
1135+
1136+
assert_ne!(satisfaction_weight, modified_satisfaction_weight);
1137+
1138+
// add foreign utxo with same outpoint but satisfaction weight x - 6wu
1139+
let _ = builder.add_foreign_utxo(
1140+
utxo1.outpoint,
1141+
psbt::Input {
1142+
non_witness_utxo: Some(tx1.as_ref().clone()),
1143+
..Default::default()
1144+
},
1145+
modified_satisfaction_weight,
1146+
);
1147+
1148+
assert_eq!(builder.params.foreign_utxos.len(), 1);
1149+
}
11031150
}

0 commit comments

Comments
 (0)