Skip to main content

stratum_apps/network_helpers/
mod.rs

1//! High-level networking utilities for SV2 connections
2//!
3//! This module provides connection management, encrypted streams, and protocol handling
4//! for Stratum V2 applications. It includes support for:
5//!
6//! - Noise-encrypted connections ([`crate::network_helpers::noise_connection`],
7//!   [`crate::network_helpers::noise_stream`])
8//! - SV1 protocol connections (`sv1_connection`) - when `sv1` feature is enabled
9//! - Hostname resolution ([`crate::network_helpers::resolve_hostname`])
10//!
11//! Originally from the `network_helpers_sv2` crate.
12
13pub mod noise_connection;
14pub mod noise_stream;
15pub mod resolve_hostname;
16
17#[cfg(feature = "sv1")]
18pub mod sv1_connection;
19
20pub use resolve_hostname::{resolve_host, resolve_host_port, ResolveError};
21
22use async_channel::{RecvError, SendError};
23use std::{fmt, time::Duration};
24use stratum_core::{
25    binary_sv2::{Deserialize, GetSize, Serialize},
26    codec_sv2::{Error as CodecError, HandshakeRole},
27    noise_sv2::{Initiator, Responder},
28};
29use tokio::net::TcpStream;
30
31use crate::{
32    key_utils::{Secp256k1PublicKey, Secp256k1SecretKey},
33    network_helpers::noise_stream::NoiseTcpStream,
34};
35
36/// Networking errors that can occur in SV2 connections
37#[derive(Debug)]
38pub enum Error {
39    /// Invalid handshake message received from remote peer
40    HandshakeRemoteInvalidMessage,
41    /// Error from the codec layer
42    CodecError(CodecError),
43    /// Error receiving from async channel
44    RecvError,
45    /// Error sending to async channel
46    SendError,
47    /// Socket was closed, likely by the peer
48    SocketClosed,
49    /// Handshake timeout
50    HandshakeTimeout,
51    /// Invalid key provided to construct an Initiator or Responder
52    InvalidKey,
53    /// DNS resolution failed for a hostname
54    DnsResolutionFailed(String),
55}
56
57impl fmt::Display for Error {
58    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
59        match self {
60            Error::HandshakeRemoteInvalidMessage => {
61                write!(f, "Invalid handshake message received from remote peer")
62            }
63
64            Error::CodecError(e) => write!(f, "{e}"),
65
66            Error::RecvError => write!(f, "Error receiving from async channel"),
67
68            Error::SendError => write!(f, "Error sending to async channel"),
69
70            Error::SocketClosed => write!(f, "Socket was closed (likely by the peer)"),
71
72            Error::HandshakeTimeout => write!(f, "Handshake timeout"),
73
74            Error::InvalidKey => write!(f, "Invalid key provided for handshake"),
75
76            Error::DnsResolutionFailed(msg) => write!(f, "DNS resolution failed: {msg}"),
77        }
78    }
79}
80
81impl From<CodecError> for Error {
82    fn from(e: CodecError) -> Self {
83        Error::CodecError(e)
84    }
85}
86
87impl From<RecvError> for Error {
88    fn from(_: RecvError) -> Self {
89        Error::RecvError
90    }
91}
92
93impl<T> From<SendError<T>> for Error {
94    fn from(_: SendError<T>) -> Self {
95        Error::SendError
96    }
97}
98
99impl From<ResolveError> for Error {
100    fn from(e: ResolveError) -> Self {
101        Error::DnsResolutionFailed(e.to_string())
102    }
103}
104
105/// Default handshake timeout used by [`connect_with_noise`] and [`accept_noise_connection`].
106/// Use [`noise_stream::NoiseTcpStream::new`] directly to override.
107const NOISE_HANDSHAKE_TIMEOUT: Duration = Duration::from_secs(10);
108
109/// Default timeout for establishing outbound TCP connections to SV2 peers.
110/// This keeps fallback attempts bounded when a remote endpoint is unreachable or filtered.
111pub const TCP_CONNECT_TIMEOUT: Duration = Duration::from_secs(5);
112
113/// Connects to an upstream server as a Noise initiator, returning the split read/write halves.
114///
115/// The handshake timeout is opinionated and fixed at `NOISE_HANDSHAKE_TIMEOUT`. If you need a
116/// custom timeout, use [`noise_stream::NoiseTcpStream::new`] directly.
117///
118/// Pass `Some(key)` to verify the server's authority public key, or `None` to skip
119/// verification (encrypted but unauthenticated — use only on trusted networks).
120pub async fn connect_with_noise<Message>(
121    stream: TcpStream,
122    authority_pub_key: Option<Secp256k1PublicKey>,
123) -> Result<NoiseTcpStream<Message>, Error>
124where
125    Message: Serialize + Deserialize<'static> + GetSize + Send + 'static,
126{
127    let initiator = match authority_pub_key {
128        Some(key) => Initiator::from_raw_k(key.into_bytes()).map_err(|_| Error::InvalidKey)?,
129        None => Initiator::without_pk().map_err(|_| Error::InvalidKey)?,
130    };
131    let stream = noise_stream::NoiseTcpStream::new(
132        stream,
133        HandshakeRole::Initiator(initiator),
134        NOISE_HANDSHAKE_TIMEOUT,
135    )
136    .await?;
137    Ok(stream)
138}
139
140/// Accepts a downstream connection as a Noise responder, returning the split read/write halves.
141///
142/// The handshake timeout is opinionated and fixed at `NOISE_HANDSHAKE_TIMEOUT`. If you need a
143/// custom timeout, use [`noise_stream::NoiseTcpStream::new`] directly.
144///
145/// `cert_validity` controls how long the generated Noise certificate is valid,
146/// which is independent of the handshake timeout.
147pub async fn accept_noise_connection<Message>(
148    stream: TcpStream,
149    pub_key: Secp256k1PublicKey,
150    prv_key: Secp256k1SecretKey,
151    cert_validity: u64,
152) -> Result<NoiseTcpStream<Message>, Error>
153where
154    Message: Serialize + Deserialize<'static> + GetSize + Send + 'static,
155{
156    let responder = Responder::from_authority_kp(
157        &pub_key.into_bytes(),
158        &prv_key.into_bytes(),
159        Duration::from_secs(cert_validity),
160    )
161    .map_err(|_| Error::InvalidKey)?;
162    let stream = noise_stream::NoiseTcpStream::new(
163        stream,
164        HandshakeRole::Responder(responder),
165        NOISE_HANDSHAKE_TIMEOUT,
166    )
167    .await?;
168    Ok(stream)
169}