Skip to main content

linera_rpc/
config.rs

1// Copyright (c) Zefchain Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use std::ffi::OsString;
5
6use clap::Parser;
7use linera_base::{crypto::ValidatorPublicKey, identifiers::ChainId};
8use serde::{Deserialize, Serialize};
9
10#[cfg(with_simple_network)]
11use crate::simple;
12
13/// The configuration for cross-chain message delivery.
14#[derive(Clone, Debug, Parser)]
15#[cfg_attr(with_testing, derive(PartialEq))]
16pub struct CrossChainConfig {
17    /// Number of cross-chain messages allowed before dropping them.
18    #[arg(long = "cross-chain-queue-size", default_value = "1000")]
19    pub(crate) queue_size: usize,
20
21    /// Maximum number of retries for a cross-chain message.
22    #[arg(long = "cross-chain-max-retries", default_value = "10")]
23    pub(crate) max_retries: u32,
24
25    /// Delay before retrying of cross-chain message.
26    #[arg(long = "cross-chain-retry-delay-ms", default_value = "2000")]
27    pub(crate) retry_delay_ms: u64,
28
29    /// Maximum backoff delay for cross-chain message retries.
30    #[arg(long = "cross-chain-max-backoff-ms", default_value = "30000")]
31    pub(crate) max_backoff_ms: u64,
32
33    /// Introduce a delay before sending every cross-chain message (e.g. for testing purpose).
34    #[arg(long = "cross-chain-sender-delay-ms", default_value = "0")]
35    pub(crate) sender_delay_ms: u64,
36
37    /// Drop cross-chain messages randomly at the given rate (0 <= rate < 1) (meant for testing).
38    #[arg(long = "cross-chain-sender-failure-rate", default_value = "0.0")]
39    pub(crate) sender_failure_rate: f32,
40}
41
42impl Default for CrossChainConfig {
43    fn default() -> Self {
44        CrossChainConfig::parse_from::<[OsString; 1], OsString>(["".into()])
45    }
46}
47
48impl CrossChainConfig {
49    /// Returns the command-line arguments corresponding to this configuration.
50    pub fn to_args(&self) -> Vec<String> {
51        vec![
52            "--cross-chain-queue-size".to_string(),
53            self.queue_size.to_string(),
54            "--cross-chain-max-retries".to_string(),
55            self.max_retries.to_string(),
56            "--cross-chain-retry-delay-ms".to_string(),
57            self.retry_delay_ms.to_string(),
58            "--cross-chain-max-backoff-ms".to_string(),
59            self.max_backoff_ms.to_string(),
60            "--cross-chain-sender-delay-ms".to_string(),
61            self.sender_delay_ms.to_string(),
62            "--cross-chain-sender-failure-rate".to_string(),
63            self.sender_failure_rate.to_string(),
64        ]
65    }
66}
67
68/// The configuration for notification delivery to proxies.
69#[derive(Clone, Debug, Parser)]
70pub struct NotificationConfig {
71    /// Size of the broadcast channel buffer for notifications
72    #[arg(long = "notification-queue-size", default_value = "1000")]
73    pub notification_queue_size: usize,
74
75    /// Maximum number of notifications per batch sent to proxy
76    #[arg(long = "notification-batch-size", default_value = "100")]
77    pub notification_batch_size: usize,
78
79    /// Maximum number of concurrent batch send tasks per proxy
80    #[arg(long = "notification-max-in-flight", default_value = "8")]
81    pub notification_max_in_flight: usize,
82}
83
84/// The index of a shard within a validator.
85pub type ShardId = usize;
86
87/// The network configuration of a shard.
88#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
89pub struct ShardConfig {
90    /// The host name (e.g., an IP address).
91    pub host: String,
92    /// The port.
93    pub port: u16,
94    /// The port on which metrics are served.
95    pub metrics_port: Option<u16>,
96}
97
98impl ShardConfig {
99    /// Returns the `host:port` address of the shard.
100    pub fn address(&self) -> String {
101        format!("{}:{}", self.host, self.port)
102    }
103
104    /// Returns the HTTP URL of the shard.
105    pub fn http_address(&self) -> String {
106        format!("http://{}:{}", self.host, self.port)
107    }
108}
109
110/// The network configuration of a proxy.
111#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
112pub struct ProxyConfig {
113    /// The hostname (e.g., an IP address).
114    pub host: String,
115    /// The public facing port. Receives incoming connections from clients.
116    pub public_port: u16,
117    /// The private port. Used for communicating with shards.
118    pub private_port: u16,
119    /// The port on which metrics are served.
120    pub metrics_port: u16,
121}
122
123impl ProxyConfig {
124    /// Returns the internal URL used by shards to reach the proxy over the given protocol.
125    pub fn internal_address(&self, protocol: &NetworkProtocol) -> String {
126        format!(
127            "{}://{}:{}",
128            protocol.scheme(),
129            self.host,
130            self.private_port
131        )
132    }
133}
134
135/// The network protocol.
136#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)]
137pub enum NetworkProtocol {
138    /// The simple TCP/UDP transport protocol.
139    #[cfg(with_simple_network)]
140    Simple(simple::TransportProtocol),
141    /// The gRPC protocol, with the given TLS configuration.
142    Grpc(TlsConfig),
143}
144
145/// The TLS configuration for the gRPC protocol.
146#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)]
147pub enum TlsConfig {
148    /// Communicate over plaintext, without TLS encryption.
149    ClearText,
150    /// Communicate over TLS-encrypted connections.
151    Tls,
152}
153
154impl NetworkProtocol {
155    fn scheme(&self) -> &'static str {
156        match self {
157            #[cfg(with_simple_network)]
158            NetworkProtocol::Simple(transport) => transport.scheme(),
159            NetworkProtocol::Grpc(tls) => match tls {
160                TlsConfig::ClearText => "http",
161                TlsConfig::Tls => "https",
162            },
163        }
164    }
165}
166
167/// The network configuration for all shards.
168pub type ValidatorInternalNetworkConfig = ValidatorInternalNetworkPreConfig<NetworkProtocol>;
169
170/// The public network configuration for a validator.
171pub type ValidatorPublicNetworkConfig = ValidatorPublicNetworkPreConfig<NetworkProtocol>;
172
173/// The network configuration for all shards.
174#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
175pub struct ValidatorInternalNetworkPreConfig<P> {
176    /// The public key of the validator.
177    pub public_key: ValidatorPublicKey,
178    /// The network protocol to use internally.
179    pub protocol: P,
180    /// The available shards. Each chain UID is mapped to a unique shard in the vector in
181    /// a static way.
182    pub shards: Vec<ShardConfig>,
183    /// The server configurations for the linera-exporter.
184    /// They can be used as optional locations to forward notifications to destinations other than
185    /// the proxy, by the workers.
186    pub block_exporters: Vec<ExporterServiceConfig>,
187    /// The available proxies.
188    pub proxies: Vec<ProxyConfig>,
189}
190
191impl<P> ValidatorInternalNetworkPreConfig<P> {
192    /// Returns a copy of this configuration with the protocol replaced by the given one.
193    pub fn clone_with_protocol<Q>(&self, protocol: Q) -> ValidatorInternalNetworkPreConfig<Q> {
194        ValidatorInternalNetworkPreConfig {
195            public_key: self.public_key,
196            protocol,
197            shards: self.shards.clone(),
198            block_exporters: self.block_exporters.clone(),
199            proxies: self.proxies.clone(),
200        }
201    }
202}
203
204impl ValidatorInternalNetworkConfig {
205    /// Returns the URLs of the configured block exporters.
206    pub fn exporter_addresses(&self) -> Vec<String> {
207        self.block_exporters
208            .iter()
209            .map(|ExporterServiceConfig { host, port }| {
210                format!("{}://{}:{}", self.protocol.scheme(), host, port)
211            })
212            .collect::<Vec<_>>()
213    }
214}
215
216impl ValidatorPublicNetworkConfig {
217    /// Returns the public HTTP URL of the validator.
218    pub fn http_address(&self) -> String {
219        format!("{}://{}:{}", self.protocol.scheme(), self.host, self.port)
220    }
221}
222
223/// The public network configuration for a validator.
224#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
225pub struct ValidatorPublicNetworkPreConfig<P> {
226    /// The network protocol to use for the validator frontend.
227    pub protocol: P,
228    /// The host name of the validator (IP or hostname).
229    pub host: String,
230    /// The port the validator listens on.
231    pub port: u16,
232}
233
234impl<P> ValidatorPublicNetworkPreConfig<P> {
235    /// Returns a copy of this configuration with the protocol replaced by the given one.
236    pub fn clone_with_protocol<Q>(&self, protocol: Q) -> ValidatorPublicNetworkPreConfig<Q> {
237        ValidatorPublicNetworkPreConfig {
238            protocol,
239            host: self.host.clone(),
240            port: self.port,
241        }
242    }
243}
244
245impl<P> std::fmt::Display for ValidatorPublicNetworkPreConfig<P>
246where
247    P: std::fmt::Display,
248{
249    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
250        write!(f, "{}:{}:{}", self.protocol, self.host, self.port)
251    }
252}
253
254impl std::fmt::Display for NetworkProtocol {
255    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
256        match self {
257            #[cfg(with_simple_network)]
258            NetworkProtocol::Simple(protocol) => write!(f, "{protocol:?}"),
259            NetworkProtocol::Grpc(tls) => match tls {
260                TlsConfig::ClearText => write!(f, "grpc"),
261                TlsConfig::Tls => write!(f, "grpcs"),
262            },
263        }
264    }
265}
266
267impl<P> std::str::FromStr for ValidatorPublicNetworkPreConfig<P>
268where
269    P: std::str::FromStr,
270    P::Err: std::fmt::Display,
271{
272    type Err = anyhow::Error;
273
274    fn from_str(s: &str) -> Result<Self, Self::Err> {
275        let parts = s.split(':').collect::<Vec<_>>();
276        anyhow::ensure!(
277            parts.len() == 3,
278            "Expecting format `(tcp|udp|grpc|grpcs):host:port`"
279        );
280        let protocol = parts[0].parse().map_err(|s| anyhow::anyhow!("{s}"))?;
281        let host = parts[1].to_owned();
282        let port = parts[2].parse()?;
283        Ok(ValidatorPublicNetworkPreConfig {
284            protocol,
285            host,
286            port,
287        })
288    }
289}
290
291impl std::str::FromStr for NetworkProtocol {
292    type Err = String;
293
294    fn from_str(s: &str) -> Result<Self, Self::Err> {
295        let protocol = match s {
296            "grpc" => Self::Grpc(TlsConfig::ClearText),
297            "grpcs" => Self::Grpc(TlsConfig::Tls),
298            #[cfg(with_simple_network)]
299            s => Self::Simple(simple::TransportProtocol::from_str(s)?),
300            #[cfg(not(with_simple_network))]
301            s => return Err(format!("unsupported protocol: {s:?}")),
302        };
303        Ok(protocol)
304    }
305}
306
307impl<P> ValidatorInternalNetworkPreConfig<P> {
308    /// Static shard assignment
309    #[expect(
310        clippy::cast_possible_truncation,
311        reason = "result is reduced modulo shards.len(), so any truncation is irrelevant"
312    )]
313    pub fn get_shard_id(&self, chain_id: ChainId) -> ShardId {
314        use std::hash::{Hash, Hasher};
315        let mut s = std::collections::hash_map::DefaultHasher::new();
316        // Use the validator public key to randomise shard assignment.
317        self.public_key.hash(&mut s);
318        chain_id.hash(&mut s);
319        (s.finish() as ShardId) % self.shards.len()
320    }
321
322    /// Returns the [`ShardConfig`] for the given shard id.
323    pub fn shard(&self, shard_id: ShardId) -> &ShardConfig {
324        &self.shards[shard_id]
325    }
326
327    /// Gets the [`ShardConfig`] of the shard assigned to the `chain_id`.
328    pub fn get_shard_for(&self, chain_id: ChainId) -> &ShardConfig {
329        self.shard(self.get_shard_id(chain_id))
330    }
331}
332
333#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
334/// The server configuration for the linera-exporter.
335pub struct ExporterServiceConfig {
336    /// The host name of the server (IP or hostname).
337    pub host: String,
338    /// The port for the server to listen on.
339    pub port: u16,
340}
341
342impl ExporterServiceConfig {
343    /// Creates a new [`ExporterServiceConfig`] from the given host and port.
344    pub fn new(host: String, port: u16) -> ExporterServiceConfig {
345        ExporterServiceConfig { host, port }
346    }
347}
348
349#[test]
350fn cross_chain_config_to_args() {
351    let config = CrossChainConfig::default();
352    let args = config.to_args();
353    let mut cmd = vec![String::new()];
354    cmd.extend(args.clone());
355    let config2 = CrossChainConfig::parse_from(cmd);
356    let args2 = config2.to_args();
357    assert_eq!(config, config2);
358    assert_eq!(args, args2);
359}