1use std::collections::HashMap;
15use std::convert::TryFrom;
16use std::str::FromStr;
17use std::thread;
18
19use bitcoin::consensus::encode::serialize_hex;
20#[allow(unused_imports)]
21use log::{debug, error, info, trace};
22
23use minreq::{Proxy, Request, Response};
24
25use bitcoin::block::Header as BlockHeader;
26use bitcoin::consensus::{deserialize, serialize, Decodable};
27use bitcoin::hashes::{sha256, Hash};
28use bitcoin::hex::{DisplayHex, FromHex};
29use bitcoin::{Address, Block, BlockHash, MerkleBlock, Script, Transaction, Txid};
30
31use crate::{
32 AddressStats, BlockInfo, BlockStatus, BlockSummary, Builder, Error, MempoolRecentTx,
33 MempoolStats, MerkleProof, OutputStatus, ScriptHashStats, SubmitPackageResult, Tx, TxStatus,
34 Utxo, BASE_BACKOFF_MILLIS, RETRYABLE_ERROR_CODES,
35};
36
37#[derive(Debug, Clone)]
39pub struct BlockingClient {
40 url: String,
42 pub proxy: Option<String>,
44 pub timeout: Option<u64>,
46 pub headers: HashMap<String, String>,
48 pub max_retries: usize,
50}
51
52impl BlockingClient {
53 pub fn from_builder(builder: Builder) -> Self {
55 Self {
56 url: builder.base_url,
57 proxy: builder.proxy,
58 timeout: builder.timeout,
59 headers: builder.headers,
60 max_retries: builder.max_retries,
61 }
62 }
63
64 pub fn url(&self) -> &str {
66 &self.url
67 }
68
69 pub fn get_request(&self, path: &str) -> Result<Request, Error> {
71 let mut request = minreq::get(format!("{}{}", self.url, path));
72
73 if let Some(proxy) = &self.proxy {
74 let proxy = Proxy::new(proxy.as_str())?;
75 request = request.with_proxy(proxy);
76 }
77
78 if let Some(timeout) = &self.timeout {
79 request = request.with_timeout(*timeout);
80 }
81
82 if !self.headers.is_empty() {
83 for (key, value) in &self.headers {
84 request = request.with_header(key, value);
85 }
86 }
87
88 Ok(request)
89 }
90
91 fn post_request<T>(&self, path: &str, body: T) -> Result<Request, Error>
92 where
93 T: Into<Vec<u8>>,
94 {
95 let mut request = minreq::post(format!("{}{}", self.url, path)).with_body(body);
96
97 if let Some(proxy) = &self.proxy {
98 let proxy = Proxy::new(proxy.as_str())?;
99 request = request.with_proxy(proxy);
100 }
101
102 if let Some(timeout) = &self.timeout {
103 request = request.with_timeout(*timeout);
104 }
105
106 Ok(request)
107 }
108
109 fn get_opt_response<T: Decodable>(&self, path: &str) -> Result<Option<T>, Error> {
110 match self.get_with_retry(path) {
111 Ok(resp) if is_status_not_found(resp.status_code) => Ok(None),
112 Ok(resp) if !is_status_ok(resp.status_code) => {
113 let status = u16::try_from(resp.status_code).map_err(Error::StatusCode)?;
114 let message = resp.as_str().unwrap_or_default().to_string();
115 Err(Error::HttpResponse { status, message })
116 }
117 Ok(resp) => Ok(Some(deserialize::<T>(resp.as_bytes())?)),
118 Err(e) => Err(e),
119 }
120 }
121
122 fn get_opt_response_txid(&self, path: &str) -> Result<Option<Txid>, Error> {
123 match self.get_with_retry(path) {
124 Ok(resp) if is_status_not_found(resp.status_code) => Ok(None),
125 Ok(resp) if !is_status_ok(resp.status_code) => {
126 let status = u16::try_from(resp.status_code).map_err(Error::StatusCode)?;
127 let message = resp.as_str().unwrap_or_default().to_string();
128 Err(Error::HttpResponse { status, message })
129 }
130 Ok(resp) => Ok(Some(
131 Txid::from_str(resp.as_str().map_err(Error::Minreq)?).map_err(Error::HexToArray)?,
132 )),
133 Err(e) => Err(e),
134 }
135 }
136
137 fn get_opt_response_hex<T: Decodable>(&self, path: &str) -> Result<Option<T>, Error> {
138 match self.get_with_retry(path) {
139 Ok(resp) if is_status_not_found(resp.status_code) => Ok(None),
140 Ok(resp) if !is_status_ok(resp.status_code) => {
141 let status = u16::try_from(resp.status_code).map_err(Error::StatusCode)?;
142 let message = resp.as_str().unwrap_or_default().to_string();
143 Err(Error::HttpResponse { status, message })
144 }
145 Ok(resp) => {
146 let hex_str = resp.as_str().map_err(Error::Minreq)?;
147 let hex_vec = Vec::from_hex(hex_str)?;
148 deserialize::<T>(&hex_vec)
149 .map_err(Error::BitcoinEncoding)
150 .map(|r| Some(r))
151 }
152 Err(e) => Err(e),
153 }
154 }
155
156 fn get_response_hex<T: Decodable>(&self, path: &str) -> Result<T, Error> {
157 match self.get_with_retry(path) {
158 Ok(resp) if !is_status_ok(resp.status_code) => {
159 let status = u16::try_from(resp.status_code).map_err(Error::StatusCode)?;
160 let message = resp.as_str().unwrap_or_default().to_string();
161 Err(Error::HttpResponse { status, message })
162 }
163 Ok(resp) => {
164 let hex_str = resp.as_str().map_err(Error::Minreq)?;
165 let hex_vec = Vec::from_hex(hex_str)?;
166 deserialize::<T>(&hex_vec).map_err(Error::BitcoinEncoding)
167 }
168 Err(e) => Err(e),
169 }
170 }
171
172 fn get_response_json<'a, T: serde::de::DeserializeOwned>(
173 &'a self,
174 path: &'a str,
175 ) -> Result<T, Error> {
176 let response = self.get_with_retry(path);
177 match response {
178 Ok(resp) if !is_status_ok(resp.status_code) => {
179 let status = u16::try_from(resp.status_code).map_err(Error::StatusCode)?;
180 let message = resp.as_str().unwrap_or_default().to_string();
181 Err(Error::HttpResponse { status, message })
182 }
183 Ok(resp) => Ok(resp.json::<T>().map_err(Error::Minreq)?),
184 Err(e) => Err(e),
185 }
186 }
187
188 fn get_opt_response_json<T: serde::de::DeserializeOwned>(
189 &self,
190 path: &str,
191 ) -> Result<Option<T>, Error> {
192 match self.get_with_retry(path) {
193 Ok(resp) if is_status_not_found(resp.status_code) => Ok(None),
194 Ok(resp) if !is_status_ok(resp.status_code) => {
195 let status = u16::try_from(resp.status_code).map_err(Error::StatusCode)?;
196 let message = resp.as_str().unwrap_or_default().to_string();
197 Err(Error::HttpResponse { status, message })
198 }
199 Ok(resp) => Ok(Some(resp.json::<T>()?)),
200 Err(e) => Err(e),
201 }
202 }
203
204 fn get_response_str(&self, path: &str) -> Result<String, Error> {
205 match self.get_with_retry(path) {
206 Ok(resp) if !is_status_ok(resp.status_code) => {
207 let status = u16::try_from(resp.status_code).map_err(Error::StatusCode)?;
208 let message = resp.as_str().unwrap_or_default().to_string();
209 Err(Error::HttpResponse { status, message })
210 }
211 Ok(resp) => Ok(resp.as_str()?.to_string()),
212 Err(e) => Err(e),
213 }
214 }
215
216 pub fn get_tx(&self, txid: &Txid) -> Result<Option<Transaction>, Error> {
218 self.get_opt_response(&format!("/tx/{txid}/raw"))
219 }
220
221 pub fn get_tx_no_opt(&self, txid: &Txid) -> Result<Transaction, Error> {
223 match self.get_tx(txid) {
224 Ok(Some(tx)) => Ok(tx),
225 Ok(None) => Err(Error::TransactionNotFound(*txid)),
226 Err(e) => Err(e),
227 }
228 }
229
230 pub fn get_txid_at_block_index(
233 &self,
234 block_hash: &BlockHash,
235 index: usize,
236 ) -> Result<Option<Txid>, Error> {
237 self.get_opt_response_txid(&format!("/block/{block_hash}/txid/{index}"))
238 }
239
240 pub fn get_tx_status(&self, txid: &Txid) -> Result<TxStatus, Error> {
242 self.get_response_json(&format!("/tx/{txid}/status"))
243 }
244
245 pub fn get_tx_info(&self, txid: &Txid) -> Result<Option<Tx>, Error> {
247 self.get_opt_response_json(&format!("/tx/{txid}"))
248 }
249
250 pub fn get_tx_outspends(&self, txid: &Txid) -> Result<Vec<OutputStatus>, Error> {
252 self.get_response_json(&format!("/tx/{txid}/outspends"))
253 }
254
255 pub fn get_header_by_hash(&self, block_hash: &BlockHash) -> Result<BlockHeader, Error> {
257 self.get_response_hex(&format!("/block/{block_hash}/header"))
258 }
259
260 pub fn get_block_status(&self, block_hash: &BlockHash) -> Result<BlockStatus, Error> {
262 self.get_response_json(&format!("/block/{block_hash}/status"))
263 }
264
265 pub fn get_block_by_hash(&self, block_hash: &BlockHash) -> Result<Option<Block>, Error> {
267 self.get_opt_response(&format!("/block/{block_hash}/raw"))
268 }
269
270 pub fn get_merkle_proof(&self, txid: &Txid) -> Result<Option<MerkleProof>, Error> {
273 self.get_opt_response_json(&format!("/tx/{txid}/merkle-proof"))
274 }
275
276 pub fn get_merkle_block(&self, txid: &Txid) -> Result<Option<MerkleBlock>, Error> {
279 self.get_opt_response_hex(&format!("/tx/{txid}/merkleblock-proof"))
280 }
281
282 pub fn get_output_status(
285 &self,
286 txid: &Txid,
287 index: u64,
288 ) -> Result<Option<OutputStatus>, Error> {
289 self.get_opt_response_json(&format!("/tx/{txid}/outspend/{index}"))
290 }
291
292 pub fn broadcast(&self, transaction: &Transaction) -> Result<(), Error> {
294 let request = self.post_request(
295 "/tx",
296 serialize(transaction)
297 .to_lower_hex_string()
298 .as_bytes()
299 .to_vec(),
300 )?;
301
302 match request.send() {
303 Ok(resp) if !is_status_ok(resp.status_code) => {
304 let status = u16::try_from(resp.status_code).map_err(Error::StatusCode)?;
305 let message = resp.as_str().unwrap_or_default().to_string();
306 Err(Error::HttpResponse { status, message })
307 }
308 Ok(_resp) => Ok(()),
309 Err(e) => Err(Error::Minreq(e)),
310 }
311 }
312
313 pub fn submit_package(
322 &self,
323 transactions: &[Transaction],
324 maxfeerate: Option<f64>,
325 maxburnamount: Option<f64>,
326 ) -> Result<SubmitPackageResult, Error> {
327 let serialized_txs = transactions
328 .iter()
329 .map(|tx| serialize_hex(&tx))
330 .collect::<Vec<_>>();
331
332 let mut request = self.post_request(
333 "/txs/package",
334 serde_json::to_string(&serialized_txs)
335 .unwrap_or_default()
336 .into_bytes(),
337 )?;
338
339 if let Some(maxfeerate) = maxfeerate {
340 request = request.with_param("maxfeerate", maxfeerate.to_string())
341 }
342
343 if let Some(maxburnamount) = maxburnamount {
344 request = request.with_param("maxburnamount", maxburnamount.to_string())
345 }
346
347 match request.send() {
348 Ok(resp) if !is_status_ok(resp.status_code) => {
349 let status = u16::try_from(resp.status_code).map_err(Error::StatusCode)?;
350 let message = resp.as_str().unwrap_or_default().to_string();
351 Err(Error::HttpResponse { status, message })
352 }
353 Ok(resp) => Ok(resp.json::<SubmitPackageResult>().map_err(Error::Minreq)?),
354 Err(e) => Err(Error::Minreq(e)),
355 }
356 }
357
358 pub fn get_height(&self) -> Result<u32, Error> {
360 self.get_response_str("/blocks/tip/height")
361 .map(|s| u32::from_str(s.as_str()).map_err(Error::Parsing))?
362 }
363
364 pub fn get_tip_hash(&self) -> Result<BlockHash, Error> {
366 self.get_response_str("/blocks/tip/hash")
367 .map(|s| BlockHash::from_str(s.as_str()).map_err(Error::HexToArray))?
368 }
369
370 pub fn get_block_hash(&self, block_height: u32) -> Result<BlockHash, Error> {
372 self.get_response_str(&format!("/block-height/{block_height}"))
373 .map(|s| BlockHash::from_str(s.as_str()).map_err(Error::HexToArray))?
374 }
375
376 pub fn get_mempool_stats(&self) -> Result<MempoolStats, Error> {
378 self.get_response_json("/mempool")
379 }
380
381 pub fn get_mempool_recent_txs(&self) -> Result<Vec<MempoolRecentTx>, Error> {
383 self.get_response_json("/mempool/recent")
384 }
385
386 pub fn get_mempool_txids(&self) -> Result<Vec<Txid>, Error> {
390 self.get_response_json("/mempool/txids")
391 }
392
393 pub fn get_fee_estimates(&self) -> Result<HashMap<u16, f64>, Error> {
396 self.get_response_json("/fee-estimates")
397 }
398
399 pub fn get_address_stats(&self, address: &Address) -> Result<AddressStats, Error> {
402 let path = format!("/address/{address}");
403 self.get_response_json(&path)
404 }
405
406 pub fn get_scripthash_stats(&self, script: &Script) -> Result<ScriptHashStats, Error> {
408 let script_hash = sha256::Hash::hash(script.as_bytes());
409 let path = format!("/scripthash/{script_hash}");
410 self.get_response_json(&path)
411 }
412
413 pub fn get_address_txs(
419 &self,
420 address: &Address,
421 last_seen: Option<Txid>,
422 ) -> Result<Vec<Tx>, Error> {
423 let path = match last_seen {
424 Some(last_seen) => format!("/address/{address}/txs/chain/{last_seen}"),
425 None => format!("/address/{address}/txs"),
426 };
427
428 self.get_response_json(&path)
429 }
430
431 pub fn get_mempool_address_txs(&self, address: &Address) -> Result<Vec<Tx>, Error> {
433 let path = format!("/address/{address}/txs/mempool");
434
435 self.get_response_json(&path)
436 }
437
438 pub fn scripthash_txs(
443 &self,
444 script: &Script,
445 last_seen: Option<Txid>,
446 ) -> Result<Vec<Tx>, Error> {
447 let script_hash = sha256::Hash::hash(script.as_bytes());
448 let path = match last_seen {
449 Some(last_seen) => format!("/scripthash/{script_hash:x}/txs/chain/{last_seen}"),
450 None => format!("/scripthash/{script_hash:x}/txs"),
451 };
452 self.get_response_json(&path)
453 }
454
455 pub fn get_mempool_scripthash_txs(&self, script: &Script) -> Result<Vec<Tx>, Error> {
458 let script_hash = sha256::Hash::hash(script.as_bytes());
459 let path = format!("/scripthash/{script_hash:x}/txs/mempool");
460
461 self.get_response_json(&path)
462 }
463
464 pub fn get_block_info(&self, blockhash: &BlockHash) -> Result<BlockInfo, Error> {
466 let path = format!("/block/{blockhash}");
467
468 self.get_response_json(&path)
469 }
470
471 pub fn get_block_txids(&self, blockhash: &BlockHash) -> Result<Vec<Txid>, Error> {
473 let path = format!("/block/{blockhash}/txids");
474
475 self.get_response_json(&path)
476 }
477
478 pub fn get_block_txs(
484 &self,
485 blockhash: &BlockHash,
486 start_index: Option<u32>,
487 ) -> Result<Vec<Tx>, Error> {
488 let path = match start_index {
489 None => format!("/block/{blockhash}/txs"),
490 Some(start_index) => format!("/block/{blockhash}/txs/{start_index}"),
491 };
492
493 self.get_response_json(&path)
494 }
495
496 pub fn get_blocks(&self, height: Option<u32>) -> Result<Vec<BlockSummary>, Error> {
502 let path = match height {
503 Some(height) => format!("/blocks/{height}"),
504 None => "/blocks".to_string(),
505 };
506 let blocks: Vec<BlockSummary> = self.get_response_json(&path)?;
507 if blocks.is_empty() {
508 return Err(Error::InvalidResponse);
509 }
510 Ok(blocks)
511 }
512
513 pub fn get_address_utxos(&self, address: &Address) -> Result<Vec<Utxo>, Error> {
515 let path = format!("/address/{address}/utxo");
516
517 self.get_response_json(&path)
518 }
519
520 pub fn get_scripthash_utxos(&self, script: &Script) -> Result<Vec<Utxo>, Error> {
522 let script_hash = sha256::Hash::hash(script.as_bytes());
523 let path = format!("/scripthash/{script_hash}/utxo");
524
525 self.get_response_json(&path)
526 }
527
528 fn get_with_retry(&self, url: &str) -> Result<Response, Error> {
531 let mut delay = BASE_BACKOFF_MILLIS;
532 let mut attempts = 0;
533
534 loop {
535 match self.get_request(url)?.send()? {
536 resp if attempts < self.max_retries && is_status_retryable(resp.status_code) => {
537 thread::sleep(delay);
538 attempts += 1;
539 delay *= 2;
540 }
541 resp => return Ok(resp),
542 }
543 }
544 }
545}
546
547fn is_status_ok(status: i32) -> bool {
548 status == 200
549}
550
551fn is_status_not_found(status: i32) -> bool {
552 status == 404
553}
554
555fn is_status_retryable(status: i32) -> bool {
556 let status = status as u16;
557 RETRYABLE_ERROR_CODES.contains(&status)
558}