1use std::{collections::HashSet, fmt, iter};
5
6use linera_base::{
7 data_types::{ApplicationPermissions, TimeDelta},
8 identifiers::{AccountOwner, ApplicationId, ChainId, GenericApplicationId},
9 ownership::ChainOwnership,
10 time::Duration,
11};
12use linera_core::{
13 client::{
14 BlanketMessagePolicy, ChainClientOptions, MessagePolicy,
15 DEFAULT_CERTIFICATE_DOWNLOAD_BATCH_SIZE, 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, serde::Deserialize, tsify::Tsify)]
39#[tsify(from_wasm_abi)]
40#[group(skip)]
41#[serde(default, rename_all = "camelCase")]
42pub struct Options {
43 #[arg(long = "send-timeout-ms", default_value = "4000", value_parser = util::parse_millis)]
45 pub send_timeout: Duration,
46
47 #[arg(long = "recv-timeout-ms", default_value = "4000", value_parser = util::parse_millis)]
49 pub recv_timeout: Duration,
50
51 #[arg(long, default_value = "10")]
53 pub max_pending_message_bundles: usize,
54
55 #[arg(
57 long = "chain-worker-ttl-ms",
58 default_value = "30000",
59 env = "LINERA_CHAIN_WORKER_TTL_MS",
60 value_parser = util::parse_millis,
61 )]
62 pub chain_worker_ttl: Duration,
63
64 #[arg(
67 long = "sender-chain-worker-ttl-ms",
68 default_value = "1000",
69 env = "LINERA_SENDER_CHAIN_WORKER_TTL_MS",
70 value_parser = util::parse_millis
71 )]
72 pub sender_chain_worker_ttl: Duration,
73
74 #[arg(
76 long = "retry-delay-ms",
77 default_value = "1000",
78 value_parser = util::parse_millis
79 )]
80 pub retry_delay: Duration,
81
82 #[arg(long, default_value = "10")]
84 pub max_retries: u32,
85
86 #[arg(long)]
89 pub wait_for_outgoing_messages: bool,
90
91 #[arg(long)]
93 pub long_lived_services: bool,
94
95 #[arg(long, default_value_t, value_enum)]
97 pub blanket_message_policy: BlanketMessagePolicy,
98
99 #[arg(long, value_parser = util::parse_chain_set)]
103 pub restrict_chain_ids_to: Option<HashSet<ChainId>>,
104
105 #[arg(long, value_parser = util::parse_app_set)]
108 pub reject_message_bundles_without_application_ids: Option<HashSet<GenericApplicationId>>,
109
110 #[arg(long, value_parser = util::parse_app_set)]
113 pub reject_message_bundles_with_other_application_ids: Option<HashSet<GenericApplicationId>>,
114
115 #[cfg(not(web))]
117 #[arg(long)]
118 pub timings: bool,
119
120 #[cfg(not(web))]
122 #[arg(long, default_value = "5")]
123 pub timing_interval: u64,
124
125 #[arg(long, default_value_t = DEFAULT_QUORUM_GRACE_PERIOD)]
128 pub quorum_grace_period: f64,
129
130 #[arg(
132 long = "blob-download-timeout-ms",
133 default_value = "1000",
134 value_parser = util::parse_millis,
135 )]
136 pub blob_download_timeout: Duration,
137
138 #[arg(
141 long = "cert-batch-download-timeout-ms",
142 default_value = "1000",
143 value_parser = util::parse_millis
144 )]
145 pub certificate_batch_download_timeout: Duration,
146
147 #[arg(
150 long,
151 default_value_t = DEFAULT_CERTIFICATE_DOWNLOAD_BATCH_SIZE,
152 )]
153 pub certificate_download_batch_size: u64,
154
155 #[arg(
158 long,
159 default_value_t = DEFAULT_SENDER_CERTIFICATE_DOWNLOAD_BATCH_SIZE,
160 )]
161 pub sender_certificate_download_batch_size: usize,
162
163 #[arg(long, default_value = "100")]
165 pub max_joined_tasks: usize,
166
167 #[arg(
169 long,
170 default_value_t = linera_core::client::requests_scheduler::MAX_ACCEPTED_LATENCY_MS,
171 env = "LINERA_REQUESTS_SCHEDULER_MAX_ACCEPTED_LATENCY_MS"
172 )]
173 pub max_accepted_latency_ms: f64,
174
175 #[arg(
177 long,
178 default_value_t = linera_core::client::requests_scheduler::CACHE_TTL_MS,
179 env = "LINERA_REQUESTS_SCHEDULER_CACHE_TTL_MS"
180 )]
181 pub cache_ttl_ms: u64,
182
183 #[arg(
185 long,
186 default_value_t = linera_core::client::requests_scheduler::CACHE_MAX_SIZE,
187 env = "LINERA_REQUESTS_SCHEDULER_CACHE_MAX_SIZE"
188 )]
189 pub cache_max_size: usize,
190
191 #[arg(
193 long,
194 default_value_t = linera_core::client::requests_scheduler::MAX_REQUEST_TTL_MS,
195 env = "LINERA_REQUESTS_SCHEDULER_MAX_REQUEST_TTL_MS"
196 )]
197 pub max_request_ttl_ms: u64,
198
199 #[arg(
205 long,
206 default_value_t = linera_core::client::requests_scheduler::ALPHA_SMOOTHING_FACTOR,
207 env = "LINERA_REQUESTS_SCHEDULER_ALPHA"
208 )]
209 pub alpha: f64,
210
211 #[arg(
214 long,
215 default_value_t = linera_core::client::requests_scheduler::STAGGERED_DELAY_MS,
216 env = "LINERA_REQUESTS_SCHEDULER_ALTERNATIVE_PEERS_RETRY_DELAY_MS"
217 )]
218 pub alternative_peers_retry_delay_ms: u64,
219
220 #[serde(flatten)]
221 #[clap(flatten)]
222 pub chain_listener_config: crate::chain_listener::ChainListenerConfig,
223}
224
225impl Default for Options {
226 fn default() -> Self {
227 use clap::Parser;
228
229 #[derive(Parser)]
230 struct OptionsParser {
231 #[clap(flatten)]
232 options: Options,
233 }
234
235 OptionsParser::try_parse_from(std::iter::empty::<std::ffi::OsString>())
236 .expect("Options has no required arguments")
237 .options
238 }
239}
240
241impl Options {
242 pub(crate) fn to_chain_client_options(&self) -> ChainClientOptions {
244 let message_policy = MessagePolicy::new(
245 self.blanket_message_policy,
246 self.restrict_chain_ids_to.clone(),
247 self.reject_message_bundles_without_application_ids.clone(),
248 self.reject_message_bundles_with_other_application_ids
249 .clone(),
250 );
251 let cross_chain_message_delivery =
252 CrossChainMessageDelivery::new(self.wait_for_outgoing_messages);
253 ChainClientOptions {
254 max_pending_message_bundles: self.max_pending_message_bundles,
255 message_policy,
256 cross_chain_message_delivery,
257 quorum_grace_period: self.quorum_grace_period,
258 blob_download_timeout: self.blob_download_timeout,
259 certificate_batch_download_timeout: self.certificate_batch_download_timeout,
260 certificate_download_batch_size: self.certificate_download_batch_size,
261 sender_certificate_download_batch_size: self.sender_certificate_download_batch_size,
262 max_joined_tasks: self.max_joined_tasks,
263 }
264 }
265
266 #[cfg(not(web))]
268 pub(crate) fn to_timing_config(&self) -> TimingConfig {
269 TimingConfig {
270 enabled: self.timings,
271 report_interval_secs: self.timing_interval,
272 }
273 }
274
275 pub(crate) fn to_requests_scheduler_config(
277 &self,
278 ) -> linera_core::client::RequestsSchedulerConfig {
279 linera_core::client::RequestsSchedulerConfig {
280 max_accepted_latency_ms: self.max_accepted_latency_ms,
281 cache_ttl_ms: self.cache_ttl_ms,
282 cache_max_size: self.cache_max_size,
283 max_request_ttl_ms: self.max_request_ttl_ms,
284 alpha: self.alpha,
285 retry_delay_ms: self.alternative_peers_retry_delay_ms,
286 }
287 }
288}
289
290#[derive(Debug, Clone, clap::Args)]
291pub struct ChainOwnershipConfig {
292 #[arg(long, value_parser = util::parse_json::<Vec<AccountOwner>>)]
300 pub super_owners: Option<std::vec::Vec<AccountOwner>>,
301
302 #[arg(long, value_parser = util::parse_json::<Vec<AccountOwner>>)]
305 pub owners: Option<std::vec::Vec<AccountOwner>>,
306
307 #[arg(long, value_parser = util::parse_json::<Vec<u64>>)]
317 pub owner_weights: Option<std::vec::Vec<u64>>,
318
319 #[arg(long, value_parser = util::parse_json::<Option<u32>>)]
323 pub multi_leader_rounds: Option<std::option::Option<u32>>,
324
325 #[arg(long)]
329 pub open_multi_leader_rounds: bool,
330
331 #[arg(long = "fast-round-ms", value_parser = util::parse_json_optional_millis_delta)]
334 pub fast_round_duration: Option<std::option::Option<TimeDelta>>,
335
336 #[arg(
339 long = "base-timeout-ms",
340 value_parser = util::parse_millis_delta
341 )]
342 pub base_timeout: Option<TimeDelta>,
343
344 #[arg(
347 long = "timeout-increment-ms",
348 value_parser = util::parse_millis_delta
349 )]
350 pub timeout_increment: Option<TimeDelta>,
351
352 #[arg(
356 long = "fallback-duration-ms",
357 value_parser = util::parse_millis_delta
358 )]
359 pub fallback_duration: Option<TimeDelta>,
360}
361
362impl ChainOwnershipConfig {
363 pub fn update(self, chain_ownership: &mut ChainOwnership) -> Result<(), Error> {
364 let ChainOwnershipConfig {
365 super_owners,
366 owners,
367 owner_weights,
368 multi_leader_rounds,
369 fast_round_duration,
370 open_multi_leader_rounds,
371 base_timeout,
372 timeout_increment,
373 fallback_duration,
374 } = self;
375
376 if let Some(owner_weights) = owner_weights {
377 let owners = owners
378 .unwrap_or_else(|| chain_ownership.owners.keys().cloned().collect::<Vec<_>>());
379 if owner_weights.len() != owners.len() {
380 return Err(Error::MisalignedWeights {
381 public_keys: owners.len(),
382 weights: owner_weights.len(),
383 });
384 }
385 chain_ownership.owners = owners.into_iter().zip(owner_weights).collect();
386 } else if let Some(owners) = owners {
387 chain_ownership.owners = owners.into_iter().zip(iter::repeat(100)).collect();
388 }
389
390 if let Some(super_owners) = super_owners {
391 chain_ownership.super_owners = super_owners.into_iter().collect();
392 }
393
394 if let Some(multi_leader_rounds) = multi_leader_rounds {
395 chain_ownership.multi_leader_rounds = multi_leader_rounds.unwrap_or(u32::MAX);
396 }
397
398 chain_ownership.open_multi_leader_rounds = open_multi_leader_rounds;
399
400 if let Some(fast_round_duration) = fast_round_duration {
401 chain_ownership.timeout_config.fast_round_duration = fast_round_duration;
402 }
403 if let Some(base_timeout) = base_timeout {
404 chain_ownership.timeout_config.base_timeout = base_timeout;
405 }
406 if let Some(timeout_increment) = timeout_increment {
407 chain_ownership.timeout_config.timeout_increment = timeout_increment;
408 }
409 if let Some(fallback_duration) = fallback_duration {
410 chain_ownership.timeout_config.fallback_duration = fallback_duration;
411 }
412
413 Ok(())
414 }
415}
416
417impl TryFrom<ChainOwnershipConfig> for ChainOwnership {
418 type Error = Error;
419
420 fn try_from(config: ChainOwnershipConfig) -> Result<ChainOwnership, Error> {
421 let mut chain_ownership = ChainOwnership::default();
422 config.update(&mut chain_ownership)?;
423 Ok(chain_ownership)
424 }
425}
426
427#[derive(Debug, Clone, clap::Args)]
428pub struct ApplicationPermissionsConfig {
429 #[arg(long, value_parser = util::parse_json::<Option<Vec<ApplicationId>>>)]
439 pub execute_operations: Option<std::option::Option<Vec<ApplicationId>>>,
440 #[arg(long, value_parser = util::parse_json::<Vec<ApplicationId>>)]
444 pub mandatory_applications: Option<std::vec::Vec<ApplicationId>>,
445 #[arg(long, value_parser = util::parse_json::<Vec<ApplicationId>>)]
448 pub close_chain: Option<std::vec::Vec<ApplicationId>>,
449 #[arg(long, value_parser = util::parse_json::<Vec<ApplicationId>>)]
452 pub change_application_permissions: Option<std::vec::Vec<ApplicationId>>,
453 #[arg(long, value_parser = util::parse_json::<Option<Vec<ApplicationId>>>)]
457 pub call_service_as_oracle: Option<std::option::Option<Vec<ApplicationId>>>,
458 #[arg(long, value_parser = util::parse_json::<Option<Vec<ApplicationId>>>)]
462 pub make_http_requests: Option<std::option::Option<Vec<ApplicationId>>>,
463}
464
465impl ApplicationPermissionsConfig {
466 pub fn update(self, application_permissions: &mut ApplicationPermissions) {
467 if let Some(execute_operations) = self.execute_operations {
468 application_permissions.execute_operations = execute_operations;
469 }
470 if let Some(mandatory_applications) = self.mandatory_applications {
471 application_permissions.mandatory_applications = mandatory_applications;
472 }
473 if let Some(close_chain) = self.close_chain {
474 application_permissions.close_chain = close_chain;
475 }
476 if let Some(change_application_permissions) = self.change_application_permissions {
477 application_permissions.change_application_permissions = change_application_permissions;
478 }
479 if let Some(call_service_as_oracle) = self.call_service_as_oracle {
480 application_permissions.call_service_as_oracle = call_service_as_oracle;
481 }
482 if let Some(make_http_requests) = self.make_http_requests {
483 application_permissions.make_http_requests = make_http_requests;
484 }
485 }
486}
487
488#[derive(clap::ValueEnum, Clone, Copy, Debug, PartialEq, Eq)]
489pub enum ResourceControlPolicyConfig {
490 NoFees,
491 Testnet,
492 #[cfg(with_testing)]
493 OnlyFuel,
494 #[cfg(with_testing)]
495 AllCategories,
496}
497
498impl ResourceControlPolicyConfig {
499 pub fn into_policy(self) -> ResourceControlPolicy {
500 match self {
501 ResourceControlPolicyConfig::NoFees => ResourceControlPolicy::no_fees(),
502 ResourceControlPolicyConfig::Testnet => ResourceControlPolicy::testnet(),
503 #[cfg(with_testing)]
504 ResourceControlPolicyConfig::OnlyFuel => ResourceControlPolicy::only_fuel(),
505 #[cfg(with_testing)]
506 ResourceControlPolicyConfig::AllCategories => ResourceControlPolicy::all_categories(),
507 }
508 }
509}
510
511impl std::str::FromStr for ResourceControlPolicyConfig {
512 type Err = String;
513
514 fn from_str(s: &str) -> Result<Self, Self::Err> {
515 clap::ValueEnum::from_str(s, true)
516 }
517}
518
519impl fmt::Display for ResourceControlPolicyConfig {
520 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
521 write!(f, "{:?}", self)
522 }
523}