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}