Skip to content

Commit a6e580b

Browse files
committed
fix(wallet): route Tor websocket subscriptions through Arti
1 parent 6d117e3 commit a6e580b

7 files changed

Lines changed: 117 additions & 5 deletions

File tree

crates/cdk-http-client/src/transport/mod.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,15 @@ use crate::{HttpClient, HttpClientBuilder};
1616
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
1717
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
1818
pub trait Transport: Default + Send + Sync + Debug + Clone {
19+
/// Connect to a WebSocket endpoint using this transport.
20+
async fn ws_connect(
21+
&self,
22+
url: &str,
23+
headers: &[(&str, &str)],
24+
) -> Result<(crate::ws::WsSender, crate::ws::WsReceiver), crate::ws::WsError> {
25+
crate::ws::connect(url, headers).await
26+
}
27+
1928
/// Make the transport use a proxy.
2029
///
2130
/// SOCKS proxy schemes such as `socks5h` are available only when this crate

crates/cdk-http-client/src/transport/tor_transport.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,22 @@ impl Transport for TorAsync {
295295
Ok(txts)
296296
}
297297

298+
async fn ws_connect(
299+
&self,
300+
url: &str,
301+
headers: &[(&str, &str)],
302+
) -> Result<(crate::ws::WsSender, crate::ws::WsReceiver), crate::ws::WsError> {
303+
let parsed_url = Url::parse(url)
304+
.map_err(|e| crate::ws::WsError::Connection(format!("Invalid URL: {e}")))?;
305+
let pool = self
306+
.ensure_pool()
307+
.await
308+
.map_err(|e| crate::ws::WsError::Connection(e.to_string()))?;
309+
let idx = self.index_for_request(&http::Method::GET, &parsed_url, None, pool.len());
310+
311+
crate::ws::connect_tor(pool[idx].clone(), url, headers).await
312+
}
313+
298314
async fn http_get<R>(&self, url: Url, auth: Option<AuthToken>) -> Result<R, HttpError>
299315
where
300316
R: DeserializeOwned,

crates/cdk-http-client/src/ws/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ mod native;
1010
mod wasm;
1111

1212
pub use error::WsError;
13+
#[cfg(all(feature = "tor", not(target_arch = "wasm32")))]
14+
pub(crate) use native::connect_tor;
1315
#[cfg(not(target_arch = "wasm32"))]
1416
pub use native::{connect, WsReceiver, WsSender};
1517
#[cfg(target_arch = "wasm32")]

crates/cdk-http-client/src/ws/native.rs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,3 +102,55 @@ pub async fn connect(
102102
},
103103
))
104104
}
105+
106+
/// Connect to a WebSocket endpoint through an Arti Tor client.
107+
#[cfg(feature = "tor")]
108+
pub(crate) async fn connect_tor(
109+
tor_client: arti_client::TorClient<tor_rtcompat::PreferredRuntime>,
110+
url: &str,
111+
headers: &[(&str, &str)],
112+
) -> Result<(WsSender, WsReceiver), WsError> {
113+
let parsed_url =
114+
url::Url::parse(url).map_err(|e| WsError::Connection(format!("Invalid URL: {e}")))?;
115+
116+
let host = parsed_url
117+
.host_str()
118+
.ok_or_else(|| WsError::Connection("WebSocket URL must include a host".to_string()))?;
119+
let port = parsed_url
120+
.port_or_known_default()
121+
.ok_or_else(|| WsError::Connection("WebSocket URL must include a port".to_string()))?;
122+
123+
let mut request = url
124+
.into_client_request()
125+
.map_err(|e| WsError::Connection(e.to_string()))?;
126+
127+
for &(name, value) in headers {
128+
if let (Ok(header_name), Ok(header_value)) = (
129+
name.parse::<tokio_tungstenite::tungstenite::http::header::HeaderName>(),
130+
value.parse::<tokio_tungstenite::tungstenite::http::header::HeaderValue>(),
131+
) {
132+
request.headers_mut().insert(header_name, header_value);
133+
}
134+
}
135+
136+
let stream = tor_client
137+
.connect((host, port))
138+
.await
139+
.map_err(|e| WsError::Connection(e.to_string()))?;
140+
141+
let (ws_stream, _) =
142+
tokio_tungstenite::client_async_tls_with_config(request, stream, None, None)
143+
.await
144+
.map_err(|e| WsError::Connection(e.to_string()))?;
145+
146+
let (sink, stream) = ws_stream.split();
147+
148+
Ok((
149+
WsSender {
150+
inner: Box::new(sink),
151+
},
152+
WsReceiver {
153+
inner: Box::new(stream),
154+
},
155+
))
156+
}

crates/cdk/src/wallet/mint_connector/http_client.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,20 @@ impl<T> MintConnector for HttpClient<T>
311311
where
312312
T: Transport + Send + Sync + 'static,
313313
{
314+
async fn connect_websocket(
315+
&self,
316+
url: &str,
317+
headers: &[(&str, &str)],
318+
) -> Result<
319+
(
320+
cdk_common::ws_client::WsSender,
321+
cdk_common::ws_client::WsReceiver,
322+
),
323+
cdk_common::ws_client::WsError,
324+
> {
325+
self.transport.ws_connect(url, headers).await
326+
}
327+
314328
#[cfg(all(feature = "bip353", not(target_arch = "wasm32")))]
315329
#[instrument(skip(self), fields(mint_url = %self.mint_url))]
316330
async fn resolve_dns_txt(&self, domain: &str) -> Result<Vec<String>, Error> {

crates/cdk/src/wallet/mint_connector/mod.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,21 @@ pub type TorHttpClient = http_client::HttpClient<transport::TorAsync>;
3636
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
3737
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
3838
pub trait MintConnector: Debug {
39+
/// Connect to a WebSocket endpoint using the connector's transport.
40+
async fn connect_websocket(
41+
&self,
42+
url: &str,
43+
headers: &[(&str, &str)],
44+
) -> Result<
45+
(
46+
cdk_common::ws_client::WsSender,
47+
cdk_common::ws_client::WsReceiver,
48+
),
49+
cdk_common::ws_client::WsError,
50+
> {
51+
cdk_common::ws_client::connect(url, headers).await
52+
}
53+
3954
#[cfg(all(feature = "bip353", not(target_arch = "wasm32")))]
4055
/// Resolve the DNS record getting the TXT value
4156
async fn resolve_dns_txt(&self, _domain: &str) -> Result<Vec<String>, Error>;

crates/cdk/src/wallet/subscription.rs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ use cdk_common::pub_sub::remote_consumer::{
2121
};
2222
use cdk_common::pub_sub::{Error as PubsubError, Spec, Subscriber};
2323
use cdk_common::subscription::WalletParams;
24-
use cdk_common::ws_client::{connect as ws_connect, WsError};
24+
use cdk_common::ws_client::WsError;
2525
use cdk_common::{CheckStateRequest, Method, PaymentMethod, RoutePath};
2626
use tokio::sync::mpsc;
2727
use uuid::Uuid;
@@ -528,10 +528,14 @@ async fn stream_client(
528528
let header_refs: Vec<(&str, &str)> = headers.iter().map(|(k, v)| (*k, v.as_str())).collect();
529529

530530
tracing::debug!("Connecting to {}", url);
531-
let (mut sender, mut receiver) = ws_connect(&url_str, &header_refs).await.map_err(|err| {
532-
tracing::error!("Error connecting: {err:?}");
533-
map_ws_error(err)
534-
})?;
531+
let (mut sender, mut receiver) = client
532+
.http_client
533+
.connect_websocket(&url_str, &header_refs)
534+
.await
535+
.map_err(|err| {
536+
tracing::error!("Error connecting: {err:?}");
537+
map_ws_error(err)
538+
})?;
535539

536540
tracing::debug!("Connected to {}", url);
537541

0 commit comments

Comments
 (0)