linera_service/cli/
command.rs

1// Copyright (c) Zefchain Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use std::{borrow::Cow, num::NonZeroU16, path::PathBuf};
5
6use chrono::{DateTime, Utc};
7use linera_base::{
8    crypto::{AccountPublicKey, CryptoHash, ValidatorPublicKey},
9    data_types::{Amount, BlockHeight, Epoch},
10    identifiers::{Account, AccountOwner, ApplicationId, ChainId, ModuleId, StreamId},
11    time::Duration,
12    vm::VmRuntime,
13};
14use linera_client::{
15    chain_listener::ChainListenerConfig,
16    client_options::{
17        ApplicationPermissionsConfig, ChainOwnershipConfig, ResourceControlPolicyConfig,
18    },
19    util,
20};
21use linera_rpc::config::CrossChainConfig;
22
23use crate::{cli::validator, task_processor::parse_operator};
24
25const DEFAULT_TOKENS_PER_CHAIN: Amount = Amount::from_millis(100);
26const DEFAULT_TRANSACTIONS_PER_BLOCK: usize = 1;
27const DEFAULT_WRAP_UP_MAX_IN_FLIGHT: usize = 5;
28const DEFAULT_NUM_CHAINS: usize = 10;
29const DEFAULT_BPS: usize = 10;
30
31/// Specification for a validator to be added to the committee.
32#[derive(Clone, Debug)]
33pub struct ValidatorToAdd {
34    pub public_key: ValidatorPublicKey,
35    pub account_key: AccountPublicKey,
36    pub address: String,
37    pub votes: u64,
38}
39
40impl std::str::FromStr for ValidatorToAdd {
41    type Err = anyhow::Error;
42
43    fn from_str(s: &str) -> Result<Self, Self::Err> {
44        let parts: Vec<&str> = s.split(',').collect();
45        anyhow::ensure!(
46            parts.len() == 4,
47            "Validator spec must be in format: public_key,account_key,address,votes"
48        );
49
50        Ok(ValidatorToAdd {
51            public_key: parts[0].parse()?,
52            account_key: parts[1].parse()?,
53            address: parts[2].to_string(),
54            votes: parts[3].parse()?,
55        })
56    }
57}
58
59#[derive(Clone, clap::Args, serde::Serialize)]
60#[serde(rename_all = "kebab-case")]
61pub struct BenchmarkOptions {
62    /// How many chains to use.
63    #[arg(long, default_value_t = DEFAULT_NUM_CHAINS)]
64    pub num_chains: usize,
65
66    /// How many tokens to assign to each newly created chain.
67    /// These need to cover the transaction fees per chain for the benchmark.
68    #[arg(long, default_value_t = DEFAULT_TOKENS_PER_CHAIN)]
69    pub tokens_per_chain: Amount,
70
71    /// How many transactions to put in each block.
72    #[arg(long, default_value_t = DEFAULT_TRANSACTIONS_PER_BLOCK)]
73    pub transactions_per_block: usize,
74
75    /// The application ID of a fungible token on the wallet's default chain.
76    /// If none is specified, the benchmark uses the native token.
77    #[arg(long)]
78    pub fungible_application_id: Option<ApplicationId>,
79
80    /// The fixed BPS (Blocks Per Second) rate that block proposals will be sent at.
81    #[arg(long, default_value_t = DEFAULT_BPS)]
82    pub bps: usize,
83
84    /// If provided, will close the chains after the benchmark is finished. Keep in mind that
85    /// closing the chains might take a while, and will increase the validator latency while
86    /// they're being closed.
87    #[arg(long)]
88    pub close_chains: bool,
89
90    /// A comma-separated list of host:port pairs to query for health metrics.
91    /// If provided, the benchmark will check these endpoints for validator health
92    /// and terminate if any validator is unhealthy.
93    /// Example: "127.0.0.1:21100,validator-1.some-network.linera.net:21100"
94    #[arg(long)]
95    pub health_check_endpoints: Option<String>,
96
97    /// The maximum number of in-flight requests to validators when wrapping up the benchmark.
98    /// While wrapping up, this controls the concurrency level when processing inboxes and
99    /// closing chains.
100    #[arg(long, default_value_t = DEFAULT_WRAP_UP_MAX_IN_FLIGHT)]
101    pub wrap_up_max_in_flight: usize,
102
103    /// Confirm before starting the benchmark.
104    #[arg(long)]
105    pub confirm_before_start: bool,
106
107    /// How long to run the benchmark for. If not provided, the benchmark will run until
108    /// it is interrupted.
109    #[arg(long)]
110    pub runtime_in_seconds: Option<u64>,
111
112    /// The delay between chains, in milliseconds. For example, if set to 200ms, the first
113    /// chain will start, then the second will start 200 ms after the first one, the third
114    /// 200 ms after the second one, and so on.
115    /// This is used for slowly ramping up the TPS, so we don't pound the validators with the full
116    /// TPS all at once.
117    #[arg(long)]
118    pub delay_between_chains_ms: Option<u64>,
119
120    /// Path to YAML file containing chain IDs to send transfers to.
121    /// If not provided, only transfers between chains in the same wallet.
122    #[arg(long)]
123    pub config_path: Option<PathBuf>,
124
125    /// Transaction distribution mode. If false (default), distributes transactions evenly
126    /// across chains within each block. If true, sends all transactions in each block
127    /// to a single chain, rotating through chains for subsequent blocks.
128    #[arg(long)]
129    pub single_destination_per_block: bool,
130}
131
132impl Default for BenchmarkOptions {
133    fn default() -> Self {
134        Self {
135            num_chains: DEFAULT_NUM_CHAINS,
136            tokens_per_chain: DEFAULT_TOKENS_PER_CHAIN,
137            transactions_per_block: DEFAULT_TRANSACTIONS_PER_BLOCK,
138            wrap_up_max_in_flight: DEFAULT_WRAP_UP_MAX_IN_FLIGHT,
139            fungible_application_id: None,
140            bps: DEFAULT_BPS,
141            close_chains: false,
142            health_check_endpoints: None,
143            confirm_before_start: false,
144            runtime_in_seconds: None,
145            delay_between_chains_ms: None,
146            config_path: None,
147            single_destination_per_block: false,
148        }
149    }
150}
151
152#[derive(Clone, clap::Subcommand, serde::Serialize)]
153#[serde(rename_all = "kebab-case")]
154pub enum BenchmarkCommand {
155    /// Start a single benchmark process, maintaining a given TPS.
156    Single {
157        #[command(flatten)]
158        options: BenchmarkOptions,
159    },
160
161    /// Run multiple benchmark processes in parallel.
162    Multi {
163        #[command(flatten)]
164        options: BenchmarkOptions,
165
166        /// The number of benchmark processes to run in parallel.
167        #[arg(long, default_value = "1")]
168        processes: usize,
169
170        /// The faucet (which implicitly defines the network)
171        #[arg(long, env = "LINERA_FAUCET_URL")]
172        faucet: String,
173
174        /// If specified, a directory with a random name will be created in this directory, and the
175        /// client state will be stored there.
176        /// If not specified, a temporary directory will be used for each client.
177        #[arg(long)]
178        client_state_dir: Option<String>,
179
180        /// The delay between starting the benchmark processes, in seconds.
181        /// If --cross-wallet-transfers is true, this will be ignored.
182        #[arg(long, default_value = "10")]
183        delay_between_processes: u64,
184
185        /// Whether to send transfers between chains in different wallets.
186        #[arg(long)]
187        cross_wallet_transfers: bool,
188    },
189}
190
191impl BenchmarkCommand {
192    pub fn transactions_per_block(&self) -> usize {
193        match self {
194            Self::Single { options } => options.transactions_per_block,
195            Self::Multi { options, .. } => options.transactions_per_block,
196        }
197    }
198}
199
200#[cfg(feature = "kubernetes")]
201use crate::cli_wrappers::local_kubernetes_net::BuildMode;
202use crate::util::{
203    DEFAULT_PAUSE_AFTER_GQL_MUTATIONS_SECS, DEFAULT_PAUSE_AFTER_LINERA_SERVICE_SECS,
204};
205
206#[derive(Clone, clap::Subcommand)]
207pub enum ClientCommand {
208    /// Transfer funds
209    Transfer {
210        /// Sending chain ID (must be one of our chains)
211        #[arg(long = "from")]
212        sender: Account,
213
214        /// Recipient account
215        #[arg(long = "to")]
216        recipient: Account,
217
218        /// Amount to transfer
219        amount: Amount,
220    },
221
222    /// Open (i.e. activate) a new chain deriving the UID from an existing one.
223    OpenChain {
224        /// Chain ID (must be one of our chains).
225        #[arg(long = "from")]
226        chain_id: Option<ChainId>,
227
228        /// The new owner (otherwise create a key pair and remember it)
229        #[arg(long = "owner")]
230        owner: Option<AccountOwner>,
231
232        /// The initial balance of the new chain. This is subtracted from the parent chain's
233        /// balance.
234        #[arg(long = "initial-balance", default_value = "0")]
235        balance: Amount,
236
237        /// Whether to create a super owner for the new chain.
238        #[arg(long)]
239        super_owner: bool,
240    },
241
242    /// Open (i.e. activate) a new multi-owner chain deriving the UID from an existing one.
243    OpenMultiOwnerChain {
244        /// Chain ID (must be one of our chains).
245        #[arg(long = "from")]
246        chain_id: Option<ChainId>,
247
248        #[clap(flatten)]
249        ownership_config: ChainOwnershipConfig,
250
251        #[clap(flatten)]
252        application_permissions_config: ApplicationPermissionsConfig,
253
254        /// The initial balance of the new chain. This is subtracted from the parent chain's
255        /// balance.
256        #[arg(long = "initial-balance", default_value = "0")]
257        balance: Amount,
258    },
259
260    /// Display who owns the chain, and how the owners work together proposing blocks.
261    ShowOwnership {
262        /// The ID of the chain whose owners will be changed.
263        #[clap(long)]
264        chain_id: Option<ChainId>,
265    },
266
267    /// Change who owns the chain, and how the owners work together proposing blocks.
268    ///
269    /// Specify the complete set of new owners, by public key. Existing owners that are
270    /// not included will be removed.
271    ChangeOwnership {
272        /// The ID of the chain whose owners will be changed.
273        #[clap(long)]
274        chain_id: Option<ChainId>,
275
276        #[clap(flatten)]
277        ownership_config: ChainOwnershipConfig,
278    },
279
280    /// Change the preferred owner of a chain.
281    SetPreferredOwner {
282        /// The ID of the chain whose preferred owner will be changed.
283        #[clap(long)]
284        chain_id: Option<ChainId>,
285
286        /// The new preferred owner.
287        #[arg(long)]
288        owner: AccountOwner,
289    },
290
291    /// Changes the application permissions configuration.
292    ChangeApplicationPermissions {
293        /// The ID of the chain to which the new permissions will be applied.
294        #[arg(long)]
295        chain_id: Option<ChainId>,
296
297        #[clap(flatten)]
298        application_permissions_config: ApplicationPermissionsConfig,
299    },
300
301    /// Close an existing chain.
302    ///
303    /// A closed chain cannot execute operations or accept messages anymore.
304    /// It can still reject incoming messages, so they bounce back to the sender.
305    CloseChain {
306        /// Chain ID (must be one of our chains)
307        chain_id: ChainId,
308    },
309
310    /// Print out the network description.
311    ShowNetworkDescription,
312
313    /// Read the current native-token balance of the given account directly from the local
314    /// state.
315    ///
316    /// NOTE: The local balance does not reflect messages that are waiting to be picked in
317    /// the local inbox, or that have not been synchronized from validators yet. Use
318    /// `linera sync` then either `linera query-balance` or `linera process-inbox &&
319    /// linera local-balance` for a consolidated balance.
320    LocalBalance {
321        /// The account to read, written as `OWNER@CHAIN-ID` or simply `CHAIN-ID` for the
322        /// chain balance. By default, we read the chain balance of the default chain in
323        /// the wallet.
324        account: Option<Account>,
325    },
326
327    /// Simulate the execution of one block made of pending messages from the local inbox,
328    /// then read the native-token balance of the account from the local state.
329    ///
330    /// NOTE: The balance does not reflect messages that have not been synchronized from
331    /// validators yet. Call `linera sync` first to do so.
332    QueryBalance {
333        /// The account to query, written as `OWNER@CHAIN-ID` or simply `CHAIN-ID` for the
334        /// chain balance. By default, we read the chain balance of the default chain in
335        /// the wallet.
336        account: Option<Account>,
337    },
338
339    /// (DEPRECATED) Synchronize the local state of the chain with a quorum validators, then query the
340    /// local balance.
341    ///
342    /// This command is deprecated. Use `linera sync && linera query-balance` instead.
343    SyncBalance {
344        /// The account to query, written as `OWNER@CHAIN-ID` or simply `CHAIN-ID` for the
345        /// chain balance. By default, we read the chain balance of the default chain in
346        /// the wallet.
347        account: Option<Account>,
348    },
349
350    /// Synchronize the local state of the chain with a quorum validators.
351    Sync {
352        /// The chain to synchronize with validators. If omitted, synchronizes the
353        /// default chain of the wallet.
354        chain_id: Option<ChainId>,
355    },
356
357    /// Process all pending incoming messages from the inbox of the given chain by creating as many
358    /// blocks as needed to execute all (non-failing) messages. Failing messages will be
359    /// marked as rejected and may bounce to their sender depending on their configuration.
360    ProcessInbox {
361        /// The chain to process. If omitted, uses the default chain of the wallet.
362        chain_id: Option<ChainId>,
363    },
364
365    /// Query validators for shard information about a specific chain.
366    QueryShardInfo {
367        /// The chain to query shard information for.
368        chain_id: ChainId,
369    },
370
371    /// Deprecates all committees up to and including the specified one.
372    RevokeEpochs { epoch: Epoch },
373
374    /// View or update the resource control policy
375    ResourceControlPolicy {
376        /// Set the price per unit of Wasm fuel.
377        #[arg(long)]
378        wasm_fuel_unit: Option<Amount>,
379
380        /// Set the price per unit of EVM fuel.
381        #[arg(long)]
382        evm_fuel_unit: Option<Amount>,
383
384        /// Set the price per read operation.
385        #[arg(long)]
386        read_operation: Option<Amount>,
387
388        /// Set the price per write operation.
389        #[arg(long)]
390        write_operation: Option<Amount>,
391
392        /// Set the price per byte read from runtime.
393        #[arg(long)]
394        byte_runtime: Option<Amount>,
395
396        /// Set the price per byte read.
397        #[arg(long)]
398        byte_read: Option<Amount>,
399
400        /// Set the price per byte written.
401        #[arg(long)]
402        byte_written: Option<Amount>,
403
404        /// Set the base price to read a blob.
405        #[arg(long)]
406        blob_read: Option<Amount>,
407
408        /// Set the base price to publish a blob.
409        #[arg(long)]
410        blob_published: Option<Amount>,
411
412        /// Set the price to read a blob, per byte.
413        #[arg(long)]
414        blob_byte_read: Option<Amount>,
415
416        /// The price to publish a blob, per byte.
417        #[arg(long)]
418        blob_byte_published: Option<Amount>,
419
420        /// Set the price per byte stored.
421        #[arg(long)]
422        byte_stored: Option<Amount>,
423
424        /// Set the base price of sending an operation from a block..
425        #[arg(long)]
426        operation: Option<Amount>,
427
428        /// Set the additional price for each byte in the argument of a user operation.
429        #[arg(long)]
430        operation_byte: Option<Amount>,
431
432        /// Set the base price of sending a message from a block..
433        #[arg(long)]
434        message: Option<Amount>,
435
436        /// Set the additional price for each byte in the argument of a user message.
437        #[arg(long)]
438        message_byte: Option<Amount>,
439
440        /// Set the price per query to a service as an oracle.
441        #[arg(long)]
442        service_as_oracle_query: Option<Amount>,
443
444        /// Set the price for performing an HTTP request.
445        #[arg(long)]
446        http_request: Option<Amount>,
447
448        /// Set the maximum amount of Wasm fuel per block.
449        #[arg(long)]
450        maximum_wasm_fuel_per_block: Option<u64>,
451
452        /// Set the maximum amount of EVM fuel per block.
453        #[arg(long)]
454        maximum_evm_fuel_per_block: Option<u64>,
455
456        /// Set the maximum time in milliseconds that a block can spend executing services as oracles.
457        #[arg(long)]
458        maximum_service_oracle_execution_ms: Option<u64>,
459
460        /// Set the maximum size of a block, in bytes.
461        #[arg(long)]
462        maximum_block_size: Option<u64>,
463
464        /// Set the maximum size of data blobs, compressed bytecode and other binary blobs,
465        /// in bytes.
466        #[arg(long)]
467        maximum_blob_size: Option<u64>,
468
469        /// Set the maximum number of published blobs per block.
470        #[arg(long)]
471        maximum_published_blobs: Option<u64>,
472
473        /// Set the maximum size of decompressed contract or service bytecode, in bytes.
474        #[arg(long)]
475        maximum_bytecode_size: Option<u64>,
476
477        /// Set the maximum size of a block proposal, in bytes.
478        #[arg(long)]
479        maximum_block_proposal_size: Option<u64>,
480
481        /// Set the maximum read data per block.
482        #[arg(long)]
483        maximum_bytes_read_per_block: Option<u64>,
484
485        /// Set the maximum write data per block.
486        #[arg(long)]
487        maximum_bytes_written_per_block: Option<u64>,
488
489        /// Set the maximum size of oracle responses.
490        #[arg(long)]
491        maximum_oracle_response_bytes: Option<u64>,
492
493        /// Set the maximum size in bytes of a received HTTP response.
494        #[arg(long)]
495        maximum_http_response_bytes: Option<u64>,
496
497        /// Set the maximum amount of time allowed to wait for an HTTP response.
498        #[arg(long)]
499        http_request_timeout_ms: Option<u64>,
500
501        /// Set the list of hosts that contracts and services can send HTTP requests to.
502        #[arg(long, value_delimiter = ',')]
503        http_request_allow_list: Option<Vec<String>>,
504
505        /// Set the list of application IDs for which message- and event-related fees are waived.
506        #[arg(long, value_delimiter = ',')]
507        free_application_ids: Option<Vec<String>>,
508    },
509
510    /// Run benchmarks to test network performance.
511    #[command(subcommand)]
512    Benchmark(BenchmarkCommand),
513
514    /// Create genesis configuration for a Linera deployment.
515    /// Create initial user chains and print information to be used for initialization of validator setup.
516    /// This will also create an initial wallet for the owner of the initial "root" chains.
517    CreateGenesisConfig {
518        /// Sets the file describing the public configurations of all validators
519        #[arg(long = "committee")]
520        committee_config_path: PathBuf,
521
522        /// The output config path to be consumed by the server
523        #[arg(long = "genesis")]
524        genesis_config_path: PathBuf,
525
526        /// Known initial balance of the chain
527        #[arg(long, default_value = "0")]
528        initial_funding: Amount,
529
530        /// The start timestamp: no blocks can be created before this time.
531        #[arg(long)]
532        start_timestamp: Option<DateTime<Utc>>,
533
534        /// Number of initial (aka "root") chains to create in addition to the admin chain.
535        num_other_initial_chains: u32,
536
537        /// Configure the resource control policy (notably fees) according to pre-defined
538        /// settings.
539        #[arg(long, default_value = "no-fees")]
540        policy_config: ResourceControlPolicyConfig,
541
542        /// Set the price per unit of Wasm fuel.
543        /// (This will overwrite value from `--policy-config`)
544        #[arg(long)]
545        wasm_fuel_unit_price: Option<Amount>,
546
547        /// Set the price per unit of EVM fuel.
548        /// (This will overwrite value from `--policy-config`)
549        #[arg(long)]
550        evm_fuel_unit_price: Option<Amount>,
551
552        /// Set the price per read operation.
553        /// (This will overwrite value from `--policy-config`)
554        #[arg(long)]
555        read_operation_price: Option<Amount>,
556
557        /// Set the price per write operation.
558        /// (This will overwrite value from `--policy-config`)
559        #[arg(long)]
560        write_operation_price: Option<Amount>,
561
562        /// Set the price per byte read from runtime.
563        /// (This will overwrite value from `--policy-config`)
564        #[arg(long)]
565        byte_runtime_price: Option<Amount>,
566
567        /// Set the price per byte read.
568        /// (This will overwrite value from `--policy-config`)
569        #[arg(long)]
570        byte_read_price: Option<Amount>,
571
572        /// Set the price per byte written.
573        /// (This will overwrite value from `--policy-config`)
574        #[arg(long)]
575        byte_written_price: Option<Amount>,
576
577        /// Set the base price to read a blob.
578        /// (This will overwrite value from `--policy-config`)
579        #[arg(long)]
580        blob_read_price: Option<Amount>,
581
582        /// Set the base price to publish a blob.
583        /// (This will overwrite value from `--policy-config`)
584        #[arg(long)]
585        blob_published_price: Option<Amount>,
586
587        /// Set the price to read a blob, per byte.
588        /// (This will overwrite value from `--policy-config`)
589        #[arg(long)]
590        blob_byte_read_price: Option<Amount>,
591
592        /// Set the price to publish a blob, per byte.
593        /// (This will overwrite value from `--policy-config`)
594        #[arg(long)]
595        blob_byte_published_price: Option<Amount>,
596
597        /// Set the price per byte stored.
598        /// (This will overwrite value from `--policy-config`)
599        #[arg(long)]
600        byte_stored_price: Option<Amount>,
601
602        /// Set the base price of sending an operation from a block..
603        /// (This will overwrite value from `--policy-config`)
604        #[arg(long)]
605        operation_price: Option<Amount>,
606
607        /// Set the additional price for each byte in the argument of a user operation.
608        /// (This will overwrite value from `--policy-config`)
609        #[arg(long)]
610        operation_byte_price: Option<Amount>,
611
612        /// Set the base price of sending a message from a block..
613        /// (This will overwrite value from `--policy-config`)
614        #[arg(long)]
615        message_price: Option<Amount>,
616
617        /// Set the additional price for each byte in the argument of a user message.
618        /// (This will overwrite value from `--policy-config`)
619        #[arg(long)]
620        message_byte_price: Option<Amount>,
621
622        /// Set the price per query to a service as an oracle.
623        #[arg(long)]
624        service_as_oracle_query_price: Option<Amount>,
625
626        /// Set the price for performing an HTTP request.
627        #[arg(long)]
628        http_request_price: Option<Amount>,
629
630        /// Set the maximum amount of Wasm fuel per block.
631        /// (This will overwrite value from `--policy-config`)
632        #[arg(long)]
633        maximum_wasm_fuel_per_block: Option<u64>,
634
635        /// Set the maximum amount of EVM fuel per block.
636        /// (This will overwrite value from `--policy-config`)
637        #[arg(long)]
638        maximum_evm_fuel_per_block: Option<u64>,
639
640        /// Set the maximum time in milliseconds that a block can spend executing services as oracles.
641        #[arg(long)]
642        maximum_service_oracle_execution_ms: Option<u64>,
643
644        /// Set the maximum size of a block.
645        /// (This will overwrite value from `--policy-config`)
646        #[arg(long)]
647        maximum_block_size: Option<u64>,
648
649        /// Set the maximum size of decompressed contract or service bytecode, in bytes.
650        /// (This will overwrite value from `--policy-config`)
651        #[arg(long)]
652        maximum_bytecode_size: Option<u64>,
653
654        /// Set the maximum size of data blobs, compressed bytecode and other binary blobs,
655        /// in bytes.
656        /// (This will overwrite value from `--policy-config`)
657        #[arg(long)]
658        maximum_blob_size: Option<u64>,
659
660        /// Set the maximum number of published blobs per block.
661        /// (This will overwrite value from `--policy-config`)
662        #[arg(long)]
663        maximum_published_blobs: Option<u64>,
664
665        /// Set the maximum size of a block proposal, in bytes.
666        /// (This will overwrite value from `--policy-config`)
667        #[arg(long)]
668        maximum_block_proposal_size: Option<u64>,
669
670        /// Set the maximum read data per block.
671        /// (This will overwrite value from `--policy-config`)
672        #[arg(long)]
673        maximum_bytes_read_per_block: Option<u64>,
674
675        /// Set the maximum write data per block.
676        /// (This will overwrite value from `--policy-config`)
677        #[arg(long)]
678        maximum_bytes_written_per_block: Option<u64>,
679
680        /// Set the maximum size of oracle responses.
681        /// (This will overwrite value from `--policy-config`)
682        #[arg(long)]
683        maximum_oracle_response_bytes: Option<u64>,
684
685        /// Set the maximum size in bytes of a received HTTP response.
686        #[arg(long)]
687        maximum_http_response_bytes: Option<u64>,
688
689        /// Set the maximum amount of time allowed to wait for an HTTP response.
690        #[arg(long)]
691        http_request_timeout_ms: Option<u64>,
692
693        /// Set the list of hosts that contracts and services can send HTTP requests to.
694        #[arg(long, value_delimiter = ',')]
695        http_request_allow_list: Option<Vec<String>>,
696
697        /// Set the list of application IDs for which message- and event-related fees are waived.
698        #[arg(long, value_delimiter = ',')]
699        free_application_ids: Option<Vec<String>>,
700
701        /// Force this wallet to generate keys using a PRNG and a given seed. USE FOR
702        /// TESTING ONLY.
703        #[arg(long)]
704        testing_prng_seed: Option<u64>,
705
706        /// A unique name to identify this network.
707        #[arg(long)]
708        network_name: Option<String>,
709    },
710
711    /// Watch the network for notifications.
712    Watch {
713        /// The chain ID to watch.
714        chain_id: Option<ChainId>,
715
716        /// Show all notifications from all validators.
717        #[arg(long)]
718        raw: bool,
719    },
720
721    /// Run a GraphQL service to explore and extend the chains of the wallet.
722    Service {
723        #[command(flatten)]
724        config: ChainListenerConfig,
725
726        /// The port on which to run the server
727        #[arg(long)]
728        port: NonZeroU16,
729
730        /// The port to expose metrics on.
731        #[cfg(with_metrics)]
732        #[arg(long)]
733        metrics_port: NonZeroU16,
734
735        /// Application IDs of operator applications to watch.
736        /// When specified, a task processor is started alongside the node service.
737        #[arg(long = "operator-application-ids")]
738        operator_application_ids: Vec<ApplicationId>,
739
740        /// A controller to execute a dynamic set of applications running on a dynamic set of
741        /// chains.
742        #[arg(long = "controller-id")]
743        controller_application_id: Option<ApplicationId>,
744
745        /// Supported operators and their binary paths.
746        /// Format: `name=path` or just `name` (uses name as path).
747        /// Example: `--operators my-operator=/path/to/binary`
748        #[arg(long = "operators", value_parser = parse_operator)]
749        operators: Vec<(String, PathBuf)>,
750
751        /// Delay in seconds before retrying a failed operator task batch.
752        /// Only relevant when operators are configured via `--operator-application-ids`
753        /// or `--controller-id`.
754        #[arg(long, default_value = "5")]
755        task_retry_delay_secs: u64,
756
757        /// Run in read-only mode: disallow mutations and prevent queries from scheduling
758        /// operations. Use this when exposing the service to untrusted clients.
759        #[arg(long)]
760        read_only: bool,
761
762        /// Enable the application query response cache with the given per-chain capacity.
763        /// Each entry stores a serialized GraphQL response keyed by
764        /// (application_id, request_bytes). Incompatible with `--long-lived-services`.
765        #[arg(long, env = "LINERA_QUERY_CACHE_SIZE")]
766        query_cache_size: Option<usize>,
767
768        /// Allow a named GraphQL subscription query.
769        /// The operation name is extracted from the query string.
770        /// Repeatable.
771        /// Example: `--allow-subscription 'query CounterValue { getCounter { value } }'`
772        #[arg(long = "allow-subscription")]
773        allowed_subscriptions: Vec<String>,
774    },
775
776    /// Query an application with a read-only GraphQL query.
777    QueryApplication {
778        /// The chain on which the application is running.
779        #[arg(long)]
780        chain_id: Option<ChainId>,
781
782        /// The application to query.
783        #[arg(long)]
784        application_id: ApplicationId,
785
786        /// The GraphQL query to send (e.g. "value" for a counter application).
787        query: String,
788    },
789
790    /// Run a GraphQL service that exposes a faucet where users can claim tokens.
791    /// This gives away the chain's tokens, and is mainly intended for testing.
792    Faucet {
793        /// The chain that gives away its tokens.
794        chain_id: Option<ChainId>,
795
796        /// The port on which to run the server
797        #[arg(long, default_value = "8080")]
798        port: u16,
799
800        /// The port for prometheus to scrape.
801        #[cfg(with_metrics)]
802        #[arg(long, default_value = "9090")]
803        metrics_port: u16,
804
805        /// The number of tokens to send to each new chain.
806        #[arg(long)]
807        amount: Amount,
808
809        /// The number of tokens to send per daily claim. Set to 0 to disable daily claims.
810        #[arg(long, default_value = "0")]
811        daily_claim_amount: Amount,
812
813        /// The end timestamp: The faucet will rate-limit the token supply so it runs out of money
814        /// no earlier than this.
815        #[arg(long)]
816        limit_rate_until: Option<DateTime<Utc>>,
817
818        /// Configuration for the faucet chain listener.
819        #[command(flatten)]
820        config: ChainListenerConfig,
821
822        /// Path to the persistent storage file for faucet mappings.
823        #[arg(long)]
824        storage_path: PathBuf,
825
826        /// Maximum number of operations to include in a single block (default: 100).
827        #[arg(long, default_value = "100")]
828        max_batch_size: usize,
829    },
830
831    /// Publish module.
832    PublishModule {
833        /// Path to the Wasm file for the application "contract" bytecode.
834        contract: PathBuf,
835
836        /// Path to the Wasm file for the application "service" bytecode.
837        service: PathBuf,
838
839        /// The virtual machine runtime to use.
840        #[arg(long, default_value = "wasm")]
841        vm_runtime: VmRuntime,
842
843        /// An optional chain ID to publish the module. The default chain of the wallet
844        /// is used otherwise.
845        publisher: Option<ChainId>,
846    },
847
848    /// Print events from a specific chain and stream from a specified index.
849    ListEventsFromIndex {
850        /// The chain to query. If omitted, query the default chain of the wallet.
851        chain_id: Option<ChainId>,
852
853        /// The stream being considered.
854        #[arg(long)]
855        stream_id: StreamId,
856
857        /// Index of the message to start with
858        #[arg(long, default_value = "0")]
859        start_index: u32,
860    },
861
862    /// Publish a data blob of binary data.
863    PublishDataBlob {
864        /// Path to data blob file to be published.
865        blob_path: PathBuf,
866        /// An optional chain ID to publish the blob. The default chain of the wallet
867        /// is used otherwise.
868        publisher: Option<ChainId>,
869    },
870
871    // TODO(#2490): Consider removing or renaming this.
872    /// Verify that a data blob is readable.
873    ReadDataBlob {
874        /// The hash of the content.
875        hash: CryptoHash,
876        /// An optional chain ID to verify the blob. The default chain of the wallet
877        /// is used otherwise.
878        reader: Option<ChainId>,
879    },
880
881    /// Create an application.
882    CreateApplication {
883        /// The module ID of the application to create.
884        module_id: ModuleId,
885
886        /// An optional chain ID to host the application. The default chain of the wallet
887        /// is used otherwise.
888        creator: Option<ChainId>,
889
890        /// The shared parameters as JSON string.
891        #[arg(long)]
892        json_parameters: Option<String>,
893
894        /// Path to a JSON file containing the shared parameters.
895        #[arg(long)]
896        json_parameters_path: Option<PathBuf>,
897
898        /// The instantiation argument as a JSON string.
899        #[arg(long)]
900        json_argument: Option<String>,
901
902        /// Path to a JSON file containing the instantiation argument.
903        #[arg(long)]
904        json_argument_path: Option<PathBuf>,
905
906        /// The list of required dependencies of application, if any.
907        #[arg(long, num_args(0..))]
908        required_application_ids: Option<Vec<ApplicationId>>,
909    },
910
911    /// Create an application, and publish the required module.
912    PublishAndCreate {
913        /// Path to the Wasm file for the application "contract" bytecode.
914        contract: PathBuf,
915
916        /// Path to the Wasm file for the application "service" bytecode.
917        service: PathBuf,
918
919        /// The virtual machine runtime to use.
920        #[arg(long, default_value = "wasm")]
921        vm_runtime: VmRuntime,
922
923        /// An optional chain ID to publish the module. The default chain of the wallet
924        /// is used otherwise.
925        publisher: Option<ChainId>,
926
927        /// The shared parameters as JSON string.
928        #[arg(long)]
929        json_parameters: Option<String>,
930
931        /// Path to a JSON file containing the shared parameters.
932        #[arg(long)]
933        json_parameters_path: Option<PathBuf>,
934
935        /// The instantiation argument as a JSON string.
936        #[arg(long)]
937        json_argument: Option<String>,
938
939        /// Path to a JSON file containing the instantiation argument.
940        #[arg(long)]
941        json_argument_path: Option<PathBuf>,
942
943        /// The list of required dependencies of application, if any.
944        #[arg(long, num_args(0..))]
945        required_application_ids: Option<Vec<ApplicationId>>,
946    },
947
948    /// Create an unassigned key pair.
949    Keygen,
950
951    /// Link the owner to the chain.
952    /// Expects that the caller has a private key corresponding to the `public_key`,
953    /// otherwise block proposals will fail when signing with it.
954    Assign {
955        /// The owner to assign.
956        #[arg(long)]
957        owner: AccountOwner,
958
959        /// The ID of the chain.
960        #[arg(long)]
961        chain_id: ChainId,
962    },
963
964    /// Retry a block we unsuccessfully tried to propose earlier.
965    ///
966    /// As long as a block is pending most other commands will fail, since it is unsafe to propose
967    /// multiple blocks at the same height.
968    RetryPendingBlock {
969        /// The chain with the pending block. If not specified, the wallet's default chain is used.
970        chain_id: Option<ChainId>,
971    },
972
973    /// Show the contents of the wallet.
974    #[command(subcommand)]
975    Wallet(WalletCommand),
976
977    /// Show the information about a chain.
978    #[command(subcommand)]
979    Chain(ChainCommand),
980
981    /// Manage Linera projects.
982    #[command(subcommand)]
983    Project(ProjectCommand),
984
985    /// Manage a local Linera Network.
986    #[command(subcommand)]
987    Net(NetCommand),
988
989    /// Manage validators in the committee.
990    #[command(subcommand)]
991    Validator(validator::Command),
992
993    /// Operation on the storage.
994    #[command(subcommand)]
995    Storage(DatabaseToolCommand),
996
997    /// Print CLI help in Markdown format, and exit.
998    #[command(hide = true)]
999    HelpMarkdown,
1000
1001    /// Extract a Bash and GraphQL script embedded in a markdown file and print it on
1002    /// `stdout`.
1003    #[command(hide = true)]
1004    ExtractScriptFromMarkdown {
1005        /// The source file
1006        path: PathBuf,
1007
1008        /// Insert a pause of N seconds after calls to `linera service`.
1009        #[arg(long, default_value = DEFAULT_PAUSE_AFTER_LINERA_SERVICE_SECS, value_parser = util::parse_secs)]
1010        pause_after_linera_service: Duration,
1011
1012        /// Insert a pause of N seconds after GraphQL queries.
1013        #[arg(long, default_value = DEFAULT_PAUSE_AFTER_GQL_MUTATIONS_SECS, value_parser = util::parse_secs)]
1014        pause_after_gql_mutations: Duration,
1015    },
1016
1017    /// Generate shell completion scripts
1018    Completion {
1019        /// The shell to generate completions for
1020        #[arg(value_enum)]
1021        shell: clap_complete::Shell,
1022    },
1023}
1024
1025impl ClientCommand {
1026    /// Returns the log file name to use based on the [`ClientCommand`] that will run.
1027    pub fn log_file_name(&self) -> Cow<'static, str> {
1028        match self {
1029            ClientCommand::Transfer { .. }
1030            | ClientCommand::OpenChain { .. }
1031            | ClientCommand::OpenMultiOwnerChain { .. }
1032            | ClientCommand::ShowOwnership { .. }
1033            | ClientCommand::ChangeOwnership { .. }
1034            | ClientCommand::SetPreferredOwner { .. }
1035            | ClientCommand::ChangeApplicationPermissions { .. }
1036            | ClientCommand::CloseChain { .. }
1037            | ClientCommand::ShowNetworkDescription
1038            | ClientCommand::LocalBalance { .. }
1039            | ClientCommand::QueryBalance { .. }
1040            | ClientCommand::SyncBalance { .. }
1041            | ClientCommand::Sync { .. }
1042            | ClientCommand::ProcessInbox { .. }
1043            | ClientCommand::QueryShardInfo { .. }
1044            | ClientCommand::ResourceControlPolicy { .. }
1045            | ClientCommand::RevokeEpochs { .. }
1046            | ClientCommand::CreateGenesisConfig { .. }
1047            | ClientCommand::PublishModule { .. }
1048            | ClientCommand::ListEventsFromIndex { .. }
1049            | ClientCommand::PublishDataBlob { .. }
1050            | ClientCommand::ReadDataBlob { .. }
1051            | ClientCommand::CreateApplication { .. }
1052            | ClientCommand::PublishAndCreate { .. }
1053            | ClientCommand::Keygen
1054            | ClientCommand::Assign { .. }
1055            | ClientCommand::Wallet { .. }
1056            | ClientCommand::Chain { .. }
1057            | ClientCommand::Validator { .. }
1058            | ClientCommand::RetryPendingBlock { .. }
1059            | ClientCommand::QueryApplication { .. } => "client".into(),
1060            ClientCommand::Benchmark(BenchmarkCommand::Single { .. }) => "single-benchmark".into(),
1061            ClientCommand::Benchmark(BenchmarkCommand::Multi { .. }) => "multi-benchmark".into(),
1062            ClientCommand::Net { .. } => "net".into(),
1063            ClientCommand::Project { .. } => "project".into(),
1064            ClientCommand::Watch { .. } => "watch".into(),
1065            ClientCommand::Storage { .. } => "storage".into(),
1066            ClientCommand::Service { port, .. } => format!("service-{port}").into(),
1067            ClientCommand::Faucet { .. } => "faucet".into(),
1068            ClientCommand::HelpMarkdown
1069            | ClientCommand::ExtractScriptFromMarkdown { .. }
1070            | ClientCommand::Completion { .. } => "tool".into(),
1071        }
1072    }
1073}
1074
1075#[derive(Clone, clap::Parser)]
1076pub enum DatabaseToolCommand {
1077    /// Delete all the namespaces in the database
1078    DeleteAll,
1079
1080    /// Delete a single namespace from the database
1081    DeleteNamespace,
1082
1083    /// Check existence of a namespace in the database
1084    CheckExistence,
1085
1086    /// Initialize a namespace in the database
1087    Initialize {
1088        #[arg(long = "genesis")]
1089        genesis_config_path: PathBuf,
1090    },
1091
1092    /// List the namespaces in the database
1093    ListNamespaces,
1094
1095    /// List the blob IDs in the database
1096    ListBlobIds,
1097
1098    /// List the chain IDs in the database
1099    ListChainIds,
1100
1101    /// List the event IDs in the database
1102    ListEventIds,
1103}
1104
1105#[allow(clippy::large_enum_variant)]
1106#[derive(Clone, clap::Parser)]
1107pub enum NetCommand {
1108    /// Start a Local Linera Network
1109    Up {
1110        /// The number of initial "root" chains created in the genesis config on top of
1111        /// the default "admin" chain. All initial chains belong to the first "admin"
1112        /// wallet. It is recommended to use at least one other initial chain for the
1113        /// faucet.
1114        #[arg(long, default_value = "2")]
1115        other_initial_chains: u32,
1116
1117        /// The initial amount of native tokens credited in the initial "root" chains,
1118        /// including the default "admin" chain.
1119        #[arg(long, default_value = "1000000")]
1120        initial_amount: u128,
1121
1122        /// The number of validators in the local test network.
1123        #[arg(long, default_value = "1")]
1124        validators: usize,
1125
1126        /// The number of proxies in the local test network.
1127        #[arg(long, default_value = "1")]
1128        proxies: usize,
1129
1130        /// The number of shards per validator in the local test network.
1131        #[arg(long, default_value = "1")]
1132        shards: usize,
1133
1134        /// Configure the resource control policy (notably fees) according to pre-defined
1135        /// settings.
1136        #[arg(long, default_value = "no-fees")]
1137        policy_config: ResourceControlPolicyConfig,
1138
1139        /// The configuration for cross-chain messages.
1140        #[clap(flatten)]
1141        cross_chain_config: CrossChainConfig,
1142
1143        /// Force this wallet to generate keys using a PRNG and a given seed. USE FOR
1144        /// TESTING ONLY.
1145        #[arg(long)]
1146        testing_prng_seed: Option<u64>,
1147
1148        /// Start the local network on a local Kubernetes deployment.
1149        #[cfg(feature = "kubernetes")]
1150        #[arg(long)]
1151        kubernetes: bool,
1152
1153        /// If this is not set, we'll build the binaries from within the Docker container
1154        /// If it's set, but with no directory path arg, we'll look for the binaries based on `current_binary_parent`
1155        /// If it's set, but with a directory path arg, we'll get the binaries from that path directory
1156        #[cfg(feature = "kubernetes")]
1157        #[arg(long, num_args=0..=1)]
1158        binaries: Option<Option<PathBuf>>,
1159
1160        /// Don't build docker image. This assumes that the image is already built.
1161        #[cfg(feature = "kubernetes")]
1162        #[arg(long, default_value = "false")]
1163        no_build: bool,
1164
1165        /// The name of the docker image to use.
1166        #[cfg(feature = "kubernetes")]
1167        #[arg(long, default_value = "linera:latest")]
1168        docker_image_name: String,
1169
1170        /// The build mode to use.
1171        #[cfg(feature = "kubernetes")]
1172        #[arg(long, default_value = "release")]
1173        build_mode: BuildMode,
1174
1175        /// Run with a specific path where the wallet and validator input files are.
1176        /// If none, then a temporary directory is created.
1177        #[arg(long)]
1178        path: Option<String>,
1179
1180        /// External protocol used, either `grpc` or `grpcs`.
1181        #[arg(long, default_value = "grpc")]
1182        external_protocol: String,
1183
1184        /// If present, a faucet is started on a dedicated chain with its own wallet.
1185        #[arg(long, default_value = "false")]
1186        with_faucet: bool,
1187
1188        /// The port on which to run the faucet server
1189        #[arg(long, default_value = "8080")]
1190        faucet_port: NonZeroU16,
1191
1192        /// The number of tokens to send to each new chain created by the faucet.
1193        #[arg(long, default_value = "1000")]
1194        faucet_amount: Amount,
1195
1196        /// Whether to start a block exporter for each validator.
1197        #[arg(long, default_value = "false")]
1198        with_block_exporter: bool,
1199
1200        /// The number of block exporters to start.
1201        #[arg(long, default_value = "1")]
1202        num_block_exporters: usize,
1203
1204        /// The address of the block exporter.
1205        #[arg(long, default_value = "localhost")]
1206        exporter_address: String,
1207
1208        /// The port on which to run the block exporter.
1209        #[arg(long, default_value = "8081")]
1210        exporter_port: NonZeroU16,
1211
1212        /// The name of the indexer docker image to use.
1213        #[cfg(feature = "kubernetes")]
1214        #[arg(long, default_value = "linera-indexer:latest")]
1215        indexer_image_name: String,
1216
1217        /// The name of the explorer docker image to use.
1218        #[cfg(feature = "kubernetes")]
1219        #[arg(long, default_value = "linera-explorer:latest")]
1220        explorer_image_name: String,
1221
1222        /// Use dual store (rocksdb and scylladb) instead of just scylladb. This is exclusive for
1223        /// kubernetes deployments.
1224        #[cfg(feature = "kubernetes")]
1225        #[arg(long, default_value = "false")]
1226        dual_store: bool,
1227    },
1228
1229    /// Print a bash helper script to make `linera net up` easier to use. The script is
1230    /// meant to be installed in `~/.bash_profile` or sourced when needed.
1231    Helper,
1232}
1233
1234#[derive(Clone, clap::Subcommand)]
1235pub enum WalletCommand {
1236    /// Show the contents of the wallet.
1237    Show {
1238        /// The chain to show the metadata.
1239        chain_id: Option<ChainId>,
1240        /// Only print a non-formatted list of the wallet's chain IDs.
1241        #[arg(long)]
1242        short: bool,
1243        /// Print only the chains that we have a key pair for.
1244        #[arg(long)]
1245        owned: bool,
1246    },
1247
1248    /// Change the wallet default chain.
1249    SetDefault { chain_id: ChainId },
1250
1251    /// Initialize a wallet from the genesis configuration.
1252    Init {
1253        /// The path to the genesis configuration for a Linera deployment. Either this or `--faucet`
1254        /// must be specified.
1255        ///
1256        /// Overrides `--faucet` if provided.
1257        #[arg(long = "genesis")]
1258        genesis_config_path: Option<PathBuf>,
1259
1260        /// The address of a faucet.
1261        #[arg(long, env = "LINERA_FAUCET_URL")]
1262        faucet: Option<String>,
1263
1264        /// Force this wallet to generate keys using a PRNG and a given seed. USE FOR
1265        /// TESTING ONLY.
1266        #[arg(long)]
1267        testing_prng_seed: Option<u64>,
1268    },
1269
1270    /// Request a new chain from a faucet and add it to the wallet.
1271    RequestChain {
1272        /// The address of a faucet.
1273        #[arg(long, env = "LINERA_FAUCET_URL")]
1274        faucet: String,
1275
1276        /// Whether this chain should become the default chain.
1277        #[arg(long)]
1278        set_default: bool,
1279    },
1280
1281    /// Export the genesis configuration to a JSON file.
1282    ///
1283    /// By default, exports the genesis config from the current wallet. Alternatively,
1284    /// use `--faucet` to retrieve the genesis config directly from a faucet URL.
1285    ExportGenesis {
1286        /// Path to save the genesis configuration JSON file.
1287        output: PathBuf,
1288
1289        /// The address of a faucet to retrieve the genesis config from.
1290        /// If not specified, the genesis config is read from the current wallet.
1291        #[arg(long)]
1292        faucet: Option<String>,
1293    },
1294
1295    /// Add a new followed chain (i.e. a chain without keypair) to the wallet.
1296    FollowChain {
1297        /// The chain ID.
1298        chain_id: ChainId,
1299        /// Synchronize the new chain and download all its blocks from the validators.
1300        #[arg(long)]
1301        sync: bool,
1302    },
1303
1304    /// Forgets the specified chain's keys. The chain will still be followed by the
1305    /// wallet.
1306    ForgetKeys { chain_id: ChainId },
1307
1308    /// Forgets the specified chain, including the associated key pair.
1309    ForgetChain { chain_id: ChainId },
1310}
1311
1312#[derive(Clone, clap::Subcommand)]
1313pub enum ChainCommand {
1314    /// Show the contents of a block.
1315    ShowBlock {
1316        /// The height of the block.
1317        height: BlockHeight,
1318        /// The chain to show the block (if not specified, the default chain from the
1319        /// wallet is used).
1320        chain_id: Option<ChainId>,
1321    },
1322
1323    /// Show the chain description of a chain.
1324    ShowChainDescription {
1325        /// The chain ID to show (if not specified, the default chain from the wallet is
1326        /// used).
1327        chain_id: Option<ChainId>,
1328    },
1329}
1330
1331#[derive(Clone, clap::Parser)]
1332pub enum ProjectCommand {
1333    /// Create a new Linera project.
1334    New {
1335        /// The project name. A directory of the same name will be created in the current directory.
1336        name: String,
1337
1338        /// Use the given clone of the Linera repository instead of remote crates.
1339        #[arg(long)]
1340        linera_root: Option<PathBuf>,
1341    },
1342
1343    /// Test a Linera project.
1344    ///
1345    /// Equivalent to running `cargo test` with the appropriate test runner.
1346    Test { path: Option<PathBuf> },
1347
1348    /// Build and publish a Linera project.
1349    PublishAndCreate {
1350        /// The path of the root of the Linera project.
1351        /// Defaults to current working directory if unspecified.
1352        path: Option<PathBuf>,
1353
1354        /// Specify the name of the Linera project.
1355        /// This is used to locate the generated bytecode files. The generated bytecode files should
1356        /// be of the form `<name>_{contract,service}.wasm`.
1357        ///
1358        /// Defaults to the package name in Cargo.toml, with dashes replaced by
1359        /// underscores.
1360        name: Option<String>,
1361
1362        /// An optional chain ID to publish the module. The default chain of the wallet
1363        /// is used otherwise.
1364        publisher: Option<ChainId>,
1365
1366        /// The virtual machine runtime to use.
1367        #[arg(long, default_value = "wasm")]
1368        vm_runtime: VmRuntime,
1369
1370        /// The shared parameters as JSON string.
1371        #[arg(long)]
1372        json_parameters: Option<String>,
1373
1374        /// Path to a JSON file containing the shared parameters.
1375        #[arg(long)]
1376        json_parameters_path: Option<PathBuf>,
1377
1378        /// The instantiation argument as a JSON string.
1379        #[arg(long)]
1380        json_argument: Option<String>,
1381
1382        /// Path to a JSON file containing the instantiation argument.
1383        #[arg(long)]
1384        json_argument_path: Option<PathBuf>,
1385
1386        /// The list of required dependencies of application, if any.
1387        #[arg(long, num_args(0..))]
1388        required_application_ids: Option<Vec<ApplicationId>>,
1389    },
1390}