Skip to content

Commit 0531593

Browse files
committed
refactor(esplora): clear remaining panic paths
1 parent faf520d commit 0531593

File tree

3 files changed

+65
-44
lines changed

3 files changed

+65
-44
lines changed

crates/esplora/src/async_ext.rs

Lines changed: 16 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,7 @@ use bdk_core::{
1010
use esplora_client::Sleeper;
1111
use futures::{stream::FuturesOrdered, TryStreamExt};
1212

13-
use crate::{insert_anchor_or_seen_at_from_status, insert_prevouts};
14-
15-
/// [`esplora_client::Error`]
16-
type Error = Box<esplora_client::Error>;
13+
use crate::{insert_anchor_or_seen_at_from_status, insert_prevouts, Error};
1714

1815
/// Trait to extend the functionality of [`esplora_client::AsyncClient`].
1916
///
@@ -256,15 +253,13 @@ async fn chain_update<S: Sleeper>(
256253
let mut tip = match point_of_agreement {
257254
Some(tip) => tip,
258255
None => {
259-
return Err(Box::new(esplora_client::Error::HeaderHashNotFound(
260-
local_cp_hash,
261-
)));
256+
return Err(esplora_client::Error::HeaderHashNotFound(local_cp_hash).into());
262257
}
263258
};
264259

265260
tip = tip
266261
.extend(conflicts.into_iter().rev().map(|b| (b.height, b.hash)))
267-
.expect("evicted are in order");
262+
.map_err(Error::Checkpoint)?;
268263

269264
for (anchor, _txid) in anchors {
270265
let height = anchor.block_id.height;
@@ -314,8 +309,9 @@ where
314309
type TxsOfSpkIndex = (u32, Vec<esplora_client::Tx>, HashSet<Txid>);
315310

316311
let mut update = TxUpdate::<ConfirmationBlockTime>::default();
317-
let mut last_index = Option::<u32>::None;
318312
let mut last_active_index = Option::<u32>::None;
313+
let mut consecutive_unused = 0usize;
314+
let gap_limit = stop_gap.max(parallel_requests.max(1));
319315

320316
loop {
321317
let handles = keychain_spks
@@ -352,8 +348,10 @@ where
352348
}
353349

354350
for (index, txs, evicted) in handles.try_collect::<Vec<TxsOfSpkIndex>>().await? {
355-
last_index = Some(index);
356-
if !txs.is_empty() {
351+
if txs.is_empty() {
352+
consecutive_unused = consecutive_unused.saturating_add(1);
353+
} else {
354+
consecutive_unused = 0;
357355
last_active_index = Some(index);
358356
}
359357
for tx in txs {
@@ -368,13 +366,7 @@ where
368366
.extend(evicted.into_iter().map(|txid| (txid, start_time)));
369367
}
370368

371-
let last_index = last_index.expect("Must be set since handles wasn't empty.");
372-
let gap_limit_reached = if let Some(i) = last_active_index {
373-
last_index >= i.saturating_add(stop_gap as u32)
374-
} else {
375-
last_index + 1 >= stop_gap as u32
376-
};
377-
if gap_limit_reached {
369+
if consecutive_unused >= gap_limit {
378370
break;
379371
}
380372
}
@@ -563,7 +555,10 @@ mod test {
563555
use bdk_testenv::{anyhow, bitcoincore_rpc::RpcApi, TestEnv};
564556
use esplora_client::Builder;
565557

566-
use crate::async_ext::{chain_update, fetch_latest_blocks};
558+
use crate::{
559+
async_ext::{chain_update, fetch_latest_blocks},
560+
Error as EsploraError,
561+
};
567562

568563
macro_rules! h {
569564
($index:literal) => {{
@@ -594,9 +589,9 @@ mod test {
594589

595590
let anchors = BTreeSet::new();
596591
let res = chain_update(&client, &latest_blocks, &cp, &anchors).await;
597-
use esplora_client::Error;
592+
use esplora_client::Error as ClientError;
598593
assert!(
599-
matches!(*res.unwrap_err(), Error::HeaderHashNotFound(hash) if hash == genesis_hash),
594+
matches!(res.unwrap_err(), EsploraError::Client(ClientError::HeaderHashNotFound(hash)) if hash == genesis_hash),
600595
"`chain_update` should error if it can't connect to the local CP",
601596
);
602597

crates/esplora/src/blocking_ext.rs

Lines changed: 14 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,7 @@ use bdk_core::{
99
use esplora_client::{OutputStatus, Tx};
1010
use std::thread::JoinHandle;
1111

12-
use crate::{insert_anchor_or_seen_at_from_status, insert_prevouts};
13-
14-
/// [`esplora_client::Error`]
15-
pub type Error = Box<esplora_client::Error>;
12+
use crate::{insert_anchor_or_seen_at_from_status, insert_prevouts, Error};
1613

1714
/// Trait to extend the functionality of [`esplora_client::BlockingClient`].
1815
///
@@ -241,15 +238,13 @@ fn chain_update(
241238
let mut tip = match point_of_agreement {
242239
Some(tip) => tip,
243240
None => {
244-
return Err(Box::new(esplora_client::Error::HeaderHashNotFound(
245-
local_cp_hash,
246-
)));
241+
return Err(esplora_client::Error::HeaderHashNotFound(local_cp_hash).into());
247242
}
248243
};
249244

250245
tip = tip
251246
.extend(conflicts.into_iter().rev().map(|b| (b.height, b.hash)))
252-
.expect("evicted are in order");
247+
.map_err(Error::Checkpoint)?;
253248

254249
for (anchor, _) in anchors {
255250
let height = anchor.block_id.height;
@@ -282,8 +277,9 @@ fn fetch_txs_with_keychain_spks<I: Iterator<Item = Indexed<SpkWithExpectedTxids>
282277
type TxsOfSpkIndex = (u32, Vec<esplora_client::Tx>, HashSet<Txid>);
283278

284279
let mut update = TxUpdate::<ConfirmationBlockTime>::default();
285-
let mut last_index = Option::<u32>::None;
286280
let mut last_active_index = Option::<u32>::None;
281+
let mut consecutive_unused = 0usize;
282+
let gap_limit = stop_gap.max(1);
287283

288284
loop {
289285
let handles = keychain_spks
@@ -321,8 +317,10 @@ fn fetch_txs_with_keychain_spks<I: Iterator<Item = Indexed<SpkWithExpectedTxids>
321317

322318
for handle in handles {
323319
let (index, txs, evicted) = handle.join().expect("thread must not panic")?;
324-
last_index = Some(index);
325-
if !txs.is_empty() {
320+
if txs.is_empty() {
321+
consecutive_unused = consecutive_unused.saturating_add(1);
322+
} else {
323+
consecutive_unused = 0;
326324
last_active_index = Some(index);
327325
}
328326
for tx in txs {
@@ -337,13 +335,7 @@ fn fetch_txs_with_keychain_spks<I: Iterator<Item = Indexed<SpkWithExpectedTxids>
337335
.extend(evicted.into_iter().map(|txid| (txid, start_time)));
338336
}
339337

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 {
338+
if consecutive_unused >= gap_limit {
347339
break;
348340
}
349341
}
@@ -406,7 +398,7 @@ fn fetch_txs_with_txids<I: IntoIterator<Item = Txid>>(
406398
std::thread::spawn(move || {
407399
client
408400
.get_tx_info(&txid)
409-
.map_err(Box::new)
401+
.map_err(Error::Client)
410402
.map(|t| (txid, t))
411403
})
412404
})
@@ -468,7 +460,7 @@ fn fetch_txs_with_outpoints<I: IntoIterator<Item = OutPoint>>(
468460
std::thread::spawn(move || {
469461
client
470462
.get_output_status(&op.txid, op.vout as _)
471-
.map_err(Box::new)
463+
.map_err(Error::Client)
472464
})
473465
})
474466
.collect::<Vec<JoinHandle<Result<Option<OutputStatus>, Error>>>>();
@@ -512,6 +504,7 @@ fn fetch_txs_with_outpoints<I: IntoIterator<Item = OutPoint>>(
512504
#[cfg_attr(coverage_nightly, coverage(off))]
513505
mod test {
514506
use crate::blocking_ext::{chain_update, fetch_latest_blocks};
507+
use crate::Error as EsploraError;
515508
use bdk_chain::bitcoin;
516509
use bdk_chain::bitcoin::hashes::Hash;
517510
use bdk_chain::bitcoin::Txid;
@@ -562,7 +555,7 @@ mod test {
562555
let res = chain_update(&client, &latest_blocks, &cp, &anchors);
563556
use esplora_client::Error;
564557
assert!(
565-
matches!(*res.unwrap_err(), Error::HeaderHashNotFound(hash) if hash == genesis_hash),
558+
matches!(res.unwrap_err(), EsploraError::Client(Error::HeaderHashNotFound(hash)) if hash == genesis_hash),
566559
"`chain_update` should error if it can't connect to the local CP",
567560
);
568561

crates/esplora/src/lib.rs

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,10 @@
2121
//! [`esplora_client::AsyncClient`].
2222
#![cfg_attr(coverage_nightly, feature(coverage_attribute))]
2323

24-
use bdk_core::bitcoin::{Amount, OutPoint, TxOut, Txid};
25-
use bdk_core::{BlockId, ConfirmationBlockTime, TxUpdate};
24+
use bdk_core::bitcoin::{Amount, BlockHash, OutPoint, TxOut, Txid};
25+
use bdk_core::{BlockId, CheckPoint, ConfirmationBlockTime, TxUpdate};
2626
use esplora_client::TxStatus;
27+
use std::fmt;
2728

2829
pub use esplora_client;
2930

@@ -37,6 +38,38 @@ mod async_ext;
3738
#[cfg(feature = "async")]
3839
pub use async_ext::*;
3940

41+
#[derive(Debug)]
42+
pub enum Error {
43+
Client(esplora_client::Error),
44+
Checkpoint(CheckPoint<BlockHash>),
45+
}
46+
47+
impl From<esplora_client::Error> for Error {
48+
fn from(err: esplora_client::Error) -> Self {
49+
Self::Client(err)
50+
}
51+
}
52+
53+
impl fmt::Display for Error {
54+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
55+
match self {
56+
Self::Client(err) => write!(f, "{err}"),
57+
Self::Checkpoint(cp) => {
58+
write!(f, "checkpoint ordering error at height {}", cp.height())
59+
}
60+
}
61+
}
62+
}
63+
64+
impl std::error::Error for Error {
65+
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
66+
match self {
67+
Self::Client(err) => Some(err),
68+
Self::Checkpoint(_) => None,
69+
}
70+
}
71+
}
72+
4073
#[allow(dead_code)]
4174
fn insert_anchor_or_seen_at_from_status(
4275
update: &mut TxUpdate<ConfirmationBlockTime>,

0 commit comments

Comments
 (0)