Skip to main content

ave_network/
lib.rs

1//! # Network package.
2
3#![warn(missing_docs)]
4
5mod behaviour;
6mod control_list;
7pub mod error;
8//mod node;
9pub mod metrics;
10mod monitor;
11mod routing;
12mod service;
13mod transport;
14mod utils;
15mod worker;
16
17use std::fmt::{self, Debug, Display};
18
19use ave_common::identity::PublicKey;
20use borsh::{BorshDeserialize, BorshSerialize};
21pub use control_list::Config as ControlListConfig;
22pub use error::Error;
23pub use libp2p::{
24    PeerId,
25    identity::{
26        PublicKey as PublicKeyLibP2P, ed25519::PublicKey as PublicKeyEd25519,
27    },
28};
29pub use monitor::*;
30pub use routing::{Config as RoutingConfig, RoutingNode};
31pub use service::NetworkService;
32pub use utils::NetworkState;
33pub use worker::NetworkWorker;
34
35use bytes::Bytes;
36use serde::{Deserialize, Serialize};
37
38pub use crate::utils::ReqResConfig;
39
40#[cfg(all(feature = "test", not(test), not(debug_assertions)))]
41compile_error!(
42    "The 'test' feature should only be used during development/testing"
43);
44
45/// How to size the network backend.
46///
47/// - `Profile` — use a predefined instance type: implies fixed vCPU and RAM.
48/// - `Custom`  — supply exact RAM (MB) and vCPU count manually.
49/// - Absent (`None` in `Config`) — auto-detect from the running host.
50#[derive(Serialize, Deserialize, Debug, Clone)]
51#[serde(rename_all = "snake_case")]
52pub enum MachineSpec {
53    /// Use a predefined profile.
54    Profile(MachineProfile),
55    /// Supply exact machine dimensions.
56    Custom {
57        /// Total RAM in megabytes.
58        ram_mb: u64,
59        /// Available CPU cores.
60        cpu_cores: usize,
61    },
62}
63
64/// Predefined instance profiles with fixed vCPU and RAM.
65/// They only exist to provide convenient default values — the actual
66/// network tuning is derived from the resolved `ram_mb` and `cpu_cores`.
67///
68/// | Profile  | vCPU | RAM    |
69/// |----------|------|--------|
70/// | Nano     | 2    | 512 MB |
71/// | Micro    | 2    | 1 GB   |
72/// | Small    | 2    | 2 GB   |
73/// | Medium   | 2    | 4 GB   |
74/// | Large    | 2    | 8 GB   |
75/// | XLarge   | 4    | 16 GB  |
76/// | XXLarge  | 8    | 32 GB  |
77#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
78#[serde(rename_all = "snake_case")]
79pub enum MachineProfile {
80    /// 2 vCPU, 512 MB RAM.
81    Nano,
82    /// 2 vCPU, 1 GB RAM.
83    Micro,
84    /// 2 vCPU, 2 GB RAM.
85    Small,
86    /// 2 vCPU, 4 GB RAM.
87    Medium,
88    /// 2 vCPU, 8 GB RAM.
89    Large,
90    /// 4 vCPU, 16 GB RAM.
91    XLarge,
92    /// 8 vCPU, 32 GB RAM.
93    #[serde(rename = "2xlarge")]
94    XXLarge,
95}
96
97impl MachineProfile {
98    /// Canonical RAM for this profile in megabytes.
99    pub const fn ram_mb(self) -> u64 {
100        match self {
101            Self::Nano => 512,
102            Self::Micro => 1_024,
103            Self::Small => 2_048,
104            Self::Medium => 4_096,
105            Self::Large => 8_192,
106            Self::XLarge => 16_384,
107            Self::XXLarge => 32_768,
108        }
109    }
110
111    /// vCPU count for this profile.
112    pub const fn cpu_cores(self) -> usize {
113        match self {
114            Self::Nano => 2,
115            Self::Micro => 2,
116            Self::Small => 2,
117            Self::Medium => 2,
118            Self::Large => 2,
119            Self::XLarge => 4,
120            Self::XXLarge => 8,
121        }
122    }
123}
124
125impl Display for MachineProfile {
126    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
127        match self {
128            Self::Nano => write!(f, "nano"),
129            Self::Micro => write!(f, "micro"),
130            Self::Small => write!(f, "small"),
131            Self::Medium => write!(f, "medium"),
132            Self::Large => write!(f, "large"),
133            Self::XLarge => write!(f, "xlarge"),
134            Self::XXLarge => write!(f, "2xlarge"),
135        }
136    }
137}
138
139/// Resolved machine parameters ready to be consumed by the network backend.
140/// Network tuning is computed directly from these two values.
141pub struct ResolvedSpec {
142    /// Total RAM in megabytes.
143    pub ram_mb: u64,
144    /// Available CPU cores.
145    pub cpu_cores: usize,
146}
147
148/// Resolve the final network sizing parameters from a [`MachineSpec`]:
149///
150/// - `Profile(p)` → use the profile's canonical RAM and vCPU.
151/// - `Custom { ram_mb, cpu_cores }` → use the supplied values directly.
152/// - `None` → auto-detect total RAM and available CPU cores from the host.
153pub fn resolve_spec(spec: Option<MachineSpec>) -> ResolvedSpec {
154    match spec {
155        Some(MachineSpec::Profile(p)) => ResolvedSpec {
156            ram_mb: p.ram_mb(),
157            cpu_cores: p.cpu_cores(),
158        },
159        Some(MachineSpec::Custom { ram_mb, cpu_cores }) => {
160            ResolvedSpec { ram_mb, cpu_cores }
161        }
162        None => ResolvedSpec {
163            ram_mb: detect_ram_mb(),
164            cpu_cores: detect_cpu_cores(),
165        },
166    }
167}
168
169/// Detect total system RAM from `/proc/meminfo` (Linux). Falls back to 4 096 MB.
170pub(crate) fn detect_ram_mb() -> u64 {
171    #[cfg(target_os = "linux")]
172    {
173        if let Ok(meminfo) = std::fs::read_to_string("/proc/meminfo") {
174            for line in meminfo.lines() {
175                if let Some(rest) = line.strip_prefix("MemTotal:")
176                    && let Some(kb_str) = rest.split_whitespace().next()
177                    && let Ok(kb) = kb_str.parse::<u64>()
178                {
179                    return kb / 1024;
180                }
181            }
182        }
183    }
184    4_096
185}
186
187/// Detect available CPU parallelism. Falls back to 2.
188pub(crate) fn detect_cpu_cores() -> usize {
189    std::thread::available_parallelism()
190        .map(|n| n.get())
191        .unwrap_or(2)
192}
193
194/// The network configuration.
195/// Memory-based connection limit policy.
196///
197/// Controls when libp2p should stop accepting new connections based on
198/// process memory usage. The default is `Disabled`.
199///
200/// # Config examples
201/// ```toml
202/// # Reject new connections when process RAM exceeds 80% of system RAM (value must be 0.0–1.0)
203/// [network.memory_limits]
204/// type = "percentage"
205/// value = 0.8
206///
207/// # Reject new connections when process RAM exceeds 512 MB
208/// [network.memory_limits]
209/// type = "mb"
210/// value = 512
211///
212/// # No memory-based limit (default — omit the section or set type = "disabled")
213/// ```
214#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Default)]
215#[serde(tag = "type", rename_all = "snake_case")]
216pub enum MemoryLimitsConfig {
217    /// No memory-based connection limit (default).
218    #[default]
219    Disabled,
220    /// Reject new connections when process memory exceeds `value` fraction of total RAM.
221    /// Must be in the range 0.0–1.0 (e.g. `0.8` means 80% of system RAM).
222    Percentage {
223        /// Range into 0.0–1.0
224        value: f64,
225    },
226    /// Reject new connections when process memory exceeds `value` megabytes.
227    Mb {
228        /// `value` in megabytes
229        value: usize,
230    },
231}
232
233impl MemoryLimitsConfig {
234    /// Returns an error string if the configuration values are out of range.
235    pub fn validate(&self) -> Result<(), String> {
236        if let Self::Percentage { value } = self
237            && (*value <= 0.0 || *value > 1.0)
238        {
239            return Err(format!(
240                "network.memory_limits percentage must be in range (0.0, 1.0], got {}",
241                value
242            ));
243        }
244
245        Ok(())
246    }
247}
248
249impl Display for MemoryLimitsConfig {
250    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
251        match self {
252            Self::Disabled => write!(f, "disabled"),
253            Self::Percentage { value } => {
254                write!(f, "{:.0}% of system RAM", value * 100.0)
255            }
256            Self::Mb { value } => write!(f, "{} MB", value),
257        }
258    }
259}
260
261#[derive(Debug, Clone, Deserialize, Serialize)]
262#[serde(default)]
263#[serde(rename_all = "snake_case")]
264/// Network config
265pub struct Config {
266    /// The node type.
267    pub node_type: NodeType,
268
269    /// Listen addresses.
270    pub listen_addresses: Vec<String>,
271
272    /// External addresses.
273    pub external_addresses: Vec<String>,
274
275    /// Bootnodes to connect to.
276    pub boot_nodes: Vec<RoutingNode>,
277
278    /// Routing configuration.
279    pub routing: routing::Config,
280
281    /// Control List configuration.
282    pub control_list: control_list::Config,
283
284    /// Memory-based connection limit policy.
285    pub memory_limits: MemoryLimitsConfig,
286
287    /// Maximum accepted application message payload in bytes.
288    #[serde(default = "default_max_app_message_bytes")]
289    pub max_app_message_bytes: usize,
290
291    /// Maximum buffered outbound bytes per peer while disconnected.
292    /// `0` disables the per-peer bytes limit.
293    #[serde(default = "default_max_pending_outbound_bytes_per_peer")]
294    pub max_pending_outbound_bytes_per_peer: usize,
295
296    /// Maximum buffered inbound bytes per peer before helper delivery.
297    /// `0` disables the per-peer bytes limit.
298    #[serde(default = "default_max_pending_inbound_bytes_per_peer")]
299    pub max_pending_inbound_bytes_per_peer: usize,
300
301    /// Maximum total buffered outbound bytes across all peers while disconnected.
302    /// `0` disables the global bytes limit.
303    #[serde(default = "default_max_pending_outbound_bytes_total")]
304    pub max_pending_outbound_bytes_total: usize,
305
306    /// Maximum total buffered inbound bytes across all peers before helper delivery.
307    /// `0` disables the global bytes limit.
308    #[serde(default = "default_max_pending_inbound_bytes_total")]
309    pub max_pending_inbound_bytes_total: usize,
310}
311
312impl Config {
313    /// Create a new configuration.
314    pub fn new(
315        node_type: NodeType,
316        listen_addresses: Vec<String>,
317        external_addresses: Vec<String>,
318        boot_nodes: Vec<RoutingNode>,
319    ) -> Self {
320        Self {
321            boot_nodes,
322            node_type,
323            listen_addresses,
324            external_addresses,
325            routing: routing::Config::default(),
326            control_list: control_list::Config::default(),
327            memory_limits: MemoryLimitsConfig::default(),
328            max_app_message_bytes: default_max_app_message_bytes(),
329            max_pending_outbound_bytes_per_peer:
330                default_max_pending_outbound_bytes_per_peer(),
331            max_pending_inbound_bytes_per_peer:
332                default_max_pending_inbound_bytes_per_peer(),
333            max_pending_outbound_bytes_total:
334                default_max_pending_outbound_bytes_total(),
335            max_pending_inbound_bytes_total:
336                default_max_pending_inbound_bytes_total(),
337        }
338    }
339}
340
341const fn default_max_app_message_bytes() -> usize {
342    crate::utils::MAX_APP_MESSAGE_BYTES
343}
344
345const fn default_max_pending_outbound_bytes_per_peer() -> usize {
346    crate::utils::DEFAULT_MAX_PENDING_OUTBOUND_BYTES_PER_PEER
347}
348
349const fn default_max_pending_inbound_bytes_per_peer() -> usize {
350    crate::utils::DEFAULT_MAX_PENDING_INBOUND_BYTES_PER_PEER
351}
352
353const fn default_max_pending_outbound_bytes_total() -> usize {
354    crate::utils::DEFAULT_MAX_PENDING_OUTBOUND_BYTES_TOTAL
355}
356
357const fn default_max_pending_inbound_bytes_total() -> usize {
358    crate::utils::DEFAULT_MAX_PENDING_INBOUND_BYTES_TOTAL
359}
360
361impl Default for Config {
362    fn default() -> Self {
363        Self {
364            node_type: NodeType::default(),
365            listen_addresses: Vec::default(),
366            external_addresses: Vec::default(),
367            boot_nodes: Vec::default(),
368            routing: routing::Config::default(),
369            control_list: control_list::Config::default(),
370            memory_limits: MemoryLimitsConfig::default(),
371            max_app_message_bytes: default_max_app_message_bytes(),
372            max_pending_outbound_bytes_per_peer:
373                default_max_pending_outbound_bytes_per_peer(),
374            max_pending_inbound_bytes_per_peer:
375                default_max_pending_inbound_bytes_per_peer(),
376            max_pending_outbound_bytes_total:
377                default_max_pending_outbound_bytes_total(),
378            max_pending_inbound_bytes_total:
379                default_max_pending_inbound_bytes_total(),
380        }
381    }
382}
383
384/// Type of a node.
385#[derive(Debug, Clone, Deserialize, Default, PartialEq, Eq, Serialize)]
386pub enum NodeType {
387    /// Bootstrap node.
388    #[default]
389    Bootstrap,
390    /// Addressable node.
391    Addressable,
392    /// Ephemeral node.
393    Ephemeral,
394}
395
396impl fmt::Display for NodeType {
397    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
398        match self {
399            Self::Bootstrap => write!(f, "Bootstrap"),
400            Self::Addressable => write!(f, "Addressable"),
401            Self::Ephemeral => write!(f, "Ephemeral"),
402        }
403    }
404}
405
406/// Command enumeration for the network service.
407#[derive(Debug)]
408pub enum Command {
409    /// Send a message to the given peer.
410    SendMessage {
411        /// The peer to send the message to.
412        peer: PeerId,
413        /// The message to send.
414        message: Bytes,
415    },
416}
417
418/// Event enumeration for the network service.
419#[derive(Debug, Serialize, Deserialize, Clone)]
420pub enum Event {
421    /// Network state changed.
422    StateChanged(utils::NetworkState),
423
424    /// Network error.
425    Error(Error),
426}
427
428/// Command enumeration for the Helper service.
429#[derive(Debug, Serialize, Deserialize)]
430pub enum CommandHelper<T>
431where
432    T: Debug + Serialize,
433{
434    /// Send a message to the given peer.
435    SendMessage {
436        /// The message to send.
437        message: T,
438    },
439    /// Received a message.
440    ReceivedMessage {
441        /// Sender public key
442        sender: [u8; 32],
443        /// The message received.
444        message: Bytes,
445    },
446}
447
448/// Event enumeration for the Helper service.
449#[derive(
450    Debug, Serialize, Deserialize, Clone, BorshDeserialize, BorshSerialize,
451)]
452pub struct ComunicateInfo {
453    /// The request id.
454    pub request_id: String,
455    /// The request version.
456    pub version: u64,
457    /// The receiver key identifier.
458    pub receiver: PublicKey,
459    /// The receiver actor.
460    pub receiver_actor: String,
461}