Skip to main content

ab_networking/
constructor.rs

1pub(crate) mod temporary_bans;
2mod transport;
3
4use crate::behavior::persistent_parameters::{KnownPeersRegistry, StubNetworkingParametersManager};
5use crate::behavior::{Behavior, BehaviorConfig};
6use crate::constructor::temporary_bans::TemporaryBans;
7use crate::constructor::transport::build_transport;
8use crate::node::Node;
9use crate::node_runner::{NodeRunner, NodeRunnerConfig};
10use crate::protocols::autonat_wrapper::Config as AutonatWrapperConfig;
11use crate::protocols::request_response::request_response_factory::RequestHandler;
12use crate::protocols::reserved_peers::Config as ReservedPeersConfig;
13use crate::shared::Shared;
14use crate::utils::rate_limiter::RateLimiter;
15use crate::utils::{SubspaceMetrics, strip_peer_id};
16use ab_core_primitives::pieces::Piece;
17use backon::ExponentialBuilder;
18use futures::channel::mpsc;
19use libp2p::autonat::Config as AutonatConfig;
20use libp2p::connection_limits::ConnectionLimits;
21use libp2p::gossipsub::{
22    Config as GossipsubConfig, ConfigBuilder as GossipsubConfigBuilder,
23    Message as GossipsubMessage, MessageId, ValidationMode,
24};
25use libp2p::identify::Config as IdentifyConfig;
26use libp2p::kad::store::RecordStore;
27use libp2p::kad::{
28    BucketInserts, Config as KademliaConfig, Mode, ProviderRecord, Record, RecordKey, StoreInserts,
29    store,
30};
31use libp2p::metrics::Metrics;
32use libp2p::multiaddr::Protocol;
33use libp2p::yamux::Config as YamuxConfig;
34use libp2p::{Multiaddr, PeerId, StreamProtocol, SwarmBuilder, TransportError, identity};
35use parking_lot::Mutex;
36use prometheus_client::registry::Registry;
37use std::borrow::Cow;
38use std::iter::Empty;
39use std::sync::Arc as StdArc;
40use std::time::Duration;
41use std::{fmt, io, iter};
42use thiserror::Error;
43use tracing::{debug, info};
44use triomphe::Arc;
45
46const DEFAULT_NETWORK_PROTOCOL_VERSION: &str = "dev";
47const KADEMLIA_PROTOCOL: &str = "/subspace/kad/0.1.0";
48const GOSSIPSUB_PROTOCOL_PREFIX: &str = "subspace/gossipsub";
49
50/// Defines max_negotiating_inbound_streams constant for the swarm.
51/// It must be set for large plots.
52const SWARM_MAX_NEGOTIATING_INBOUND_STREAMS: usize = 100_000;
53/// How long will connection be allowed to be open without any usage
54const IDLE_CONNECTION_TIMEOUT: Duration = Duration::from_secs(3);
55/// The default maximum established incoming connection number for the swarm.
56const SWARM_MAX_ESTABLISHED_INCOMING_CONNECTIONS: u32 = 100;
57/// The default maximum established incoming connection number for the swarm.
58const SWARM_MAX_ESTABLISHED_OUTGOING_CONNECTIONS: u32 = 100;
59/// The default maximum pending incoming connection number for the swarm.
60const SWARM_MAX_PENDING_INCOMING_CONNECTIONS: u32 = 80;
61/// The default maximum pending incoming connection number for the swarm.
62const SWARM_MAX_PENDING_OUTGOING_CONNECTIONS: u32 = 80;
63const KADEMLIA_QUERY_TIMEOUT: Duration = Duration::from_secs(40);
64const SWARM_MAX_ESTABLISHED_CONNECTIONS_PER_PEER: u32 = 3;
65const MAX_CONCURRENT_STREAMS_PER_CONNECTION: usize = 10;
66// TODO: Consider moving this constant to configuration or removing `Toggle` wrapper when we find a
67//  use-case for gossipsub protocol.
68const ENABLE_GOSSIP_PROTOCOL: bool = false;
69
70const TEMPORARY_BANS_CACHE_SIZE: u32 = 10_000;
71const TEMPORARY_BANS_DEFAULT_BACKOFF_INITIAL_INTERVAL: Duration = Duration::from_secs(5);
72const TEMPORARY_BANS_DEFAULT_BACKOFF_MULTIPLIER: f64 = 1.5;
73const TEMPORARY_BANS_DEFAULT_MAX_INTERVAL: Duration = Duration::from_mins(30);
74
75/// We pause between reserved peers dialing otherwise we could do multiple dials to offline peers
76/// wasting resources and producing a ton of log records.
77const DIALING_INTERVAL_IN_SECS: Duration = Duration::from_secs(1);
78
79/// Specific YAMUX settings for Subspace applications: additional buffer space for pieces and
80/// substream's limit.
81///
82/// Defines a replication factor for Kademlia on get_record operation.
83/// "Good citizen" supports the network health.
84const YAMUX_MAX_STREAMS: usize = 256;
85
86/// Max confidence for autonat protocol. Could affect Kademlia mode change.
87pub(crate) const AUTONAT_MAX_CONFIDENCE: usize = 3;
88/// We set a very long pause before autonat initialization (Duration::Max panics).
89const AUTONAT_SERVER_PROBE_DELAY: Duration = Duration::from_days(365);
90
91/// Defines Kademlia mode
92#[derive(Clone, Debug)]
93pub enum KademliaMode {
94    /// The Kademlia mode is static for the duration of the application.
95    Static(Mode),
96    /// Kademlia mode will be changed using Autonat protocol when max confidence reached.
97    Dynamic,
98}
99
100impl KademliaMode {
101    /// Returns true if the mode is Dynamic.
102    pub fn is_dynamic(&self) -> bool {
103        matches!(self, Self::Dynamic)
104    }
105
106    /// Returns true if the mode is Static.
107    pub fn is_static(&self) -> bool {
108        matches!(self, Self::Static(..))
109    }
110}
111
112pub(crate) struct DummyRecordStore;
113
114impl RecordStore for DummyRecordStore {
115    type RecordsIter<'a>
116        = Empty<Cow<'a, Record>>
117    where
118        Self: 'a;
119    type ProvidedIter<'a>
120        = Empty<Cow<'a, ProviderRecord>>
121    where
122        Self: 'a;
123
124    #[inline]
125    fn get(&self, _key: &RecordKey) -> Option<Cow<'_, Record>> {
126        // Not supported
127        None
128    }
129
130    #[inline]
131    fn put(&mut self, _record: Record) -> store::Result<()> {
132        // Not supported
133        Ok(())
134    }
135
136    #[inline]
137    fn remove(&mut self, _key: &RecordKey) {
138        // Not supported
139    }
140
141    #[inline]
142    fn records(&self) -> Self::RecordsIter<'_> {
143        // Not supported
144        iter::empty()
145    }
146
147    #[inline]
148    fn add_provider(&mut self, _record: ProviderRecord) -> store::Result<()> {
149        // Not supported
150        Ok(())
151    }
152
153    #[inline]
154    fn providers(&self, _key: &RecordKey) -> Vec<ProviderRecord> {
155        // Not supported
156        Vec::new()
157    }
158
159    #[inline]
160    fn provided(&self) -> Self::ProvidedIter<'_> {
161        // Not supported
162        iter::empty()
163    }
164
165    #[inline]
166    fn remove_provider(&mut self, _key: &RecordKey, _provider: &PeerId) {
167        // Not supported
168    }
169}
170
171/// [`Node`] configuration.
172pub struct Config {
173    /// Identity keypair of a node used for authenticated connections.
174    pub keypair: identity::Keypair,
175    /// List of [`Multiaddr`] on which to listen for incoming connections.
176    pub listen_on: Vec<Multiaddr>,
177    /// Fallback to random port if specified (or default) port is already occupied.
178    pub listen_on_fallback_to_random_port: bool,
179    /// Adds a timeout to the setup and protocol upgrade process for all inbound and outbound
180    /// connections established through the transport.
181    pub timeout: Duration,
182    /// The configuration for the Identify behaviour.
183    pub identify: IdentifyConfig,
184    /// The configuration for the Kademlia behaviour.
185    pub kademlia: KademliaConfig,
186    /// The configuration for the Gossip behaviour.
187    pub gossipsub: Option<GossipsubConfig>,
188    /// Yamux multiplexing configuration.
189    pub yamux_config: YamuxConfig,
190    /// Should non-global addresses be added to the DHT?
191    pub allow_non_global_addresses_in_dht: bool,
192    /// How frequently should random queries be done using Kademlia DHT to populate routing table.
193    pub initial_random_query_interval: Duration,
194    /// A reference to the `NetworkingParametersRegistry` implementation.
195    pub known_peers_registry: Box<dyn KnownPeersRegistry>,
196    /// The configuration for the `RequestResponsesBehaviour` protocol.
197    pub request_response_protocols: Vec<Box<dyn RequestHandler>>,
198    /// Defines set of peers with a permanent connection (and reconnection if necessary).
199    pub reserved_peers: Vec<Multiaddr>,
200    /// Established incoming swarm connection limit.
201    pub max_established_incoming_connections: u32,
202    /// Established outgoing swarm connection limit.
203    pub max_established_outgoing_connections: u32,
204    /// Pending incoming swarm connection limit.
205    pub max_pending_incoming_connections: u32,
206    /// Pending outgoing swarm connection limit.
207    pub max_pending_outgoing_connections: u32,
208    /// How many temporarily banned unreachable peers to keep in memory.
209    pub temporary_bans_cache_size: u32,
210    /// Backoff policy for temporary banning of unreachable peers.
211    pub temporary_ban_backoff: ExponentialBuilder,
212    /// Optional libp2p prometheus metrics. None will disable metrics gathering.
213    pub libp2p_metrics: Option<Metrics>,
214    /// Internal prometheus metrics. None will disable metrics gathering.
215    pub metrics: Option<SubspaceMetrics>,
216    /// Defines protocol version for the network peers. Affects network partition.
217    pub protocol_version: String,
218    /// Addresses to bootstrap Kademlia network
219    pub bootstrap_addresses: Vec<Multiaddr>,
220    /// Kademlia mode. The default value is set to Static(Client). The peer won't add its address
221    /// to other peers` Kademlia routing table. Changing this behaviour implies that a peer can
222    /// provide pieces to others.
223    pub kademlia_mode: KademliaMode,
224    /// Known external addresses to the local peer. The addresses will be added on the swarm start
225    /// and enable peer to notify others about its reachable address.
226    pub external_addresses: Vec<Multiaddr>,
227}
228
229impl fmt::Debug for Config {
230    #[inline]
231    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
232        f.debug_struct("Config").finish()
233    }
234}
235
236/// This default can only be used for `dev` networks.
237/// Other networks should use `Config::new()` to apply the correct prefix to the protocol version.
238impl Default for Config {
239    #[inline]
240    fn default() -> Self {
241        let ed25519_keypair = identity::ed25519::Keypair::generate();
242        let keypair = identity::Keypair::from(ed25519_keypair);
243
244        Self::new(DEFAULT_NETWORK_PROTOCOL_VERSION.to_string(), keypair, None)
245    }
246}
247
248impl Config {
249    /// Creates a new [`Config`].
250    /// Applies a subspace-specific version prefix to the `protocol_version`.
251    pub fn new(
252        protocol_version: String,
253        keypair: identity::Keypair,
254        prometheus_registry: Option<&mut Registry>,
255    ) -> Self {
256        let (libp2p_metrics, metrics) = prometheus_registry.map_or((None, None), |registry| {
257            (
258                Some(Metrics::new(registry)),
259                Some(SubspaceMetrics::new(registry)),
260            )
261        });
262
263        let mut kademlia = KademliaConfig::new(
264            StreamProtocol::try_from_owned(KADEMLIA_PROTOCOL.to_owned())
265                .expect("Manual protocol name creation."),
266        );
267        kademlia
268            .set_query_timeout(KADEMLIA_QUERY_TIMEOUT)
269            .disjoint_query_paths(true)
270            .set_max_packet_size(2 * Piece::SIZE)
271            .set_kbucket_inserts(BucketInserts::Manual)
272            .set_record_filtering(StoreInserts::FilterBoth)
273            // We don't use records and providers publication.
274            .set_provider_record_ttl(None)
275            .set_provider_publication_interval(None)
276            .set_record_ttl(None)
277            .set_replication_interval(None);
278
279        let mut yamux_config = YamuxConfig::default();
280        yamux_config.set_max_num_streams(YAMUX_MAX_STREAMS);
281
282        let gossipsub = ENABLE_GOSSIP_PROTOCOL.then(|| {
283            GossipsubConfigBuilder::default()
284                .protocol_id_prefix(GOSSIPSUB_PROTOCOL_PREFIX)
285                // TODO: Do we want message signing?
286                .validation_mode(ValidationMode::None)
287                // To content-address message, we can take the hash of message and use it as an ID.
288                .message_id_fn(|message: &GossipsubMessage| {
289                    MessageId::from(blake3::hash(&message.data).as_bytes())
290                })
291                .max_transmit_size(2 * 1024 * 1024) // 2MB
292                .build()
293                .expect("Default config for gossipsub is always correct; qed")
294        });
295
296        let protocol_version = format!("/subspace/2/{protocol_version}");
297        let identify = IdentifyConfig::new(protocol_version.clone(), keypair.public());
298
299        let temporary_ban_backoff = ExponentialBuilder::default()
300            .with_factor(TEMPORARY_BANS_DEFAULT_BACKOFF_MULTIPLIER as f32)
301            .with_min_delay(TEMPORARY_BANS_DEFAULT_BACKOFF_INITIAL_INTERVAL)
302            .with_max_delay(TEMPORARY_BANS_DEFAULT_MAX_INTERVAL)
303            .without_max_times();
304
305        Self {
306            keypair,
307            listen_on: vec![],
308            listen_on_fallback_to_random_port: true,
309            timeout: Duration::from_secs(10),
310            identify,
311            kademlia,
312            gossipsub,
313            allow_non_global_addresses_in_dht: false,
314            initial_random_query_interval: Duration::from_secs(1),
315            known_peers_registry: StubNetworkingParametersManager.boxed(),
316            request_response_protocols: Vec::new(),
317            yamux_config,
318            reserved_peers: Vec::new(),
319            max_established_incoming_connections: SWARM_MAX_ESTABLISHED_INCOMING_CONNECTIONS,
320            max_established_outgoing_connections: SWARM_MAX_ESTABLISHED_OUTGOING_CONNECTIONS,
321            max_pending_incoming_connections: SWARM_MAX_PENDING_INCOMING_CONNECTIONS,
322            max_pending_outgoing_connections: SWARM_MAX_PENDING_OUTGOING_CONNECTIONS,
323            temporary_bans_cache_size: TEMPORARY_BANS_CACHE_SIZE,
324            temporary_ban_backoff,
325            libp2p_metrics,
326            metrics,
327            protocol_version,
328            bootstrap_addresses: Vec::new(),
329            kademlia_mode: KademliaMode::Static(Mode::Client),
330            external_addresses: Vec::new(),
331        }
332    }
333}
334
335/// Errors that might happen during network creation.
336#[derive(Debug, Error)]
337pub enum CreationError {
338    /// Circuit relay client error.
339    #[error("Expected relay server node.")]
340    RelayServerExpected,
341    /// I/O error.
342    #[error("I/O error: {0}")]
343    Io(#[from] io::Error),
344    /// Transport creation error.
345    #[error("Transport creation error: {0}")]
346    // TODO: Restore `#[from] TransportError` once https://github.com/libp2p/rust-libp2p/issues/4824
347    //  is resolved
348    TransportCreationError(Box<dyn std::error::Error + Send + Sync>),
349    /// Transport error when attempting to listen on multiaddr.
350    #[error("Transport error when attempting to listen on multiaddr: {0}")]
351    TransportError(#[from] TransportError<io::Error>),
352}
353
354/// Converts public key from keypair to PeerId.
355/// It serves as the shared PeerId generating algorithm.
356pub fn peer_id(keypair: &identity::Keypair) -> PeerId {
357    keypair.public().to_peer_id()
358}
359
360/// Create a new network node and node runner instances.
361pub fn construct(config: Config) -> Result<(Node, NodeRunner), CreationError> {
362    let Config {
363        keypair,
364        listen_on,
365        listen_on_fallback_to_random_port,
366        timeout,
367        identify,
368        kademlia,
369        gossipsub,
370        yamux_config,
371        allow_non_global_addresses_in_dht,
372        initial_random_query_interval,
373        known_peers_registry,
374        request_response_protocols,
375        reserved_peers,
376        max_established_incoming_connections,
377        max_established_outgoing_connections,
378        max_pending_incoming_connections,
379        max_pending_outgoing_connections,
380        temporary_bans_cache_size,
381        temporary_ban_backoff,
382        libp2p_metrics,
383        metrics,
384        protocol_version,
385        bootstrap_addresses,
386        kademlia_mode,
387        external_addresses,
388    } = config;
389    let local_peer_id = peer_id(&keypair);
390
391    info!(
392        %allow_non_global_addresses_in_dht,
393        peer_id = %local_peer_id,
394        %protocol_version,
395        "DSN instance configured."
396    );
397
398    let connection_limits = ConnectionLimits::default()
399        .with_max_established_per_peer(Some(SWARM_MAX_ESTABLISHED_CONNECTIONS_PER_PEER))
400        .with_max_pending_incoming(Some(max_pending_incoming_connections))
401        .with_max_pending_outgoing(Some(max_pending_outgoing_connections))
402        .with_max_established_incoming(Some(max_established_incoming_connections))
403        .with_max_established_outgoing(Some(max_established_outgoing_connections));
404
405    debug!(?connection_limits, "DSN connection limits set.");
406
407    let autonat_boot_delay = if kademlia_mode.is_static() || !external_addresses.is_empty() {
408        AUTONAT_SERVER_PROBE_DELAY
409    } else {
410        AutonatConfig::default().boot_delay
411    };
412
413    debug!(
414        ?autonat_boot_delay,
415        ?kademlia_mode,
416        ?external_addresses,
417        "Autonat boot delay set."
418    );
419
420    let mut behaviour = Behavior::new(BehaviorConfig {
421        peer_id: local_peer_id,
422        identify,
423        kademlia,
424        gossipsub,
425        request_response_protocols,
426        request_response_max_concurrent_streams: {
427            let max_num_connections = max_established_incoming_connections as usize
428                + max_established_outgoing_connections as usize;
429            max_num_connections * MAX_CONCURRENT_STREAMS_PER_CONNECTION
430        },
431        connection_limits,
432        reserved_peers: ReservedPeersConfig {
433            reserved_peers: reserved_peers.clone(),
434            dialing_interval: DIALING_INTERVAL_IN_SECS,
435        },
436        autonat: AutonatWrapperConfig {
437            inner_config: AutonatConfig {
438                use_connected: true,
439                only_global_ips: !config.allow_non_global_addresses_in_dht,
440                confidence_max: AUTONAT_MAX_CONFIDENCE,
441                boot_delay: autonat_boot_delay,
442                ..Default::default()
443            },
444            local_peer_id,
445            servers: bootstrap_addresses.clone(),
446        },
447    });
448
449    match (kademlia_mode, external_addresses.is_empty()) {
450        (KademliaMode::Static(mode), _) => {
451            behaviour.kademlia.set_mode(Some(mode));
452        }
453        (KademliaMode::Dynamic, false) => {
454            behaviour.kademlia.set_mode(Some(Mode::Server));
455        }
456        _ => {
457            // Autonat will figure it out
458        }
459    }
460
461    let temporary_bans = Arc::new(Mutex::new(TemporaryBans::new(
462        temporary_bans_cache_size,
463        temporary_ban_backoff,
464    )));
465
466    let mut swarm = SwarmBuilder::with_existing_identity(keypair)
467        .with_tokio()
468        .with_other_transport(|keypair| {
469            Ok(build_transport(
470                allow_non_global_addresses_in_dht,
471                keypair,
472                Arc::clone(&temporary_bans),
473                timeout,
474                yamux_config,
475            )?)
476        })
477        .map_err(|error| CreationError::TransportCreationError(error.into()))?
478        .with_behaviour(move |_keypair| Ok(behaviour))
479        .expect("Not fallible; qed")
480        .with_swarm_config(|config| {
481            config
482                .with_max_negotiating_inbound_streams(SWARM_MAX_NEGOTIATING_INBOUND_STREAMS)
483                .with_idle_connection_timeout(IDLE_CONNECTION_TIMEOUT)
484        })
485        .build();
486
487    let is_listening = !listen_on.is_empty();
488
489    // Setup listen_on addresses
490    for mut addr in listen_on {
491        if let Err(error) = swarm.listen_on(addr.clone()) {
492            if !listen_on_fallback_to_random_port {
493                return Err(error.into());
494            }
495
496            let addr_string = addr.to_string();
497            // Listen on random port if specified is already occupied
498            if let Some(Protocol::Tcp(_port)) = addr.pop() {
499                info!("Failed to listen on {addr_string} ({error}), falling back to random port");
500                addr.push(Protocol::Tcp(0));
501                swarm.listen_on(addr)?;
502            }
503        }
504    }
505
506    // Setup external addresses
507    for addr in external_addresses.iter().cloned() {
508        info!("DSN external address added: {addr}");
509        swarm.add_external_address(addr);
510    }
511
512    // Create final structs
513    let (command_sender, command_receiver) = mpsc::channel(1);
514
515    let rate_limiter = RateLimiter::new(
516        max_established_outgoing_connections,
517        max_pending_outgoing_connections,
518    );
519
520    let shared = StdArc::new(Shared::new(local_peer_id, command_sender, rate_limiter));
521    let shared_weak = StdArc::downgrade(&shared);
522
523    let node = Node::new(shared);
524    let node_runner = NodeRunner::new(NodeRunnerConfig {
525        allow_non_global_addresses_in_dht,
526        is_listening,
527        command_receiver,
528        swarm,
529        shared_weak,
530        next_random_query_interval: initial_random_query_interval,
531        known_peers_registry,
532        reserved_peers: strip_peer_id(reserved_peers).into_iter().collect(),
533        temporary_bans,
534        libp2p_metrics,
535        metrics,
536        protocol_version,
537        bootstrap_addresses,
538    });
539
540    Ok((node, node_runner))
541}