1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
//! Creating isolated connections to specific peers.

use std::future::Future;

use futures::future::TryFutureExt;
use tokio::io::{AsyncRead, AsyncWrite};
use tower::{util::Oneshot, Service};

use zebra_chain::{chain_tip::NoChainTip, parameters::Network};

use crate::{
    peer::{self, Client, ConnectedAddr, HandshakeRequest},
    peer_set::ActiveConnectionCounter,
    BoxError, Config, PeerSocketAddr, Request, Response,
};

// Wait until `arti-client`'s dependency `x25519-dalek v1.2.0` is updated to a higher version. (#5492)
// #[cfg(feature = "tor")]
// pub(crate) mod tor;

#[cfg(test)]
mod tests;

/// Creates a Zcash peer connection using the provided data stream.
/// This connection is completely isolated from all other node state.
///
/// The connection pool returned by [`init`](crate::init)
/// should be used for all requests that
/// don't require isolated state or use of an existing TCP connection. However,
/// this low-level API is useful for custom network crawlers or Tor connections.
///
/// In addition to being completely isolated from all other node state, this
/// function also aims to be minimally distinguishable from other clients.
///
/// SECURITY TODO: check if the timestamp field can be zeroed, to remove another distinguisher (#3300)
///
/// Note that this function does not implement any timeout behavior, so callers may
/// want to layer it with a timeout as appropriate for their application.
///
/// # Inputs
///
/// - `network`: the Zcash [`Network`] used for this connection.
///
/// - `data_stream`: an existing data stream. This can be a non-anonymised TCP connection,
///                  or a Tor client `arti_client::DataStream`.
///
/// - `user_agent`: a valid BIP14 user-agent, e.g., the empty string.
pub fn connect_isolated<PeerTransport>(
    network: &Network,
    data_stream: PeerTransport,
    user_agent: String,
) -> impl Future<Output = Result<Client, BoxError>>
where
    PeerTransport: AsyncRead + AsyncWrite + Unpin + Send + 'static,
{
    let nil_inbound_service =
        tower::service_fn(|_req| async move { Ok::<Response, BoxError>(Response::Nil) });

    connect_isolated_with_inbound(network, data_stream, user_agent, nil_inbound_service)
}

/// Creates an isolated Zcash peer connection using the provided data stream.
/// This function is for testing purposes only.
///
/// See [`connect_isolated`] for details.
///
/// # Additional Inputs
///
/// - `inbound_service`: a [`tower::Service`] that answers inbound requests from the connected peer.
///
/// # Privacy
///
/// This function can make the isolated connection send different responses to peers,
/// which makes it stand out from other isolated connections from other peers.
pub fn connect_isolated_with_inbound<PeerTransport, InboundService>(
    network: &Network,
    data_stream: PeerTransport,
    user_agent: String,
    inbound_service: InboundService,
) -> impl Future<Output = Result<Client, BoxError>>
where
    PeerTransport: AsyncRead + AsyncWrite + Unpin + Send + 'static,
    InboundService:
        Service<Request, Response = Response, Error = BoxError> + Clone + Send + 'static,
    InboundService::Future: Send,
{
    let config = Config {
        network: network.clone(),
        ..Config::default()
    };

    let handshake = peer::Handshake::builder()
        .with_config(config)
        .with_inbound_service(inbound_service)
        .with_user_agent(user_agent)
        .with_latest_chain_tip(NoChainTip)
        .finish()
        .expect("provided mandatory builder parameters");

    // Don't send or track any metadata about the connection
    let connected_addr = ConnectedAddr::new_isolated();
    let connection_tracker = ActiveConnectionCounter::new_counter().track_connection();

    Oneshot::new(
        handshake,
        HandshakeRequest {
            data_stream,
            connected_addr,
            connection_tracker,
        },
    )
}

/// Creates a direct TCP Zcash peer connection to `addr`.
/// This connection is completely isolated from all other node state.
///
/// See [`connect_isolated`] for details.
///
/// # Privacy
///
/// Transactions sent over this connection can be linked to the sending and receiving IP address
/// by passive internet observers.
///
///
/// Prefer `connect_isolated_tor` if available.
pub fn connect_isolated_tcp_direct(
    network: &Network,
    addr: impl Into<PeerSocketAddr>,
    user_agent: String,
) -> impl Future<Output = Result<Client, BoxError>> {
    let nil_inbound_service =
        tower::service_fn(|_req| async move { Ok::<Response, BoxError>(Response::Nil) });

    connect_isolated_tcp_direct_with_inbound(network, addr, user_agent, nil_inbound_service)
}

/// Creates an isolated Zcash peer connection using the provided data stream.
/// This function is for testing purposes only.
///
/// See [`connect_isolated_with_inbound`] and [`connect_isolated_tcp_direct`] for details.
///
/// # Privacy
///
/// This function can make the isolated connection send different responses to peers,
/// which makes it stand out from other isolated connections from other peers.
pub fn connect_isolated_tcp_direct_with_inbound<InboundService>(
    network: &Network,
    addr: impl Into<PeerSocketAddr>,
    user_agent: String,
    inbound_service: InboundService,
) -> impl Future<Output = Result<Client, BoxError>>
where
    InboundService:
        Service<Request, Response = Response, Error = BoxError> + Clone + Send + 'static,
    InboundService::Future: Send,
{
    let addr = addr.into();
    let network = network.clone();

    tokio::net::TcpStream::connect(*addr)
        .err_into()
        .and_then(move |tcp_stream| {
            connect_isolated_with_inbound(&network, tcp_stream, user_agent, inbound_service)
        })
}