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