Skip to content

Commit 269360f

Browse files
Merge #58: Propagate errors from Esplora backends
76dc3ae doc: add note for MSRV to README (Vladimir Fomene) 48ee09f test: propagate errors to users (Vladimir Fomene) a1fadb0 chore: remove ampersand on pinned dependencies line (Vladimir Fomene) fef4a77 Expose error message for Async client (Vladimir Fomene) 8d974ff Expose error message in addition to status in Blocking client (remix7531) Pull request description: Quick fix for #47 Top commit has no ACKs. Tree-SHA512: eeea5bebcb1d835015ef735e489eb85280e3cf36fa7e576dbe2d85970fffa5ec11c4065279ac5ba841dbb2f7cb29d64f8b12c6fa8eb2a1a8586b45bdbb2587e5
2 parents 615f76f + 76dc3ae commit 269360f

File tree

5 files changed

+289
-74
lines changed

5 files changed

+289
-74
lines changed

.github/workflows/cont_integration.yml

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ jobs:
1616
strategy:
1717
matrix:
1818
rust:
19-
- version: 1.65.0 # STABLE
19+
- version: stable # STABLE
2020
clippy: true
2121
- version: 1.57.0 # MSRV
2222
features:
@@ -50,7 +50,21 @@ jobs:
5050
run: rustup update
5151
- name: pin dependencies
5252
if: matrix.rust.version == '1.57.0'
53-
run: cargo update -p log --precise 0.4.18 && cargo update -p rustls:0.21.2 --precise 0.21.1 && cargo update -p time:0.3.15 --precise 0.3.13
53+
run: |
54+
cargo update -p tokio --precise 1.29.1
55+
cargo update -p reqwest --precise 0.11.18
56+
cargo update -p rustls:0.20.9 --precise 0.20.8
57+
cargo update -p rustix --precise 0.38.6
58+
cargo update -p rustls:0.21.7 --precise 0.21.1
59+
cargo update -p hyper-rustls:0.24.1 --precise 0.24.0
60+
cargo update -p rustls-webpki:0.100.3 --precise 0.100.1
61+
cargo update -p rustls-webpki:0.101.6 --precise 0.101.1
62+
cargo update -p tempfile --precise 3.6.0
63+
cargo update -p h2 --precise 0.3.20
64+
cargo update -p flate2:1.0.27 --precise 1.0.26
65+
cargo update -p cc --precise 1.0.81
66+
cargo update -p tokio-util --precise 0.7.8
67+
cargo update -p time:0.3.15 --precise 0.3.13
5468
- name: Build
5569
run: cargo build --features ${{ matrix.features }} --no-default-features
5670
- name: Clippy

README.md

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,26 @@ Bitcoin Esplora API client library. Supports plaintext, TLS and Onion servers. B
1010
<a href="https://docs.rs/esplora-client"><img alt="API Docs" src="https://img.shields.io/badge/docs.rs-esplora--client-green"/></a>
1111
<a href="https://blog.rust-lang.org/2021/12/02/Rust-1.57.0.html"><img alt="Rustc Version 1.57.0+" src="https://img.shields.io/badge/rustc-1.57.0%2B-lightgrey.svg"/></a>
1212
<a href="https://discord.gg/d7NkDKm"><img alt="Chat on Discord" src="https://img.shields.io/discord/753336465005608961?logo=discord"></a>
13-
</p>
13+
</p>
14+
15+
## Minimum Supported Rust Version (MSRV)
16+
This library should compile with any combination of features with Rust 1.57.0.
17+
18+
To build with the MSRV you will need to pin dependencies as follows:
19+
20+
```shell
21+
cargo update -p tokio --precise 1.29.1
22+
cargo update -p reqwest --precise 0.11.18
23+
cargo update -p rustls:0.20.9 --precise 0.20.8
24+
cargo update -p rustix --precise 0.38.6
25+
cargo update -p rustls:0.21.7 --precise 0.21.1
26+
cargo update -p hyper-rustls:0.24.1 --precise 0.24.0
27+
cargo update -p rustls-webpki:0.100.3 --precise 0.100.1
28+
cargo update -p rustls-webpki:0.101.6 --precise 0.101.1
29+
cargo update -p tempfile --precise 3.6.0
30+
cargo update -p h2 --precise 0.3.20
31+
cargo update -p flate2:1.0.27 --precise 1.0.26
32+
cargo update -p cc --precise 1.0.81
33+
cargo update -p tokio-util --precise 0.7.8
34+
cargo update -p time:0.3.15 --precise 0.3.13
35+
```

src/async.rs

Lines changed: 145 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@ use std::str::FromStr;
1717
use bitcoin::consensus::{deserialize, serialize};
1818
use bitcoin::hashes::hex::FromHex;
1919
use bitcoin::hashes::{sha256, Hash};
20-
use bitcoin::{Block, BlockHash, block::Header as BlockHeader, MerkleBlock, Script, Transaction, Txid};
20+
use bitcoin::{
21+
block::Header as BlockHeader, Block, BlockHash, MerkleBlock, Script, Transaction, Txid,
22+
};
2123
use bitcoin_internals::hex::display::DisplayHex;
2224

2325
#[allow(unused_imports)]
@@ -68,7 +70,14 @@ impl AsyncClient {
6870
return Ok(None);
6971
}
7072

71-
Ok(Some(deserialize(&resp.error_for_status()?.bytes().await?)?))
73+
if resp.status().is_server_error() || resp.status().is_client_error() {
74+
Err(Error::HttpResponse {
75+
status: resp.status().as_u16(),
76+
message: resp.text().await?,
77+
})
78+
} else {
79+
Ok(Some(deserialize(&resp.bytes().await?)?))
80+
}
7281
}
7382

7483
/// Get a [`Transaction`] given its [`Txid`].
@@ -96,7 +105,14 @@ impl AsyncClient {
96105
return Ok(None);
97106
}
98107

99-
Ok(Some(Txid::from_str(&resp.text().await?)?))
108+
if resp.status().is_server_error() || resp.status().is_client_error() {
109+
Err(Error::HttpResponse {
110+
status: resp.status().as_u16(),
111+
message: resp.text().await?,
112+
})
113+
} else {
114+
Ok(Some(Txid::from_str(&resp.text().await?)?))
115+
}
100116
}
101117

102118
/// Get the status of a [`Transaction`] given its [`Txid`].
@@ -106,8 +122,14 @@ impl AsyncClient {
106122
.get(&format!("{}/tx/{}/status", self.url, txid))
107123
.send()
108124
.await?;
109-
110-
Ok(resp.error_for_status()?.json().await?)
125+
if resp.status().is_server_error() || resp.status().is_client_error() {
126+
Err(Error::HttpResponse {
127+
status: resp.status().as_u16(),
128+
message: resp.text().await?,
129+
})
130+
} else {
131+
Ok(resp.json().await?)
132+
}
111133
}
112134

113135
#[deprecated(
@@ -128,9 +150,15 @@ impl AsyncClient {
128150
.send()
129151
.await?;
130152

131-
let header = deserialize(&Vec::from_hex(&resp.text().await?)?)?;
132-
133-
Ok(header)
153+
if resp.status().is_server_error() || resp.status().is_client_error() {
154+
Err(Error::HttpResponse {
155+
status: resp.status().as_u16(),
156+
message: resp.text().await?,
157+
})
158+
} else {
159+
let header = deserialize(&Vec::from_hex(&resp.text().await?)?)?;
160+
Ok(header)
161+
}
134162
}
135163

136164
/// Get the [`BlockStatus`] given a particular [`BlockHash`].
@@ -141,7 +169,14 @@ impl AsyncClient {
141169
.send()
142170
.await?;
143171

144-
Ok(resp.error_for_status()?.json().await?)
172+
if resp.status().is_server_error() || resp.status().is_client_error() {
173+
Err(Error::HttpResponse {
174+
status: resp.status().as_u16(),
175+
message: resp.text().await?,
176+
})
177+
} else {
178+
Ok(resp.json().await?)
179+
}
145180
}
146181

147182
/// Get a [`Block`] given a particular [`BlockHash`].
@@ -155,7 +190,15 @@ impl AsyncClient {
155190
if let StatusCode::NOT_FOUND = resp.status() {
156191
return Ok(None);
157192
}
158-
Ok(Some(deserialize(&resp.error_for_status()?.bytes().await?)?))
193+
194+
if resp.status().is_server_error() || resp.status().is_client_error() {
195+
Err(Error::HttpResponse {
196+
status: resp.status().as_u16(),
197+
message: resp.text().await?,
198+
})
199+
} else {
200+
Ok(Some(deserialize(&resp.bytes().await?)?))
201+
}
159202
}
160203

161204
/// Get a merkle inclusion proof for a [`Transaction`] with the given [`Txid`].
@@ -170,7 +213,14 @@ impl AsyncClient {
170213
return Ok(None);
171214
}
172215

173-
Ok(Some(resp.error_for_status()?.json().await?))
216+
if resp.status().is_server_error() || resp.status().is_client_error() {
217+
Err(Error::HttpResponse {
218+
status: resp.status().as_u16(),
219+
message: resp.text().await?,
220+
})
221+
} else {
222+
Ok(Some(resp.json().await?))
223+
}
174224
}
175225

176226
/// Get a [`MerkleBlock`] inclusion proof for a [`Transaction`] with the given [`Txid`].
@@ -185,9 +235,15 @@ impl AsyncClient {
185235
return Ok(None);
186236
}
187237

188-
let merkle_block = deserialize(&Vec::from_hex(&resp.text().await?)?)?;
189-
190-
Ok(Some(merkle_block))
238+
if resp.status().is_server_error() || resp.status().is_client_error() {
239+
Err(Error::HttpResponse {
240+
status: resp.status().as_u16(),
241+
message: resp.text().await?,
242+
})
243+
} else {
244+
let merkle_block = deserialize(&Vec::from_hex(&resp.text().await?)?)?;
245+
Ok(Some(merkle_block))
246+
}
191247
}
192248

193249
/// Get the spending status of an output given a [`Txid`] and the output index.
@@ -206,19 +262,33 @@ impl AsyncClient {
206262
return Ok(None);
207263
}
208264

209-
Ok(Some(resp.error_for_status()?.json().await?))
265+
if resp.status().is_server_error() || resp.status().is_client_error() {
266+
Err(Error::HttpResponse {
267+
status: resp.status().as_u16(),
268+
message: resp.text().await?,
269+
})
270+
} else {
271+
Ok(Some(resp.json().await?))
272+
}
210273
}
211274

212275
/// Broadcast a [`Transaction`] to Esplora
213276
pub async fn broadcast(&self, transaction: &Transaction) -> Result<(), Error> {
214-
self.client
277+
let resp = self
278+
.client
215279
.post(&format!("{}/tx", self.url))
216280
.body(serialize(transaction).to_lower_hex_string())
217281
.send()
218-
.await?
219-
.error_for_status()?;
282+
.await?;
220283

221-
Ok(())
284+
if resp.status().is_server_error() || resp.status().is_client_error() {
285+
Err(Error::HttpResponse {
286+
status: resp.status().as_u16(),
287+
message: resp.text().await?,
288+
})
289+
} else {
290+
Ok(())
291+
}
222292
}
223293

224294
/// Get the current height of the blockchain tip
@@ -229,7 +299,14 @@ impl AsyncClient {
229299
.send()
230300
.await?;
231301

232-
Ok(resp.error_for_status()?.text().await?.parse()?)
302+
if resp.status().is_server_error() || resp.status().is_client_error() {
303+
Err(Error::HttpResponse {
304+
status: resp.status().as_u16(),
305+
message: resp.text().await?,
306+
})
307+
} else {
308+
Ok(resp.text().await?.parse()?)
309+
}
233310
}
234311

235312
/// Get the [`BlockHash`] of the current blockchain tip.
@@ -240,9 +317,14 @@ impl AsyncClient {
240317
.send()
241318
.await?;
242319

243-
Ok(BlockHash::from_str(
244-
&resp.error_for_status()?.text().await?,
245-
)?)
320+
if resp.status().is_server_error() || resp.status().is_client_error() {
321+
Err(Error::HttpResponse {
322+
status: resp.status().as_u16(),
323+
message: resp.text().await?,
324+
})
325+
} else {
326+
Ok(BlockHash::from_str(&resp.text().await?)?)
327+
}
246328
}
247329

248330
/// Get the [`BlockHash`] of a specific block height
@@ -257,9 +339,14 @@ impl AsyncClient {
257339
return Err(Error::HeaderHeightNotFound(block_height));
258340
}
259341

260-
Ok(BlockHash::from_str(
261-
&resp.error_for_status()?.text().await?,
262-
)?)
342+
if resp.status().is_server_error() || resp.status().is_client_error() {
343+
Err(Error::HttpResponse {
344+
status: resp.status().as_u16(),
345+
message: resp.text().await?,
346+
})
347+
} else {
348+
Ok(BlockHash::from_str(&resp.text().await?)?)
349+
}
263350
}
264351

265352
/// Get confirmed transaction history for the specified address/scripthash,
@@ -278,27 +365,36 @@ impl AsyncClient {
278365
),
279366
None => format!("{}/scripthash/{:x}/txs", self.url, script_hash),
280367
};
281-
Ok(self
282-
.client
283-
.get(url)
284-
.send()
285-
.await?
286-
.error_for_status()?
287-
.json::<Vec<Tx>>()
288-
.await?)
368+
369+
let resp = self.client.get(url).send().await?;
370+
371+
if resp.status().is_server_error() || resp.status().is_client_error() {
372+
Err(Error::HttpResponse {
373+
status: resp.status().as_u16(),
374+
message: resp.text().await?,
375+
})
376+
} else {
377+
Ok(resp.json::<Vec<Tx>>().await?)
378+
}
289379
}
290380

291381
/// Get an map where the key is the confirmation target (in number of blocks)
292382
/// and the value is the estimated feerate (in sat/vB).
293383
pub async fn get_fee_estimates(&self) -> Result<HashMap<String, f64>, Error> {
294-
Ok(self
384+
let resp = self
295385
.client
296386
.get(&format!("{}/fee-estimates", self.url,))
297387
.send()
298-
.await?
299-
.error_for_status()?
300-
.json::<HashMap<String, f64>>()
301-
.await?)
388+
.await?;
389+
390+
if resp.status().is_server_error() || resp.status().is_client_error() {
391+
Err(Error::HttpResponse {
392+
status: resp.status().as_u16(),
393+
message: resp.text().await?,
394+
})
395+
} else {
396+
Ok(resp.json::<HashMap<String, f64>>().await?)
397+
}
302398
}
303399

304400
/// Gets some recent block summaries starting at the tip or at `height` if provided.
@@ -311,14 +407,16 @@ impl AsyncClient {
311407
None => format!("{}/blocks", self.url),
312408
};
313409

314-
Ok(self
315-
.client
316-
.get(&url)
317-
.send()
318-
.await?
319-
.error_for_status()?
320-
.json()
321-
.await?)
410+
let resp = self.client.get(&url).send().await?;
411+
412+
if resp.status().is_server_error() || resp.status().is_client_error() {
413+
Err(Error::HttpResponse {
414+
status: resp.status().as_u16(),
415+
message: resp.text().await?,
416+
})
417+
} else {
418+
Ok(resp.json::<Vec<BlockSummary>>().await?)
419+
}
322420
}
323421

324422
/// Get the underlying base URL.

0 commit comments

Comments
 (0)