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 pub libp2p_port: Option<u16>,
19 pub listen_port: Option<u16>,
21 pub identity_pub_key: PublicKey,
23 pub initial_peers: Vec<P2pConnectionOutgoingInitOpts>,
25 pub external_addrs: Vec<IpAddr>,
27
28 pub enabled_channels: BTreeSet<ChannelId>,
29
30 pub timeouts: P2pTimeouts,
31
32 pub limits: P2pLimits,
33
34 pub peer_discovery: bool,
36
37 pub meshsub: P2pMeshsubConfig,
38}
39
40#[derive(Serialize, Deserialize, Debug, Clone)]
41pub struct P2pMeshsubConfig {
42 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 max_peers,
300 with_max_peers
302 );
303 limit!(
304 min_peers_in_state
306 );
307 limit!(
308 max_peers_in_state
310 );
311 limit!(
312 max_streams,
314 with_max_streams
316 );
317 limit!(
318 yamux_message_size,
320 with_yamux_message_size
322 );
323 limit!(
324 yamux_pending_outgoing_per_peer,
326 with_yamux_pending_outgoing_per_peer
328 );
329
330 limit!(
331 min_peers(&self): self.max_peers.map(|v| (v / 2).max(3).min(v))
333 );
334
335 limit!(
336 max_stable_peers(&self): self.max_peers.map(|v| v.saturating_mul(8).saturating_div(10))
338 );
339
340 limit!(
341 max_connections(&self): self.max_peers.map(|v| v + 10)
343 );
344
345 limit!(
346 identify_message
348 );
349 limit!(
350 kademlia_request
352 );
353 limit!(
354 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 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); let rpc_service_message = Limit::Some(7); let rpc_query = Limit::Some(256); let rpc_get_best_tip = Limit::Some(3_500_000); let rpc_answer_sync_ledger_query = Limit::Some(200_000); let rpc_get_staged_ledger = Limit::Some(400_000_000); let rpc_get_transition_chain = Limit::Some(3_500_000); let rpc_get_some_initial_peers = Limit::Some(32_000); 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}