clementine_core/
cli.rs

1//! # Command Line Interface
2//!
3//! This module defines command line interface for server binaries. `Clap` is used
4//! for easy generation of help messages and handling arguments.
5
6use crate::config::protocol::ProtocolParamset;
7use crate::config::BridgeConfig;
8use crate::errors::BridgeError;
9use crate::errors::ErrorExt;
10use crate::utils::delayed_panic;
11use clap::Parser;
12use clap::ValueEnum;
13use eyre::Context;
14use std::env;
15use std::ffi::OsString;
16use std::path::PathBuf;
17use std::process;
18
19#[derive(Debug, Clone, Copy, ValueEnum, Eq, PartialEq)]
20pub enum Actors {
21    Verifier,
22    Operator,
23    Aggregator,
24    TestActor,
25}
26
27/// Clementine (C) 2025 Chainway Limited
28#[derive(Parser, Debug, Clone)]
29#[command(version, about, long_about = None)]
30pub struct Args {
31    /// Actor to run.
32    pub actor: Actors,
33    /// TOML formatted configuration file.
34    #[arg(short, long)]
35    pub config: Option<PathBuf>,
36    /// TOML formatted protocol parameters file.
37    #[arg(short, long)]
38    pub protocol_params: Option<PathBuf>,
39    /// Verbosity level, ranging from 0 (none) to 5 (highest)
40    #[arg(short, long, default_value_t = 3)]
41    pub verbose: u8,
42}
43
44/// Parse given iterator. This is good for isolated environments, like tests.
45fn parse_from<I, T>(itr: I) -> Result<Args, BridgeError>
46where
47    I: IntoIterator<Item = T>,
48    T: Into<OsString> + Clone,
49{
50    match Args::try_parse_from(itr) {
51        Ok(c) => Ok(c),
52        Err(e)
53            if matches!(
54                e.kind(),
55                clap::error::ErrorKind::DisplayHelp
56                    | clap::error::ErrorKind::DisplayHelpOnMissingArgumentOrSubcommand
57                    | clap::error::ErrorKind::DisplayVersion
58            ) =>
59        {
60            Err(BridgeError::CLIDisplayAndExit(e.render()))
61        }
62        Err(e) => Err(BridgeError::ConfigError(e.to_string())),
63    }
64}
65
66#[derive(Debug, Clone, Eq, PartialEq)]
67pub enum ConfigSource {
68    File(PathBuf),
69    Env,
70}
71/// Selects a configuration source for the main config or the protocol paramset.
72///
73/// Configuration can be loaded either from a file specified by a path in the CLI args,
74/// or from environment variables.
75///
76/// Selection logic is as follows:
77///
78/// 1. If the named environment variable (eg. `READ_CONFIG_FROM_ENV`) is not set
79///    or if the named environment variable is set to `0` or `off`, we use the file
80///    path provided in the CLI args (fail if not provided)
81///
82/// 2. If the named environment variable is set to `1` or `on`, we explicitly read from the
83///    environment variable
84///
85/// 3. If the named environment variable is set to an unknown value, we print a
86///    warning and default to environment variables
87///
88/// # Examples
89///
90/// ```bash
91/// # Load config from a file and protocol params from a file
92/// READ_CONFIG_FROM_ENV=0 READ_PARAMSET_FROM_ENV=0 clementine-core verifier --config /path/to/config.toml --protocol-params /path/to/protocol-params.toml
93///
94/// # or
95/// # define all config variables in the environment
96/// export CONFIG_ONE=1
97/// export PARAM_ONE=1
98/// # and source from environment variables
99/// READ_CONFIG_FROM_ENV=1 READ_PARAMSET_FROM_ENV=1 clementine-core verifier
100///
101/// # or
102/// # source paramset from environment variables but use config from a file
103/// export PARAM_ONE=1
104/// export PARAM_TWO=1
105/// READ_CONFIG_FROM_ENV=0 READ_PARAMSET_FROM_ENV=1 clementine-core verifier --config /path/to/config.toml
106///
107/// # WRONG usage (will use environment variables for both config and paramset)
108/// export CONFIG_ONE=1
109/// export PARAM_ONE=1
110/// READ_CONFIG_FROM_ENV=1 READ_PARAMSET_FROM_ENV=1 clementine-core --config /path/to/config.toml --protocol-params /path/to/protocol-params.toml
111/// ```
112pub fn get_config_source(
113    read_from_env_name: &'static str,
114    provided_arg: Option<PathBuf>,
115) -> Result<ConfigSource, BridgeError> {
116    Ok(match std::env::var(read_from_env_name) {
117        Err(_) => ConfigSource::File(provided_arg.ok_or(BridgeError::ConfigError(
118            "No file path or environment variable provided for config file.".to_string(),
119        ))?),
120        Ok(str) if str == "0" || str == "off" => ConfigSource::File(provided_arg.ok_or(
121            BridgeError::ConfigError("No file path provided for config file.".to_string()),
122        )?),
123        Ok(str) => {
124            if str != "1" && str != "on" {
125                tracing::warn!("Unknown value for {read_from_env_name}: {str}. Expected 1/0/off/on. Defaulting to environment variables.");
126            }
127
128            if provided_arg.is_some() {
129                tracing::warn!("File path provided in CLI arguments while {read_from_env_name} is set to 1. Ignoring provided file path and reading from environment variables.");
130            }
131
132            ConfigSource::Env
133        }
134    })
135}
136
137/// Gets configuration using CLI arguments, for binaries. If there are any errors, prints
138/// error and panics.
139///
140/// Steps:
141///
142/// 1. Get CLI arguments
143/// 2. Initialize logger
144/// 3. Get configuration, either from environment variables or
145///    configuration file
146/// 4. Get protocol parameters, either from environment variables or
147///    protocol parameters file
148///
149/// # Returns
150///
151/// A tuple, containing:
152///
153/// - [`BridgeConfig`] from CLI argument
154/// - [`Args`] from CLI options
155pub fn get_cli_config() -> (BridgeConfig, Args) {
156    let args = env::args();
157
158    match get_cli_config_from_args(args) {
159        Ok(config) => config,
160        Err(e) => {
161            let e = e.into_eyre();
162            match e.root_cause().downcast_ref::<BridgeError>() {
163                Some(BridgeError::CLIDisplayAndExit(msg)) => {
164                    println!("{msg}");
165                    process::exit(0);
166                }
167                _ => delayed_panic!("Failed to get CLI config: {e:?}"),
168            }
169        }
170    }
171}
172
173/// Wrapped function for tests
174fn get_cli_config_from_args<I, T>(itr: I) -> Result<(BridgeConfig, Args), BridgeError>
175where
176    I: IntoIterator<Item = T>,
177    T: Into<OsString> + Clone,
178{
179    let args = parse_from(itr).wrap_err("Failed to parse CLI arguments.")?;
180
181    let config_source = get_config_source("READ_CONFIG_FROM_ENV", args.config.clone());
182
183    let mut config =
184        match config_source.wrap_err("Failed to determine source for configuration.")? {
185            ConfigSource::File(config_file) => {
186                // Read from configuration file ONLY
187                BridgeConfig::try_parse_file(config_file)
188                    .wrap_err("Failed to read configuration from file.")?
189            }
190            ConfigSource::Env => BridgeConfig::from_env()
191                .wrap_err("Failed to read configuration from environment variables.")?,
192        };
193
194    let protocol_params_source =
195        get_config_source("READ_PARAMSET_FROM_ENV", args.protocol_params.clone())
196            .wrap_err("Failed to determine source for protocol parameters.")?;
197
198    // Leaks memory to get a static reference to the paramset
199    // This is needed to reduce copies of the protocol paramset when passing it around.
200    // This is fine, since this will only run once in the lifetime of the program.
201    let paramset: &'static ProtocolParamset = Box::leak(Box::new(match protocol_params_source {
202        ConfigSource::File(path) => ProtocolParamset::from_toml_file(path.as_path())
203            .wrap_err("Failed to read protocol parameters from file.")?,
204        ConfigSource::Env => ProtocolParamset::from_env()
205            .wrap_err("Failed to read protocol parameters from environment.")?,
206    }));
207
208    // The default will be REGTEST_PARAMSET and is overridden from the selected source above.
209    config.protocol_paramset = paramset;
210
211    Ok((config, args))
212}
213
214#[cfg(test)]
215mod tests {
216    use super::{get_cli_config_from_args, get_config_source, parse_from, ConfigSource};
217    use crate::cli::Actors;
218    use crate::errors::BridgeError;
219    use std::env;
220    use std::fs::File;
221    use std::io::Write;
222    use std::path::PathBuf;
223
224    /// With help message flag, we should see the help message. Shocking.
225    #[test]
226    fn help_message() {
227        match parse_from(vec!["clementine-core", "--help"]) {
228            Ok(_) => panic!("expected configuration error"),
229            Err(BridgeError::CLIDisplayAndExit(_)) => {}
230            e => panic!("unexpected error {e:#?}"),
231        }
232    }
233
234    /// With version flag, we should see the program version read from
235    /// `Cargo.toml`.
236    #[test]
237    fn version() {
238        match parse_from(vec!["clementine-core", "--version"]) {
239            Ok(_) => panic!("expected configuration error"),
240            Err(BridgeError::CLIDisplayAndExit(_)) => {}
241            e => panic!("unexpected error {e:#?}"),
242        }
243    }
244
245    // Helper function to set and unset environment variables for tests
246    fn with_env_var<F, T>(name: &str, value: Option<&str>, test: F) -> T
247    where
248        F: FnOnce() -> T,
249    {
250        let prev_value = env::var(name).ok();
251        match value {
252            Some(val) => env::set_var(name, val),
253            None => env::remove_var(name),
254        }
255        let result = test();
256        match prev_value {
257            Some(val) => env::set_var(name, val),
258            None => env::remove_var(name),
259        }
260        result
261    }
262
263    #[test]
264    #[serial_test::serial]
265    fn test_get_config_source_env_not_set() {
266        with_env_var("TEST_READ_FROM_ENV", None, || {
267            let path = PathBuf::from("/path/to/config");
268            let result = get_config_source("TEST_READ_FROM_ENV", Some(path.clone()));
269            assert_eq!(result.unwrap(), ConfigSource::File(path));
270
271            // When path is not provided, should return error
272            let result = get_config_source("TEST_READ_FROM_ENV", None);
273            assert!(result.is_err());
274            assert!(matches!(result.unwrap_err(), BridgeError::ConfigError(_)));
275        })
276    }
277
278    #[test]
279    #[serial_test::serial]
280    fn test_get_config_source_env_set_to_off() {
281        // Test with "0"
282        with_env_var("TEST_READ_FROM_ENV", Some("0"), || {
283            let path = PathBuf::from("/path/to/config");
284            let result = get_config_source("TEST_READ_FROM_ENV", Some(path.clone()));
285            assert_eq!(result.unwrap(), ConfigSource::File(path));
286
287            // When path is not provided, should return error
288            let result = get_config_source("TEST_READ_FROM_ENV", None);
289            assert!(result.is_err());
290        });
291
292        // Test with "off"
293        with_env_var("TEST_READ_FROM_ENV", Some("off"), || {
294            let path = PathBuf::from("/path/to/config");
295            let result = get_config_source("TEST_READ_FROM_ENV", Some(path.clone()));
296            assert_eq!(result.unwrap(), ConfigSource::File(path));
297        })
298    }
299
300    #[test]
301    #[serial_test::serial]
302    fn test_get_config_source_env_set_to_on() {
303        // Test with "1"
304        with_env_var("TEST_READ_FROM_ENV", Some("1"), || {
305            let result = get_config_source("TEST_READ_FROM_ENV", None);
306            assert_eq!(result.unwrap(), ConfigSource::Env);
307
308            // Even if path is provided, should still return Env
309            let path = PathBuf::from("/path/to/config");
310            let result = get_config_source("TEST_READ_FROM_ENV", Some(path));
311            assert_eq!(result.unwrap(), ConfigSource::Env);
312        });
313
314        // Test with "on"
315        with_env_var("TEST_READ_FROM_ENV", Some("on"), || {
316            let result = get_config_source("TEST_READ_FROM_ENV", None);
317            assert_eq!(result.unwrap(), ConfigSource::Env);
318        })
319    }
320
321    #[test]
322    #[serial_test::serial]
323    fn test_get_config_source_env_unknown_value() {
324        with_env_var("TEST_READ_FROM_ENV", Some("invalid"), || {
325            let result = get_config_source("TEST_READ_FROM_ENV", None);
326            assert_eq!(result.unwrap(), ConfigSource::Env);
327        })
328    }
329
330    // Helper to create a temporary config file
331    fn with_temp_config_file<F, T>(content: &str, test: F) -> T
332    where
333        F: FnOnce(PathBuf) -> T,
334    {
335        let temp_dir = tempfile::tempdir().unwrap();
336        let file_path = temp_dir.path().join("bridge_config.toml");
337
338        let mut file = File::create(&file_path).unwrap();
339        file.write_all(content.as_bytes()).unwrap();
340
341        let result = test(file_path);
342        temp_dir.close().unwrap();
343        result
344    }
345
346    // Helper to set up all environment variables needed for config
347    fn setup_config_env_vars() {
348        env::set_var("HOST", "127.0.0.1");
349        env::set_var("PORT", "17000");
350        env::set_var(
351            "SECRET_KEY",
352            "1111111111111111111111111111111111111111111111111111111111111111",
353        );
354        env::set_var("OPERATOR_WITHDRAWAL_FEE_SATS", "100000");
355        env::set_var("BITCOIN_RPC_URL", "http://127.0.0.1:18443/wallet/admin");
356        env::set_var("BITCOIN_RPC_USER", "admin");
357        env::set_var("BITCOIN_RPC_PASSWORD", "admin");
358        env::set_var("DB_HOST", "127.0.0.1");
359        env::set_var("DB_PORT", "5432");
360        env::set_var("DB_USER", "clementine");
361        env::set_var("DB_PASSWORD", "clementine");
362        env::set_var("DB_NAME", "clementine");
363        env::set_var("CITREA_RPC_URL", "");
364        env::set_var("CITREA_LIGHT_CLIENT_PROVER_URL", "");
365        env::set_var("CITREA_CHAIN_ID", "5655");
366        env::set_var(
367            "BRIDGE_CONTRACT_ADDRESS",
368            "3100000000000000000000000000000000000002",
369        );
370        env::set_var("SERVER_CERT_PATH", "certs/server/server.pem");
371        env::set_var("SERVER_KEY_PATH", "certs/server/server.key");
372        env::set_var("CA_CERT_PATH", "certs/ca/ca.pem");
373        env::set_var("CLIENT_CERT_PATH", "certs/client/client.pem");
374        env::set_var("CLIENT_KEY_PATH", "certs/client/client.key");
375        env::set_var("AGGREGATOR_CERT_PATH", "certs/aggregator/aggregator.pem");
376        env::set_var("CLIENT_VERIFICATION", "true");
377        env::set_var(
378            "SECURITY_COUNCIL",
379            "1:50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0",
380        );
381
382        env::set_var("TELEMETRY_HOST", "0.0.0.0");
383        env::set_var("TELEMETRY_PORT", "8081");
384        env::set_var("TX_SENDER_FEE_RATE_HARD_CAP", "100");
385        env::set_var("TX_SENDER_MEMPOOL_FEE_RATE_MULTIPLIER", "1");
386        env::set_var("TX_SENDER_MEMPOOL_FEE_RATE_OFFSET_SAT_KVB", "0");
387        env::set_var("TX_SENDER_CPFP_FEE_PAYER_BUMP_WAIT_TIME_SECONDS", "3600");
388        env::set_var("TIME_TO_SEND_WATCHTOWER_CHALLENGE", "216");
389    }
390
391    // Helper to set up all environment variables needed for protocol paramset
392    fn setup_protocol_paramset_env_vars() {
393        env::set_var("NETWORK", "regtest");
394        env::set_var("NUM_ROUND_TXS", "2");
395        env::set_var("NUM_KICKOFFS_PER_ROUND", "10");
396        env::set_var("NUM_SIGNED_KICKOFFS", "2");
397        env::set_var("BRIDGE_AMOUNT", "1000000000");
398        env::set_var("KICKOFF_AMOUNT", "0");
399        env::set_var("OPERATOR_CHALLENGE_AMOUNT", "200000000");
400        env::set_var("COLLATERAL_FUNDING_AMOUNT", "99000000");
401        env::set_var("KICKOFF_BLOCKHASH_COMMIT_LENGTH", "40");
402        env::set_var("WATCHTOWER_CHALLENGE_BYTES", "144");
403        env::set_var("WINTERNITZ_LOG_D", "4");
404        env::set_var("USER_TAKES_AFTER", "200");
405        env::set_var("OPERATOR_CHALLENGE_TIMEOUT_TIMELOCK", "144");
406        env::set_var("OPERATOR_CHALLENGE_NACK_TIMELOCK", "432");
407        env::set_var("DISPROVE_TIMEOUT_TIMELOCK", "720");
408        env::set_var("ASSERT_TIMEOUT_TIMELOCK", "576");
409        env::set_var("OPERATOR_REIMBURSE_TIMELOCK", "12");
410        env::set_var("WATCHTOWER_CHALLENGE_TIMEOUT_TIMELOCK", "288");
411        env::set_var("LATEST_BLOCKHASH_TIMEOUT_TIMELOCK", "360");
412        env::set_var("FINALITY_DEPTH", "1");
413        env::set_var("START_HEIGHT", "8148");
414        env::set_var("GENESIS_HEIGHT", "0");
415        env::set_var(
416            "GENESIS_CHAIN_STATE_HASH",
417            "5f7302ad16c8bd9ef2f3be00c8199a86f9e0ba861484abb4af5f7e457f8c2216",
418        );
419        env::set_var("HEADER_CHAIN_PROOF_BATCH_SIZE", "100");
420        env::set_var("BRIDGE_NONSTANDARD", "true");
421    }
422
423    // Helper to clean up all environment variables
424    fn cleanup_config_env_vars() {
425        env::remove_var("HOST");
426        env::remove_var("PORT");
427        env::remove_var("SECRET_KEY");
428        env::remove_var("OPERATOR_WITHDRAWAL_FEE_SATS");
429        env::remove_var("BITCOIN_RPC_URL");
430        env::remove_var("BITCOIN_RPC_USER");
431        env::remove_var("BITCOIN_RPC_PASSWORD");
432        env::remove_var("DB_HOST");
433        env::remove_var("DB_PORT");
434        env::remove_var("DB_USER");
435        env::remove_var("DB_PASSWORD");
436        env::remove_var("DB_NAME");
437        env::remove_var("CITREA_RPC_URL");
438        env::remove_var("CITREA_LIGHT_CLIENT_PROVER_URL");
439        env::remove_var("BRIDGE_CONTRACT_ADDRESS");
440        env::remove_var("SERVER_CERT_PATH");
441        env::remove_var("SERVER_KEY_PATH");
442        env::remove_var("CA_CERT_PATH");
443        env::remove_var("CLIENT_CERT_PATH");
444        env::remove_var("CLIENT_KEY_PATH");
445        env::remove_var("AGGREGATOR_CERT_PATH");
446        env::remove_var("CLIENT_VERIFICATION");
447        env::remove_var("SECURITY_COUNCIL");
448        env::remove_var("TELEMETRY_HOST");
449        env::remove_var("TELEMETRY_PORT");
450        env::remove_var("TIME_TO_SEND_WATCHTOWER_CHALLENGE");
451    }
452
453    // Helper to clean up all protocol paramset environment variables
454    fn cleanup_protocol_paramset_env_vars() {
455        env::remove_var("NETWORK");
456        env::remove_var("NUM_ROUND_TXS");
457        env::remove_var("NUM_KICKOFFS_PER_ROUND");
458        env::remove_var("NUM_SIGNED_KICKOFFS");
459        env::remove_var("BRIDGE_AMOUNT");
460        env::remove_var("KICKOFF_AMOUNT");
461        env::remove_var("OPERATOR_CHALLENGE_AMOUNT");
462        env::remove_var("COLLATERAL_FUNDING_AMOUNT");
463        env::remove_var("KICKOFF_BLOCKHASH_COMMIT_LENGTH");
464        env::remove_var("WATCHTOWER_CHALLENGE_BYTES");
465        env::remove_var("WINTERNITZ_LOG_D");
466        env::remove_var("USER_TAKES_AFTER");
467        env::remove_var("OPERATOR_CHALLENGE_TIMEOUT_TIMELOCK");
468        env::remove_var("OPERATOR_CHALLENGE_NACK_TIMELOCK");
469        env::remove_var("DISPROVE_TIMEOUT_TIMELOCK");
470        env::remove_var("ASSERT_TIMEOUT_TIMELOCK");
471        env::remove_var("OPERATOR_REIMBURSE_TIMELOCK");
472        env::remove_var("WATCHTOWER_CHALLENGE_TIMEOUT_TIMELOCK");
473        env::remove_var("FINALITY_DEPTH");
474        env::remove_var("START_HEIGHT");
475        env::remove_var("HEADER_CHAIN_PROOF_BATCH_SIZE");
476    }
477
478    // Basic minimum toml config content
479    const MINIMAL_CONFIG_CONTENT: &str = include_str!("test/data/bridge_config.toml");
480
481    #[test]
482    #[serial_test::serial]
483    fn test_get_cli_config_file_mode() {
484        with_env_var("READ_CONFIG_FROM_ENV", Some("0"), || {
485            with_temp_config_file(MINIMAL_CONFIG_CONTENT, |config_path| {
486                // Create a temp protocol paramset file
487                with_temp_config_file(
488                    include_str!("./test/data/protocol_paramset.toml"),
489                    |protocol_path| {
490                        let args = vec![
491                            "clementine-core",
492                            "verifier",
493                            "--config",
494                            config_path.to_str().unwrap(),
495                            "--protocol-params",
496                            protocol_path.to_str().unwrap(),
497                        ];
498
499                        let result = get_cli_config_from_args(args);
500
501                        let (config, cli_args) = result.expect("Failed to get CLI config");
502                        assert_eq!(config.host, "127.0.0.1");
503                        assert_eq!(config.port, 17000);
504                        assert_eq!(cli_args.actor, Actors::Verifier);
505
506                        // Assert some protocol paramset values
507                        assert_eq!(config.protocol_paramset.network.to_string(), "regtest");
508                        assert_eq!(config.protocol_paramset.num_round_txs, 2);
509                        assert_eq!(config.protocol_paramset.winternitz_log_d, 4);
510                    },
511                )
512            })
513        })
514    }
515
516    #[test]
517    #[serial_test::serial]
518    fn test_get_cli_config_env_mode() {
519        setup_config_env_vars();
520        setup_protocol_paramset_env_vars();
521
522        with_env_var("READ_CONFIG_FROM_ENV", Some("1"), || {
523            with_env_var("READ_PARAMSET_FROM_ENV", Some("1"), || {
524                let args = vec!["clementine-core", "operator"];
525
526                let result = get_cli_config_from_args(args);
527
528                let (config, cli_args) = result.expect("Failed to get CLI config");
529                assert_eq!(config.host, "127.0.0.1");
530                assert_eq!(config.port, 17000);
531                assert_eq!(cli_args.actor, Actors::Operator);
532
533                // Assert some protocol paramset values
534                assert_eq!(config.protocol_paramset.network.to_string(), "regtest");
535                assert_eq!(config.protocol_paramset.num_round_txs, 2);
536                assert_eq!(config.protocol_paramset.winternitz_log_d, 4);
537                assert_eq!(config.protocol_paramset.start_height, 8148); // This should be from the environment variable
538            });
539        });
540
541        cleanup_config_env_vars();
542        cleanup_protocol_paramset_env_vars();
543    }
544
545    #[test]
546    #[serial_test::serial]
547    fn test_mixed_config_sources() {
548        // Set up config from file but protocol paramset from env
549        setup_protocol_paramset_env_vars();
550
551        with_env_var("READ_CONFIG_FROM_ENV", Some("0"), || {
552            with_env_var("READ_PARAMSET_FROM_ENV", Some("1"), || {
553                with_temp_config_file(MINIMAL_CONFIG_CONTENT, |config_path| {
554                    let args = vec![
555                        "clementine-core",
556                        "verifier",
557                        "--config",
558                        config_path.to_str().unwrap(),
559                    ];
560
561                    let result = get_cli_config_from_args(args);
562
563                    let (config, cli_args) = result.expect("Failed to get CLI config");
564                    assert_eq!(config.host, "127.0.0.1");
565                    assert_eq!(config.port, 17000);
566                    assert_eq!(cli_args.actor, Actors::Verifier);
567
568                    // Assert some protocol paramset values from env
569                    assert_eq!(config.protocol_paramset.network.to_string(), "regtest");
570                    assert_eq!(config.protocol_paramset.start_height, 8148); // This should be from the environment variable
571                })
572            })
573        });
574
575        cleanup_protocol_paramset_env_vars();
576    }
577
578    #[test]
579    #[serial_test::serial]
580    fn test_get_cli_config_file_without_path() {
581        with_env_var("READ_CONFIG_FROM_ENV", Some("0"), || {
582            let args = vec!["clementine-core", "verifier"];
583
584            let result = get_cli_config_from_args(args);
585            result.expect_err("Expected error when config file path is not provided");
586        })
587    }
588}