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},
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_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("persistence error: {0}")]
33    Persistence(#[from] Box<dyn std::error::Error + Send + Sync>),
34    #[error("config error: {0}")]
35    Config(#[from] crate::config::Error),
36}
37
38#[cfg(feature = "fs")]
39util::impl_from_dynamic!(Error:Persistence, linera_persistent::file::Error);
40
41#[cfg(web)]
42util::impl_from_dynamic!(Error:Persistence, linera_persistent::indexed_db::Error);
43
44util::impl_from_infallible!(Error);
45
46#[derive(Clone, clap::Parser)]
47pub struct ClientContextOptions {
48    /// Sets the file storing the private state of user chains (an empty one will be created if missing)
49    #[arg(long = "wallet")]
50    pub wallet_state_path: Option<PathBuf>,
51
52    /// Sets the file storing the keystore state.
53    #[arg(long = "keystore")]
54    pub keystore_path: Option<PathBuf>,
55
56    /// Given an ASCII alphanumeric parameter `X`, read the wallet state and the wallet
57    /// storage config from the environment variables `LINERA_WALLET_{X}` and
58    /// `LINERA_STORAGE_{X}` instead of `LINERA_WALLET` and
59    /// `LINERA_STORAGE`.
60    #[arg(long, short = 'w', value_parser = util::parse_ascii_alphanumeric_string)]
61    pub with_wallet: Option<String>,
62
63    /// Timeout for sending queries (milliseconds)
64    #[arg(long = "send-timeout-ms", default_value = "4000", value_parser = util::parse_millis)]
65    pub send_timeout: Duration,
66
67    /// Timeout for receiving responses (milliseconds)
68    #[arg(long = "recv-timeout-ms", default_value = "4000", value_parser = util::parse_millis)]
69    pub recv_timeout: Duration,
70
71    /// The maximum number of incoming message bundles to include in a block proposal.
72    #[arg(long, default_value = "10")]
73    pub max_pending_message_bundles: usize,
74
75    /// The duration in milliseconds after which an idle chain worker will free its memory.
76    #[arg(
77        long = "chain-worker-ttl-ms",
78        default_value = "30000",
79        env = "LINERA_CHAIN_WORKER_TTL_MS",
80        value_parser = util::parse_millis
81    )]
82    pub chain_worker_ttl: Duration,
83
84    /// The duration, in milliseconds, after which an idle sender chain worker will
85    /// free its memory.
86    #[arg(
87        long = "sender-chain-worker-ttl-ms",
88        default_value = "1000",
89        env = "LINERA_SENDER_CHAIN_WORKER_TTL_MS",
90        value_parser = util::parse_millis
91    )]
92    pub sender_chain_worker_ttl: Duration,
93
94    /// Delay increment for retrying to connect to a validator.
95    #[arg(
96        long = "retry-delay-ms",
97        default_value = "1000",
98        value_parser = util::parse_millis
99    )]
100    pub retry_delay: Duration,
101
102    /// Number of times to retry connecting to a validator.
103    #[arg(long, default_value = "10")]
104    pub max_retries: u32,
105
106    /// Enable OpenTelemetry Chrome JSON exporter for trace data analysis.
107    #[arg(long)]
108    pub chrome_trace_exporter: bool,
109
110    /// Output file path for Chrome trace JSON format.
111    /// Can be visualized in chrome://tracing or Perfetto UI.
112    #[arg(long, env = "LINERA_CHROME_TRACE_FILE")]
113    pub chrome_trace_file: Option<String>,
114
115    /// OpenTelemetry OTLP exporter endpoint (requires opentelemetry feature).
116    #[arg(long, env = "LINERA_OTLP_EXPORTER_ENDPOINT")]
117    pub otlp_exporter_endpoint: Option<String>,
118
119    /// Whether to wait until a quorum of validators has confirmed that all sent cross-chain
120    /// messages have been delivered.
121    #[arg(long)]
122    pub wait_for_outgoing_messages: bool,
123
124    /// (EXPERIMENTAL) Whether application services can persist in some cases between queries.
125    #[arg(long)]
126    pub long_lived_services: bool,
127
128    /// The policy for handling incoming messages.
129    #[arg(long, default_value = "accept")]
130    pub blanket_message_policy: BlanketMessagePolicy,
131
132    /// A set of chains to restrict incoming messages from. By default, messages
133    /// from all chains are accepted. To reject messages from all chains, specify
134    /// an empty string.
135    #[arg(long, value_parser = util::parse_chain_set)]
136    pub restrict_chain_ids_to: Option<HashSet<ChainId>>,
137
138    /// Enable timing reports during operations
139    #[cfg(not(web))]
140    #[arg(long)]
141    pub timings: bool,
142
143    /// Interval in seconds between timing reports (defaults to 5)
144    #[cfg(not(web))]
145    #[arg(long, default_value = "5")]
146    pub timing_interval: u64,
147
148    /// An additional delay, after reaching a quorum, to wait for additional validator signatures,
149    /// as a fraction of time taken to reach quorum.
150    #[arg(long, default_value_t = DEFAULT_GRACE_PERIOD)]
151    pub grace_period: f64,
152
153    /// The delay when downloading a blob, after which we try a second validator, in milliseconds.
154    #[arg(
155        long = "blob-download-timeout-ms",
156        default_value = "1000",
157        value_parser = util::parse_millis
158    )]
159    pub blob_download_timeout: Duration,
160
161    /// The delay when downloading a batch of certificates, after which we try a second validator,
162    /// in milliseconds.
163    #[arg(
164        long = "cert-batch-download-timeout-ms",
165        default_value = "1000",
166        value_parser = util::parse_millis
167    )]
168    pub certificate_batch_download_timeout: Duration,
169
170    /// Maximum number of certificates that we download at a time from one validator when
171    /// synchronizing one of our chains.
172    #[arg(
173        long,
174        default_value_t = DEFAULT_CERTIFICATE_DOWNLOAD_BATCH_SIZE,
175    )]
176    pub certificate_download_batch_size: u64,
177
178    /// Maximum number of sender certificates we try to download and receive in one go
179    /// when syncing sender chains.
180    #[arg(
181        long,
182        default_value_t = DEFAULT_SENDER_CERTIFICATE_DOWNLOAD_BATCH_SIZE,
183    )]
184    pub sender_certificate_download_batch_size: usize,
185
186    /// Maximum number of tasks that can are joined concurrently in the client.
187    #[arg(long, default_value = "100")]
188    pub max_joined_tasks: usize,
189}
190
191impl ClientContextOptions {
192    /// Creates [`chain_client::Options`] with the corresponding values.
193    pub(crate) fn to_chain_client_options(&self) -> chain_client::Options {
194        let message_policy = MessagePolicy::new(
195            self.blanket_message_policy,
196            self.restrict_chain_ids_to.clone(),
197        );
198        let cross_chain_message_delivery =
199            CrossChainMessageDelivery::new(self.wait_for_outgoing_messages);
200        chain_client::Options {
201            max_pending_message_bundles: self.max_pending_message_bundles,
202            message_policy,
203            cross_chain_message_delivery,
204            grace_period: self.grace_period,
205            blob_download_timeout: self.blob_download_timeout,
206            certificate_batch_download_timeout: self.certificate_batch_download_timeout,
207            certificate_download_batch_size: self.certificate_download_batch_size,
208            sender_certificate_download_batch_size: self.sender_certificate_download_batch_size,
209            max_joined_tasks: self.max_joined_tasks,
210        }
211    }
212
213    /// Creates [`TimingConfig`] with the corresponding values.
214    #[cfg(not(web))]
215    pub(crate) fn to_timing_config(&self) -> TimingConfig {
216        TimingConfig {
217            enabled: self.timings,
218            report_interval_secs: self.timing_interval,
219        }
220    }
221}
222
223#[derive(Debug, Clone, clap::Args)]
224pub struct ChainOwnershipConfig {
225    /// The new super owners.
226    #[arg(long, num_args(0..))]
227    pub super_owners: Vec<AccountOwner>,
228
229    /// The new regular owners.
230    #[arg(long, num_args(0..))]
231    pub owners: Vec<AccountOwner>,
232
233    /// Weights for the new owners.
234    ///
235    /// If they are specified there must be exactly one weight for each owner.
236    /// If no weights are given, every owner will have weight 100.
237    #[arg(long, num_args(0..))]
238    pub owner_weights: Vec<u64>,
239
240    /// The number of rounds in which every owner can propose blocks, i.e. the first round
241    /// number in which only a single designated leader is allowed to propose blocks.
242    #[arg(long)]
243    pub multi_leader_rounds: Option<u32>,
244
245    /// Whether the multi-leader rounds are unrestricted, i.e. not limited to chain owners.
246    /// This should only be `true` on chains with restrictive application permissions and an
247    /// application-based mechanism to select block proposers.
248    #[arg(long)]
249    pub open_multi_leader_rounds: bool,
250
251    /// The duration of the fast round, in milliseconds.
252    #[arg(long = "fast-round-ms", value_parser = util::parse_millis_delta)]
253    pub fast_round_duration: Option<TimeDelta>,
254
255    /// The duration of the first single-leader and all multi-leader rounds.
256    #[arg(
257        long = "base-timeout-ms",
258        default_value = "10000",
259        value_parser = util::parse_millis_delta
260    )]
261    pub base_timeout: TimeDelta,
262
263    /// The number of milliseconds by which the timeout increases after each
264    /// single-leader round.
265    #[arg(
266        long = "timeout-increment-ms",
267        default_value = "1000",
268        value_parser = util::parse_millis_delta
269    )]
270    pub timeout_increment: TimeDelta,
271
272    /// The age of an incoming tracked or protected message after which the validators start
273    /// transitioning the chain to fallback mode, in milliseconds.
274    #[arg(
275        long = "fallback-duration-ms",
276        default_value = "86400000", // 1 day
277        value_parser = util::parse_millis_delta
278    )]
279    pub fallback_duration: TimeDelta,
280}
281
282impl TryFrom<ChainOwnershipConfig> for ChainOwnership {
283    type Error = Error;
284
285    fn try_from(config: ChainOwnershipConfig) -> Result<ChainOwnership, Error> {
286        let ChainOwnershipConfig {
287            super_owners,
288            owners,
289            owner_weights,
290            multi_leader_rounds,
291            fast_round_duration,
292            open_multi_leader_rounds,
293            base_timeout,
294            timeout_increment,
295            fallback_duration,
296        } = config;
297        if !owner_weights.is_empty() && owner_weights.len() != owners.len() {
298            return Err(Error::MisalignedWeights {
299                public_keys: owners.len(),
300                weights: owner_weights.len(),
301            });
302        }
303        let super_owners = super_owners.into_iter().collect();
304        let owners = owners
305            .into_iter()
306            .zip(owner_weights.into_iter().chain(iter::repeat(100)))
307            .collect();
308        let multi_leader_rounds = multi_leader_rounds.unwrap_or(u32::MAX);
309        let timeout_config = TimeoutConfig {
310            fast_round_duration,
311            base_timeout,
312            timeout_increment,
313            fallback_duration,
314        };
315        Ok(ChainOwnership {
316            super_owners,
317            owners,
318            multi_leader_rounds,
319            open_multi_leader_rounds,
320            timeout_config,
321        })
322    }
323}
324
325#[derive(Debug, Clone, clap::Args)]
326pub struct ApplicationPermissionsConfig {
327    /// If present, only operations from the specified applications are allowed, and
328    /// no system operations. Otherwise all operations are allowed.
329    #[arg(long)]
330    pub execute_operations: Option<Vec<ApplicationId>>,
331    /// At least one operation or incoming message from each of these applications must occur in
332    /// every block.
333    #[arg(long)]
334    pub mandatory_applications: Option<Vec<ApplicationId>>,
335    /// These applications are allowed to close the current chain using the system API.
336    #[arg(long)]
337    pub close_chain: Option<Vec<ApplicationId>>,
338    /// These applications are allowed to change the application permissions on the current chain
339    /// using the system API.
340    #[arg(long)]
341    pub change_application_permissions: Option<Vec<ApplicationId>>,
342    /// These applications are allowed to call services as oracles on the current chain using the
343    /// system API.
344    #[arg(long)]
345    pub call_service_as_oracle: Option<Vec<ApplicationId>>,
346    /// These applications are allowed to make HTTP requests on the current chain using the system
347    /// API.
348    #[arg(long)]
349    pub make_http_requests: Option<Vec<ApplicationId>>,
350}
351
352impl From<ApplicationPermissionsConfig> for ApplicationPermissions {
353    fn from(config: ApplicationPermissionsConfig) -> ApplicationPermissions {
354        ApplicationPermissions {
355            execute_operations: config.execute_operations,
356            mandatory_applications: config.mandatory_applications.unwrap_or_default(),
357            close_chain: config.close_chain.unwrap_or_default(),
358            change_application_permissions: config
359                .change_application_permissions
360                .unwrap_or_default(),
361            call_service_as_oracle: config.call_service_as_oracle,
362            make_http_requests: config.make_http_requests,
363        }
364    }
365}
366
367#[derive(clap::ValueEnum, Clone, Copy, Debug, PartialEq, Eq)]
368pub enum ResourceControlPolicyConfig {
369    NoFees,
370    Testnet,
371    #[cfg(with_testing)]
372    OnlyFuel,
373    #[cfg(with_testing)]
374    AllCategories,
375}
376
377impl ResourceControlPolicyConfig {
378    pub fn into_policy(self) -> ResourceControlPolicy {
379        match self {
380            ResourceControlPolicyConfig::NoFees => ResourceControlPolicy::no_fees(),
381            ResourceControlPolicyConfig::Testnet => ResourceControlPolicy::testnet(),
382            #[cfg(with_testing)]
383            ResourceControlPolicyConfig::OnlyFuel => ResourceControlPolicy::only_fuel(),
384            #[cfg(with_testing)]
385            ResourceControlPolicyConfig::AllCategories => ResourceControlPolicy::all_categories(),
386        }
387    }
388}
389
390impl std::str::FromStr for ResourceControlPolicyConfig {
391    type Err = String;
392
393    fn from_str(s: &str) -> Result<Self, Self::Err> {
394        clap::ValueEnum::from_str(s, true)
395    }
396}
397
398impl fmt::Display for ResourceControlPolicyConfig {
399    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
400        write!(f, "{:?}", self)
401    }
402}