Skip to content

Commit 28a88b0

Browse files
committed
psbt: Add method PsbtParams::add_utxos
- Changed type of `PsbtParams::utxos` field to `HashSet<OutPoint>` - Fix logic of plan error in `create_psbt` - Add test `test_create_psbt_cltv`
1 parent 283fe4a commit 28a88b0

File tree

3 files changed

+84
-11
lines changed

3 files changed

+84
-11
lines changed

wallet/src/psbt/params.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,14 @@ use bdk_tx::DefiniteDescriptor;
66
use bitcoin::{absolute, transaction::Version, Amount, FeeRate, OutPoint, ScriptBuf, Sequence};
77
use miniscript::plan::Assets;
88

9+
use crate::collections::HashSet;
10+
911
/// Parameters to create a PSBT.
1012
#[derive(Debug)]
1113
#[allow(unused)]
1214
pub struct PsbtParams {
1315
// Inputs
14-
pub(crate) utxos: Vec<OutPoint>,
16+
pub(crate) utxos: HashSet<OutPoint>,
1517

1618
// Outputs
1719
pub(crate) recipients: Vec<(ScriptBuf, Amount)>,
@@ -50,6 +52,12 @@ impl Default for PsbtParams {
5052

5153
// TODO: more setters for PsbtParams
5254
impl PsbtParams {
55+
/// Add UTXOs by outpoint to fund the transaction.
56+
pub fn add_utxos(&mut self, outpoints: &[OutPoint]) -> &mut Self {
57+
self.utxos.extend(outpoints);
58+
self
59+
}
60+
5361
/// Add the spend [`Assets`].
5462
///
5563
/// Assets are required to create a spending plan for an output controlled by the wallet's

wallet/src/wallet/mod.rs

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2705,19 +2705,14 @@ impl Wallet {
27052705
};
27062706

27072707
// Get input candidates
2708-
let manually_selected: HashSet<OutPoint> = params.utxos.clone().into_iter().collect();
27092708
let (must_spend, mut may_spend): (Vec<Input>, Vec<Input>) = self
27102709
.list_unspent()
27112710
.flat_map(|output| self.plan_input(&output, &assets))
2712-
.partition(|input| manually_selected.contains(&input.prev_outpoint()));
2711+
.partition(|input| params.utxos.contains(&input.prev_outpoint()));
27132712

2714-
if must_spend
2715-
.iter()
2716-
.map(|input| input.prev_outpoint())
2717-
.any(|op| !manually_selected.contains(&op))
2718-
{
2713+
if must_spend.len() < params.utxos.len() {
27192714
// Try plans again, this time propagating the error.
2720-
for op in manually_selected {
2715+
for op in params.utxos.iter().copied() {
27212716
if self.try_plan(op, &assets).is_none() {
27222717
return Err(CreatePsbtError::Plan(op));
27232718
}

wallet/tests/psbt.rs

Lines changed: 72 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use bdk_chain::BlockId;
2+
use bdk_chain::ConfirmationBlockTime;
13
use bdk_wallet::bitcoin::hashes::Hash;
24
use bdk_wallet::bitcoin::secp256k1;
35
use bdk_wallet::bitcoin::{Amount, FeeRate, Network, Psbt, TxIn};
@@ -17,8 +19,6 @@ fn test_create_psbt() {
1719
.unwrap();
1820

1921
// Receive coins
20-
use bdk_chain::BlockId;
21-
use bdk_chain::ConfirmationBlockTime;
2222
let anchor = ConfirmationBlockTime {
2323
block_id: BlockId {
2424
height: 100,
@@ -67,6 +67,76 @@ fn test_create_psbt() {
6767
.any(|(_, (fp, _))| fp.to_string() == "f6a5cb8b")));
6868
}
6969

70+
#[test]
71+
fn test_create_psbt_cltv() {
72+
use bdk_wallet::CreatePsbtError;
73+
use bitcoin::absolute::LockTime;
74+
use miniscript::plan::Assets;
75+
76+
let desc = get_test_single_sig_cltv();
77+
let mut wallet = Wallet::create_single(desc)
78+
.network(Network::Regtest)
79+
.create_wallet_no_persist()
80+
.unwrap();
81+
82+
// Receive coins
83+
let anchor = ConfirmationBlockTime {
84+
block_id: BlockId {
85+
height: 99_999,
86+
hash: Hash::hash(b"abc"),
87+
},
88+
confirmation_time: 1234567000,
89+
};
90+
insert_checkpoint(&mut wallet, anchor.block_id);
91+
let op = receive_output(&mut wallet, Amount::ONE_BTC, ReceiveTo::Block(anchor));
92+
93+
let mut rng = rand::thread_rng();
94+
95+
let addr = wallet.reveal_next_address(KeychainKind::External);
96+
97+
// No assets fail
98+
{
99+
let mut params = psbt::PsbtParams::default();
100+
params
101+
.add_utxos(&[op])
102+
.add_recipients([(addr.script_pubkey(), Amount::from_btc(0.42).unwrap())]);
103+
let res = wallet.create_psbt(params, &mut rng);
104+
assert!(
105+
matches!(res, Err(CreatePsbtError::Plan(err)) if err == op),
106+
"UTXO requires CLTV but the assets are insufficient",
107+
);
108+
}
109+
110+
// Add assets ok
111+
{
112+
let mut params = psbt::PsbtParams::default();
113+
params
114+
.add_utxos(&[op])
115+
.add_assets(Assets::new().after(LockTime::from_consensus(100_000)))
116+
.add_recipients([(addr.script_pubkey(), Amount::from_btc(0.42).unwrap())]);
117+
let _ = wallet
118+
.create_psbt(params, &mut rng)
119+
.expect("Create psbt should succeed");
120+
}
121+
122+
// New chain tip (no assets) ok
123+
{
124+
let block_id = BlockId {
125+
height: 100_000,
126+
hash: Hash::hash(b"123"),
127+
};
128+
insert_checkpoint(&mut wallet, block_id);
129+
130+
let mut params = psbt::PsbtParams::default();
131+
params
132+
.add_utxos(&[op])
133+
.add_recipients([(addr.script_pubkey(), Amount::from_btc(0.42).unwrap())]);
134+
let _ = wallet
135+
.create_psbt(params, &mut rng)
136+
.expect("Create psbt should succeed");
137+
}
138+
}
139+
70140
#[test]
71141
#[should_panic(expected = "InputIndexOutOfRange")]
72142
fn test_psbt_malformed_psbt_input_legacy() {

0 commit comments

Comments
 (0)