lurk/cli/
config.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
//! Global config for the CLI
//! Includes settings for cache locations and proof backend
use std::collections::HashMap;

use crate::config::{lurk_config_file, Settings, LURK_CONFIG};
use crate::field::LanguageField;
use camino::Utf8PathBuf;
use config::{Config, ConfigError, Environment, File};
use once_cell::sync::OnceCell;
use serde::Deserialize;

use super::backend::Backend;
use super::paths::{circom_default_dir, commits_default_dir, proofs_default_dir};

/// Global config varable for `CliSettings`
pub(crate) static CLI_CONFIG: OnceCell<CliSettings> = OnceCell::new();

/// Gets the `CLI_CONFIG` settings. If uninitialized, sets the global variable
/// in the following order (greatest to least precedence):
/// - `settings` map if provided, e.g. with key ("proofs", "$HOME/lurk-rs/proofs")
///   This contains any CLI args, set e.g. by `lurk --proofs-dir /path/to/proofs_dir`
/// - Env var per setting, e.g. `LURK_PROOFS_DIR`
/// - Config file, which also has a configurable location (see `lurk_config_file()`),
///   and has the following syntax for e.g. TOML:
///   ```toml
///   proofs_dir = "/path/to/proofs_dir"
///   ```
///   Other file formats are supported by the `config` crate, but only TOML is tested
/// - Default values, e.g. `$HOME/.lurk/proofs`
pub(crate) fn cli_config(
    config_file: Option<&Utf8PathBuf>,
    settings: Option<&HashMap<&str, String>>,
) -> &'static CliSettings {
    LURK_CONFIG
        .set(Settings::from_config(lurk_config_file(config_file), settings).unwrap_or_default())
        .unwrap_or(());
    CLI_CONFIG.get_or_init(|| {
        CliSettings::from_config(lurk_config_file(config_file), settings).unwrap_or_default()
    })
}

/// Contains the CLI configuration settings
// NOTE: Config settings share the same file for both the Lurk library and the CLI.
// It's good practice to avoid duplication of shared settings like `public_params_dir`
// in downstream configs like these to prevent conflicts.
#[derive(Debug, Deserialize)]
pub(crate) struct CliSettings {
    /// Cache directory for proofs
    pub(crate) proofs_dir: Utf8PathBuf,
    /// Cache directory for commitments
    pub(crate) commits_dir: Utf8PathBuf,
    /// Cache directory for Circom files
    pub(crate) circom_dir: Utf8PathBuf,
    /// Proof generation and verification system
    pub(crate) backend: Backend,
    /// Finite field used for evaluation and proving
    pub(crate) field: LanguageField,
    /// Reduction count, which is the number of circuit reductions per step
    pub(crate) rc: usize,
    /// Iteration limit for the program, which is arbitrary to user preferences
    /// Used mainly as a safety check, similar to default stack size
    pub(crate) limit: usize,
}

impl CliSettings {
    /// Loads config settings from a file or env var, or CLI arg if applicable
    pub(crate) fn from_config(
        config_file: &Utf8PathBuf,
        cli_settings: Option<&HashMap<&str, String>>,
    ) -> Result<Self, ConfigError> {
        let (proofs, commits, circom, backend, field, rc, limit) = (
            "proofs_dir",
            "commits_dir",
            "circom_dir",
            "backend",
            "field",
            "rc",
            "limit",
        );
        Config::builder()
            .set_default(proofs, proofs_default_dir().to_string())?
            .set_default(commits, commits_default_dir().to_string())?
            .set_default(circom, circom_default_dir().to_string())?
            .set_default(backend, Backend::default().to_string())?
            .set_default(field, LanguageField::default().to_string())?
            .set_default(rc, 10)?
            .set_default(limit, 100_000_000)?
            .add_source(File::with_name(config_file.as_str()).required(false))
            // Then overwrite with any `LURK` environment variables
            .add_source(Environment::with_prefix("LURK"))
            // TODO: Derive config::Source for `cli_settings` and use `add_source` instead
            .set_override_option(proofs, cli_settings.and_then(|s| s.get(proofs).cloned()))?
            .set_override_option(commits, cli_settings.and_then(|s| s.get(commits).cloned()))?
            .set_override_option(circom, cli_settings.and_then(|s| s.get(circom).cloned()))?
            .set_override_option(backend, cli_settings.and_then(|s| s.get(backend).cloned()))?
            .set_override_option(field, cli_settings.and_then(|s| s.get(field).cloned()))?
            .set_override_option(rc, cli_settings.and_then(|s| s.get(rc).cloned()))?
            .set_override_option(limit, cli_settings.and_then(|s| s.get(limit).cloned()))?
            .build()
            .and_then(|c| c.try_deserialize())
    }
}

impl Default for CliSettings {
    fn default() -> Self {
        Self {
            proofs_dir: proofs_default_dir(),
            commits_dir: commits_default_dir(),
            circom_dir: circom_default_dir(),
            backend: Backend::default(),
            field: LanguageField::default(),
            rc: 10,
            limit: 100_000_000,
        }
    }
}

#[cfg(test)]
mod tests {
    use camino::Utf8Path;
    use std::io::prelude::*;
    use tempfile::Builder;

    use crate::cli::backend::Backend;
    use crate::cli::config::CliSettings;
    use crate::config::Settings;
    use crate::field::LanguageField;

    // Tests a generic config file with identical syntax to that used in `CLI_CONFIG`
    #[test]
    fn test_config_cli() {
        let tmp_dir = Builder::new().prefix("tmp").tempdir().unwrap();
        let tmp_dir = Utf8Path::from_path(tmp_dir.path()).unwrap();
        let config_dir = tmp_dir.join("lurk.toml");
        let public_params_dir = tmp_dir.join("public_params").into_string();
        let proofs_dir = tmp_dir.join("proofs").into_string();
        let commits_dir = tmp_dir.join("commits").into_string();
        let circom_dir = tmp_dir.join("circom").into_string();
        let backend = "Nova";
        let field = "Pallas";
        let rc = 100;
        let limit = 100_000;

        let mut config_file = std::fs::File::create(config_dir.clone()).unwrap();
        config_file
            .write_all(format!("public_params_dir = \"{public_params_dir}\"\n").as_bytes())
            .unwrap();
        config_file
            .write_all(format!("proofs_dir = \"{proofs_dir}\"\n").as_bytes())
            .unwrap();
        config_file
            .write_all(format!("commits_dir = \"{commits_dir}\"\n").as_bytes())
            .unwrap();
        config_file
            .write_all(format!("circom_dir = \"{circom_dir}\"\n").as_bytes())
            .unwrap();
        config_file
            .write_all(format!("backend = \"{backend}\"\n").as_bytes())
            .unwrap();
        config_file
            .write_all(format!("field = \"{field}\"\n").as_bytes())
            .unwrap();
        config_file
            .write_all(format!("rc = {rc}\n").as_bytes())
            .unwrap();
        config_file
            .write_all(format!("limit = {limit}\n").as_bytes())
            .unwrap();

        let cli_config = CliSettings::from_config(&config_dir, None).unwrap();
        let lurk_config = Settings::from_config(&config_dir, None).unwrap();
        assert_eq!(lurk_config.public_params_dir, public_params_dir);
        assert_eq!(cli_config.proofs_dir, proofs_dir);
        assert_eq!(cli_config.commits_dir, commits_dir);
        assert_eq!(cli_config.circom_dir, circom_dir);
        assert_eq!(cli_config.backend, Backend::Nova);
        assert_eq!(cli_config.field, LanguageField::Pallas);
        assert_eq!(cli_config.rc, rc);
        assert_eq!(cli_config.limit, limit);
    }
}