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//! # };
24//! # use cuprate_wire::{common::PeerSupportFlags, BasicNodeData};
25//! # use cuprate_test_utils::monerod::monerod;
26//! #
27//! # tokio_test::block_on(async move {
28//! #
29//! # let _monerod = monerod::<&str>([]).await;
30//! # let addr = _monerod.p2p_addr();
31//! #
32//! // The information about our local node.
33//! let our_basic_node_data = BasicNodeData {
34//! my_port: 0,
35//! network_id: Network::Mainnet.network_id(),
36//! peer_id: 0,
37//! support_flags: PeerSupportFlags::FLUFFY_BLOCKS,
38//! rpc_port: 0,
39//! rpc_credits_per_hash: 0,
40//! };
41//!
42//! // See [`HandshakerBuilder`] for information about the default values set, they may not be
43//! // appropriate for every use case.
44//! let handshaker = HandshakerBuilder::<ClearNet>::new(our_basic_node_data).build();
45//!
46//! // The outbound connector.
47//! let mut connector = Connector::new(handshaker);
48//!
49//! // The connection.
50//! let connection = connector
51//! .oneshot(ConnectRequest {
52//! addr,
53//! permit: None,
54//! })
55//! .await
56//! .unwrap();
57//! # });
58//! ```
59
60cfg_if::cfg_if! {
61 // Used in `tests/`
62 if #[cfg(test)] {
63 use cuprate_test_utils as _;
64 use tokio_test as _;
65 use hex as _;
66 }
67}
68
69use std::{fmt::Debug, hash::Hash};
70
71use futures::{Sink, Stream};
72
73use cuprate_wire::{
74 levin::LevinMessage, network_address::NetworkAddressIncorrectZone, BucketError, Message,
75 NetworkAddress,
76};
77
78pub mod client;
79mod constants;
80pub mod error;
81pub mod handles;
82mod network_zones;
83pub mod protocol;
84pub mod services;
85pub mod types;
86
87pub use error::*;
88pub use network_zones::{ClearNet, ClearNetServerCfg};
89pub use protocol::*;
90use services::*;
91//re-export
92pub use cuprate_helper::network::Network;
93pub use cuprate_wire::CoreSyncData;
94
95/// The direction of a connection.
96#[derive(Debug, Copy, Clone, Eq, PartialEq)]
97pub enum ConnectionDirection {
98 /// An inbound connection to our node.
99 Inbound,
100 /// An outbound connection from our node.
101 Outbound,
102}
103
104/// An address on a specific [`NetworkZone`].
105pub trait NetZoneAddress:
106 TryFrom<NetworkAddress, Error = NetworkAddressIncorrectZone>
107 + Into<NetworkAddress>
108 + std::fmt::Display
109 + Hash
110 + Eq
111 + Copy
112 + Send
113 + Sync
114 + Unpin
115 + 'static
116{
117 /// Cuprate needs to be able to ban peers by IP addresses and not just by `SocketAddr` as
118 /// that include the port, to be able to facilitate this network addresses must have a ban ID
119 /// which for hidden services could just be the address it self but for clear net addresses will
120 /// be the IP address.
121 ///
122 /// - TODO: IP zone banning?
123 /// - TODO: rename this to Host.
124 type BanID: Debug + Hash + Eq + Clone + Copy + Send + 'static;
125
126 /// Changes the port of this address to `port`.
127 fn set_port(&mut self, port: u16);
128
129 /// Turns this address into its canonical form.
130 fn make_canonical(&mut self);
131
132 /// Returns the [`Self::BanID`] for this address.
133 fn ban_id(&self) -> Self::BanID;
134
135 fn should_add_to_peer_list(&self) -> bool;
136}
137
138/// An abstraction over a network zone (tor/i2p/clear)
139#[async_trait::async_trait]
140pub trait NetworkZone: Clone + Copy + Send + 'static {
141 /// The network name.
142 const NAME: &'static str;
143 /// Check if our node ID matches the incoming peers node ID for this network.
144 ///
145 /// This has privacy implications on an anonymity network if true so should be set
146 /// to false.
147 const CHECK_NODE_ID: bool;
148
149 /// The address type of this network.
150 type Addr: NetZoneAddress;
151
152 /// The stream (incoming data) type for this network.
153 type Stream: Stream<Item = Result<Message, BucketError>> + Unpin + Send + 'static;
154 /// The sink (outgoing data) type for this network.
155 type Sink: Sink<LevinMessage<Message>, Error = BucketError> + Unpin + Send + 'static;
156 /// The inbound connection listener for this network.
157 type Listener: Stream<Item = Result<(Option<Self::Addr>, Self::Stream, Self::Sink), std::io::Error>>
158 + Send
159 + 'static;
160 /// Config used to start a server which listens for incoming connections.
161 type ServerCfg: Clone + Debug + Send + 'static;
162
163 /// Connects to a peer with the given address.
164 ///
165 /// <div class="warning">
166 ///
167 /// This does not complete a handshake with the peer, to do that see the [crate](crate) docs.
168 ///
169 /// </div>
170 ///
171 /// Returns the [`Self::Stream`] and [`Self::Sink`] to send messages to the peer.
172 async fn connect_to_peer(
173 addr: Self::Addr,
174 ) -> Result<(Self::Stream, Self::Sink), std::io::Error>;
175
176 async fn incoming_connection_listener(
177 config: Self::ServerCfg,
178 port: u16,
179 ) -> Result<Self::Listener, std::io::Error>;
180}
181
182// ####################################################################################
183// Below here is just helper traits, so we don't have to type out tower::Service bounds
184// everywhere but still get to use tower.
185
186pub trait AddressBook<Z: NetworkZone>:
187 tower::Service<
188 AddressBookRequest<Z>,
189 Response = AddressBookResponse<Z>,
190 Error = tower::BoxError,
191 Future: Send + 'static,
192 > + Send
193 + 'static
194{
195}
196
197impl<T, Z: NetworkZone> AddressBook<Z> for T where
198 T: tower::Service<
199 AddressBookRequest<Z>,
200 Response = AddressBookResponse<Z>,
201 Error = tower::BoxError,
202 Future: Send + 'static,
203 > + Send
204 + 'static
205{
206}
207
208pub trait CoreSyncSvc:
209 tower::Service<
210 CoreSyncDataRequest,
211 Response = CoreSyncDataResponse,
212 Error = tower::BoxError,
213 Future: Send + 'static,
214 > + Send
215 + 'static
216{
217}
218
219impl<T> CoreSyncSvc for T where
220 T: tower::Service<
221 CoreSyncDataRequest,
222 Response = CoreSyncDataResponse,
223 Error = tower::BoxError,
224 Future: Send + 'static,
225 > + Send
226 + 'static
227{
228}
229
230pub trait ProtocolRequestHandler:
231 tower::Service<
232 ProtocolRequest,
233 Response = ProtocolResponse,
234 Error = tower::BoxError,
235 Future: Send + 'static,
236 > + Send
237 + 'static
238{
239}
240
241impl<T> ProtocolRequestHandler for T where
242 T: tower::Service<
243 ProtocolRequest,
244 Response = ProtocolResponse,
245 Error = tower::BoxError,
246 Future: Send + 'static,
247 > + Send
248 + 'static
249{
250}
251
252pub trait ProtocolRequestHandlerMaker<Z: NetworkZone>:
253 tower::MakeService<
254 client::PeerInformation<Z::Addr>,
255 ProtocolRequest,
256 MakeError = tower::BoxError,
257 Service: ProtocolRequestHandler,
258 Future: Send + 'static,
259 > + Send
260 + 'static
261{
262}
263
264impl<T, Z: NetworkZone> ProtocolRequestHandlerMaker<Z> for T where
265 T: tower::MakeService<
266 client::PeerInformation<Z::Addr>,
267 ProtocolRequest,
268 MakeError = tower::BoxError,
269 Service: ProtocolRequestHandler,
270 Future: Send + 'static,
271 > + Send
272 + 'static
273{
274}