Skip to main content

linera_service/cli/
common_options.rs

1// Copyright (c) Zefchain Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4//! CLI options shared between the Linera CLI and other tools (e.g. pm-benchmark).
5
6use std::{env, path::PathBuf};
7
8use anyhow::{bail, Error};
9use linera_client::config::GenesisConfig;
10use linera_execution::WasmRuntime;
11
12use crate::{
13    storage::{CommonStorageOptions, StorageConfig},
14    Wallet,
15};
16
17/// Wallet, keystore, and storage configuration options common to all Linera client tools.
18#[derive(Clone, clap::Parser)]
19pub struct CommonCliOptions {
20    /// Sets the file storing the private state of user chains (an empty one will be created
21    /// if missing).
22    #[arg(long = "wallet")]
23    pub wallet_state_path: Option<PathBuf>,
24
25    /// Sets the file storing the keystore state.
26    #[arg(long = "keystore")]
27    pub keystore_path: Option<PathBuf>,
28
29    /// Given an ASCII alphanumeric parameter `X`, read the wallet state and the wallet
30    /// storage config from the environment variables `LINERA_WALLET_{X}` and
31    /// `LINERA_STORAGE_{X}` instead of `LINERA_WALLET` and
32    /// `LINERA_STORAGE`.
33    #[arg(long, short = 'w', value_parser = crate::util::parse_ascii_alphanumeric_string)]
34    pub with_wallet: Option<String>,
35
36    /// Storage configuration for the blockchain history.
37    #[arg(long = "storage", global = true)]
38    pub storage_config: Option<String>,
39
40    /// Common storage options.
41    #[command(flatten)]
42    pub common_storage_options: CommonStorageOptions,
43
44    /// The WebAssembly runtime to use.
45    #[arg(long)]
46    pub wasm_runtime: Option<WasmRuntime>,
47
48    /// Output log messages from contract execution.
49    #[arg(long = "with-application-logs", env = "LINERA_APPLICATION_LOGS")]
50    pub application_logs: bool,
51
52    /// The number of Tokio worker threads to use.
53    #[arg(long, env = "LINERA_CLIENT_TOKIO_THREADS")]
54    pub tokio_threads: Option<usize>,
55
56    /// The number of Tokio blocking threads to use.
57    #[arg(long, env = "LINERA_CLIENT_TOKIO_BLOCKING_THREADS")]
58    pub tokio_blocking_threads: Option<usize>,
59}
60
61impl CommonCliOptions {
62    /// Returns the wallet-specific suffix used when resolving paths and environment variables.
63    pub fn suffix(&self) -> String {
64        self.with_wallet
65            .as_ref()
66            .map(|x| format!("_{x}"))
67            .unwrap_or_default()
68    }
69
70    /// Resolves the storage configuration from CLI options, environment variables, or defaults.
71    pub fn storage_config(&self) -> Result<StorageConfig, Error> {
72        if let Some(config) = &self.storage_config {
73            return config.parse();
74        }
75        let suffix = self.suffix();
76        let storage_env_var = env::var(format!("LINERA_STORAGE{suffix}")).ok();
77        if let Some(config) = storage_env_var {
78            return config.parse();
79        }
80        cfg_if::cfg_if! {
81            if #[cfg(feature = "rocksdb")] {
82                let spawn_mode =
83                    linera_views::rocks_db::RocksDbSpawnMode::get_spawn_mode_from_runtime();
84                let inner_storage_config = crate::storage::InnerStorageConfig::RocksDb {
85                    path: linera_wallet_json::paths::config_dir()?.join("wallet.db"),
86                    spawn_mode,
87                };
88                let namespace = linera_storage::DEFAULT_NAMESPACE.to_string();
89                Ok(StorageConfig {
90                    inner_storage_config,
91                    namespace,
92                })
93            } else {
94                bail!("Cannot apply default storage because the feature 'rocksdb' was not selected");
95            }
96        }
97    }
98
99    /// Returns the path to the wallet file.
100    pub fn wallet_path(&self) -> Result<PathBuf, Error> {
101        linera_wallet_json::paths::wallet_path(self.wallet_state_path.as_ref(), &self.suffix())
102    }
103
104    /// Returns the path to the keystore file.
105    pub fn keystore_path(&self) -> Result<PathBuf, Error> {
106        linera_wallet_json::paths::keystore_path(self.keystore_path.as_ref(), &self.suffix())
107    }
108
109    /// Reads and returns the wallet.
110    pub fn wallet(&self) -> Result<Wallet, Error> {
111        Ok(Wallet::read(&self.wallet_path()?)?)
112    }
113
114    /// Reads and returns the keystore.
115    pub fn keystore(&self) -> Result<linera_wallet_json::Keystore, Error> {
116        Ok(linera_wallet_json::Keystore::read(&self.keystore_path()?)?)
117    }
118
119    /// Creates and saves a new wallet from the given genesis configuration.
120    pub fn create_wallet(&self, genesis_config: GenesisConfig) -> Result<Wallet, Error> {
121        let wallet_path = self.wallet_path()?;
122        if wallet_path.exists() {
123            bail!("Wallet already exists: {}", wallet_path.display());
124        }
125        let wallet = Wallet::create(&wallet_path, genesis_config)?;
126        wallet.save()?;
127        Ok(wallet)
128    }
129
130    /// Creates and saves a new keystore, optionally seeded for deterministic testing.
131    pub fn create_keystore(
132        &self,
133        testing_prng_seed: Option<u64>,
134    ) -> Result<linera_wallet_json::Keystore, Error> {
135        let keystore_path = self.keystore_path()?;
136        if keystore_path.exists() {
137            bail!("Keystore already exists: {}", keystore_path.display());
138        }
139        Ok(linera_wallet_json::Keystore::create(
140            &keystore_path,
141            testing_prng_seed,
142        )?)
143    }
144}