zebra_network/isolated.rs
1//! Creating isolated connections to specific peers.
2
3use std::future::Future;
4
5use futures::future::TryFutureExt;
6use tokio::io::{AsyncRead, AsyncWrite};
7use tower::{util::Oneshot, Service};
8
9use zebra_chain::{chain_tip::NoChainTip, parameters::Network};
10
11use crate::{
12 peer::{self, Client, ConnectedAddr, HandshakeRequest},
13 peer_set::ActiveConnectionCounter,
14 BoxError, Config, PeerSocketAddr, Request, Response,
15};
16
17// Wait until `arti-client`'s dependency `x25519-dalek v1.2.0` is updated to a higher version. (#5492)
18// #[cfg(feature = "tor")]
19// pub(crate) mod tor;
20
21#[cfg(test)]
22mod tests;
23
24/// Creates a Zcash peer connection using the provided data stream.
25/// This connection is completely isolated from all other node state.
26///
27/// The connection pool returned by [`init`](crate::init)
28/// should be used for all requests that
29/// don't require isolated state or use of an existing TCP connection. However,
30/// this low-level API is useful for custom network crawlers or Tor connections.
31///
32/// In addition to being completely isolated from all other node state, this
33/// function also aims to be minimally distinguishable from other clients.
34///
35/// SECURITY TODO: check if the timestamp field can be zeroed, to remove another distinguisher (#3300)
36///
37/// Note that this function does not implement any timeout behavior, so callers may
38/// want to layer it with a timeout as appropriate for their application.
39///
40/// # Inputs
41///
42/// - `network`: the Zcash [`Network`] used for this connection.
43///
44/// - `data_stream`: an existing data stream. This can be a non-anonymised TCP connection,
45/// or a Tor client `arti_client::DataStream`.
46///
47/// - `user_agent`: a valid BIP14 user-agent, e.g., the empty string.
48pub fn connect_isolated<PeerTransport>(
49 network: &Network,
50 data_stream: PeerTransport,
51 user_agent: String,
52) -> impl Future<Output = Result<Client, BoxError>>
53where
54 PeerTransport: AsyncRead + AsyncWrite + Unpin + Send + 'static,
55{
56 let nil_inbound_service =
57 tower::service_fn(|_req| async move { Ok::<Response, BoxError>(Response::Nil) });
58
59 connect_isolated_with_inbound(network, data_stream, user_agent, nil_inbound_service)
60}
61
62/// Creates an isolated Zcash peer connection using the provided data stream.
63/// This function is for testing purposes only.
64///
65/// See [`connect_isolated`] for details.
66///
67/// # Additional Inputs
68///
69/// - `inbound_service`: a [`tower::Service`] that answers inbound requests from the connected peer.
70///
71/// # Privacy
72///
73/// This function can make the isolated connection send different responses to peers,
74/// which makes it stand out from other isolated connections from other peers.
75pub fn connect_isolated_with_inbound<PeerTransport, InboundService>(
76 network: &Network,
77 data_stream: PeerTransport,
78 user_agent: String,
79 inbound_service: InboundService,
80) -> impl Future<Output = Result<Client, BoxError>>
81where
82 PeerTransport: AsyncRead + AsyncWrite + Unpin + Send + 'static,
83 InboundService:
84 Service<Request, Response = Response, Error = BoxError> + Clone + Send + 'static,
85 InboundService::Future: Send,
86{
87 let config = Config {
88 network: network.clone(),
89 ..Config::default()
90 };
91
92 let handshake = peer::Handshake::builder()
93 .with_config(config)
94 .with_inbound_service(inbound_service)
95 .with_user_agent(user_agent)
96 .with_latest_chain_tip(NoChainTip)
97 .finish()
98 .expect("provided mandatory builder parameters");
99
100 // Don't send or track any metadata about the connection
101 let connected_addr = ConnectedAddr::new_isolated();
102 let connection_tracker = ActiveConnectionCounter::new_counter().track_connection();
103
104 Oneshot::new(
105 handshake,
106 HandshakeRequest {
107 data_stream,
108 connected_addr,
109 connection_tracker,
110 },
111 )
112}
113
114/// Creates a direct TCP Zcash peer connection to `addr`.
115/// This connection is completely isolated from all other node state.
116///
117/// See [`connect_isolated`] for details.
118///
119/// # Privacy
120///
121/// Transactions sent over this connection can be linked to the sending and receiving IP address
122/// by passive internet observers.
123///
124///
125/// Prefer `connect_isolated_tor` if available.
126pub fn connect_isolated_tcp_direct(
127 network: &Network,
128 addr: impl Into<PeerSocketAddr>,
129 user_agent: String,
130) -> impl Future<Output = Result<Client, BoxError>> {
131 let nil_inbound_service =
132 tower::service_fn(|_req| async move { Ok::<Response, BoxError>(Response::Nil) });
133
134 connect_isolated_tcp_direct_with_inbound(network, addr, user_agent, nil_inbound_service)
135}
136
137/// Creates an isolated Zcash peer connection using the provided data stream.
138/// This function is for testing purposes only.
139///
140/// See [`connect_isolated_with_inbound`] and [`connect_isolated_tcp_direct`] for details.
141///
142/// # Privacy
143///
144/// This function can make the isolated connection send different responses to peers,
145/// which makes it stand out from other isolated connections from other peers.
146pub fn connect_isolated_tcp_direct_with_inbound<InboundService>(
147 network: &Network,
148 addr: impl Into<PeerSocketAddr>,
149 user_agent: String,
150 inbound_service: InboundService,
151) -> impl Future<Output = Result<Client, BoxError>>
152where
153 InboundService:
154 Service<Request, Response = Response, Error = BoxError> + Clone + Send + 'static,
155 InboundService::Future: Send,
156{
157 let addr = addr.into();
158 let network = network.clone();
159
160 tokio::net::TcpStream::connect(*addr)
161 .err_into()
162 .and_then(move |tcp_stream| {
163 connect_isolated_with_inbound(&network, tcp_stream, user_agent, inbound_service)
164 })
165}