mirror of
https://github.com/comit-network/xmr-btc-swap.git
synced 2025-12-21 11:25:50 -05:00
refactor(monero-rpc-pool): ureq -> raw hyper (#487)
* refactor(monero-rpc-pool): ureq -> raw hyper * whitelist "getblocks.bin", refactor config constructors, use arti-client, record lowerst seen block height, small style changes * display effective bandwidth * compact wallet overview page a bit * record latencies correctly * add setting for monero tor routing, add ssl support for hyper, lengthen window duration for bandwidth tracker * remove unwrap * refactor ui * dont fail silently tor bootstrap * some workarounds for buggy wallet2 stuff
This commit is contained in:
parent
cd12d17580
commit
d21baa8350
19 changed files with 957 additions and 741 deletions
36
Cargo.lock
generated
36
Cargo.lock
generated
|
|
@ -4559,9 +4559,11 @@ dependencies = [
|
|||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"socket2 0.6.0",
|
||||
"system-configuration 0.6.1",
|
||||
"tokio",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
"windows-registry",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -6219,25 +6221,31 @@ name = "monero-rpc-pool"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"arti-client",
|
||||
"axum",
|
||||
"chrono",
|
||||
"clap 4.5.41",
|
||||
"futures",
|
||||
"http-body-util",
|
||||
"hyper 1.6.0",
|
||||
"hyper-util",
|
||||
"monero",
|
||||
"monero-rpc",
|
||||
"native-tls",
|
||||
"rand 0.8.5",
|
||||
"regex",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sqlx",
|
||||
"tokio",
|
||||
"tokio-native-tls",
|
||||
"tokio-test",
|
||||
"tor-rtcompat",
|
||||
"tower 0.4.13",
|
||||
"tower-http 0.5.2",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
"typeshare",
|
||||
"ureq",
|
||||
"url",
|
||||
"uuid",
|
||||
]
|
||||
|
|
@ -12872,21 +12880,6 @@ version = "0.0.4"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae"
|
||||
|
||||
[[package]]
|
||||
name = "ureq"
|
||||
version = "2.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02d1a66277ed75f640d608235660df48c8e3c19f3b4edb6a263315626cc3c01d"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"log",
|
||||
"once_cell",
|
||||
"rustls 0.23.29",
|
||||
"rustls-pki-types",
|
||||
"url",
|
||||
"webpki-roots 0.26.11",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "url"
|
||||
version = "2.5.4"
|
||||
|
|
@ -13594,6 +13587,17 @@ dependencies = [
|
|||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-registry"
|
||||
version = "0.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
"windows-result 0.3.4",
|
||||
"windows-strings",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-result"
|
||||
version = "0.1.2"
|
||||
|
|
|
|||
|
|
@ -27,9 +27,17 @@ tower-http = { version = "0.5", features = ["cors"] }
|
|||
tracing = { workspace = true }
|
||||
tracing-subscriber = { workspace = true }
|
||||
typeshare = { workspace = true }
|
||||
ureq = { version = "2.10", default-features = false, features = ["tls"] }
|
||||
url = "2.0"
|
||||
uuid = { workspace = true }
|
||||
|
||||
arti-client = { workspace = true, features = ["tokio"] }
|
||||
tor-rtcompat = { workspace = true, features = ["tokio", "rustls"] }
|
||||
|
||||
http-body-util = "0.1"
|
||||
hyper = { version = "1", features = ["full"] }
|
||||
hyper-util = { version = "0.1", features = ["full"] }
|
||||
native-tls = "0.2"
|
||||
tokio-native-tls = "0.3"
|
||||
|
||||
[dev-dependencies]
|
||||
tokio-test = "0.4"
|
||||
|
|
|
|||
|
|
@ -1,27 +1,53 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
use crate::TorClientArc;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Config {
|
||||
pub host: String,
|
||||
pub port: u16,
|
||||
pub data_dir: PathBuf,
|
||||
pub tor_client: Option<TorClientArc>,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for Config {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("Config")
|
||||
.field("host", &self.host)
|
||||
.field("port", &self.port)
|
||||
.field("data_dir", &self.data_dir)
|
||||
.field("tor_client", &self.tor_client.is_some())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn new_with_port(host: String, port: u16, data_dir: PathBuf) -> Self {
|
||||
Self::new_with_port_and_tor_client(host, port, data_dir, None)
|
||||
}
|
||||
|
||||
pub fn new_with_port_and_tor_client(
|
||||
host: String,
|
||||
port: u16,
|
||||
data_dir: PathBuf,
|
||||
tor_client: impl Into<Option<TorClientArc>>,
|
||||
) -> Self {
|
||||
Self {
|
||||
host,
|
||||
port,
|
||||
data_dir,
|
||||
tor_client: tor_client.into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_random_port(host: String, data_dir: PathBuf) -> Self {
|
||||
Self {
|
||||
host,
|
||||
port: 0,
|
||||
data_dir,
|
||||
}
|
||||
pub fn new_random_port(data_dir: PathBuf) -> Self {
|
||||
Self::new_random_port_with_tor_client(data_dir, None)
|
||||
}
|
||||
|
||||
pub fn new_random_port_with_tor_client(
|
||||
data_dir: PathBuf,
|
||||
tor_client: impl Into<Option<TorClientArc>>,
|
||||
) -> Self {
|
||||
Self::new_with_port_and_tor_client("127.0.0.1".to_string(), 0, data_dir, tor_client)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use anyhow::Result;
|
||||
use arti_client::TorClient;
|
||||
use axum::{
|
||||
routing::{any, get},
|
||||
Router,
|
||||
|
|
@ -8,9 +9,13 @@ use axum::{
|
|||
use monero::Network;
|
||||
|
||||
use tokio::task::JoinHandle;
|
||||
use tor_rtcompat::tokio::TokioRustlsRuntime;
|
||||
use tower_http::cors::CorsLayer;
|
||||
use tracing::{error, info};
|
||||
|
||||
/// Type alias for the Tor client used throughout the crate
|
||||
pub type TorClientArc = Arc<TorClient<TokioRustlsRuntime>>;
|
||||
|
||||
pub trait ToNetworkString {
|
||||
fn to_network_string(&self) -> String;
|
||||
}
|
||||
|
|
@ -39,7 +44,7 @@ use proxy::{proxy_handler, stats_handler};
|
|||
#[derive(Clone)]
|
||||
pub struct AppState {
|
||||
pub node_pool: Arc<NodePool>,
|
||||
pub http_client: ureq::Agent,
|
||||
pub tor_client: Option<TorClientArc>,
|
||||
}
|
||||
|
||||
/// Manages background tasks for the RPC pool
|
||||
|
|
@ -104,17 +109,9 @@ async fn create_app_with_receiver(
|
|||
status_update_handle,
|
||||
};
|
||||
|
||||
// Create shared HTTP client with connection pooling and keep-alive
|
||||
// TODO: Add dangerous certificate acceptance equivalent to reqwest's danger_accept_invalid_certs(true)
|
||||
let http_client = ureq::AgentBuilder::new()
|
||||
.timeout(std::time::Duration::from_secs(30))
|
||||
.max_idle_connections(100)
|
||||
.max_idle_connections_per_host(10)
|
||||
.build();
|
||||
|
||||
let app_state = AppState {
|
||||
node_pool,
|
||||
http_client,
|
||||
tor_client: config.tor_client,
|
||||
};
|
||||
|
||||
// Build the app
|
||||
|
|
@ -177,14 +174,8 @@ pub async fn start_server_with_random_port(
|
|||
tokio::sync::broadcast::Receiver<PoolStatus>,
|
||||
PoolHandle,
|
||||
)> {
|
||||
// Clone the host before moving config
|
||||
let host = config.host.clone();
|
||||
|
||||
// If port is 0, the system will assign a random available port
|
||||
let config_with_random_port = Config::new_random_port(config.host, config.data_dir);
|
||||
|
||||
let (app, status_receiver, pool_handle) =
|
||||
create_app_with_receiver(config_with_random_port, network).await?;
|
||||
let (app, status_receiver, pool_handle) = create_app_with_receiver(config, network).await?;
|
||||
|
||||
// Bind to port 0 to get a random available port
|
||||
let listener = tokio::net::TcpListener::bind(format!("{}:0", host)).await?;
|
||||
|
|
@ -209,18 +200,3 @@ pub async fn start_server_with_random_port(
|
|||
|
||||
Ok((server_info, status_receiver, pool_handle))
|
||||
}
|
||||
|
||||
/// Start a server with a random port and custom data directory for library usage
|
||||
/// Returns the server info with the actual port used, a receiver for pool status updates, and pool handle
|
||||
pub async fn start_server_with_random_port_and_data_dir(
|
||||
config: Config,
|
||||
network: Network,
|
||||
data_dir: std::path::PathBuf,
|
||||
) -> Result<(
|
||||
ServerInfo,
|
||||
tokio::sync::broadcast::Receiver<PoolStatus>,
|
||||
PoolHandle,
|
||||
)> {
|
||||
let config_with_data_dir = Config::new_random_port(config.host, data_dir);
|
||||
start_server_with_random_port(config_with_data_dir, network).await
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
use arti_client::{TorClient, TorClientConfig};
|
||||
use clap::Parser;
|
||||
use monero_rpc_pool::{config::Config, run_server};
|
||||
use tracing::info;
|
||||
|
|
@ -47,6 +48,11 @@ struct Args {
|
|||
#[arg(short, long)]
|
||||
#[arg(help = "Enable verbose logging")]
|
||||
verbose: bool,
|
||||
|
||||
#[arg(short, long)]
|
||||
#[arg(help = "Enable Tor routing")]
|
||||
#[arg(default_value = "true")]
|
||||
tor: bool,
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
|
|
@ -54,16 +60,46 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||
let args = Args::parse();
|
||||
|
||||
tracing_subscriber::fmt()
|
||||
.with_env_filter(EnvFilter::new("trace"))
|
||||
.with_env_filter(EnvFilter::new("info"))
|
||||
.with_target(false)
|
||||
.with_file(true)
|
||||
.with_line_number(true)
|
||||
.init();
|
||||
|
||||
let config = Config::new_with_port(
|
||||
let tor_client = if args.tor {
|
||||
let config = TorClientConfig::default();
|
||||
let runtime = tor_rtcompat::tokio::TokioRustlsRuntime::current()
|
||||
.expect("We are always running with tokio");
|
||||
|
||||
let client = TorClient::with_runtime(runtime)
|
||||
.config(config)
|
||||
.create_unbootstrapped_async()
|
||||
.await?;
|
||||
|
||||
let client = std::sync::Arc::new(client);
|
||||
|
||||
let client_clone = client.clone();
|
||||
tokio::spawn(async move {
|
||||
match client_clone.bootstrap().await {
|
||||
Ok(()) => {
|
||||
info!("Tor client successfully bootstrapped");
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::error!("Failed to bootstrap Tor client: {}. Tor functionality will be unavailable.", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Some(client)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let config = Config::new_with_port_and_tor_client(
|
||||
args.host,
|
||||
args.port,
|
||||
std::env::temp_dir().join("monero-rpc-pool"),
|
||||
tor_client,
|
||||
);
|
||||
|
||||
info!(
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
use anyhow::{Context, Result};
|
||||
use std::collections::VecDeque;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::time::{Duration, Instant};
|
||||
use tokio::sync::broadcast;
|
||||
use tracing::{debug, warn};
|
||||
use tracing::warn;
|
||||
use typeshare::typeshare;
|
||||
|
||||
use crate::database::Database;
|
||||
|
|
@ -16,6 +19,7 @@ pub struct PoolStatus {
|
|||
#[typeshare(serialized_as = "number")]
|
||||
pub unsuccessful_health_checks: u64,
|
||||
pub top_reliable_nodes: Vec<ReliableNodeInfo>,
|
||||
pub bandwidth_kb_per_sec: f64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize)]
|
||||
|
|
@ -26,10 +30,69 @@ pub struct ReliableNodeInfo {
|
|||
pub avg_latency_ms: Option<f64>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct BandwidthEntry {
|
||||
timestamp: Instant,
|
||||
bytes: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct BandwidthTracker {
|
||||
entries: VecDeque<BandwidthEntry>,
|
||||
window_duration: Duration,
|
||||
}
|
||||
|
||||
impl BandwidthTracker {
|
||||
const WINDOW_DURATION: Duration = Duration::from_secs(60 * 3);
|
||||
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
entries: VecDeque::new(),
|
||||
window_duration: Self::WINDOW_DURATION,
|
||||
}
|
||||
}
|
||||
|
||||
fn record_bytes(&mut self, bytes: u64) {
|
||||
let now = Instant::now();
|
||||
self.entries.push_back(BandwidthEntry {
|
||||
timestamp: now,
|
||||
bytes,
|
||||
});
|
||||
|
||||
// Clean up old entries
|
||||
let cutoff = now - self.window_duration;
|
||||
while let Some(front) = self.entries.front() {
|
||||
if front.timestamp < cutoff {
|
||||
self.entries.pop_front();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_kb_per_sec(&self) -> f64 {
|
||||
if self.entries.len() < 5 {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
let total_bytes: u64 = self.entries.iter().map(|e| e.bytes).sum();
|
||||
let now = Instant::now();
|
||||
let oldest_time = self.entries.front().unwrap().timestamp;
|
||||
let duration_secs = (now - oldest_time).as_secs_f64();
|
||||
|
||||
if duration_secs > 0.0 {
|
||||
(total_bytes as f64 / 1024.0) / duration_secs
|
||||
} else {
|
||||
0.0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct NodePool {
|
||||
db: Database,
|
||||
network: String,
|
||||
status_sender: broadcast::Sender<PoolStatus>,
|
||||
bandwidth_tracker: Arc<Mutex<BandwidthTracker>>,
|
||||
}
|
||||
|
||||
impl NodePool {
|
||||
|
|
@ -39,6 +102,7 @@ impl NodePool {
|
|||
db,
|
||||
network,
|
||||
status_sender,
|
||||
bandwidth_tracker: Arc::new(Mutex::new(BandwidthTracker::new())),
|
||||
};
|
||||
(pool, status_receiver)
|
||||
}
|
||||
|
|
@ -63,13 +127,19 @@ impl NodePool {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn record_bandwidth(&self, bytes: u64) {
|
||||
if let Ok(mut tracker) = self.bandwidth_tracker.lock() {
|
||||
tracker.record_bytes(bytes);
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn publish_status_update(&self) -> Result<()> {
|
||||
let status = self.get_current_status().await?;
|
||||
|
||||
if let Err(e) = self.status_sender.send(status.clone()) {
|
||||
warn!("Failed to send status update: {}", e);
|
||||
} else {
|
||||
debug!(?status, "Sent status update");
|
||||
tracing::debug!(?status, "Sent status update");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
|
@ -81,6 +151,12 @@ impl NodePool {
|
|||
let (successful_checks, unsuccessful_checks) =
|
||||
self.db.get_health_check_stats(&self.network).await?;
|
||||
|
||||
let bandwidth_kb_per_sec = if let Ok(tracker) = self.bandwidth_tracker.lock() {
|
||||
tracker.get_kb_per_sec()
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
|
||||
let top_reliable_nodes = reliable_nodes
|
||||
.into_iter()
|
||||
.take(5)
|
||||
|
|
@ -97,6 +173,7 @@ impl NodePool {
|
|||
successful_health_checks: successful_checks,
|
||||
unsuccessful_health_checks: unsuccessful_checks,
|
||||
top_reliable_nodes,
|
||||
bandwidth_kb_per_sec,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -105,9 +182,10 @@ impl NodePool {
|
|||
pub async fn get_top_reliable_nodes(&self, limit: usize) -> Result<Vec<NodeAddress>> {
|
||||
use rand::seq::SliceRandom;
|
||||
|
||||
debug!(
|
||||
tracing::debug!(
|
||||
"Getting top reliable nodes for network {} (target: {})",
|
||||
self.network, limit
|
||||
self.network,
|
||||
limit
|
||||
);
|
||||
|
||||
let available_nodes = self
|
||||
|
|
@ -149,7 +227,7 @@ impl NodePool {
|
|||
selected_nodes.push(node);
|
||||
}
|
||||
|
||||
debug!(
|
||||
tracing::debug!(
|
||||
"Pool size: {} nodes for network {} (target: {})",
|
||||
selected_nodes.len(),
|
||||
self.network,
|
||||
|
|
@ -158,53 +236,4 @@ impl NodePool {
|
|||
|
||||
Ok(selected_nodes)
|
||||
}
|
||||
|
||||
pub async fn get_pool_stats(&self) -> Result<PoolStats> {
|
||||
let (total, reachable, reliable) = self.db.get_node_stats(&self.network).await?;
|
||||
let reliable_nodes = self.db.get_reliable_nodes(&self.network).await?;
|
||||
|
||||
let avg_reliable_latency = if reliable_nodes.is_empty() {
|
||||
None
|
||||
} else {
|
||||
let total_latency: f64 = reliable_nodes
|
||||
.iter()
|
||||
.filter_map(|node| node.health.avg_latency_ms)
|
||||
.sum();
|
||||
let count = reliable_nodes
|
||||
.iter()
|
||||
.filter(|node| node.health.avg_latency_ms.is_some())
|
||||
.count();
|
||||
|
||||
if count > 0 {
|
||||
Some(total_latency / count as f64)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
Ok(PoolStats {
|
||||
total_nodes: total,
|
||||
reachable_nodes: reachable,
|
||||
reliable_nodes: reliable,
|
||||
avg_reliable_latency_ms: avg_reliable_latency,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PoolStats {
|
||||
pub total_nodes: i64,
|
||||
pub reachable_nodes: i64,
|
||||
pub reliable_nodes: i64,
|
||||
pub avg_reliable_latency_ms: Option<f64>, // TOOD: Why is this an Option, we hate Options
|
||||
}
|
||||
|
||||
impl PoolStats {
|
||||
pub fn health_percentage(&self) -> f64 {
|
||||
if self.total_nodes == 0 {
|
||||
0.0
|
||||
} else {
|
||||
(self.reachable_nodes as f64 / self.total_nodes as f64) * 100.0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -75,6 +75,12 @@ export default function MoneroPoolHealthBox() {
|
|||
variant="outlined"
|
||||
size="small"
|
||||
/>
|
||||
<Chip
|
||||
label={`${poolStatus.bandwidth_kb_per_sec?.toFixed(1) ?? '0.0'} KB/s Bandwidth`}
|
||||
color={poolStatus.bandwidth_kb_per_sec != null && poolStatus.bandwidth_kb_per_sec > 10 ? "info" : "default"}
|
||||
variant="outlined"
|
||||
size="small"
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ import {
|
|||
setFiatCurrency,
|
||||
setTheme,
|
||||
setTorEnabled,
|
||||
setEnableMoneroTor,
|
||||
setUseMoneroRpcPool,
|
||||
setDonateToDevelopment,
|
||||
} from "store/features/settingsSlice";
|
||||
|
|
@ -91,6 +92,7 @@ export default function SettingsBox() {
|
|||
<Table>
|
||||
<TableBody>
|
||||
<TorSettings />
|
||||
<MoneroTorSettings />
|
||||
<DonationTipSetting />
|
||||
<ElectrumRpcUrlSetting />
|
||||
<MoneroRpcPoolSetting />
|
||||
|
|
@ -715,6 +717,42 @@ export function TorSettings() {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* A setting that allows you to enable or disable routing Monero wallet traffic through Tor.
|
||||
* This setting is only visible when Tor is enabled.
|
||||
*/
|
||||
function MoneroTorSettings() {
|
||||
const dispatch = useAppDispatch();
|
||||
const torEnabled = useSettings((settings) => settings.enableTor);
|
||||
const enableMoneroTor = useSettings((settings) => settings.enableMoneroTor);
|
||||
|
||||
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) =>
|
||||
dispatch(setEnableMoneroTor(event.target.checked));
|
||||
|
||||
// Hide this setting if Tor is disabled entirely
|
||||
if (!torEnabled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<SettingLabel
|
||||
label="Route Monero traffic through Tor"
|
||||
tooltip="When enabled, Monero wallet traffic will be routed through Tor for additional privacy. Requires main Tor setting to be enabled."
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Switch
|
||||
checked={enableMoneroTor}
|
||||
onChange={handleChange}
|
||||
color="primary"
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* A setting that allows you to manage rendezvous points for maker discovery
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -1,14 +1,5 @@
|
|||
import {
|
||||
Box,
|
||||
Typography,
|
||||
CircularProgress,
|
||||
Button,
|
||||
Card,
|
||||
CardContent,
|
||||
Divider,
|
||||
CardHeader,
|
||||
LinearProgress,
|
||||
} from "@mui/material";
|
||||
import { Box, Typography, Card, LinearProgress } from "@mui/material";
|
||||
import { useAppSelector } from "store/hooks";
|
||||
import { PiconeroAmount } from "../../../other/Units";
|
||||
import { FiatPiconeroAmount } from "../../../other/Units";
|
||||
import StateIndicator from "./StateIndicator";
|
||||
|
|
@ -30,23 +21,77 @@ export default function WalletOverview({
|
|||
balance,
|
||||
syncProgress,
|
||||
}: WalletOverviewProps) {
|
||||
const lowestCurrentBlock = useAppSelector(
|
||||
(state) => state.wallet.state.lowestCurrentBlock,
|
||||
);
|
||||
|
||||
const poolStatus = useAppSelector((state) => state.pool.status);
|
||||
|
||||
const pendingBalance =
|
||||
parseFloat(balance.total_balance) - parseFloat(balance.unlocked_balance);
|
||||
|
||||
const isSyncing = syncProgress && syncProgress.progress_percentage < 100;
|
||||
const blocksLeft = syncProgress?.target_block - syncProgress?.current_block;
|
||||
|
||||
// Treat blocksLeft = 1 as if we have no direct knowledge
|
||||
const hasDirectKnowledge = blocksLeft != null && blocksLeft > 1;
|
||||
|
||||
// syncProgress.progress_percentage is not good to display
|
||||
// assuming we have an old wallet, eventually we will always only use the last few cm of the progress bar
|
||||
//
|
||||
// We calculate our own progress percentage
|
||||
// lowestCurrentBlock is the lowest block we have seen
|
||||
// currentBlock is the current block we are on (how war we've synced)
|
||||
// targetBlock is the target block we are syncing to
|
||||
//
|
||||
// The progressPercentage below is the progress on that path
|
||||
// If the lowestCurrentBlock is null, we fallback to the syncProgress.progress_percentage
|
||||
const progressPercentage =
|
||||
lowestCurrentBlock === null || !syncProgress
|
||||
? syncProgress?.progress_percentage || 0
|
||||
: syncProgress.target_block === lowestCurrentBlock
|
||||
? 100 // Fully synced when target equals lowest current block
|
||||
: Math.max(
|
||||
0,
|
||||
Math.min(
|
||||
100,
|
||||
((syncProgress.current_block - lowestCurrentBlock) /
|
||||
(syncProgress.target_block - lowestCurrentBlock)) *
|
||||
100,
|
||||
),
|
||||
);
|
||||
|
||||
const isStuck = poolStatus?.bandwidth_kb_per_sec != null && poolStatus.bandwidth_kb_per_sec < 0.01;
|
||||
|
||||
// Calculate estimated time remaining for sync
|
||||
const formatTimeRemaining = (seconds: number): string => {
|
||||
if (seconds < 60) return `${Math.round(seconds)}s`;
|
||||
if (seconds < 3600) return `${Math.round(seconds / 60)}m`;
|
||||
if (seconds < 86400) return `${Math.round(seconds / 3600)}h`;
|
||||
return `${Math.round(seconds / 86400)}d`;
|
||||
};
|
||||
|
||||
const estimatedTimeRemaining =
|
||||
hasDirectKnowledge && poolStatus?.bandwidth_kb_per_sec != null && poolStatus.bandwidth_kb_per_sec > 0
|
||||
? (blocksLeft * 130) / poolStatus.bandwidth_kb_per_sec // blocks * 130kb / kb_per_sec = seconds
|
||||
: null;
|
||||
|
||||
return (
|
||||
<Card sx={{ p: 2, position: "relative", borderRadius: 2 }} elevation={4}>
|
||||
{syncProgress && syncProgress.progress_percentage < 100 && (
|
||||
<LinearProgress
|
||||
value={syncProgress.progress_percentage}
|
||||
variant="determinate"
|
||||
value={hasDirectKnowledge ? progressPercentage : undefined}
|
||||
valueBuffer={
|
||||
// If the bandwidth is low, we may not be making progress
|
||||
// We don't show the buffer in this case
|
||||
hasDirectKnowledge && !isStuck ? progressPercentage : undefined
|
||||
}
|
||||
variant={hasDirectKnowledge ? "buffer" : "indeterminate"}
|
||||
sx={{
|
||||
width: "100%",
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: "100%",
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
|
@ -54,95 +99,105 @@ export default function WalletOverview({
|
|||
{/* Balance */}
|
||||
<Box
|
||||
sx={{
|
||||
display: "grid",
|
||||
gridTemplateColumns: "1.5fr 1fr max-content",
|
||||
rowGap: 0.5,
|
||||
columnGap: 2,
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "flex-start",
|
||||
mb: 1,
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
variant="body2"
|
||||
color="text.secondary"
|
||||
sx={{ mb: 1, gridColumn: "1", gridRow: "1" }}
|
||||
>
|
||||
Available Funds
|
||||
</Typography>
|
||||
<Typography variant="h4" sx={{ gridColumn: "1", gridRow: "2" }}>
|
||||
<PiconeroAmount
|
||||
amount={parseFloat(balance.unlocked_balance)}
|
||||
fixedPrecision={4}
|
||||
disableTooltip
|
||||
/>
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="body2"
|
||||
color="text.secondary"
|
||||
sx={{ gridColumn: "1", gridRow: "3" }}
|
||||
>
|
||||
<FiatPiconeroAmount amount={parseFloat(balance.unlocked_balance)} />
|
||||
</Typography>
|
||||
{pendingBalance > 0 && (
|
||||
<>
|
||||
<Typography
|
||||
variant="body2"
|
||||
color="warning"
|
||||
sx={{
|
||||
mb: 1,
|
||||
animation: "pulse 2s infinite",
|
||||
gridColumn: "2",
|
||||
gridRow: "1",
|
||||
alignSelf: "end",
|
||||
}}
|
||||
>
|
||||
Pending
|
||||
</Typography>
|
||||
|
||||
<Typography
|
||||
variant="h5"
|
||||
sx={{ gridColumn: "2", gridRow: "2", alignSelf: "center" }}
|
||||
>
|
||||
<PiconeroAmount amount={pendingBalance} fixedPrecision={4} />
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="body2"
|
||||
color="text.secondary"
|
||||
sx={{ gridColumn: "2", gridRow: "3" }}
|
||||
>
|
||||
<FiatPiconeroAmount amount={pendingBalance} />
|
||||
</Typography>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Left side content */}
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "flex-end",
|
||||
flexDirection: "row",
|
||||
gap: 4,
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
gap: 1,
|
||||
flexDirection: "column",
|
||||
gap: 0.5,
|
||||
}}
|
||||
>
|
||||
<Typography variant="body2">
|
||||
{isSyncing ? "syncing" : "synced"}
|
||||
<Typography variant="body2" color="text.secondary" sx={{ mb: 1 }}>
|
||||
Available Funds
|
||||
</Typography>
|
||||
<Typography variant="h4">
|
||||
<PiconeroAmount
|
||||
amount={parseFloat(balance.unlocked_balance)}
|
||||
fixedPrecision={4}
|
||||
disableTooltip
|
||||
/>
|
||||
</Typography>
|
||||
<StateIndicator
|
||||
color={isSyncing ? "primary" : "success"}
|
||||
pulsating={isSyncing}
|
||||
/>
|
||||
</Box>
|
||||
{isSyncing && (
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
{blocksLeft.toLocaleString()} blocks left
|
||||
<FiatPiconeroAmount
|
||||
amount={parseFloat(balance.unlocked_balance)}
|
||||
/>
|
||||
</Typography>
|
||||
</Box>
|
||||
{pendingBalance > 0 && (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: 0.5,
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
variant="body2"
|
||||
color="warning"
|
||||
sx={{
|
||||
mb: 1,
|
||||
animation: "pulse 2s infinite",
|
||||
}}
|
||||
>
|
||||
Pending
|
||||
</Typography>
|
||||
<Typography variant="h5">
|
||||
<PiconeroAmount amount={pendingBalance} fixedPrecision={4} />
|
||||
</Typography>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
<FiatPiconeroAmount amount={pendingBalance} />
|
||||
</Typography>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{/* Right side - simple approach */}
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "flex-end",
|
||||
gap: 2,
|
||||
}}
|
||||
>
|
||||
<StateIndicator
|
||||
color={isSyncing ? "primary" : "success"}
|
||||
pulsating={isSyncing}
|
||||
/>
|
||||
<Box sx={{ textAlign: "right" }}>
|
||||
{isSyncing && hasDirectKnowledge && (
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
{blocksLeft?.toLocaleString()} blocks left
|
||||
</Typography>
|
||||
)}
|
||||
{poolStatus && isSyncing && !isStuck && (
|
||||
<>
|
||||
<Typography
|
||||
variant="caption"
|
||||
color="text.secondary"
|
||||
sx={{ mt: 0.5, fontSize: "0.7rem", display: "block" }}
|
||||
>
|
||||
{estimatedTimeRemaining && !isStuck && (
|
||||
<>{formatTimeRemaining(estimatedTimeRemaining)} left</>
|
||||
)} / {poolStatus.bandwidth_kb_per_sec?.toFixed(1) ?? '0.0'} KB/s
|
||||
</Typography>
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
</Card>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -21,30 +21,29 @@ export default function WalletPageLoadingState() {
|
|||
{/* Balance */}
|
||||
<Box
|
||||
sx={{
|
||||
display: "grid",
|
||||
gridTemplateColumns: "1.5fr 1fr max-content",
|
||||
rowGap: 0.5,
|
||||
columnGap: 2,
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "flex-start",
|
||||
mb: 1,
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
variant="body2"
|
||||
color="text.secondary"
|
||||
sx={{ mb: 1, gridColumn: "1", gridRow: "1" }}
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: 0.5,
|
||||
}}
|
||||
>
|
||||
Available Funds
|
||||
</Typography>
|
||||
<Typography variant="h4" sx={{ gridColumn: "1", gridRow: "2" }}>
|
||||
<Skeleton variant="text" width="80%" />
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="body2"
|
||||
color="text.secondary"
|
||||
sx={{ gridColumn: "1", gridRow: "3" }}
|
||||
>
|
||||
<Skeleton variant="text" width="40%" />
|
||||
</Typography>
|
||||
<Typography variant="body2" color="text.secondary" sx={{ mb: 1 }}>
|
||||
Available Funds
|
||||
</Typography>
|
||||
<Typography variant="h4">
|
||||
<Skeleton variant="text" width="80%" />
|
||||
</Typography>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
<Skeleton variant="text" width="40%" />
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
|
|
@ -61,7 +60,6 @@ export default function WalletPageLoadingState() {
|
|||
gap: 1,
|
||||
}}
|
||||
>
|
||||
<Typography variant="body2">loading</Typography>
|
||||
<StateIndicator color="primary" pulsating={true} />
|
||||
</Box>
|
||||
</Box>
|
||||
|
|
|
|||
|
|
@ -319,6 +319,8 @@ export async function initializeContext() {
|
|||
// For Monero nodes, determine whether to use pool or custom node
|
||||
const useMoneroRpcPool = store.getState().settings.useMoneroRpcPool;
|
||||
|
||||
const useMoneroTor = store.getState().settings.enableMoneroTor;
|
||||
|
||||
const moneroNodeUrl =
|
||||
store.getState().settings.nodes[network][Blockchain.Monero][0] ?? null;
|
||||
|
||||
|
|
@ -341,6 +343,7 @@ export async function initializeContext() {
|
|||
electrum_rpc_urls: bitcoinNodes,
|
||||
monero_node_config: moneroNodeConfig,
|
||||
use_tor: useTor,
|
||||
enable_monero_tor: useMoneroTor,
|
||||
};
|
||||
|
||||
logger.info("Initializing context with settings", tauriSettings);
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@ export interface SettingsState {
|
|||
fiatCurrency: FiatCurrency;
|
||||
/// Whether to enable Tor for p2p connections
|
||||
enableTor: boolean;
|
||||
/// Whether to route Monero wallet traffic through Tor
|
||||
enableMoneroTor: boolean;
|
||||
/// Whether to use the Monero RPC pool for load balancing (true) or custom nodes (false)
|
||||
useMoneroRpcPool: boolean;
|
||||
userHasSeenIntroduction: boolean;
|
||||
|
|
@ -126,6 +128,7 @@ const initialState: SettingsState = {
|
|||
fetchFiatPrices: false,
|
||||
fiatCurrency: FiatCurrency.Usd,
|
||||
enableTor: true,
|
||||
enableMoneroTor: false, // Default to not routing Monero traffic through Tor
|
||||
useMoneroRpcPool: true, // Default to using RPC pool
|
||||
userHasSeenIntroduction: false,
|
||||
rendezvousPoints: DEFAULT_RENDEZVOUS_POINTS,
|
||||
|
|
@ -215,6 +218,9 @@ const alertsSlice = createSlice({
|
|||
setTorEnabled(slice, action: PayloadAction<boolean>) {
|
||||
slice.enableTor = action.payload;
|
||||
},
|
||||
setEnableMoneroTor(slice, action: PayloadAction<boolean>) {
|
||||
slice.enableMoneroTor = action.payload;
|
||||
},
|
||||
setUseMoneroRpcPool(slice, action: PayloadAction<boolean>) {
|
||||
slice.useMoneroRpcPool = action.payload;
|
||||
},
|
||||
|
|
@ -236,6 +242,7 @@ export const {
|
|||
setFetchFiatPrices,
|
||||
setFiatCurrency,
|
||||
setTorEnabled,
|
||||
setEnableMoneroTor,
|
||||
setUseMoneroRpcPool,
|
||||
setUserHasSeenIntroduction,
|
||||
addRendezvousPoint,
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ interface WalletState {
|
|||
balance: GetMoneroBalanceResponse | null;
|
||||
syncProgress: GetMoneroSyncProgressResponse | null;
|
||||
history: GetMoneroHistoryResponse | null;
|
||||
lowestCurrentBlock: number | null;
|
||||
}
|
||||
|
||||
export interface WalletSlice {
|
||||
|
|
@ -24,6 +25,7 @@ const initialState: WalletSlice = {
|
|||
balance: null,
|
||||
syncProgress: null,
|
||||
history: null,
|
||||
lowestCurrentBlock: null,
|
||||
},
|
||||
};
|
||||
|
||||
|
|
@ -42,6 +44,16 @@ export const walletSlice = createSlice({
|
|||
slice,
|
||||
action: PayloadAction<GetMoneroSyncProgressResponse>,
|
||||
) {
|
||||
slice.state.lowestCurrentBlock = Math.min(
|
||||
// We ignore anything below 10 blocks as this may be something like wallet2
|
||||
// sending a wrong value when it hasn't initialized yet
|
||||
slice.state.lowestCurrentBlock < 10 ||
|
||||
slice.state.lowestCurrentBlock === null
|
||||
? Infinity
|
||||
: slice.state.lowestCurrentBlock,
|
||||
action.payload.current_block,
|
||||
);
|
||||
|
||||
slice.state.syncProgress = action.payload;
|
||||
},
|
||||
setHistory(slice, action: PayloadAction<GetMoneroHistoryResponse>) {
|
||||
|
|
|
|||
|
|
@ -440,6 +440,7 @@ async fn initialize_context(
|
|||
.with_json(false)
|
||||
.with_debug(true)
|
||||
.with_tor(settings.use_tor)
|
||||
.with_enable_monero_tor(settings.enable_monero_tor)
|
||||
.with_tauri(tauri_handle.clone())
|
||||
.build()
|
||||
.await;
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ use structopt::clap;
|
|||
use structopt::clap::ErrorKind;
|
||||
use swap::asb::command::{parse_args, Arguments, Command};
|
||||
use swap::asb::{cancel, punish, redeem, refund, safely_abort, EventLoop, Finality, KrakenRate};
|
||||
use swap::common::tor::init_tor_client;
|
||||
use swap::common::tor::{bootstrap_tor_client, create_tor_client};
|
||||
use swap::common::tracing_util::Format;
|
||||
use swap::common::{self, get_logs, warn_if_outdated};
|
||||
use swap::database::{open_db, AccessMode};
|
||||
|
|
@ -201,8 +201,10 @@ pub async fn main() -> Result<()> {
|
|||
let kraken_rate = KrakenRate::new(config.maker.ask_spread, kraken_price_updates);
|
||||
let namespace = XmrBtcNamespace::from_is_testnet(testnet);
|
||||
|
||||
// Initialize Tor client
|
||||
let tor_client = init_tor_client(&config.data.dir, None).await?.into();
|
||||
// Initialize and bootstrap Tor client
|
||||
let tor_client = create_tor_client(&config.data.dir).await?;
|
||||
bootstrap_tor_client(tor_client.clone(), None).await?;
|
||||
let tor_client = tor_client.into();
|
||||
|
||||
let (mut swarm, onion_addresses) = swarm::asb(
|
||||
&seed,
|
||||
|
|
@ -495,10 +497,7 @@ async fn init_monero_wallet(
|
|||
|
||||
let (server_info, _status_receiver, _pool_handle) =
|
||||
monero_rpc_pool::start_server_with_random_port(
|
||||
monero_rpc_pool::config::Config::new_random_port(
|
||||
"127.0.0.1".to_string(),
|
||||
config.data.dir.join("monero-rpc-pool"),
|
||||
),
|
||||
monero_rpc_pool::config::Config::new_random_port(config.data.dir.join("monero-rpc-pool")),
|
||||
env_config.monero_network,
|
||||
)
|
||||
.await
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ pub mod tauri_bindings;
|
|||
|
||||
use crate::cli::api::tauri_bindings::SeedChoice;
|
||||
use crate::cli::command::{Bitcoin, Monero};
|
||||
use crate::common::tor::init_tor_client;
|
||||
use crate::common::tor::{bootstrap_tor_client, create_tor_client};
|
||||
use crate::common::tracing_util::Format;
|
||||
use crate::database::{open_db, AccessMode};
|
||||
use crate::network::rendezvous::XmrBtcNamespace;
|
||||
|
|
@ -204,6 +204,7 @@ pub struct ContextBuilder {
|
|||
debug: bool,
|
||||
json: bool,
|
||||
tor: bool,
|
||||
enable_monero_tor: bool,
|
||||
tauri_handle: Option<TauriHandle>,
|
||||
}
|
||||
|
||||
|
|
@ -227,6 +228,7 @@ impl ContextBuilder {
|
|||
debug: false,
|
||||
json: false,
|
||||
tor: false,
|
||||
enable_monero_tor: false,
|
||||
tauri_handle: None,
|
||||
}
|
||||
}
|
||||
|
|
@ -280,6 +282,12 @@ impl ContextBuilder {
|
|||
self
|
||||
}
|
||||
|
||||
/// Whether to route Monero wallet traffic through Tor (default false)
|
||||
pub fn with_enable_monero_tor(mut self, enable_monero_tor: bool) -> Self {
|
||||
self.enable_monero_tor = enable_monero_tor;
|
||||
self
|
||||
}
|
||||
|
||||
/// Takes the builder, initializes the context by initializing the wallets and other components and returns the Context.
|
||||
pub async fn build(self) -> Result<Context> {
|
||||
// This is the data directory for the eigenwallet (wallet files)
|
||||
|
|
@ -314,12 +322,29 @@ impl ContextBuilder {
|
|||
);
|
||||
});
|
||||
|
||||
// Start the rpc pool for the monero wallet
|
||||
// Create unbootstrapped Tor client early if enabled
|
||||
let unbootstrapped_tor_client = if self.tor {
|
||||
match create_tor_client(&base_data_dir).await.inspect_err(|err| {
|
||||
tracing::warn!(%err, "Failed to create Tor client. We will continue without Tor");
|
||||
}) {
|
||||
Ok(client) => Some(client),
|
||||
Err(_) => None,
|
||||
}
|
||||
} else {
|
||||
tracing::warn!("Internal Tor client not enabled, skipping initialization");
|
||||
None
|
||||
};
|
||||
|
||||
// Start the rpc pool for the monero wallet with optional Tor client based on enable_monero_tor setting
|
||||
let (server_info, mut status_receiver, pool_handle) =
|
||||
monero_rpc_pool::start_server_with_random_port(
|
||||
monero_rpc_pool::config::Config::new_random_port(
|
||||
"127.0.0.1".to_string(),
|
||||
monero_rpc_pool::config::Config::new_random_port_with_tor_client(
|
||||
base_data_dir.join("monero-rpc-pool"),
|
||||
if self.enable_monero_tor {
|
||||
unbootstrapped_tor_client.clone()
|
||||
} else {
|
||||
None
|
||||
},
|
||||
),
|
||||
match self.is_testnet {
|
||||
true => crate::monero::Network::Stagenet,
|
||||
|
|
@ -460,25 +485,25 @@ impl ContextBuilder {
|
|||
}
|
||||
};
|
||||
|
||||
let initialize_tor_client = async {
|
||||
// Don't init a tor client unless we should use it.
|
||||
if !self.tor {
|
||||
tracing::warn!("Internal Tor client not enabled, skipping initialization");
|
||||
return Ok(None);
|
||||
let bootstrap_tor_client_task = async {
|
||||
// Bootstrap the Tor client if we have one
|
||||
match unbootstrapped_tor_client.clone() {
|
||||
Some(tor_client) => {
|
||||
bootstrap_tor_client(tor_client.clone(), tauri_handle.clone())
|
||||
.await
|
||||
.inspect_err(|err| {
|
||||
tracing::warn!(%err, "Failed to bootstrap Tor client. It will remain unbootstrapped");
|
||||
})
|
||||
.ok();
|
||||
|
||||
Ok(Some(tor_client))
|
||||
}
|
||||
None => Ok(None),
|
||||
}
|
||||
|
||||
let maybe_tor_client = init_tor_client(&data_dir, tauri_handle.clone())
|
||||
.await
|
||||
.inspect_err(|err| {
|
||||
tracing::warn!(%err, "Failed to create Tor client. We will continue without Tor");
|
||||
})
|
||||
.ok();
|
||||
|
||||
Ok(maybe_tor_client)
|
||||
};
|
||||
|
||||
let (bitcoin_wallet, tor) =
|
||||
tokio::try_join!(initialize_bitcoin_wallet, initialize_tor_client,)?;
|
||||
tokio::try_join!(initialize_bitcoin_wallet, bootstrap_tor_client_task,)?;
|
||||
|
||||
// If we have a bitcoin wallet and a tauri handle, we start a background task
|
||||
if let Some(wallet) = bitcoin_wallet.clone() {
|
||||
|
|
|
|||
|
|
@ -1008,6 +1008,8 @@ pub struct TauriSettings {
|
|||
pub electrum_rpc_urls: Vec<String>,
|
||||
/// Whether to initialize and use a tor client.
|
||||
pub use_tor: bool,
|
||||
/// Whether to route Monero wallet traffic through Tor
|
||||
pub enable_monero_tor: bool,
|
||||
}
|
||||
|
||||
#[typeshare]
|
||||
|
|
|
|||
|
|
@ -8,9 +8,9 @@ use arti_client::{config::TorClientConfigBuilder, status::BootstrapStatus, Error
|
|||
use futures::StreamExt;
|
||||
use tor_rtcompat::tokio::TokioRustlsRuntime;
|
||||
|
||||
pub async fn init_tor_client(
|
||||
/// Creates an unbootstrapped Tor client
|
||||
pub async fn create_tor_client(
|
||||
data_dir: &Path,
|
||||
tauri_handle: Option<TauriHandle>,
|
||||
) -> Result<Arc<TorClient<TokioRustlsRuntime>>, Error> {
|
||||
// We store the Tor state in the data directory
|
||||
let data_dir = data_dir.join("tor");
|
||||
|
|
@ -23,20 +23,28 @@ pub async fn init_tor_client(
|
|||
.build()
|
||||
.expect("We initialized the Tor client all required attributes");
|
||||
|
||||
// Start the Arti client, and let it bootstrap a connection to the Tor network.
|
||||
// (This takes a while to gather the necessary directory information.
|
||||
// It uses cached information when possible.)
|
||||
// Create the Arti client without bootstrapping
|
||||
let runtime = TokioRustlsRuntime::current().expect("We are always running with tokio");
|
||||
|
||||
tracing::debug!("Bootstrapping Tor client");
|
||||
tracing::debug!("Creating unbootstrapped Tor client");
|
||||
|
||||
let tor_client = TorClient::with_runtime(runtime)
|
||||
.config(config)
|
||||
.create_unbootstrapped_async()
|
||||
.await?;
|
||||
|
||||
Ok(Arc::new(tor_client))
|
||||
}
|
||||
|
||||
/// Bootstraps an existing Tor client
|
||||
pub async fn bootstrap_tor_client(
|
||||
tor_client: Arc<TorClient<TokioRustlsRuntime>>,
|
||||
tauri_handle: Option<TauriHandle>,
|
||||
) -> Result<(), Error> {
|
||||
let mut bootstrap_events = tor_client.bootstrap_events();
|
||||
|
||||
tracing::debug!("Bootstrapping Tor client");
|
||||
|
||||
// Create a background progress handle for the Tor bootstrap process
|
||||
// The handle manages the TauriHandle internally, so we don't need to worry about it anymore
|
||||
let progress_handle =
|
||||
|
|
@ -67,7 +75,7 @@ pub async fn init_tor_client(
|
|||
},
|
||||
}?;
|
||||
|
||||
Ok(Arc::new(tor_client))
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// A trait to convert the Tor bootstrap event into a TauriBootstrapStatus
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue