11use bdk_chain:: {
22 bitcoin:: { hashes:: Hash , Address , Amount , ScriptBuf , Txid , WScriptHash } ,
33 local_chain:: LocalChain ,
4- spk_client:: { FullScanRequest , SyncRequest } ,
4+ spk_client:: { FullScanRequest , SyncRequest , SyncResult } ,
55 spk_txout:: SpkTxOutIndex ,
6- Balance , ConfirmationBlockTime , IndexedTxGraph ,
6+ Balance , ConfirmationBlockTime , IndexedTxGraph , Indexer , Merge ,
77} ;
88use bdk_electrum:: BdkElectrumClient ;
99use bdk_testenv:: { anyhow, bitcoincore_rpc:: RpcApi , TestEnv } ;
10+ use core:: time:: Duration ;
1011use std:: collections:: { BTreeSet , HashSet } ;
1112use std:: str:: FromStr ;
1213
14+ // Batch size for `sync_with_electrum`.
15+ const BATCH_SIZE : usize = 5 ;
16+
1317fn get_balance (
1418 recv_chain : & LocalChain ,
1519 recv_graph : & IndexedTxGraph < ConfirmationBlockTime , SpkTxOutIndex < ( ) > > ,
@@ -22,6 +26,39 @@ fn get_balance(
2226 Ok ( balance)
2327}
2428
29+ fn sync_with_electrum < I , Spks > (
30+ client : & BdkElectrumClient < electrum_client:: Client > ,
31+ spks : Spks ,
32+ chain : & mut LocalChain ,
33+ graph : & mut IndexedTxGraph < ConfirmationBlockTime , I > ,
34+ ) -> anyhow:: Result < SyncResult >
35+ where
36+ I : Indexer ,
37+ I :: ChangeSet : Default + Merge ,
38+ Spks : IntoIterator < Item = ScriptBuf > ,
39+ Spks :: IntoIter : ExactSizeIterator + Send + ' static ,
40+ {
41+ let mut update = client. sync (
42+ SyncRequest :: from_chain_tip ( chain. tip ( ) ) . chain_spks ( spks) ,
43+ BATCH_SIZE ,
44+ true ,
45+ ) ?;
46+
47+ // Update `last_seen` to be able to calculate balance for unconfirmed transactions.
48+ let now = std:: time:: UNIX_EPOCH
49+ . elapsed ( )
50+ . expect ( "must get time" )
51+ . as_secs ( ) ;
52+ let _ = update. graph_update . update_last_seen_unconfirmed ( now) ;
53+
54+ let _ = chain
55+ . apply_update ( update. chain_update . clone ( ) )
56+ . map_err ( |err| anyhow:: anyhow!( "LocalChain update error: {:?}" , err) ) ?;
57+ let _ = graph. apply_update ( update. graph_update . clone ( ) ) ;
58+
59+ Ok ( update)
60+ }
61+
2562#[ test]
2663pub fn test_update_tx_graph_without_keychain ( ) -> anyhow:: Result < ( ) > {
2764 let env = TestEnv :: new ( ) ?;
@@ -60,7 +97,7 @@ pub fn test_update_tx_graph_without_keychain() -> anyhow::Result<()> {
6097 None ,
6198 ) ?;
6299 env. mine_blocks ( 1 , None ) ?;
63- env. wait_until_electrum_sees_block ( ) ?;
100+ env. wait_until_electrum_sees_block ( Duration :: from_secs ( 6 ) ) ?;
64101
65102 // use a full checkpoint linked list (since this is not what we are testing)
66103 let cp_tip = env. make_checkpoint_tip ( ) ;
@@ -162,7 +199,7 @@ pub fn test_update_tx_graph_stop_gap() -> anyhow::Result<()> {
162199 None ,
163200 ) ?;
164201 env. mine_blocks ( 1 , None ) ?;
165- env. wait_until_electrum_sees_block ( ) ?;
202+ env. wait_until_electrum_sees_block ( Duration :: from_secs ( 6 ) ) ?;
166203
167204 // use a full checkpoint linked list (since this is not what we are testing)
168205 let cp_tip = env. make_checkpoint_tip ( ) ;
@@ -204,7 +241,7 @@ pub fn test_update_tx_graph_stop_gap() -> anyhow::Result<()> {
204241 None ,
205242 ) ?;
206243 env. mine_blocks ( 1 , None ) ?;
207- env. wait_until_electrum_sees_block ( ) ?;
244+ env. wait_until_electrum_sees_block ( Duration :: from_secs ( 6 ) ) ?;
208245
209246 // A scan with gap limit 5 won't find the second transaction, but a scan with gap limit 6 will.
210247 // The last active indice won't be updated in the first case but will in the second one.
@@ -238,14 +275,11 @@ pub fn test_update_tx_graph_stop_gap() -> anyhow::Result<()> {
238275 Ok ( ( ) )
239276}
240277
241- /// Ensure that [`ElectrumExt`] can sync properly.
242- ///
243- /// 1. Mine 101 blocks.
244- /// 2. Send a tx.
245- /// 3. Mine extra block to confirm sent tx.
246- /// 4. Check [`Balance`] to ensure tx is confirmed.
278+ /// Ensure that [`BdkElectrumClient::sync`] can confirm previously unconfirmed transactions in both
279+ /// reorg and no-reorg situations. After the transaction is confirmed after reorg, check if floating
280+ /// txouts for previous outputs were inserted for transaction fee calculation.
247281#[ test]
248- fn scan_detects_confirmed_tx ( ) -> anyhow:: Result < ( ) > {
282+ fn test_sync ( ) -> anyhow:: Result < ( ) > {
249283 const SEND_AMOUNT : Amount = Amount :: from_sat ( 10_000 ) ;
250284
251285 let env = TestEnv :: new ( ) ?;
@@ -271,35 +305,88 @@ fn scan_detects_confirmed_tx() -> anyhow::Result<()> {
271305
272306 // Mine some blocks.
273307 env. mine_blocks ( 101 , Some ( addr_to_mine) ) ?;
308+ env. wait_until_electrum_sees_block ( Duration :: from_secs ( 6 ) ) ?;
274309
275- // Create transaction that is tracked by our receiver.
276- env. send ( & addr_to_track, SEND_AMOUNT ) ?;
310+ // Broadcast transaction to mempool.
311+ let txid = env. send ( & addr_to_track, SEND_AMOUNT ) ?;
312+ env. wait_until_electrum_sees_txid ( txid, Duration :: from_secs ( 6 ) ) ?;
277313
278- // Mine a block to confirm sent tx.
314+ sync_with_electrum (
315+ & client,
316+ [ spk_to_track. clone ( ) ] ,
317+ & mut recv_chain,
318+ & mut recv_graph,
319+ ) ?;
320+
321+ // Check for unconfirmed balance when transaction exists only in mempool.
322+ assert_eq ! (
323+ get_balance( & recv_chain, & recv_graph) ?,
324+ Balance {
325+ trusted_pending: SEND_AMOUNT ,
326+ ..Balance :: default ( )
327+ } ,
328+ "balance must be correct" ,
329+ ) ;
330+
331+ // Mine block to confirm transaction.
279332 env. mine_blocks ( 1 , None ) ?;
333+ env. wait_until_electrum_sees_block ( Duration :: from_secs ( 6 ) ) ?;
280334
281- // Sync up to tip.
282- env. wait_until_electrum_sees_block ( ) ?;
283- let update = client. sync (
284- SyncRequest :: from_chain_tip ( recv_chain. tip ( ) ) . chain_spks ( core:: iter:: once ( spk_to_track) ) ,
285- 5 ,
286- true ,
335+ sync_with_electrum (
336+ & client,
337+ [ spk_to_track. clone ( ) ] ,
338+ & mut recv_chain,
339+ & mut recv_graph,
287340 ) ?;
288341
289- let _ = recv_chain
290- . apply_update ( update. chain_update )
291- . map_err ( |err| anyhow:: anyhow!( "LocalChain update error: {:?}" , err) ) ?;
292- let _ = recv_graph. apply_update ( update. graph_update ) ;
342+ // Check if balance is correct when transaction is confirmed.
343+ assert_eq ! (
344+ get_balance( & recv_chain, & recv_graph) ?,
345+ Balance {
346+ confirmed: SEND_AMOUNT ,
347+ ..Balance :: default ( )
348+ } ,
349+ "balance must be correct" ,
350+ ) ;
351+
352+ // Perform reorg on block with confirmed transaction.
353+ env. reorg_empty_blocks ( 1 ) ?;
354+ env. wait_until_electrum_sees_block ( Duration :: from_secs ( 6 ) ) ?;
355+
356+ sync_with_electrum (
357+ & client,
358+ [ spk_to_track. clone ( ) ] ,
359+ & mut recv_chain,
360+ & mut recv_graph,
361+ ) ?;
293362
294- // Check to see if tx is confirmed.
363+ // Check if balance is correct when transaction returns to mempool.
364+ assert_eq ! (
365+ get_balance( & recv_chain, & recv_graph) ?,
366+ Balance {
367+ trusted_pending: SEND_AMOUNT ,
368+ ..Balance :: default ( )
369+ } ,
370+ ) ;
371+
372+ // Mine block to confirm transaction again.
373+ env. mine_blocks ( 1 , None ) ?;
374+ env. wait_until_electrum_sees_block ( Duration :: from_secs ( 6 ) ) ?;
375+
376+ sync_with_electrum ( & client, [ spk_to_track] , & mut recv_chain, & mut recv_graph) ?;
377+
378+ // Check if balance is correct once transaction is confirmed again.
295379 assert_eq ! (
296380 get_balance( & recv_chain, & recv_graph) ?,
297381 Balance {
298382 confirmed: SEND_AMOUNT ,
299383 ..Balance :: default ( )
300384 } ,
385+ "balance must be correct" ,
301386 ) ;
302387
388+ // Check to see if we have the floating txouts available from our transactions' previous outputs
389+ // in order to calculate transaction fees.
303390 for tx in recv_graph. graph ( ) . full_txs ( ) {
304391 // Retrieve the calculated fee from `TxGraph`, which will panic if we do not have the
305392 // floating txouts available from the transaction's previous outputs.
@@ -371,18 +458,14 @@ fn tx_can_become_unconfirmed_after_reorg() -> anyhow::Result<()> {
371458 }
372459
373460 // Sync up to tip.
374- env. wait_until_electrum_sees_block ( ) ?;
375- let update = client. sync (
376- SyncRequest :: from_chain_tip ( recv_chain. tip ( ) ) . chain_spks ( [ spk_to_track. clone ( ) ] ) ,
377- 5 ,
378- false ,
461+ env. wait_until_electrum_sees_block ( Duration :: from_secs ( 6 ) ) ?;
462+ let update = sync_with_electrum (
463+ & client,
464+ [ spk_to_track. clone ( ) ] ,
465+ & mut recv_chain,
466+ & mut recv_graph,
379467 ) ?;
380468
381- let _ = recv_chain
382- . apply_update ( update. chain_update )
383- . map_err ( |err| anyhow:: anyhow!( "LocalChain update error: {:?}" , err) ) ?;
384- let _ = recv_graph. apply_update ( update. graph_update . clone ( ) ) ;
385-
386469 // Retain a snapshot of all anchors before reorg process.
387470 let initial_anchors = update. graph_update . all_anchors ( ) ;
388471 let anchors: Vec < _ > = initial_anchors. iter ( ) . cloned ( ) . collect ( ) ;
@@ -407,24 +490,21 @@ fn tx_can_become_unconfirmed_after_reorg() -> anyhow::Result<()> {
407490 for depth in 1 ..=REORG_COUNT {
408491 env. reorg_empty_blocks ( depth) ?;
409492
410- env. wait_until_electrum_sees_block ( ) ?;
411- let update = client. sync (
412- SyncRequest :: from_chain_tip ( recv_chain. tip ( ) ) . chain_spks ( [ spk_to_track. clone ( ) ] ) ,
413- 5 ,
414- false ,
493+ env. wait_until_electrum_sees_block ( Duration :: from_secs ( 6 ) ) ?;
494+ let update = sync_with_electrum (
495+ & client,
496+ [ spk_to_track. clone ( ) ] ,
497+ & mut recv_chain,
498+ & mut recv_graph,
415499 ) ?;
416500
417- let _ = recv_chain
418- . apply_update ( update. chain_update )
419- . map_err ( |err| anyhow:: anyhow!( "LocalChain update error: {:?}" , err) ) ?;
420-
421501 // Check that no new anchors are added during current reorg.
422502 assert ! ( initial_anchors. is_superset( update. graph_update. all_anchors( ) ) ) ;
423- let _ = recv_graph. apply_update ( update. graph_update ) ;
424503
425504 assert_eq ! (
426505 get_balance( & recv_chain, & recv_graph) ?,
427506 Balance {
507+ trusted_pending: SEND_AMOUNT * depth as u64 ,
428508 confirmed: SEND_AMOUNT * ( REORG_COUNT - depth) as u64 ,
429509 ..Balance :: default ( )
430510 } ,
0 commit comments