diff --git a/Cargo.toml b/Cargo.toml index 5137c7f2..994ea1be 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,12 +11,12 @@ miniscript = { version = "0.12" } serde = { version = "^1.0", features = ["derive"] } serde_json = { version = "^1.0" } base64 = "^0.11" -async-trait = "0.1" # Optional dependencies sled = { version = "0.31.0", optional = true } -electrum-client = { git = "https://github.com/MagicalBitcoin/rust-electrum-client.git", optional = true } +electrum-client = { version = "0.2.0-beta.1", optional = true } reqwest = { version = "0.10", optional = true, features = ["json"] } +tokio = { version = "0.2", optional = true, features = ["rt-core"] } futures = { version = "0.3", optional = true } clap = { version = "2.33", optional = true } @@ -25,12 +25,11 @@ minimal = [] compiler = ["miniscript/compiler"] default = ["key-value-db", "electrum"] electrum = ["electrum-client"] -esplora = ["reqwest", "futures"] +esplora = ["reqwest", "futures", "tokio"] key-value-db = ["sled"] cli-utils = ["clap"] [dev-dependencies] -tokio = { version = "0.2", features = ["macros"] } lazy_static = "1.4" rustyline = "6.0" dirs = "2.0" diff --git a/examples/repl.rs b/examples/repl.rs index 5eaf3f42..1b76321c 100644 --- a/examples/repl.rs +++ b/examples/repl.rs @@ -32,8 +32,7 @@ fn prepare_home_dir() -> PathBuf { dir } -#[tokio::main] -async fn main() { +fn main() { env_logger::init(); let app = cli::make_cli_subcommands(); @@ -65,9 +64,11 @@ async fn main() { .unwrap(); debug!("database opened successfully"); - let client = Client::new(matches.value_of("server").unwrap()) - .await - .unwrap(); + let client = Client::new( + matches.value_of("server").unwrap(), + matches.value_of("proxy"), + ) + .unwrap(); let wallet = Wallet::new( descriptor, change_descriptor, @@ -75,7 +76,6 @@ async fn main() { tree, ElectrumBlockchain::from(client), ) - .await .unwrap(); let wallet = Arc::new(wallet); @@ -101,9 +101,8 @@ async fn main() { continue; } - if let Some(s) = cli::handle_matches(&Arc::clone(&wallet), matches.unwrap()) - .await - .unwrap() + if let Some(s) = + cli::handle_matches(&Arc::clone(&wallet), matches.unwrap()).unwrap() { println!("{}", s); } @@ -119,7 +118,7 @@ async fn main() { // rl.save_history("history.txt").unwrap(); } else { - if let Some(s) = cli::handle_matches(&wallet, matches).await.unwrap() { + if let Some(s) = cli::handle_matches(&wallet, matches).unwrap() { println!("{}", s); } } diff --git a/src/blockchain/electrum.rs b/src/blockchain/electrum.rs index fe40c78b..8225f554 100644 --- a/src/blockchain/electrum.rs +++ b/src/blockchain/electrum.rs @@ -5,23 +5,22 @@ use log::{debug, error, info, trace}; use bitcoin::{Script, Transaction, Txid}; -use electrum_client::tokio::io::{AsyncRead, AsyncWrite}; -use electrum_client::Client; +use electrum_client::{Client, ElectrumApi}; use self::utils::{ELSGetHistoryRes, ELSListUnspentRes, ElectrumLikeSync}; use super::*; use crate::database::{BatchDatabase, DatabaseUtils}; use crate::error::Error; -pub struct ElectrumBlockchain(Option>); +pub struct ElectrumBlockchain(Option); -impl std::convert::From> for ElectrumBlockchain { - fn from(client: Client) -> Self { +impl std::convert::From for ElectrumBlockchain { + fn from(client: Client) -> Self { ElectrumBlockchain(Some(client)) } } -impl Blockchain for ElectrumBlockchain { +impl Blockchain for ElectrumBlockchain { fn offline() -> Self { ElectrumBlockchain(None) } @@ -31,15 +30,14 @@ impl Blockchain for ElectrumBlockchain { } } -#[async_trait(?Send)] -impl OnlineBlockchain for ElectrumBlockchain { - async fn get_capabilities(&self) -> HashSet { +impl OnlineBlockchain for ElectrumBlockchain { + fn get_capabilities(&self) -> HashSet { vec![Capability::FullHistory, Capability::GetAnyTx] .into_iter() .collect() } - async fn setup( + fn setup( &mut self, stop_gap: Option, database: &mut D, @@ -49,30 +47,27 @@ impl OnlineBlockchain for ElectrumBlockchain Result, Error> { + fn get_tx(&mut self, txid: &Txid) -> Result, Error> { Ok(self .0 .as_mut() .ok_or(Error::OfflineClient)? .transaction_get(txid) - .await .map(Option::Some)?) } - async fn broadcast(&mut self, tx: &Transaction) -> Result<(), Error> { + fn broadcast(&mut self, tx: &Transaction) -> Result<(), Error> { Ok(self .0 .as_mut() .ok_or(Error::OfflineClient)? .transaction_broadcast(tx) - .await .map(|_| ())?) } - async fn get_height(&mut self) -> Result { + fn get_height(&mut self) -> Result { // TODO: unsubscribe when added to the client, or is there a better call to use here? Ok(self @@ -80,19 +75,16 @@ impl OnlineBlockchain for ElectrumBlockchain ElectrumLikeSync for Client { - async fn els_batch_script_get_history<'s, I: IntoIterator>( +impl ElectrumLikeSync for Client { + fn els_batch_script_get_history<'s, I: IntoIterator>( &mut self, scripts: I, ) -> Result>, Error> { self.batch_script_get_history(scripts) - .await .map(|v| { v.into_iter() .map(|v| { @@ -112,12 +104,11 @@ impl ElectrumLikeSync for Client { .map_err(Error::Electrum) } - async fn els_batch_script_list_unspent<'s, I: IntoIterator>( + fn els_batch_script_list_unspent<'s, I: IntoIterator>( &mut self, scripts: I, ) -> Result>, Error> { self.batch_script_list_unspent(scripts) - .await .map(|v| { v.into_iter() .map(|v| { @@ -141,7 +132,7 @@ impl ElectrumLikeSync for Client { .map_err(Error::Electrum) } - async fn els_transaction_get(&mut self, txid: &Txid) -> Result { - self.transaction_get(txid).await.map_err(Error::Electrum) + fn els_transaction_get(&mut self, txid: &Txid) -> Result { + self.transaction_get(txid).map_err(Error::Electrum) } } diff --git a/src/blockchain/esplora.rs b/src/blockchain/esplora.rs index 0e254324..150b4cf9 100644 --- a/src/blockchain/esplora.rs +++ b/src/blockchain/esplora.rs @@ -1,14 +1,16 @@ use std::collections::HashSet; +use std::sync::Mutex; use futures::stream::{self, StreamExt, TryStreamExt}; +use tokio::runtime::Runtime; + #[allow(unused_imports)] use log::{debug, error, info, trace}; use serde::Deserialize; -use reqwest::Client; -use reqwest::StatusCode; +use reqwest::{Client, StatusCode}; use bitcoin::consensus::{deserialize, serialize}; use bitcoin::hashes::hex::ToHex; @@ -23,7 +25,12 @@ use crate::error::Error; #[derive(Debug)] pub struct UrlClient { url: String, + // We use the async client instead of the blocking one because it automatically uses `fetch` + // when the target platform is wasm32. For some reason the blocking client doesn't, so we are + // stuck with this client: Client, + + runtime: Mutex, } #[derive(Debug)] @@ -40,6 +47,8 @@ impl EsploraBlockchain { EsploraBlockchain(Some(UrlClient { url: base_url.to_string(), client: Client::new(), + + runtime: Mutex::new(Runtime::new().unwrap()), })) } } @@ -54,15 +63,14 @@ impl Blockchain for EsploraBlockchain { } } -#[async_trait(?Send)] impl OnlineBlockchain for EsploraBlockchain { - async fn get_capabilities(&self) -> HashSet { + fn get_capabilities(&self) -> HashSet { vec![Capability::FullHistory, Capability::GetAnyTx] .into_iter() .collect() } - async fn setup( + fn setup( &mut self, stop_gap: Option, database: &mut D, @@ -72,34 +80,22 @@ impl OnlineBlockchain for EsploraBlockchain { .as_mut() .ok_or(Error::OfflineClient)? .electrum_like_setup(stop_gap, database, progress_update) - .await } - async fn get_tx(&mut self, txid: &Txid) -> Result, Error> { + fn get_tx(&mut self, txid: &Txid) -> Result, Error> { + Ok(self.0.as_mut().ok_or(Error::OfflineClient)?._get_tx(txid)?) + } + + fn broadcast(&mut self, tx: &Transaction) -> Result<(), Error> { Ok(self .0 .as_mut() .ok_or(Error::OfflineClient)? - ._get_tx(txid) - .await?) + ._broadcast(tx)?) } - async fn broadcast(&mut self, tx: &Transaction) -> Result<(), Error> { - Ok(self - .0 - .as_mut() - .ok_or(Error::OfflineClient)? - ._broadcast(tx) - .await?) - } - - async fn get_height(&mut self) -> Result { - Ok(self - .0 - .as_mut() - .ok_or(Error::OfflineClient)? - ._get_height() - .await?) + fn get_height(&mut self) -> Result { + Ok(self.0.as_mut().ok_or(Error::OfflineClient)?._get_height()?) } } @@ -108,40 +104,53 @@ impl UrlClient { sha256::Hash::hash(script.as_bytes()).into_inner().to_hex() } - async fn _get_tx(&self, txid: &Txid) -> Result, EsploraError> { - let resp = self - .client - .get(&format!("{}/api/tx/{}/raw", self.url, txid)) - .send() - .await?; + fn _get_tx(&self, txid: &Txid) -> Result, EsploraError> { + let resp = self.runtime.lock().unwrap().block_on( + self.client + .get(&format!("{}/api/tx/{}/raw", self.url, txid)) + .send(), + )?; if let StatusCode::NOT_FOUND = resp.status() { return Ok(None); } - Ok(Some(deserialize(&resp.error_for_status()?.bytes().await?)?)) + Ok(Some(deserialize( + &self + .runtime + .lock() + .unwrap() + .block_on(resp.error_for_status()?.bytes())?, + )?)) } - async fn _broadcast(&self, transaction: &Transaction) -> Result<(), EsploraError> { - self.client - .post(&format!("{}/api/tx", self.url)) - .body(serialize(transaction).to_hex()) - .send() - .await? + fn _broadcast(&self, transaction: &Transaction) -> Result<(), EsploraError> { + self.runtime + .lock() + .unwrap() + .block_on( + self.client + .post(&format!("{}/api/tx", self.url)) + .body(serialize(transaction).to_hex()) + .send(), + )? .error_for_status()?; Ok(()) } - async fn _get_height(&self) -> Result { + fn _get_height(&self) -> Result { + let req = self.runtime.lock().unwrap().block_on( + self.client + .get(&format!("{}/api/blocks/tip/height", self.url)) + .send(), + )?; + Ok(self - .client - .get(&format!("{}/api/blocks/tip/height", self.url)) - .send() - .await? - .error_for_status()? - .text() - .await? + .runtime + .lock() + .unwrap() + .block_on(req.error_for_status()?.text())? .parse()?) } @@ -238,32 +247,34 @@ impl UrlClient { } } -#[async_trait(?Send)] impl ElectrumLikeSync for UrlClient { - async fn els_batch_script_get_history<'s, I: IntoIterator>( + fn els_batch_script_get_history<'s, I: IntoIterator>( &mut self, scripts: I, ) -> Result>, Error> { - Ok(stream::iter(scripts) - .then(|script| self._script_get_history(&script)) - .try_collect() - .await?) + self.runtime.lock().unwrap().block_on(async { + Ok(stream::iter(scripts) + .then(|script| self._script_get_history(&script)) + .try_collect() + .await?) + }) } - async fn els_batch_script_list_unspent<'s, I: IntoIterator>( + fn els_batch_script_list_unspent<'s, I: IntoIterator>( &mut self, scripts: I, ) -> Result>, Error> { - Ok(stream::iter(scripts) - .then(|script| self._script_list_unspent(&script)) - .try_collect() - .await?) + self.runtime.lock().unwrap().block_on(async { + Ok(stream::iter(scripts) + .then(|script| self._script_list_unspent(&script)) + .try_collect() + .await?) + }) } - async fn els_transaction_get(&mut self, txid: &Txid) -> Result { + fn els_transaction_get(&mut self, txid: &Txid) -> Result { Ok(self - ._get_tx(txid) - .await? + ._get_tx(txid)? .ok_or_else(|| EsploraError::TransactionNotFound(*txid))?) } } diff --git a/src/blockchain/mod.rs b/src/blockchain/mod.rs index 3418d967..3e13d5f9 100644 --- a/src/blockchain/mod.rs +++ b/src/blockchain/mod.rs @@ -41,29 +41,28 @@ impl Blockchain for OfflineBlockchain { } } -#[async_trait(?Send)] pub trait OnlineBlockchain: Blockchain { - async fn get_capabilities(&self) -> HashSet; + fn get_capabilities(&self) -> HashSet; - async fn setup( + fn setup( &mut self, stop_gap: Option, database: &mut D, progress_update: P, ) -> Result<(), Error>; - async fn sync( + fn sync( &mut self, stop_gap: Option, database: &mut D, progress_update: P, ) -> Result<(), Error> { - self.setup(stop_gap, database, progress_update).await + self.setup(stop_gap, database, progress_update) } - async fn get_tx(&mut self, txid: &Txid) -> Result, Error>; - async fn broadcast(&mut self, tx: &Transaction) -> Result<(), Error>; + fn get_tx(&mut self, txid: &Txid) -> Result, Error>; + fn broadcast(&mut self, tx: &Transaction) -> Result<(), Error>; - async fn get_height(&mut self) -> Result; + fn get_height(&mut self) -> Result; } pub type ProgressData = (f32, Option); diff --git a/src/blockchain/utils.rs b/src/blockchain/utils.rs index 4bbe6281..e8e3769c 100644 --- a/src/blockchain/utils.rs +++ b/src/blockchain/utils.rs @@ -27,23 +27,22 @@ pub struct ELSListUnspentRes { } /// Implements the synchronization logic for an Electrum-like client. -#[async_trait(?Send)] pub trait ElectrumLikeSync { - async fn els_batch_script_get_history<'s, I: IntoIterator>( + fn els_batch_script_get_history<'s, I: IntoIterator>( &mut self, scripts: I, ) -> Result>, Error>; - async fn els_batch_script_list_unspent<'s, I: IntoIterator>( + fn els_batch_script_list_unspent<'s, I: IntoIterator>( &mut self, scripts: I, ) -> Result>, Error>; - async fn els_transaction_get(&mut self, txid: &Txid) -> Result; + fn els_transaction_get(&mut self, txid: &Txid) -> Result; // Provided methods down here... - async fn electrum_like_setup( + fn electrum_like_setup( &mut self, stop_gap: Option, database: &mut D, @@ -86,7 +85,7 @@ pub trait ElectrumLikeSync { let until = cmp::min(to_check_later.len(), batch_query_size); let chunk: Vec