zcash_client_backend/tor/
grpc.rs1use std::{
2 error::Error as _,
3 fmt,
4 future::Future,
5 pin::Pin,
6 task::{Context, Poll},
7};
8
9use arti_client::{DataStream, StreamPrefs, config::BoolOrAuto};
10use hyper_util::rt::TokioIo;
11use tonic::transport::{Channel, ClientTlsConfig, Endpoint, Uri};
12use tower::Service;
13use tracing::debug;
14
15use super::{Client, Error, http};
16use crate::proto::service::compact_tx_streamer_client::CompactTxStreamerClient;
17
18impl Client {
19 pub async fn connect_to_lightwalletd(
34 &self,
35 endpoint: Uri,
36 allow_onion_services: bool,
37 ) -> Result<CompactTxStreamerClient<Channel>, Error> {
38 self.ensure_bootstrapped().await?;
39
40 let is_https = http::url_is_https(&endpoint)?;
41
42 let connector = if allow_onion_services {
43 HttpTcpConnector::with_onion_services(self.clone())
44 } else {
45 HttpTcpConnector::new(self.clone())
46 };
47
48 let channel = Endpoint::from(endpoint)
49 .connect_timeout(self.timeouts.connect)
50 .timeout(self.timeouts.request)
51 .http2_keep_alive_interval(self.timeouts.grpc_keepalive_interval)
52 .keep_alive_timeout(self.timeouts.grpc_keepalive_timeout)
53 .keep_alive_while_idle(false);
54 let channel = if is_https {
55 channel
56 .tls_config(ClientTlsConfig::new().with_webpki_roots())
57 .map_err(GrpcError::Tonic)?
58 } else {
59 channel
60 };
61
62 let conn = channel
63 .connect_with_connector(connector)
64 .await
65 .map_err(GrpcError::Tonic)?;
66
67 Ok(CompactTxStreamerClient::new(conn))
68 }
69}
70
71struct HttpTcpConnector {
72 client: Client,
73 prefs: StreamPrefs,
74}
75
76impl HttpTcpConnector {
77 fn new(client: Client) -> Self {
82 HttpTcpConnector {
83 client,
84 prefs: StreamPrefs::new(),
85 }
86 }
87
88 fn with_onion_services(client: Client) -> Self {
93 let mut prefs = StreamPrefs::new();
94 prefs.connect_to_onion_services(BoolOrAuto::Explicit(true));
95 HttpTcpConnector { client, prefs }
96 }
97}
98
99impl Service<Uri> for HttpTcpConnector {
100 type Response = TokioIo<DataStream>;
101 type Error = Error;
102 type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>;
103
104 fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
105 Poll::Ready(Ok(()))
106 }
107
108 fn call(&mut self, endpoint: Uri) -> Self::Future {
109 let parsed = http::parse_url(&endpoint);
110 let client = self.client.clone();
111 let prefs = self.prefs.clone();
112
113 let fut = async move {
114 let (_, host, port) = parsed?;
115
116 debug!("Connecting through Tor to {}:{}", host, port);
117 let stream = client
118 .inner
119 .connect_with_prefs((host.as_str(), port), &prefs)
120 .await?;
121
122 Ok(TokioIo::new(stream))
123 };
124
125 Box::pin(fut)
126 }
127}
128
129#[derive(Debug)]
131#[non_exhaustive]
132pub enum GrpcError {
133 Tonic(tonic::transport::Error),
135}
136
137impl fmt::Display for GrpcError {
138 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
139 match self {
140 GrpcError::Tonic(e) => {
141 if let Some(source) = e.source() {
142 write!(f, "Tonic error: {e}: {source}")
145 } else {
146 write!(f, "Tonic error: {e}")
147 }
148 }
149 }
150 }
151}
152
153impl std::error::Error for GrpcError {
154 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
155 match self {
156 GrpcError::Tonic(e) => Some(e),
157 }
158 }
159}
160
161impl From<tonic::transport::Error> for GrpcError {
162 fn from(e: tonic::transport::Error) -> Self {
163 GrpcError::Tonic(e)
164 }
165}