vls_util/
util.rs

1use log::*;
2use std::fs::File;
3use std::io::{BufRead, BufReader};
4#[cfg(feature = "main")]
5use std::path::Path;
6use std::path::PathBuf;
7use std::{env, fs};
8
9/// Compare environment variable to a value
10pub fn compare_env_var(name: &str, value: &str) -> bool {
11    match env::var(name) {
12        Ok(val) => val == value,
13        Err(_) => false,
14    }
15}
16
17pub fn line_filter(line: &str) -> Option<String> {
18    let whitespace_removed = line.trim();
19    if whitespace_removed.is_empty() {
20        return None;
21    }
22    let comment_removed = whitespace_removed.split('#').next()?.trim();
23    if comment_removed.is_empty() {
24        return None;
25    }
26    Some(comment_removed.to_string())
27}
28
29pub fn read_allowlist() -> Vec<String> {
30    if let Ok(allowlist_path) = env::var("REMOTE_SIGNER_ALLOWLIST") {
31        return read_allowlist_path(&allowlist_path);
32    }
33    Vec::new()
34}
35
36pub fn read_allowlist_path(path: &str) -> Vec<String> {
37    let file = File::open(path).expect(format!("open {} failed", path).as_str());
38    let allowlist: Vec<String> =
39        BufReader::new(file).lines().filter_map(|l| line_filter(&l.expect("line"))).collect();
40
41    allowlist
42}
43
44/// Determine if we should auto approve payments
45pub fn should_auto_approve() -> bool {
46    if compare_env_var("VLS_PERMISSIVE", "1") {
47        warn!("VLS_PERMISSIVE: ALL INVOICES, KEYSENDS, AND PAYMENTS AUTOMATICALLY APPROVED");
48        return true;
49    }
50
51    if compare_env_var("VLS_AUTOAPPROVE", "1") {
52        warn!("VLS_AUTOAPPROVE: ALL INVOICES, KEYSENDS, AND PAYMENTS AUTOMATICALLY APPROVED");
53        return true;
54    }
55
56    info!("VLS_ENFORCING: ALL INVOICES, KEYSENDS, AND PAYMENTS REQUIRE APPROVAL");
57    false
58}
59
60/// Abort on panic.
61/// Use this instead of `panic = abort` in Cargo.toml, which doesn't show
62/// nice backtraces.
63pub fn abort_on_panic() {
64    let old = std::panic::take_hook();
65    std::panic::set_hook(Box::new(move |info| {
66        old(info);
67        std::process::abort();
68    }));
69}
70
71#[cfg(feature = "main")]
72pub fn setup_logging<P: AsRef<Path>>(datadir: P, who: &str, level_arg: &str) {
73    use fern::colors::{Color, ColoredLevelConfig};
74    use std::str::FromStr;
75
76    // Should we support seperate console and file log levels?
77    let level = env::var("RUST_LOG").unwrap_or(level_arg.to_string());
78
79    // file
80    let who_clone = who.to_string();
81    let logfile = datadir.as_ref().join(format!("{}.log", who));
82    let file_config = fern::Dispatch::new()
83        .format(move |out, message, record| {
84            out.finish(format_args!(
85                "[{} {}/{} {}] {}",
86                tstamp(),
87                who_clone,
88                record.target(),
89                record.level(),
90                message
91            ))
92        })
93        .level(log::LevelFilter::from_str(&level).expect("level"))
94        .level_for("h2", log::LevelFilter::Info)
95        .chain(fern::log_file(logfile).expect("file log config"));
96
97    // console
98    let who_clone = who.to_string();
99    let colors = ColoredLevelConfig::new().info(Color::Green).error(Color::Red).warn(Color::Yellow);
100    let console_config = fern::Dispatch::new()
101        .format(move |out, message, record| {
102            out.finish(format_args!(
103                "[{} {}/{} {}] {}",
104                tstamp(),
105                who_clone,
106                record.target(),
107                colors.color(record.level()),
108                message
109            ))
110        })
111        .level(log::LevelFilter::from_str(&level).expect("level"))
112        .level_for("h2", log::LevelFilter::Info)
113        .chain(std::io::stdout());
114
115    fern::Dispatch::new().chain(console_config).chain(file_config).apply().expect("log config");
116}
117
118// Would prefer to use now_local but https://rustsec.org/advisories/RUSTSEC-2020-0071
119// Also, https://time-rs.github.io/api/time/struct.OffsetDateTime.html#method.now_local
120#[cfg(feature = "main")]
121pub fn tstamp() -> String {
122    use time::{macros::format_description, OffsetDateTime};
123
124    OffsetDateTime::now_utc()
125        .format(format_description!(
126            "[year]-[month]-[day] [hour]:[minute]:[second].[subsecond digits:3]"
127        ))
128        .expect("formatted tstamp")
129}
130
131pub fn read_integration_test_seed<P: AsRef<Path>>(datadir: P) -> Option<[u8; 32]> {
132    let path = PathBuf::from(datadir.as_ref()).join("hsm_secret");
133    tracing::warn!("reading integration hsm_secret from {:?}", path);
134    let result = fs::read(path);
135    if let Ok(data) = result {
136        Some(
137            data.as_slice().try_into().unwrap_or_else(|_| {
138                panic!("Expected hsm_secret to be 32 bytes, got {}", data.len())
139            }),
140        )
141    } else {
142        None
143    }
144}
145
146#[cfg(test)]
147mod tests {
148    use super::*;
149    use std::io::Write;
150
151    #[test]
152    fn line_filter_test() {
153        assert_eq!(line_filter("#"), None);
154        assert_eq!(
155            line_filter("tb1qhetd7l0rv6kca6wvmt25ax5ej05eaat9q29z7z # comment"),
156            Some("tb1qhetd7l0rv6kca6wvmt25ax5ej05eaat9q29z7z".to_string())
157        );
158        assert_eq!(line_filter("   "), None);
159        assert_eq!(line_filter("   #   "), None);
160        assert_eq!(
161            line_filter("   tb1qhetd7l0rv6kca6wvmt25ax5ej05eaat9q29z7z   "),
162            Some("tb1qhetd7l0rv6kca6wvmt25ax5ej05eaat9q29z7z".to_string())
163        );
164    }
165
166    #[test]
167    fn read_allowlist_test() {
168        let test_file_content = "\
169        # Sample Allowlist
170        tb1qhetd7l0rv6kca6wvmt25ax5ej05eaat9q29z7z
171        tb1qexampleaddress1234567890123456789012345678
172
173        # Another comment line after blank line
174    ";
175        let mut temp_file = tempfile::NamedTempFile::new().unwrap();
176        write!(temp_file, "{}", test_file_content).unwrap();
177        let allowlist = read_allowlist_path(temp_file.path().to_str().unwrap());
178        assert_eq!(
179            allowlist,
180            vec![
181                "tb1qhetd7l0rv6kca6wvmt25ax5ej05eaat9q29z7z".to_string(),
182                "tb1qexampleaddress1234567890123456789012345678".to_string(),
183            ]
184        );
185        temp_file.close().unwrap();
186    }
187}