bitcoindevkit / bitcoindevkit/bdk_wallet
Reachable panic in `Utxo::txout`
- Dominant language
- Rust
- Stars
- 59
- Forks
- 105
- Avg merge
- 10d 9h
- Merged PRs (30d)
- 1
Description
`Utxo::txout()` returns `&TxOut` but contains two reachable panics for the `Foreign` variant:
1. `prev_tx.output[outpoint.vout as usize]` — panics if the attached `non_witness_utxo` has fewer outputs than the `outpoint.vout` indicates.
2. `unreachable!("Foreign UTXOs will always have one of these set")` — asserts an invariant the type does not enforce. Because `Utxo::Foreign` is a public enum variant with public fields, a caller can theoretically construct one with an empty `psbt::Input`, making this branch reachable.
The validation that guards against these states lives in `TxBuilder::add_foreign_utxo_with_sequence` but is bypassed by any direct construction of `Utxo::Foreign`.
**To Reproduce**
```rust
let utxo = Utxo::Foreign {
outpoint: OutPoint::null(),
sequence: Sequence::MAX,
psbt_input: Box::new(psbt::Input::default()),
};
utxo.txout();
```
**Expected behavior**
`txout()` should not panic on a value that is constructable through the public API.
**Build environment**
- BDK tag/commit: current master
- OS+version:
- Rust/Cargo version:
- Rust/Cargo target:
**Which backend(s) are relevant (if any)?**
- [x] None / not backend-related
**Is this blocking production use?**
- [ ] Yes
- [x] No
**Additional context**
Raised during review of #445. Three options identified:
1. ~~Make `Utxo::Foreign` wrap a type with private fields~~.
- [x] Short-term non-breaking — collapse the two `if let` arms and the `unreachable!` into a single chained `Option` with an honest `.expect("Foreign UTXOs should have one of witness_utxo, non_witness_utxo set")`. #487
- [ ] Return `Option<&TxOut>` from `txout()` (breaking).
Contributor guide
Assessment
This issue has not been assessed yet.