p2p/
p2p_config.rs

1use std::{collections::BTreeSet, net::IpAddr, time::Duration};
2
3use serde::{Deserialize, Serialize};
4
5use crate::{
6    channels::ChannelId, connection::outgoing::P2pConnectionOutgoingInitOpts, identity::PublicKey,
7};
8
9pub const DEVNET_SEEDS: &[&str] = &[
10    "/dns4/seed-1.devnet.gcp.o1test.net/tcp/10003/p2p/12D3KooWAdgYL6hv18M3iDBdaK1dRygPivSfAfBNDzie6YqydVbs",
11    "/dns4/seed-2.devnet.gcp.o1test.net/tcp/10003/p2p/12D3KooWLjs54xHzVmMmGYb7W5RVibqbwD1co7M2ZMfPgPm7iAag",
12    "/dns4/seed-3.devnet.gcp.o1test.net/tcp/10003/p2p/12D3KooWEiGVAFC7curXWXiGZyMWnZK9h8BKr88U8D5PKV3dXciv",
13];
14
15#[derive(Serialize, Deserialize, Debug, Clone)]
16pub struct P2pConfig {
17    /// TCP port where libp2p is listening incoming connections.
18    pub libp2p_port: Option<u16>,
19    /// The HTTP port where signaling server is listening SDP offers and SDP answers.
20    pub listen_port: Option<u16>,
21    /// The public key used for authentication all p2p communication.
22    pub identity_pub_key: PublicKey,
23    /// A list addresses of seed nodes.
24    pub initial_peers: Vec<P2pConnectionOutgoingInitOpts>,
25    /// External addresses
26    pub external_addrs: Vec<IpAddr>,
27
28    pub enabled_channels: BTreeSet<ChannelId>,
29
30    pub timeouts: P2pTimeouts,
31
32    pub limits: P2pLimits,
33
34    /// Use peers discovery.
35    pub peer_discovery: bool,
36
37    pub meshsub: P2pMeshsubConfig,
38}
39
40#[derive(Serialize, Deserialize, Debug, Clone)]
41pub struct P2pMeshsubConfig {
42    /// Unix time. Used as an initial nonce for pubsub.
43    pub initial_time: Duration,
44
45    pub outbound_degree_desired: usize,
46    pub outbound_degree_low: usize,
47    pub outbound_degree_high: usize,
48    pub mcache_len: usize,
49}
50
51impl Default for P2pMeshsubConfig {
52    fn default() -> Self {
53        P2pMeshsubConfig {
54            initial_time: Duration::ZERO,
55            outbound_degree_desired: 6,
56            outbound_degree_low: 4,
57            outbound_degree_high: 12,
58            mcache_len: 256,
59        }
60    }
61}
62
63#[derive(Serialize, Deserialize, Debug, Clone)]
64pub struct P2pTimeouts {
65    pub incoming_connection_timeout: Option<Duration>,
66    pub outgoing_connection_timeout: Option<Duration>,
67    pub reconnect_timeout: Option<Duration>,
68    pub incoming_error_reconnect_timeout: Option<Duration>,
69    pub outgoing_error_reconnect_timeout: Option<Duration>,
70    pub best_tip_with_proof: Option<Duration>,
71    pub ledger_query: Option<Duration>,
72    pub staged_ledger_aux_and_pending_coinbases_at_block: Option<Duration>,
73    pub block: Option<Duration>,
74    pub snark: Option<Duration>,
75    pub transaction: Option<Duration>,
76    pub initial_peers: Option<Duration>,
77    pub kademlia_bootstrap: Option<Duration>,
78    pub kademlia_initial_bootstrap: Option<Duration>,
79    pub select: Option<Duration>,
80    pub pnet: Option<Duration>,
81}
82
83fn from_env_or(name: &str, default: Option<Duration>) -> Option<Duration> {
84    None.or_else(|| {
85        let val = std::env::var(name).ok()?.to_ascii_lowercase();
86        Some(match val.as_ref() {
87            "none" => None,
88            s => Some(Duration::from_secs(s.parse().ok()?)),
89        })
90    })
91    .unwrap_or(default)
92}
93
94impl Default for P2pTimeouts {
95    fn default() -> Self {
96        Self {
97            incoming_connection_timeout: from_env_or(
98                "INCOMING_CONNECTION_TIMEOUT",
99                Some(Duration::from_secs(30)),
100            ),
101            outgoing_connection_timeout: from_env_or(
102                "OUTGOING_CONNECTION_TIMEOUT",
103                Some(Duration::from_secs(15)),
104            ),
105            reconnect_timeout: from_env_or("RECONNECT_TIMEOUT", Some(Duration::from_secs(30))),
106            incoming_error_reconnect_timeout: from_env_or(
107                "INCOMING_ERROR_RECONNECT_TIMEOUT",
108                Some(Duration::from_secs(30)),
109            ),
110            outgoing_error_reconnect_timeout: from_env_or(
111                "OUTGOING_ERROR_RECONNECT_TIMEOUT",
112                Some(Duration::from_secs(30)),
113            ),
114            best_tip_with_proof: from_env_or(
115                "BEST_TIP_WITH_PROOF_TIMEOUT",
116                Some(Duration::from_secs(15)),
117            ),
118            ledger_query: from_env_or("LEDGER_QUERY_TIMEOUT", Some(Duration::from_secs(4))),
119            staged_ledger_aux_and_pending_coinbases_at_block: from_env_or(
120                "STAGED_LEDGER_AUX_AND_PENDING_COINBASES_AT_BLOCK_TIMEOUT",
121                Some(Duration::from_secs(180)),
122            ),
123            block: from_env_or("BLOCK_TIMEOUT", Some(Duration::from_secs(8))),
124            snark: from_env_or("SNARK_TIMEOUT", Some(Duration::from_secs(8))),
125            transaction: from_env_or("TRANSACTION_TIMEOUT", Some(Duration::from_secs(8))),
126            initial_peers: from_env_or("INITIAL_PEERS_TIMEOUT", Some(Duration::from_secs(5))),
127            kademlia_bootstrap: from_env_or(
128                "KADEMLIA_BOOTSTRAP_TIMEOUT",
129                Some(Duration::from_secs(60)),
130            ),
131            kademlia_initial_bootstrap: from_env_or(
132                "KADEMLIA_INITIAL_BOOTSTRAP_TIMEOUT",
133                Some(Duration::from_secs(5)),
134            ),
135            select: from_env_or("SELECT_TIMEOUT", Some(Duration::from_secs(5))),
136            pnet: from_env_or("PNET_TIMEOUT", Some(Duration::from_secs(2))),
137        }
138    }
139}
140
141impl P2pTimeouts {
142    pub fn without_rpc() -> Self {
143        Self {
144            best_tip_with_proof: None,
145            ledger_query: None,
146            staged_ledger_aux_and_pending_coinbases_at_block: None,
147            block: None,
148            snark: None,
149            ..Default::default()
150        }
151    }
152}
153
154#[derive(Debug, Clone, Copy, Serialize, Deserialize, derive_more::Display, Default)]
155pub enum Limit<T> {
156    #[display(fmt = "{}", _0)]
157    Some(T),
158    #[default]
159    #[display(fmt = "unlimited")]
160    Unlimited,
161}
162
163impl<T> Limit<T> {
164    pub fn map<F, O>(self, f: F) -> Limit<O>
165    where
166        F: FnOnce(T) -> O,
167    {
168        match self {
169            Limit::Some(t) => Limit::Some(f(t)),
170            Limit::Unlimited => Limit::Unlimited,
171        }
172    }
173}
174
175macro_rules! impls {
176    ($ty:ty) => {
177        impl From<Option<$ty>> for Limit<$ty> {
178            fn from(value: Option<$ty>) -> Self {
179                match value {
180                    Some(v) => Limit::Some(v),
181                    None => Limit::Unlimited,
182                }
183            }
184        }
185
186        impl From<Limit<$ty>> for Option<$ty> {
187            fn from(value: Limit<$ty>) -> Self {
188                match value {
189                    Limit::Some(v) => Some(v),
190                    Limit::Unlimited => None,
191                }
192            }
193        }
194
195        impl std::cmp::PartialEq<$ty> for Limit<$ty> {
196            fn eq(&self, other: &$ty) -> bool {
197                match self {
198                    Limit::Some(v) => v.eq(other),
199                    Limit::Unlimited => false,
200                }
201            }
202        }
203
204        impl std::cmp::PartialEq<Limit<$ty>> for $ty {
205            fn eq(&self, other: &Limit<$ty>) -> bool {
206                match other {
207                    Limit::Some(other) => self.eq(other),
208                    Limit::Unlimited => false,
209                }
210            }
211        }
212
213        impl std::cmp::PartialEq<Limit<$ty>> for Limit<$ty> {
214            fn eq(&self, other: &Limit<$ty>) -> bool {
215                match (self, other) {
216                    (Limit::Some(this), Limit::Some(other)) => this.eq(other),
217                    (Limit::Unlimited, Limit::Unlimited) => true,
218                    _ => false,
219                }
220            }
221        }
222
223        impl std::cmp::Eq for Limit<$ty> {}
224
225        impl std::cmp::PartialOrd<$ty> for Limit<$ty> {
226            fn partial_cmp(&self, other: &$ty) -> Option<std::cmp::Ordering> {
227                match self {
228                    Limit::Some(v) => v.partial_cmp(other),
229                    Limit::Unlimited => Some(std::cmp::Ordering::Greater),
230                }
231            }
232        }
233
234        impl std::cmp::PartialOrd<Limit<$ty>> for $ty {
235            fn partial_cmp(&self, other: &Limit<$ty>) -> Option<std::cmp::Ordering> {
236                match other {
237                    Limit::Some(other) => self.partial_cmp(other),
238                    Limit::Unlimited => Some(std::cmp::Ordering::Less),
239                }
240            }
241        }
242    };
243}
244
245impls!(usize);
246impls!(std::time::Duration);
247
248#[derive(Debug, Clone, Serialize, Deserialize, Copy)]
249pub struct P2pLimits {
250    max_peers: Limit<usize>,
251    min_peers_in_state: Limit<usize>,
252    max_peers_in_state: Limit<usize>,
253    max_streams: Limit<usize>,
254    yamux_message_size: Limit<usize>,
255    yamux_pending_outgoing_per_peer: Limit<usize>,
256
257    identify_message: Limit<usize>,
258    kademlia_request: Limit<usize>,
259    kademlia_response: Limit<usize>,
260
261    rpc_service_message: Limit<usize>,
262    rpc_query: Limit<usize>,
263    rpc_get_best_tip: Limit<usize>,
264    rpc_answer_sync_ledger_query: Limit<usize>,
265    rpc_get_staged_ledger: Limit<usize>,
266    rpc_get_transition_chain: Limit<usize>,
267    rpc_get_some_initial_peers: Limit<usize>,
268}
269
270macro_rules! limit {
271    (#[$meta:meta] $limit:ident) => {
272        #[$meta]
273        pub fn $limit(&self) -> Limit<usize> {
274            self.$limit
275        }
276    };
277
278    (#[$meta:meta] $limit:ident, #[$setter_meta:meta] $setter:ident) => {
279        limit!(#[$meta] $limit);
280
281        #[$setter_meta]
282        pub fn $setter<T: Into<Limit<usize>>>(mut self, $limit: T) -> Self {
283            self.$limit = $limit.into();
284            self
285        }
286    };
287
288    (#[$meta:meta] $limit:ident(&$self:ident): $expr:expr) => {
289        #[$meta]
290        pub fn $limit(&$self) -> Limit<usize> {
291            $expr
292        }
293    };
294}
295
296impl P2pLimits {
297    limit!(
298        /// Maximum number of peers.
299        max_peers,
300        /// Sets maximum number of peers.
301        with_max_peers
302    );
303    limit!(
304        /// Minimum number of peers in state
305        min_peers_in_state
306    );
307    limit!(
308        /// Maximum number of peers in state
309        max_peers_in_state
310    );
311    limit!(
312        /// Maximum number of streams from a peer.
313        max_streams,
314        /// Sets the maximum number of streams that a peer is allowed to open simultaneously.
315        with_max_streams
316    );
317    limit!(
318        /// Maximum number of streams from a peer.
319        yamux_message_size,
320        /// Sets the maximum number of streams that a peer is allowed to open simultaneously.
321        with_yamux_message_size
322    );
323    limit!(
324        /// Maximum number of streams from a peer.
325        yamux_pending_outgoing_per_peer,
326        /// Sets the maximum number of streams that a peer is allowed to open simultaneously.
327        with_yamux_pending_outgoing_per_peer
328    );
329
330    limit!(
331        /// Minimum number of peers.
332        min_peers(&self): self.max_peers.map(|v| (v / 2).max(3).min(v))
333    );
334
335    limit!(
336        /// Above this limit, peers will be randomly disconnected to free up space.
337        max_stable_peers(&self): self.max_peers.map(|v| v.saturating_mul(8).saturating_div(10))
338    );
339
340    limit!(
341        /// Maximum number of connections.
342        max_connections(&self): self.max_peers.map(|v| v + 10)
343    );
344
345    limit!(
346        /// Maximum length of Identify message.
347        identify_message
348    );
349    limit!(
350        /// Maximum length of Kademlia request message.
351        kademlia_request
352    );
353    limit!(
354        /// Maximum length of Kademlia response message.
355        kademlia_response
356    );
357
358    limit!(
359        #[doc = "RPC service message"]
360        rpc_service_message
361    );
362    limit!(
363        #[doc = "RPC query"]
364        rpc_query
365    );
366    limit!(
367        #[doc = "RPC get_best_tip"]
368        rpc_get_best_tip
369    );
370    limit!(
371        #[doc = "RPC answer_sync_ledger_query"]
372        rpc_answer_sync_ledger_query
373    );
374    limit!(
375        #[doc = "RPC get_staged_ledger"]
376        rpc_get_staged_ledger
377    );
378    limit!(
379        #[doc = "RPC get_transition_chain"]
380        rpc_get_transition_chain
381    );
382    limit!(
383        #[doc = "RPC some_initial_peers"]
384        rpc_get_some_initial_peers
385    );
386}
387
388impl Default for P2pLimits {
389    fn default() -> Self {
390        let max_peers = Limit::Some(100);
391        let min_peers_in_state = Limit::Some(50);
392        let max_peers_in_state = Limit::Some(100);
393        let max_streams = Limit::Some(10);
394        // 256 MiB
395        let yamux_message_size = Limit::Some(0x10000000);
396
397        let identify_message = Limit::Some(0x1000);
398        let kademlia_request = Limit::Some(50);
399        let kademlia_response = identify_message.map(|v| v * 20); // should be enough to fit 20 addresses supplied by identify
400
401        let rpc_service_message = Limit::Some(7); // 7 for handshake, 1 for heartbeat
402        let rpc_query = Limit::Some(256); // max is 96
403        let rpc_get_best_tip = Limit::Some(3_500_000); // 3182930 as observed, may vary
404        let rpc_answer_sync_ledger_query = Limit::Some(200_000); // 124823 as observed
405        let rpc_get_staged_ledger = Limit::Some(400_000_000); // 59286608 as observed, may go higher
406        let rpc_get_transition_chain = Limit::Some(3_500_000); // 2979112 as observed
407        let rpc_get_some_initial_peers = Limit::Some(32_000); // TODO: calculate
408
409        Self {
410            max_peers,
411            min_peers_in_state,
412            max_peers_in_state,
413            max_streams,
414            yamux_message_size,
415            yamux_pending_outgoing_per_peer: rpc_get_staged_ledger,
416
417            identify_message,
418            kademlia_request,
419            kademlia_response,
420
421            rpc_service_message,
422            rpc_query,
423            rpc_get_best_tip,
424            rpc_answer_sync_ledger_query,
425            rpc_get_staged_ledger,
426            rpc_get_transition_chain,
427            rpc_get_some_initial_peers,
428        }
429    }
430}
431
432#[cfg(test)]
433mod tests {
434
435    use super::Limit;
436
437    #[test]
438    fn test_limits() {
439        let limit = Limit::Some(10);
440        assert!(0 < limit);
441        assert!(10 <= limit);
442        assert!(11 > limit);
443
444        let unlimited = Limit::Unlimited;
445        assert!(0 < unlimited);
446        assert!(usize::MAX < unlimited);
447    }
448}