Skip to content

Commit 0eb4a9c

Browse files
committed
Merge #199: test: Move bump fee + foreign utxo tests
ea39339 test: refactor wallet tests (valued mammal) Pull request description: These files are added to wallet/tests directory - `add_foreign_utxo.rs` - `persisted_wallet.rs` - `build_fee_bump`.rs - `common.rs` fixes #238 ### Checklists #### All Submissions: * [x] I followed the [contribution guidelines](https://github.com/bitcoindevkit/bdk/blob/master/CONTRIBUTING.md) ACKs for top commit: tvpeter: tACK ea39339 oleonardolima: ACK ea39339 Tree-SHA512: 50f63a369320fb8df240d78943044e402ab565eb742ccae09b7470150b611d1ab9be89c6838f154f9e90ca577d4f98e3af0a3d344e5b5fdbde674020b3494618
2 parents 890eb05 + ea39339 commit 0eb4a9c

File tree

5 files changed

+1763
-1790
lines changed

5 files changed

+1763
-1790
lines changed

wallet/tests/add_foreign_utxo.rs

Lines changed: 292 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,292 @@
1+
use std::str::FromStr;
2+
3+
use bdk_wallet::psbt::PsbtUtils;
4+
use bdk_wallet::signer::SignOptions;
5+
use bdk_wallet::test_utils::*;
6+
use bdk_wallet::tx_builder::AddForeignUtxoError;
7+
use bdk_wallet::KeychainKind;
8+
use bitcoin::{psbt, Address, Amount};
9+
10+
mod common;
11+
12+
#[test]
13+
fn test_add_foreign_utxo() {
14+
let (mut wallet1, _) = get_funded_wallet_wpkh();
15+
let (wallet2, _) =
16+
get_funded_wallet_single("wpkh(cVbZ8ovhye9AoAHFsqobCf7LxbXDAECy9Kb8TZdfsDYMZGBUyCnm)");
17+
18+
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
19+
.unwrap()
20+
.assume_checked();
21+
let utxo = wallet2.list_unspent().next().expect("must take!");
22+
let foreign_utxo_satisfaction = wallet2
23+
.public_descriptor(KeychainKind::External)
24+
.max_weight_to_satisfy()
25+
.unwrap();
26+
27+
let psbt_input = psbt::Input {
28+
witness_utxo: Some(utxo.txout.clone()),
29+
..Default::default()
30+
};
31+
32+
let mut builder = wallet1.build_tx();
33+
builder
34+
.add_recipient(addr.script_pubkey(), Amount::from_sat(60_000))
35+
.only_witness_utxo()
36+
.add_foreign_utxo(utxo.outpoint, psbt_input, foreign_utxo_satisfaction)
37+
.unwrap();
38+
let mut psbt = builder.finish().unwrap();
39+
wallet1.insert_txout(utxo.outpoint, utxo.txout);
40+
let fee = check_fee!(wallet1, psbt);
41+
let (sent, received) =
42+
wallet1.sent_and_received(&psbt.clone().extract_tx().expect("failed to extract tx"));
43+
44+
assert_eq!(
45+
(sent - received),
46+
Amount::from_sat(10_000) + fee,
47+
"we should have only net spent ~10_000"
48+
);
49+
50+
assert!(
51+
psbt.unsigned_tx
52+
.input
53+
.iter()
54+
.any(|input| input.previous_output == utxo.outpoint),
55+
"foreign_utxo should be in there"
56+
);
57+
58+
let finished = wallet1
59+
.sign(
60+
&mut psbt,
61+
SignOptions {
62+
trust_witness_utxo: true,
63+
..Default::default()
64+
},
65+
)
66+
.unwrap();
67+
68+
assert!(
69+
!finished,
70+
"only one of the inputs should have been signed so far"
71+
);
72+
73+
let finished = wallet2
74+
.sign(
75+
&mut psbt,
76+
SignOptions {
77+
trust_witness_utxo: true,
78+
..Default::default()
79+
},
80+
)
81+
.unwrap();
82+
assert!(finished, "all the inputs should have been signed now");
83+
}
84+
85+
#[test]
86+
fn test_calculate_fee_with_missing_foreign_utxo() {
87+
use bdk_chain::tx_graph::CalculateFeeError;
88+
let (mut wallet1, _) = get_funded_wallet_wpkh();
89+
let (wallet2, _) =
90+
get_funded_wallet_single("wpkh(cVbZ8ovhye9AoAHFsqobCf7LxbXDAECy9Kb8TZdfsDYMZGBUyCnm)");
91+
92+
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
93+
.unwrap()
94+
.assume_checked();
95+
let utxo = wallet2.list_unspent().next().expect("must take!");
96+
let foreign_utxo_satisfaction = wallet2
97+
.public_descriptor(KeychainKind::External)
98+
.max_weight_to_satisfy()
99+
.unwrap();
100+
101+
let psbt_input = psbt::Input {
102+
witness_utxo: Some(utxo.txout.clone()),
103+
..Default::default()
104+
};
105+
106+
let mut builder = wallet1.build_tx();
107+
builder
108+
.add_recipient(addr.script_pubkey(), Amount::from_sat(60_000))
109+
.only_witness_utxo()
110+
.add_foreign_utxo(utxo.outpoint, psbt_input, foreign_utxo_satisfaction)
111+
.unwrap();
112+
let psbt = builder.finish().unwrap();
113+
let tx = psbt.extract_tx().expect("failed to extract tx");
114+
let res = wallet1.calculate_fee(&tx);
115+
assert!(
116+
matches!(res, Err(CalculateFeeError::MissingTxOut(outpoints)) if outpoints[0] == utxo.outpoint)
117+
);
118+
}
119+
120+
#[test]
121+
fn test_add_foreign_utxo_invalid_psbt_input() {
122+
let (mut wallet, _) = get_funded_wallet_wpkh();
123+
let outpoint = wallet.list_unspent().next().expect("must exist").outpoint;
124+
let foreign_utxo_satisfaction = wallet
125+
.public_descriptor(KeychainKind::External)
126+
.max_weight_to_satisfy()
127+
.unwrap();
128+
129+
let mut builder = wallet.build_tx();
130+
let result =
131+
builder.add_foreign_utxo(outpoint, psbt::Input::default(), foreign_utxo_satisfaction);
132+
assert!(matches!(result, Err(AddForeignUtxoError::MissingUtxo)));
133+
}
134+
135+
#[test]
136+
fn test_add_foreign_utxo_where_outpoint_doesnt_match_psbt_input() {
137+
let (mut wallet1, txid1) = get_funded_wallet_wpkh();
138+
let (wallet2, txid2) =
139+
get_funded_wallet_single("wpkh(cVbZ8ovhye9AoAHFsqobCf7LxbXDAECy9Kb8TZdfsDYMZGBUyCnm)");
140+
141+
let utxo2 = wallet2.list_unspent().next().unwrap();
142+
let tx1 = wallet1.get_tx(txid1).unwrap().tx_node.tx.clone();
143+
let tx2 = wallet2.get_tx(txid2).unwrap().tx_node.tx.clone();
144+
145+
let satisfaction_weight = wallet2
146+
.public_descriptor(KeychainKind::External)
147+
.max_weight_to_satisfy()
148+
.unwrap();
149+
150+
let mut builder = wallet1.build_tx();
151+
assert!(
152+
builder
153+
.add_foreign_utxo(
154+
utxo2.outpoint,
155+
psbt::Input {
156+
non_witness_utxo: Some(tx1.as_ref().clone()),
157+
..Default::default()
158+
},
159+
satisfaction_weight
160+
)
161+
.is_err(),
162+
"should fail when outpoint doesn't match psbt_input"
163+
);
164+
assert!(
165+
builder
166+
.add_foreign_utxo(
167+
utxo2.outpoint,
168+
psbt::Input {
169+
non_witness_utxo: Some(tx2.as_ref().clone()),
170+
..Default::default()
171+
},
172+
satisfaction_weight
173+
)
174+
.is_ok(),
175+
"should be ok when outpoint does match psbt_input"
176+
);
177+
}
178+
179+
#[test]
180+
fn test_add_foreign_utxo_only_witness_utxo() {
181+
let (mut wallet1, _) = get_funded_wallet_wpkh();
182+
let (wallet2, txid2) =
183+
get_funded_wallet_single("wpkh(cVbZ8ovhye9AoAHFsqobCf7LxbXDAECy9Kb8TZdfsDYMZGBUyCnm)");
184+
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
185+
.unwrap()
186+
.assume_checked();
187+
let utxo2 = wallet2.list_unspent().next().unwrap();
188+
189+
let satisfaction_weight = wallet2
190+
.public_descriptor(KeychainKind::External)
191+
.max_weight_to_satisfy()
192+
.unwrap();
193+
194+
{
195+
let mut builder = wallet1.build_tx();
196+
builder.add_recipient(addr.script_pubkey(), Amount::from_sat(60_000));
197+
198+
let psbt_input = psbt::Input {
199+
witness_utxo: Some(utxo2.txout.clone()),
200+
..Default::default()
201+
};
202+
builder
203+
.add_foreign_utxo(utxo2.outpoint, psbt_input, satisfaction_weight)
204+
.unwrap();
205+
assert!(
206+
builder.finish().is_err(),
207+
"psbt_input with witness_utxo should fail with only witness_utxo"
208+
);
209+
}
210+
211+
{
212+
let mut builder = wallet1.build_tx();
213+
builder.add_recipient(addr.script_pubkey(), Amount::from_sat(60_000));
214+
215+
let psbt_input = psbt::Input {
216+
witness_utxo: Some(utxo2.txout.clone()),
217+
..Default::default()
218+
};
219+
builder
220+
.only_witness_utxo()
221+
.add_foreign_utxo(utxo2.outpoint, psbt_input, satisfaction_weight)
222+
.unwrap();
223+
assert!(
224+
builder.finish().is_ok(),
225+
"psbt_input with just witness_utxo should succeed when `only_witness_utxo` is enabled"
226+
);
227+
}
228+
229+
{
230+
let mut builder = wallet1.build_tx();
231+
builder.add_recipient(addr.script_pubkey(), Amount::from_sat(60_000));
232+
233+
let tx2 = wallet2.get_tx(txid2).unwrap().tx_node.tx;
234+
let psbt_input = psbt::Input {
235+
non_witness_utxo: Some(tx2.as_ref().clone()),
236+
..Default::default()
237+
};
238+
builder
239+
.add_foreign_utxo(utxo2.outpoint, psbt_input, satisfaction_weight)
240+
.unwrap();
241+
assert!(
242+
builder.finish().is_ok(),
243+
"psbt_input with non_witness_utxo should succeed by default"
244+
);
245+
}
246+
}
247+
248+
#[test]
249+
fn test_taproot_foreign_utxo() {
250+
let (mut wallet1, _) = get_funded_wallet_wpkh();
251+
let (wallet2, _) = get_funded_wallet_single(get_test_tr_single_sig());
252+
253+
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
254+
.unwrap()
255+
.assume_checked();
256+
let utxo = wallet2.list_unspent().next().unwrap();
257+
let psbt_input = wallet2.get_psbt_input(utxo.clone(), None, false).unwrap();
258+
let foreign_utxo_satisfaction = wallet2
259+
.public_descriptor(KeychainKind::External)
260+
.max_weight_to_satisfy()
261+
.unwrap();
262+
263+
assert!(
264+
psbt_input.non_witness_utxo.is_none(),
265+
"`non_witness_utxo` should never be populated for taproot"
266+
);
267+
268+
let mut builder = wallet1.build_tx();
269+
builder
270+
.add_recipient(addr.script_pubkey(), Amount::from_sat(60_000))
271+
.add_foreign_utxo(utxo.outpoint, psbt_input, foreign_utxo_satisfaction)
272+
.unwrap();
273+
let psbt = builder.finish().unwrap();
274+
let (sent, received) =
275+
wallet1.sent_and_received(&psbt.clone().extract_tx().expect("failed to extract tx"));
276+
wallet1.insert_txout(utxo.outpoint, utxo.txout);
277+
let fee = check_fee!(wallet1, psbt);
278+
279+
assert_eq!(
280+
sent - received,
281+
Amount::from_sat(10_000) + fee,
282+
"we should have only net spent ~10_000"
283+
);
284+
285+
assert!(
286+
psbt.unsigned_tx
287+
.input
288+
.iter()
289+
.any(|input| input.previous_output == utxo.outpoint),
290+
"foreign_utxo should be in there"
291+
);
292+
}

0 commit comments

Comments
 (0)