Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions protocols/gossipsub/src/behaviour.rs
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,17 @@ where
{
/// Creates a Gossipsub [`Behaviour`] struct given a set of parameters specified via a
/// [`Config`]. This has no subscription filter and uses no compression.
///
/// The default subscription filter wired in by `F::default()` is
/// [`AllowAllSubscriptionFilter`], which imposes no cap on per-RPC or per-peer subscription
/// growth. For deployments that accept connections from untrusted peers, prefer
/// [`Behaviour::new_with_subscription_filter_and_transform`] with
/// [`MaxCountSubscriptionFilter`] or [`WhitelistSubscriptionFilter`]. See the crate root
/// docs for the "Production hardening" section.
///
/// [`AllowAllSubscriptionFilter`]: crate::subscription_filter::AllowAllSubscriptionFilter
/// [`MaxCountSubscriptionFilter`]: crate::subscription_filter::MaxCountSubscriptionFilter
/// [`WhitelistSubscriptionFilter`]: crate::subscription_filter::WhitelistSubscriptionFilter
pub fn new(privacy: MessageAuthenticity, config: Config) -> Result<Self, &'static str> {
Self::new_with_subscription_filter_and_transform(
privacy,
Expand Down
46 changes: 46 additions & 0 deletions protocols/gossipsub/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,52 @@
//! metadata. Peers use control messages to broadcast and request known messages and
//! subscribe/unsubscribe from topics in the mesh network.
//!
//! # Production hardening
//!
//! [`Behaviour::new`] uses the default [`subscription_filter::AllowAllSubscriptionFilter`],
//! which imposes no upper bound on the number of subscription entries decoded per inbound
//! RPC frame or on the number of topics a single peer can accumulate in its per-peer set.
//! This is appropriate for development, integration tests, and closed networks where every
//! connected peer is trusted, but it should not be used for deployments that accept
//! connections from untrusted peers: a single attacker peer can repeatedly subscribe to
//! unique topics and drive the receiver's gossipsub state to grow linearly for the
//! lifetime of its connection.
//!
//! Production deployments should construct [`Behaviour`] with an explicit
//! [`subscription_filter::TopicSubscriptionFilter`] via
//! [`Behaviour::new_with_subscription_filter_and_transform`] (or the
//! `_with_subscription_filter` variant). Two implementations are provided:
//!
//! - [`subscription_filter::WhitelistSubscriptionFilter`] — accepts only a fixed, pre-declared
//! topic set. Recommended when the full topic set is known at construction time (for example,
//! Ethereum 2 consensus clients enumerate the `/eth2/<fork-digest>/<topic>/ssz_snappy` strings
//! before peering).
//! - [`subscription_filter::MaxCountSubscriptionFilter`] — wraps an inner filter with per-RPC and
//! per-peer accumulation caps. Recommended when the topic set is open-ended; choose the cap based
//! on the legitimate topic fan-out of the application plus a comfort margin.
//!
//! A minimal hardened constructor:
//!
//! ```rust,ignore
//! use libp2p::gossipsub::{
//! AllowAllSubscriptionFilter, Behaviour, ConfigBuilder, IdentityTransform,
//! MaxCountSubscriptionFilter, MessageAuthenticity,
//! };
//!
//! let filter = MaxCountSubscriptionFilter {
//! filter: AllowAllSubscriptionFilter::default(),
//! max_subscribed_topics: 16_384,
//! max_subscriptions_per_request: 500,
//! };
//! let behaviour: Behaviour<IdentityTransform, MaxCountSubscriptionFilter<_>> =
//! Behaviour::new_with_subscription_filter_and_transform(
//! MessageAuthenticity::Signed(local_key),
//! ConfigBuilder::default().build()?,
//! filter,
//! IdentityTransform,
//! )?;
//! ```
//!
//! # Important Discrepancies
//!
//! This section outlines the current implementation's potential discrepancies from that of other
Expand Down
57 changes: 54 additions & 3 deletions protocols/gossipsub/src/subscription_filter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,31 @@ pub trait TopicSubscriptionFilter {

// some useful implementers

/// Allows all subscriptions
/// A subscription filter that imposes **no policy**: every topic a peer announces is accepted
/// and recorded in the per-peer subscribed-topics set.
///
/// This is the default filter used by [`crate::Behaviour::new`].
///
/// # When to use
///
/// Suitable for closed networks, integration tests, local development, and deployments where
/// every connected peer is fully trusted. The filter performs no validation and no accounting,
/// which keeps it light-weight for those settings.
///
/// # Production deployments
///
/// In a deployment that accepts connections from untrusted peers, `AllowAllSubscriptionFilter`
/// places no upper bound on:
///
/// * the number of subscription entries decoded per inbound RPC frame, or
/// * the number of topics a single peer can accumulate in its per-peer subscribed-topics set.
///
/// Because each accepted topic costs a heap allocation in the receiver's bookkeeping, a peer
/// that repeatedly subscribes to unique topics can drive the gossipsub state to grow linearly
/// for the lifetime of its connection. Prefer [`MaxCountSubscriptionFilter`] (or
/// [`WhitelistSubscriptionFilter`] if the topic set is known up-front) and wire it through
/// [`crate::Behaviour::new_with_subscription_filter_and_transform`]. See the
/// "Production hardening" section in the crate root for a worked example.
#[derive(Default, Clone)]
pub struct AllowAllSubscriptionFilter {}

Expand All @@ -94,7 +118,12 @@ impl TopicSubscriptionFilter for AllowAllSubscriptionFilter {
}
}

/// Allows only whitelisted subscriptions
/// Accepts subscriptions only for a fixed, pre-declared topic set.
///
/// Recommended for deployments where the full set of valid topics is known at construction time
/// (for example, Ethereum 2 consensus clients enumerate `/eth2/<fork-digest>/<topic>/ssz_snappy`
/// strings before peering). Any inbound subscription for a topic outside the whitelist is
/// dropped before it is recorded against the peer.
#[derive(Default, Clone)]
pub struct WhitelistSubscriptionFilter(pub HashSet<TopicHash>);

Expand All @@ -104,7 +133,29 @@ impl TopicSubscriptionFilter for WhitelistSubscriptionFilter {
}
}

/// Adds a max count to a given subscription filter
/// Wraps another [`TopicSubscriptionFilter`] with per-RPC and per-peer accumulation caps.
///
/// `max_subscriptions_per_request` bounds the number of `Subscribe` / `Unsubscribe` actions
/// that may be applied per inbound RPC frame; `max_subscribed_topics` bounds the cardinality of
/// the per-peer subscribed-topics set after the inner filter has run. Subscriptions that would
/// breach either bound are rejected before the peer's bookkeeping is updated.
///
/// This is the recommended default for production deployments that accept connections from
/// untrusted peers. A common pattern is:
///
/// ```rust,ignore
/// use libp2p::gossipsub::{AllowAllSubscriptionFilter, MaxCountSubscriptionFilter};
///
/// let filter = MaxCountSubscriptionFilter {
/// filter: AllowAllSubscriptionFilter::default(),
/// max_subscribed_topics: 16_384,
/// max_subscriptions_per_request: 500,
/// };
/// ```
///
/// Choose `max_subscribed_topics` to accommodate the legitimate topic fan-out of your
/// application plus a comfort margin (the value above is a generic upper bound; consensus
/// clients with a known topic count typically use a much smaller cap).
pub struct MaxCountSubscriptionFilter<T: TopicSubscriptionFilter> {
pub filter: T,
pub max_subscribed_topics: usize,
Expand Down