Skip to content

Commit 81a1cef

Browse files
committed
fix(tx_builder)!: check foreign utxos are not local before inclusion
The new check is added to ensure all added foreign utxos are not owned by the wallet.
1 parent cc15e5d commit 81a1cef

File tree

2 files changed

+43
-3
lines changed

2 files changed

+43
-3
lines changed

crates/wallet/src/wallet/tx_builder.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -342,8 +342,9 @@ impl<'a, Cs> TxBuilder<'a, Cs> {
342342
///
343343
/// This method returns errors in the following circumstances:
344344
///
345-
/// 1. The `psbt_input` does not contain a `witness_utxo` or `non_witness_utxo`.
346-
/// 2. The data in `non_witness_utxo` does not match what is in `outpoint`.
345+
/// 1. The provided outpoint is associated to a [`LocalOutput`].
346+
/// 2. The `psbt_input` does not contain a `witness_utxo` or `non_witness_utxo`.
347+
/// 3. The data in `non_witness_utxo` does not match what is in `outpoint`.
347348
///
348349
/// Note unless you set [`only_witness_utxo`] any non-taproot `psbt_input` you pass to this
349350
/// method must have `non_witness_utxo` set otherwise you will get an error when [`finish`]
@@ -374,6 +375,10 @@ impl<'a, Cs> TxBuilder<'a, Cs> {
374375
satisfaction_weight: Weight,
375376
sequence: Sequence,
376377
) -> Result<&mut Self, AddForeignUtxoError> {
378+
// Avoid the inclusion of local utxos as foreign utxos
379+
if self.wallet.tx_graph().get_txout(outpoint).is_some() {
380+
return Err(AddForeignUtxoError::NotForeignUtxo);
381+
};
377382
if psbt_input.witness_utxo.is_none() {
378383
match psbt_input.non_witness_utxo.as_ref() {
379384
Some(tx) => {
@@ -711,6 +716,8 @@ pub enum AddForeignUtxoError {
711716
InvalidOutpoint(OutPoint),
712717
/// Foreign utxo missing witness_utxo or non_witness_utxo
713718
MissingUtxo,
719+
/// UTxO is owned by wallet
720+
NotForeignUtxo,
714721
}
715722

716723
impl fmt::Display for AddForeignUtxoError {
@@ -730,6 +737,7 @@ impl fmt::Display for AddForeignUtxoError {
730737
outpoint.txid, outpoint.vout,
731738
),
732739
Self::MissingUtxo => write!(f, "Foreign utxo missing witness_utxo or non_witness_utxo"),
740+
Self::NotForeignUtxo => write!(f, "UTxO is owned by wallet"),
733741
}
734742
}
735743
}

crates/wallet/tests/wallet.rs

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1645,6 +1645,38 @@ fn test_calculate_fee_with_missing_foreign_utxo() {
16451645

16461646
#[test]
16471647
fn test_add_foreign_utxo_invalid_psbt_input() {
1648+
let (mut wallet, _) = get_funded_wallet_wpkh();
1649+
let address = Address::from_str("bcrt1q3qtze4ys45tgdvguj66zrk4fu6hq3a3v9pfly5")
1650+
.expect("address")
1651+
.require_network(Network::Regtest)
1652+
.unwrap();
1653+
let tx0 = Transaction {
1654+
output: vec![TxOut {
1655+
value: Amount::from_sat(76_000),
1656+
script_pubkey: address.script_pubkey(),
1657+
}],
1658+
..new_tx(0)
1659+
};
1660+
let external_outpoint = OutPoint {
1661+
txid: tx0.compute_txid(),
1662+
vout: 0,
1663+
};
1664+
let foreign_utxo_satisfaction = wallet
1665+
.public_descriptor(KeychainKind::External)
1666+
.max_weight_to_satisfy()
1667+
.unwrap();
1668+
1669+
let mut builder = wallet.build_tx();
1670+
let result = builder.add_foreign_utxo(
1671+
external_outpoint,
1672+
psbt::Input::default(),
1673+
foreign_utxo_satisfaction,
1674+
);
1675+
assert!(matches!(result, Err(AddForeignUtxoError::MissingUtxo)));
1676+
}
1677+
1678+
#[test]
1679+
fn test_add_foreign_utxo_fails_when_utxo_is_owned_by_wallet() {
16481680
let (mut wallet, _) = get_funded_wallet_wpkh();
16491681
let outpoint = wallet.list_unspent().next().expect("must exist").outpoint;
16501682
let foreign_utxo_satisfaction = wallet
@@ -1655,7 +1687,7 @@ fn test_add_foreign_utxo_invalid_psbt_input() {
16551687
let mut builder = wallet.build_tx();
16561688
let result =
16571689
builder.add_foreign_utxo(outpoint, psbt::Input::default(), foreign_utxo_satisfaction);
1658-
assert!(matches!(result, Err(AddForeignUtxoError::MissingUtxo)));
1690+
assert!(matches!(result, Err(AddForeignUtxoError::NotForeignUtxo)));
16591691
}
16601692

16611693
#[test]

0 commit comments

Comments
 (0)