Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Integrated TOR proxy #810

Closed
wants to merge 2 commits into from
Closed
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
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ env_logger = "0.7"
electrsd = "0.21"
# Move back to importing from rust-bitcoin once https://github.com/rust-bitcoin/rust-bitcoin/pull/1342 is released
base64 = "^0.13"
libtor = "47.8.0+0.4.7.x"

[[example]]
name = "compact_filters_balance"
Expand Down
26 changes: 25 additions & 1 deletion examples/compact_filters_balance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,40 @@ use blockchain::compact_filters::CompactFiltersError;
use log::info;
use std::sync::Arc;

pub mod utils;
use crate::utils::tor::{start_tor, use_tor};

/// This will return wallet balance using compact filters
/// Requires a synced local bitcoin node 0.21 running on testnet with blockfilterindex=1 and peerblockfilters=1
fn main() -> Result<(), CompactFiltersError> {
env_logger::init();
info!("start");

let tor_addrs = if use_tor() {
Some(start_tor(Some(18333)))
} else {
None
};

let num_threads = 4;
let mempool = Arc::new(Mempool::default());
let peers = (0..num_threads)
.map(|_| Peer::connect("localhost:18333", Arc::clone(&mempool), Network::Testnet))
.map(|_| {
if use_tor() {
let addr = &tor_addrs.as_ref().unwrap().hidden_service.as_ref().unwrap();
let proxy = &tor_addrs.as_ref().unwrap().socks;
info!("conecting to {} via {}", &addr, &proxy);
Peer::connect_proxy(
addr.as_str(),
&proxy,
None,
Arc::clone(&mempool),
Network::Testnet,
)
} else {
Peer::connect("localhost:18333", Arc::clone(&mempool), Network::Testnet)
}
})
.collect::<Result<_, _>>()?;
let blockchain = CompactFiltersBlockchain::new(peers, "./wallet-filters", Some(500_000))?;
info!("done {:?}", blockchain);
Expand Down
20 changes: 18 additions & 2 deletions examples/electrum_backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@ use bdk::template::Bip84;
use bdk::wallet::export::FullyNodedExport;
use bdk::{KeychainKind, SyncOptions, Wallet};

use bdk::electrum_client::Client;
use bdk::electrum_client::{Client, ConfigBuilder, Socks5Config};
use bdk::wallet::AddressIndex;
use bitcoin::util::bip32;

pub mod utils;

use crate::utils::tor::{start_tor, use_tor};
use crate::utils::tx::build_signed_tx;

/// This will create a wallet from an xpriv and get the balance by connecting to an Electrum server.
Expand Down Expand Up @@ -44,8 +45,23 @@ fn create_wallet(network: &Network, xpriv: &ExtendedPrivKey) -> Wallet<MemoryDat
fn run(network: &Network, electrum_url: &str, xpriv: &str) {
let xpriv = bip32::ExtendedPrivKey::from_str(xpriv).unwrap();

let client = if use_tor() {
let tor_addrs = start_tor(None);
let config = ConfigBuilder::new()
.validate_domain(false)
.socks5(Some(Socks5Config {
addr: tor_addrs.socks,
credentials: None,
}))
.unwrap()
.build();
Client::from_config(electrum_url, config).unwrap()
} else {
Client::new(electrum_url).unwrap()
};

// Apparently it works only with Electrs (not EletrumX)
let blockchain = ElectrumBlockchain::from(Client::new(electrum_url).unwrap());
let blockchain = ElectrumBlockchain::from(client);

let wallet = create_wallet(network, &xpriv);

Expand Down
26 changes: 19 additions & 7 deletions examples/esplora_backend_asynchronous.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
use std::str::FromStr;

use bitcoin::{
util::bip32::{self, ExtendedPrivKey},
Network,
};
use esplora_client::Builder;

use bdk::blockchain::Blockchain;
use bdk::{
blockchain::esplora::EsploraBlockchain,
Expand All @@ -8,15 +14,12 @@ use bdk::{
wallet::{export::FullyNodedExport, AddressIndex},
KeychainKind, SyncOptions, Wallet,
};
use bitcoin::{
util::bip32::{self, ExtendedPrivKey},
Network,
};

pub mod utils;

use crate::utils::tor::{start_tor, use_tor};
use crate::utils::tx::build_signed_tx;

pub mod utils;

/// This will create a wallet from an xpriv and get the balance by connecting to an Esplora server,
/// using non blocking asynchronous calls with `reqwest`.
/// If enough amount is available, this will send a transaction to an address.
Expand Down Expand Up @@ -48,7 +51,16 @@ fn create_wallet(network: &Network, xpriv: &ExtendedPrivKey) -> Wallet<MemoryDat
async fn run(network: &Network, esplora_url: &str, xpriv: &str) {
let xpriv = bip32::ExtendedPrivKey::from_str(xpriv).unwrap();

let blockchain = EsploraBlockchain::new(esplora_url, 20);
let blockchain = if use_tor() {
let tor_addrs = start_tor(None);
let client = Builder::new(esplora_url)
.proxy(format!("socks5://{}", tor_addrs.socks).as_ref())
.build_async()
.expect("Should never fail with no proxy and timeout");
EsploraBlockchain::from_client(client, 20)
} else {
EsploraBlockchain::new(esplora_url, 20)
};

let wallet = create_wallet(network, &xpriv);

Expand Down
13 changes: 12 additions & 1 deletion examples/esplora_backend_synchronous.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use std::str::FromStr;

use crate::utils::tor::{start_tor, use_tor};
use bdk::blockchain::Blockchain;
use bdk::esplora_client::Builder;
use bdk::{
blockchain::esplora::EsploraBlockchain,
database::MemoryDatabase,
Expand Down Expand Up @@ -47,7 +49,16 @@ fn create_wallet(network: &Network, xpriv: &ExtendedPrivKey) -> Wallet<MemoryDat
fn run(network: &Network, esplora_url: &str, xpriv: &str) {
let xpriv = bip32::ExtendedPrivKey::from_str(xpriv).unwrap();

let blockchain = EsploraBlockchain::new(esplora_url, 20);
let blockchain = if use_tor() {
let tor_addrs = start_tor(None);
let client = Builder::new(esplora_url)
.proxy(format!("socks5://{}", tor_addrs.socks).as_ref())
.build_blocking()
.expect("Should never fail with no proxy and timeout");
EsploraBlockchain::from_client(client, 20)
} else {
EsploraBlockchain::new(esplora_url, 20)
};

let wallet = create_wallet(network, &xpriv);

Expand Down
130 changes: 130 additions & 0 deletions examples/utils/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,133 @@ pub(crate) mod tx {
psbt.extract_tx()
}
}

pub(crate) mod tor {
use std::fs::File;
use std::io::prelude::*;
use std::thread;
use std::time::Duration;

use libtor::LogDestination;
use libtor::LogLevel;
use libtor::{HiddenServiceVersion, Tor, TorAddress, TorFlag};

use std::env;

pub struct TorAddresses {
pub socks: String,
pub hidden_service: Option<String>,
}

pub fn use_tor() -> bool {
match env::var("TOR") {
Ok(val) => val == "1" || val == "true",
_ => false,
}
}

pub fn start_tor(hidden_service_port: Option<u16>) -> TorAddresses {
let socks_port = 19050;

let data_dir = format!("{}/{}", env::temp_dir().display(), "bdk-tor-example");
let log_file_name = format!("{}/{}", &data_dir, "log");
let hidden_service_dir =
hidden_service_port.map(|port| format!("{}/{}-{}", &data_dir, "hs-dir", port));

println!("Staring Tor in {}", &data_dir);

truncate_log(&log_file_name);

if let Some(hs_port) = hidden_service_port {
Tor::new()
.flag(TorFlag::DataDirectory(data_dir.into()))
.flag(TorFlag::LogTo(
LogLevel::Notice,
LogDestination::File(log_file_name.as_str().into()),
))
.flag(TorFlag::SocksPort(socks_port))
.flag(TorFlag::Custom("ExitPolicy reject *:*".into()))
.flag(TorFlag::Custom("BridgeRelay 0".into()))
.flag(TorFlag::HiddenServiceDir(
hidden_service_dir.as_ref().unwrap().into(),
))
.flag(TorFlag::HiddenServiceVersion(HiddenServiceVersion::V3))
.flag(TorFlag::HiddenServicePort(
TorAddress::Port(hs_port),
None.into(),
))
.start_background()
} else {
Tor::new()
.flag(TorFlag::DataDirectory(data_dir.into()))
.flag(TorFlag::LogTo(
LogLevel::Notice,
LogDestination::File(log_file_name.as_str().into()),
))
.flag(TorFlag::SocksPort(socks_port))
.flag(TorFlag::Custom("ExitPolicy reject *:*".into()))
.flag(TorFlag::Custom("BridgeRelay 0".into()))
.start_background()
};

let mut started = false;
let mut tries = 0;
while !started {
tries += 1;
if tries > 120 {
panic!(
"It took too long to start Tor. See {} for details",
&log_file_name
);
}

thread::sleep(Duration::from_millis(1000));
started = find_string_in_log(&log_file_name, &"Bootstrapped 100%".into());
}

println!("Tor started");

TorAddresses {
socks: format!("127.0.0.1:{}", socks_port),
hidden_service: hidden_service_port.map(|port| {
format!(
"{}:{}",
get_onion_address(&hidden_service_dir.unwrap()),
port
)
}),
}
}

fn truncate_log(filename: &String) {
let path = std::path::Path::new(filename);
if path.exists() {
let file = File::options()
.write(true)
.open(path)
.expect("no such file");
file.set_len(0).expect("cannot set size to 0");
}
}

fn find_string_in_log(filename: &String, str: &String) -> bool {
let path = std::path::Path::new(filename);
if path.exists() {
let mut file = File::open(path).expect("cannot open log file");
let mut buf = String::new();
file.read_to_string(&mut buf).expect("cannot read log file");
buf.contains(str)
} else {
false
}
}

fn get_onion_address(hidden_service_dir: &String) -> String {
let filename = format!("{}/{}", hidden_service_dir, "hostname");
let path = std::path::Path::new(&filename);
let mut file = File::open(path).expect("cannot open hostname file");
let mut buf = String::new();
file.read_to_string(&mut buf).expect("cannot read log file");
buf.replace("\n", "")
}
}