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