Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 24 additions & 26 deletions src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,17 @@
//!
//! [Esplora API]: <https://github.com/Blockstream/esplora/blob/master/API.md>

use bitcoin::hash_types;
use serde::Deserialize;
use std::collections::HashMap;

pub use bitcoin::consensus::{deserialize, serialize};
use bitcoin::hash_types;
use bitcoin::hash_types::TxMerkleNode;
pub use bitcoin::hex::FromHex;
pub use bitcoin::{
absolute, block, transaction, Address, Amount, Block, BlockHash, CompactTarget, FeeRate,
OutPoint, Script, ScriptBuf, ScriptHash, Transaction, TxIn, TxOut, Txid, Weight, Witness,
Wtxid,
OutPoint, Script, ScriptBuf, ScriptHash, Sequence, Transaction, TxIn, TxOut, Txid, Weight,
Witness, Wtxid,
};

/// An input to a [`Transaction`].
Expand All @@ -35,11 +35,11 @@ pub struct Vin {
pub prevout: Option<Vout>,
/// The [`Script`] that unlocks this input.
pub scriptsig: ScriptBuf,
/// The Witness that unlocks this input.
#[serde(deserialize_with = "deserialize_witness", default)]
pub witness: Vec<Vec<u8>>,
/// The [`Witness`] that unlocks this input.
#[serde(default)]
pub witness: Witness,
/// The sequence value for this input.
pub sequence: u32,
pub sequence: Sequence,
/// Whether this is a coinbase input.
pub is_coinbase: bool,
}
Expand Down Expand Up @@ -118,11 +118,11 @@ pub struct BlockStatus {
pub struct EsploraTx {
/// The [`Txid`] of the [`Transaction`].
pub txid: Txid,
/// The version number of the [`Transaction`].
pub version: i32,
/// The version of the [`Transaction`].
pub version: transaction::Version,
/// The locktime of the [`Transaction`].
/// Sets a time or height after which the [`Transaction`] can be mined.
pub locktime: u32,
pub locktime: absolute::LockTime,
/// The array of inputs in the [`Transaction`].
pub vin: Vec<Vin>,
/// The array of outputs in the [`Transaction`].
Expand Down Expand Up @@ -232,8 +232,9 @@ pub struct BlockSummary {
/// Statistics about an [`Address`].
#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
pub struct AddressStats {
/// The [`Address`], as a [`String`].
pub address: String,
/// The [`Address`].
#[serde(deserialize_with = "deserialize_address_assume_checked")]
pub address: Address,
/// The summary of confirmed [`Transaction`]s for this [`Address`].
pub chain_stats: AddressTxsSummary,
/// The summary of unconfirmed mempool [`Transaction`]s for this [`Address`].
Expand Down Expand Up @@ -399,8 +400,8 @@ impl EsploraTx {
/// and reconstructs the [`Transaction`] from its inputs and outputs.
pub fn to_tx(&self) -> Transaction {
Transaction {
version: transaction::Version::non_standard(self.version),
lock_time: bitcoin::absolute::LockTime::from_consensus(self.locktime),
version: self.version,
lock_time: self.locktime,
input: self
.vin
.iter()
Expand All @@ -411,8 +412,8 @@ impl EsploraTx {
vout: vin.vout,
},
script_sig: vin.scriptsig,
sequence: bitcoin::Sequence(vin.sequence),
witness: Witness::from_slice(&vin.witness),
sequence: vin.sequence,
witness: vin.witness,
})
.collect(),
output: self
Expand Down Expand Up @@ -473,20 +474,17 @@ impl From<&EsploraTx> for Transaction {
}
}

/// Deserializes a witness from a list of hex-encoded strings.
/// Deserializes an [`Address`] from an Esplora address string.
///
/// The Esplora API represents witness data as an array of hex strings,
/// e.g. `["deadbeef", "cafebabe"]`. This deserializer decodes each string
/// into raw bytes.
fn deserialize_witness<'de, D>(d: D) -> Result<Vec<Vec<u8>>, D::Error>
/// Esplora returns address strings without separately providing the expected
/// network, so this deserializer parses the address and assumes the embedded
/// network marker is correct.
fn deserialize_address_assume_checked<'de, D>(d: D) -> Result<Address, D::Error>
where
D: serde::de::Deserializer<'de>,
{
let list = Vec::<String>::deserialize(d)?;
list.into_iter()
.map(|hex_str| Vec::<u8>::from_hex(&hex_str))
.collect::<Result<Vec<Vec<u8>>, _>>()
.map_err(serde::de::Error::custom)
let address = Address::<bitcoin::address::NetworkUnchecked>::deserialize(d)?;
Ok(address.assume_checked())
}

/// Deserializes an optional [`FeeRate`] from an `f64` BTC/kvB value.
Expand Down
14 changes: 7 additions & 7 deletions src/async.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,9 @@ use bitcoin::{Address, Amount, Block, BlockHash, FeeRate, MerkleBlock, Script, T
use bitreq::{Client, Method, Proxy, Request, RequestExt, Response};

use crate::{
is_retryable, is_success, sat_per_vbyte_to_feerate, AddressStats, BlockInfo, BlockStatus,
Builder, Error, EsploraTx, MempoolRecentTx, MempoolStats, MerkleProof, OutputStatus,
ScriptHashStats, SubmitPackageResult, TxStatus, Utxo, BASE_BACKOFF_MILLIS,
duration_to_timeout_secs, is_retryable, is_success, sat_per_vbyte_to_feerate, AddressStats,
BlockInfo, BlockStatus, Builder, Error, EsploraTx, MempoolRecentTx, MempoolStats, MerkleProof,
OutputStatus, ScriptHashStats, SubmitPackageResult, TxStatus, Utxo, BASE_BACKOFF_MILLIS,
};

#[allow(deprecated)]
Expand Down Expand Up @@ -79,8 +79,8 @@ pub struct AsyncClient<S = DefaultSleeper> {
///
/// NOTE: The proxy is ignored when targeting `wasm32`.
proxy: Option<String>,
/// Per-request socket timeout, in seconds.
timeout: Option<u64>,
/// Per-request socket timeout.
timeout: Option<Duration>,
/// HTTP headers to set on every request made to the Esplora server.
headers: HashMap<String, String>,
/// Maximum number of retry attempts for retryable responses.
Expand Down Expand Up @@ -142,8 +142,8 @@ impl<S: Sleeper> AsyncClient<S> {
}

#[cfg(not(target_arch = "wasm32"))]
if let Some(timeout) = &self.timeout {
request = request.with_timeout(*timeout);
if let Some(timeout) = self.timeout {
request = request.with_timeout(duration_to_timeout_secs(timeout));
}

if !self.headers.is_empty() {
Expand Down
15 changes: 8 additions & 7 deletions src/blocking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ use std::collections::{HashMap, HashSet};
use std::convert::TryFrom;
use std::str::FromStr;
use std::thread;
use std::time::Duration;

use bitcoin::consensus::encode::serialize_hex;
use bitreq::{Method, Proxy, Request, Response};
Expand All @@ -42,9 +43,9 @@ use bitcoin::hex::{DisplayHex, FromHex};
use bitcoin::{Address, Amount, Block, BlockHash, FeeRate, MerkleBlock, Script, Transaction, Txid};

use crate::{
is_retryable, is_success, sat_per_vbyte_to_feerate, AddressStats, BlockInfo, BlockStatus,
Builder, Error, EsploraTx, MempoolRecentTx, MempoolStats, MerkleProof, OutputStatus,
ScriptHashStats, SubmitPackageResult, TxStatus, Utxo, BASE_BACKOFF_MILLIS,
duration_to_timeout_secs, is_retryable, is_success, sat_per_vbyte_to_feerate, AddressStats,
BlockInfo, BlockStatus, Builder, Error, EsploraTx, MempoolRecentTx, MempoolStats, MerkleProof,
OutputStatus, ScriptHashStats, SubmitPackageResult, TxStatus, Utxo, BASE_BACKOFF_MILLIS,
};

#[allow(deprecated)]
Expand Down Expand Up @@ -74,8 +75,8 @@ pub struct BlockingClient {
///
/// NOTE: The proxy is ignored when targeting `wasm32`.
pub proxy: Option<String>,
/// Per-request socket timeout, in seconds.
pub timeout: Option<u64>,
/// Per-request socket timeout.
pub timeout: Option<Duration>,
/// HTTP headers to set on every request made to the Esplora server.
pub headers: HashMap<String, String>,
/// Maximum number of retry attempts for retryable responses.
Expand Down Expand Up @@ -115,8 +116,8 @@ impl BlockingClient {
request = request.with_proxy(Proxy::new_http(proxy)?);
}

if let Some(timeout) = &self.timeout {
request = request.with_timeout(*timeout);
if let Some(timeout) = self.timeout {
request = request.with_timeout(duration_to_timeout_secs(timeout));
}

if !self.headers.is_empty() {
Expand Down
23 changes: 17 additions & 6 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,6 @@
use std::collections::HashMap;
use std::fmt;
use std::num::TryFromIntError;
#[cfg(any(feature = "blocking", feature = "async"))]
use std::time::Duration;

#[cfg(feature = "async")]
Expand Down Expand Up @@ -171,6 +170,16 @@ fn is_retryable(response: &Response) -> bool {
RETRYABLE_ERROR_CODES.contains(&(response.status_code as u16))
}

/// Convert a [`Duration`] to whole timeout seconds for `bitreq`.
#[cfg(any(feature = "blocking", feature = "async"))]
fn duration_to_timeout_secs(duration: Duration) -> u64 {
if duration.subsec_nanos() == 0 {
duration.as_secs()
} else {
duration.as_secs().saturating_add(1)
}
}

/// Return the [`FeeRate`] for the given confirmation target in blocks.
///
/// Selects the highest confirmation target from `estimates` that is at or
Expand Down Expand Up @@ -201,10 +210,12 @@ pub fn sat_per_vbyte_to_feerate(estimates: HashMap<u16, f64>) -> HashMap<u16, Fe
/// # Example
///
/// ```no_run
/// use std::time::Duration;
///
/// # #[cfg(feature = "blocking")]
/// # {
/// let client = esplora_client::Builder::new("https://mempool.space/testnet/api")
/// .timeout(30)
/// .timeout(Duration::from_secs(30))
/// .max_retries(4)
/// .header("user-agent", "my-wallet/0.1")
/// .build_blocking();
Expand All @@ -228,8 +239,8 @@ pub struct Builder {
///
/// The proxy is ignored when targeting `wasm32`.
pub proxy: Option<String>,
/// Per-request socket timeout, in seconds.
pub timeout: Option<u64>,
/// Per-request socket timeout.
pub timeout: Option<Duration>,
/// HTTP headers to set on every request made to the Esplora server.
pub headers: HashMap<String, String>,
/// Maximum number of retry attempts for retryable HTTP responses.
Expand Down Expand Up @@ -264,8 +275,8 @@ impl Builder {
self
}

/// Set the per-request socket timeout, in seconds.
pub fn timeout(mut self, timeout: u64) -> Self {
/// Set the per-request socket timeout.
pub fn timeout(mut self, timeout: Duration) -> Self {
self.timeout = Some(timeout);
self
}
Expand Down
Loading