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