Skip to content

Commit bb48396

Browse files
committed
Merge #220: More API fixes
515fd53 refactor(api)!: use `bitcoin::LockTime` for `EsploraTx::locktime` (Luis Schwab) f4331b9 refactor(api)!: use `bitcoin::transaction::Version` for `EsploraTx::version` (Luis Schwab) 632a88b refactor(api)!: use `bitcoin::Sequence` for `Vin::sequence` (Luis Schwab) c8b011d refactor(client)!: use `Duration` for timeout (Luis Schwab) de4d799 refactor(api)!: use `bitcoin::Address` for `AddressStats::address` (Luis Schwab) b5b583d refactor(api)!: use `bitcoin::Witness` for `Vin::witness` (Luis Schwab) Pull request description: ## Changelog ``` - Use `bitcoin::Witness` for `Vin::witness` - Use `bitcoin::Address` for `AddressStats::address` - Use `Duration` for `{Builder,AsyncClient,BlockingClient}::timeout` - Use `bitcoin::LockTime` for `EsploraTx::locktime` - Use `bitcoin::transaction::Version` for `EsploraTx::version` - Use `bitcoin::Sequence` for `Vin::sequence` ``` ACKs for top commit: oleonardolima: ACK 515fd53 Tree-SHA512: 0b40fe61d6c0dd4e177d78d3d92be7d4ecfd6fc65c11543cbc0d7e4738ab4685fef6ca9d59fd3d7a7ba35190e61e4fd7e59abb3ee5342636b6bebceed3e1da8b
2 parents 874a28c + 515fd53 commit bb48396

4 files changed

Lines changed: 56 additions & 46 deletions

File tree

src/api.rs

Lines changed: 24 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,17 @@
99
//!
1010
//! [Esplora API]: <https://github.com/Blockstream/esplora/blob/master/API.md>
1111
12-
use bitcoin::hash_types;
1312
use serde::Deserialize;
1413
use std::collections::HashMap;
1514

1615
pub use bitcoin::consensus::{deserialize, serialize};
16+
use bitcoin::hash_types;
1717
use bitcoin::hash_types::TxMerkleNode;
1818
pub use bitcoin::hex::FromHex;
1919
pub use bitcoin::{
2020
absolute, block, transaction, Address, Amount, Block, BlockHash, CompactTarget, FeeRate,
21-
OutPoint, Script, ScriptBuf, ScriptHash, Transaction, TxIn, TxOut, Txid, Weight, Witness,
22-
Wtxid,
21+
OutPoint, Script, ScriptBuf, ScriptHash, Sequence, Transaction, TxIn, TxOut, Txid, Weight,
22+
Witness, Wtxid,
2323
};
2424

2525
/// An input to a [`Transaction`].
@@ -35,11 +35,11 @@ pub struct Vin {
3535
pub prevout: Option<Vout>,
3636
/// The [`Script`] that unlocks this input.
3737
pub scriptsig: ScriptBuf,
38-
/// The Witness that unlocks this input.
39-
#[serde(deserialize_with = "deserialize_witness", default)]
40-
pub witness: Vec<Vec<u8>>,
38+
/// The [`Witness`] that unlocks this input.
39+
#[serde(default)]
40+
pub witness: Witness,
4141
/// The sequence value for this input.
42-
pub sequence: u32,
42+
pub sequence: Sequence,
4343
/// Whether this is a coinbase input.
4444
pub is_coinbase: bool,
4545
}
@@ -118,11 +118,11 @@ pub struct BlockStatus {
118118
pub struct EsploraTx {
119119
/// The [`Txid`] of the [`Transaction`].
120120
pub txid: Txid,
121-
/// The version number of the [`Transaction`].
122-
pub version: i32,
121+
/// The version of the [`Transaction`].
122+
pub version: transaction::Version,
123123
/// The locktime of the [`Transaction`].
124124
/// Sets a time or height after which the [`Transaction`] can be mined.
125-
pub locktime: u32,
125+
pub locktime: absolute::LockTime,
126126
/// The array of inputs in the [`Transaction`].
127127
pub vin: Vec<Vin>,
128128
/// The array of outputs in the [`Transaction`].
@@ -232,8 +232,9 @@ pub struct BlockSummary {
232232
/// Statistics about an [`Address`].
233233
#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
234234
pub struct AddressStats {
235-
/// The [`Address`], as a [`String`].
236-
pub address: String,
235+
/// The [`Address`].
236+
#[serde(deserialize_with = "deserialize_address_assume_checked")]
237+
pub address: Address,
237238
/// The summary of confirmed [`Transaction`]s for this [`Address`].
238239
pub chain_stats: AddressTxsSummary,
239240
/// The summary of unconfirmed mempool [`Transaction`]s for this [`Address`].
@@ -399,8 +400,8 @@ impl EsploraTx {
399400
/// and reconstructs the [`Transaction`] from its inputs and outputs.
400401
pub fn to_tx(&self) -> Transaction {
401402
Transaction {
402-
version: transaction::Version::non_standard(self.version),
403-
lock_time: bitcoin::absolute::LockTime::from_consensus(self.locktime),
403+
version: self.version,
404+
lock_time: self.locktime,
404405
input: self
405406
.vin
406407
.iter()
@@ -411,8 +412,8 @@ impl EsploraTx {
411412
vout: vin.vout,
412413
},
413414
script_sig: vin.scriptsig,
414-
sequence: bitcoin::Sequence(vin.sequence),
415-
witness: Witness::from_slice(&vin.witness),
415+
sequence: vin.sequence,
416+
witness: vin.witness,
416417
})
417418
.collect(),
418419
output: self
@@ -473,20 +474,17 @@ impl From<&EsploraTx> for Transaction {
473474
}
474475
}
475476

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

492490
/// Deserializes an optional [`FeeRate`] from an `f64` BTC/kvB value.

src/async.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,9 @@ use bitcoin::{Address, Amount, Block, BlockHash, FeeRate, MerkleBlock, Script, T
4646
use bitreq::{Client, Method, Proxy, Request, RequestExt, Response};
4747

4848
use crate::{
49-
is_retryable, is_success, sat_per_vbyte_to_feerate, AddressStats, BlockInfo, BlockStatus,
50-
Builder, Error, EsploraTx, MempoolRecentTx, MempoolStats, MerkleProof, OutputStatus,
51-
ScriptHashStats, SubmitPackageResult, TxStatus, Utxo, BASE_BACKOFF_MILLIS,
49+
duration_to_timeout_secs, is_retryable, is_success, sat_per_vbyte_to_feerate, AddressStats,
50+
BlockInfo, BlockStatus, Builder, Error, EsploraTx, MempoolRecentTx, MempoolStats, MerkleProof,
51+
OutputStatus, ScriptHashStats, SubmitPackageResult, TxStatus, Utxo, BASE_BACKOFF_MILLIS,
5252
};
5353

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

144144
#[cfg(not(target_arch = "wasm32"))]
145-
if let Some(timeout) = &self.timeout {
146-
request = request.with_timeout(*timeout);
145+
if let Some(timeout) = self.timeout {
146+
request = request.with_timeout(duration_to_timeout_secs(timeout));
147147
}
148148

149149
if !self.headers.is_empty() {

src/blocking.rs

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ use std::collections::{HashMap, HashSet};
3131
use std::convert::TryFrom;
3232
use std::str::FromStr;
3333
use std::thread;
34+
use std::time::Duration;
3435

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

4445
use crate::{
45-
is_retryable, is_success, sat_per_vbyte_to_feerate, AddressStats, BlockInfo, BlockStatus,
46-
Builder, Error, EsploraTx, MempoolRecentTx, MempoolStats, MerkleProof, OutputStatus,
47-
ScriptHashStats, SubmitPackageResult, TxStatus, Utxo, BASE_BACKOFF_MILLIS,
46+
duration_to_timeout_secs, is_retryable, is_success, sat_per_vbyte_to_feerate, AddressStats,
47+
BlockInfo, BlockStatus, Builder, Error, EsploraTx, MempoolRecentTx, MempoolStats, MerkleProof,
48+
OutputStatus, ScriptHashStats, SubmitPackageResult, TxStatus, Utxo, BASE_BACKOFF_MILLIS,
4849
};
4950

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

118-
if let Some(timeout) = &self.timeout {
119-
request = request.with_timeout(*timeout);
119+
if let Some(timeout) = self.timeout {
120+
request = request.with_timeout(duration_to_timeout_secs(timeout));
120121
}
121122

122123
if !self.headers.is_empty() {

src/lib.rs

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,6 @@
9393
use std::collections::HashMap;
9494
use std::fmt;
9595
use std::num::TryFromIntError;
96-
#[cfg(any(feature = "blocking", feature = "async"))]
9796
use std::time::Duration;
9897

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

173+
/// Convert a [`Duration`] to whole timeout seconds for `bitreq`.
174+
#[cfg(any(feature = "blocking", feature = "async"))]
175+
fn duration_to_timeout_secs(duration: Duration) -> u64 {
176+
if duration.subsec_nanos() == 0 {
177+
duration.as_secs()
178+
} else {
179+
duration.as_secs().saturating_add(1)
180+
}
181+
}
182+
174183
/// Return the [`FeeRate`] for the given confirmation target in blocks.
175184
///
176185
/// Selects the highest confirmation target from `estimates` that is at or
@@ -201,10 +210,12 @@ pub fn sat_per_vbyte_to_feerate(estimates: HashMap<u16, f64>) -> HashMap<u16, Fe
201210
/// # Example
202211
///
203212
/// ```no_run
213+
/// use std::time::Duration;
214+
///
204215
/// # #[cfg(feature = "blocking")]
205216
/// # {
206217
/// let client = esplora_client::Builder::new("https://mempool.space/testnet/api")
207-
/// .timeout(30)
218+
/// .timeout(Duration::from_secs(30))
208219
/// .max_retries(4)
209220
/// .header("user-agent", "my-wallet/0.1")
210221
/// .build_blocking();
@@ -228,8 +239,8 @@ pub struct Builder {
228239
///
229240
/// The proxy is ignored when targeting `wasm32`.
230241
pub proxy: Option<String>,
231-
/// Per-request socket timeout, in seconds.
232-
pub timeout: Option<u64>,
242+
/// Per-request socket timeout.
243+
pub timeout: Option<Duration>,
233244
/// HTTP headers to set on every request made to the Esplora server.
234245
pub headers: HashMap<String, String>,
235246
/// Maximum number of retry attempts for retryable HTTP responses.
@@ -264,8 +275,8 @@ impl Builder {
264275
self
265276
}
266277

267-
/// Set the per-request socket timeout, in seconds.
268-
pub fn timeout(mut self, timeout: u64) -> Self {
278+
/// Set the per-request socket timeout.
279+
pub fn timeout(mut self, timeout: Duration) -> Self {
269280
self.timeout = Some(timeout);
270281
self
271282
}

0 commit comments

Comments
 (0)