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}