fedimint_core/
envs.rs

1use std::collections::BTreeMap;
2use std::str::FromStr;
3use std::{cmp, env};
4
5use anyhow::Context;
6use fedimint_core::util::SafeUrl;
7use fedimint_derive::{Decodable, Encodable};
8use fedimint_logging::LOG_CORE;
9use jsonrpsee_core::Serialize;
10use serde::Deserialize;
11use tracing::warn;
12
13use crate::util::FmtCompact as _;
14
15/// In tests we want to routinely enable an extra unknown module to ensure
16/// all client code handles correct modules that client doesn't know about.
17pub const FM_USE_UNKNOWN_MODULE_ENV: &str = "FM_USE_UNKNOWN_MODULE";
18
19pub const FM_ENABLE_MODULE_LNV2_ENV: &str = "FM_ENABLE_MODULE_LNV2";
20
21/// Print sensitive secrets without redacting them. Use only for debugging.
22pub const FM_DEBUG_SHOW_SECRETS_ENV: &str = "FM_DEBUG_SHOW_SECRETS";
23
24/// Check if env variable is set and not equal `0` or `false` which are common
25/// ways to disable something.
26pub fn is_env_var_set(var: &str) -> bool {
27    std::env::var_os(var).is_some_and(|v| v != "0" && v != "false")
28}
29
30/// Use to detect if running in a test environment, either `cargo test` or
31/// `devimint`.
32pub fn is_running_in_test_env() -> bool {
33    let unit_test = cfg!(test);
34
35    unit_test || is_env_var_set("NEXTEST") || is_env_var_set(FM_IN_DEVIMINT_ENV)
36}
37
38/// Use to allow `process_output` to process RBF withdrawal outputs.
39pub fn is_rbf_withdrawal_enabled() -> bool {
40    is_env_var_set("FM_UNSAFE_ENABLE_RBF_WITHDRAWAL")
41}
42
43/// Get value of `FEDIMINT_BUILD_CODE_VERSION` at compile time
44#[macro_export]
45macro_rules! fedimint_build_code_version_env {
46    () => {
47        env!("FEDIMINT_BUILD_CODE_VERSION")
48    };
49}
50
51/// Env var for bitcoin RPC kind (obsolete, use FM_DEFAULT_* instead)
52pub const FM_BITCOIN_RPC_KIND_ENV: &str = "FM_BITCOIN_RPC_KIND";
53/// Env var for bitcoin URL (obsolete, use FM_DEFAULT_* instead)
54pub const FM_BITCOIN_RPC_URL_ENV: &str = "FM_BITCOIN_RPC_URL";
55/// Env var how often to poll bitcoin source
56pub const FM_BITCOIN_POLLING_INTERVAL_SECS_ENV: &str = "FM_BITCOIN_POLLING_INTERVAL_SECS";
57
58/// Env var for bitcoin RPC kind (default, used only as a default value for DKG
59/// config settings)
60pub const FM_DEFAULT_BITCOIN_RPC_KIND_ENV: &str = "FM_DEFAULT_BITCOIN_RPC_KIND";
61pub const FM_DEFAULT_BITCOIN_RPC_KIND_BAD_ENV: &str = "FM_DEFAULT_BITCOIND_RPC_KIND";
62/// Env var for bitcoin URL (default, used only as a default value for DKG
63/// config settings)
64pub const FM_DEFAULT_BITCOIN_RPC_URL_ENV: &str = "FM_DEFAULT_BITCOIN_RPC_URL";
65pub const FM_DEFAULT_BITCOIN_RPC_URL_BAD_ENV: &str = "FM_DEFAULT_BITCOIND_RPC_URL";
66
67/// Env var for bitcoin RPC kind (forced, takes priority over config settings)
68pub const FM_FORCE_BITCOIN_RPC_KIND_ENV: &str = "FM_FORCE_BITCOIN_RPC_KIND";
69pub const FM_FORCE_BITCOIN_RPC_KIND_BAD_ENV: &str = "FM_FORCE_BITCOIND_RPC_BAD_KIND";
70/// Env var for bitcoin URL (default, takes priority over config settings)
71pub const FM_FORCE_BITCOIN_RPC_URL_ENV: &str = "FM_FORCE_BITCOIN_RPC_URL";
72pub const FM_FORCE_BITCOIN_RPC_URL_BAD_ENV: &str = "FM_FORCE_BITCOIND_RPC_URL";
73
74/// Env var to override iroh connectivity
75///
76/// Comma separated key-value list (`<node_id>=<ticket>,<node_id>=<ticket>,...`)
77pub const FM_IROH_CONNECT_OVERRIDES_ENV: &str = "FM_IROH_CONNECT_OVERRIDES";
78
79/// Env var to override tcp api connectivity
80///
81/// Comma separated key-value list (`peer_id=url,peer_id=url`)
82pub const FM_WS_API_CONNECT_OVERRIDES_ENV: &str = "FM_WS_API_CONNECT_OVERRIDES";
83
84pub const FM_IROH_API_SECRET_KEY_OVERRIDE_ENV: &str = "FM_IROH_API_SECRET_KEY_OVERRIDE";
85pub const FM_IROH_P2P_SECRET_KEY_OVERRIDE_ENV: &str = "FM_IROH_P2P_SECRET_KEY_OVERRIDE";
86
87/// List of json api endpoint sources to use as a source of
88/// fee rate estimation.
89///
90/// `;`-separated list of urls with part after `#`
91/// ("fragment") specifying jq filter to extract sats/vB fee rate.
92/// Eg. `https://mempool.space/api/v1/fees/recommended#.halfHourFee`
93///
94/// Note that `#` is a standalone separator and *not* parsed as a part of the
95/// Url. Which means there's no need to escape it.
96pub const FM_WALLET_FEERATE_SOURCES_ENV: &str = "FM_WALLET_FEERATE_SOURCES";
97
98/// `devimint` will set when code is running inside `devimint`
99pub const FM_IN_DEVIMINT_ENV: &str = "FM_IN_DEVIMINT";
100
101/// Configuration for the bitcoin RPC
102#[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize, Encodable, Decodable)]
103pub struct BitcoinRpcConfig {
104    pub kind: String,
105    pub url: SafeUrl,
106}
107
108impl BitcoinRpcConfig {
109    pub fn get_defaults_from_env_vars() -> anyhow::Result<Self> {
110        Ok(Self {
111        kind: env::var(FM_FORCE_BITCOIN_RPC_KIND_ENV)
112            .or_else(|_| env::var(FM_DEFAULT_BITCOIN_RPC_KIND_ENV))
113            .or_else(|_| env::var(FM_BITCOIN_RPC_KIND_ENV).inspect(|_v| {
114                warn!(target: LOG_CORE, "{FM_BITCOIN_RPC_KIND_ENV} is obsolete, use {FM_DEFAULT_BITCOIN_RPC_KIND_ENV} instead");
115            }))
116            .or_else(|_| env::var(FM_FORCE_BITCOIN_RPC_KIND_BAD_ENV).inspect(|_v| {
117                warn!(target: LOG_CORE, "{FM_FORCE_BITCOIN_RPC_KIND_BAD_ENV} is obsolete, use {FM_FORCE_BITCOIN_RPC_KIND_ENV} instead");
118            }))
119            .or_else(|_| env::var(FM_DEFAULT_BITCOIN_RPC_KIND_BAD_ENV).inspect(|_v| {
120                warn!(target: LOG_CORE, "{FM_DEFAULT_BITCOIN_RPC_KIND_BAD_ENV} is obsolete, use {FM_DEFAULT_BITCOIN_RPC_KIND_ENV} instead");
121            }))
122            .with_context(|| {
123                anyhow::anyhow!("failure looking up env var for Bitcoin RPC kind")
124            })?,
125        url: env::var(FM_FORCE_BITCOIN_RPC_URL_ENV)
126            .or_else(|_| env::var(FM_DEFAULT_BITCOIN_RPC_URL_ENV))
127            .or_else(|_| env::var(FM_BITCOIN_RPC_URL_ENV).inspect(|_v| {
128                warn!(target: LOG_CORE, "{FM_BITCOIN_RPC_URL_ENV} is obsolete, use {FM_DEFAULT_BITCOIN_RPC_URL_ENV} instead");
129            }))
130            .or_else(|_| env::var(FM_FORCE_BITCOIN_RPC_URL_BAD_ENV).inspect(|_v| {
131                warn!(target: LOG_CORE, "{FM_FORCE_BITCOIN_RPC_URL_BAD_ENV} is obsolete, use {FM_FORCE_BITCOIN_RPC_URL_ENV} instead");
132            }))
133            .or_else(|_| env::var(FM_DEFAULT_BITCOIN_RPC_URL_BAD_ENV).inspect(|_v| {
134                warn!(target: LOG_CORE, "{FM_DEFAULT_BITCOIN_RPC_URL_BAD_ENV} is obsolete, use {FM_DEFAULT_BITCOIN_RPC_URL_ENV} instead");
135            }))
136            .with_context(|| {
137                anyhow::anyhow!("failure looking up env var for Bitcoin RPC URL")
138            })?
139            .parse()
140            .with_context(|| {
141                anyhow::anyhow!("failure parsing Bitcoin RPC URL")
142            })?,
143    })
144    }
145}
146
147pub fn parse_kv_list_from_env<K, V>(env: &str) -> anyhow::Result<BTreeMap<K, V>>
148where
149    K: FromStr + cmp::Ord,
150    <K as FromStr>::Err: std::error::Error,
151    V: FromStr,
152    <V as FromStr>::Err: std::error::Error,
153{
154    let mut map = BTreeMap::new();
155    let Ok(env_value) = std::env::var(env) else {
156        return Ok(BTreeMap::new());
157    };
158    for kv in env_value.split(',') {
159        let kv = kv.trim();
160
161        if kv.is_empty() {
162            continue;
163        }
164
165        if let Some((k, v)) = kv.split_once('=') {
166            let Some(k) = K::from_str(k)
167                .inspect_err(|err| {
168                    warn!(
169                        target: LOG_CORE,
170                        err = %err.fmt_compact(),
171                        "Error parsing value"
172                    );
173                })
174                .ok()
175            else {
176                continue;
177            };
178            let Some(v) = V::from_str(v)
179                .inspect_err(|err| {
180                    warn!(
181                        target: LOG_CORE,
182                        err = %err.fmt_compact(),
183                        "Error parsing value"
184                    );
185                })
186                .ok()
187            else {
188                continue;
189            };
190
191            map.insert(k, v);
192        }
193    }
194
195    Ok(map)
196}