linera_client/
client_options.rs

1// Copyright (c) Zefchain Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use std::{collections::HashSet, fmt, iter, path::PathBuf};
5
6use linera_base::{
7    data_types::{ApplicationPermissions, TimeDelta},
8    identifiers::{AccountOwner, ApplicationId, ChainId, GenericApplicationId},
9    ownership::{ChainOwnership, TimeoutConfig},
10    time::Duration,
11};
12use linera_core::{
13    client::{
14        chain_client, BlanketMessagePolicy, MessagePolicy, DEFAULT_CERTIFICATE_DOWNLOAD_BATCH_SIZE,
15        DEFAULT_SENDER_CERTIFICATE_DOWNLOAD_BATCH_SIZE,
16    },
17    node::CrossChainMessageDelivery,
18    DEFAULT_QUORUM_GRACE_PERIOD,
19};
20use linera_execution::ResourceControlPolicy;
21
22#[cfg(not(web))]
23use crate::client_metrics::TimingConfig;
24use crate::util;
25
26#[derive(Debug, thiserror::Error)]
27pub enum Error {
28    #[error("I/O error: {0}")]
29    IoError(#[from] std::io::Error),
30    #[error("there are {public_keys} public keys but {weights} weights")]
31    MisalignedWeights { public_keys: usize, weights: usize },
32    #[error("config error: {0}")]
33    Config(#[from] crate::config::Error),
34}
35
36util::impl_from_infallible!(Error);
37
38#[derive(Clone, clap::Parser)]
39pub struct ClientContextOptions {
40    /// Sets the file storing the private state of user chains (an empty one will be created if missing)
41    #[arg(long = "wallet")]
42    pub wallet_state_path: Option<PathBuf>,
43
44    /// Sets the file storing the keystore state.
45    #[arg(long = "keystore")]
46    pub keystore_path: Option<PathBuf>,
47
48    /// Given an ASCII alphanumeric parameter `X`, read the wallet state and the wallet
49    /// storage config from the environment variables `LINERA_WALLET_{X}` and
50    /// `LINERA_STORAGE_{X}` instead of `LINERA_WALLET` and
51    /// `LINERA_STORAGE`.
52    #[arg(long, short = 'w', value_parser = util::parse_ascii_alphanumeric_string)]
53    pub with_wallet: Option<String>,
54
55    /// Timeout for sending queries (milliseconds)
56    #[arg(long = "send-timeout-ms", default_value = "4000", value_parser = util::parse_millis)]
57    pub send_timeout: Duration,
58
59    /// Timeout for receiving responses (milliseconds)
60    #[arg(long = "recv-timeout-ms", default_value = "4000", value_parser = util::parse_millis)]
61    pub recv_timeout: Duration,
62
63    /// The maximum number of incoming message bundles to include in a block proposal.
64    #[arg(long, default_value = "10")]
65    pub max_pending_message_bundles: usize,
66
67    /// The duration in milliseconds after which an idle chain worker will free its memory.
68    #[arg(
69        long = "chain-worker-ttl-ms",
70        default_value = "30000",
71        env = "LINERA_CHAIN_WORKER_TTL_MS",
72        value_parser = util::parse_millis
73    )]
74    pub chain_worker_ttl: Duration,
75
76    /// The duration, in milliseconds, after which an idle sender chain worker will
77    /// free its memory.
78    #[arg(
79        long = "sender-chain-worker-ttl-ms",
80        default_value = "1000",
81        env = "LINERA_SENDER_CHAIN_WORKER_TTL_MS",
82        value_parser = util::parse_millis
83    )]
84    pub sender_chain_worker_ttl: Duration,
85
86    /// Delay increment for retrying to connect to a validator.
87    #[arg(
88        long = "retry-delay-ms",
89        default_value = "1000",
90        value_parser = util::parse_millis
91    )]
92    pub retry_delay: Duration,
93
94    /// Number of times to retry connecting to a validator.
95    #[arg(long, default_value = "10")]
96    pub max_retries: u32,
97
98    /// Enable OpenTelemetry Chrome JSON exporter for trace data analysis.
99    #[arg(long)]
100    pub chrome_trace_exporter: bool,
101
102    /// Output file path for Chrome trace JSON format.
103    /// Can be visualized in chrome://tracing or Perfetto UI.
104    #[arg(long, env = "LINERA_CHROME_TRACE_FILE")]
105    pub chrome_trace_file: Option<String>,
106
107    /// OpenTelemetry OTLP exporter endpoint (requires opentelemetry feature).
108    #[arg(long, env = "LINERA_OTLP_EXPORTER_ENDPOINT")]
109    pub otlp_exporter_endpoint: Option<String>,
110
111    /// Whether to wait until a quorum of validators has confirmed that all sent cross-chain
112    /// messages have been delivered.
113    #[arg(long)]
114    pub wait_for_outgoing_messages: bool,
115
116    /// (EXPERIMENTAL) Whether application services can persist in some cases between queries.
117    #[arg(long)]
118    pub long_lived_services: bool,
119
120    /// The policy for handling incoming messages.
121    #[arg(long, default_value = "accept")]
122    pub blanket_message_policy: BlanketMessagePolicy,
123
124    /// A set of chains to restrict incoming messages from. By default, messages
125    /// from all chains are accepted. To reject messages from all chains, specify
126    /// an empty string.
127    #[arg(long, value_parser = util::parse_chain_set)]
128    pub restrict_chain_ids_to: Option<HashSet<ChainId>>,
129
130    /// A set of application IDs. If specified, only bundles with at least one message from one of
131    /// these applications will be accepted.
132    #[arg(long, value_parser = util::parse_app_set)]
133    pub reject_message_bundles_without_application_ids: Option<HashSet<GenericApplicationId>>,
134
135    /// A set of application IDs. If specified, only bundles where all messages are from one of
136    /// these applications will be accepted.
137    #[arg(long, value_parser = util::parse_app_set)]
138    pub reject_message_bundles_with_other_application_ids: Option<HashSet<GenericApplicationId>>,
139
140    /// Enable timing reports during operations
141    #[cfg(not(web))]
142    #[arg(long)]
143    pub timings: bool,
144
145    /// Interval in seconds between timing reports (defaults to 5)
146    #[cfg(not(web))]
147    #[arg(long, default_value = "5")]
148    pub timing_interval: u64,
149
150    /// An additional delay, after reaching a quorum, to wait for additional validator signatures,
151    /// as a fraction of time taken to reach quorum.
152    #[arg(long, default_value_t = DEFAULT_QUORUM_GRACE_PERIOD)]
153    pub quorum_grace_period: f64,
154
155    /// The delay when downloading a blob, after which we try a second validator, in milliseconds.
156    #[arg(
157        long = "blob-download-timeout-ms",
158        default_value = "1000",
159        value_parser = util::parse_millis
160    )]
161    pub blob_download_timeout: Duration,
162
163    /// The delay when downloading a batch of certificates, after which we try a second validator,
164    /// in milliseconds.
165    #[arg(
166        long = "cert-batch-download-timeout-ms",
167        default_value = "1000",
168        value_parser = util::parse_millis
169    )]
170    pub certificate_batch_download_timeout: Duration,
171
172    /// Maximum number of certificates that we download at a time from one validator when
173    /// synchronizing one of our chains.
174    #[arg(
175        long,
176        default_value_t = DEFAULT_CERTIFICATE_DOWNLOAD_BATCH_SIZE,
177    )]
178    pub certificate_download_batch_size: u64,
179
180    /// Maximum number of sender certificates we try to download and receive in one go
181    /// when syncing sender chains.
182    #[arg(
183        long,
184        default_value_t = DEFAULT_SENDER_CERTIFICATE_DOWNLOAD_BATCH_SIZE,
185    )]
186    pub sender_certificate_download_batch_size: usize,
187
188    /// Maximum number of tasks that can are joined concurrently in the client.
189    #[arg(long, default_value = "100")]
190    pub max_joined_tasks: usize,
191
192    /// Maximum expected latency in milliseconds for score normalization.
193    #[arg(
194        long,
195        default_value_t = linera_core::client::requests_scheduler::MAX_ACCEPTED_LATENCY_MS,
196        env = "LINERA_REQUESTS_SCHEDULER_MAX_ACCEPTED_LATENCY_MS"
197    )]
198    pub max_accepted_latency_ms: f64,
199
200    /// Time-to-live for cached responses in milliseconds.
201    #[arg(
202        long,
203        default_value_t = linera_core::client::requests_scheduler::CACHE_TTL_MS,
204        env = "LINERA_REQUESTS_SCHEDULER_CACHE_TTL_MS"
205    )]
206    pub cache_ttl_ms: u64,
207
208    /// Maximum number of entries in the cache.
209    #[arg(
210        long,
211        default_value_t = linera_core::client::requests_scheduler::CACHE_MAX_SIZE,
212        env = "LINERA_REQUESTS_SCHEDULER_CACHE_MAX_SIZE"
213    )]
214    pub cache_max_size: usize,
215
216    /// Maximum latency for an in-flight request before we stop deduplicating it (in milliseconds).
217    #[arg(
218        long,
219        default_value_t = linera_core::client::requests_scheduler::MAX_REQUEST_TTL_MS,
220        env = "LINERA_REQUESTS_SCHEDULER_MAX_REQUEST_TTL_MS"
221    )]
222    pub max_request_ttl_ms: u64,
223
224    /// Smoothing factor for Exponential Moving Averages (0 < alpha < 1).
225    /// Higher values give more weight to recent observations.
226    /// Typical values are between 0.01 and 0.5.
227    /// A value of 0.1 means that 10% of the new observation is considered
228    /// and 90% of the previous average is retained.
229    #[arg(
230        long,
231        default_value_t = linera_core::client::requests_scheduler::ALPHA_SMOOTHING_FACTOR,
232        env = "LINERA_REQUESTS_SCHEDULER_ALPHA"
233    )]
234    pub alpha: f64,
235
236    /// Delay in milliseconds between starting requests to different peers.
237    /// This helps to stagger requests and avoid overwhelming the network.
238    #[arg(
239        long,
240        default_value_t = linera_core::client::requests_scheduler::STAGGERED_DELAY_MS,
241        env = "LINERA_REQUESTS_SCHEDULER_ALTERNATIVE_PEERS_RETRY_DELAY_MS"
242    )]
243    pub alternative_peers_retry_delay_ms: u64,
244}
245
246impl ClientContextOptions {
247    /// Creates [`chain_client::Options`] with the corresponding values.
248    pub(crate) fn to_chain_client_options(&self) -> chain_client::Options {
249        let message_policy = MessagePolicy::new(
250            self.blanket_message_policy,
251            self.restrict_chain_ids_to.clone(),
252            self.reject_message_bundles_without_application_ids.clone(),
253            self.reject_message_bundles_with_other_application_ids
254                .clone(),
255        );
256        let cross_chain_message_delivery =
257            CrossChainMessageDelivery::new(self.wait_for_outgoing_messages);
258        chain_client::Options {
259            max_pending_message_bundles: self.max_pending_message_bundles,
260            message_policy,
261            cross_chain_message_delivery,
262            quorum_grace_period: self.quorum_grace_period,
263            blob_download_timeout: self.blob_download_timeout,
264            certificate_batch_download_timeout: self.certificate_batch_download_timeout,
265            certificate_download_batch_size: self.certificate_download_batch_size,
266            sender_certificate_download_batch_size: self.sender_certificate_download_batch_size,
267            max_joined_tasks: self.max_joined_tasks,
268        }
269    }
270
271    /// Creates [`TimingConfig`] with the corresponding values.
272    #[cfg(not(web))]
273    pub(crate) fn to_timing_config(&self) -> TimingConfig {
274        TimingConfig {
275            enabled: self.timings,
276            report_interval_secs: self.timing_interval,
277        }
278    }
279
280    /// Creates [`RequestsSchedulerConfig`] with the corresponding values.
281    pub(crate) fn to_requests_scheduler_config(
282        &self,
283    ) -> linera_core::client::RequestsSchedulerConfig {
284        linera_core::client::RequestsSchedulerConfig {
285            max_accepted_latency_ms: self.max_accepted_latency_ms,
286            cache_ttl_ms: self.cache_ttl_ms,
287            cache_max_size: self.cache_max_size,
288            max_request_ttl_ms: self.max_request_ttl_ms,
289            alpha: self.alpha,
290            retry_delay_ms: self.alternative_peers_retry_delay_ms,
291        }
292    }
293}
294
295#[derive(Debug, Clone, clap::Args)]
296pub struct ChainOwnershipConfig {
297    /// The new super owners.
298    #[arg(long, num_args(0..))]
299    pub super_owners: Vec<AccountOwner>,
300
301    /// The new regular owners.
302    #[arg(long, num_args(0..))]
303    pub owners: Vec<AccountOwner>,
304
305    /// The leader of the first single-leader round. If not set, this is random like other rounds.
306    #[arg(long)]
307    pub first_leader: Option<AccountOwner>,
308
309    /// Weights for the new owners.
310    ///
311    /// If they are specified there must be exactly one weight for each owner.
312    /// If no weights are given, every owner will have weight 100.
313    #[arg(long, num_args(0..))]
314    pub owner_weights: Vec<u64>,
315
316    /// The number of rounds in which every owner can propose blocks, i.e. the first round
317    /// number in which only a single designated leader is allowed to propose blocks.
318    #[arg(long)]
319    pub multi_leader_rounds: Option<u32>,
320
321    /// Whether the multi-leader rounds are unrestricted, i.e. not limited to chain owners.
322    /// This should only be `true` on chains with restrictive application permissions and an
323    /// application-based mechanism to select block proposers.
324    #[arg(long)]
325    pub open_multi_leader_rounds: bool,
326
327    /// The duration of the fast round, in milliseconds.
328    #[arg(long = "fast-round-ms", value_parser = util::parse_millis_delta)]
329    pub fast_round_duration: Option<TimeDelta>,
330
331    /// The duration of the first single-leader and all multi-leader rounds.
332    #[arg(
333        long = "base-timeout-ms",
334        default_value = "10000",
335        value_parser = util::parse_millis_delta
336    )]
337    pub base_timeout: TimeDelta,
338
339    /// The number of milliseconds by which the timeout increases after each
340    /// single-leader round.
341    #[arg(
342        long = "timeout-increment-ms",
343        default_value = "1000",
344        value_parser = util::parse_millis_delta
345    )]
346    pub timeout_increment: TimeDelta,
347
348    /// The age of an incoming tracked or protected message after which the validators start
349    /// transitioning the chain to fallback mode, in milliseconds.
350    #[arg(
351        long = "fallback-duration-ms",
352        default_value = "86400000", // 1 day
353        value_parser = util::parse_millis_delta
354    )]
355    pub fallback_duration: TimeDelta,
356}
357
358impl TryFrom<ChainOwnershipConfig> for ChainOwnership {
359    type Error = Error;
360
361    fn try_from(config: ChainOwnershipConfig) -> Result<ChainOwnership, Error> {
362        let ChainOwnershipConfig {
363            super_owners,
364            owners,
365            first_leader,
366            owner_weights,
367            multi_leader_rounds,
368            fast_round_duration,
369            open_multi_leader_rounds,
370            base_timeout,
371            timeout_increment,
372            fallback_duration,
373        } = config;
374        if !owner_weights.is_empty() && owner_weights.len() != owners.len() {
375            return Err(Error::MisalignedWeights {
376                public_keys: owners.len(),
377                weights: owner_weights.len(),
378            });
379        }
380        let super_owners = super_owners.into_iter().collect();
381        let owners = owners
382            .into_iter()
383            .zip(owner_weights.into_iter().chain(iter::repeat(100)))
384            .collect();
385        let multi_leader_rounds = multi_leader_rounds.unwrap_or(u32::MAX);
386        let timeout_config = TimeoutConfig {
387            fast_round_duration,
388            base_timeout,
389            timeout_increment,
390            fallback_duration,
391        };
392        Ok(ChainOwnership {
393            super_owners,
394            owners,
395            first_leader,
396            multi_leader_rounds,
397            open_multi_leader_rounds,
398            timeout_config,
399        })
400    }
401}
402
403#[derive(Debug, Clone, clap::Args)]
404pub struct ApplicationPermissionsConfig {
405    /// If present, only operations from the specified applications are allowed, and
406    /// no system operations. Otherwise all operations are allowed.
407    #[arg(long)]
408    pub execute_operations: Option<Vec<ApplicationId>>,
409    /// At least one operation or incoming message from each of these applications must occur in
410    /// every block.
411    #[arg(long)]
412    pub mandatory_applications: Option<Vec<ApplicationId>>,
413    /// These applications are allowed to close the current chain using the system API.
414    #[arg(long)]
415    pub close_chain: Option<Vec<ApplicationId>>,
416    /// These applications are allowed to change the application permissions on the current chain
417    /// using the system API.
418    #[arg(long)]
419    pub change_application_permissions: Option<Vec<ApplicationId>>,
420    /// These applications are allowed to call services as oracles on the current chain using the
421    /// system API.
422    #[arg(long)]
423    pub call_service_as_oracle: Option<Vec<ApplicationId>>,
424    /// These applications are allowed to make HTTP requests on the current chain using the system
425    /// API.
426    #[arg(long)]
427    pub make_http_requests: Option<Vec<ApplicationId>>,
428}
429
430impl From<ApplicationPermissionsConfig> for ApplicationPermissions {
431    fn from(config: ApplicationPermissionsConfig) -> ApplicationPermissions {
432        ApplicationPermissions {
433            execute_operations: config.execute_operations,
434            mandatory_applications: config.mandatory_applications.unwrap_or_default(),
435            close_chain: config.close_chain.unwrap_or_default(),
436            change_application_permissions: config
437                .change_application_permissions
438                .unwrap_or_default(),
439            call_service_as_oracle: config.call_service_as_oracle,
440            make_http_requests: config.make_http_requests,
441        }
442    }
443}
444
445#[derive(clap::ValueEnum, Clone, Copy, Debug, PartialEq, Eq)]
446pub enum ResourceControlPolicyConfig {
447    NoFees,
448    Testnet,
449    #[cfg(with_testing)]
450    OnlyFuel,
451    #[cfg(with_testing)]
452    AllCategories,
453}
454
455impl ResourceControlPolicyConfig {
456    pub fn into_policy(self) -> ResourceControlPolicy {
457        match self {
458            ResourceControlPolicyConfig::NoFees => ResourceControlPolicy::no_fees(),
459            ResourceControlPolicyConfig::Testnet => ResourceControlPolicy::testnet(),
460            #[cfg(with_testing)]
461            ResourceControlPolicyConfig::OnlyFuel => ResourceControlPolicy::only_fuel(),
462            #[cfg(with_testing)]
463            ResourceControlPolicyConfig::AllCategories => ResourceControlPolicy::all_categories(),
464        }
465    }
466}
467
468impl std::str::FromStr for ResourceControlPolicyConfig {
469    type Err = String;
470
471    fn from_str(s: &str) -> Result<Self, Self::Err> {
472        clap::ValueEnum::from_str(s, true)
473    }
474}
475
476impl fmt::Display for ResourceControlPolicyConfig {
477    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
478        write!(f, "{:?}", self)
479    }
480}