1#[macro_use]
13extern crate quote;
14
15use proc_macro::TokenStream;
16
17use syn::spanned::Spanned;
18use syn::{parse, parse2, Ident, ReturnType};
19
20#[proc_macro_attribute]
21pub fn bdk_blockchain_tests(attr: TokenStream, item: TokenStream) -> TokenStream {
22 let root_ident = if !attr.is_empty() {
23 match parse::<syn::ExprPath>(attr) {
24 Ok(parsed) => parsed,
25 Err(e) => {
26 let error_string = e.to_string();
27 return (quote! {
28 compile_error!("Invalid crate path: {:?}", #error_string)
29 })
30 .into();
31 }
32 }
33 } else {
34 parse2::<syn::ExprPath>(quote! { bdk }).unwrap()
35 };
36
37 match parse::<syn::ItemFn>(item) {
38 Err(_) => (quote! {
39 compile_error!("#[bdk_blockchain_tests] can only be used on `fn`s")
40 })
41 .into(),
42 Ok(parsed) => {
43 let parsed_sig_ident = parsed.sig.ident.clone();
44 let mod_name = Ident::new(
45 &format!("generated_tests_{}", parsed_sig_ident.to_string()),
46 parsed.span(),
47 );
48
49 let return_type = match parsed.sig.output {
50 ReturnType::Type(_, ref t) => t.clone(),
51 ReturnType::Default => {
52 return (quote! {
53 compile_error!("The tagged function must return a type that impl `Blockchain`")
54 }).into();
55 }
56 };
57
58 let output = quote! {
59
60 #parsed
61
62 mod #mod_name {
63 use bitcoin::Network;
64
65 use miniscript::Descriptor;
66
67 use testutils::{TestClient, serial};
68
69 use #root_ident::blockchain::{Blockchain, noop_progress};
70 use #root_ident::descriptor::ExtendedDescriptor;
71 use #root_ident::database::MemoryDatabase;
72 use #root_ident::types::KeychainKind;
73 use #root_ident::{Wallet, TxBuilder, FeeRate};
74 use #root_ident::wallet::AddressIndex::New;
75
76 use super::*;
77
78 fn get_blockchain() -> #return_type {
79 #parsed_sig_ident()
80 }
81
82 fn get_wallet_from_descriptors(descriptors: &(String, Option<String>)) -> Wallet<#return_type, MemoryDatabase> {
83 Wallet::new(&descriptors.0.to_string(), descriptors.1.as_ref(), Network::Regtest, MemoryDatabase::new(), get_blockchain()).unwrap()
84 }
85
86 fn init_single_sig() -> (Wallet<#return_type, MemoryDatabase>, (String, Option<String>), TestClient) {
87 let descriptors = testutils! {
88 @descriptors ( "wpkh(Alice)" ) ( "wpkh(Alice)" ) ( @keys ( "Alice" => (@generate_xprv "/44'/0'/0'/0/*", "/44'/0'/0'/1/*") ) )
89 };
90
91 let test_client = TestClient::new();
92 let wallet = get_wallet_from_descriptors(&descriptors);
93
94 (wallet, descriptors, test_client)
95 }
96
97 #[test]
98 #[serial]
99 fn test_sync_simple() {
100 let (wallet, descriptors, mut test_client) = init_single_sig();
101
102 let tx = testutils! {
103 @tx ( (@external descriptors, 0) => 50_000 )
104 };
105 println!("{:?}", tx);
106 let txid = test_client.receive(tx);
107
108 wallet.sync(noop_progress(), None).unwrap();
109
110 assert_eq!(wallet.get_balance().unwrap(), 50_000);
111 assert_eq!(wallet.list_unspent().unwrap()[0].keychain, KeychainKind::External);
112
113 let list_tx_item = &wallet.list_transactions(false).unwrap()[0];
114 assert_eq!(list_tx_item.txid, txid);
115 assert_eq!(list_tx_item.received, 50_000);
116 assert_eq!(list_tx_item.sent, 0);
117 assert_eq!(list_tx_item.height, None);
118 }
119
120 #[test]
121 #[serial]
122 fn test_sync_stop_gap_20() {
123 let (wallet, descriptors, mut test_client) = init_single_sig();
124
125 test_client.receive(testutils! {
126 @tx ( (@external descriptors, 5) => 50_000 )
127 });
128 test_client.receive(testutils! {
129 @tx ( (@external descriptors, 25) => 50_000 )
130 });
131
132 wallet.sync(noop_progress(), None).unwrap();
133
134 assert_eq!(wallet.get_balance().unwrap(), 100_000);
135 assert_eq!(wallet.list_transactions(false).unwrap().len(), 2);
136 }
137
138 #[test]
139 #[serial]
140 fn test_sync_before_and_after_receive() {
141 let (wallet, descriptors, mut test_client) = init_single_sig();
142
143 wallet.sync(noop_progress(), None).unwrap();
144 assert_eq!(wallet.get_balance().unwrap(), 0);
145
146 test_client.receive(testutils! {
147 @tx ( (@external descriptors, 0) => 50_000 )
148 });
149
150 wallet.sync(noop_progress(), None).unwrap();
151
152 assert_eq!(wallet.get_balance().unwrap(), 50_000);
153 assert_eq!(wallet.list_transactions(false).unwrap().len(), 1);
154 }
155
156 #[test]
157 #[serial]
158 fn test_sync_multiple_outputs_same_tx() {
159 let (wallet, descriptors, mut test_client) = init_single_sig();
160
161 let txid = test_client.receive(testutils! {
162 @tx ( (@external descriptors, 0) => 50_000, (@external descriptors, 1) => 25_000, (@external descriptors, 5) => 30_000 )
163 });
164
165 wallet.sync(noop_progress(), None).unwrap();
166
167 assert_eq!(wallet.get_balance().unwrap(), 105_000);
168 assert_eq!(wallet.list_transactions(false).unwrap().len(), 1);
169 assert_eq!(wallet.list_unspent().unwrap().len(), 3);
170
171 let list_tx_item = &wallet.list_transactions(false).unwrap()[0];
172 assert_eq!(list_tx_item.txid, txid);
173 assert_eq!(list_tx_item.received, 105_000);
174 assert_eq!(list_tx_item.sent, 0);
175 assert_eq!(list_tx_item.height, None);
176 }
177
178 #[test]
179 #[serial]
180 fn test_sync_receive_multi() {
181 let (wallet, descriptors, mut test_client) = init_single_sig();
182
183 test_client.receive(testutils! {
184 @tx ( (@external descriptors, 0) => 50_000 )
185 });
186 test_client.receive(testutils! {
187 @tx ( (@external descriptors, 5) => 25_000 )
188 });
189
190 wallet.sync(noop_progress(), None).unwrap();
191
192 assert_eq!(wallet.get_balance().unwrap(), 75_000);
193 assert_eq!(wallet.list_transactions(false).unwrap().len(), 2);
194 assert_eq!(wallet.list_unspent().unwrap().len(), 2);
195 }
196
197 #[test]
198 #[serial]
199 fn test_sync_address_reuse() {
200 let (wallet, descriptors, mut test_client) = init_single_sig();
201
202 test_client.receive(testutils! {
203 @tx ( (@external descriptors, 0) => 50_000 )
204 });
205
206 wallet.sync(noop_progress(), None).unwrap();
207 assert_eq!(wallet.get_balance().unwrap(), 50_000);
208
209 test_client.receive(testutils! {
210 @tx ( (@external descriptors, 0) => 25_000 )
211 });
212
213 wallet.sync(noop_progress(), None).unwrap();
214 assert_eq!(wallet.get_balance().unwrap(), 75_000);
215 }
216
217 #[test]
218 #[serial]
219 fn test_sync_receive_rbf_replaced() {
220 let (wallet, descriptors, mut test_client) = init_single_sig();
221
222 let txid = test_client.receive(testutils! {
223 @tx ( (@external descriptors, 0) => 50_000 ) ( @replaceable true )
224 });
225
226 wallet.sync(noop_progress(), None).unwrap();
227
228 assert_eq!(wallet.get_balance().unwrap(), 50_000);
229 assert_eq!(wallet.list_transactions(false).unwrap().len(), 1);
230 assert_eq!(wallet.list_unspent().unwrap().len(), 1);
231
232 let list_tx_item = &wallet.list_transactions(false).unwrap()[0];
233 assert_eq!(list_tx_item.txid, txid);
234 assert_eq!(list_tx_item.received, 50_000);
235 assert_eq!(list_tx_item.sent, 0);
236 assert_eq!(list_tx_item.height, None);
237
238 let new_txid = test_client.bump_fee(&txid);
239
240 wallet.sync(noop_progress(), None).unwrap();
241
242 assert_eq!(wallet.get_balance().unwrap(), 50_000);
243 assert_eq!(wallet.list_transactions(false).unwrap().len(), 1);
244 assert_eq!(wallet.list_unspent().unwrap().len(), 1);
245
246 let list_tx_item = &wallet.list_transactions(false).unwrap()[0];
247 assert_eq!(list_tx_item.txid, new_txid);
248 assert_eq!(list_tx_item.received, 50_000);
249 assert_eq!(list_tx_item.sent, 0);
250 assert_eq!(list_tx_item.height, None);
251 }
252
253 #[test]
254 #[serial]
255 fn test_sync_reorg_block() {
256 let (wallet, descriptors, mut test_client) = init_single_sig();
257
258 let txid = test_client.receive(testutils! {
259 @tx ( (@external descriptors, 0) => 50_000 ) ( @confirmations 1 ) ( @replaceable true )
260 });
261
262 wallet.sync(noop_progress(), None).unwrap();
263
264 assert_eq!(wallet.get_balance().unwrap(), 50_000);
265 assert_eq!(wallet.list_transactions(false).unwrap().len(), 1);
266 assert_eq!(wallet.list_unspent().unwrap().len(), 1);
267
268 let list_tx_item = &wallet.list_transactions(false).unwrap()[0];
269 assert_eq!(list_tx_item.txid, txid);
270 assert!(list_tx_item.height.is_some());
271
272 test_client.invalidate(1);
274
275 wallet.sync(noop_progress(), None).unwrap();
276
277 assert_eq!(wallet.get_balance().unwrap(), 50_000);
278
279 let list_tx_item = &wallet.list_transactions(false).unwrap()[0];
280 assert_eq!(list_tx_item.txid, txid);
281 assert_eq!(list_tx_item.height, None);
282 }
283
284 #[test]
285 #[serial]
286 fn test_sync_after_send() {
287 let (wallet, descriptors, mut test_client) = init_single_sig();
288 println!("{}", descriptors.0);
289 let node_addr = test_client.get_node_address(None);
290
291 test_client.receive(testutils! {
292 @tx ( (@external descriptors, 0) => 50_000 )
293 });
294
295 wallet.sync(noop_progress(), None).unwrap();
296 assert_eq!(wallet.get_balance().unwrap(), 50_000);
297
298 let mut builder = wallet.build_tx();
299 builder.add_recipient(node_addr.script_pubkey(), 25_000);
300 let (mut psbt, details) = builder.finish().unwrap();
301 let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
302 assert!(finalized, "Cannot finalize transaction");
303 let tx = psbt.extract_tx();
304 println!("{}", bitcoin::consensus::encode::serialize_hex(&tx));
305 wallet.broadcast(tx).unwrap();
306
307 wallet.sync(noop_progress(), None).unwrap();
308 assert_eq!(wallet.get_balance().unwrap(), details.received);
309
310 assert_eq!(wallet.list_transactions(false).unwrap().len(), 2);
311 assert_eq!(wallet.list_unspent().unwrap().len(), 1);
312 }
313
314 #[test]
315 #[serial]
316 fn test_sync_outgoing_from_scratch() {
317 let (wallet, descriptors, mut test_client) = init_single_sig();
318 let node_addr = test_client.get_node_address(None);
319
320 let received_txid = test_client.receive(testutils! {
321 @tx ( (@external descriptors, 0) => 50_000 )
322 });
323
324 wallet.sync(noop_progress(), None).unwrap();
325 assert_eq!(wallet.get_balance().unwrap(), 50_000);
326
327 let mut builder = wallet.build_tx();
328 builder.add_recipient(node_addr.script_pubkey(), 25_000);
329 let (mut psbt, details) = builder.finish().unwrap();
330 let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
331 assert!(finalized, "Cannot finalize transaction");
332 let sent_txid = wallet.broadcast(psbt.extract_tx()).unwrap();
333
334 wallet.sync(noop_progress(), None).unwrap();
335 assert_eq!(wallet.get_balance().unwrap(), details.received);
336
337 let wallet = get_wallet_from_descriptors(&descriptors);
339 wallet.sync(noop_progress(), None).unwrap();
340
341 let tx_map = wallet.list_transactions(false).unwrap().into_iter().map(|tx| (tx.txid, tx)).collect::<std::collections::HashMap<_, _>>();
342
343 let received = tx_map.get(&received_txid).unwrap();
344 assert_eq!(received.received, 50_000);
345 assert_eq!(received.sent, 0);
346
347 let sent = tx_map.get(&sent_txid).unwrap();
348 assert_eq!(sent.received, details.received);
349 assert_eq!(sent.sent, details.sent);
350 assert_eq!(sent.fees, details.fees);
351 }
352
353 #[test]
354 #[serial]
355 fn test_sync_long_change_chain() {
356 let (wallet, descriptors, mut test_client) = init_single_sig();
357 let node_addr = test_client.get_node_address(None);
358
359 test_client.receive(testutils! {
360 @tx ( (@external descriptors, 0) => 50_000 )
361 });
362
363 wallet.sync(noop_progress(), None).unwrap();
364 assert_eq!(wallet.get_balance().unwrap(), 50_000);
365
366 let mut total_sent = 0;
367 for _ in 0..5 {
368 let mut builder = wallet.build_tx();
369 builder.add_recipient(node_addr.script_pubkey(), 5_000);
370 let (mut psbt, details) = builder.finish().unwrap();
371 let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
372 assert!(finalized, "Cannot finalize transaction");
373 wallet.broadcast(psbt.extract_tx()).unwrap();
374
375 wallet.sync(noop_progress(), None).unwrap();
376
377 total_sent += 5_000 + details.fees;
378 }
379
380 wallet.sync(noop_progress(), None).unwrap();
381 assert_eq!(wallet.get_balance().unwrap(), 50_000 - total_sent);
382
383 let wallet = get_wallet_from_descriptors(&descriptors);
385 wallet.sync(noop_progress(), None).unwrap();
386 assert_eq!(wallet.get_balance().unwrap(), 50_000 - total_sent);
387 }
388
389 #[test]
390 #[serial]
391 fn test_sync_bump_fee() {
392 let (wallet, descriptors, mut test_client) = init_single_sig();
393 let node_addr = test_client.get_node_address(None);
394
395 test_client.receive(testutils! {
396 @tx ( (@external descriptors, 0) => 50_000 ) (@confirmations 1)
397 });
398
399 wallet.sync(noop_progress(), None).unwrap();
400 assert_eq!(wallet.get_balance().unwrap(), 50_000);
401
402 let mut builder = wallet.build_tx();
403 builder.add_recipient(node_addr.script_pubkey().clone(), 5_000).enable_rbf();
404 let (mut psbt, details) = builder.finish().unwrap();
405 let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
406 assert!(finalized, "Cannot finalize transaction");
407 wallet.broadcast(psbt.extract_tx()).unwrap();
408 wallet.sync(noop_progress(), None).unwrap();
409 assert_eq!(wallet.get_balance().unwrap(), 50_000 - details.fees - 5_000);
410 assert_eq!(wallet.get_balance().unwrap(), details.received);
411
412 let mut builder = wallet.build_fee_bump(details.txid).unwrap();
413 builder.fee_rate(FeeRate::from_sat_per_vb(2.1));
414 let (mut new_psbt, new_details) = builder.finish().unwrap();
415 let finalized = wallet.sign(&mut new_psbt, Default::default()).unwrap();
416 assert!(finalized, "Cannot finalize transaction");
417 wallet.broadcast(new_psbt.extract_tx()).unwrap();
418 wallet.sync(noop_progress(), None).unwrap();
419 assert_eq!(wallet.get_balance().unwrap(), 50_000 - new_details.fees - 5_000);
420 assert_eq!(wallet.get_balance().unwrap(), new_details.received);
421
422 assert!(new_details.fees > details.fees);
423 }
424
425 #[test]
426 #[serial]
427 fn test_sync_bump_fee_remove_change() {
428 let (wallet, descriptors, mut test_client) = init_single_sig();
429 let node_addr = test_client.get_node_address(None);
430
431 test_client.receive(testutils! {
432 @tx ( (@external descriptors, 0) => 50_000 ) (@confirmations 1)
433 });
434
435 wallet.sync(noop_progress(), None).unwrap();
436 assert_eq!(wallet.get_balance().unwrap(), 50_000);
437
438 let mut builder = wallet.build_tx();
439 builder.add_recipient(node_addr.script_pubkey().clone(), 49_000).enable_rbf();
440 let (mut psbt, details) = builder.finish().unwrap();
441 let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
442 assert!(finalized, "Cannot finalize transaction");
443 wallet.broadcast(psbt.extract_tx()).unwrap();
444 wallet.sync(noop_progress(), None).unwrap();
445 assert_eq!(wallet.get_balance().unwrap(), 1_000 - details.fees);
446 assert_eq!(wallet.get_balance().unwrap(), details.received);
447
448 let mut builder = wallet.build_fee_bump(details.txid).unwrap();
449 builder.fee_rate(FeeRate::from_sat_per_vb(5.0));
450 let (mut new_psbt, new_details) = builder.finish().unwrap();
451 let finalized = wallet.sign(&mut new_psbt, Default::default()).unwrap();
452 assert!(finalized, "Cannot finalize transaction");
453 wallet.broadcast(new_psbt.extract_tx()).unwrap();
454 wallet.sync(noop_progress(), None).unwrap();
455 assert_eq!(wallet.get_balance().unwrap(), 0);
456 assert_eq!(new_details.received, 0);
457
458 assert!(new_details.fees > details.fees);
459 }
460
461 #[test]
462 #[serial]
463 fn test_sync_bump_fee_add_input() {
464 let (wallet, descriptors, mut test_client) = init_single_sig();
465 let node_addr = test_client.get_node_address(None);
466
467 test_client.receive(testutils! {
468 @tx ( (@external descriptors, 0) => 50_000, (@external descriptors, 1) => 25_000 ) (@confirmations 1)
469 });
470
471 wallet.sync(noop_progress(), None).unwrap();
472 assert_eq!(wallet.get_balance().unwrap(), 75_000);
473
474 let mut builder = wallet.build_tx();
475 builder.add_recipient(node_addr.script_pubkey().clone(), 49_000).enable_rbf();
476 let (mut psbt, details) = builder.finish().unwrap();
477 let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
478 assert!(finalized, "Cannot finalize transaction");
479 wallet.broadcast(psbt.extract_tx()).unwrap();
480 wallet.sync(noop_progress(), None).unwrap();
481 assert_eq!(wallet.get_balance().unwrap(), 26_000 - details.fees);
482 assert_eq!(details.received, 1_000 - details.fees);
483
484 let mut builder = wallet.build_fee_bump(details.txid).unwrap();
485 builder.fee_rate(FeeRate::from_sat_per_vb(10.0));
486 let (mut new_psbt, new_details) = builder.finish().unwrap();
487 let finalized = wallet.sign(&mut new_psbt, Default::default()).unwrap();
488 assert!(finalized, "Cannot finalize transaction");
489 wallet.broadcast(new_psbt.extract_tx()).unwrap();
490 wallet.sync(noop_progress(), None).unwrap();
491 assert_eq!(new_details.sent, 75_000);
492 assert_eq!(wallet.get_balance().unwrap(), new_details.received);
493 }
494
495 #[test]
496 #[serial]
497 fn test_sync_bump_fee_add_input_no_change() {
498 let (wallet, descriptors, mut test_client) = init_single_sig();
499 let node_addr = test_client.get_node_address(None);
500
501 test_client.receive(testutils! {
502 @tx ( (@external descriptors, 0) => 50_000, (@external descriptors, 1) => 25_000 ) (@confirmations 1)
503 });
504
505 wallet.sync(noop_progress(), None).unwrap();
506 assert_eq!(wallet.get_balance().unwrap(), 75_000);
507
508 let mut builder = wallet.build_tx();
509 builder.add_recipient(node_addr.script_pubkey().clone(), 49_000).enable_rbf();
510 let (mut psbt, details) = builder.finish().unwrap();
511 let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
512 assert!(finalized, "Cannot finalize transaction");
513 wallet.broadcast(psbt.extract_tx()).unwrap();
514 wallet.sync(noop_progress(), None).unwrap();
515 assert_eq!(wallet.get_balance().unwrap(), 26_000 - details.fees);
516 assert_eq!(details.received, 1_000 - details.fees);
517
518 let mut builder = wallet.build_fee_bump(details.txid).unwrap();
519 builder.fee_rate(FeeRate::from_sat_per_vb(123.0));
520 let (mut new_psbt, new_details) = builder.finish().unwrap();
521 println!("{:#?}", new_details);
522
523 let finalized = wallet.sign(&mut new_psbt, Default::default()).unwrap();
524 assert!(finalized, "Cannot finalize transaction");
525 wallet.broadcast(new_psbt.extract_tx()).unwrap();
526 wallet.sync(noop_progress(), None).unwrap();
527 assert_eq!(new_details.sent, 75_000);
528 assert_eq!(wallet.get_balance().unwrap(), 0);
529 assert_eq!(new_details.received, 0);
530 }
531
532 #[test]
533 #[serial]
534 fn test_sync_receive_coinbase() {
535 let (wallet, descriptors, mut test_client) = init_single_sig();
536 let wallet_addr = wallet.get_address(New).unwrap();
537
538 wallet.sync(noop_progress(), None).unwrap();
539 assert_eq!(wallet.get_balance().unwrap(), 0);
540
541 test_client.generate(1, Some(wallet_addr));
542
543 wallet.sync(noop_progress(), None).unwrap();
544 assert!(wallet.get_balance().unwrap() > 0);
545 }
546 }
547
548 };
549
550 output.into()
551 }
552 }
553}