2020-09-01 20:41:43 -07:00
|
|
|
//! Code for creating isolated connections to specific peers.
|
|
|
|
|
|
|
|
use std::{
|
|
|
|
future::Future,
|
|
|
|
pin::Pin,
|
|
|
|
task::{Context, Poll},
|
|
|
|
};
|
|
|
|
|
|
|
|
use futures::future::{FutureExt, TryFutureExt};
|
|
|
|
use tokio::net::TcpStream;
|
|
|
|
use tower::{
|
|
|
|
util::{BoxService, Oneshot},
|
|
|
|
Service,
|
|
|
|
};
|
|
|
|
|
2021-08-26 18:34:33 -07:00
|
|
|
use zebra_chain::chain_tip::NoChainTip;
|
|
|
|
|
|
|
|
use crate::{
|
|
|
|
peer::{self, ConnectedAddr},
|
|
|
|
BoxError, Config, Request, Response,
|
|
|
|
};
|
2020-09-01 20:41:43 -07:00
|
|
|
|
|
|
|
/// Use the provided TCP connection to create a Zcash connection completely
|
|
|
|
/// isolated from all other node state.
|
|
|
|
///
|
|
|
|
/// The connection pool returned by `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
|
|
|
|
/// method also aims to be minimally distinguishable from other clients.
|
|
|
|
///
|
|
|
|
/// Note that this method does not implement any timeout behavior, so callers may
|
|
|
|
/// want to layer it with a timeout as appropriate for their application.
|
|
|
|
///
|
|
|
|
/// # Inputs
|
|
|
|
///
|
|
|
|
/// - `conn`: an existing TCP connection to use. Passing an existing TCP
|
|
|
|
/// connection allows this method to be used with clearnet or Tor transports.
|
|
|
|
///
|
|
|
|
/// - `user_agent`: a valid BIP14 user-agent, e.g., the empty string.
|
2021-02-04 14:11:21 -08:00
|
|
|
///
|
|
|
|
/// # Bug
|
|
|
|
///
|
|
|
|
/// `connect_isolated` only works on `Mainnet`, see #1687.
|
2020-09-17 11:06:42 -07:00
|
|
|
pub fn connect_isolated(
|
2020-09-01 20:41:43 -07:00
|
|
|
conn: TcpStream,
|
|
|
|
user_agent: String,
|
2020-09-17 11:06:42 -07:00
|
|
|
) -> impl Future<
|
|
|
|
Output = Result<
|
|
|
|
BoxService<Request, Response, Box<dyn std::error::Error + Send + Sync + 'static>>,
|
|
|
|
Box<dyn std::error::Error + Send + Sync + 'static>,
|
|
|
|
>,
|
|
|
|
> {
|
2020-09-01 20:41:43 -07:00
|
|
|
let handshake = peer::Handshake::builder()
|
|
|
|
.with_config(Config::default())
|
|
|
|
.with_inbound_service(tower::service_fn(|_req| async move {
|
2020-09-17 11:06:42 -07:00
|
|
|
Ok::<Response, Box<dyn std::error::Error + Send + Sync + 'static>>(Response::Nil)
|
2020-09-01 20:41:43 -07:00
|
|
|
}))
|
|
|
|
.with_user_agent(user_agent)
|
2021-08-26 18:34:33 -07:00
|
|
|
.with_chain_tip_receiver(NoChainTip)
|
2020-09-01 20:41:43 -07:00
|
|
|
.finish()
|
|
|
|
.expect("provided mandatory builder parameters");
|
|
|
|
|
2021-05-06 17:50:04 -07:00
|
|
|
// Don't send any metadata about the connection
|
|
|
|
let connected_addr = ConnectedAddr::new_isolated();
|
2020-09-01 20:41:43 -07:00
|
|
|
|
2021-05-06 17:50:04 -07:00
|
|
|
Oneshot::new(handshake, (conn, connected_addr))
|
|
|
|
.map_ok(|client| BoxService::new(Wrapper(client)))
|
2020-09-01 20:41:43 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// This can be deleted when a new version of Tower with map_err is released.
|
|
|
|
struct Wrapper(peer::Client);
|
|
|
|
|
|
|
|
impl Service<Request> for Wrapper {
|
|
|
|
type Response = Response;
|
2020-09-18 11:20:55 -07:00
|
|
|
type Error = BoxError;
|
2020-09-01 20:41:43 -07:00
|
|
|
type Future =
|
|
|
|
Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send + 'static>>;
|
|
|
|
|
|
|
|
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
|
|
|
self.0.poll_ready(cx).map_err(Into::into)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn call(&mut self, req: Request) -> Self::Future {
|
|
|
|
self.0.call(req).map_err(Into::into).boxed()
|
|
|
|
}
|
|
|
|
}
|
2020-09-14 12:47:07 -07:00
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
async fn connect_isolated_sends_minimally_distinguished_version_message() {
|
|
|
|
use crate::{
|
|
|
|
protocol::external::{Codec, Message},
|
|
|
|
types::PeerServices,
|
|
|
|
};
|
|
|
|
use futures::stream::StreamExt;
|
|
|
|
use tokio_util::codec::Framed;
|
|
|
|
|
2020-11-19 11:54:20 -08:00
|
|
|
let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
|
2020-09-14 12:47:07 -07:00
|
|
|
let listen_addr = listener.local_addr().unwrap();
|
|
|
|
|
|
|
|
let conn = tokio::net::TcpStream::connect(listen_addr).await.unwrap();
|
|
|
|
|
|
|
|
tokio::spawn(connect_isolated(conn, "".to_string()));
|
|
|
|
|
|
|
|
let (conn, _) = listener.accept().await.unwrap();
|
|
|
|
|
|
|
|
let mut stream = Framed::new(conn, Codec::builder().finish());
|
|
|
|
if let Message::Version {
|
|
|
|
services,
|
|
|
|
timestamp,
|
|
|
|
address_from,
|
|
|
|
user_agent,
|
|
|
|
start_height,
|
|
|
|
relay,
|
|
|
|
..
|
|
|
|
} = stream
|
|
|
|
.next()
|
|
|
|
.await
|
|
|
|
.expect("stream item")
|
|
|
|
.expect("item is Ok(msg)")
|
|
|
|
{
|
|
|
|
// Check that the version message sent by connect_isolated
|
|
|
|
// has the fields specified in the Stolon RFC.
|
|
|
|
assert_eq!(services, PeerServices::empty());
|
|
|
|
assert_eq!(timestamp.timestamp() % (5 * 60), 0);
|
|
|
|
assert_eq!(
|
|
|
|
address_from,
|
|
|
|
(PeerServices::empty(), "0.0.0.0:8233".parse().unwrap())
|
|
|
|
);
|
|
|
|
assert_eq!(user_agent, "");
|
|
|
|
assert_eq!(start_height.0, 0);
|
2021-04-27 05:57:45 -07:00
|
|
|
assert!(!relay);
|
2020-09-14 12:47:07 -07:00
|
|
|
} else {
|
|
|
|
panic!("handshake did not send version message");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|