Skip to main content

linera_client/
client_options.rs

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