From: Alekos Filini Date: Thu, 29 Sep 2022 09:59:21 +0000 (+0200) Subject: Rename internal esplora modules, fix docs X-Git-Tag: v0.23.0-rc.1~1^2~1 X-Git-Url: http://internal-gitweb-vhost/script/%22https:/database/struct.ScanOptions.html?a=commitdiff_plain;h=b11c86d074a8f56f99bd5f3af77d3b056af71de4;p=bdk Rename internal esplora modules, fix docs --- diff --git a/src/blockchain/esplora/async.rs b/src/blockchain/esplora/async.rs new file mode 100644 index 00000000..5ddbdeb4 --- /dev/null +++ b/src/blockchain/esplora/async.rs @@ -0,0 +1,249 @@ +// Bitcoin Dev Kit +// Written in 2020 by Alekos Filini +// +// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers +// +// This file is licensed under the Apache License, Version 2.0 or the MIT license +// , at your option. +// You may not use this file except in accordance with one or both of these +// licenses. + +//! Esplora by way of `reqwest` HTTP client. + +use std::collections::{HashMap, HashSet}; +use std::ops::Deref; + +use bitcoin::{Transaction, Txid}; + +#[allow(unused_imports)] +use log::{debug, error, info, trace}; + +use esplora_client::{convert_fee_rate, AsyncClient, Builder, Tx}; +use futures::stream::{FuturesOrdered, TryStreamExt}; + +use crate::blockchain::*; +use crate::database::BatchDatabase; +use crate::error::Error; +use crate::FeeRate; + +/// Structure that implements the logic to sync with Esplora +/// +/// ## Example +/// See the [`blockchain::esplora`](crate::blockchain::esplora) module for a usage example. +#[derive(Debug)] +pub struct EsploraBlockchain { + url_client: AsyncClient, + stop_gap: usize, + concurrency: u8, +} + +impl std::convert::From for EsploraBlockchain { + fn from(url_client: AsyncClient) -> Self { + EsploraBlockchain { + url_client, + stop_gap: 20, + concurrency: super::DEFAULT_CONCURRENT_REQUESTS, + } + } +} + +impl EsploraBlockchain { + /// Create a new instance of the client from a base URL and `stop_gap`. + pub fn new(base_url: &str, stop_gap: usize) -> Self { + let url_client = Builder::new(base_url) + .build_async() + .expect("Should never fail with no proxy and timeout"); + + Self::from_client(url_client, stop_gap) + } + + /// Build a new instance given a client + pub fn from_client(url_client: AsyncClient, stop_gap: usize) -> Self { + EsploraBlockchain { + url_client, + stop_gap, + concurrency: super::DEFAULT_CONCURRENT_REQUESTS, + } + } + + /// Set the concurrency to use when doing batch queries against the Esplora instance. + pub fn with_concurrency(mut self, concurrency: u8) -> Self { + self.concurrency = concurrency; + self + } +} + +#[maybe_async] +impl Blockchain for EsploraBlockchain { + fn get_capabilities(&self) -> HashSet { + vec![ + Capability::FullHistory, + Capability::GetAnyTx, + Capability::AccurateFees, + ] + .into_iter() + .collect() + } + + fn broadcast(&self, tx: &Transaction) -> Result<(), Error> { + Ok(await_or_block!(self.url_client.broadcast(tx))?) + } + + fn estimate_fee(&self, target: usize) -> Result { + let estimates = await_or_block!(self.url_client.get_fee_estimates())?; + Ok(FeeRate::from_sat_per_vb(convert_fee_rate( + target, estimates, + )?)) + } +} + +impl Deref for EsploraBlockchain { + type Target = AsyncClient; + + fn deref(&self) -> &Self::Target { + &self.url_client + } +} + +impl StatelessBlockchain for EsploraBlockchain {} + +#[maybe_async] +impl GetHeight for EsploraBlockchain { + fn get_height(&self) -> Result { + Ok(await_or_block!(self.url_client.get_height())?) + } +} + +#[maybe_async] +impl GetTx for EsploraBlockchain { + fn get_tx(&self, txid: &Txid) -> Result, Error> { + Ok(await_or_block!(self.url_client.get_tx(txid))?) + } +} + +#[maybe_async] +impl GetBlockHash for EsploraBlockchain { + fn get_block_hash(&self, height: u64) -> Result { + let block_header = await_or_block!(self.url_client.get_header(height as u32))?; + Ok(block_header.block_hash()) + } +} + +#[maybe_async] +impl WalletSync for EsploraBlockchain { + fn wallet_setup( + &self, + database: &mut D, + _progress_update: Box, + ) -> Result<(), Error> { + use crate::blockchain::script_sync::Request; + let mut request = script_sync::start(database, self.stop_gap)?; + let mut tx_index: HashMap = HashMap::new(); + + let batch_update = loop { + request = match request { + Request::Script(script_req) => { + let futures: FuturesOrdered<_> = script_req + .request() + .take(self.concurrency as usize) + .map(|script| async move { + let mut related_txs: Vec = + self.url_client.scripthash_txs(script, None).await?; + + let n_confirmed = + related_txs.iter().filter(|tx| tx.status.confirmed).count(); + // esplora pages on 25 confirmed transactions. If there's 25 or more we + // keep requesting to see if there's more. + if n_confirmed >= 25 { + loop { + let new_related_txs: Vec = self + .url_client + .scripthash_txs( + script, + Some(related_txs.last().unwrap().txid), + ) + .await?; + let n = new_related_txs.len(); + related_txs.extend(new_related_txs); + // we've reached the end + if n < 25 { + break; + } + } + } + Result::<_, Error>::Ok(related_txs) + }) + .collect(); + let txs_per_script: Vec> = await_or_block!(futures.try_collect())?; + let mut satisfaction = vec![]; + + for txs in txs_per_script { + satisfaction.push( + txs.iter() + .map(|tx| (tx.txid, tx.status.block_height)) + .collect(), + ); + for tx in txs { + tx_index.insert(tx.txid, tx); + } + } + + script_req.satisfy(satisfaction)? + } + Request::Conftime(conftime_req) => { + let conftimes = conftime_req + .request() + .map(|txid| { + tx_index + .get(txid) + .expect("must be in index") + .confirmation_time() + .map(Into::into) + }) + .collect(); + conftime_req.satisfy(conftimes)? + } + Request::Tx(tx_req) => { + let full_txs = tx_req + .request() + .map(|txid| { + let tx = tx_index.get(txid).expect("must be in index"); + Ok((tx.previous_outputs(), tx.to_tx())) + }) + .collect::>()?; + tx_req.satisfy(full_txs)? + } + Request::Finish(batch_update) => break batch_update, + } + }; + + database.commit_batch(batch_update)?; + Ok(()) + } +} + +impl ConfigurableBlockchain for EsploraBlockchain { + type Config = super::EsploraBlockchainConfig; + + fn from_config(config: &Self::Config) -> Result { + let mut builder = Builder::new(config.base_url.as_str()); + + if let Some(timeout) = config.timeout { + builder = builder.timeout(timeout); + } + + if let Some(proxy) = &config.proxy { + builder = builder.proxy(proxy); + } + + let mut blockchain = + EsploraBlockchain::from_client(builder.build_async()?, config.stop_gap); + + if let Some(concurrency) = config.concurrency { + blockchain = blockchain.with_concurrency(concurrency); + } + + Ok(blockchain) + } +} diff --git a/src/blockchain/esplora/blocking.rs b/src/blockchain/esplora/blocking.rs new file mode 100644 index 00000000..1e9d1cfc --- /dev/null +++ b/src/blockchain/esplora/blocking.rs @@ -0,0 +1,239 @@ +// Bitcoin Dev Kit +// Written in 2020 by Alekos Filini +// +// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers +// +// This file is licensed under the Apache License, Version 2.0 or the MIT license +// , at your option. +// You may not use this file except in accordance with one or both of these +// licenses. + +//! Esplora by way of `ureq` HTTP client. + +use std::collections::{HashMap, HashSet}; + +#[allow(unused_imports)] +use log::{debug, error, info, trace}; + +use bitcoin::{Transaction, Txid}; + +use esplora_client::{convert_fee_rate, BlockingClient, Builder, Tx}; + +use crate::blockchain::*; +use crate::database::BatchDatabase; +use crate::error::Error; +use crate::FeeRate; + +/// Structure that implements the logic to sync with Esplora +/// +/// ## Example +/// See the [`blockchain::esplora`](crate::blockchain::esplora) module for a usage example. +#[derive(Debug)] +pub struct EsploraBlockchain { + url_client: BlockingClient, + stop_gap: usize, + concurrency: u8, +} + +impl EsploraBlockchain { + /// Create a new instance of the client from a base URL and the `stop_gap`. + pub fn new(base_url: &str, stop_gap: usize) -> Self { + let url_client = Builder::new(base_url) + .build_blocking() + .expect("Should never fail with no proxy and timeout"); + + Self::from_client(url_client, stop_gap) + } + + /// Build a new instance given a client + pub fn from_client(url_client: BlockingClient, stop_gap: usize) -> Self { + EsploraBlockchain { + url_client, + concurrency: super::DEFAULT_CONCURRENT_REQUESTS, + stop_gap, + } + } + + /// Set the number of parallel requests the client can make. + pub fn with_concurrency(mut self, concurrency: u8) -> Self { + self.concurrency = concurrency; + self + } +} + +impl Blockchain for EsploraBlockchain { + fn get_capabilities(&self) -> HashSet { + vec![ + Capability::FullHistory, + Capability::GetAnyTx, + Capability::AccurateFees, + ] + .into_iter() + .collect() + } + + fn broadcast(&self, tx: &Transaction) -> Result<(), Error> { + self.url_client.broadcast(tx)?; + Ok(()) + } + + fn estimate_fee(&self, target: usize) -> Result { + let estimates = self.url_client.get_fee_estimates()?; + Ok(FeeRate::from_sat_per_vb(convert_fee_rate( + target, estimates, + )?)) + } +} + +impl Deref for EsploraBlockchain { + type Target = BlockingClient; + + fn deref(&self) -> &Self::Target { + &self.url_client + } +} + +impl StatelessBlockchain for EsploraBlockchain {} + +impl GetHeight for EsploraBlockchain { + fn get_height(&self) -> Result { + Ok(self.url_client.get_height()?) + } +} + +impl GetTx for EsploraBlockchain { + fn get_tx(&self, txid: &Txid) -> Result, Error> { + Ok(self.url_client.get_tx(txid)?) + } +} + +impl GetBlockHash for EsploraBlockchain { + fn get_block_hash(&self, height: u64) -> Result { + let block_header = self.url_client.get_header(height as u32)?; + Ok(block_header.block_hash()) + } +} + +impl WalletSync for EsploraBlockchain { + fn wallet_setup( + &self, + database: &mut D, + _progress_update: Box, + ) -> Result<(), Error> { + use crate::blockchain::script_sync::Request; + let mut request = script_sync::start(database, self.stop_gap)?; + let mut tx_index: HashMap = HashMap::new(); + let batch_update = loop { + request = match request { + Request::Script(script_req) => { + let scripts = script_req + .request() + .take(self.concurrency as usize) + .cloned(); + + let mut handles = vec![]; + for script in scripts { + let client = self.url_client.clone(); + // make each request in its own thread. + handles.push(std::thread::spawn(move || { + let mut related_txs: Vec = client.scripthash_txs(&script, None)?; + + let n_confirmed = + related_txs.iter().filter(|tx| tx.status.confirmed).count(); + // esplora pages on 25 confirmed transactions. If there's 25 or more we + // keep requesting to see if there's more. + if n_confirmed >= 25 { + loop { + let new_related_txs: Vec = client.scripthash_txs( + &script, + Some(related_txs.last().unwrap().txid), + )?; + let n = new_related_txs.len(); + related_txs.extend(new_related_txs); + // we've reached the end + if n < 25 { + break; + } + } + } + Result::<_, Error>::Ok(related_txs) + })); + } + + let txs_per_script: Vec> = handles + .into_iter() + .map(|handle| handle.join().unwrap()) + .collect::>()?; + let mut satisfaction = vec![]; + + for txs in txs_per_script { + satisfaction.push( + txs.iter() + .map(|tx| (tx.txid, tx.status.block_height)) + .collect(), + ); + for tx in txs { + tx_index.insert(tx.txid, tx); + } + } + + script_req.satisfy(satisfaction)? + } + Request::Conftime(conftime_req) => { + let conftimes = conftime_req + .request() + .map(|txid| { + tx_index + .get(txid) + .expect("must be in index") + .confirmation_time() + .map(Into::into) + }) + .collect(); + conftime_req.satisfy(conftimes)? + } + Request::Tx(tx_req) => { + let full_txs = tx_req + .request() + .map(|txid| { + let tx = tx_index.get(txid).expect("must be in index"); + Ok((tx.previous_outputs(), tx.to_tx())) + }) + .collect::>()?; + tx_req.satisfy(full_txs)? + } + Request::Finish(batch_update) => break batch_update, + } + }; + + database.commit_batch(batch_update)?; + + Ok(()) + } +} + +impl ConfigurableBlockchain for EsploraBlockchain { + type Config = super::EsploraBlockchainConfig; + + fn from_config(config: &Self::Config) -> Result { + let mut builder = Builder::new(config.base_url.as_str()); + + if let Some(timeout) = config.timeout { + builder = builder.timeout(timeout); + } + + if let Some(proxy) = &config.proxy { + builder = builder.proxy(proxy); + } + + let mut blockchain = + EsploraBlockchain::from_client(builder.build_blocking()?, config.stop_gap); + + if let Some(concurrency) = config.concurrency { + blockchain = blockchain.with_concurrency(concurrency); + } + + Ok(blockchain) + } +} diff --git a/src/blockchain/esplora/mod.rs b/src/blockchain/esplora/mod.rs index 7a4b1937..57032e49 100644 --- a/src/blockchain/esplora/mod.rs +++ b/src/blockchain/esplora/mod.rs @@ -15,22 +15,22 @@ //! depending on your needs (blocking or async respectively). //! //! Please note, to configure the Esplora HTTP client correctly use one of: -//! Blocking: --features='esplora,ureq' -//! Async: --features='async-interface,esplora,reqwest' --no-default-features +//! Blocking: --features='use-esplora-blocking' +//! Async: --features='async-interface,use-esplora-async' --no-default-features pub use esplora_client::Error as EsploraError; #[cfg(feature = "use-esplora-async")] -mod reqwest; +mod r#async; #[cfg(feature = "use-esplora-async")] -pub use self::reqwest::*; +pub use self::r#async::*; #[cfg(feature = "use-esplora-blocking")] -mod ureq; +mod blocking; #[cfg(feature = "use-esplora-blocking")] -pub use self::ureq::*; +pub use self::blocking::*; /// Configuration for an [`EsploraBlockchain`] #[derive(Debug, serde::Deserialize, serde::Serialize, Clone, PartialEq)] diff --git a/src/blockchain/esplora/reqwest.rs b/src/blockchain/esplora/reqwest.rs deleted file mode 100644 index 5ddbdeb4..00000000 --- a/src/blockchain/esplora/reqwest.rs +++ /dev/null @@ -1,249 +0,0 @@ -// Bitcoin Dev Kit -// Written in 2020 by Alekos Filini -// -// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers -// -// This file is licensed under the Apache License, Version 2.0 or the MIT license -// , at your option. -// You may not use this file except in accordance with one or both of these -// licenses. - -//! Esplora by way of `reqwest` HTTP client. - -use std::collections::{HashMap, HashSet}; -use std::ops::Deref; - -use bitcoin::{Transaction, Txid}; - -#[allow(unused_imports)] -use log::{debug, error, info, trace}; - -use esplora_client::{convert_fee_rate, AsyncClient, Builder, Tx}; -use futures::stream::{FuturesOrdered, TryStreamExt}; - -use crate::blockchain::*; -use crate::database::BatchDatabase; -use crate::error::Error; -use crate::FeeRate; - -/// Structure that implements the logic to sync with Esplora -/// -/// ## Example -/// See the [`blockchain::esplora`](crate::blockchain::esplora) module for a usage example. -#[derive(Debug)] -pub struct EsploraBlockchain { - url_client: AsyncClient, - stop_gap: usize, - concurrency: u8, -} - -impl std::convert::From for EsploraBlockchain { - fn from(url_client: AsyncClient) -> Self { - EsploraBlockchain { - url_client, - stop_gap: 20, - concurrency: super::DEFAULT_CONCURRENT_REQUESTS, - } - } -} - -impl EsploraBlockchain { - /// Create a new instance of the client from a base URL and `stop_gap`. - pub fn new(base_url: &str, stop_gap: usize) -> Self { - let url_client = Builder::new(base_url) - .build_async() - .expect("Should never fail with no proxy and timeout"); - - Self::from_client(url_client, stop_gap) - } - - /// Build a new instance given a client - pub fn from_client(url_client: AsyncClient, stop_gap: usize) -> Self { - EsploraBlockchain { - url_client, - stop_gap, - concurrency: super::DEFAULT_CONCURRENT_REQUESTS, - } - } - - /// Set the concurrency to use when doing batch queries against the Esplora instance. - pub fn with_concurrency(mut self, concurrency: u8) -> Self { - self.concurrency = concurrency; - self - } -} - -#[maybe_async] -impl Blockchain for EsploraBlockchain { - fn get_capabilities(&self) -> HashSet { - vec![ - Capability::FullHistory, - Capability::GetAnyTx, - Capability::AccurateFees, - ] - .into_iter() - .collect() - } - - fn broadcast(&self, tx: &Transaction) -> Result<(), Error> { - Ok(await_or_block!(self.url_client.broadcast(tx))?) - } - - fn estimate_fee(&self, target: usize) -> Result { - let estimates = await_or_block!(self.url_client.get_fee_estimates())?; - Ok(FeeRate::from_sat_per_vb(convert_fee_rate( - target, estimates, - )?)) - } -} - -impl Deref for EsploraBlockchain { - type Target = AsyncClient; - - fn deref(&self) -> &Self::Target { - &self.url_client - } -} - -impl StatelessBlockchain for EsploraBlockchain {} - -#[maybe_async] -impl GetHeight for EsploraBlockchain { - fn get_height(&self) -> Result { - Ok(await_or_block!(self.url_client.get_height())?) - } -} - -#[maybe_async] -impl GetTx for EsploraBlockchain { - fn get_tx(&self, txid: &Txid) -> Result, Error> { - Ok(await_or_block!(self.url_client.get_tx(txid))?) - } -} - -#[maybe_async] -impl GetBlockHash for EsploraBlockchain { - fn get_block_hash(&self, height: u64) -> Result { - let block_header = await_or_block!(self.url_client.get_header(height as u32))?; - Ok(block_header.block_hash()) - } -} - -#[maybe_async] -impl WalletSync for EsploraBlockchain { - fn wallet_setup( - &self, - database: &mut D, - _progress_update: Box, - ) -> Result<(), Error> { - use crate::blockchain::script_sync::Request; - let mut request = script_sync::start(database, self.stop_gap)?; - let mut tx_index: HashMap = HashMap::new(); - - let batch_update = loop { - request = match request { - Request::Script(script_req) => { - let futures: FuturesOrdered<_> = script_req - .request() - .take(self.concurrency as usize) - .map(|script| async move { - let mut related_txs: Vec = - self.url_client.scripthash_txs(script, None).await?; - - let n_confirmed = - related_txs.iter().filter(|tx| tx.status.confirmed).count(); - // esplora pages on 25 confirmed transactions. If there's 25 or more we - // keep requesting to see if there's more. - if n_confirmed >= 25 { - loop { - let new_related_txs: Vec = self - .url_client - .scripthash_txs( - script, - Some(related_txs.last().unwrap().txid), - ) - .await?; - let n = new_related_txs.len(); - related_txs.extend(new_related_txs); - // we've reached the end - if n < 25 { - break; - } - } - } - Result::<_, Error>::Ok(related_txs) - }) - .collect(); - let txs_per_script: Vec> = await_or_block!(futures.try_collect())?; - let mut satisfaction = vec![]; - - for txs in txs_per_script { - satisfaction.push( - txs.iter() - .map(|tx| (tx.txid, tx.status.block_height)) - .collect(), - ); - for tx in txs { - tx_index.insert(tx.txid, tx); - } - } - - script_req.satisfy(satisfaction)? - } - Request::Conftime(conftime_req) => { - let conftimes = conftime_req - .request() - .map(|txid| { - tx_index - .get(txid) - .expect("must be in index") - .confirmation_time() - .map(Into::into) - }) - .collect(); - conftime_req.satisfy(conftimes)? - } - Request::Tx(tx_req) => { - let full_txs = tx_req - .request() - .map(|txid| { - let tx = tx_index.get(txid).expect("must be in index"); - Ok((tx.previous_outputs(), tx.to_tx())) - }) - .collect::>()?; - tx_req.satisfy(full_txs)? - } - Request::Finish(batch_update) => break batch_update, - } - }; - - database.commit_batch(batch_update)?; - Ok(()) - } -} - -impl ConfigurableBlockchain for EsploraBlockchain { - type Config = super::EsploraBlockchainConfig; - - fn from_config(config: &Self::Config) -> Result { - let mut builder = Builder::new(config.base_url.as_str()); - - if let Some(timeout) = config.timeout { - builder = builder.timeout(timeout); - } - - if let Some(proxy) = &config.proxy { - builder = builder.proxy(proxy); - } - - let mut blockchain = - EsploraBlockchain::from_client(builder.build_async()?, config.stop_gap); - - if let Some(concurrency) = config.concurrency { - blockchain = blockchain.with_concurrency(concurrency); - } - - Ok(blockchain) - } -} diff --git a/src/blockchain/esplora/ureq.rs b/src/blockchain/esplora/ureq.rs deleted file mode 100644 index 1e9d1cfc..00000000 --- a/src/blockchain/esplora/ureq.rs +++ /dev/null @@ -1,239 +0,0 @@ -// Bitcoin Dev Kit -// Written in 2020 by Alekos Filini -// -// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers -// -// This file is licensed under the Apache License, Version 2.0 or the MIT license -// , at your option. -// You may not use this file except in accordance with one or both of these -// licenses. - -//! Esplora by way of `ureq` HTTP client. - -use std::collections::{HashMap, HashSet}; - -#[allow(unused_imports)] -use log::{debug, error, info, trace}; - -use bitcoin::{Transaction, Txid}; - -use esplora_client::{convert_fee_rate, BlockingClient, Builder, Tx}; - -use crate::blockchain::*; -use crate::database::BatchDatabase; -use crate::error::Error; -use crate::FeeRate; - -/// Structure that implements the logic to sync with Esplora -/// -/// ## Example -/// See the [`blockchain::esplora`](crate::blockchain::esplora) module for a usage example. -#[derive(Debug)] -pub struct EsploraBlockchain { - url_client: BlockingClient, - stop_gap: usize, - concurrency: u8, -} - -impl EsploraBlockchain { - /// Create a new instance of the client from a base URL and the `stop_gap`. - pub fn new(base_url: &str, stop_gap: usize) -> Self { - let url_client = Builder::new(base_url) - .build_blocking() - .expect("Should never fail with no proxy and timeout"); - - Self::from_client(url_client, stop_gap) - } - - /// Build a new instance given a client - pub fn from_client(url_client: BlockingClient, stop_gap: usize) -> Self { - EsploraBlockchain { - url_client, - concurrency: super::DEFAULT_CONCURRENT_REQUESTS, - stop_gap, - } - } - - /// Set the number of parallel requests the client can make. - pub fn with_concurrency(mut self, concurrency: u8) -> Self { - self.concurrency = concurrency; - self - } -} - -impl Blockchain for EsploraBlockchain { - fn get_capabilities(&self) -> HashSet { - vec![ - Capability::FullHistory, - Capability::GetAnyTx, - Capability::AccurateFees, - ] - .into_iter() - .collect() - } - - fn broadcast(&self, tx: &Transaction) -> Result<(), Error> { - self.url_client.broadcast(tx)?; - Ok(()) - } - - fn estimate_fee(&self, target: usize) -> Result { - let estimates = self.url_client.get_fee_estimates()?; - Ok(FeeRate::from_sat_per_vb(convert_fee_rate( - target, estimates, - )?)) - } -} - -impl Deref for EsploraBlockchain { - type Target = BlockingClient; - - fn deref(&self) -> &Self::Target { - &self.url_client - } -} - -impl StatelessBlockchain for EsploraBlockchain {} - -impl GetHeight for EsploraBlockchain { - fn get_height(&self) -> Result { - Ok(self.url_client.get_height()?) - } -} - -impl GetTx for EsploraBlockchain { - fn get_tx(&self, txid: &Txid) -> Result, Error> { - Ok(self.url_client.get_tx(txid)?) - } -} - -impl GetBlockHash for EsploraBlockchain { - fn get_block_hash(&self, height: u64) -> Result { - let block_header = self.url_client.get_header(height as u32)?; - Ok(block_header.block_hash()) - } -} - -impl WalletSync for EsploraBlockchain { - fn wallet_setup( - &self, - database: &mut D, - _progress_update: Box, - ) -> Result<(), Error> { - use crate::blockchain::script_sync::Request; - let mut request = script_sync::start(database, self.stop_gap)?; - let mut tx_index: HashMap = HashMap::new(); - let batch_update = loop { - request = match request { - Request::Script(script_req) => { - let scripts = script_req - .request() - .take(self.concurrency as usize) - .cloned(); - - let mut handles = vec![]; - for script in scripts { - let client = self.url_client.clone(); - // make each request in its own thread. - handles.push(std::thread::spawn(move || { - let mut related_txs: Vec = client.scripthash_txs(&script, None)?; - - let n_confirmed = - related_txs.iter().filter(|tx| tx.status.confirmed).count(); - // esplora pages on 25 confirmed transactions. If there's 25 or more we - // keep requesting to see if there's more. - if n_confirmed >= 25 { - loop { - let new_related_txs: Vec = client.scripthash_txs( - &script, - Some(related_txs.last().unwrap().txid), - )?; - let n = new_related_txs.len(); - related_txs.extend(new_related_txs); - // we've reached the end - if n < 25 { - break; - } - } - } - Result::<_, Error>::Ok(related_txs) - })); - } - - let txs_per_script: Vec> = handles - .into_iter() - .map(|handle| handle.join().unwrap()) - .collect::>()?; - let mut satisfaction = vec![]; - - for txs in txs_per_script { - satisfaction.push( - txs.iter() - .map(|tx| (tx.txid, tx.status.block_height)) - .collect(), - ); - for tx in txs { - tx_index.insert(tx.txid, tx); - } - } - - script_req.satisfy(satisfaction)? - } - Request::Conftime(conftime_req) => { - let conftimes = conftime_req - .request() - .map(|txid| { - tx_index - .get(txid) - .expect("must be in index") - .confirmation_time() - .map(Into::into) - }) - .collect(); - conftime_req.satisfy(conftimes)? - } - Request::Tx(tx_req) => { - let full_txs = tx_req - .request() - .map(|txid| { - let tx = tx_index.get(txid).expect("must be in index"); - Ok((tx.previous_outputs(), tx.to_tx())) - }) - .collect::>()?; - tx_req.satisfy(full_txs)? - } - Request::Finish(batch_update) => break batch_update, - } - }; - - database.commit_batch(batch_update)?; - - Ok(()) - } -} - -impl ConfigurableBlockchain for EsploraBlockchain { - type Config = super::EsploraBlockchainConfig; - - fn from_config(config: &Self::Config) -> Result { - let mut builder = Builder::new(config.base_url.as_str()); - - if let Some(timeout) = config.timeout { - builder = builder.timeout(timeout); - } - - if let Some(proxy) = &config.proxy { - builder = builder.proxy(proxy); - } - - let mut blockchain = - EsploraBlockchain::from_client(builder.build_blocking()?, config.stop_gap); - - if let Some(concurrency) = config.concurrency { - blockchain = blockchain.with_concurrency(concurrency); - } - - Ok(blockchain) - } -}