use crate::{Ingress, authenticated::discovery::types}; use commonware_cryptography::PublicKey; #[cfg(test)] use commonware_cryptography::{ChaCha20Poly1305, Signer}; use commonware_runtime::Quota; use commonware_stream::Upgrader; #[cfg(test)] use commonware_stream::{ SakeCups, cups::{self, Cups}, sake::{self, Sake}, }; use commonware_utils::{NZU32, NZUsize, Widen}; use std::{ net::SocketAddr, num::{NonZeroU32, NonZeroUsize}, time::Duration, }; /// Known peer and its accompanying ingress address that will be dialed on startup. pub type Bootstrapper

= (P, Ingress); /// Configuration for the peer-to-peer instance. /// /// # Warning /// It is recommended to synchronize network and handshake settings across peers (with /// the exception of local signing credentials, `listen`, `bootstrappers`, `allow_private_ips`, /// `max_peers_per_set`, `mailbox_size`, `send_batch_size`, and `dial_timeout`). If this is not /// synchronized, connections could be unnecessarily dropped, messages could be parsed /// incorrectly, and/or peers will rate limit each other during normal operation. #[derive(Clone)] pub struct Config where U::PublicKey: PublicKey, { /// Authenticates peers, establishes their message streams, and signs discovery gossip. pub handshake: U, /// Prefix for all signed messages to avoid replay attacks. pub namespace: Vec, /// Address to listen on. pub listen: SocketAddr, /// Dialable ingress address of the peer. pub dialable: Ingress, /// Peers dialed on startup. pub bootstrappers: Vec>, /// Whether or not to allow DNS-based ingress addresses. /// /// When dialing a DNS-based address, the hostname is resolved and a random IP /// is selected from the results (shuffled for each dial attempt). pub allow_dns: bool, /// Whether or not to allow connections with private IP addresses. pub allow_private_ips: bool, /// Maximum size allowed for an application payload passed to a sender. /// /// The largest supported value is [`crate::authenticated::max_size::()`]. Peer discovery /// messages share each connection with application payloads, so the network sizes its frames /// for the larger of this value and the peer discovery message bound. That bound depends on /// [`Config::max_peers_per_set`] and [`Config::peer_gossip_max_count`]. This value alone /// bounds application payloads. /// /// [`crate::max_message_size`] computes this value from the largest payload of each component /// that sends on the network. Output from wrappers such as codecs and multiplexers is part of /// the payload and counts toward this limit. Sending a larger payload panics, and a peer that /// sends a larger payload is disconnected. /// /// Framing and transport overhead are added after this size check and do not count toward /// the limit, so the resulting network message will be larger. pub max_message_size: u32, /// Maximum number of distinct identities at one peer-set index, including the local identity. /// /// This applies to the deduplicated union of a peer set's primary and secondary identities. /// The local identity counts even when omitted from the registered set. Configure only this /// per-set value; do not multiply it or add bootstrappers separately. The network derives its /// retained identity bound by multiplying it by [`Config::tracked_peer_sets`] and adding the /// distinct configured bootstrappers other than the local identity. Tracking an oversized peer /// set panics. /// /// The derived bound sizes every application channel's inbound mailbox and its contribution /// to the shared outbound router mailbox. It also limits primary peer-set bit vectors decoded /// from the network. pub max_peers_per_set: NonZeroUsize, /// Capacity for internal actor mailboxes. /// /// This does not affect inbound application message capacity, which is derived from channel /// rate limits and [`Config::max_peers_per_set`]. pub mailbox_size: NonZeroUsize, /// Maximum number of already-queued outbound messages passed to one /// [`commonware_stream::Sender::send_many`] call. /// /// Set this to `1` to disable batching. pub send_batch_size: NonZeroUsize, /// Maximum time into the future allowed for timestamps in discovery gossip. pub synchrony_bound: Duration, /// Maximum time to authenticate an established connection, including peer admission. /// /// This starts after dialing or accepting the transport connection and bounds how long an /// unauthenticated peer can hold it open. pub handshake_timeout: Duration, /// Timeout for an outbound dial attempt. /// /// This bounds address resolution, connection establishment, and the handshake so a /// peer reservation cannot be held indefinitely. pub dial_timeout: Duration, /// Minimum time between connection reservations for a single peer. pub peer_connection_cooldown: Duration, /// Maximum number of concurrent handshake attempts allowed. pub max_concurrent_handshakes: NonZeroU32, /// Quota for handshake attempts originating from a single IP address. /// /// To cap the number of handshakes concurrently attempted for a single /// IP, set this to [Config::handshake_timeout]. pub allowed_handshake_rate_per_ip: Quota, /// Quota for handshake attempts originating from a single IP subnet. pub allowed_handshake_rate_per_subnet: Quota, /// Average frequency at which we make a single dial attempt across all peers. pub dial_frequency: Duration, /// Times that dialing a given peer should fail before asking for updated peer information for /// that peer. pub dial_fail_limit: usize, /// Number of peer sets to track. /// /// We will attempt to maintain connections to peers stored /// across all peer sets, not just the most recent. This allows /// us to continue serving requests to peers that have recently /// been evicted and/or to communicate with peers in a future /// set (if we, for example, are trying to do a reshare of a threshold /// key). pub tracked_peer_sets: NonZeroUsize, /// Frequency we gossip about known peers. /// /// If there is no other network activity, this message is used as a ping /// and should be sent more often than the read_timeout. /// /// This also determines the rate limit for incoming BitVec and Peers messages /// (one per half this frequency to account for jitter). pub gossip_bit_vec_frequency: Duration, /// Maximum number of peers we will send or consider valid when receiving in a single message. /// /// This is used to prevent malicious peers from sending us a large number of peers at one time (each /// of which requires a signature verification). pub peer_gossip_max_count: usize, /// Duration after which a blocked peer is allowed to reconnect. pub block_duration: Duration, } impl Config where U::PublicKey: PublicKey, { /// Returns an upper bound on the encoded size of a peer discovery message under this /// configuration's [`Config::max_peers_per_set`] and [`Config::peer_gossip_max_count`]. pub(crate) fn max_gossip_size(&self) -> usize { let max_peers_per_set: u64 = Widen::widen(self.max_peers_per_set.get()); types::max_gossip_size::(max_peers_per_set, self.peer_gossip_max_count) } /// Generates a configuration with reasonable defaults for usage in production. #[allow(clippy::too_many_arguments)] pub fn recommended( handshake: U, namespace: &[u8], listen: SocketAddr, dialable: impl Into, bootstrappers: Vec>, max_peers_per_set: NonZeroUsize, max_message_size: u32, mailbox_size: NonZeroUsize, ) -> Self { Self { handshake, namespace: namespace.to_vec(), listen, dialable: dialable.into(), bootstrappers, allow_dns: true, allow_private_ips: false, max_message_size, max_peers_per_set, mailbox_size, send_batch_size: NZUsize!(8), synchrony_bound: Duration::from_secs(5), handshake_timeout: Duration::from_secs(5), dial_timeout: Duration::from_secs(15), peer_connection_cooldown: Duration::from_secs(60), max_concurrent_handshakes: NZU32!(512), allowed_handshake_rate_per_ip: Quota::with_period(Duration::from_secs(5)).unwrap(), // 1 concurrent handshake per IP allowed_handshake_rate_per_subnet: Quota::per_second(NZU32!(64)), dial_frequency: Duration::from_secs(1), dial_fail_limit: 2, tracked_peer_sets: NZUsize!(4), gossip_bit_vec_frequency: Duration::from_secs(50), peer_gossip_max_count: 32, block_duration: Duration::from_hours(4), } } /// Generates a configuration that minimizes peer discovery latency. This /// can be useful when running local demos. /// /// # Warning /// /// It is not recommended to use this configuration in production. #[allow(clippy::too_many_arguments)] pub fn local( handshake: U, namespace: &[u8], listen: SocketAddr, dialable: impl Into, bootstrappers: Vec>, max_peers_per_set: NonZeroUsize, max_message_size: u32, mailbox_size: NonZeroUsize, ) -> Self { Self { handshake, namespace: namespace.to_vec(), listen, dialable: dialable.into(), bootstrappers, allow_dns: true, allow_private_ips: true, max_message_size, max_peers_per_set, mailbox_size, send_batch_size: NZUsize!(8), synchrony_bound: Duration::from_secs(5), handshake_timeout: Duration::from_secs(5), dial_timeout: Duration::from_secs(15), peer_connection_cooldown: Duration::from_secs(1), max_concurrent_handshakes: NZU32!(1_024), allowed_handshake_rate_per_ip: Quota::per_second(NZU32!(16)), // 80 concurrent handshakes per IP allowed_handshake_rate_per_subnet: Quota::per_second(NZU32!(128)), dial_frequency: Duration::from_millis(500), dial_fail_limit: 1, tracked_peer_sets: NZUsize!(4), gossip_bit_vec_frequency: Duration::from_secs(5), peer_gossip_max_count: 32, block_duration: Duration::from_hours(1), } } } #[cfg(test)] impl Config> { pub fn test( signer: C, listen: SocketAddr, bootstrappers: Vec>, max_message_size: u32, ) -> Self { let mut config = Self::local( Cups::<_, ChaCha20Poly1305>::new( Sake { signer, synchrony_bound: Duration::from_secs(5), max_handshake_age: Duration::from_secs(10), version: sake::Version::V1, }, cups::Version::V1, ), b"test_namespace", listen, listen, bootstrappers, NZUsize!(32), max_message_size, NZUsize!(1_000), ); config.peer_connection_cooldown = Duration::from_millis(250); config.allowed_handshake_rate_per_ip = Quota::per_second(NZU32!(128)); config.allowed_handshake_rate_per_subnet = Quota::per_second(NZU32!(256)); config.dial_frequency = Duration::from_millis(200); config.gossip_bit_vec_frequency = Duration::from_secs(1); config.block_duration = Duration::from_mins(1); config } }