cuprate_p2p_core/
lib.rs

1//! # Cuprate P2P Core
2//!
3//! This crate is general purpose P2P networking library for working with Monero. This is a low level
4//! crate, which means it may seem verbose for a lot of use cases, if you want a crate that handles
5//! more of the P2P logic have a look at `cuprate-p2p`.
6//!
7//! # Network Zones
8//!
9//! This crate abstracts over network zones, Tor/I2p/clearnet with the [`NetworkZone`] trait. Currently only clearnet is implemented: [`ClearNet`].
10//!
11//! # Usage
12//!
13//! ## Connecting to a peer
14//!
15//! ```rust
16//! # use std::{net::SocketAddr, str::FromStr};
17//! #
18//! # use tower::ServiceExt;
19//! #
20//! # use cuprate_p2p_core::{
21//! #    client::{ConnectRequest, Connector, HandshakerBuilder},
22//! #    ClearNet, Network,
23//! #    transports::Tcp
24//! # };
25//! # use cuprate_wire::{common::PeerSupportFlags, BasicNodeData};
26//! # use cuprate_test_utils::monerod::monerod;
27//! #
28//! # tokio_test::block_on(async move {
29//! #
30//! # let _monerod = monerod::<&str>([]).await;
31//! # let addr = _monerod.p2p_addr();
32//! #
33//! // The information about our local node.
34//! let our_basic_node_data = BasicNodeData {
35//!     my_port: 0,
36//!     network_id: Network::Mainnet.network_id(),
37//!     peer_id: 0,
38//!     support_flags: PeerSupportFlags::FLUFFY_BLOCKS,
39//!     rpc_port: 0,
40//!     rpc_credits_per_hash: 0,
41//! };
42//!
43//! // See [`HandshakerBuilder`] for information about the default values set, they may not be
44//! // appropriate for every use case.
45//! let handshaker = HandshakerBuilder::<ClearNet, Tcp>::new(our_basic_node_data, ()).build();
46//!
47//! // The outbound connector.
48//! let mut connector = Connector::new(handshaker);
49//!
50//! // The connection.
51//! let connection = connector
52//!     .oneshot(ConnectRequest {
53//!         addr,
54//!         permit: None,
55//!     })
56//!     .await
57//!     .unwrap();
58//! # });
59//! ```
60
61cfg_if::cfg_if! {
62    // Used in `tests/`
63    if #[cfg(test)] {
64        use cuprate_test_utils as _;
65        use tokio_test as _;
66        use hex as _;
67    }
68}
69
70use std::{fmt::Debug, hash::Hash};
71
72use futures::{Sink, Stream};
73
74use cuprate_wire::{
75    levin::LevinMessage, network_address::NetworkAddressIncorrectZone, BucketError, Message,
76    NetworkAddress,
77};
78
79pub mod client;
80mod constants;
81pub mod error;
82pub mod handles;
83mod network_zones;
84pub mod protocol;
85pub mod services;
86pub mod transports;
87pub mod types;
88
89pub use error::*;
90pub use network_zones::ClearNet;
91pub use protocol::*;
92use services::*;
93//re-export
94pub use cuprate_helper::network::Network;
95pub use cuprate_wire::CoreSyncData;
96
97/// The direction of a connection.
98#[derive(Debug, Copy, Clone, Eq, PartialEq)]
99pub enum ConnectionDirection {
100    /// An inbound connection to our node.
101    Inbound,
102    /// An outbound connection from our node.
103    Outbound,
104}
105
106/// An address on a specific [`NetworkZone`].
107pub trait NetZoneAddress:
108    TryFrom<NetworkAddress, Error = NetworkAddressIncorrectZone>
109    + Into<NetworkAddress>
110    + std::fmt::Display
111    + Hash
112    + Eq
113    + Copy
114    + Send
115    + Sync
116    + Unpin
117    + 'static
118{
119    /// Cuprate needs to be able to ban peers by IP addresses and not just by `SocketAddr` as
120    /// that include the port, to be able to facilitate this network addresses must have a ban ID
121    /// which for hidden services could just be the address it self but for clear net addresses will
122    /// be the IP address.
123    ///
124    /// - TODO: IP zone banning?
125    /// - TODO: rename this to Host.
126    type BanID: Debug + Hash + Eq + Clone + Copy + Send + 'static;
127
128    /// Changes the port of this address to `port`.
129    fn set_port(&mut self, port: u16);
130
131    /// Turns this address into its canonical form.
132    fn make_canonical(&mut self);
133
134    /// Returns the [`Self::BanID`] for this address.
135    fn ban_id(&self) -> Self::BanID;
136
137    fn should_add_to_peer_list(&self) -> bool;
138}
139
140/// An abstraction over a network zone (tor/i2p/clear)
141pub trait NetworkZone: Clone + Copy + Send + 'static {
142    /// The network name.
143    const NAME: &'static str;
144    /// Check if our node ID matches the incoming peers node ID for this network.
145    ///
146    /// This has privacy implications on an anonymity network if true so should be set
147    /// to false.
148    const CHECK_NODE_ID: bool;
149    /// If `true`, this network zone requires us to blend our own address and port into
150    /// the address book we plan on sharing to other peers.
151    const BROADCAST_OWN_ADDR: bool;
152
153    /// The address type of this network.
154    type Addr: NetZoneAddress;
155}
156
157/// An abstraction over a transport method (TCP/Tor/SOCKS5/...)
158///
159/// This trait implements the required methods and types for establishing connection to a
160/// peer or instantiating a listener for the `NetworkZone` `Z` over a `Transport` method `T`.
161///
162/// Ultimately, multiple transports can implement the same trait for providing alternative
163/// ways for a network zone to operate (example: ClearNet can operate on both TCP and Tor.)
164#[async_trait::async_trait]
165pub trait Transport<Z: NetworkZone>: Clone + Send + 'static {
166    /// Client configuration necessary when establishing a connection to a peer.
167    ///
168    /// Note: Currently, this client config is considered immutable during operational runtime. If one
169    /// wish to apply modifications on the fly, they will need to make use of an inner shared and mutable
170    /// reference to do so.
171    type ClientConfig: Default + Clone + Debug + Send + Sync + 'static;
172    /// Server configuration necessary when instantiating a listener for inbound connections.
173    type ServerConfig: Default + Clone + Debug + Send + Sync + 'static;
174
175    /// The stream (incoming data) type of this transport method.
176    type Stream: Stream<Item = Result<Message, BucketError>> + Unpin + Send + 'static;
177    /// The sink (outgoing data) type of this transport method.
178    type Sink: Sink<LevinMessage<Message>, Error = BucketError> + Unpin + Send + 'static;
179    /// The inbound connection listener for this transport method.
180    type Listener: Stream<Item = Result<(Option<Z::Addr>, Self::Stream, Self::Sink), std::io::Error>>
181        + Send
182        + 'static;
183
184    /// Connects to a peer with the given address.
185    ///
186    /// Take in argument the destination [`NetworkZone::Addr`] and [`Self::ClientConfig`] which should contain mandatory parameters
187    /// for a connection to be established.
188    ///
189    /// <div class="warning">
190    ///
191    /// This does not complete a handshake with the peer, to do that see the [crate](crate) docs.
192    ///
193    /// </div>
194    ///
195    /// Returns the [`Self::Stream`] and [`Self::Sink`] to send messages to the peer.
196    async fn connect_to_peer(
197        addr: Z::Addr,
198        config: &Self::ClientConfig,
199    ) -> Result<(Self::Stream, Self::Sink), std::io::Error>;
200
201    /// Instantiate a listener for inbound peer connections
202    ///
203    /// Take in argument [`Self::ServerConfig`] which should contain mandatory parameters
204    /// for the listener.
205    ///
206    /// Returns the [`Self::Listener`] to listen to new connections.
207    async fn incoming_connection_listener(
208        config: Self::ServerConfig,
209    ) -> Result<Self::Listener, std::io::Error>;
210}
211
212// ####################################################################################
213// Below here is just helper traits, so we don't have to type out tower::Service bounds
214// everywhere but still get to use tower.
215
216pub trait AddressBook<Z: NetworkZone>:
217    tower::Service<
218        AddressBookRequest<Z>,
219        Response = AddressBookResponse<Z>,
220        Error = tower::BoxError,
221        Future: Send + 'static,
222    > + Send
223    + 'static
224{
225}
226
227impl<T, Z: NetworkZone> AddressBook<Z> for T where
228    T: tower::Service<
229            AddressBookRequest<Z>,
230            Response = AddressBookResponse<Z>,
231            Error = tower::BoxError,
232            Future: Send + 'static,
233        > + Send
234        + 'static
235{
236}
237
238pub trait CoreSyncSvc:
239    tower::Service<
240        CoreSyncDataRequest,
241        Response = CoreSyncDataResponse,
242        Error = tower::BoxError,
243        Future: Send + 'static,
244    > + Send
245    + 'static
246{
247}
248
249impl<T> CoreSyncSvc for T where
250    T: tower::Service<
251            CoreSyncDataRequest,
252            Response = CoreSyncDataResponse,
253            Error = tower::BoxError,
254            Future: Send + 'static,
255        > + Send
256        + 'static
257{
258}
259
260pub trait ProtocolRequestHandler:
261    tower::Service<
262        ProtocolRequest,
263        Response = ProtocolResponse,
264        Error = tower::BoxError,
265        Future: Send + 'static,
266    > + Send
267    + 'static
268{
269}
270
271impl<T> ProtocolRequestHandler for T where
272    T: tower::Service<
273            ProtocolRequest,
274            Response = ProtocolResponse,
275            Error = tower::BoxError,
276            Future: Send + 'static,
277        > + Send
278        + 'static
279{
280}
281
282pub trait ProtocolRequestHandlerMaker<Z: NetworkZone>:
283    tower::MakeService<
284        client::PeerInformation<Z::Addr>,
285        ProtocolRequest,
286        MakeError = tower::BoxError,
287        Service: ProtocolRequestHandler,
288        Future: Send + 'static,
289    > + Send
290    + 'static
291{
292}
293
294impl<T, Z: NetworkZone> ProtocolRequestHandlerMaker<Z> for T where
295    T: tower::MakeService<
296            client::PeerInformation<Z::Addr>,
297            ProtocolRequest,
298            MakeError = tower::BoxError,
299            Service: ProtocolRequestHandler,
300            Future: Send + 'static,
301        > + Send
302        + 'static
303{
304}