@@ -249,7 +249,7 @@ fn chain_update(
249249
250250 tip = tip
251251 . extend ( conflicts. into_iter ( ) . rev ( ) . map ( |b| ( b. height , b. hash ) ) )
252- . expect ( "evicted are in order" ) ;
252+ . map_err ( |_| Box :: new ( esplora_client :: Error :: InvalidResponse ) ) ? ;
253253
254254 for ( anchor, _) in anchors {
255255 let height = anchor. block_id . height ;
@@ -282,8 +282,10 @@ fn fetch_txs_with_keychain_spks<I: Iterator<Item = Indexed<SpkWithExpectedTxids>
282282 type TxsOfSpkIndex = ( u32 , Vec < esplora_client:: Tx > , HashSet < Txid > ) ;
283283
284284 let mut update = TxUpdate :: < ConfirmationBlockTime > :: default ( ) ;
285- let mut last_index = Option :: < u32 > :: None ;
286285 let mut last_active_index = Option :: < u32 > :: None ;
286+ let mut consecutive_unused = 0usize ;
287+ let mut processed_any = false ;
288+ let gap_limit = stop_gap. max ( 1 ) ;
287289
288290 loop {
289291 let handles = keychain_spks
@@ -316,13 +318,22 @@ fn fetch_txs_with_keychain_spks<I: Iterator<Item = Indexed<SpkWithExpectedTxids>
316318 . collect :: < Vec < JoinHandle < Result < TxsOfSpkIndex , Error > > > > ( ) ;
317319
318320 if handles. is_empty ( ) {
321+ if !processed_any {
322+ return Err ( Box :: new ( esplora_client:: Error :: InvalidResponse ) ) ;
323+ }
319324 break ;
320325 }
321326
322327 for handle in handles {
323- let ( index, txs, evicted) = handle. join ( ) . expect ( "thread must not panic" ) ?;
324- last_index = Some ( index) ;
325- if !txs. is_empty ( ) {
328+ let handle_result = handle
329+ . join ( )
330+ . map_err ( |_| Box :: new ( esplora_client:: Error :: InvalidResponse ) ) ?;
331+ let ( index, txs, evicted) = handle_result?;
332+ processed_any = true ;
333+ if txs. is_empty ( ) {
334+ consecutive_unused = consecutive_unused. saturating_add ( 1 ) ;
335+ } else {
336+ consecutive_unused = 0 ;
326337 last_active_index = Some ( index) ;
327338 }
328339 for tx in txs {
@@ -337,13 +348,7 @@ fn fetch_txs_with_keychain_spks<I: Iterator<Item = Indexed<SpkWithExpectedTxids>
337348 . extend ( evicted. into_iter ( ) . map ( |txid| ( txid, start_time) ) ) ;
338349 }
339350
340- let last_index = last_index. expect ( "Must be set since handles wasn't empty." ) ;
341- let gap_limit_reached = if let Some ( i) = last_active_index {
342- last_index >= i. saturating_add ( stop_gap as u32 )
343- } else {
344- last_index + 1 >= stop_gap as u32
345- } ;
346- if gap_limit_reached {
351+ if consecutive_unused >= gap_limit {
347352 break ;
348353 }
349354 }
@@ -417,7 +422,10 @@ fn fetch_txs_with_txids<I: IntoIterator<Item = Txid>>(
417422 }
418423
419424 for handle in handles {
420- let ( txid, tx_info) = handle. join ( ) . expect ( "thread must not panic" ) ?;
425+ let handle_result = handle
426+ . join ( )
427+ . map_err ( |_| Box :: new ( esplora_client:: Error :: InvalidResponse ) ) ?;
428+ let ( txid, tx_info) = handle_result?;
421429 if let Some ( tx_info) = tx_info {
422430 if inserted_txs. insert ( txid) {
423431 update. txs . push ( tx_info. to_tx ( ) . into ( ) ) ;
@@ -478,7 +486,10 @@ fn fetch_txs_with_outpoints<I: IntoIterator<Item = OutPoint>>(
478486 }
479487
480488 for handle in handles {
481- if let Some ( op_status) = handle. join ( ) . expect ( "thread must not panic" ) ? {
489+ let handle_result = handle
490+ . join ( )
491+ . map_err ( |_| Box :: new ( esplora_client:: Error :: InvalidResponse ) ) ?;
492+ if let Some ( op_status) = handle_result? {
482493 let spend_txid = match op_status. txid {
483494 Some ( txid) => txid,
484495 None => continue ,
@@ -511,7 +522,7 @@ fn fetch_txs_with_outpoints<I: IntoIterator<Item = OutPoint>>(
511522#[ cfg( test) ]
512523#[ cfg_attr( coverage_nightly, coverage( off) ) ]
513524mod test {
514- use crate :: blocking_ext:: { chain_update, fetch_latest_blocks} ;
525+ use crate :: blocking_ext:: { chain_update, fetch_latest_blocks, Error } ;
515526 use bdk_chain:: bitcoin;
516527 use bdk_chain:: bitcoin:: hashes:: Hash ;
517528 use bdk_chain:: bitcoin:: Txid ;
@@ -529,6 +540,35 @@ mod test {
529540 } } ;
530541 }
531542
543+ #[ test]
544+ fn thread_join_panic_maps_to_error ( ) {
545+ let handle = std:: thread:: spawn ( || -> Result < ( ) , Error > {
546+ panic ! ( "expected panic for test coverage" ) ;
547+ } ) ;
548+
549+ let res = ( || -> Result < ( ) , Error > {
550+ let handle_result = handle
551+ . join ( )
552+ . map_err ( |_| Box :: new ( esplora_client:: Error :: InvalidResponse ) ) ?;
553+ handle_result?;
554+ Ok ( ( ) )
555+ } ) ( ) ;
556+
557+ assert ! ( matches!(
558+ * res. unwrap_err( ) ,
559+ esplora_client:: Error :: InvalidResponse
560+ ) ) ;
561+ }
562+
563+ #[ test]
564+ fn ensure_last_index_none_returns_error ( ) {
565+ let last_index: Option < u32 > = None ;
566+ let err = last_index
567+ . ok_or_else ( || Box :: new ( esplora_client:: Error :: InvalidResponse ) )
568+ . unwrap_err ( ) ;
569+ assert ! ( matches!( * err, esplora_client:: Error :: InvalidResponse ) ) ;
570+ }
571+
532572 macro_rules! local_chain {
533573 [ $( ( $height: expr, $block_hash: expr) ) , * ] => { {
534574 #[ allow( unused_mut) ]
0 commit comments