diff --git a/src/commands/enhance.rs b/src/commands/enhance.rs index f97c6ac..6fac516 100644 --- a/src/commands/enhance.rs +++ b/src/commands/enhance.rs @@ -21,7 +21,7 @@ use zcash_protocol::consensus::{BlockHeight, BranchId, Network}; use crate::{ data::{get_db_paths, get_wallet_network}, - remote::Servers, + remote::{tor_client, Servers}, }; // Options accepted for the `enhance` command @@ -89,7 +89,16 @@ impl Command { anyhow!("Chain height must be available to perform transaction enhancement.") })?; - let mut client = self.server.pick(params)?.connect_direct().await?; + // TODO: + // - Create a shared Tor client. + // - Create an isolated `lightwalletd` connection for each transaction. + // - Spread transactions across all available servers. + // - Fetch transactions in parallel, with timing noise. + let mut client = self + .server + .pick(params)? + .connect(|| tor_client(wallet_dir.as_ref())) + .await?; let mut satisfied_requests = BTreeSet::new(); loop { diff --git a/src/commands/import_ufvk.rs b/src/commands/import_ufvk.rs index 946de70..a2a8986 100644 --- a/src/commands/import_ufvk.rs +++ b/src/commands/import_ufvk.rs @@ -10,7 +10,11 @@ use zcash_client_sqlite::WalletDb; use zcash_keys::keys::UnifiedFullViewingKey; use zcash_primitives::consensus; -use crate::{data::get_db_paths, error, remote::Servers}; +use crate::{ + data::get_db_paths, + error, + remote::{tor_client, Servers}, +}; // Options accepted for the `import-ufvk` command #[derive(Debug, Options)] @@ -45,14 +49,18 @@ impl Command { } }?; - let (_, db_data) = get_db_paths(wallet_dir); + let (_, db_data) = get_db_paths(wallet_dir.as_ref()); let mut db_data = WalletDb::for_path(db_data, params)?; // Construct an `AccountBirthday` for the account's birthday. let birthday = { // Fetch the tree state corresponding to the last block prior to the wallet's // birthday height. NOTE: THIS APPROACH LEAKS THE BIRTHDAY TO THE SERVER! - let mut client = self.server.pick(params)?.connect_direct().await?; + let mut client = self + .server + .pick(params)? + .connect(|| tor_client(wallet_dir)) + .await?; let request = service::BlockId { height: (self.birthday - 1).into(), ..Default::default() diff --git a/src/commands/init.rs b/src/commands/init.rs index 0812256..3eda76b 100644 --- a/src/commands/init.rs +++ b/src/commands/init.rs @@ -15,7 +15,7 @@ use zcash_primitives::consensus::{self, Parameters}; use crate::{ data::{get_db_paths, init_wallet_config, Network}, error, - remote::Servers, + remote::{tor_client, Servers}, }; // Options accepted for the `init` command @@ -50,7 +50,11 @@ impl Command { let params = consensus::Network::from(opts.network); // Get the current chain height (for the wallet's birthday). - let mut client = opts.server.pick(params)?.connect_direct().await?; + let mut client = opts + .server + .pick(params)? + .connect(|| tor_client(wallet_dir.as_ref())) + .await?; let birthday = if let Some(birthday) = opts.birthday { birthday } else { diff --git a/src/commands/reset.rs b/src/commands/reset.rs index 111cab4..c8b4d36 100644 --- a/src/commands/reset.rs +++ b/src/commands/reset.rs @@ -3,7 +3,7 @@ use gumdrop::Options; use crate::{ data::{erase_wallet_state, read_config}, - remote::Servers, + remote::{tor_client, Servers}, }; // Options accepted for the `reset` command @@ -27,7 +27,11 @@ impl Command { let params = keys.network(); // Connect to the client (for re-initializing the wallet). - let client = self.server.pick(params)?.connect_direct().await?; + let client = self + .server + .pick(params)? + .connect(|| tor_client(wallet_dir.as_ref())) + .await?; // Erase the wallet state (excluding key material). erase_wallet_state(wallet_dir.as_ref()).await; diff --git a/src/commands/send.rs b/src/commands/send.rs index c3b724b..4fb9c9d 100644 --- a/src/commands/send.rs +++ b/src/commands/send.rs @@ -26,7 +26,7 @@ use crate::{ commands::propose::{parse_fee_rule, FeeRule}, data::{get_db_paths, read_config}, error, - remote::Servers, + remote::{tor_client, Servers}, MIN_CONFIRMATIONS, }; @@ -62,7 +62,7 @@ impl Command { let keys = read_config(wallet_dir.as_ref())?; let params = keys.network(); - let (_, db_data) = get_db_paths(wallet_dir); + let (_, db_data) = get_db_paths(wallet_dir.as_ref()); let mut db_data = WalletDb::for_path(db_data, params)?; let account_id = *db_data .get_account_ids()? @@ -87,7 +87,11 @@ impl Command { ) .map_err(error::Error::from)?; - let mut client = self.server.pick(params)?.connect_direct().await?; + let mut client = self + .server + .pick(params)? + .connect(|| tor_client(wallet_dir.as_ref())) + .await?; // Create the transaction. println!("Creating transaction..."); diff --git a/src/remote.rs b/src/remote.rs index 5b2d2eb..64b05f1 100644 --- a/src/remote.rs +++ b/src/remote.rs @@ -1,4 +1,4 @@ -use std::{borrow::Cow, fmt, path::Path}; +use std::{borrow::Cow, fmt, future::Future, path::Path}; use anyhow::anyhow; use tonic::transport::{Channel, ClientTlsConfig}; @@ -152,6 +152,36 @@ impl<'a> Server<'a> { Ok(CompactTxStreamerClient::new(channel.connect().await?)) } + + async fn connect_over_tor( + &self, + tor: &tor::Client, + ) -> Result, anyhow::Error> { + if !self.use_tls() { + return Err(anyhow!( + "Cannot connect to local lightwalletd server over Tor" + )); + } + + info!("Connecting to {} over Tor", self); + let endpoint = self.endpoint().try_into()?; + Ok(tor.connect_to_lightwalletd(endpoint).await?) + } + + /// Connects to the server over Tor, unless it is running on localhost without HTTPS. + pub(crate) async fn connect( + &self, + tor: impl FnOnce() -> F, + ) -> Result, anyhow::Error> + where + F: Future>, + { + if self.use_tls() { + self.connect_over_tor(&tor().await?).await + } else { + self.connect_direct().await + } + } } pub(crate) async fn tor_client>(