use crate::Ingress;
use commonware_cryptography::Signer;
use commonware_runtime::Quota;
use commonware_utils::{NZU32, NZUsize};
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 this configuration across peers in the network (with
/// the exception of `crypto`, `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 {
/// Cryptographic primitives.
pub crypto: C,
/// 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`].
///
/// Sending a larger payload panics. Output from wrappers such as codecs and multiplexers is
/// part of the payload and counts toward this limit.
///
/// 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 to combine into one connection write.
///
/// Set this to `1` to disable batching.
pub send_batch_size: NonZeroUsize,
/// Time into the future that a timestamp can be and still be considered valid.
pub synchrony_bound: Duration,
/// Duration after which a handshake message is considered stale.
pub max_handshake_age: Duration,
/// Timeout for the handshake process.
///
/// This is often set to some value less than the connection read timeout to prevent
/// unauthenticated peers from holding open connection.
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 {
/// Generates a configuration with reasonable defaults for usage in production.
pub fn recommended(
crypto: C,
namespace: &[u8],
listen: SocketAddr,
dialable: impl Into,
bootstrappers: Vec>,
max_peers_per_set: NonZeroUsize,
max_message_size: u32,
) -> Self {
Self {
crypto,
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: NZUsize!(1_000),
send_batch_size: NZUsize!(8),
synchrony_bound: Duration::from_secs(5),
max_handshake_age: Duration::from_secs(10),
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.
pub fn local(
crypto: C,
namespace: &[u8],
listen: SocketAddr,
dialable: impl Into,
bootstrappers: Vec>,
max_peers_per_set: NonZeroUsize,
max_message_size: u32,
) -> Self {
Self {
crypto,
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: NZUsize!(1_000),
send_batch_size: NZUsize!(8),
synchrony_bound: Duration::from_secs(5),
max_handshake_age: Duration::from_secs(10),
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)]
pub fn test(
crypto: C,
listen: SocketAddr,
bootstrappers: Vec>,
max_message_size: u32,
) -> Self {
Self {
crypto,
namespace: b"test_namespace".to_vec(),
listen,
dialable: listen.into(),
bootstrappers,
allow_dns: true,
allow_private_ips: true,
max_message_size,
max_peers_per_set: NZUsize!(32),
mailbox_size: NZUsize!(1_000),
send_batch_size: NZUsize!(8),
synchrony_bound: Duration::from_secs(5),
max_handshake_age: Duration::from_secs(10),
handshake_timeout: Duration::from_secs(5),
dial_timeout: Duration::from_secs(15),
peer_connection_cooldown: Duration::from_millis(250),
max_concurrent_handshakes: NZU32!(1_024),
allowed_handshake_rate_per_ip: Quota::per_second(NZU32!(128)), // 640 concurrent handshakes per IP
allowed_handshake_rate_per_subnet: Quota::per_second(NZU32!(256)),
dial_frequency: Duration::from_millis(200),
dial_fail_limit: 1,
tracked_peer_sets: NZUsize!(4),
gossip_bit_vec_frequency: Duration::from_secs(1),
peer_gossip_max_count: 32,
block_duration: Duration::from_mins(1),
}
}
}