p2p changes

This commit is contained in:
Boog900 2024-04-26 21:13:53 +01:00
parent bf6c21c71e
commit 1c72c0952f
No known key found for this signature in database
GPG key ID: 42AB1287CB0041C2
31 changed files with 947 additions and 301 deletions

1
Cargo.lock generated
View file

@ -1728,6 +1728,7 @@ dependencies = [
name = "monero-wire" name = "monero-wire"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"bitflags 2.5.0",
"bytes", "bytes",
"epee-encoding", "epee-encoding",
"fixed-bytes", "fixed-bytes",

View file

@ -39,11 +39,7 @@ impl<T: Containerable + EpeeValue> EpeeValue for ContainerAsBlob<T> {
} }
Ok(ContainerAsBlob( Ok(ContainerAsBlob(
bytes bytes.chunks(T::SIZE).map(T::from_bytes).collect(),
.windows(T::SIZE)
.step_by(T::SIZE)
.map(T::from_bytes)
.collect(),
)) ))
} }

View file

@ -1,5 +1,8 @@
use core::fmt::{Debug, Formatter}; use core::{
use core::{num::TryFromIntError, str::Utf8Error}; fmt::{Debug, Formatter},
num::TryFromIntError,
str::Utf8Error,
};
pub type Result<T> = core::result::Result<T, Error>; pub type Result<T> = core::result::Result<T, Error>;

View file

@ -308,11 +308,7 @@ impl<const N: usize> EpeeValue for ByteArrayVec<N> {
return Err(Error::Format("Byte array exceeded max length")); return Err(Error::Format("Byte array exceeded max length"));
} }
if r.remaining() if r.remaining() < usize::try_from(len)? {
< usize::try_from(len)?
.checked_mul(N)
.ok_or(Error::Value("Length of field is too long".to_string()))?
{
return Err(Error::IO("Not enough bytes to fill object")); return Err(Error::IO("Not enough bytes to fill object"));
} }

View file

@ -1,8 +1,9 @@
use core::ops::Deref; use core::{
use std::fmt::{Debug, Formatter}; fmt::{Debug, Formatter},
use std::ops::Index; ops::{Deref, Index},
};
use bytes::Bytes; use bytes::{BufMut, Bytes, BytesMut};
#[cfg_attr(feature = "std", derive(thiserror::Error))] #[cfg_attr(feature = "std", derive(thiserror::Error))]
pub enum FixedByteError { pub enum FixedByteError {
@ -101,6 +102,40 @@ impl<const N: usize> ByteArrayVec<N> {
pub fn take_bytes(self) -> Bytes { pub fn take_bytes(self) -> Bytes {
self.0 self.0
} }
/// Splits the byte array vec into two at the given index.
///
/// Afterwards self contains elements [0, at), and the returned Bytes contains elements [at, len).
///
/// This is an O(1) operation that just increases the reference count and sets a few indices.
///
/// # Panics
/// Panics if at > len.
pub fn split_off(&mut self, at: usize) -> Self {
Self(self.0.split_off(at * N))
}
}
impl<const N: usize> From<&ByteArrayVec<N>> for Vec<[u8; N]> {
fn from(value: &ByteArrayVec<N>) -> Self {
let mut out = Vec::with_capacity(value.len());
for i in 0..value.len() {
out.push(value[i])
}
out
}
}
impl<const N: usize> From<Vec<[u8; N]>> for ByteArrayVec<N> {
fn from(value: Vec<[u8; N]>) -> Self {
let mut bytes = BytesMut::with_capacity(N * value.len());
for i in value.into_iter() {
bytes.extend_from_slice(&i)
}
ByteArrayVec(bytes.freeze())
}
} }
impl<const N: usize> TryFrom<Bytes> for ByteArrayVec<N> { impl<const N: usize> TryFrom<Bytes> for ByteArrayVec<N> {
@ -115,8 +150,38 @@ impl<const N: usize> TryFrom<Bytes> for ByteArrayVec<N> {
} }
} }
impl<const N: usize> From<[u8; N]> for ByteArrayVec<N> {
fn from(value: [u8; N]) -> Self {
ByteArrayVec(Bytes::copy_from_slice(value.as_slice()))
}
}
impl<const N: usize, const LEN: usize> From<[[u8; N]; LEN]> for ByteArrayVec<N> {
fn from(value: [[u8; N]; LEN]) -> Self {
let mut bytes = BytesMut::with_capacity(N * LEN);
for val in value.into_iter() {
bytes.put_slice(val.as_slice());
}
ByteArrayVec(bytes.freeze())
}
}
impl<const N: usize> TryFrom<Vec<u8>> for ByteArrayVec<N> {
type Error = FixedByteError;
fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
if value.len() % N != 0 {
return Err(FixedByteError::InvalidLength);
}
Ok(ByteArrayVec(Bytes::from(value)))
}
}
impl<const N: usize> Index<usize> for ByteArrayVec<N> { impl<const N: usize> Index<usize> for ByteArrayVec<N> {
type Output = [u8; 32]; type Output = [u8; N];
fn index(&self, index: usize) -> &Self::Output { fn index(&self, index: usize) -> &Self::Output {
if (index + 1) * N > self.0.len() { if (index + 1) * N > self.0.len() {

View file

@ -15,7 +15,8 @@ levin-cuprate = {path="../levin"}
epee-encoding = { path = "../epee-encoding" } epee-encoding = { path = "../epee-encoding" }
fixed-bytes = { path = "../fixed-bytes" } fixed-bytes = { path = "../fixed-bytes" }
bytes = { workspace = true } bitflags = { workspace = true, features = ["std"] }
bytes = { workspace = true, features = ["std"] }
thiserror = { workspace = true } thiserror = { workspace = true }
[dev-dependencies] [dev-dependencies]

View file

@ -139,8 +139,7 @@ mod tests {
my_port: 0, my_port: 0,
network_id: [ network_id: [
18, 48, 241, 113, 97, 4, 65, 97, 23, 49, 0, 130, 22, 161, 161, 16, 18, 48, 241, 113, 97, 4, 65, 97, 23, 49, 0, 130, 22, 161, 161, 16,
] ],
.into(),
peer_id: 9671405426614699871, peer_id: 9671405426614699871,
support_flags: PeerSupportFlags::from(1_u32), support_flags: PeerSupportFlags::from(1_u32),
rpc_port: 0, rpc_port: 0,
@ -945,8 +944,7 @@ mod tests {
my_port: 18080, my_port: 18080,
network_id: [ network_id: [
18, 48, 241, 113, 97, 4, 65, 97, 23, 49, 0, 130, 22, 161, 161, 16, 18, 48, 241, 113, 97, 4, 65, 97, 23, 49, 0, 130, 22, 161, 161, 16,
] ],
.into(),
peer_id: 6037804360359455404, peer_id: 6037804360359455404,
support_flags: PeerSupportFlags::from(1_u32), support_flags: PeerSupportFlags::from(1_u32),
rpc_port: 18089, rpc_port: 18089,

View file

@ -15,7 +15,9 @@
//! Common types that are used across multiple messages. //! Common types that are used across multiple messages.
use bitflags::bitflags;
use bytes::{Buf, BufMut, Bytes}; use bytes::{Buf, BufMut, Bytes};
use epee_encoding::{epee_object, EpeeValue, InnerMarker}; use epee_encoding::{epee_object, EpeeValue, InnerMarker};
use fixed_bytes::ByteArray; use fixed_bytes::ByteArray;
@ -24,6 +26,14 @@ use crate::NetworkAddress;
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PeerSupportFlags(u32); pub struct PeerSupportFlags(u32);
bitflags! {
impl PeerSupportFlags: u32 {
const FLUFFY_BLOCKS = 0b0000_0001;
const _ = !0;
}
}
impl From<u32> for PeerSupportFlags { impl From<u32> for PeerSupportFlags {
fn from(value: u32) -> Self { fn from(value: u32) -> Self {
PeerSupportFlags(value) PeerSupportFlags(value)
@ -42,27 +52,14 @@ impl<'a> From<&'a PeerSupportFlags> for &'a u32 {
} }
} }
impl PeerSupportFlags {
//const FLUFFY_BLOCKS: u32 = 0b0000_0001;
pub fn is_empty(&self) -> bool {
self.0 == 0
}
}
impl From<u8> for PeerSupportFlags {
fn from(value: u8) -> Self {
PeerSupportFlags(value.into())
}
}
/// Basic Node Data, information on the connected peer /// Basic Node Data, information on the connected peer
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub struct BasicNodeData { pub struct BasicNodeData {
/// Port /// Port
pub my_port: u32, pub my_port: u32,
/// The Network Id /// The Network Id
pub network_id: ByteArray<16>, // We don't use ByteArray here to allow users to keep this data long term.
pub network_id: [u8; 16],
/// Peer ID /// Peer ID
pub peer_id: u64, pub peer_id: u64,
/// The Peers Support Flags /// The Peers Support Flags
@ -79,7 +76,7 @@ pub struct BasicNodeData {
epee_object! { epee_object! {
BasicNodeData, BasicNodeData,
my_port: u32, my_port: u32,
network_id: ByteArray<16>, network_id: [u8; 16],
peer_id: u64, peer_id: u64,
support_flags: PeerSupportFlags as u32 = 0_u32, support_flags: PeerSupportFlags as u32 = 0_u32,
rpc_port: u16 = 0_u16, rpc_port: u16 = 0_u16,
@ -101,7 +98,8 @@ pub struct CoreSyncData {
/// (If this is not in the message the default is 0) /// (If this is not in the message the default is 0)
pub pruning_seed: u32, pub pruning_seed: u32,
/// Hash of the top block /// Hash of the top block
pub top_id: ByteArray<32>, // We don't use ByteArray here to allow users to keep this data long term.
pub top_id: [u8; 32],
/// Version of the top block /// Version of the top block
pub top_version: u8, pub top_version: u8,
} }
@ -112,7 +110,7 @@ epee_object! {
cumulative_difficulty_top64: u64 = 0_u64, cumulative_difficulty_top64: u64 = 0_u64,
current_height: u64, current_height: u64,
pruning_seed: u32 = 0_u32, pruning_seed: u32 = 0_u32,
top_id: ByteArray<32>, top_id: [u8; 32],
top_version: u8 = 0_u8, top_version: u8 = 0_u8,
} }
@ -131,7 +129,7 @@ impl CoreSyncData {
cumulative_difficulty_top64, cumulative_difficulty_top64,
current_height, current_height,
pruning_seed, pruning_seed,
top_id: top_id.into(), top_id,
top_version, top_version,
} }
} }

View file

@ -114,7 +114,7 @@ pub struct ChainResponse {
/// Total Height /// Total Height
pub total_height: u64, pub total_height: u64,
/// Cumulative Difficulty Low /// Cumulative Difficulty Low
pub cumulative_difficulty: u64, pub cumulative_difficulty_low64: u64,
/// Cumulative Difficulty High /// Cumulative Difficulty High
pub cumulative_difficulty_top64: u64, pub cumulative_difficulty_top64: u64,
/// Block IDs /// Block IDs
@ -125,11 +125,19 @@ pub struct ChainResponse {
pub first_block: Bytes, pub first_block: Bytes,
} }
impl ChainResponse {
#[inline]
pub fn cumulative_difficulty(&self) -> u128 {
let cumulative_difficulty = self.cumulative_difficulty_top64 as u128;
cumulative_difficulty << 64 | self.cumulative_difficulty_low64 as u128
}
}
epee_object!( epee_object!(
ChainResponse, ChainResponse,
start_height: u64, start_height: u64,
total_height: u64, total_height: u64,
cumulative_difficulty: u64, cumulative_difficulty_low64("cumulative_difficulty"): u64,
cumulative_difficulty_top64: u64 = 0_u64, cumulative_difficulty_top64: u64 = 0_u64,
m_block_ids: ByteArrayVec<32>, m_block_ids: ByteArrayVec<32>,
m_block_weights: Vec<u64> as ContainerAsBlob<u64>, m_block_weights: Vec<u64> as ContainerAsBlob<u64>,

View file

@ -84,7 +84,7 @@ impl<Z: NetworkZone> AddressBook<Z> {
let connected_peers = HashMap::new(); let connected_peers = HashMap::new();
let mut peer_save_interval = interval(cfg.peer_save_period); let mut peer_save_interval = interval(cfg.peer_save_period);
peer_save_interval.set_missed_tick_behavior(MissedTickBehavior::Delay); peer_save_interval.set_missed_tick_behavior(MissedTickBehavior::Skip);
Self { Self {
white_list, white_list,
@ -236,7 +236,9 @@ impl<Z: NetworkZone> AddressBook<Z> {
) { ) {
tracing::debug!("Received new peer list, length: {}", peer_list.len()); tracing::debug!("Received new peer list, length: {}", peer_list.len());
peer_list.retain(|peer| { peer_list.retain_mut(|peer| {
peer.adr.make_canonical();
if !peer.adr.should_add_to_peer_list() { if !peer.adr.should_add_to_peer_list() {
false false
} else { } else {
@ -259,7 +261,7 @@ impl<Z: NetworkZone> AddressBook<Z> {
) -> Option<ZoneSpecificPeerListEntryBase<Z::Addr>> { ) -> Option<ZoneSpecificPeerListEntryBase<Z::Addr>> {
tracing::debug!("Retrieving random white peer"); tracing::debug!("Retrieving random white peer");
self.white_list self.white_list
.take_random_peer(&mut rand::thread_rng(), block_needed) .take_random_peer(&mut rand::thread_rng(), block_needed, &self.anchor_list)
} }
fn take_random_gray_peer( fn take_random_gray_peer(
@ -268,7 +270,7 @@ impl<Z: NetworkZone> AddressBook<Z> {
) -> Option<ZoneSpecificPeerListEntryBase<Z::Addr>> { ) -> Option<ZoneSpecificPeerListEntryBase<Z::Addr>> {
tracing::debug!("Retrieving random gray peer"); tracing::debug!("Retrieving random gray peer");
self.gray_list self.gray_list
.take_random_peer(&mut rand::thread_rng(), block_needed) .take_random_peer(&mut rand::thread_rng(), block_needed, &HashSet::new())
} }
fn get_white_peers(&self, len: usize) -> Vec<ZoneSpecificPeerListEntryBase<Z::Addr>> { fn get_white_peers(&self, len: usize) -> Vec<ZoneSpecificPeerListEntryBase<Z::Addr>> {

View file

@ -1,8 +1,7 @@
use std::{path::PathBuf, sync::Arc, time::Duration}; use std::{path::PathBuf, sync::Arc, time::Duration};
use futures::StreamExt; use futures::StreamExt;
use tokio::sync::Semaphore; use tokio::{sync::Semaphore, time::interval};
use tokio::time::interval;
use monero_p2p::handles::HandleBuilder; use monero_p2p::handles::HandleBuilder;
use monero_pruning::PruningSeed; use monero_pruning::PruningSeed;

View file

@ -82,5 +82,5 @@ pub async fn init_address_book<Z: NetworkZone>(
let address_book = book::AddressBook::<Z>::new(cfg, white_list, gray_list, Vec::new()); let address_book = book::AddressBook::<Z>::new(cfg, white_list, gray_list, Vec::new());
Ok(Buffer::new(address_book, 15)) Ok(Buffer::new(address_book, 150))
} }

View file

@ -89,7 +89,9 @@ impl<Z: NetworkZone> PeerList<Z> {
&mut self, &mut self,
r: &mut R, r: &mut R,
block_needed: Option<u64>, block_needed: Option<u64>,
must_keep_peers: &HashSet<Z::Addr>,
) -> Option<ZoneSpecificPeerListEntryBase<Z::Addr>> { ) -> Option<ZoneSpecificPeerListEntryBase<Z::Addr>> {
for _ in 0..3 {
if let Some(needed_height) = block_needed { if let Some(needed_height) = block_needed {
let (_, addresses_with_block) = self.pruning_seeds.iter().find(|(seed, _)| { let (_, addresses_with_block) = self.pruning_seeds.iter().find(|(seed, _)| {
// TODO: factor in peer blockchain height? // TODO: factor in peer blockchain height?
@ -99,20 +101,31 @@ impl<Z: NetworkZone> PeerList<Z> {
})?; })?;
let n = r.gen_range(0..addresses_with_block.len()); let n = r.gen_range(0..addresses_with_block.len());
let peer = addresses_with_block[n]; let peer = addresses_with_block[n];
self.remove_peer(&peer) if must_keep_peers.contains(&peer) {
continue;
}
return self.remove_peer(&peer);
} else { } else {
let len = self.len(); let len = self.len();
if len == 0 { if len == 0 {
None return None;
} else { } else {
let n = r.gen_range(0..len); let n = r.gen_range(0..len);
let (&key, _) = self.peers.get_index(n).unwrap(); let (&key, _) = self.peers.get_index(n).unwrap();
self.remove_peer(&key) if must_keep_peers.contains(&key) {
continue;
}
return self.remove_peer(&key);
} }
} }
} }
None
}
pub fn get_random_peers<R: Rng>( pub fn get_random_peers<R: Rng>(
&self, &self,
r: &mut R, r: &mut R,

View file

@ -17,14 +17,14 @@ monero-pruning = { path = "../../pruning" }
tokio = { workspace = true, features = ["net", "sync", "macros", "time"]} tokio = { workspace = true, features = ["net", "sync", "macros", "time"]}
tokio-util = { workspace = true, features = ["codec"] } tokio-util = { workspace = true, features = ["codec"] }
tokio-stream = { workspace = true, features = ["sync"]} tokio-stream = { workspace = true, features = ["sync"]}
futures = { workspace = true, features = ["std", "async-await"] } futures = { workspace = true, features = ["std"] }
async-trait = { workspace = true } async-trait = { workspace = true }
tower = { workspace = true, features = ["util"] } tower = { workspace = true, features = ["util", "tracing"] }
thiserror = { workspace = true } thiserror = { workspace = true }
tracing = { workspace = true, features = ["std", "attributes"] } tracing = { workspace = true, features = ["std", "attributes"] }
borsh = { workspace = true, default-features = false, features = ["derive", "std"], optional = true } borsh = { workspace = true, features = ["derive", "std"], optional = true }
[dev-dependencies] [dev-dependencies]
cuprate-test-utils = {path = "../../test-utils"} cuprate-test-utils = {path = "../../test-utils"}

View file

@ -1,32 +1,40 @@
use std::fmt::Formatter;
use std::{ use std::{
fmt::{Debug, Display}, fmt::{Debug, Display, Formatter},
task::{Context, Poll}, sync::Arc,
task::{ready, Context, Poll},
}; };
use futures::channel::oneshot; use futures::{channel::oneshot, FutureExt};
use tokio::{sync::mpsc, task::JoinHandle}; use tokio::{
use tokio_util::sync::PollSender; sync::{mpsc, OwnedSemaphorePermit, Semaphore},
task::JoinHandle,
};
use tokio_util::sync::PollSemaphore;
use tower::Service; use tower::Service;
use cuprate_helper::asynch::InfallibleOneshotReceiver; use cuprate_helper::asynch::InfallibleOneshotReceiver;
use crate::{ use crate::{
handles::ConnectionHandle, NetworkZone, PeerError, PeerRequest, PeerResponse, SharedError, handles::ConnectionHandle, ConnectionDirection, NetworkZone, PeerError, PeerRequest,
PeerResponse, SharedError,
}; };
mod connection; mod connection;
mod connector; mod connector;
pub mod handshaker; pub mod handshaker;
mod timeout_monitor;
pub use connector::{ConnectRequest, Connector}; pub use connector::{ConnectRequest, Connector};
pub use handshaker::{DoHandshakeRequest, HandShaker, HandshakeError}; pub use handshaker::{DoHandshakeRequest, HandShaker, HandshakeError};
use monero_pruning::PruningSeed;
/// An internal identifier for a given peer, will be their address if known /// An internal identifier for a given peer, will be their address if known
/// or a random u64 if not. /// or a random u64 if not.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum InternalPeerID<A> { pub enum InternalPeerID<A> {
/// A known address
KnownAddr(A), KnownAddr(A),
/// An unknown address (probably an inbound anonymity network connection).
Unknown(u64), Unknown(u64),
} }
@ -34,38 +42,70 @@ impl<A: Display> Display for InternalPeerID<A> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self { match self {
InternalPeerID::KnownAddr(addr) => addr.fmt(f), InternalPeerID::KnownAddr(addr) => addr.fmt(f),
InternalPeerID::Unknown(id) => f.write_str(&format!("Unknown addr, ID: {}", id)), InternalPeerID::Unknown(id) => f.write_str(&format!("Unknown, ID: {}", id)),
} }
} }
} }
/// Information on a connected peer.
#[derive(Debug, Clone)]
pub struct PeerInformation<A> {
/// The internal peer ID of this peer.
pub id: InternalPeerID<A>,
/// The [`ConnectionHandle`] for this peer, allows banning this peer and checking if it is still
/// alive.
pub handle: ConnectionHandle,
/// The direction of this connection (inbound|outbound).
pub direction: ConnectionDirection,
/// The peers pruning seed.
pub pruning_seed: PruningSeed,
}
/// This represents a connection to a peer.
///
/// It allows sending requests to the peer, but does only does minimal checks that the data returned
/// is the data asked for, i.e. for a certain request the only thing checked will be that the response
/// is the correct response for that request, not that the response contains the correct data.
pub struct Client<Z: NetworkZone> { pub struct Client<Z: NetworkZone> {
id: InternalPeerID<Z::Addr>, /// Information on the connected peer.
handle: ConnectionHandle, pub info: PeerInformation<Z::Addr>,
connection_tx: PollSender<connection::ConnectionTaskRequest>, /// The channel to the [`Connection`](connection::Connection) task.
connection_tx: mpsc::Sender<connection::ConnectionTaskRequest>,
/// The [`JoinHandle`] of the spawned connection task.
connection_handle: JoinHandle<()>, connection_handle: JoinHandle<()>,
/// The [`JoinHandle`] of the spawned timeout monitor task.
timeout_handle: JoinHandle<Result<(), tower::BoxError>>,
semaphore: PollSemaphore,
permit: Option<OwnedSemaphorePermit>,
/// The error slot shared between the [`Client`] and [`Connection`](connection::Connection).
error: SharedError<PeerError>, error: SharedError<PeerError>,
} }
impl<Z: NetworkZone> Client<Z> { impl<Z: NetworkZone> Client<Z> {
pub fn new( /// Creates a new [`Client`].
id: InternalPeerID<Z::Addr>, pub(crate) fn new(
handle: ConnectionHandle, info: PeerInformation<Z::Addr>,
connection_tx: mpsc::Sender<connection::ConnectionTaskRequest>, connection_tx: mpsc::Sender<connection::ConnectionTaskRequest>,
connection_handle: JoinHandle<()>, connection_handle: JoinHandle<()>,
timeout_handle: JoinHandle<Result<(), tower::BoxError>>,
semaphore: Arc<Semaphore>,
error: SharedError<PeerError>, error: SharedError<PeerError>,
) -> Self { ) -> Self {
Self { Self {
id, info,
handle, connection_tx,
connection_tx: PollSender::new(connection_tx), timeout_handle,
semaphore: PollSemaphore::new(semaphore),
permit: None,
connection_handle, connection_handle,
error, error,
} }
} }
/// Internal function to set an error on the [`SharedError`].
fn set_err(&self, err: PeerError) -> tower::BoxError { fn set_err(&self, err: PeerError) -> tower::BoxError {
let err_str = err.to_string(); let err_str = err.to_string();
match self.error.try_insert_err(err) { match self.error.try_insert_err(err) {
@ -86,25 +126,38 @@ impl<Z: NetworkZone> Service<PeerRequest> for Client<Z> {
return Poll::Ready(Err(err.to_string().into())); return Poll::Ready(Err(err.to_string().into()));
} }
if self.connection_handle.is_finished() { if self.connection_handle.is_finished() || self.timeout_handle.is_finished() {
let err = self.set_err(PeerError::ClientChannelClosed); let err = self.set_err(PeerError::ClientChannelClosed);
return Poll::Ready(Err(err)); return Poll::Ready(Err(err));
} }
self.connection_tx if self.permit.is_some() {
.poll_reserve(cx) return Poll::Ready(Ok(()));
.map_err(|_| PeerError::ClientChannelClosed.into()) }
let Some(permit) = ready!(self.semaphore.poll_acquire(cx)) else {
unreachable!("Client semaphore should not be closed!");
};
self.permit = Some(permit);
Poll::Ready(Ok(()))
} }
fn call(&mut self, request: PeerRequest) -> Self::Future { fn call(&mut self, request: PeerRequest) -> Self::Future {
let Some(permit) = self.permit.take() else {
panic!("poll_ready did not return ready before call to call")
};
let (tx, rx) = oneshot::channel(); let (tx, rx) = oneshot::channel();
let req = connection::ConnectionTaskRequest { let req = connection::ConnectionTaskRequest {
response_channel: tx, response_channel: tx,
request, request,
permit: Some(permit),
}; };
self.connection_tx self.connection_tx
.send_item(req) .try_send(req)
.map_err(|_| ()) .map_err(|_| ())
.expect("poll_ready should have been called"); .expect("poll_ready should have been called");

View file

@ -1,37 +1,60 @@
use std::sync::Arc; //! The Connection Task
//!
//! This module handles routing requests from a [`Client`](crate::client::Client) or a broadcast channel to
//! a peer. This module also handles routing requests from the connected peer to a request handler.
//!
use std::{pin::Pin, sync::Arc, time::Duration};
use futures::{ use futures::{
channel::oneshot, channel::oneshot,
lock::{Mutex, OwnedMutexGuard},
stream::{Fuse, FusedStream}, stream::{Fuse, FusedStream},
SinkExt, StreamExt, SinkExt, Stream, StreamExt,
}; };
use tokio::sync::{broadcast, mpsc}; use tokio::{
use tokio_stream::wrappers::{BroadcastStream, ReceiverStream}; sync::{mpsc, OwnedSemaphorePermit},
time::{sleep, timeout, Sleep},
};
use tokio_stream::wrappers::ReceiverStream;
use tower::ServiceExt; use tower::ServiceExt;
use monero_wire::{LevinCommand, Message, ProtocolMessage}; use monero_wire::{LevinCommand, Message, ProtocolMessage};
use crate::{ use crate::{
handles::ConnectionGuard, MessageID, NetworkZone, PeerBroadcast, PeerError, PeerRequest, constants::{REQUEST_TIMEOUT, SENDING_TIMEOUT},
PeerRequestHandler, PeerResponse, SharedError, handles::ConnectionGuard,
BroadcastMessage, MessageID, NetworkZone, PeerError, PeerRequest, PeerRequestHandler,
PeerResponse, SharedError,
}; };
/// A request to the connection task from a [`Client`](crate::client::Client).
pub struct ConnectionTaskRequest { pub struct ConnectionTaskRequest {
/// The request.
pub request: PeerRequest, pub request: PeerRequest,
/// The response channel.
pub response_channel: oneshot::Sender<Result<PeerResponse, tower::BoxError>>, pub response_channel: oneshot::Sender<Result<PeerResponse, tower::BoxError>>,
/// A permit for this request
pub permit: Option<OwnedSemaphorePermit>,
} }
/// The connection state.
pub enum State { pub enum State {
/// Waiting for a request from Cuprate or the connected peer.
WaitingForRequest, WaitingForRequest,
/// Waiting for a response from the peer.
WaitingForResponse { WaitingForResponse {
/// The requests ID.
request_id: MessageID, request_id: MessageID,
/// The channel to send the response down.
tx: oneshot::Sender<Result<PeerResponse, tower::BoxError>>, tx: oneshot::Sender<Result<PeerResponse, tower::BoxError>>,
/// A permit for this request.
_req_permit: Option<OwnedSemaphorePermit>,
}, },
} }
/// Returns if the [`LevinCommand`] is the correct response message for our request. /// Returns if the [`LevinCommand`] is the correct response message for our request.
/// ///
/// e.g that we didn't get a block for a txs request. /// e.g. that we didn't get a block for a txs request.
fn levin_command_response(message_id: &MessageID, command: LevinCommand) -> bool { fn levin_command_response(message_id: &MessageID, command: LevinCommand) -> bool {
matches!( matches!(
(message_id, command), (message_id, command),
@ -49,46 +72,82 @@ fn levin_command_response(message_id: &MessageID, command: LevinCommand) -> bool
) )
} }
pub struct Connection<Z: NetworkZone, ReqHndlr> { /// This represents a connection to a peer.
pub struct Connection<Z: NetworkZone, ReqHndlr, BrdcstStrm> {
/// The peer sink - where we send messages to the peer.
peer_sink: Z::Sink, peer_sink: Z::Sink,
/// The connections current state.
state: State, state: State,
client_rx: Fuse<ReceiverStream<ConnectionTaskRequest>>, /// Will be [`Some`] if we are expecting a response from the peer.
broadcast_rx: Fuse<BroadcastStream<Arc<PeerBroadcast>>>, request_timeout: Option<Pin<Box<Sleep>>>,
/// The client channel where requests from Cuprate to this peer will come from for us to route.
client_rx: Fuse<ReceiverStream<ConnectionTaskRequest>>,
/// A stream of messages to broadcast from Cuprate.
broadcast_stream: Pin<Box<BrdcstStrm>>,
/// The inner handler for any requests that come from the requested peer.
peer_request_handler: ReqHndlr, peer_request_handler: ReqHndlr,
/// The connection guard which will send signals to other parts of Cuprate when this connection is dropped.
connection_guard: ConnectionGuard, connection_guard: ConnectionGuard,
/// An error slot which is shared with the client.
error: SharedError<PeerError>, error: SharedError<PeerError>,
} }
impl<Z: NetworkZone, ReqHndlr> Connection<Z, ReqHndlr> impl<Z: NetworkZone, ReqHndlr, BrdcstStrm> Connection<Z, ReqHndlr, BrdcstStrm>
where where
ReqHndlr: PeerRequestHandler, ReqHndlr: PeerRequestHandler,
BrdcstStrm: Stream<Item = BroadcastMessage> + Send + 'static,
{ {
/// Create a new connection struct.
pub fn new( pub fn new(
peer_sink: Z::Sink, peer_sink: Z::Sink,
client_rx: mpsc::Receiver<ConnectionTaskRequest>, client_rx: mpsc::Receiver<ConnectionTaskRequest>,
broadcast_rx: broadcast::Receiver<Arc<PeerBroadcast>>, broadcast_stream: BrdcstStrm,
peer_request_handler: ReqHndlr, peer_request_handler: ReqHndlr,
connection_guard: ConnectionGuard, connection_guard: ConnectionGuard,
error: SharedError<PeerError>, error: SharedError<PeerError>,
) -> Connection<Z, ReqHndlr> { ) -> Connection<Z, ReqHndlr, BrdcstStrm> {
Connection { Connection {
peer_sink, peer_sink,
state: State::WaitingForRequest, state: State::WaitingForRequest,
request_timeout: None,
client_rx: ReceiverStream::new(client_rx).fuse(), client_rx: ReceiverStream::new(client_rx).fuse(),
broadcast_rx: BroadcastStream::new(broadcast_rx).fuse(), broadcast_stream: Box::pin(broadcast_stream),
peer_request_handler, peer_request_handler,
connection_guard, connection_guard,
error, error,
} }
} }
/// Sends a message to the peer, this function implements a timeout, so we don't get stuck sending a message to the
/// peer.
async fn send_message_to_peer(&mut self, mes: Message) -> Result<(), PeerError> { async fn send_message_to_peer(&mut self, mes: Message) -> Result<(), PeerError> {
Ok(self.peer_sink.send(mes.into()).await?) tracing::debug!("Sending message: [{}] to peer", mes.command());
timeout(SENDING_TIMEOUT, self.peer_sink.send(mes.into()))
.await
.map_err(|_| PeerError::TimedOut)
.and_then(|res| res.map_err(PeerError::BucketError))
} }
/// Handles a broadcast request from Cuprate.
async fn handle_client_broadcast(&mut self, mes: BroadcastMessage) -> Result<(), PeerError> {
match mes {
BroadcastMessage::NewFluffyBlock(block) => {
self.send_message_to_peer(Message::Protocol(ProtocolMessage::NewFluffyBlock(block)))
.await
}
BroadcastMessage::NewTransaction(txs) => {
self.send_message_to_peer(Message::Protocol(ProtocolMessage::NewTransactions(txs)))
.await
}
}
}
/// Handles a request from Cuprate, unlike a broadcast this request will be directed specifically at this peer.
async fn handle_client_request(&mut self, req: ConnectionTaskRequest) -> Result<(), PeerError> { async fn handle_client_request(&mut self, req: ConnectionTaskRequest) -> Result<(), PeerError> {
tracing::debug!("handling client request, id: {:?}", req.request.id()); tracing::debug!("handling client request, id: {:?}", req.request.id());
@ -96,21 +155,31 @@ where
self.state = State::WaitingForResponse { self.state = State::WaitingForResponse {
request_id: req.request.id(), request_id: req.request.id(),
tx: req.response_channel, tx: req.response_channel,
_req_permit: req.permit,
}; };
self.send_message_to_peer(req.request.into()).await?; self.send_message_to_peer(req.request.into()).await?;
// Set the timeout after sending the message, TODO: Is this a good idea.
self.request_timeout = Some(Box::pin(sleep(REQUEST_TIMEOUT)));
} else { } else {
// For future devs: This function cannot exit early without sending a response back down the
// response channel.
let res = self.send_message_to_peer(req.request.into()).await; let res = self.send_message_to_peer(req.request.into()).await;
// send the response now, the request does not need a response from the peer.
if let Err(e) = res { if let Err(e) = res {
let err_str = e.to_string(); let err_str = e.to_string();
let _ = req.response_channel.send(Err(err_str.clone().into())); let _ = req.response_channel.send(Err(err_str.clone().into()));
Err(e)? Err(e)?
} else { } else {
req.response_channel.send(Ok(PeerResponse::NA)); // We still need to respond even if the response is this.
let _ = req.response_channel.send(Ok(PeerResponse::NA));
} }
} }
Ok(()) Ok(())
} }
/// Handles a request from the connected peer to this node.
async fn handle_peer_request(&mut self, req: PeerRequest) -> Result<(), PeerError> { async fn handle_peer_request(&mut self, req: PeerRequest) -> Result<(), PeerError> {
tracing::debug!("Received peer request: {:?}", req.id()); tracing::debug!("Received peer request: {:?}", req.id());
@ -120,12 +189,19 @@ where
return Ok(()); return Ok(());
} }
self.send_message_to_peer(res.try_into().unwrap()).await self.send_message_to_peer(
res.try_into()
.expect("We just checked if the response was `NA`"),
)
.await
} }
/// Handles a message from a peer when we are in [`State::WaitingForResponse`].
async fn handle_potential_response(&mut self, mes: Message) -> Result<(), PeerError> { async fn handle_potential_response(&mut self, mes: Message) -> Result<(), PeerError> {
tracing::debug!("Received peer message, command: {:?}", mes.command()); tracing::debug!("Received peer message, command: {:?}", mes.command());
// If the message is defiantly a request then there is no way it can be a response to
// our request.
if mes.is_request() { if mes.is_request() {
return self.handle_peer_request(mes.try_into().unwrap()).await; return self.handle_peer_request(mes.try_into().unwrap()).await;
} }
@ -134,6 +210,7 @@ where
panic!("Not in correct state, can't receive response!") panic!("Not in correct state, can't receive response!")
}; };
// Check if the message is a response to our request.
if levin_command_response(request_id, mes.command()) { if levin_command_response(request_id, mes.command()) {
// TODO: Do more checks before returning response. // TODO: Do more checks before returning response.
@ -143,7 +220,12 @@ where
panic!("Not in correct state, can't receive response!") panic!("Not in correct state, can't receive response!")
}; };
let _ = tx.send(Ok(mes.try_into().unwrap())); let _ = tx.send(Ok(mes
.try_into()
.map_err(|_| PeerError::PeerSentInvalidMessage)?));
self.request_timeout = None;
Ok(()) Ok(())
} else { } else {
self.handle_peer_request( self.handle_peer_request(
@ -154,15 +236,21 @@ where
} }
} }
/// The main-loop for when we are in [`State::WaitingForRequest`].
async fn state_waiting_for_request<Str>(&mut self, stream: &mut Str) -> Result<(), PeerError> async fn state_waiting_for_request<Str>(&mut self, stream: &mut Str) -> Result<(), PeerError>
where where
Str: FusedStream<Item = Result<Message, monero_wire::BucketError>> + Unpin, Str: FusedStream<Item = Result<Message, monero_wire::BucketError>> + Unpin,
{ {
tracing::debug!("waiting for peer/client request."); tracing::debug!("waiting for peer/client request.");
tokio::select! { tokio::select! {
biased; biased;
broadcast_req = self.broadcast_rx.next() => { broadcast_req = self.broadcast_stream.next() => {
todo!() if let Some(broadcast_req) = broadcast_req {
self.handle_client_broadcast(broadcast_req).await
} else {
Err(PeerError::ClientChannelClosed)
}
} }
client_req = self.client_rx.next() => { client_req = self.client_rx.next() => {
if let Some(client_req) = client_req { if let Some(client_req) = client_req {
@ -181,16 +269,26 @@ where
} }
} }
/// The main-loop for when we are in [`State::WaitingForResponse`].
async fn state_waiting_for_response<Str>(&mut self, stream: &mut Str) -> Result<(), PeerError> async fn state_waiting_for_response<Str>(&mut self, stream: &mut Str) -> Result<(), PeerError>
where where
Str: FusedStream<Item = Result<Message, monero_wire::BucketError>> + Unpin, Str: FusedStream<Item = Result<Message, monero_wire::BucketError>> + Unpin,
{ {
tracing::debug!("waiting for peer response.."); tracing::debug!("waiting for peer response.");
tokio::select! { tokio::select! {
biased; biased;
broadcast_req = self.broadcast_rx.next() => { _ = self.request_timeout.as_mut().expect("Request timeout was not set!") => {
todo!() Err(PeerError::ClientChannelClosed)
} }
broadcast_req = self.broadcast_stream.next() => {
if let Some(broadcast_req) = broadcast_req {
self.handle_client_broadcast(broadcast_req).await
} else {
Err(PeerError::ClientChannelClosed)
}
}
// We don't wait for client requests as we are already handling one.
peer_message = stream.next() => { peer_message = stream.next() => {
if let Some(peer_message) = peer_message { if let Some(peer_message) = peer_message {
self.handle_potential_response(peer_message?).await self.handle_potential_response(peer_message?).await
@ -201,6 +299,9 @@ where
} }
} }
/// Runs the Connection handler logic, this should be put in a separate task.
///
/// `eager_protocol_messages` are protocol messages that we received during a handshake.
pub async fn run<Str>(mut self, mut stream: Str, eager_protocol_messages: Vec<ProtocolMessage>) pub async fn run<Str>(mut self, mut stream: Str, eager_protocol_messages: Vec<ProtocolMessage>)
where where
Str: FusedStream<Item = Result<Message, monero_wire::BucketError>> + Unpin, Str: FusedStream<Item = Result<Message, monero_wire::BucketError>> + Unpin,
@ -241,8 +342,11 @@ where
} }
} }
/// Shutdowns the connection, flushing pending requests and setting the error slot, if it hasn't been
/// set already.
fn shutdown(mut self, err: PeerError) { fn shutdown(mut self, err: PeerError) {
tracing::debug!("Connection task shutting down: {}", err); tracing::debug!("Connection task shutting down: {}", err);
let mut client_rx = self.client_rx.into_inner().into_inner(); let mut client_rx = self.client_rx.into_inner().into_inner();
client_rx.close(); client_rx.close();
@ -251,6 +355,12 @@ where
tracing::debug!("Shared error already contains an error: {}", err); tracing::debug!("Shared error already contains an error: {}", err);
} }
if let State::WaitingForResponse { tx, .. } =
std::mem::replace(&mut self.state, State::WaitingForRequest)
{
let _ = tx.send(Err(err_str.clone().into()));
}
while let Ok(req) = client_rx.try_recv() { while let Ok(req) = client_rx.try_recv() {
let _ = req.response_channel.send(Err(err_str.clone().into())); let _ = req.response_channel.send(Err(err_str.clone().into()));
} }

View file

@ -1,39 +1,58 @@
//! Connector
//!
//! This module handles connecting to peers and giving the sink/stream to the handshaker which will then
//! perform a handshake and create a [`Client`].
//!
//! This is where outbound connections are crated.
//!
use std::{ use std::{
future::Future, future::Future,
pin::Pin, pin::Pin,
task::{Context, Poll}, task::{Context, Poll},
}; };
use futures::FutureExt; use futures::{FutureExt, Stream};
use tokio::sync::OwnedSemaphorePermit; use tokio::sync::OwnedSemaphorePermit;
use tower::{Service, ServiceExt}; use tower::{Service, ServiceExt};
use crate::{ use crate::{
client::{Client, DoHandshakeRequest, HandShaker, HandshakeError, InternalPeerID}, client::{Client, DoHandshakeRequest, HandShaker, HandshakeError, InternalPeerID},
AddressBook, ConnectionDirection, CoreSyncSvc, NetworkZone, PeerRequestHandler, AddressBook, BroadcastMessage, ConnectionDirection, CoreSyncSvc, NetworkZone,
PeerRequestHandler, PeerSyncSvc,
}; };
/// A request to connect to a peer.
pub struct ConnectRequest<Z: NetworkZone> { pub struct ConnectRequest<Z: NetworkZone> {
/// The peers address
pub addr: Z::Addr, pub addr: Z::Addr,
/// A permit which will be held be the connection allowing you to set limits on the number of
/// connections.
pub permit: OwnedSemaphorePermit, pub permit: OwnedSemaphorePermit,
} }
pub struct Connector<Z: NetworkZone, AdrBook, CSync, ReqHdlr> { /// The connector service, this service connects to peer and returns the [`Client`].
handshaker: HandShaker<Z, AdrBook, CSync, ReqHdlr>, pub struct Connector<Z: NetworkZone, AdrBook, CSync, PSync, ReqHdlr, BrdcstStrmMkr> {
handshaker: HandShaker<Z, AdrBook, CSync, PSync, ReqHdlr, BrdcstStrmMkr>,
} }
impl<Z: NetworkZone, AdrBook, CSync, ReqHdlr> Connector<Z, AdrBook, CSync, ReqHdlr> { impl<Z: NetworkZone, AdrBook, CSync, PSync, ReqHdlr, BrdcstStrmMkr>
pub fn new(handshaker: HandShaker<Z, AdrBook, CSync, ReqHdlr>) -> Self { Connector<Z, AdrBook, CSync, PSync, ReqHdlr, BrdcstStrmMkr>
{
/// Create a new connector from a handshaker.
pub fn new(handshaker: HandShaker<Z, AdrBook, CSync, PSync, ReqHdlr, BrdcstStrmMkr>) -> Self {
Self { handshaker } Self { handshaker }
} }
} }
impl<Z: NetworkZone, AdrBook, CSync, ReqHdlr> Service<ConnectRequest<Z>> impl<Z: NetworkZone, AdrBook, CSync, PSync, ReqHdlr, BrdcstStrmMkr, BrdcstStrm>
for Connector<Z, AdrBook, CSync, ReqHdlr> Service<ConnectRequest<Z>> for Connector<Z, AdrBook, CSync, PSync, ReqHdlr, BrdcstStrmMkr>
where where
AdrBook: AddressBook<Z> + Clone, AdrBook: AddressBook<Z> + Clone,
CSync: CoreSyncSvc + Clone, CSync: CoreSyncSvc + Clone,
PSync: PeerSyncSvc<Z> + Clone,
ReqHdlr: PeerRequestHandler + Clone, ReqHdlr: PeerRequestHandler + Clone,
BrdcstStrm: Stream<Item = BroadcastMessage> + Send + 'static,
BrdcstStrmMkr: Fn(InternalPeerID<Z::Addr>) -> BrdcstStrm + Clone + Send + 'static,
{ {
type Response = Client<Z>; type Response = Client<Z>;
type Error = HandshakeError; type Error = HandshakeError;

View file

@ -10,16 +10,15 @@ use std::{
pin::Pin, pin::Pin,
sync::Arc, sync::Arc,
task::{Context, Poll}, task::{Context, Poll},
time::Duration,
}; };
use futures::{FutureExt, SinkExt, StreamExt}; use futures::{FutureExt, SinkExt, Stream, StreamExt};
use tokio::{ use tokio::{
sync::{broadcast, mpsc, OwnedSemaphorePermit}, sync::{mpsc, OwnedSemaphorePermit, Semaphore},
time::{error::Elapsed, timeout}, time::{error::Elapsed, timeout},
}; };
use tower::{Service, ServiceExt}; use tower::{Service, ServiceExt};
use tracing::{info_span, instrument, Instrument}; use tracing::{info_span, Instrument};
use monero_pruning::{PruningError, PruningSeed}; use monero_pruning::{PruningError, PruningSeed};
use monero_wire::{ use monero_wire::{
@ -28,40 +27,25 @@ use monero_wire::{
PING_OK_RESPONSE_STATUS_TEXT, PING_OK_RESPONSE_STATUS_TEXT,
}, },
common::PeerSupportFlags, common::PeerSupportFlags,
BasicNodeData, BucketError, CoreSyncData, LevinCommand, Message, RequestMessage, BasicNodeData, BucketError, LevinCommand, Message, RequestMessage, ResponseMessage,
ResponseMessage,
}; };
use crate::{ use crate::{
client::{connection::Connection, Client, InternalPeerID}, client::{
connection::Connection, timeout_monitor::connection_timeout_monitor_task, Client,
InternalPeerID, PeerInformation,
},
constants::{
HANDSHAKE_TIMEOUT, MAX_EAGER_PROTOCOL_MESSAGES, MAX_PEERS_IN_PEER_LIST_MESSAGE,
PING_TIMEOUT,
},
handles::HandleBuilder, handles::HandleBuilder,
AddressBook, AddressBookRequest, AddressBookResponse, ConnectionDirection, CoreSyncDataRequest, services::PeerSyncRequest,
CoreSyncDataResponse, CoreSyncSvc, MessageID, NetZoneAddress, NetworkZone, PeerBroadcast, AddressBook, AddressBookRequest, AddressBookResponse, BroadcastMessage, ConnectionDirection,
PeerRequestHandler, SharedError, MAX_PEERS_IN_PEER_LIST_MESSAGE, CoreSyncDataRequest, CoreSyncDataResponse, CoreSyncSvc, NetZoneAddress, NetworkZone,
PeerRequestHandler, PeerSyncSvc, SharedError,
}; };
/// This is a Cuprate specific constant.
///
/// When completing a handshake monerod might send protocol messages before the handshake is actually
/// complete, this is a problem for Cuprate as we must complete the handshake before responding to any
/// protocol requests. So when we receive a protocol message during a handshake we keep them around to handle
/// after the handshake.
///
/// Because we use the [bytes crate](https://crates.io/crates/bytes) in monero-wire for zero-copy parsing
/// it is not safe to keep too many of these messages around for long.
const MAX_EAGER_PROTOCOL_MESSAGES: usize = 1;
/// The time given to complete a handshake before the handshake fails.
const HANDSHAKE_TIMEOUT: Duration = Duration::from_secs(120);
/// A timeout put on pings during handshakes.
///
/// When we receive an inbound connection we open an outbound connection to the node and send a ping message
/// to see if we can reach the node, so we can add it to our address book.
///
/// This timeout must be significantly shorter than [`HANDSHAKE_TIMEOUT`] so we don't drop inbound connections that
/// don't have ports open.
const PING_TIMEOUT: Duration = Duration::from_secs(10);
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]
pub enum HandshakeError { pub enum HandshakeError {
#[error("The handshake timed out")] #[error("The handshake timed out")]
@ -100,52 +84,60 @@ pub struct DoHandshakeRequest<Z: NetworkZone> {
/// The peer handshaking service. /// The peer handshaking service.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct HandShaker<Z: NetworkZone, AdrBook, CSync, ReqHdlr> { pub struct HandShaker<Z: NetworkZone, AdrBook, CSync, PSync, ReqHdlr, BrdcstStrmMkr> {
/// The address book service. /// The address book service.
address_book: AdrBook, address_book: AdrBook,
/// The core sync data service. /// The core sync data service.
core_sync_svc: CSync, core_sync_svc: CSync,
/// The peer sync service.
peer_sync_svc: PSync,
/// The peer request handler service. /// The peer request handler service.
peer_request_svc: ReqHdlr, peer_request_svc: ReqHdlr,
/// Our [`BasicNodeData`] /// Our [`BasicNodeData`]
our_basic_node_data: BasicNodeData, our_basic_node_data: BasicNodeData,
/// The channel to broadcast messages to all peers created with this handshaker. /// A function that returns a stream that will give items to be broadcast by a connection.
broadcast_tx: broadcast::Sender<Arc<PeerBroadcast>>, broadcast_stream_maker: BrdcstStrmMkr,
/// The network zone. /// The network zone.
_zone: PhantomData<Z>, _zone: PhantomData<Z>,
} }
impl<Z: NetworkZone, AdrBook, CSync, ReqHdlr> HandShaker<Z, AdrBook, CSync, ReqHdlr> { impl<Z: NetworkZone, AdrBook, CSync, PSync, ReqHdlr, BrdcstStrmMkr>
HandShaker<Z, AdrBook, CSync, PSync, ReqHdlr, BrdcstStrmMkr>
{
/// Creates a new handshaker. /// Creates a new handshaker.
pub fn new( pub fn new(
address_book: AdrBook, address_book: AdrBook,
peer_sync_svc: PSync,
core_sync_svc: CSync, core_sync_svc: CSync,
peer_request_svc: ReqHdlr, peer_request_svc: ReqHdlr,
broadcast_stream_maker: BrdcstStrmMkr,
broadcast_tx: broadcast::Sender<Arc<PeerBroadcast>>,
our_basic_node_data: BasicNodeData, our_basic_node_data: BasicNodeData,
) -> Self { ) -> Self {
Self { Self {
address_book, address_book,
peer_sync_svc,
core_sync_svc, core_sync_svc,
peer_request_svc, peer_request_svc,
broadcast_tx, broadcast_stream_maker,
our_basic_node_data, our_basic_node_data,
_zone: PhantomData, _zone: PhantomData,
} }
} }
} }
impl<Z: NetworkZone, AdrBook, CSync, ReqHdlr> Service<DoHandshakeRequest<Z>> impl<Z: NetworkZone, AdrBook, CSync, PSync, ReqHdlr, BrdcstStrmMkr, BrdcstStrm>
for HandShaker<Z, AdrBook, CSync, ReqHdlr> Service<DoHandshakeRequest<Z>> for HandShaker<Z, AdrBook, CSync, PSync, ReqHdlr, BrdcstStrmMkr>
where where
AdrBook: AddressBook<Z> + Clone, AdrBook: AddressBook<Z> + Clone,
CSync: CoreSyncSvc + Clone, CSync: CoreSyncSvc + Clone,
PSync: PeerSyncSvc<Z> + Clone,
ReqHdlr: PeerRequestHandler + Clone, ReqHdlr: PeerRequestHandler + Clone,
BrdcstStrm: Stream<Item = BroadcastMessage> + Send + 'static,
BrdcstStrmMkr: Fn(InternalPeerID<Z::Addr>) -> BrdcstStrm + Clone + Send + 'static,
{ {
type Response = Client<Z>; type Response = Client<Z>;
type Error = HandshakeError; type Error = HandshakeError;
@ -157,11 +149,12 @@ where
} }
fn call(&mut self, req: DoHandshakeRequest<Z>) -> Self::Future { fn call(&mut self, req: DoHandshakeRequest<Z>) -> Self::Future {
let broadcast_rx = self.broadcast_tx.subscribe(); let broadcast_stream_maker = self.broadcast_stream_maker.clone();
let address_book = self.address_book.clone(); let address_book = self.address_book.clone();
let peer_request_svc = self.peer_request_svc.clone(); let peer_request_svc = self.peer_request_svc.clone();
let core_sync_svc = self.core_sync_svc.clone(); let core_sync_svc = self.core_sync_svc.clone();
let peer_sync_svc = self.peer_sync_svc.clone();
let our_basic_node_data = self.our_basic_node_data.clone(); let our_basic_node_data = self.our_basic_node_data.clone();
let span = info_span!(parent: &tracing::Span::current(), "handshaker", addr=%req.addr); let span = info_span!(parent: &tracing::Span::current(), "handshaker", addr=%req.addr);
@ -171,9 +164,10 @@ where
HANDSHAKE_TIMEOUT, HANDSHAKE_TIMEOUT,
handshake( handshake(
req, req,
broadcast_rx, broadcast_stream_maker,
address_book, address_book,
core_sync_svc, core_sync_svc,
peer_sync_svc,
peer_request_svc, peer_request_svc,
our_basic_node_data, our_basic_node_data,
), ),
@ -226,20 +220,24 @@ pub async fn ping<N: NetworkZone>(addr: N::Addr) -> Result<u64, HandshakeError>
} }
/// This function completes a handshake with the requested peer. /// This function completes a handshake with the requested peer.
async fn handshake<Z: NetworkZone, AdrBook, CSync, ReqHdlr>( async fn handshake<Z: NetworkZone, AdrBook, CSync, PSync, ReqHdlr, BrdcstStrmMkr, BrdcstStrm>(
req: DoHandshakeRequest<Z>, req: DoHandshakeRequest<Z>,
broadcast_rx: broadcast::Receiver<Arc<PeerBroadcast>>, broadcast_stream_maker: BrdcstStrmMkr,
mut address_book: AdrBook, mut address_book: AdrBook,
mut core_sync_svc: CSync, mut core_sync_svc: CSync,
mut peer_sync_svc: PSync,
peer_request_svc: ReqHdlr, peer_request_svc: ReqHdlr,
our_basic_node_data: BasicNodeData, our_basic_node_data: BasicNodeData,
) -> Result<Client<Z>, HandshakeError> ) -> Result<Client<Z>, HandshakeError>
where where
AdrBook: AddressBook<Z>, AdrBook: AddressBook<Z>,
CSync: CoreSyncSvc, CSync: CoreSyncSvc,
PSync: PeerSyncSvc<Z>,
ReqHdlr: PeerRequestHandler, ReqHdlr: PeerRequestHandler,
BrdcstStrm: Stream<Item = BroadcastMessage> + Send + 'static,
BrdcstStrmMkr: Fn(InternalPeerID<Z::Addr>) -> BrdcstStrm + Send + 'static,
{ {
let DoHandshakeRequest { let DoHandshakeRequest {
addr, addr,
@ -253,7 +251,7 @@ where
// see: [`MAX_EAGER_PROTOCOL_MESSAGES`] // see: [`MAX_EAGER_PROTOCOL_MESSAGES`]
let mut eager_protocol_messages = Vec::new(); let mut eager_protocol_messages = Vec::new();
let (peer_core_sync, mut peer_node_data) = match direction { let (peer_core_sync, peer_node_data) = match direction {
ConnectionDirection::InBound => { ConnectionDirection::InBound => {
// Inbound handshake the peer sends the request. // Inbound handshake the peer sends the request.
tracing::debug!("waiting for handshake request."); tracing::debug!("waiting for handshake request.");
@ -424,39 +422,39 @@ where
} }
}; };
// Tell the core sync service about the new peer.
core_sync_svc
.ready()
.await?
.call(CoreSyncDataRequest::HandleIncoming(peer_core_sync.clone()))
.await?;
tracing::debug!("Handshake complete."); tracing::debug!("Handshake complete.");
// Set up the connection data. // Set up the connection data.
let error_slot = SharedError::new(); let error_slot = SharedError::new();
let (connection_guard, handle) = HandleBuilder::new().with_permit(permit).build(); let (connection_guard, handle) = HandleBuilder::new().with_permit(permit).build();
let (connection_tx, client_rx) = mpsc::channel(3); let (connection_tx, client_rx) = mpsc::channel(1);
let connection = Connection::<Z, _>::new( let connection = Connection::<Z, _, _>::new(
peer_sink, peer_sink,
client_rx, client_rx,
broadcast_rx, broadcast_stream_maker(addr),
peer_request_svc, peer_request_svc,
connection_guard, connection_guard,
error_slot.clone(), error_slot.clone(),
); );
let connection_handle = let connection_span = tracing::error_span!(parent: &tracing::Span::none(), "connection", %addr);
tokio::spawn(connection.run(peer_stream.fuse(), eager_protocol_messages)); let connection_handle = tokio::spawn(
connection
.run(peer_stream.fuse(), eager_protocol_messages)
.instrument(connection_span),
);
let client = Client::<Z>::new( // Tell the core sync service about the new peer.
peer_sync_svc
.ready()
.await?
.call(PeerSyncRequest::IncomingCoreSyncData(
addr, addr,
handle.clone(), handle.clone(),
connection_tx, peer_core_sync,
connection_handle, ))
error_slot, .await?;
);
// Tell the address book about the new connection. // Tell the address book about the new connection.
address_book address_book
@ -465,7 +463,7 @@ where
.call(AddressBookRequest::NewConnection { .call(AddressBookRequest::NewConnection {
internal_peer_id: addr, internal_peer_id: addr,
public_address, public_address,
handle, handle: handle.clone(),
id: peer_node_data.peer_id, id: peer_node_data.peer_id,
pruning_seed, pruning_seed,
rpc_port: peer_node_data.rpc_port, rpc_port: peer_node_data.rpc_port,
@ -473,6 +471,34 @@ where
}) })
.await?; .await?;
let info = PeerInformation {
id: addr,
handle,
direction,
pruning_seed: pruning_seed.clone(),
};
let semaphore = Arc::new(Semaphore::new(1));
let timeout_handle = tokio::spawn(connection_timeout_monitor_task(
info.id,
info.handle.clone(),
connection_tx.clone(),
semaphore.clone(),
address_book,
core_sync_svc,
peer_sync_svc,
));
let client = Client::<Z>::new(
info,
connection_tx,
connection_handle,
timeout_handle,
semaphore,
error_slot,
);
Ok(client) Ok(client)
} }
@ -485,14 +511,11 @@ async fn send_hs_request<Z: NetworkZone, CSync>(
where where
CSync: CoreSyncSvc, CSync: CoreSyncSvc,
{ {
let CoreSyncDataResponse::Ours(our_core_sync_data) = core_sync_svc let CoreSyncDataResponse(our_core_sync_data) = core_sync_svc
.ready() .ready()
.await? .await?
.call(CoreSyncDataRequest::Ours) .call(CoreSyncDataRequest)
.await? .await?;
else {
panic!("core sync service returned wrong response!");
};
let req = HandshakeRequest { let req = HandshakeRequest {
node_data: our_basic_node_data, node_data: our_basic_node_data,
@ -519,14 +542,11 @@ where
AdrBook: AddressBook<Z>, AdrBook: AddressBook<Z>,
CSync: CoreSyncSvc, CSync: CoreSyncSvc,
{ {
let CoreSyncDataResponse::Ours(our_core_sync_data) = core_sync_svc let CoreSyncDataResponse(our_core_sync_data) = core_sync_svc
.ready() .ready()
.await? .await?
.call(CoreSyncDataRequest::Ours) .call(CoreSyncDataRequest)
.await? .await?;
else {
panic!("core sync service returned wrong response!");
};
let AddressBookResponse::Peers(our_peer_list) = address_book let AddressBookResponse::Peers(our_peer_list) = address_book
.ready() .ready()
@ -612,7 +632,7 @@ async fn wait_for_message<Z: NetworkZone>(
continue; continue;
} }
RequestMessage::Ping => { RequestMessage::Ping => {
if !allow_support_flag_req { if !allow_ping {
return Err(HandshakeError::PeerSentInvalidMessage( return Err(HandshakeError::PeerSentInvalidMessage(
"Peer sent 2 ping requests", "Peer sent 2 ping requests",
)); ));

View file

@ -0,0 +1,135 @@
//! Timeout Monitor
//!
//! This module holds the task that sends periodic [TimedSync](PeerRequest::TimedSync) requests to a peer to make
//! sure the connection is still active.
use std::sync::Arc;
use futures::channel::oneshot;
use monero_wire::admin::TimedSyncRequest;
use tokio::{
sync::{mpsc, Semaphore},
time::{interval, MissedTickBehavior},
};
use tower::ServiceExt;
use tracing::instrument;
use crate::{
client::{connection::ConnectionTaskRequest, InternalPeerID},
constants::{MAX_PEERS_IN_PEER_LIST_MESSAGE, TIMEOUT_INTERVAL},
handles::ConnectionHandle,
services::{AddressBookRequest, CoreSyncDataRequest, CoreSyncDataResponse, PeerSyncRequest},
AddressBook, CoreSyncSvc, NetworkZone, PeerRequest, PeerResponse, PeerSyncSvc,
};
/// The timeout monitor task, this task will send periodic timed sync requests to the peer to make sure it is still active.
#[instrument(
name = "timeout_monitor",
level = "debug",
fields(addr = %id),
skip_all,
)]
pub async fn connection_timeout_monitor_task<N: NetworkZone, AdrBook, CSync, PSync>(
id: InternalPeerID<N::Addr>,
handle: ConnectionHandle,
connection_tx: mpsc::Sender<ConnectionTaskRequest>,
semaphore: Arc<Semaphore>,
mut address_book_svc: AdrBook,
mut core_sync_svc: CSync,
mut peer_core_sync_svc: PSync,
) -> Result<(), tower::BoxError>
where
AdrBook: AddressBook<N>,
CSync: CoreSyncSvc,
PSync: PeerSyncSvc<N>,
{
// Instead of tracking the time from last message from the peer and sending a timed sync if this value is too high,
// we just send a timed sync every [TIMEOUT_INTERVAL] seconds.
let mut interval = interval(TIMEOUT_INTERVAL);
interval.set_missed_tick_behavior(MissedTickBehavior::Skip);
// The first tick ticks instantly.
interval.tick().await;
loop {
interval.tick().await;
tracing::trace!("timeout monitor tick.");
if connection_tx.is_closed() {
tracing::debug!("Closing timeout monitor, connection disconnected.");
return Ok(());
}
let Ok(permit) = semaphore.clone().try_acquire_owned() else {
// If we can't get a permit the connection is currently waiting for a response, so no need to
// do a timed sync.
continue;
};
let ping_span = tracing::debug_span!("timed_sync");
// get our core sync data
tracing::trace!(parent: &ping_span, "Attempting to get our core sync data");
let CoreSyncDataResponse(core_sync_data) = core_sync_svc
.ready()
.await?
.call(CoreSyncDataRequest)
.await?;
let (tx, rx) = oneshot::channel();
// TODO: Instead of always sending timed syncs, send pings if we have a full peer list.
tracing::debug!(parent: &ping_span, "Sending timed sync to peer");
connection_tx
.send(ConnectionTaskRequest {
request: PeerRequest::TimedSync(TimedSyncRequest {
payload_data: core_sync_data,
}),
response_channel: tx,
permit: Some(permit),
})
.await?;
let PeerResponse::TimedSync(timed_sync) = rx.await?? else {
panic!("Connection task returned wrong response!");
};
tracing::debug!(
parent: &ping_span,
"Received timed sync response, incoming peer list len: {}",
timed_sync.local_peerlist_new.len()
);
if timed_sync.local_peerlist_new.len() > MAX_PEERS_IN_PEER_LIST_MESSAGE {
return Err("Peer sent too many peers in peer list".into());
}
// Tell our address book about the new peers.
address_book_svc
.ready()
.await?
.call(AddressBookRequest::IncomingPeerList(
timed_sync
.local_peerlist_new
.into_iter()
.map(TryInto::try_into)
.collect::<Result<_, _>>()?,
))
.await?;
// Tell the peer sync service about the peers core sync data
peer_core_sync_svc
.ready()
.await?
.call(PeerSyncRequest::IncomingCoreSyncData(
id,
handle.clone(),
timed_sync.payload_data,
))
.await?;
}
}

View file

@ -0,0 +1,43 @@
//! Constants used around monero-p2p
use std::time::Duration;
/// The request timeout - the time we give a peer to respond to a request.
pub(crate) const REQUEST_TIMEOUT: Duration = Duration::from_secs(60);
/// The timeout used when sending messages to a peer.
///
/// TODO: Make this configurable?
/// TODO: Is this a good default.
pub(crate) const SENDING_TIMEOUT: Duration = Duration::from_secs(20);
/// The interval between timed syncs.
///
/// TODO: Make this configurable?
/// TODO: Is this a good default.
pub(crate) const TIMEOUT_INTERVAL: Duration = Duration::from_secs(61);
/// This is a Cuprate specific constant.
///
/// When completing a handshake monerod might send protocol messages before the handshake is actually
/// complete, this is a problem for Cuprate as we must complete the handshake before responding to any
/// protocol requests. So when we receive a protocol message during a handshake we keep them around to handle
/// after the handshake.
///
/// Because we use the [bytes crate](https://crates.io/crates/bytes) in monero-wire for zero-copy parsing
/// it is not safe to keep too many of these messages around for long.
pub(crate) const MAX_EAGER_PROTOCOL_MESSAGES: usize = 1;
/// A timeout put on pings during handshakes.
///
/// When we receive an inbound connection we open an outbound connection to the node and send a ping message
/// to see if we can reach the node, so we can add it to our address book.
///
/// This timeout must be significantly shorter than [`HANDSHAKE_TIMEOUT`] so we don't drop inbound connections that
/// don't have ports open.
pub(crate) const PING_TIMEOUT: Duration = Duration::from_secs(10);
/// A timeout for a handshake - the handshake must complete before this.
pub(crate) const HANDSHAKE_TIMEOUT: Duration = Duration::from_secs(60);
pub(crate) const MAX_PEERS_IN_PEER_LIST_MESSAGE: usize = 250;

View file

@ -30,6 +30,8 @@ impl<T> SharedError<T> {
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]
pub enum PeerError { pub enum PeerError {
#[error("The connection timed out.")]
TimedOut,
#[error("The connection was closed.")] #[error("The connection was closed.")]
ConnectionClosed, ConnectionClosed,
#[error("The connection tasks client channel was closed")] #[error("The connection tasks client channel was closed")]

View file

@ -7,9 +7,8 @@ use std::{
time::Duration, time::Duration,
}; };
use futures::SinkExt; use tokio::sync::OwnedSemaphorePermit;
use tokio::sync::{OwnedSemaphorePermit, Semaphore}; use tokio_util::sync::{CancellationToken, WaitForCancellationFutureOwned};
use tokio_util::sync::CancellationToken;
/// A [`ConnectionHandle`] builder. /// A [`ConnectionHandle`] builder.
#[derive(Default, Debug)] #[derive(Default, Debug)]
@ -40,7 +39,7 @@ impl HandleBuilder {
( (
ConnectionGuard { ConnectionGuard {
token: token.clone(), token: token.clone(),
permit: self.permit.expect("connection permit was not set!"), _permit: self.permit.expect("connection permit was not set!"),
}, },
ConnectionHandle { ConnectionHandle {
token: token.clone(), token: token.clone(),
@ -57,7 +56,7 @@ pub struct BanPeer(pub Duration);
/// A struct given to the connection task. /// A struct given to the connection task.
pub struct ConnectionGuard { pub struct ConnectionGuard {
token: CancellationToken, token: CancellationToken,
permit: OwnedSemaphorePermit, _permit: OwnedSemaphorePermit,
} }
impl ConnectionGuard { impl ConnectionGuard {
@ -88,9 +87,13 @@ pub struct ConnectionHandle {
} }
impl ConnectionHandle { impl ConnectionHandle {
pub fn closed(&self) -> WaitForCancellationFutureOwned {
self.token.clone().cancelled_owned()
}
/// Bans the peer for the given `duration`. /// Bans the peer for the given `duration`.
pub fn ban_peer(&self, duration: Duration) { pub fn ban_peer(&self, duration: Duration) {
let _ = self.ban.set(BanPeer(duration)); let _ = self.ban.set(BanPeer(duration));
self.token.cancel();
} }
/// Checks if this connection is closed. /// Checks if this connection is closed.
pub fn is_closed(&self) -> bool { pub fn is_closed(&self) -> bool {

View file

@ -1,5 +1,17 @@
#![allow(unused)] //! # Monero P2P
//!
//! This crate is general purpose P2P networking library for working with Monero. This is a low level
//! crate, which means it may seem verbose for a lot of use cases, if you want a crate that handles
//! more of the P2P logic have a look at `cuprate-p2p`.
//!
//! # Network Zones
//!
//! This crate abstracts over network zones, Tor/I2p/clearnet with the [NetworkZone] trait. Currently only clearnet is implemented: [ClearNet](network_zones::ClearNet).
//!
//! # Usage
//!
//! TODO
//!
use std::{fmt::Debug, future::Future, hash::Hash, pin::Pin}; use std::{fmt::Debug, future::Future, hash::Hash, pin::Pin};
use futures::{Sink, Stream}; use futures::{Sink, Stream};
@ -10,6 +22,7 @@ use monero_wire::{
}; };
pub mod client; pub mod client;
mod constants;
pub mod error; pub mod error;
pub mod handles; pub mod handles;
pub mod network_zones; pub mod network_zones;
@ -20,8 +33,6 @@ pub use error::*;
pub use protocol::*; pub use protocol::*;
use services::*; use services::*;
const MAX_PEERS_IN_PEER_LIST_MESSAGE: usize = 250;
#[derive(Debug, Copy, Clone, Eq, PartialEq)] #[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum ConnectionDirection { pub enum ConnectionDirection {
InBound, InBound,
@ -35,9 +46,9 @@ pub trait NetZoneAddress:
+ std::fmt::Display + std::fmt::Display
+ Hash + Hash
+ Eq + Eq
+ Clone
+ Copy + Copy
+ Send + Send
+ Sync
+ Unpin + Unpin
+ 'static + 'static
{ {
@ -48,6 +59,11 @@ pub trait NetZoneAddress:
/// TODO: IP zone banning? /// TODO: IP zone banning?
type BanID: Debug + Hash + Eq + Clone + Copy + Send + 'static; type BanID: Debug + Hash + Eq + Clone + Copy + Send + 'static;
/// Changes the port of this address to `port`.
fn set_port(&mut self, port: u16);
fn make_canonical(&mut self);
fn ban_id(&self) -> Self::BanID; fn ban_id(&self) -> Self::BanID;
fn should_add_to_peer_list(&self) -> bool; fn should_add_to_peer_list(&self) -> bool;
@ -64,6 +80,7 @@ pub trait NetZoneAddress:
+ Eq + Eq
+ Copy + Copy
+ Send + Send
+ Sync
+ Unpin + Unpin
+ 'static + 'static
{ {
@ -77,6 +94,8 @@ pub trait NetZoneAddress:
/// Changes the port of this address to `port`. /// Changes the port of this address to `port`.
fn set_port(&mut self, port: u16); fn set_port(&mut self, port: u16);
fn make_canonical(&mut self);
fn ban_id(&self) -> Self::BanID; fn ban_id(&self) -> Self::BanID;
fn should_add_to_peer_list(&self) -> bool; fn should_add_to_peer_list(&self) -> bool;
@ -100,6 +119,8 @@ pub trait NetworkZone: Clone + Copy + Send + 'static {
/// This has privacy implications on an anonymity network if true so should be set /// This has privacy implications on an anonymity network if true so should be set
/// to false. /// to false.
const CHECK_NODE_ID: bool; const CHECK_NODE_ID: bool;
/// Fixed seed nodes for this network.
const SEEDS: &'static [Self::Addr];
/// The address type of this network. /// The address type of this network.
type Addr: NetZoneAddress; type Addr: NetZoneAddress;
@ -124,7 +145,35 @@ pub trait NetworkZone: Clone + Copy + Send + 'static {
) -> Result<Self::Listener, std::io::Error>; ) -> Result<Self::Listener, std::io::Error>;
} }
pub(crate) trait AddressBook<Z: NetworkZone>: // ####################################################################################
// Below here is just helper traits, so we don't have to type out tower::Service bounds
// everywhere but still get to use tower.
pub trait PeerSyncSvc<Z: NetworkZone>:
tower::Service<
PeerSyncRequest<Z>,
Response = PeerSyncResponse<Z>,
Error = tower::BoxError,
Future = Self::Future2,
> + Send
+ 'static
{
// This allows us to put more restrictive bounds on the future without defining the future here
// explicitly.
type Future2: Future<Output = Result<Self::Response, Self::Error>> + Send + 'static;
}
impl<T, Z: NetworkZone> PeerSyncSvc<Z> for T
where
T: tower::Service<PeerSyncRequest<Z>, Response = PeerSyncResponse<Z>, Error = tower::BoxError>
+ Send
+ 'static,
T::Future: Future<Output = Result<Self::Response, Self::Error>> + Send + 'static,
{
type Future2 = T::Future;
}
pub trait AddressBook<Z: NetworkZone>:
tower::Service< tower::Service<
AddressBookRequest<Z>, AddressBookRequest<Z>,
Response = AddressBookResponse<Z>, Response = AddressBookResponse<Z>,
@ -151,7 +200,7 @@ where
type Future2 = T::Future; type Future2 = T::Future;
} }
pub(crate) trait CoreSyncSvc: pub trait CoreSyncSvc:
tower::Service< tower::Service<
CoreSyncDataRequest, CoreSyncDataRequest,
Response = CoreSyncDataResponse, Response = CoreSyncDataResponse,
@ -183,7 +232,7 @@ impl<T> CoreSyncSvc for T where
{ {
} }
pub(crate) trait PeerRequestHandler: pub trait PeerRequestHandler:
tower::Service< tower::Service<
PeerRequest, PeerRequest,
Response = PeerResponse, Response = PeerResponse,

View file

@ -1,8 +1,8 @@
use std::net::{IpAddr, SocketAddr}; use std::{
use std::pin::Pin; net::{IpAddr, Ipv4Addr, SocketAddr},
use std::task::{Context, Poll}; pin::Pin,
task::{Context, Poll},
use monero_wire::MoneroWireCodec; };
use futures::Stream; use futures::Stream;
use tokio::net::{ use tokio::net::{
@ -11,6 +11,8 @@ use tokio::net::{
}; };
use tokio_util::codec::{FramedRead, FramedWrite}; use tokio_util::codec::{FramedRead, FramedWrite};
use monero_wire::MoneroWireCodec;
use crate::{NetZoneAddress, NetworkZone}; use crate::{NetZoneAddress, NetworkZone};
impl NetZoneAddress for SocketAddr { impl NetZoneAddress for SocketAddr {
@ -24,8 +26,14 @@ impl NetZoneAddress for SocketAddr {
self.ip() self.ip()
} }
fn make_canonical(&mut self) {
let ip = self.ip().to_canonical();
self.set_ip(ip);
}
fn should_add_to_peer_list(&self) -> bool { fn should_add_to_peer_list(&self) -> bool {
todo!() // TODO
true
} }
} }
@ -36,9 +44,19 @@ pub struct ClearNetServerCfg {
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
pub enum ClearNet {} pub enum ClearNet {}
const fn ip_v4(a: u8, b: u8, c: u8, d: u8, port: u16) -> SocketAddr {
SocketAddr::new(IpAddr::V4(Ipv4Addr::new(a, b, c, d)), port)
}
#[async_trait::async_trait] #[async_trait::async_trait]
impl NetworkZone for ClearNet { impl NetworkZone for ClearNet {
const NAME: &'static str = "ClearNet"; const NAME: &'static str = "ClearNet";
const SEEDS: &'static [Self::Addr] = &[
ip_v4(37, 187, 74, 171, 18080),
ip_v4(192, 99, 8, 110, 18080),
];
const ALLOW_SYNC: bool = true; const ALLOW_SYNC: bool = true;
const DANDELION_PP: bool = true; const DANDELION_PP: bool = true;
const CHECK_NODE_ID: bool = true; const CHECK_NODE_ID: bool = true;
@ -85,7 +103,10 @@ impl Stream for InBoundStream {
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> { fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
self.listener self.listener
.poll_accept(cx) .poll_accept(cx)
.map_ok(|(stream, addr)| { .map_ok(|(stream, mut addr)| {
let ip = addr.ip().to_canonical();
addr.set_ip(ip);
let (read, write) = stream.into_split(); let (read, write) = stream.into_split();
( (
Some(addr), Some(addr),

View file

@ -1,28 +1,27 @@
/// This module defines InternalRequests and InternalResponses. Cuprate's P2P works by translating network messages into an internal //! This module defines InternalRequests and InternalResponses. Cuprate's P2P works by translating network messages into an internal
/// request/ response, this is easy for levin "requests" and "responses" (admin messages) but takes a bit more work with "notifications" //! request/ response, this is easy for levin "requests" and "responses" (admin messages) but takes a bit more work with "notifications"
/// (protocol messages). //! (protocol messages).
/// //!
/// Some notifications are easy to translate, like `GetObjectsRequest` is obviously a request but others like `NewFluffyBlock` are a //! Some notifications are easy to translate, like `GetObjectsRequest` is obviously a request but others like `NewFluffyBlock` are a
/// bit tricker. To translate a `NewFluffyBlock` into a request/ response we will have to look to see if we asked for `FluffyMissingTransactionsRequest` //! bit tri cker. To translate a `NewFluffyBlock` into a request/ response we will have to look to see if we asked for `FluffyMissingTransactionsRequest`
/// if we have we interpret `NewFluffyBlock` as a response if not its a request that doesn't require a response. //! if we have we interpret `NewFluffyBlock` as a response if not its a request that doesn't require a response.
/// //!
/// Here is every P2P request/ response. *note admin messages are already request/ response so "Handshake" is actually made of a HandshakeRequest & HandshakeResponse //! Here is every P2P request/ response. *note admin messages are already request/ response so "Handshake" is actually made of a HandshakeRequest & HandshakeResponse
/// //!
/// Admin: //! Admin:
/// Handshake, //! Handshake,
/// TimedSync, //! TimedSync,
/// Ping, //! Ping,
/// SupportFlags //! SupportFlags
/// Protocol: //! Protocol:
/// Request: GetObjectsRequest, Response: GetObjectsResponse, //! Request: GetObjectsRequest, Response: GetObjectsResponse,
/// Request: ChainRequest, Response: ChainResponse, //! Request: ChainRequest, Response: ChainResponse,
/// Request: FluffyMissingTransactionsRequest, Response: NewFluffyBlock, <- these 2 could be requests or responses //! Request: FluffyMissingTransactionsRequest, Response: NewFluffyBlock, <- these 2 could be requests or responses
/// Request: GetTxPoolCompliment, Response: NewTransactions, <- //! Request: GetTxPoolCompliment, Response: NewTransactions, <-
/// Request: NewBlock, Response: None, //! Request: NewBlock, Response: None,
/// Request: NewFluffyBlock, Response: None, //! Request: NewFluffyBlock, Response: None,
/// Request: NewTransactions, Response: None //! Request: NewTransactions, Response: None
/// //!
///
use monero_wire::{ use monero_wire::{
admin::{ admin::{
HandshakeRequest, HandshakeResponse, PingResponse, SupportFlagsResponse, TimedSyncRequest, HandshakeRequest, HandshakeResponse, PingResponse, SupportFlagsResponse, TimedSyncRequest,
@ -55,13 +54,12 @@ pub enum MessageID {
NewTransactions, NewTransactions,
} }
/// This is a sub-set of [`PeerRequest`] for requests that should be sent to all nodes. pub enum BroadcastMessage {
pub enum PeerBroadcast {
Transactions(NewTransactions),
NewBlock(NewBlock),
NewFluffyBlock(NewFluffyBlock), NewFluffyBlock(NewFluffyBlock),
NewTransaction(NewTransactions),
} }
#[derive(Debug, Clone)]
pub enum PeerRequest { pub enum PeerRequest {
Handshake(HandshakeRequest), Handshake(HandshakeRequest),
TimedSync(TimedSyncRequest), TimedSync(TimedSyncRequest),
@ -105,6 +103,7 @@ impl PeerRequest {
} }
} }
#[derive(Debug, Clone)]
pub enum PeerResponse { pub enum PeerResponse {
Handshake(HandshakeResponse), Handshake(HandshakeResponse),
TimedSync(TimedSyncResponse), TimedSync(TimedSyncResponse),

View file

@ -1,21 +1,35 @@
use monero_pruning::{PruningError, PruningSeed}; use monero_pruning::{PruningError, PruningSeed};
use monero_wire::{NetZone, NetworkAddress, PeerListEntryBase}; use monero_wire::{CoreSyncData, PeerListEntryBase};
use crate::{ use crate::{
client::InternalPeerID, handles::ConnectionHandle, NetZoneAddress, NetworkAddressIncorrectZone, client::InternalPeerID, handles::ConnectionHandle, NetZoneAddress, NetworkAddressIncorrectZone,
NetworkZone, NetworkZone,
}; };
pub enum CoreSyncDataRequest { pub enum PeerSyncRequest<N: NetworkZone> {
Ours, /// Request some peers to sync from.
HandleIncoming(monero_wire::CoreSyncData), ///
/// This takes in the current cumulative difficulty of our chain and will return peers that
/// claim to have a higher cumulative difficulty.
PeersToSyncFrom {
current_cumulative_difficulty: u128,
block_needed: Option<u64>,
},
/// Add/update a peers core sync data to the sync state service.
IncomingCoreSyncData(InternalPeerID<N::Addr>, ConnectionHandle, CoreSyncData),
} }
pub enum CoreSyncDataResponse { pub enum PeerSyncResponse<N: NetworkZone> {
Ours(monero_wire::CoreSyncData), /// The return value of [`PeerSyncRequest::PeersToSyncFrom`].
PeersToSyncFrom(Vec<InternalPeerID<N::Addr>>),
/// A generic ok response.
Ok, Ok,
} }
pub struct CoreSyncDataRequest;
pub struct CoreSyncDataResponse(pub CoreSyncData);
#[derive(Debug, Copy, Clone, Eq, PartialEq)] #[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[cfg_attr( #[cfg_attr(
feature = "borsh", feature = "borsh",

View file

@ -13,7 +13,7 @@ use tokio::{
tcp::{OwnedReadHalf, OwnedWriteHalf}, tcp::{OwnedReadHalf, OwnedWriteHalf},
TcpListener, TcpStream, TcpListener, TcpStream,
}, },
sync::{broadcast, Semaphore}, sync::Semaphore,
time::timeout, time::timeout,
}; };
use tokio_util::{ use tokio_util::{
@ -47,6 +47,7 @@ pub enum FragNet {}
#[async_trait::async_trait] #[async_trait::async_trait]
impl NetworkZone for FragNet { impl NetworkZone for FragNet {
const NAME: &'static str = "FragNet"; const NAME: &'static str = "FragNet";
const SEEDS: &'static [Self::Addr] = &[];
const ALLOW_SYNC: bool = true; const ALLOW_SYNC: bool = true;
const DANDELION_PP: bool = true; const DANDELION_PP: bool = true;
const CHECK_NODE_ID: bool = true; const CHECK_NODE_ID: bool = true;
@ -133,7 +134,6 @@ impl Encoder<LevinMessage<Message>> for FragmentCodec {
#[tokio::test] #[tokio::test]
async fn fragmented_handshake_cuprate_to_monerod() { async fn fragmented_handshake_cuprate_to_monerod() {
let (broadcast_tx, _) = broadcast::channel(1); // this isn't actually used in this test.
let semaphore = Arc::new(Semaphore::new(10)); let semaphore = Arc::new(Semaphore::new(10));
let permit = semaphore.acquire_owned().await.unwrap(); let permit = semaphore.acquire_owned().await.unwrap();
@ -141,18 +141,19 @@ async fn fragmented_handshake_cuprate_to_monerod() {
let our_basic_node_data = BasicNodeData { let our_basic_node_data = BasicNodeData {
my_port: 0, my_port: 0,
network_id: Network::Mainnet.network_id().into(), network_id: Network::Mainnet.network_id(),
peer_id: 87980, peer_id: 87980,
support_flags: PeerSupportFlags::from(1_u32), support_flags: PeerSupportFlags::from(1_u32),
rpc_port: 0, rpc_port: 0,
rpc_credits_per_hash: 0, rpc_credits_per_hash: 0,
}; };
let handshaker = HandShaker::<FragNet, _, _, _>::new( let handshaker = HandShaker::<FragNet, _, _, _, _, _>::new(
DummyAddressBook, DummyAddressBook,
DummyPeerSyncSvc,
DummyCoreSyncSvc, DummyCoreSyncSvc,
DummyPeerRequestHandlerSvc, DummyPeerRequestHandlerSvc,
broadcast_tx, |_| futures::stream::pending(),
our_basic_node_data, our_basic_node_data,
); );
@ -172,24 +173,24 @@ async fn fragmented_handshake_cuprate_to_monerod() {
#[tokio::test] #[tokio::test]
async fn fragmented_handshake_monerod_to_cuprate() { async fn fragmented_handshake_monerod_to_cuprate() {
let (broadcast_tx, _) = broadcast::channel(1); // this isn't actually used in this test.
let semaphore = Arc::new(Semaphore::new(10)); let semaphore = Arc::new(Semaphore::new(10));
let permit = semaphore.acquire_owned().await.unwrap(); let permit = semaphore.acquire_owned().await.unwrap();
let our_basic_node_data = BasicNodeData { let our_basic_node_data = BasicNodeData {
my_port: 18081, my_port: 18081,
network_id: Network::Mainnet.network_id().into(), network_id: Network::Mainnet.network_id(),
peer_id: 87980, peer_id: 87980,
support_flags: PeerSupportFlags::from(1_u32), support_flags: PeerSupportFlags::from(1_u32),
rpc_port: 0, rpc_port: 0,
rpc_credits_per_hash: 0, rpc_credits_per_hash: 0,
}; };
let mut handshaker = HandShaker::<FragNet, _, _, _>::new( let mut handshaker = HandShaker::<FragNet, _, _, _, _, _>::new(
DummyAddressBook, DummyAddressBook,
DummyPeerSyncSvc,
DummyCoreSyncSvc, DummyCoreSyncSvc,
DummyPeerRequestHandlerSvc, DummyPeerRequestHandlerSvc,
broadcast_tx, |_| futures::stream::pending(),
our_basic_node_data, our_basic_node_data,
); );

View file

@ -3,7 +3,7 @@ use std::{sync::Arc, time::Duration};
use futures::StreamExt; use futures::StreamExt;
use tokio::{ use tokio::{
io::{duplex, split}, io::{duplex, split},
sync::{broadcast, Semaphore}, sync::Semaphore,
time::timeout, time::timeout,
}; };
use tokio_util::codec::{FramedRead, FramedWrite}; use tokio_util::codec::{FramedRead, FramedWrite};
@ -31,14 +31,13 @@ async fn handshake_cuprate_to_cuprate() {
// Tests a Cuprate <-> Cuprate handshake by making 2 handshake services and making them talk to // Tests a Cuprate <-> Cuprate handshake by making 2 handshake services and making them talk to
// each other. // each other.
let (broadcast_tx, _) = broadcast::channel(1); // this isn't actually used in this test.
let semaphore = Arc::new(Semaphore::new(10)); let semaphore = Arc::new(Semaphore::new(10));
let permit_1 = semaphore.clone().acquire_owned().await.unwrap(); let permit_1 = semaphore.clone().acquire_owned().await.unwrap();
let permit_2 = semaphore.acquire_owned().await.unwrap(); let permit_2 = semaphore.acquire_owned().await.unwrap();
let our_basic_node_data_1 = BasicNodeData { let our_basic_node_data_1 = BasicNodeData {
my_port: 0, my_port: 0,
network_id: Network::Mainnet.network_id().into(), network_id: Network::Mainnet.network_id(),
peer_id: 87980, peer_id: 87980,
// TODO: This fails if the support flags are empty (0) // TODO: This fails if the support flags are empty (0)
support_flags: PeerSupportFlags::from(1_u32), support_flags: PeerSupportFlags::from(1_u32),
@ -49,19 +48,21 @@ async fn handshake_cuprate_to_cuprate() {
let mut our_basic_node_data_2 = our_basic_node_data_1.clone(); let mut our_basic_node_data_2 = our_basic_node_data_1.clone();
our_basic_node_data_2.peer_id = 2344; our_basic_node_data_2.peer_id = 2344;
let mut handshaker_1 = HandShaker::<TestNetZone<true, true, true>, _, _, _>::new( let mut handshaker_1 = HandShaker::<TestNetZone<true, true, true>, _, _, _, _, _>::new(
DummyAddressBook, DummyAddressBook,
DummyPeerSyncSvc,
DummyCoreSyncSvc, DummyCoreSyncSvc,
DummyPeerRequestHandlerSvc, DummyPeerRequestHandlerSvc,
broadcast_tx.clone(), |_| futures::stream::pending(),
our_basic_node_data_1, our_basic_node_data_1,
); );
let mut handshaker_2 = HandShaker::<TestNetZone<true, true, true>, _, _, _>::new( let mut handshaker_2 = HandShaker::<TestNetZone<true, true, true>, _, _, _, _, _>::new(
DummyAddressBook, DummyAddressBook,
DummyPeerSyncSvc,
DummyCoreSyncSvc, DummyCoreSyncSvc,
DummyPeerRequestHandlerSvc, DummyPeerRequestHandlerSvc,
broadcast_tx.clone(), |_| futures::stream::pending(),
our_basic_node_data_2, our_basic_node_data_2,
); );
@ -113,7 +114,6 @@ async fn handshake_cuprate_to_cuprate() {
#[tokio::test] #[tokio::test]
async fn handshake_cuprate_to_monerod() { async fn handshake_cuprate_to_monerod() {
let (broadcast_tx, _) = broadcast::channel(1); // this isn't actually used in this test.
let semaphore = Arc::new(Semaphore::new(10)); let semaphore = Arc::new(Semaphore::new(10));
let permit = semaphore.acquire_owned().await.unwrap(); let permit = semaphore.acquire_owned().await.unwrap();
@ -121,18 +121,19 @@ async fn handshake_cuprate_to_monerod() {
let our_basic_node_data = BasicNodeData { let our_basic_node_data = BasicNodeData {
my_port: 0, my_port: 0,
network_id: Network::Mainnet.network_id().into(), network_id: Network::Mainnet.network_id(),
peer_id: 87980, peer_id: 87980,
support_flags: PeerSupportFlags::from(1_u32), support_flags: PeerSupportFlags::from(1_u32),
rpc_port: 0, rpc_port: 0,
rpc_credits_per_hash: 0, rpc_credits_per_hash: 0,
}; };
let handshaker = HandShaker::<ClearNet, _, _, _>::new( let handshaker = HandShaker::<ClearNet, _, _, _, _, _>::new(
DummyAddressBook, DummyAddressBook,
DummyPeerSyncSvc,
DummyCoreSyncSvc, DummyCoreSyncSvc,
DummyPeerRequestHandlerSvc, DummyPeerRequestHandlerSvc,
broadcast_tx, |_| futures::stream::pending(),
our_basic_node_data, our_basic_node_data,
); );
@ -152,24 +153,24 @@ async fn handshake_cuprate_to_monerod() {
#[tokio::test] #[tokio::test]
async fn handshake_monerod_to_cuprate() { async fn handshake_monerod_to_cuprate() {
let (broadcast_tx, _) = broadcast::channel(1); // this isn't actually used in this test.
let semaphore = Arc::new(Semaphore::new(10)); let semaphore = Arc::new(Semaphore::new(10));
let permit = semaphore.acquire_owned().await.unwrap(); let permit = semaphore.acquire_owned().await.unwrap();
let our_basic_node_data = BasicNodeData { let our_basic_node_data = BasicNodeData {
my_port: 18081, my_port: 18081,
network_id: Network::Mainnet.network_id().into(), network_id: Network::Mainnet.network_id(),
peer_id: 87980, peer_id: 87980,
support_flags: PeerSupportFlags::from(1_u32), support_flags: PeerSupportFlags::from(1_u32),
rpc_port: 0, rpc_port: 0,
rpc_credits_per_hash: 0, rpc_credits_per_hash: 0,
}; };
let mut handshaker = HandShaker::<ClearNet, _, _, _>::new( let mut handshaker = HandShaker::<ClearNet, _, _, _, _, _>::new(
DummyAddressBook, DummyAddressBook,
DummyPeerSyncSvc,
DummyCoreSyncSvc, DummyCoreSyncSvc,
DummyPeerRequestHandlerSvc, DummyPeerRequestHandlerSvc,
broadcast_tx, |_| futures::stream::pending(),
our_basic_node_data, our_basic_node_data,
); );

View file

@ -0,0 +1,78 @@
use std::sync::Arc;
use tokio::sync::Semaphore;
use tower::{Service, ServiceExt};
use cuprate_helper::network::Network;
use monero_wire::{common::PeerSupportFlags, protocol::GetObjectsRequest, BasicNodeData};
use monero_p2p::{
client::{ConnectRequest, Connector, HandShaker},
network_zones::ClearNet,
protocol::{PeerRequest, PeerResponse},
};
use cuprate_test_utils::monerod::monerod;
mod utils;
use utils::*;
#[tokio::test]
async fn get_single_block_from_monerod() {
let semaphore = Arc::new(Semaphore::new(10));
let permit = semaphore.acquire_owned().await.unwrap();
let monerod = monerod(["--out-peers=0"]).await;
let our_basic_node_data = BasicNodeData {
my_port: 0,
network_id: Network::Mainnet.network_id(),
peer_id: 87980,
support_flags: PeerSupportFlags::FLUFFY_BLOCKS,
rpc_port: 0,
rpc_credits_per_hash: 0,
};
let handshaker = HandShaker::<ClearNet, _, _, _, _, _>::new(
DummyAddressBook,
DummyPeerSyncSvc,
DummyCoreSyncSvc,
DummyPeerRequestHandlerSvc,
|_| futures::stream::pending(),
our_basic_node_data,
);
let mut connector = Connector::new(handshaker);
let mut connected_peer = connector
.ready()
.await
.unwrap()
.call(ConnectRequest {
addr: monerod.p2p_addr(),
permit,
})
.await
.unwrap();
let PeerResponse::GetObjects(obj) = connected_peer
.ready()
.await
.unwrap()
.call(PeerRequest::GetObjects(GetObjectsRequest {
blocks: hex::decode("418015bb9ae982a1975da7d79277c2705727a56894ba0fb246adaabb1f4632e3")
.unwrap()
.try_into()
.unwrap(),
pruned: false,
}))
.await
.unwrap()
else {
panic!("Client returned wrong response");
};
assert_eq!(obj.blocks.len(), 1);
assert_eq!(obj.missed_ids.len(), 0);
assert_eq!(obj.current_blockchain_height, 1);
}

View file

@ -10,6 +10,7 @@ use tower::Service;
use monero_p2p::{ use monero_p2p::{
services::{ services::{
AddressBookRequest, AddressBookResponse, CoreSyncDataRequest, CoreSyncDataResponse, AddressBookRequest, AddressBookResponse, CoreSyncDataRequest, CoreSyncDataResponse,
PeerSyncRequest, PeerSyncResponse,
}, },
NetworkZone, PeerRequest, PeerResponse, NetworkZone, PeerRequest, PeerResponse,
}; };
@ -51,11 +52,9 @@ impl Service<CoreSyncDataRequest> for DummyCoreSyncSvc {
Poll::Ready(Ok(())) Poll::Ready(Ok(()))
} }
fn call(&mut self, req: CoreSyncDataRequest) -> Self::Future { fn call(&mut self, _: CoreSyncDataRequest) -> Self::Future {
async move { async move {
match req { Ok(CoreSyncDataResponse(monero_wire::CoreSyncData {
CoreSyncDataRequest::Ours => {
Ok(CoreSyncDataResponse::Ours(monero_wire::CoreSyncData {
cumulative_difficulty: 1, cumulative_difficulty: 1,
cumulative_difficulty_top64: 0, cumulative_difficulty_top64: 0,
current_height: 1, current_height: 1,
@ -69,13 +68,29 @@ impl Service<CoreSyncDataRequest> for DummyCoreSyncSvc {
top_version: 1, top_version: 1,
})) }))
} }
CoreSyncDataRequest::HandleIncoming(_) => Ok(CoreSyncDataResponse::Ok),
}
}
.boxed() .boxed()
} }
} }
#[derive(Clone)]
pub struct DummyPeerSyncSvc;
impl<N: NetworkZone> Service<PeerSyncRequest<N>> for DummyPeerSyncSvc {
type Error = tower::BoxError;
type Future =
Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send + 'static>>;
type Response = PeerSyncResponse<N>;
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, _: PeerSyncRequest<N>) -> Self::Future {
async { Ok(PeerSyncResponse::Ok) }.boxed()
}
}
#[derive(Clone)] #[derive(Clone)]
pub struct DummyPeerRequestHandlerSvc; pub struct DummyPeerRequestHandlerSvc;

View file

@ -31,6 +31,8 @@ impl NetZoneAddress for TestNetZoneAddr {
fn set_port(&mut self, _: u16) {} fn set_port(&mut self, _: u16) {}
fn make_canonical(&mut self) {}
fn ban_id(&self) -> Self::BanID { fn ban_id(&self) -> Self::BanID {
*self *self
} }
@ -74,6 +76,7 @@ impl<const ALLOW_SYNC: bool, const DANDELION_PP: bool, const CHECK_NODE_ID: bool
for TestNetZone<ALLOW_SYNC, DANDELION_PP, CHECK_NODE_ID> for TestNetZone<ALLOW_SYNC, DANDELION_PP, CHECK_NODE_ID>
{ {
const NAME: &'static str = "Testing"; const NAME: &'static str = "Testing";
const SEEDS: &'static [Self::Addr] = &[];
const ALLOW_SYNC: bool = ALLOW_SYNC; const ALLOW_SYNC: bool = ALLOW_SYNC;
const DANDELION_PP: bool = DANDELION_PP; const DANDELION_PP: bool = DANDELION_PP;
const CHECK_NODE_ID: bool = CHECK_NODE_ID; const CHECK_NODE_ID: bool = CHECK_NODE_ID;